diff options
Diffstat (limited to 'src')
| -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 */  | 
