aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorRichard <q@1bpm.net>2025-03-08 14:53:52 +0000
committerRichard <q@1bpm.net>2025-03-08 14:53:52 +0000
commitd8baa01ff91521e113260ef5d5cae272e02162e2 (patch)
tree6b118c71c308d29e517bda60bfbd69f7c4f39cbb /src
downloadlibguttersynth-d8baa01ff91521e113260ef5d5cae272e02162e2.tar.gz
libguttersynth-d8baa01ff91521e113260ef5d5cae272e02162e2.tar.bz2
libguttersynth-d8baa01ff91521e113260ef5d5cae272e02162e2.zip
initial
Diffstat (limited to 'src')
-rw-r--r--src/guttersynth.c476
1 files changed, 476 insertions, 0 deletions
diff --git a/src/guttersynth.c b/src/guttersynth.c
new file mode 100644
index 0000000..7e1607a
--- /dev/null
+++ b/src/guttersynth.c
@@ -0,0 +1,476 @@
+/*
+ * guttersynth.c
+ * Part of libguttersynth
+ *
+ * Copyright Tom Mudd 2019, Richard Knight 2021, 2022
+ *
+ * Ported to C by Richard Knight
+ * from https://github.com/tommmmudd/guttersynthesis by Tom Mudd
+ *
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include <math.h>
+#include <sys/param.h>
+#include "guttersynth.h"
+
+
+// TODO: freqs temp and Q temp, remove, not needed..
+//
+
+/*
+ * Returns a random float within a range
+ *
+ * min: bottom end of range
+ * max: top end of range
+ *
+ * returns: random float
+ */
+static FLT rangerandom(FLT min, FLT max)
+{
+ FLT random = ((FLT) rand()) / (FLT) RAND_MAX;
+ FLT diff = max - min;
+ FLT r = random * diff;
+ return min + r;
+}
+
+
+/*
+ * Calculate filter coefficients
+ *
+ * s: gutter_state struct to apply operation to
+ */
+void gutter_calccoeffs(gutter_state* s)
+{
+ int i, b, f;
+ for (b = 0; b < s->bankCount; b++) {
+ for (f = 0; f < s->filterCount; f++) {
+ i = b * s->bankCount + f;
+ s->gi.V[i] = (FLT) pow(10.0, 0.05);
+ s->gi.K[i] = (FLT) tan(((FLT)M_PI * s->filterFreqs[i]) / s->gi.samplerate);
+ s->gi.norm[i] = (1.0 / (1.0 + s->gi.K[i] / s->Q[i] + s->gi.K[i] * s->gi.K[i]));
+ s->gi.a0[i] = s->gi.K[i] / s->Q[i] * s->gi.norm[i];
+ s->gi.a1[i] = 0.0;
+ s->gi.a2[i] = - s->gi.a0[i];
+ s->gi.b1[i] = 2.0 * (s->gi.K[i] * s->gi.K[i] - 1.0) * s->gi.norm[i];
+ s->gi.b2[i] = (1.0 - s->gi.K[i] / s->Q[i] + s->gi.K[i] * s->gi.K[i]) *s->gi.norm[i];
+ }
+ }
+}
+
+
+/*
+ * Apply distortion
+ */
+static void gutter_distortion(gutter_state* s, FLT* audio_ptr)
+{
+ FLT input = *audio_ptr;
+ FLT output;
+ switch (s->distortionMethod) {
+ case 0:
+ output = MAX(MIN(input, 1), -1);
+ break;
+ case 1:
+ if (s->gi.finalY <= -1) {
+ output = -0.666666667;
+ } else if (input <= 1) {
+ output = input - input * input * input / 3.0;
+ } else {
+ output = 0.666666667;
+ }
+ break;
+ case 2:
+ output = (FLT) atan(input);
+ break;
+ case 3:
+ output = 0.75 * (((FLT)sqrt(input * 1.3 * (input * 1.3) + 1.0)) * 1.65 - 1.65) / input;
+ break;
+ case 4:
+ output = (0.1076 * input * input * input + 3.029 * input) / (input * input + 3.124);
+ break;
+ case 5:
+ output = 2.0 / (1.0 + ((FLT)exp(-1.0 * input)));
+ break;
+ default:
+ output = input;
+ }
+ *audio_ptr = output;
+}
+
+
+/*
+ * Randomise filter bank frequencies
+ *
+ * s: gutter_state struct to apply operation to
+ */
+void gutter_randomisefilters(gutter_state* s)
+{
+ int i, b, f;
+ for (b = 0; b < s->bankCount; b++) {
+ for (f = 0; f < s->filterCount; f++) {
+ i = b * s->bankCount + f;
+ s->filterFreqs[i] = rangerandom(100, 4000);
+ s->gains[i] = rangerandom(0, 1);
+ s->Q[i] = rangerandom(20, 200);
+ }
+ }
+ gutter_calccoeffs(s);
+}
+
+
+int gutter_setdistortionmethod(gutter_state* s, int method)
+{
+ if (method < 0 || method > 5) {
+ return -1;
+ }
+ s->distortionMethod = method;
+ return 0;
+}
+
+/*
+ * Set frequency of a filter in a bank
+ *
+ * s: gutter_state struct to apply operation to
+ * bank: bank index
+ * filter: filter index
+ * value: frequency to set
+ *
+ * returns: 0 on success, -1 if bank or filter is out of range
+ */
+int gutter_setfreq(gutter_state* s, int bank, int filter, FLT value)
+{
+ if (bank >= s->bankCount || filter >= s->filterCount || bank < 0 || filter < 0) {
+ return -1;
+ }
+ s->filterFreqs[bank*s->bankCount+filter] = value;
+ return 0;
+}
+
+
+/*
+ * Set Q of a filter in a bank
+ *
+ * s: gutter_state struct to apply operation to
+ * bank: bank index
+ * filter: filter index
+ * value: Q to set
+ *
+ * returns: 0 on success, -1 if bank or filter is out of range
+ */
+int gutter_setq(gutter_state* s, int bank, int filter, FLT value)
+{
+ if (bank >= s->bankCount || filter >= s->filterCount || bank < 0 || filter < 0) {
+ return -1;
+ }
+ s->Q[bank*s->bankCount+filter] = value;
+ return 0;
+}
+
+
+/*
+ * Set gain of a filter in a bank
+ *
+ * s: gutter_state struct to apply operation to
+ * bank: bank index
+ * filter: filter index
+ * value: gain to set
+ *
+ * returns: 0 on success, -1 if bank or filter is out of range
+ */
+int gutter_setgain(gutter_state* s, int bank, int filter, FLT value)
+{
+ if (bank >= s->bankCount || filter >= s->filterCount || bank < 0 || filter < 0) {
+ return -1;
+ }
+ s->gains[bank*s->bankCount+filter] = value;
+ return 0;
+}
+
+
+/*
+ * Set the Q of all filters in all banks
+ *
+ * s: gutter_state struct to apply operation to
+ * value: Q to set
+ */
+void gutter_setqall(gutter_state* s, FLT value)
+{
+ int i;
+ for (i = 0; i < s->bankCount * s->filterCount; i++) {
+ s->Q[i] = value;
+ }
+}
+
+int gutter_setqbank(gutter_state* s, int bank, FLT value)
+{
+ if (bank < 0 || bank <= s->bankCount) {
+ return -1;
+ }
+ int f;
+ for (f = 0; f < s->filterCount; f++) {
+ s->Q[bank * s->bankCount + f] = value;
+ }
+}
+
+/*
+ * Initialise with custom memory allocator and deallocator
+ *
+ * bankCount: number of banks (typically 1 or 2)
+ * filterCount: number of filters (typically up to 24)
+ * samplerate: samplerate to calculate audio at
+ * allocator: memory allocator function taking a size_t and returning void*
+ * deallocator: memory deallocator function taking a void*
+ *
+ * returns: gutter_state struct to be used in the synthesis session
+ */
+gutter_state* gutter_init_ca(int bankCount, int filterCount, int samplerate,
+ void* (*allocator)(size_t), void (*deallocator)(void*))
+{
+ int size2d, i, f, b;
+
+ srand(time(0));
+
+ gutter_state* s = (gutter_state*) allocator(sizeof(gutter_state));
+
+ s->gi.samplerate = samplerate;
+ s->bankCount = bankCount;
+ s->filterCount = filterCount;
+ s->gi.dealloc = deallocator;
+
+
+ s->gi.dcblock.xm1 = 0;
+ s->gi.dcblock.ym1 = 0;
+ s->gi.dcblock.r = 0.995;
+
+
+ size2d = sizeof(FLT) * bankCount * filterCount;
+ s->gains = (FLT*) allocator(size2d);
+ s->Q = (FLT*) allocator(size2d);
+ s->filterFreqs = (FLT*) allocator(size2d);
+ s->gi.prevX1 = (FLT*) allocator(size2d);
+ s->gi.prevX2 = (FLT*) allocator(size2d);
+ s->gi.prevY1 = (FLT*) allocator(size2d);
+ s->gi.prevY2 = (FLT*) allocator(size2d);
+ s->gi.V = (FLT*) allocator(size2d);
+ s->gi.K = (FLT*) allocator(size2d);
+ s->gi.norm = (FLT*) allocator(size2d);
+ s->gi.a0 = (FLT*) allocator(size2d);
+ s->gi.a1 = (FLT*) allocator(size2d);
+ s->gi.a2 = (FLT*) allocator(size2d);
+ s->gi.b1 = (FLT*) allocator(size2d);
+ s->gi.b2 = (FLT*) allocator(size2d);
+ s->gi.y = (FLT*) allocator(size2d);
+
+ s->filtersOn = 1;
+ s->smoothing = 1;
+ s->distortionMethod = 2;
+ s->enableAudioInput = 0;
+
+ for (b = 0; b < bankCount; b++) {
+ for (f = 0; f < filterCount; f++) {
+ i = b * bankCount + f;
+ FLT freq = ((FLT)(f + 1)) / 2.0 * 20.0 * (b + 1) * 1.2 + 80;
+ s->filterFreqs[i] = freq;
+ s->gi.y[i] = 0;
+ s->gi.prevX1[i] = 0;
+ s->gi.prevX2[i] = 0;
+ s->gi.prevY1[i] = 0;
+ s->gi.prevY2[i] = 0;
+ s->gains[i] = 1;
+ s->Q[i] = 1;
+ }
+ }
+
+ gutter_randomisefilters(s);
+
+ return s;
+}
+
+
+/*
+ * Initialise with default memory allocator and deallocator (malloc and free)
+ *
+ * bankCount: number of banks (typically 1 or 2)
+ * filterCount: number of filters (typically up to 24)
+ * samplerate: samplerate to calculate audio at
+ *
+ * returns: gutter_state struct to be used in the synthesis session
+ */
+gutter_state* gutter_init(int bankCount, int filterCount, int samplerate)
+{
+ return gutter_init_ca(bankCount, filterCount, samplerate, &malloc, &free);
+}
+
+
+/*
+ * Clean up state: free memory
+ *
+ * s: gutter_state struct to apply operation to
+ */
+void gutter_cleanup(gutter_state* s)
+{
+ void (*da)(void*) = s->gi.dealloc;
+
+ da(s->gains);
+ da(s->filterFreqs);
+ da(s->gi.prevX1);
+ da(s->gi.prevX2);
+ da(s->gi.prevY1);
+ da(s->gi.prevY2);
+ da(s->gi.V);
+ da(s->gi.K);
+ da(s->gi.norm);
+ da(s->gi.a0);
+ da(s->gi.a1);
+ da(s->gi.a2);
+ da(s->gi.b1);
+ da(s->gi.b2);
+ da(s->gi.y);
+ da(s->Q);
+ da(s);
+}
+
+
+/*
+ * Reset oscillator
+ *
+ * s: gutter_state struct to apply operation to
+ */
+void gutter_reset(gutter_state* s)
+{
+ s->gi.duffX = 0;
+ s->gi.duffY = 0;
+ s->gi.dx = 0;
+ s->gi.dy = 0;
+ s->gi.t = 0;
+}
+
+
+/*
+ * Apply DC blocking to audio referencing current state
+ *
+ * s: gutter_state struct to apply operation to
+ * input: audio to apply DC block to
+ *
+ * returns: DC blocked audio
+ */
+static FLT dcblock(gutter_state* s, FLT input) {
+ FLT y = input - s->gi.dcblock.xm1 + s->gi.dcblock.r * s->gi.dcblock.ym1;
+ s->gi.dcblock.xm1 = input;
+ s->gi.dcblock.ym1 = y;
+ return y;
+}
+
+/*
+ * Synthesise one sample with audio input
+ *
+ * s: gutter_state struct to apply operation to
+ * audioInput: audio sample to feed into synthesis
+ *
+ * returns: synthesised audio sample
+ */
+FLT gutter_process_input(gutter_state* s, FLT audioInput)
+{
+ if (s->filtersOn) {
+ int i, b, f;
+ for (b = 0; b < s->bankCount; b++) {
+ for (f = 0; f < s->filterCount; f++) {
+ i = b * s->bankCount + f;
+ s->gi.y[i] = s->gi.a0[i] * s->gi.duffX
+ + s->gi.a1[i] * s->gi.prevX1[i]
+ + s->gi.a2[i] * s->gi.prevX2[i]
+ - s->gi.b1[i] * s->gi.prevY1[i]
+ - s->gi.b2[i] * s->gi.prevY2[i];
+ s->gi.prevX2[i] = s->gi.prevX1[i];
+ s->gi.prevX1[i] = s->gi.duffX;
+ s->gi.prevY2[i] = s->gi.prevY1[i];
+ s->gi.prevY1[i] = s->gi.y[i];
+ s->gi.finalY += s->gi.y[i] * s->gains[i] * s->singleGain;
+ }
+ }
+ } else {
+ s->gi.finalY = s->gi.duffX;
+ }
+
+ if (s->enableAudioInput) {
+ s->gi.dy = (s->gi.finalY - s->gi.finalY * s->gi.finalY * s->gi.finalY
+ - s->c * s->gi.duffY + s->gamma * audioInput);
+ } else {
+ s->gi.dy = (s->gi.finalY - s->gi.finalY * s->gi.finalY * s->gi.finalY
+ - s->c * s->gi.duffY + s->gamma * ((FLT)sin(s->omega * s->gi.t)));
+
+ }
+
+ s->gi.duffY += s->gi.dy;
+ s->gi.dx = s->gi.duffY;
+
+ FLT out;
+ s->gi.duffX = ((s->gi.finalY + s->gi.dx) - s->gi.duffX) / s->smoothing; // low pass filter
+ //s->gi.finalY = dcblock(s, s->gi.finalY); // RK: experimental
+ if (s->filtersOn) {
+ gutter_distortion(s, &(s->gi.duffX));
+ out = s->gi.finalY * 0.125;
+ } else {
+ s->gi.duffX = MAX(MIN(s->gi.duffX, 100), -100);
+ if (abs(s->gi.duffX) > 99) {
+ gutter_reset(s);
+ }
+ out = MAX(MIN(s->gi.duffX * s->singleGain, 1.0), -1.0);
+ }
+
+ s->gi.t += s->dt;
+
+ if (isnan(s->gi.duffX)) {
+ gutter_reset(s);
+ }
+
+ //return out;
+ return dcblock(s, out);
+}
+
+
+/*
+ * Synthesise specified number of samples with audio input
+ *
+ * s: gutter_state struct to apply operation to
+ * audioInput: audio sample pointer to feed into synthesis, must be at least nsamps size
+ * audioOutput: audio output pointer, must be at least nsamps size
+ * nsamps: number of samples to synthesise
+ */
+void gutter_process_input_samples(gutter_state* s, FLT* audioInput, FLT* audioOutput, int nsamps)
+{
+ int i;
+ for (i = 0; i < nsamps; i++) {
+ audioOutput[i] = gutter_process_input(s, audioInput[i]);
+ }
+}
+
+
+/*
+ * Synthesise one sample
+ *
+ * s: gutter_state struct to apply operation to
+ *
+ * returns: synthesised audio sample
+ */
+FLT gutter_process(gutter_state* s)
+{
+ return gutter_process_input(s, 0);
+}
+
+
+/*
+ * Synthesise specified number of samples
+ *
+ * s: gutter_state struct to apply operation to
+ * audioOutput: audio output pointer, must be at least nsamps size
+ * nsamps: number of samples to synthesise
+ */
+void gutter_process_samples(gutter_state* s, FLT* audioOutput, int nsamps)
+{
+ int i;
+ for (i = 0; i < nsamps; i++) {
+ audioOutput[i] = gutter_process_input(s, 0);
+ }
+} \ No newline at end of file