diff options
author | gramanas <anastasis.gramm2@gmail.com> | 2023-11-26 16:29:00 +0200 |
---|---|---|
committer | gramanas <anastasis.gramm2@gmail.com> | 2023-11-26 16:29:00 +0200 |
commit | 8d17aa29baf0b33229dbdd82d8d5f6cbe3fe0240 (patch) | |
tree | bc4ec3351f8585b548ddf016c93d3715b324b64c | |
parent | c03d395f6848fe9b2d1185173a9cf5ec8277394f (diff) | |
download | synth-project-8d17aa29baf0b33229dbdd82d8d5f6cbe3fe0240.tar.gz synth-project-8d17aa29baf0b33229dbdd82d8d5f6cbe3fe0240.tar.bz2 synth-project-8d17aa29baf0b33229dbdd82d8d5f6cbe3fe0240.zip |
Many changes!
-rw-r--r-- | src/Makefile.am | 13 | ||||
-rw-r--r-- | src/midi.c | 10 | ||||
-rw-r--r-- | src/osc.h | 7 | ||||
-rw-r--r-- | src/osc_sound.c | 7 | ||||
-rw-r--r-- | src/pa_memorybarrier.h | 138 | ||||
-rw-r--r-- | src/pa_ringbuffer.c | 237 | ||||
-rw-r--r-- | src/pa_ringbuffer.h | 236 | ||||
-rw-r--r-- | src/sound.c | 2 | ||||
-rw-r--r-- | src/synth_common.h | 4 | ||||
-rw-r--r-- | src/synth_engine.c | 7 | ||||
-rw-r--r-- | src/synth_engine.h | 42 | ||||
-rw-r--r-- | src/synth_engine_v2.c | 448 | ||||
-rw-r--r-- | src/synth_gui.c | 317 | ||||
-rw-r--r-- | src/synth_gui.h | 2 |
14 files changed, 1402 insertions, 68 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index 45ac871..edec1b1 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,5 +1,5 @@ #bin_PROGRAMS = food cookbook cook synth -bin_PROGRAMS = synth gtk +bin_PROGRAMS = synth # gtk common_sources = adsr.c \ adsr.h \ @@ -22,11 +22,14 @@ common_sources = adsr.c \ osc_weird.c \ osc_sqr.c \ osc_sound.c \ + pa_ringbuffer.c \ + pa_ringbuffer.h \ + pa_memorybarrier.h \ raygui.h \ sound.c \ sound.h \ synth_common.h \ - synth_engine.c \ + synth_engine_v2.c \ synth_engine.h \ synth_gui.c \ synth_gui.h \ @@ -49,9 +52,9 @@ AM_CFLAGS = -O3 -march=native -fno-math-errno -funroll-loops -flto -pthread synth_SOURCES = synth.c $(common_sources) synth_LDADD = -lportaudio -lrt -lm -lasound -lraylib -lportmidi -ljack -lfftw3f -lsndfile -lconfig -gtk_SOURCES = gtk.c $(common_sources) $(gtk_sources) -gtk_LDADD = -lportaudio -lrt -lm -lasound -lraylib -lportmidi -ljack -lfftw3f -lsndfile -lconfig -lgtk-4 -lpangocairo-1.0 -lpango-1.0 -lharfbuzz -lgdk_pixbuf-2.0 -lcairo-gobject -lcairo -lgraphene-1.0 -lgio-2.0 -lgobject-2.0 -lglib-2.0 -gtk_CFLAGS = -I/usr/include/gtk-4.0 -I/usr/include/pango-1.0 -I/usr/include/glib-2.0 -I/usr/lib/glib-2.0/include -I/usr/include/sysprof-6 -I/usr/include/harfbuzz -I/usr/include/freetype2 -I/usr/include/libpng16 -I/usr/include/libmount -I/usr/include/blkid -I/usr/include/fribidi -I/usr/include/cairo -I/usr/include/pixman-1 -I/usr/include/gdk-pixbuf-2.0 -I/usr/include/graphene-1.0 -I/usr/lib/graphene-1.0/include -mfpmath=sse -msse -msse2 -pthread +# gtk_SOURCES = gtk.c $(common_sources) $(gtk_sources) +# gtk_LDADD = -lportaudio -lrt -lm -lasound -lraylib -lportmidi -ljack -lfftw3f -lsndfile -lconfig -lgtk-4 -lpangocairo-1.0 -lpango-1.0 -lharfbuzz -lgdk_pixbuf-2.0 -lcairo-gobject -lcairo -lgraphene-1.0 -lgio-2.0 -lgobject-2.0 -lglib-2.0 +# gtk_CFLAGS = -I/usr/include/gtk-4.0 -I/usr/include/pango-1.0 -I/usr/include/glib-2.0 -I/usr/lib/glib-2.0/include -I/usr/include/sysprof-6 -I/usr/include/harfbuzz -I/usr/include/freetype2 -I/usr/include/libpng16 -I/usr/include/libmount -I/usr/include/blkid -I/usr/include/fribidi -I/usr/include/cairo -I/usr/include/pixman-1 -I/usr/include/gdk-pixbuf-2.0 -I/usr/include/graphene-1.0 -I/usr/lib/graphene-1.0/include -mfpmath=sse -msse -msse2 -pthread #cookbook_SOURCES = cookbook.c $(common_sources) #cookbook_SOURCES = cookbook.c $(common_sources) #cook_SOURCES = cook.c $(common_sources) @@ -12,6 +12,8 @@ void midi_decode(uint32_t msg, synth_t * synth) { uint8_t channel = (status & 0x0F) + 1; // convert to human uint8_t message = status >> 4; + int flag = 1; + switch (message) { case 0x08: printf("Note Off: channel=%d, note=%d, velocity=%d\n", channel, data1, data2); @@ -28,6 +30,14 @@ void midi_decode(uint32_t msg, synth_t * synth) { synth->midi_note[data1].velocity = (float)data2 / 127.0; synth->midi_note[data1].elapsed = 0; synth->midi_note[data1].active = 1; + for (int i = 0; i < synth->midi_active_n; i++) { + if (synth->midi_active[i] == &synth->midi_note[data1]) { + flag = 0; + } + } + if (flag) { + synth->midi_active[synth->midi_active_n++] = &synth->midi_note[data1]; + } synth->active = 1; break; case 0x0A: @@ -74,6 +74,8 @@ osc_t * make_tri(const char * name); void \ set_##osc##_len(long len); \ float \ + get_##osc##_len(); \ + float \ osc_##osc(float offset); \ float \ osc_##osc##_next(float f, float offset); @@ -88,7 +90,10 @@ osc_t * make_tri(const char * name); { OSC_##osc.end = end; } \ void \ set_##osc##_len(long len) \ - { OSC_##osc.len = len; } + { OSC_##osc.len = len; } \ + float \ + get_##osc##_len() \ + { return OSC_##osc.len; } OSC_COMMON_H(tri) OSC_COMMON_H(sin) diff --git a/src/osc_sound.c b/src/osc_sound.c index e69aea2..2a0d519 100644 --- a/src/osc_sound.c +++ b/src/osc_sound.c @@ -8,7 +8,7 @@ osc_t OSC_sound = { .len = 0, .start = 0, .end = 0, - .type = SAMPLE, + .type = WAVE, }; OSC_COMMON(sound) @@ -17,7 +17,10 @@ float osc_sound(float offset) { if (!OSC_sound.len) { - osc_load_wav(&OSC_sound, "/home/gramanas/code/synth-project/waves/test_lick.wav"); +// osc_load_wav(&OSC_sound, "/home/gramanas/code/synth-project/waves/test_lick.wav"); + //osc_load_wav(&OSC_sound, "/home/gramanas/code/synth-project/waves/Free Wavetables[128]/FM Synthesis[128-44.1khz-16bit]/FM Sq- NotPM.wav"); + osc_load_wav(&OSC_sound, "/home/gramanas/code/synth-project/waves/Free Wavetables[2048]/Melda Oscillator[2048-44.1khz-32bit]/Melda CustumWave2.wav"); + OSC_sound.len = 2048; } return osc_interpolate(offset, diff --git a/src/pa_memorybarrier.h b/src/pa_memorybarrier.h new file mode 100644 index 0000000..6f07b60 --- /dev/null +++ b/src/pa_memorybarrier.h @@ -0,0 +1,138 @@ +/* + * $Id: pa_memorybarrier.h 1240 2007-07-17 13:05:07Z bjornroche $ + * Portable Audio I/O Library + * Memory barrier utilities + * + * Author: Bjorn Roche, XO Audio, LLC + * + * This program uses the PortAudio Portable Audio Library. + * For more information see: http://www.portaudio.com + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* + * The text above constitutes the entire PortAudio license; however, + * the PortAudio community also makes the following non-binding requests: + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. It is also + * requested that these non-binding requests be included along with the + * license above. + */ + +/** + @file pa_memorybarrier.h + @ingroup common_src +*/ + +/**************** + * Some memory barrier primitives based on the system. + * right now only OS X, FreeBSD, and Linux are supported. In addition to providing + * memory barriers, these functions should ensure that data cached in registers + * is written out to cache where it can be snooped by other CPUs. (ie, the volatile + * keyword should not be required) + * + * the primitives that must be defined are: + * + * PaUtil_FullMemoryBarrier() + * PaUtil_ReadMemoryBarrier() + * PaUtil_WriteMemoryBarrier() + * + ****************/ + +#if defined(__APPLE__) +/* Support for the atomic library was added in C11. + */ +# if (__STDC_VERSION__ < 201112L) || defined(__STDC_NO_ATOMICS__) +# include <libkern/OSAtomic.h> + /* Here are the memory barrier functions. Mac OS X only provides + full memory barriers, so the three types of barriers are the same, + however, these barriers are superior to compiler-based ones. + These were deprecated in MacOS 10.12. */ +# define PaUtil_FullMemoryBarrier() OSMemoryBarrier() +# define PaUtil_ReadMemoryBarrier() OSMemoryBarrier() +# define PaUtil_WriteMemoryBarrier() OSMemoryBarrier() +# else +# include <stdatomic.h> +# define PaUtil_FullMemoryBarrier() atomic_thread_fence(memory_order_seq_cst) +# define PaUtil_ReadMemoryBarrier() atomic_thread_fence(memory_order_acquire) +# define PaUtil_WriteMemoryBarrier() atomic_thread_fence(memory_order_release) +# endif +#elif defined(__GNUC__) + /* GCC >= 4.1 has built-in intrinsics. We'll use those */ +# if (__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 1) +# define PaUtil_FullMemoryBarrier() __sync_synchronize() +# define PaUtil_ReadMemoryBarrier() __sync_synchronize() +# define PaUtil_WriteMemoryBarrier() __sync_synchronize() + /* as a fallback, GCC understands volatile asm and "memory" to mean it + * should not reorder memory read/writes */ + /* Note that it is not clear that any compiler actually defines __PPC__, + * it can probably removed safely. */ +# elif defined( __ppc__ ) || defined( __powerpc__) || defined( __PPC__ ) +# define PaUtil_FullMemoryBarrier() asm volatile("sync":::"memory") +# define PaUtil_ReadMemoryBarrier() asm volatile("sync":::"memory") +# define PaUtil_WriteMemoryBarrier() asm volatile("sync":::"memory") +# elif defined( __i386__ ) || defined( __i486__ ) || defined( __i586__ ) || \ + defined( __i686__ ) || defined( __x86_64__ ) +# define PaUtil_FullMemoryBarrier() asm volatile("mfence":::"memory") +# define PaUtil_ReadMemoryBarrier() asm volatile("lfence":::"memory") +# define PaUtil_WriteMemoryBarrier() asm volatile("sfence":::"memory") +# else +# ifdef ALLOW_SMP_DANGERS +# warning Memory barriers not defined on this system or system unknown +# warning For SMP safety, you should fix this. +# define PaUtil_FullMemoryBarrier() +# define PaUtil_ReadMemoryBarrier() +# define PaUtil_WriteMemoryBarrier() +# else +# error Memory barriers are not defined on this system. You can still compile by defining ALLOW_SMP_DANGERS, but SMP safety will not be guaranteed. +# endif +# endif +#elif (_MSC_VER >= 1400) && !defined(_WIN32_WCE) +# include <intrin.h> +# pragma intrinsic(_ReadWriteBarrier) +# pragma intrinsic(_ReadBarrier) +# pragma intrinsic(_WriteBarrier) +/* note that MSVC intrinsics _ReadWriteBarrier(), _ReadBarrier(), _WriteBarrier() are just compiler barriers *not* memory barriers */ +# define PaUtil_FullMemoryBarrier() _ReadWriteBarrier() +# define PaUtil_ReadMemoryBarrier() _ReadBarrier() +# define PaUtil_WriteMemoryBarrier() _WriteBarrier() +#elif defined(_WIN32_WCE) +# define PaUtil_FullMemoryBarrier() +# define PaUtil_ReadMemoryBarrier() +# define PaUtil_WriteMemoryBarrier() +#elif defined(_MSC_VER) || defined(__BORLANDC__) +# define PaUtil_FullMemoryBarrier() _asm { lock add [esp], 0 } +# define PaUtil_ReadMemoryBarrier() _asm { lock add [esp], 0 } +# define PaUtil_WriteMemoryBarrier() _asm { lock add [esp], 0 } +#else +# ifdef ALLOW_SMP_DANGERS +# warning Memory barriers not defined on this system or system unknown +# warning For SMP safety, you should fix this. +# define PaUtil_FullMemoryBarrier() +# define PaUtil_ReadMemoryBarrier() +# define PaUtil_WriteMemoryBarrier() +# else +# error Memory barriers are not defined on this system. You can still compile by defining ALLOW_SMP_DANGERS, but SMP safety will not be guaranteed. +# endif +#endif diff --git a/src/pa_ringbuffer.c b/src/pa_ringbuffer.c new file mode 100644 index 0000000..b978d54 --- /dev/null +++ b/src/pa_ringbuffer.c @@ -0,0 +1,237 @@ +/* + * $Id$ + * Portable Audio I/O Library + * Ring Buffer utility. + * + * Author: Phil Burk, http://www.softsynth.com + * modified for SMP safety on Mac OS X by Bjorn Roche + * modified for SMP safety on Linux by Leland Lucius + * also, allowed for const where possible + * modified for multiple-byte-sized data elements by Sven Fischer + * + * Note that this is safe only for a single-thread reader and a + * single-thread writer. + * + * This program uses the PortAudio Portable Audio Library. + * For more information see: http://www.portaudio.com + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* + * The text above constitutes the entire PortAudio license; however, + * the PortAudio community also makes the following non-binding requests: + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. It is also + * requested that these non-binding requests be included along with the + * license above. + */ + +/** + @file + @ingroup common_src +*/ + +#include <stdio.h> +#include <stdlib.h> +#include <math.h> +#include "pa_ringbuffer.h" +#include <string.h> +#include "pa_memorybarrier.h" + +/*************************************************************************** + * Initialize FIFO. + * elementCount must be power of 2, returns -1 if not. + */ +ring_buffer_size_t PaUtil_InitializeRingBuffer( PaUtilRingBuffer *rbuf, ring_buffer_size_t elementSizeBytes, ring_buffer_size_t elementCount, void *dataPtr ) +{ + if( ((elementCount-1) & elementCount) != 0) return -1; /* Not Power of two. */ + rbuf->bufferSize = elementCount; + rbuf->buffer = (char *)dataPtr; + PaUtil_FlushRingBuffer( rbuf ); + rbuf->bigMask = (elementCount*2)-1; + rbuf->smallMask = (elementCount)-1; + rbuf->elementSizeBytes = elementSizeBytes; + return 0; +} + +/*************************************************************************** +** Return number of elements available for reading. */ +ring_buffer_size_t PaUtil_GetRingBufferReadAvailable( const PaUtilRingBuffer *rbuf ) +{ + return ( (rbuf->writeIndex - rbuf->readIndex) & rbuf->bigMask ); +} +/*************************************************************************** +** Return number of elements available for writing. */ +ring_buffer_size_t PaUtil_GetRingBufferWriteAvailable( const PaUtilRingBuffer *rbuf ) +{ + return ( rbuf->bufferSize - PaUtil_GetRingBufferReadAvailable(rbuf)); +} + +/*************************************************************************** +** Clear buffer. Should only be called when buffer is NOT being read or written. */ +void PaUtil_FlushRingBuffer( PaUtilRingBuffer *rbuf ) +{ + rbuf->writeIndex = rbuf->readIndex = 0; +} + +/*************************************************************************** +** Get address of region(s) to which we can write data. +** If the region is contiguous, size2 will be zero. +** If non-contiguous, size2 will be the size of second region. +** Returns room available to be written or elementCount, whichever is smaller. +*/ +ring_buffer_size_t PaUtil_GetRingBufferWriteRegions( PaUtilRingBuffer *rbuf, ring_buffer_size_t elementCount, + void **dataPtr1, ring_buffer_size_t *sizePtr1, + void **dataPtr2, ring_buffer_size_t *sizePtr2 ) +{ + ring_buffer_size_t index; + ring_buffer_size_t available = PaUtil_GetRingBufferWriteAvailable( rbuf ); + if( elementCount > available ) elementCount = available; + /* Check to see if write is not contiguous. */ + index = rbuf->writeIndex & rbuf->smallMask; + if( (index + elementCount) > rbuf->bufferSize ) + { + /* Write data in two blocks that wrap the buffer. */ + ring_buffer_size_t firstHalf = rbuf->bufferSize - index; + *dataPtr1 = &rbuf->buffer[index*rbuf->elementSizeBytes]; + *sizePtr1 = firstHalf; + *dataPtr2 = &rbuf->buffer[0]; + *sizePtr2 = elementCount - firstHalf; + } + else + { + *dataPtr1 = &rbuf->buffer[index*rbuf->elementSizeBytes]; + *sizePtr1 = elementCount; + *dataPtr2 = NULL; + *sizePtr2 = 0; + } + + if( available ) + PaUtil_FullMemoryBarrier(); /* (write-after-read) => full barrier */ + + return elementCount; +} + + +/*************************************************************************** +*/ +ring_buffer_size_t PaUtil_AdvanceRingBufferWriteIndex( PaUtilRingBuffer *rbuf, ring_buffer_size_t elementCount ) +{ + /* ensure that previous writes are seen before we update the write index + (write after write) + */ + PaUtil_WriteMemoryBarrier(); + return rbuf->writeIndex = (rbuf->writeIndex + elementCount) & rbuf->bigMask; +} + +/*************************************************************************** +** Get address of region(s) from which we can read data. +** If the region is contiguous, size2 will be zero. +** If non-contiguous, size2 will be the size of second region. +** Returns room available to be read or elementCount, whichever is smaller. +*/ +ring_buffer_size_t PaUtil_GetRingBufferReadRegions( PaUtilRingBuffer *rbuf, ring_buffer_size_t elementCount, + void **dataPtr1, ring_buffer_size_t *sizePtr1, + void **dataPtr2, ring_buffer_size_t *sizePtr2 ) +{ + ring_buffer_size_t index; + ring_buffer_size_t available = PaUtil_GetRingBufferReadAvailable( rbuf ); /* doesn't use memory barrier */ + if( elementCount > available ) elementCount = available; + /* Check to see if read is not contiguous. */ + index = rbuf->readIndex & rbuf->smallMask; + if( (index + elementCount) > rbuf->bufferSize ) + { + /* Write data in two blocks that wrap the buffer. */ + ring_buffer_size_t firstHalf = rbuf->bufferSize - index; + *dataPtr1 = &rbuf->buffer[index*rbuf->elementSizeBytes]; + *sizePtr1 = firstHalf; + *dataPtr2 = &rbuf->buffer[0]; + *sizePtr2 = elementCount - firstHalf; + } + else + { + *dataPtr1 = &rbuf->buffer[index*rbuf->elementSizeBytes]; + *sizePtr1 = elementCount; + *dataPtr2 = NULL; + *sizePtr2 = 0; + } + + if( available ) + PaUtil_ReadMemoryBarrier(); /* (read-after-read) => read barrier */ + + return elementCount; +} +/*************************************************************************** +*/ +ring_buffer_size_t PaUtil_AdvanceRingBufferReadIndex( PaUtilRingBuffer *rbuf, ring_buffer_size_t elementCount ) +{ + /* ensure that previous reads (copies out of the ring buffer) are always completed before updating (writing) the read index. + (write-after-read) => full barrier + */ + PaUtil_FullMemoryBarrier(); + return rbuf->readIndex = (rbuf->readIndex + elementCount) & rbuf->bigMask; +} + +/*************************************************************************** +** Return elements written. */ +ring_buffer_size_t PaUtil_WriteRingBuffer( PaUtilRingBuffer *rbuf, const void *data, ring_buffer_size_t elementCount ) +{ + ring_buffer_size_t size1, size2, numWritten; + void *data1, *data2; + numWritten = PaUtil_GetRingBufferWriteRegions( rbuf, elementCount, &data1, &size1, &data2, &size2 ); + if( size2 > 0 ) + { + + memcpy( data1, data, size1*rbuf->elementSizeBytes ); + data = ((char *)data) + size1*rbuf->elementSizeBytes; + memcpy( data2, data, size2*rbuf->elementSizeBytes ); + } + else + { + memcpy( data1, data, size1*rbuf->elementSizeBytes ); + } + PaUtil_AdvanceRingBufferWriteIndex( rbuf, numWritten ); + return numWritten; +} + +/*************************************************************************** +** Return elements read. */ +ring_buffer_size_t PaUtil_ReadRingBuffer( PaUtilRingBuffer *rbuf, void *data, ring_buffer_size_t elementCount ) +{ + ring_buffer_size_t size1, size2, numRead; + void *data1, *data2; + numRead = PaUtil_GetRingBufferReadRegions( rbuf, elementCount, &data1, &size1, &data2, &size2 ); + if( size2 > 0 ) + { + memcpy( data, data1, size1*rbuf->elementSizeBytes ); + data = ((char *)data) + size1*rbuf->elementSizeBytes; + memcpy( data, data2, size2*rbuf->elementSizeBytes ); + } + else + { + memcpy( data, data1, size1*rbuf->elementSizeBytes ); + } + PaUtil_AdvanceRingBufferReadIndex( rbuf, numRead ); + return numRead; +} diff --git a/src/pa_ringbuffer.h b/src/pa_ringbuffer.h new file mode 100644 index 0000000..400aaac --- /dev/null +++ b/src/pa_ringbuffer.h @@ -0,0 +1,236 @@ +#ifndef PA_RINGBUFFER_H +#define PA_RINGBUFFER_H +/* + * $Id$ + * Portable Audio I/O Library + * Ring Buffer utility. + * + * Author: Phil Burk, http://www.softsynth.com + * modified for SMP safety on OS X by Bjorn Roche. + * also allowed for const where possible. + * modified for multiple-byte-sized data elements by Sven Fischer + * + * Note that this is safe only for a single-thread reader + * and a single-thread writer. + * + * This program is distributed with the PortAudio Portable Audio Library. + * For more information see: http://www.portaudio.com + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* + * The text above constitutes the entire PortAudio license; however, + * the PortAudio community also makes the following non-binding requests: + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. It is also + * requested that these non-binding requests be included along with the + * license above. + */ + +/** @file + @ingroup common_src + @brief Single-reader single-writer lock-free ring buffer + + PaUtilRingBuffer is a ring buffer used to transport samples between + different execution contexts (threads, OS callbacks, interrupt handlers) + without requiring the use of any locks. This only works when there is + a single reader and a single writer (ie. one thread or callback writes + to the ring buffer, another thread or callback reads from it). + + The PaUtilRingBuffer structure manages a ring buffer containing N + elements, where N must be a power of two. An element may be any size + (specified in bytes). + + The memory area used to store the buffer elements must be allocated by + the client prior to calling PaUtil_InitializeRingBuffer() and must outlive + the use of the ring buffer. + + @note The ring buffer functions are not normally exposed in the PortAudio libraries. + If you want to call them then you will need to add pa_ringbuffer.c to your application source code. +*/ + +#if defined(__APPLE__) +#include <sys/types.h> +typedef int32_t ring_buffer_size_t; +#elif defined( __GNUC__ ) +typedef long ring_buffer_size_t; +#elif (_MSC_VER >= 1400) +typedef long ring_buffer_size_t; +#elif defined(_MSC_VER) || defined(__BORLANDC__) +typedef long ring_buffer_size_t; +#else +typedef long ring_buffer_size_t; +#endif + + + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + +typedef struct PaUtilRingBuffer +{ + ring_buffer_size_t bufferSize; /**< Number of elements in FIFO. Power of 2. Set by PaUtil_InitRingBuffer. */ + volatile ring_buffer_size_t writeIndex; /**< Index of next writable element. Set by PaUtil_AdvanceRingBufferWriteIndex. */ + volatile ring_buffer_size_t readIndex; /**< Index of next readable element. Set by PaUtil_AdvanceRingBufferReadIndex. */ + ring_buffer_size_t bigMask; /**< Used for wrapping indices with extra bit to distinguish full/empty. */ + ring_buffer_size_t smallMask; /**< Used for fitting indices to buffer. */ + ring_buffer_size_t elementSizeBytes; /**< Number of bytes per element. */ + char *buffer; /**< Pointer to the buffer containing the actual data. */ +}PaUtilRingBuffer; + +/** Initialize Ring Buffer to empty state ready to have elements written to it. + + @param rbuf The ring buffer. + + @param elementSizeBytes The size of a single data element in bytes. + + @param elementCount The number of elements in the buffer (must be a power of 2). + + @param dataPtr A pointer to a previously allocated area where the data + will be maintained. It must be elementCount*elementSizeBytes long. + + @return -1 if elementCount is not a power of 2, otherwise 0. +*/ +ring_buffer_size_t PaUtil_InitializeRingBuffer( PaUtilRingBuffer *rbuf, ring_buffer_size_t elementSizeBytes, ring_buffer_size_t elementCount, void *dataPtr ); + +/** Reset buffer to empty. Should only be called when buffer is NOT being read or written. + + @param rbuf The ring buffer. +*/ +void PaUtil_FlushRingBuffer( PaUtilRingBuffer *rbuf ); + +/** Retrieve the number of elements available in the ring buffer for writing. + + @param rbuf The ring buffer. + + @return The number of elements available for writing. +*/ +ring_buffer_size_t PaUtil_GetRingBufferWriteAvailable( const PaUtilRingBuffer *rbuf ); + +/** Retrieve the number of elements available in the ring buffer for reading. + + @param rbuf The ring buffer. + + @return The number of elements available for reading. +*/ +ring_buffer_size_t PaUtil_GetRingBufferReadAvailable( const PaUtilRingBuffer *rbuf ); + +/** Write data to the ring buffer. + + @param rbuf The ring buffer. + + @param data The address of new data to write to the buffer. + + @param elementCount The number of elements to be written. + + @return The number of elements written. +*/ +ring_buffer_size_t PaUtil_WriteRingBuffer( PaUtilRingBuffer *rbuf, const void *data, ring_buffer_size_t elementCount ); + +/** Read data from the ring buffer. + + @param rbuf The ring buffer. + + @param data The address where the data should be stored. + + @param elementCount The number of elements to be read. + + @return The number of elements read. +*/ +ring_buffer_size_t PaUtil_ReadRingBuffer( PaUtilRingBuffer *rbuf, void *data, ring_buffer_size_t elementCount ); + +/** Get address of region(s) to which we can write data. + + @param rbuf The ring buffer. + + @param elementCount The number of elements desired. + + @param dataPtr1 The address where the first (or only) region pointer will be + stored. + + @param sizePtr1 The address where the first (or only) region length will be + stored. + + @param dataPtr2 The address where the second region pointer will be stored if + the first region is too small to satisfy elementCount. + + @param sizePtr2 The address where the second region length will be stored if + the first region is too small to satisfy elementCount. + + @return The room available to be written or elementCount, whichever is smaller. +*/ +ring_buffer_size_t PaUtil_GetRingBufferWriteRegions( PaUtilRingBuffer *rbuf, ring_buffer_size_t elementCount, + void **dataPtr1, ring_buffer_size_t *sizePtr1, + void **dataPtr2, ring_buffer_size_t *sizePtr2 ); + +/** Advance the write index to the next location to be written. + + @param rbuf The ring buffer. + + @param elementCount The number of elements to advance. + + @return The new position. +*/ +ring_buffer_size_t PaUtil_AdvanceRingBufferWriteIndex( PaUtilRingBuffer *rbuf, ring_buffer_size_t elementCount ); + +/** Get address of region(s) from which we can read data. + + @param rbuf The ring buffer. + + @param elementCount The number of elements desired. + + @param dataPtr1 The address where the first (or only) region pointer will be + stored. + + @param sizePtr1 The address where the first (or only) region length will be + stored. + + @param dataPtr2 The address where the second region pointer will be stored if + the first region is too small to satisfy elementCount. + + @param sizePtr2 The address where the second region length will be stored if + the first region is too small to satisfy elementCount. + + @return The number of elements available for reading. +*/ +ring_buffer_size_t PaUtil_GetRingBufferReadRegions( PaUtilRingBuffer *rbuf, ring_buffer_size_t elementCount, + void **dataPtr1, ring_buffer_size_t *sizePtr1, + void **dataPtr2, ring_buffer_size_t *sizePtr2 ); + +/** Advance the read index to the next location to be read. + + @param rbuf The ring buffer. + + @param elementCount The number of elements to advance. + + @return The new position. +*/ +ring_buffer_size_t PaUtil_AdvanceRingBufferReadIndex( PaUtilRingBuffer *rbuf, ring_buffer_size_t elementCount ); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* PA_RINGBUFFER_H */ diff --git a/src/sound.c b/src/sound.c index 49de899..aaffd13 100644 --- a/src/sound.c +++ b/src/sound.c @@ -36,7 +36,7 @@ init_sound(synth_t * synth, PaStreamCallback *streamCallback) &outputParameters, SAMPLE_RATE, FRAMES_PER_BUFFER, - paClipOff, /* we won't output out of range samples so don't bother clipping them */ + paClipOff | paDitherOff, /* we won't output out of range samples so don't bother clipping them */ streamCallback, synth ); diff --git a/src/synth_common.h b/src/synth_common.h index ee10331..b9d2b90 100644 --- a/src/synth_common.h +++ b/src/synth_common.h @@ -1,8 +1,8 @@ #ifndef SYNTH_COMMON_H #define SYNTH_COMMON_H -//#define SAMPLE_RATE (44100) -#define SAMPLE_RATE (48000) +#define SAMPLE_RATE (44100) +//#define SAMPLE_RATE (48000) #define FRAMES_PER_BUFFER (256) #define VIZ_BUF 1024 diff --git a/src/synth_engine.c b/src/synth_engine.c index b47d510..15dda16 100644 --- a/src/synth_engine.c +++ b/src/synth_engine.c @@ -464,6 +464,10 @@ init_synth(synth_t * synth) CC(synth->cc_lfo_freq, "lfo_freq", 1, 1000, 2, 1); CC(synth->cc_lfo_amp, "lfo_amp", 0, 1, .01, 0); CC(synth->cc_pitch, "pitch", -3, 4, 0.01, 1); + CC(synth->cc_adsr_a, "attack", 0, 3, 0.05, 0.00); + CC(synth->cc_adsr_d, "decay", 0, 2, 0.05, 0.3); + CC(synth->cc_adsr_s, "sustain", 0, 1.0f, 0.05f, 0.7f); + CC(synth->cc_adsr_r, "release", 0, 5, 0.05f, 0.2f); synth->modi = 0; @@ -481,7 +485,7 @@ init_synth(synth_t * synth) synth->lfo.amp = 0.0f; synth->lfo.elapsed = 0; - for (int i =0; i<MIDI_NOTES; i++) { + for (int i = 0; i < MIDI_NOTES; i++) { synth->midi_note[i].freq = 0; synth->midi_note[i].channel = -1; synth->midi_note[i].noteOn = -1; @@ -529,6 +533,7 @@ init_synth(synth_t * synth) init_sound(synth, sound_gen); synth->osctri = make_tri("triangle"); + } void diff --git a/src/synth_engine.h b/src/synth_engine.h index da15fe2..d01331f 100644 --- a/src/synth_engine.h +++ b/src/synth_engine.h @@ -12,12 +12,14 @@ #include "adsr.h" #include "control.h" #include "osc.h" - +#include "pa_ringbuffer.h" #ifndef M_PI #define M_PI (3.14159265) #endif +#define RING_SIZE 65536 + typedef struct lfo_t { float freq; float amp; @@ -38,11 +40,28 @@ typedef struct midi_note_t { int active; } midi_note_t; -typedef struct viz_t { - int sample_rate_divider; - float wave[VIZ_BUF]; - int wi; -} viz_t; +typedef struct { + int spectrum_enabled; + int wave_enabled; + int adsr_enabled; + int osc_enabled; + int freeze; + int rate_divider; + + float *wave_buffer_data; + PaUtilRingBuffer wave_buffer; + + float *fft_buffer_data; + PaUtilRingBuffer fft_buffer; + + float *tmp_buffer; + float *wave_viz_buffer; + float *fft_input_buffer; + float *fft_output_buffer; + float *fft_smooth_buffer; + + int tmp_index; +} synth_viz; typedef struct { PaStream *stream; @@ -55,12 +74,18 @@ typedef struct { cc_t cc_resonance; cc_t cc_lfo_freq; cc_t cc_lfo_amp; + cc_t cc_adsr_a; + cc_t cc_adsr_d; + cc_t cc_adsr_s; + cc_t cc_adsr_r; float gain; float x; midi_note_t midi_note[MIDI_NOTES]; + midi_note_t * midi_active[MIDI_NOTES]; + int midi_active_n; adsr_t adsr; @@ -89,9 +114,8 @@ typedef struct { int active; - viz_t viz; - viz_t fftviz; - + synth_viz viz; + osc_t * osctri; } synth_t; diff --git a/src/synth_engine_v2.c b/src/synth_engine_v2.c new file mode 100644 index 0000000..726839f --- /dev/null +++ b/src/synth_engine_v2.c @@ -0,0 +1,448 @@ +#include "synth_engine.h" +#include "synth_math.h" +#include "lowpass.h" +#include "filter.h" +#include "control.h" +#include "sound.h" +#include "osc.h" + +#include <string.h> +#include <time.h> + +float +gen0(float f, midi_note_t * midi_note, float x, unsigned int sample_rate) +{ + float sample = osc_sin(midi_note->wvt_index); + midi_note->wvt_index = osc_sin_next(f, midi_note->wvt_index); + return sample; +} + +float +gen1(float f, midi_note_t * midi_note, float x, unsigned int sample_rate) +{ + float sample = osc_saw(midi_note->wvt_index); + midi_note->wvt_index = osc_saw_next(f, midi_note->wvt_index); + return sample; +} + +float +gen2(float f, midi_note_t * midi_note, float x, unsigned int sample_rate) +{ + float sample = osc_weird(midi_note->wvt_index); + midi_note->wvt_index = osc_weird_next(f, midi_note->wvt_index); + return sample; +} + +float +gen3(float f, midi_note_t * midi_note, float x, unsigned int sample_rate) +{ + float sample = osc_tri(midi_note->wvt_index); + midi_note->wvt_index = osc_tri_next(f, midi_note->wvt_index); + return sample; +} + +float +gen4(float f, midi_note_t * midi_note, float x, unsigned int sample_rate) +{ + float sample = osc_sound(midi_note->wvt_index); + midi_note->wvt_index = osc_sound_next(f, midi_note->wvt_index); + return sample; +} + +float +gen5(float f, midi_note_t * midi_note, float x, unsigned int sample_rate) +{ + float sample = osc_digisaw(midi_note->wvt_index); + midi_note->wvt_index = osc_digisaw_next(f, midi_note->wvt_index); + return sample; +} + +float +gen6(float f, midi_note_t * midi_note, float x, unsigned int sample_rate) +{ + float sample = osc_sqr(midi_note->wvt_index); + midi_note->wvt_index = osc_sqr_next(f, midi_note->wvt_index); + return sample; +} + +void deactivate_midi_note(midi_note_t * note) +{ + note->freq = 0; + note->channel = -1; + note->noteOn = -1; + note->noteOff = -1; + note->wvt_index = 0; + note->lfo_index = 0; + note->velocity = -1; + note->elapsed = -1; + note->noteOffSample = 0; + note->active = 0; +} + +int +notes_active(synth_t *synth) +{ + midi_note_t * note; + int j; + for (int i = 0; i < synth->midi_active_n; i++) { + note = synth->midi_active[i]; + if (!fix_adsr(&synth->adsr, + (float)note->noteOn, + (float)note->noteOff, + note->elapsed, + note->noteOffSample) + && note->noteOff != 0) { + deactivate_midi_note(note); + j = i + 1; + for (; j < synth->midi_active_n; j++) { + synth->midi_active[j - 1] = synth->midi_active[j]; + } + synth->midi_active[j - 1] = NULL; + synth->midi_active_n--; + } + } + + return synth->midi_active_n; +} + +#define CC_GET(name) cc_iget(&synth->cc_##name, frame, FRAMES_PER_BUFFER) + +float prev_sample = 0.0f; + +float +do_fliter(synth_t *synth, float *sample, unsigned int sample_rate, int frame) +{ + if (synth->filter) { + // ALLL THE FILTERS + float cutoff = CC_GET(cutoff); + float reso = CC_GET(resonance); + + if (cutoff == 0) cutoff = 0.001; + lpf_update(reso, cutoff, sample_rate); + *sample = lpf_filter(*sample); + + update_bw_low_pass_filter(synth->fff, SAMPLE_RATE, cutoff, reso); + *sample = bw_low_pass(synth->fff, *sample); + } +} + +float +get_max_sample(synth_t *synth, int test_size) +{ + float osc_wave[test_size]; + for (int i = 0; i < test_size; i++) { + osc_wave[i] = 0; + } + + for (int i = 0; i < synth->midi_active_n; i++) { + midi_note_t * note = synth->midi_active[i]; + midi_note_t note_dup; + note_dup.freq = note->freq; + note_dup.channel = note->channel; + note_dup.noteOn = note->noteOn; + note_dup.noteOff = note->noteOff; + note_dup.velocity = note->velocity; + note_dup.wvt_index = 0; + note_dup.lfo_index = note->lfo_index; + note_dup.elapsed = note->elapsed; + note_dup.noteOffSample = note->noteOffSample; + note_dup.adsr = note->adsr; + note_dup.active = note->active; + for (int i = 0; i < test_size; i++) { + osc_wave[i] += synth->gen[synth->geni](note_dup.freq * 3, ¬e_dup, synth->x, SAMPLE_RATE); + } + } + + float max = 0; + for (int i = 0; i < test_size; i++) { + if (fabs(osc_wave[i]) > max) max = fabs(osc_wave[i]); + } + + return max; +} + +float +make_sample(synth_t * synth, unsigned int sample_rate, int frame) +{ + float sample = 0.0f; + midi_note_t * note; + + if (synth->midi_active_n == 0) return sample; + + float rms = 0; + for (int i = 0; i < synth->midi_active_n; i++) { + rms += synth->midi_active[i]->velocity * synth->midi_active[i]->velocity; + } + + rms = sqrt(rms / (float)synth->midi_active_n); + + //float max = get_max_sample(synth, 20); + + for (int i = 0; i < synth->midi_active_n; i++) { + note = synth->midi_active[i]; + float adsr = fix_adsr(&synth->adsr, + note->noteOn, + note->noteOff, + note->elapsed, + note->noteOffSample); + + float targ_freq = note->freq * CC_GET(pitch); + targ_freq = targ_freq + targ_freq * CC_GET(lfo_amp) * osc_sin(note->lfo_index); + + note->lfo_index = osc_sin_next(CC_GET(lfo_freq), note->lfo_index); + + sample += rms * note->velocity * adsr * synth->gen[synth->geni](targ_freq, + note, + synth->x, + sample_rate); + } + + /* filter */ + do_fliter(synth, &sample, sample_rate, frame); + + sample = synth->gain * sample; + + // band stop for high freqs + //sample = bw_band_stop(synth->fff2, sample); + + if (synth->clamp) sample = clamp(sample, -1, 1); + //printf("CLICK! %f\n", fabsf(prev_sample) - fabsf(sample)); + //if (fabsf(fabsf(prev_sample) - fabsf(sample)) > 0.03) printf("CLICK! (diff: %f)\n", fabsf(prev_sample) - fabsf(sample)); + prev_sample = sample; + return sample; +} + +void +add_to_delay(synth_t *synth, float sample) +{ + synth->del[synth->deli++] = sample; + + if (synth->deli >= SAMPLE_RATE * 10) { + synth->deli = 0; + } +} + +void +increment_synth(synth_t *synth) +{ + synth->lfo.elapsed++; + synth->adsr.elapsed++; + + for (int i = 0; i < synth->midi_active_n; i++) { + if (synth->midi_active[i]) + synth->midi_active[i]->elapsed++; + } +} + +void +get_frame(void *outputBuffer, synth_t *synth, int i) +{ + float *out = (float*)outputBuffer + 2 * i; + float s = 0.0f; + + + if (!notes_active(synth)) { + synth->active = 0; + } + + if (!synth->delay) { + synth->counter = 0; + } + + s = make_sample(synth, SAMPLE_RATE, i); + synth->counter++; + if (synth->counter >= (int)(synth->del_time * SAMPLE_RATE * 10)) { + int idx = (synth->deli - (int)(synth->del_time * SAMPLE_RATE * 10)) % (SAMPLE_RATE * 10); + float tmp; + if (idx >= 0) { + tmp = synth->del[idx]; + } else { + tmp = synth->del[SAMPLE_RATE * 10 + idx]; + } + + s = clamp(s + synth->del_feedback * tmp, -1, 1); + } + + add_to_delay(synth, s); + *out++ = s; + *out++ = s; + + // move time + increment_synth(synth); + + // viz + PaUtil_WriteRingBuffer(&synth->viz.wave_buffer, &s, 1); +} + + +int +sound_gen(const void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *synthData) +{ + synth_t *synth = (synth_t*)synthData; + float *out = (float*)outputBuffer; + + float buffer[2 * FRAMES_PER_BUFFER]; + float buffer2[2 * FRAMES_PER_BUFFER]; + + (void) timeInfo; + (void) statusFlags; + (void) inputBuffer; + + clock_t begin = clock(); + + // get_changes(); + for (int i = 0; i < synth->cci; i++) { + cc_prep(synth->ccs[i]); + } + // fill buffer + for( unsigned long i=0; i<framesPerBuffer; i++ ) { + // use iget inside + get_frame(buffer, synth, i); + } + + // output buffer + for( unsigned long i=0; i<framesPerBuffer * 2; i += 2 ) { + // use iget inside + *out++ = buffer[i]; + *out++ = buffer[i+1]; + } + // finalize_changes(); + for (int i = 0; i < synth->cci; i++) { + cc_fix(synth->ccs[i]); + } + + clock_t end = clock(); + double time_spent = (double)(end - begin) / CLOCKS_PER_SEC; + if (time_spent > (double)FRAMES_PER_BUFFER / SAMPLE_RATE) { + printf("To generate %d samples per second we need to generate a sample every 1 / %d second. Since we generate samples by batching them in groups of %d, each group should take longer than %f. This one took %f\n", SAMPLE_RATE, SAMPLE_RATE, FRAMES_PER_BUFFER, (float)FRAMES_PER_BUFFER / SAMPLE_RATE, time_spent); + } + + return paContinue; +} + +void +m_init_synth(synth_t * synth) +{ + synth = (synth_t *)malloc(sizeof(synth_t)); + +} + +void +init_synth(synth_t * synth) +{ + synth->cci = 0; + CC(synth->cc_cutoff, "cutoff", 10, 22000, 30, 5000); + CC(synth->cc_resonance, "resonance", 1, 10, .02, 1); + CC(synth->cc_lfo_freq, "lfo_freq", 1, 1000, 2, 1); + CC(synth->cc_lfo_amp, "lfo_amp", 0, 1, .01f, 0); + CC(synth->cc_pitch, "pitch", -3, 4, 0.01f, 1); + CC(synth->cc_adsr_a, "attack", 0, 3, 0.05f, 0.00); + CC(synth->cc_adsr_d, "decay", 0, 2, 0.05f, 0.3); + CC(synth->cc_adsr_s, "sustain", 0, 1.0f, 0.05f, 0.7f); + CC(synth->cc_adsr_r, "release", 0, 5, 0.05f, 0.2f); + + synth->modi = 0; + + synth->gain = 0.5; + synth->x = 1; + + synth->adsr.a = 0.00001f; + synth->adsr.peak = 1.0f; + synth->adsr.d = 0.3; + synth->adsr.s = 0.7; + synth->adsr.r = 0.4; + synth->adsr.elapsed = 0; + + synth->lfo.freq = 1.0f; + synth->lfo.amp = 0.0f; + synth->lfo.elapsed = 0; + + synth->midi_active_n = 0; + for (int i = 0; i < MIDI_NOTES; i++) { + synth->midi_active[i] = NULL; + synth->midi_note[i].freq = 0; + synth->midi_note[i].channel = -1; + synth->midi_note[i].noteOn = -1; + synth->midi_note[i].noteOff = -1; + synth->midi_note[i].velocity = -1; + synth->midi_note[i].elapsed = -1; + synth->midi_note[i].active = 0; + synth->midi_note[i].wvt_index = 0; + synth->midi_note[i].lfo_index = 0; + synth->midi_note[i].adsr = &(synth->adsr); + } + + synth->octave = 3; + + synth->delay = 0; + synth->del[SAMPLE_RATE * 10]; + synth->deli = 0; + synth->del_time = .1; + synth->del_feedback = 0.5f; + synth->counter; + + synth->filter = 1; + synth->clamp = 1; + + synth->gen[0] = gen0; + synth->gen[1] = gen1; + synth->gen[2] = gen2; + synth->gen[3] = gen3; + synth->gen[4] = gen4; + synth->gen[5] = gen5; + synth->gen[6] = gen6; + synth->geni = 3; + + synth->active = 0; + + lpf_init(); + synth->fff = create_bw_low_pass_filter(2, SAMPLE_RATE, 400); + synth->fff2 = create_bw_band_stop_filter(8, SAMPLE_RATE, 15000, 22000); + + init_sound(synth, sound_gen); + + synth->osctri = make_tri("triangle"); + + synth->viz.rate_divider = 15; +// for (int i = 0; i < RING_SIZE; i++) synth->viz.wave_buffer_data[i] = 0; + synth->viz.wave_buffer_data = (float *)calloc(sizeof(float), RING_SIZE); + PaUtil_InitializeRingBuffer(&synth->viz.wave_buffer, sizeof(float), RING_SIZE, synth->viz.wave_buffer_data); +// for (int i = 0; i < RING_SIZE; i++) synth->viz.fft_buffer_data[i] = 0; + synth->viz.fft_buffer_data = (float *)calloc(sizeof(float), RING_SIZE); + PaUtil_InitializeRingBuffer(&synth->viz.fft_buffer, sizeof(float), RING_SIZE, synth->viz.fft_buffer_data); + + synth->viz.tmp_buffer = (float *)calloc(sizeof(float), RING_SIZE * 8); + synth->viz.wave_viz_buffer = (float *)calloc(sizeof(float), RING_SIZE * 8); + synth->viz.fft_input_buffer = (float *)calloc(sizeof(float), RING_SIZE * 8); + synth->viz.fft_output_buffer = (float *)calloc(sizeof(float), RING_SIZE * 8); + synth->viz.fft_smooth_buffer = (float *)calloc(sizeof(float), RING_SIZE * 8); + + synth->viz.spectrum_enabled = 1; + synth->viz.wave_enabled = 0; + synth->viz.adsr_enabled = 0; + synth->viz.osc_enabled = 0; + synth->viz.freeze = 0; + synth->viz.tmp_index = 0; +} + +void +free_synth(synth_t * synth) +{ + destroy_sound(synth); + + free(synth->viz.wave_buffer_data); + free(synth->viz.fft_buffer_data); + free(synth->viz.tmp_buffer); + free(synth->viz.wave_viz_buffer); + free(synth->viz.fft_input_buffer); + free(synth->viz.fft_output_buffer); + free(synth->viz.fft_smooth_buffer); + + //free(synth->ring_data); + free_bw_low_pass(synth->fff); + free_bw_band_stop(synth->fff2); +} diff --git a/src/synth_gui.c b/src/synth_gui.c index c210f2b..bb394c7 100644 --- a/src/synth_gui.c +++ b/src/synth_gui.c @@ -26,9 +26,8 @@ draw_text(synth_t * synth, Font f, int x, int y) } void -mouse(void *synthData, PaStream *stream) +mouse(synth_t *synth, PaStream *stream) { - synth_t * synth = (synth_t *)synthData; float m = GetMouseWheelMove(); int x = 0; if (m < 0) x = -1; @@ -51,10 +50,8 @@ mouse(void *synthData, PaStream *stream) } void -keyboard(void *synthData, PaStream *stream) +keyboard(synth_t * synth, PaStream *stream) { - synth_t * synth = (synth_t *)synthData; - int keys[] = {KEY_Q, KEY_TWO, KEY_W, KEY_THREE, KEY_E, KEY_R, KEY_FIVE, KEY_T, KEY_SIX, KEY_Y, KEY_SEVEN, KEY_U, KEY_I, KEY_NINE, KEY_O, KEY_ZERO, KEY_P, KEY_LEFT_BRACKET, KEY_EQUAL, KEY_RIGHT_BRACKET, 0}; float note; @@ -70,7 +67,16 @@ keyboard(void *synthData, PaStream *stream) synth->midi_note[i].velocity = 1.0; synth->midi_note[i].elapsed = 0; synth->midi_note[i].active = 1; - + int flag = 1; + for (int j = 0; j < synth->midi_active_n; j++) { + if (synth->midi_active[j] == &synth->midi_note[i]) { + flag = 0; + } + } + if (flag) { + synth->midi_active[synth->midi_active_n++] = &synth->midi_note[i]; + } + //synth->adsr.elapsed = 0; synth->active = 1; } @@ -171,66 +177,281 @@ draw_bars(synth_t * synth, int x, int y, int width, int height, int offset) snprintf(buf, sizeof buf, "%.3f", synth->del_feedback); synth->del_feedback = GuiSlider((Rectangle){ x, y + count++ * (height + offset), width, height }, "fb: ", buf, synth->del_feedback , 0.0f, 1.0f); snprintf(buf, sizeof buf, "%.3f", synth->del_time); - synth->del_time = GuiSlider((Rectangle){ x, y + count++ * (height + offset), width, height }, "del_time: ", buf, synth->del_time , 0.0f, 0.1f); + synth->del_time = GuiSlider((Rectangle){ x, y + count++ * (height + offset), width, height }, "del_time: ", buf, synth->del_time , 0.0f, 0.3f); +} + +void frequencyToColor(float frequency, int *red, int *green, int *blue) { + // Assuming frequency ranges from 20 to 20000 Hz + float minFrequency = 20.0; + float maxFrequency = 20000.0; + + // Map the frequency to a value between 0 and 1 + float normalizedFrequency = (frequency - minFrequency) / (maxFrequency - minFrequency); + + // Define colors for the rainbow spectrum + float hue = normalizedFrequency * 360.0; + + // Convert HSV to RGB + float c = 1.0; + float x = c * (1.0 - fabs(fmod(hue / 60.0, 2.0) - 1.0)); + float m = 0.0; + + float r, g, b; + + if (hue >= 0 && hue < 60) { + r = c; + g = x; + b = 0; + } else if (hue >= 60 && hue < 120) { + r = x; + g = c; + b = 0; + } else if (hue >= 120 && hue < 180) { + r = 0; + g = c; + b = x; + } else if (hue >= 180 && hue < 240) { + r = 0; + g = x; + b = c; + } else if (hue >= 240 && hue < 300) { + r = x; + g = 0; + b = c; + } else { + r = c; + g = 0; + b = x; + } + + *red = (int)((r + m) * 255); + *green = (int)((g + m) * 255); + *blue = (int)((b + m) * 255); +} + +#include "fftw3.h" + +float +amp(float re, float im) { + float a = fabsf(re); + float b = fabsf(im); + if (a < b) return b; + return a; } void -draw_signals(synth_t * synth, int x, int y, int width, int height) +draw_adsr(synth_t *synth, int x, int y, int width, int height) { - DrawRectangleLines(x, y, width, height, BLACK); + int x_prev = x; + for (int i = 0; i < synth->midi_active_n; i++) { + int rec_y = y + height - 1 + + 4.5 * floor( (WIDTH / 24) * + - fix_adsr(&synth->adsr, + synth->midi_active[i]->noteOn, + synth->midi_active[i]->noteOff, + synth->midi_active[i]->elapsed, + synth->midi_active[i]->noteOffSample)); + + int rec_width = (WIDTH - x - x) / synth->midi_active_n; + int rec_height = HEIGHT - rec_y; - int point_radius = 1.5; + int red, green, blue; + frequencyToColor(synth->midi_active[i]->freq * 25, &red, &green, &blue); + DrawRectangleGradientV(x_prev, rec_y - 10, rec_width, rec_height, ColorAlpha(BLUE, .2) ,ColorAlpha((Color) {red, green, blue}, .2)); + //DrawRectangleGradientV(x_prev, rec_y - 10, rec_width, rec_height, ColorAlpha(GREEN, .2) ,ColorAlpha(RED, .2)); + //DrawRectangleLines(x_prev, rec_y - 10, rec_width, rec_height, GRAY); + x_prev += rec_width; + } +} - //GuiSpinner((Rectangle){ x+ 100, y - 24 - 6, 100, 24 }, "rate divider: ", &(synth->viz.sample_rate_divider), 1, 10, 0); +int freeze; - for (int i = x; i < WIDTH - x; i++) { - if (synth->viz.wave[i - x] > 1 || synth->viz.wave[i - x] < -1) - DrawCircle(i , y + height / 2 + floor(50 * synth->viz.wave[i - x]), point_radius, RED); - else if (synth->viz.wave[i - x] >= 0.99 || synth->viz.wave[i - x] <= -0.99) - DrawCircle(i , y + height / 2 + floor(50 * synth->viz.wave[i - x]), point_radius, MAGENTA); - else - DrawCircle(i , y + height / 2 + floor(50 * synth->viz.wave[i - x]), point_radius, RAYWHITE); - } - for (int i = x; i < WIDTH - x; i++) { - DrawCircle(i , y + height / 2 + floor(5 * synth->fftviz.wave[i - x]) * -1, point_radius, RED); +void +draw_wave(synth_t *synth, int x, int y, int width, int height) +{ + Color col; + for (int i = 0; i < width; i++) { + for (int j = 0; j < synth->viz.rate_divider; j++) { + int ii = synth->viz.rate_divider * (i) + j; + if (synth->viz.wave_viz_buffer[ii] > 1 || synth->viz.wave_viz_buffer[ii] < -1) + col = RED; + else if (synth->viz.wave_viz_buffer[ii] >= 0.99 || synth->viz.wave_viz_buffer[ii] <= -0.99) + col = MAGENTA; + else + col = WHITE; + DrawPixel(i + x , y + height / 2 + floor(50 * synth->viz.wave_viz_buffer[ii + j]), col); + } } + /* for (int j = 0; j < 100; j++) { */ + /* DrawPixel(synth->viz.tmp_index / synth->viz.rate_divider + 20, j + y + height / 2, BLUE); */ + /* DrawPixel(synth->viz.tmp_index / synth->viz.rate_divider + 20, -j + y + height / 2, BLUE); */ + /* } */ + //EndScissorMode(); +} + +void +draw_fft(synth_t *synth, int x, int y, int width, int height) +{ + int viz_size = width * synth->viz.rate_divider; + int fft_output_len = viz_size / 2 + 1; + + fftwf_complex* output = (fftwf_complex*)fftwf_malloc(sizeof(fftwf_complex) * fft_output_len); + + fftwf_plan forward_plan = fftwf_plan_dft_r2c_1d(fft_output_len, synth->viz.fft_input_buffer, output, FFTW_ESTIMATE); + + fftwf_execute(forward_plan); + + fftwf_destroy_plan(forward_plan); + + // "Squash" into the Logarithmic Scale + float step = 1.06; + float lowf = 1.0f; + size_t m = 0; + float max_amp_tso = 1.0f; + for (float f = lowf; (size_t) f < fft_output_len/2; f = ceilf(f*step)) { + float f1 = ceilf(f*step); + float a = 0.0f; + for (size_t q = (size_t) f; q < fft_output_len/2 && q < (size_t) f1; ++q) { + float b = log(sqrt(output[q][0]*output[q][0] + output[q][1]*output[q][1])); + if (b > a) a = b; + } + if (max_amp_tso < a) max_amp_tso = a; + synth->viz.fft_output_buffer[m++] = a; + } + + // Normalize Frequencies to 0..1 range + for (size_t i = 0; i < m; ++i) { + synth->viz.fft_output_buffer[i] /= max_amp_tso; + } + + // Smooth out and smear the values + for (size_t i = 0; i < m; ++i) { + float smoothness = 4; + synth->viz.fft_smooth_buffer[i] += (synth->viz.fft_output_buffer[i] - synth->viz.fft_smooth_buffer[i])*smoothness*((float)1/30); + /* float smearness = 3; */ + /* ss_smear[i] += (synth->viz.fft_smooth_buffer[i] - ss_smear[i])*smearness*((float)1/30); */ + } + // Display the Bars + float cell_width = (float)width/m; + for (size_t i = 0; i < m; ++i) { + float t = synth->viz.fft_smooth_buffer[i]; + float saturation = 0.75f; + float value = 1.0f; + float hue = (float)i/m; + Color color = ColorFromHSV(hue*360, saturation, value); + Vector2 startPos = { + x + i*cell_width + cell_width/2, + y + height - height*2/3*t, + }; + Vector2 endPos = { + x + i*cell_width + cell_width/2, + y + height, + }; + float thick = cell_width/3*sqrtf(t); + DrawLineEx(startPos, endPos, thick, BLUE); + } - int c = 0; - for (int i = 0; i < MIDI_NOTES; i++) { - if (!synth->midi_note[i].active) continue; - c++; + fftwf_free(output); +} + +void +draw_osc(synth_t * synth, int x, int y, int width, int height) +{ + DrawRectangleLines(x - 3, y - 3, width + 6, height + 6, GREEN); + float osc_wave[width]; + for (int i = 0; i < width; i++) { + osc_wave[i] = 0; } - int x_prev = x; - for (int i = 0; i < MIDI_NOTES; i++) { - if (!synth->midi_note[i].active) - continue; + for (int i = 0; i < synth->midi_active_n; i++) { + midi_note_t * note = synth->midi_active[i]; + midi_note_t note_dup; + note_dup.freq = note->freq; + note_dup.channel = note->channel; + note_dup.noteOn = note->noteOn; + note_dup.noteOff = note->noteOff; + note_dup.velocity = note->velocity; + note_dup.wvt_index = synth->geni == 4 ? note->wvt_index : 0; + note_dup.lfo_index = note->lfo_index; + note_dup.elapsed = note->elapsed; + note_dup.noteOffSample = note->noteOffSample; + note_dup.adsr = note->adsr; + note_dup.active = note->active; + for (int i = 0; i < width; i++) { + float freq = SAMPLE_RATE/(float)width; + osc_wave[i] += synth->gen[synth->geni](freq, ¬e_dup, synth->x, SAMPLE_RATE); + } + break; + } - int rec_y = y + height - 1 + - 4.5 * floor( (WIDTH / 24) * - - fix_adsr(&synth->adsr, - synth->midi_note[i].noteOn, - synth->midi_note[i].noteOff, - synth->midi_note[i].elapsed, - synth->midi_note[i].noteOffSample)); - - int rec_width = (WIDTH - x - x) / c; - int rec_height = HEIGHT - rec_y; - - //DrawLine(x_prev, v, x_prev + (WIDTH - x) / c, v , WHITE); - DrawRectangleGradientV(x_prev, rec_y, rec_width, rec_height, ColorAlpha(GREEN, .2) ,ColorAlpha(RED, .2)); - DrawRectangleLines(x_prev, rec_y, rec_width, rec_height, GRAY); - x_prev += rec_width; + float max = 0; + for (int i = 0; i < width; i++) { + if (fabs(osc_wave[i]) > max) max = fabs(osc_wave[i]); } + + if (synth->midi_active_n) { + for (int i = 0; i < width; i++) { + DrawPixel(i + x , y + height / 2 + floor(height/2 * osc_wave[i] / max), RED); + } + } + } +void +draw_signals(synth_t * synth, int x, int y, int width, int height) +{ + DrawRectangleLines(x, y, width, height, WHITE); + + if (synth->viz.wave_enabled || synth->viz.spectrum_enabled) + synth->viz.rate_divider = GuiSlider((Rectangle){ x + (width / 2) / 2, y - 12, width / 2, 12 }, "", NULL, synth->viz.rate_divider , 1, 150); + + int viz_size = width * synth->viz.rate_divider; + + float samples[RING_SIZE]; + int rc = PaUtil_ReadRingBuffer( &synth->viz.wave_buffer, &samples, RING_SIZE); + + synth->viz.freeze = GuiCheckBox((Rectangle){ x + width - 16, y, 16, 16 }, "", synth->viz.freeze); + if (!synth->viz.freeze) { + for (int i = 0; i < rc; i++) { + synth->viz.fft_input_buffer[synth->viz.tmp_index] = samples[i]; + synth->viz.tmp_buffer[synth->viz.tmp_index++] = samples[i]; + if (synth->viz.tmp_index >= viz_size) { + synth->viz.tmp_index = 0; + } + } + for (int i = synth->viz.tmp_index; i < viz_size + synth->viz.tmp_index; i++) { + synth->viz.wave_viz_buffer[i - synth->viz.tmp_index] = synth->viz.tmp_buffer[i % (viz_size)]; + } + } else { + } + + synth->viz.spectrum_enabled = GuiCheckBox((Rectangle){ x, y, 16, 16 }, "spectrum", synth->viz.spectrum_enabled); + if (synth->viz.spectrum_enabled) { + draw_fft(synth, x, y, width, height); + } + + synth->viz.wave_enabled = GuiCheckBox((Rectangle){ x + 100, y, 16, 16 }, "wave", synth->viz.wave_enabled); + if (synth->viz.wave_enabled) { + draw_wave(synth, x, y, width, height); + } + + synth->viz.adsr_enabled = GuiCheckBox((Rectangle){ x + 170, y, 16, 16 }, "adsr", synth->viz.adsr_enabled); + if (synth->viz.adsr_enabled) { + draw_adsr(synth, x, y, width, height); + } + + synth->viz.osc_enabled = GuiCheckBox((Rectangle){ x + width - 170, y, 16, 16 }, "osc", synth->viz.osc_enabled); + if (synth->viz.osc_enabled) { + draw_osc(synth, x + width - 170 - 40, y + 80, 80, 80); + } +} void -rayrun(void *synthData){ - synth_t * synth = (synth_t *)synthData; +rayrun(synth_t *synth){ PaTime current_time = 0; PaTime prev_time = 0; + int len; InitWindow(WIDTH, HEIGHT, "Raylib synth"); Font f = LoadFont("/usr/share/fonts/TTF/DejaVuSans.ttf"); SetTargetFPS(60); // Set our game to run at 60 frames-per-second @@ -248,6 +469,9 @@ rayrun(void *synthData){ // GUI char buf[64]; + snprintf(buf, sizeof buf, "%d", len); + len = GuiSlider((Rectangle){WIDTH / 2 - 108, 150 + 42 + 42, 216, 24 }, "", buf, len , 0, 99); + draw_bars(synth, 33, 20, 256, 20, 4); draw_text(synth, f, WIDTH / 2 - 108, 20); @@ -268,6 +492,7 @@ rayrun(void *synthData){ // signals draw_signals(synth, 20, 390, WIDTH - 2*20, 200); + //draw_signals(synth, 300, 390, WIDTH - 2*300, 200); //DrawText("THE SYNTH!!!!!!!!!!!!!!!!!!1", WIDTH / 2 - 100, 50, 20, LIGHTGRAY); /* DrawText("KEYBOARD: Q .. ]", WIDTH / 2 -300, HEIGHT - 300 - 50, 20, LIGHTGRAY); */ diff --git a/src/synth_gui.h b/src/synth_gui.h index d08a7f4..9870e53 100644 --- a/src/synth_gui.h +++ b/src/synth_gui.h @@ -5,6 +5,6 @@ #include "synth_engine.h" -void rayrun(void *synthData); +void rayrun(synth_t *synth); #endif /* SYNTH_GUI_H */ |