summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorgramanas <anastasis.gramm2@gmail.com>2023-11-26 16:29:00 +0200
committergramanas <anastasis.gramm2@gmail.com>2023-11-26 16:29:00 +0200
commit8d17aa29baf0b33229dbdd82d8d5f6cbe3fe0240 (patch)
treebc4ec3351f8585b548ddf016c93d3715b324b64c
parentc03d395f6848fe9b2d1185173a9cf5ec8277394f (diff)
downloadsynth-project-8d17aa29baf0b33229dbdd82d8d5f6cbe3fe0240.tar.gz
synth-project-8d17aa29baf0b33229dbdd82d8d5f6cbe3fe0240.tar.bz2
synth-project-8d17aa29baf0b33229dbdd82d8d5f6cbe3fe0240.zip
Many changes!
-rw-r--r--src/Makefile.am13
-rw-r--r--src/midi.c10
-rw-r--r--src/osc.h7
-rw-r--r--src/osc_sound.c7
-rw-r--r--src/pa_memorybarrier.h138
-rw-r--r--src/pa_ringbuffer.c237
-rw-r--r--src/pa_ringbuffer.h236
-rw-r--r--src/sound.c2
-rw-r--r--src/synth_common.h4
-rw-r--r--src/synth_engine.c7
-rw-r--r--src/synth_engine.h42
-rw-r--r--src/synth_engine_v2.c448
-rw-r--r--src/synth_gui.c317
-rw-r--r--src/synth_gui.h2
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)
diff --git a/src/midi.c b/src/midi.c
index e711ecb..fbea43f 100644
--- a/src/midi.c
+++ b/src/midi.c
@@ -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:
diff --git a/src/osc.h b/src/osc.h
index 3a48bb5..8b6fdea 100644
--- a/src/osc.h
+++ b/src/osc.h
@@ -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, &note_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, &note_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 */