From 5ddad8184e9f49e53362a605afa0612049f9dd5c Mon Sep 17 00:00:00 2001 From: Colin Clark Date: Thu, 10 Oct 2024 16:20:23 -0400 Subject: [PATCH] gh-63: Forks Ladder filter for Bifocals, adding inline wavefolding and saturation. Renames sig_dsp_Sine to sig_dsp_SineOscillator. Adds new signals for clamping, wavefolding, sine, and noise gating. --- .../src/bluemchen-bare-board-test.cpp | 2 +- .../src/signaletic-bluemchen-calibrator.cpp | 4 +- .../src/signaletic-bluemchen-oscillator.cpp | 2 +- .../filter/src/lichen-bifocals-filter.cpp | 272 ++++++++++++++++-- .../src/lichen-bifocals-sine-oscillator.cpp | 2 +- .../src/lichen-medium-bare-board-test.cpp | 2 +- .../sines/src/lichen-medium-sines.cpp | 2 +- .../calibrator/src/patch-init-calibrator.cpp | 2 +- .../sines/src/signaletic-versio-sines.cpp | 2 +- .../daisy/include/lichen-bifocals-device.hpp | 14 +- hosts/daisy/include/lichen-freddie-device.hpp | 4 +- hosts/daisy/include/lichen-medium-device.hpp | 8 + .../signaletic-oscillator-worklet.js | 2 +- .../examples/console/src/print-sine.c | 4 +- libsignaletic/include/libsignaletic.h | 106 +++++-- libsignaletic/src/libsignaletic.c | 207 ++++++++++++- libsignaletic/tests/test-libsignaletic.c | 32 +-- .../bindings/libsignaletic-web-bindings.idl | 97 +++++-- .../wasm/bindings/src/libsignaletic-web.cpp | 104 ++++++- 19 files changed, 749 insertions(+), 119 deletions(-) diff --git a/hosts/daisy/examples/bluemchen/bare-board-test/src/bluemchen-bare-board-test.cpp b/hosts/daisy/examples/bluemchen/bare-board-test/src/bluemchen-bare-board-test.cpp index d0103b6..031e0d0 100644 --- a/hosts/daisy/examples/bluemchen/bare-board-test/src/bluemchen-bare-board-test.cpp +++ b/hosts/daisy/examples/bluemchen/bare-board-test/src/bluemchen-bare-board-test.cpp @@ -92,7 +92,7 @@ void buildSignalGraph(struct sig_Allocator* allocator, harmonizer->inputs.freq = harmonizerFreqScale->outputs.main; harmonizer->inputs.mul = buttonValue->outputs.main; - sine = sig_dsp_Sine_new(allocator, context); + sine = sig_dsp_SineOscillator_new(allocator, context); sig_List_append(signals, sine, status); sine->inputs.freq = freq->outputs.main; diff --git a/hosts/daisy/examples/bluemchen/calibrator/src/signaletic-bluemchen-calibrator.cpp b/hosts/daisy/examples/bluemchen/calibrator/src/signaletic-bluemchen-calibrator.cpp index ea7c762..d0f0ef3 100644 --- a/hosts/daisy/examples/bluemchen/calibrator/src/signaletic-bluemchen-calibrator.cpp +++ b/hosts/daisy/examples/bluemchen/calibrator/src/signaletic-bluemchen-calibrator.cpp @@ -138,7 +138,7 @@ void buildSignalGraph(struct sig_SignalContext* context, struct sig_Status* stat sig_List_append(&signals, sine1Voct, status); sine1Voct->inputs.source = cv1Calibrator->outputs.main; - sine1 = sig_dsp_Sine_new(&allocator, context); + sine1 = sig_dsp_SineOscillator_new(&allocator, context); sig_List_append(&signals, sine1, status); sine1->inputs.freq = sine1Voct->outputs.main; sine1->inputs.mul = ampScale->outputs.main; @@ -153,7 +153,7 @@ void buildSignalGraph(struct sig_SignalContext* context, struct sig_Status* stat sig_List_append(&signals, sine2Voct, status); sine2Voct->inputs.source = cv2Calibrator->outputs.main; - sine2 = sig_dsp_Sine_new(&allocator, context); + sine2 = sig_dsp_SineOscillator_new(&allocator, context); sig_List_append(&signals, sine2, status); sine2->inputs.freq = sine2Voct->outputs.main; sine2->inputs.mul = ampScale->outputs.main; diff --git a/hosts/daisy/examples/bluemchen/oscillator/src/signaletic-bluemchen-oscillator.cpp b/hosts/daisy/examples/bluemchen/oscillator/src/signaletic-bluemchen-oscillator.cpp index 9e331c9..513a69b 100644 --- a/hosts/daisy/examples/bluemchen/oscillator/src/signaletic-bluemchen-oscillator.cpp +++ b/hosts/daisy/examples/bluemchen/oscillator/src/signaletic-bluemchen-oscillator.cpp @@ -98,7 +98,7 @@ void buildSignalGraph(struct sig_SignalContext* context, sig_List_append(&signals, frequency, status); frequency->inputs.source = coarseVOctPlusFine->outputs.main; - osc = sig_dsp_Sine_new(&allocator, context); + osc = sig_dsp_SineOscillator_new(&allocator, context); sig_List_append(&signals, osc, status); osc->inputs.freq = frequency->outputs.main; diff --git a/hosts/daisy/examples/lichen-bifocals/filter/src/lichen-bifocals-filter.cpp b/hosts/daisy/examples/lichen-bifocals/filter/src/lichen-bifocals-filter.cpp index ae81075..121ecd2 100644 --- a/hosts/daisy/examples/lichen-bifocals/filter/src/lichen-bifocals-filter.cpp +++ b/hosts/daisy/examples/lichen-bifocals/filter/src/lichen-bifocals-filter.cpp @@ -56,6 +56,188 @@ Knob Scaling: #define HEAP_SIZE 1024 * 384 // 384 KB #define MAX_NUM_SIGNALS 64 +// TODO: Fix rampant cut and paste from libsignaletic.c's Ladder. +struct sig_dsp_BifocalsLadder_Inputs { + float_array_ptr source; + float_array_ptr frequency; + float_array_ptr resonance; + float_array_ptr inputGain; + float_array_ptr pole1Gain; + float_array_ptr pole2Gain; + float_array_ptr pole3Gain; + float_array_ptr pole4Gain; + float_array_ptr wavefolderGain; + float_array_ptr wavefolderFactor; + float_array_ptr wavefolderPosition; +}; + +struct sig_dsp_BifocalsLadder { + struct sig_dsp_Signal signal; + struct sig_dsp_BifocalsLadder_Inputs inputs; + struct sig_dsp_Ladder_Parameters parameters; + struct sig_dsp_FourPoleFilter_Outputs outputs; + + uint8_t interpolation; + float interpolationRecip; + float alpha; + float beta[4]; + float z0[4]; + float z1[4]; + float k; + float fBase; + float qAdjust; + float prevFrequency; + float prevInput; +}; + +inline void sig_dsp_BifocalsLadder_calcCoefficients( + struct sig_dsp_BifocalsLadder* self, float freq) { + float sampleRate = self->signal.audioSettings->sampleRate; + freq = sig_clamp(freq, 5.0f, sampleRate * 0.425f); + float wc = freq * (float) (sig_TWOPI / + ((float)self->interpolation * sampleRate)); + float wc2 = wc * wc; + self->alpha = 0.9892f * wc - 0.4324f * + wc2 + 0.1381f * wc * wc2 - 0.0202f * wc2 * wc2; + self->qAdjust = 1.006f + 0.0536f * wc - 0.095f * wc2 - 0.05f * wc2 * wc2; +} + +inline float sig_dsp_BifocalsLadder_calcStage( + struct sig_dsp_BifocalsLadder* self, float s, uint8_t i) { + float ft = s * (1.0f/1.3f) + (0.3f/1.3f) * self->z0[i] - self->z1[i]; + ft = ft * self->alpha + self->z1[i]; + self->z1[i] = ft; + self->z0[i] = s; + return ft; +} + +void sig_dsp_BifocalsLadder_generate(void* signal) { + struct sig_dsp_BifocalsLadder* self = + (struct sig_dsp_BifocalsLadder*) signal; + float interpolationRecip = self->interpolationRecip; + + for (size_t i = 0; i < self->signal.audioSettings->blockSize; i++) { + float input = FLOAT_ARRAY(self->inputs.source)[i]; + float frequency = FLOAT_ARRAY(self->inputs.frequency)[i]; + float resonance = FLOAT_ARRAY(self->inputs.resonance)[i]; + float wavefolderGain = FLOAT_ARRAY(self->inputs.wavefolderGain)[i]; + float wavefolderFactor = FLOAT_ARRAY(self->inputs.wavefolderFactor)[i]; + float wavefolderPosition = + FLOAT_ARRAY(self->inputs.wavefolderPosition)[i]; + float inputGain = FLOAT_ARRAY(self->inputs.inputGain)[i]; + float pole1Gain = FLOAT_ARRAY(self->inputs.pole1Gain)[i]; + float pole2Gain = FLOAT_ARRAY(self->inputs.pole2Gain)[i]; + float pole3Gain = FLOAT_ARRAY(self->inputs.pole3Gain)[i]; + float pole4Gain = FLOAT_ARRAY(self->inputs.pole4Gain)[i]; + float totals[5] = {0.0f, 0.0f, 0.0f, 0.0f, 0.0f}; + float interp = 0.0f; + + // Recalculate coefficients if the frequency has changed. + if (frequency != self->prevFrequency) { + sig_dsp_BifocalsLadder_calcCoefficients(self, frequency); + self->prevFrequency = frequency; + } + + self->k = 4.0f * resonance; + + float main = 0.0f; + for (size_t os = 0; os < self->interpolation; os++) { + float inInterp = interp * self->prevInput + (1.0f - interp) * input; + float u = inInterp - (self->z1[3] - self->parameters.passbandGain * + inInterp) * self->k * self->qAdjust; + + if (wavefolderPosition <= 0.0f) { + float prefolded = (u + + sinf(wavefolderFactor * u)) * wavefolderGain; + u = prefolded; + } + u = sig_fastTanhf(u); + + totals[0] = u; + float stage1 = sig_dsp_BifocalsLadder_calcStage(self, u, 0); + totals[1] += stage1 * interpolationRecip; + float stage2 = sig_dsp_BifocalsLadder_calcStage(self, stage1, 1); + totals[2] += stage2 * interpolationRecip; + float stage3 = sig_dsp_BifocalsLadder_calcStage(self, stage2, 2); + totals[3] += stage3 * interpolationRecip; + float stage4 = sig_dsp_BifocalsLadder_calcStage(self, stage3, 3); + totals[4] += stage4 * interpolationRecip; + + float poleSum = (u * inputGain) + + (stage1 * pole1Gain) + + (stage2 * pole2Gain) + + (stage3 * pole3Gain) + + (stage4 * pole4Gain); + + if (wavefolderPosition > 0.0f) { + poleSum = (poleSum + + sinf(wavefolderFactor * poleSum)) * wavefolderGain; + + } + float postSaturated = tanhf(poleSum); + main += postSaturated * interpolationRecip; + interp += interpolationRecip; + } + self->prevInput = input; + FLOAT_ARRAY(self->outputs.main)[i] = main; + FLOAT_ARRAY(self->outputs.twoPole)[i] = totals[2]; + FLOAT_ARRAY(self->outputs.fourPole)[i] = totals[4]; + } +} + +void sig_dsp_BifocalsLadder_init( + struct sig_dsp_BifocalsLadder* self, + struct sig_SignalContext* context) { + sig_dsp_Signal_init(self, context, *sig_dsp_BifocalsLadder_generate); + + struct sig_dsp_Ladder_Parameters parameters = { + .passbandGain = 0.5f + }; + self->parameters = parameters; + + self->interpolation = 8; + self->interpolationRecip = 1.0f / self->interpolation; + self->alpha = 1.0f; + self->beta[0] = self->beta[1] = self->beta[2] = self->beta[3] = 0.0f; + self->z0[0] = self->z0[1] = self->z0[2] = self->z0[3] = 0.0f; + self->z1[0] = self->z1[1] = self->z1[2] = self->z1[3] = 0.0f; + self->k = 1.0f; + self->fBase = 1000.0f; + self->qAdjust = 1.0f; + self->prevFrequency = -1.0f; + self->prevInput = 0.0f; + + sig_CONNECT_TO_SILENCE(self, source, context); + sig_CONNECT_TO_SILENCE(self, frequency, context); + sig_CONNECT_TO_SILENCE(self, resonance, context); + sig_CONNECT_TO_SILENCE(self, inputGain, context); + sig_CONNECT_TO_SILENCE(self, pole1Gain, context); + sig_CONNECT_TO_SILENCE(self, pole2Gain, context); + sig_CONNECT_TO_SILENCE(self, pole3Gain, context); + sig_CONNECT_TO_UNITY(self, pole4Gain, context); + sig_CONNECT_TO_SILENCE(self, wavefolderGain, context); + sig_CONNECT_TO_SILENCE(self, wavefolderFactor, context); + sig_CONNECT_TO_SILENCE(self, wavefolderPosition, context); +} + +struct sig_dsp_BifocalsLadder* sig_dsp_BifocalsLadder_new( + struct sig_Allocator* allocator, struct sig_SignalContext* context) { + struct sig_dsp_BifocalsLadder* self = sig_MALLOC(allocator, + struct sig_dsp_BifocalsLadder); + sig_dsp_BifocalsLadder_init(self, context); + sig_dsp_FourPoleFilter_Outputs_newAudioBlocks(allocator, + context->audioSettings, &self->outputs); + + return self; +} + +void sig_dsp_BifocalsLadder_destroy(struct sig_Allocator* allocator, + struct sig_dsp_BifocalsLadder* self) { + sig_dsp_FourPoleFilter_Outputs_destroyAudioBlocks(allocator, + &self->outputs); + sig_dsp_Signal_destroy(allocator, self); +} + // The best, with band passes #define NUM_FILTER_MODES 9 #define NUM_FILTER_STAGES 5 @@ -134,15 +316,19 @@ struct sig_dsp_Branch* rightSkewedFrequency; struct sig_dsp_LinearToFreq* rightFrequency; struct sig_host_AudioIn* leftIn; struct sig_host_FilteredCVIn* gainKnob; +struct sig_dsp_ScaleOffset* scaledGainKnob; struct sig_host_FilteredCVIn* gainCV; struct sig_dsp_BinaryOp* gain; struct sig_host_AudioIn* rightIn; struct sig_dsp_BinaryOp* leftVCA; struct sig_dsp_BinaryOp* rightVCA; -struct sig_dsp_Ladder* leftFilter; -struct sig_dsp_Ladder* rightFilter; -struct sig_dsp_Tanh* leftSaturation; -struct sig_dsp_Tanh* rightSaturation; +struct sig_dsp_LinearMap* wavefolderGain; +struct sig_dsp_LinearMap* wavefolderFactor; +struct sig_host_SwitchIn* wavefolderPositionButton; +struct sig_dsp_ToggleGate* wavefolderPosition; +struct sig_host_CVOut* led; +struct sig_dsp_BifocalsLadder* leftFilter; +struct sig_dsp_BifocalsLadder* rightFilter; struct sig_host_AudioOut* leftOut; struct sig_host_AudioOut* rightOut; @@ -221,7 +407,7 @@ void buildSignalGraph(struct sig_SignalContext* context, skewKnob = sig_host_FilteredCVIn_new(&allocator, context); skewKnob->hardware = &host.device.hardware; sig_List_append(&signals, skewKnob, status); - skewKnob->parameters.control = sig_host_KNOB_5; + skewKnob->parameters.control = sig_host_KNOB_3; skewKnob->parameters.scale = 5.0f; skewKnob->parameters.offset = -2.5f; skewKnob->parameters.time = 0.1f; @@ -270,36 +456,40 @@ void buildSignalGraph(struct sig_SignalContext* context, resonanceKnob = sig_host_FilteredCVIn_new(&allocator, context); resonanceKnob->hardware = &host.device.hardware; sig_List_append(&signals, resonanceKnob, status); - resonanceKnob->parameters.control = sig_host_KNOB_2; - resonanceKnob->parameters.scale = 1.8f; // 4.0f for Bob + resonanceKnob->parameters.control = sig_host_KNOB_5; + resonanceKnob->parameters.scale = 1.6f; // 4.0f for Bob resonanceCV = sig_host_FilteredCVIn_new(&allocator, context); resonanceCV->hardware = &host.device.hardware; sig_List_append(&signals, resonanceCV, status); resonanceCV->parameters.control = sig_host_CV_IN_3; - resonanceCV->parameters.scale = 1.8f; // 4.0f for Bob + resonanceCV->parameters.scale = 0.8f; // 4.0f for Bob resonance = sig_dsp_Add_new(&allocator, context); sig_List_append(&signals, resonance, status); resonance->inputs.left = resonanceKnob->outputs.main; resonance->inputs.right = resonanceCV->outputs.main; - // TODO: Clamp or calibrate to between 0 and 4 gainKnob = sig_host_FilteredCVIn_new(&allocator, context); gainKnob->hardware = &host.device.hardware; sig_List_append(&signals, gainKnob, status); - gainKnob->parameters.control = sig_host_KNOB_3; - gainKnob->parameters.scale = 3.0f; + gainKnob->parameters.control = sig_host_KNOB_2; + + scaledGainKnob = sig_dsp_ScaleOffset_new(&allocator, context); + sig_List_append(&signals, scaledGainKnob, status); + scaledGainKnob->inputs.source = gainKnob->outputs.main; + scaledGainKnob->parameters.scale = 2.0f; + scaledGainKnob->parameters.offset = 0.0f; gainCV = sig_host_FilteredCVIn_new(&allocator, context); gainCV->hardware = &host.device.hardware; sig_List_append(&signals, gainCV, status); gainCV->parameters.control = sig_host_CV_IN_4; - gainCV->parameters.scale = 2.5f; + gainCV->parameters.scale = 2.0f; gain = sig_dsp_Add_new(&allocator, context); sig_List_append(&signals, gain, status); - gain->inputs.left = gainKnob->outputs.main; + gain->inputs.left = scaledGainKnob->outputs.main; gain->inputs.right = gainCV->outputs.main; leftIn = sig_host_AudioIn_new(&allocator, context); @@ -312,7 +502,39 @@ void buildSignalGraph(struct sig_SignalContext* context, leftVCA->inputs.left = leftIn->outputs.main; leftVCA->inputs.right = gain->outputs.main; - leftFilter = sig_dsp_Ladder_new(&allocator, context); + wavefolderFactor = sig_dsp_LinearMap_new(&allocator, context); + sig_List_append(&signals, wavefolderFactor, status); + wavefolderFactor->inputs.source = gainKnob->outputs.main; + wavefolderFactor->parameters.fromMin = 1.0/3.0f; + wavefolderFactor->parameters.fromMax = 1.0f; + wavefolderFactor->parameters.toMin = 0.0f; + wavefolderFactor->parameters.toMax = 6.0f; + + wavefolderGain = sig_dsp_LinearMap_new(&allocator, context); + sig_List_append(&signals, wavefolderGain, status); + wavefolderGain->inputs.source = gainKnob->outputs.main; + wavefolderGain->parameters.fromMin = 0.75f; + wavefolderGain->parameters.fromMax = 1.0f; + wavefolderGain->parameters.toMin = 1.0f; + wavefolderGain->parameters.toMax = 2.0f; + + wavefolderPositionButton = sig_host_SwitchIn_new(&allocator, context); + wavefolderPositionButton->hardware = &host.device.hardware; + sig_List_append(&signals, wavefolderPositionButton, status); + wavefolderPositionButton->parameters.control = sig_host_TOGGLE_1; + + wavefolderPosition = sig_dsp_ToggleGate_new(&allocator, context); + sig_List_append(&signals, wavefolderPosition, status); + wavefolderPosition->inputs.trigger = wavefolderPositionButton->outputs.main; + + led = sig_host_CVOut_new(&allocator, context); + led->hardware = &host.device.hardware; + sig_List_append(&signals, led, status); + led->parameters.control = sig_host_CV_OUT_1; + led->inputs.source = wavefolderPosition->outputs.main; + led->parameters.scale = 0.51f; + + leftFilter = sig_dsp_BifocalsLadder_new(&allocator, context); sig_List_append(&signals, leftFilter, status); leftFilter->parameters.passbandGain = 0.5f; leftFilter->inputs.source = leftVCA->outputs.main; @@ -323,16 +545,15 @@ void buildSignalGraph(struct sig_SignalContext* context, leftFilter->inputs.pole2Gain = cList->outputs.main; leftFilter->inputs.pole3Gain = dList->outputs.main; leftFilter->inputs.pole4Gain = eList->outputs.main; - - leftSaturation = sig_dsp_Tanh_new(&allocator, context); - sig_List_append(&signals, leftSaturation, status); - leftSaturation->inputs.source = leftFilter->outputs.main; + leftFilter->inputs.wavefolderGain = wavefolderGain->outputs.main; + leftFilter->inputs.wavefolderFactor = wavefolderFactor->outputs.main; + leftFilter->inputs.wavefolderPosition = wavefolderPosition->outputs.main; leftOut = sig_host_AudioOut_new(&allocator, context); leftOut->hardware = &host.device.hardware; sig_List_append(&signals, leftOut, status); leftOut->parameters.channel = sig_host_AUDIO_OUT_1; - leftOut->inputs.source = leftSaturation->outputs.main; + leftOut->inputs.source = leftFilter->outputs.main; rightIn = sig_host_AudioIn_new(&allocator, context); rightIn->hardware = &host.device.hardware; @@ -344,7 +565,7 @@ void buildSignalGraph(struct sig_SignalContext* context, rightVCA->inputs.left = rightIn->outputs.main; rightVCA->inputs.right = gain->outputs.main; - rightFilter = sig_dsp_Ladder_new(&allocator, context); + rightFilter = sig_dsp_BifocalsLadder_new(&allocator, context); sig_List_append(&signals, rightFilter, status); rightFilter->parameters.passbandGain = 0.5f; rightFilter->inputs.source = rightVCA->outputs.main; @@ -355,16 +576,15 @@ void buildSignalGraph(struct sig_SignalContext* context, rightFilter->inputs.pole2Gain = cList->outputs.main; rightFilter->inputs.pole3Gain = dList->outputs.main; rightFilter->inputs.pole4Gain = eList->outputs.main; - - rightSaturation = sig_dsp_Tanh_new(&allocator, context); - sig_List_append(&signals, rightSaturation, status); - rightSaturation->inputs.source = rightFilter->outputs.main; + rightFilter->inputs.wavefolderGain = wavefolderGain->outputs.main; + rightFilter->inputs.wavefolderFactor = wavefolderFactor->outputs.main; + rightFilter->inputs.wavefolderPosition = wavefolderPosition->outputs.main; rightOut = sig_host_AudioOut_new(&allocator, context); rightOut->hardware = &host.device.hardware; sig_List_append(&signals, rightOut, status); rightOut->parameters.channel = sig_host_AUDIO_OUT_2; - rightOut->inputs.source = rightSaturation->outputs.main; + rightOut->inputs.source = rightFilter->outputs.main; } int main(void) { @@ -373,7 +593,7 @@ int main(void) { struct sig_AudioSettings audioSettings = { .sampleRate = SAMPLERATE, .numChannels = 2, - .blockSize = 2 + .blockSize = 16 }; struct sig_Status status; diff --git a/hosts/daisy/examples/lichen-bifocals/sine-oscillator/src/lichen-bifocals-sine-oscillator.cpp b/hosts/daisy/examples/lichen-bifocals/sine-oscillator/src/lichen-bifocals-sine-oscillator.cpp index 16d3e82..56cae3c 100644 --- a/hosts/daisy/examples/lichen-bifocals/sine-oscillator/src/lichen-bifocals-sine-oscillator.cpp +++ b/hosts/daisy/examples/lichen-bifocals/sine-oscillator/src/lichen-bifocals-sine-oscillator.cpp @@ -52,7 +52,7 @@ void buildSignalGraph(struct sig_SignalContext* context, freqSum->inputs.left = freqKnob->outputs.main; freqSum->inputs.right = freqCV->outputs.main; - osc = sig_dsp_Sine_new(&allocator, context); + osc = sig_dsp_SineOscillator_new(&allocator, context); sig_List_append(&signals, osc, status); osc->inputs.freq = freqSum->outputs.main; diff --git a/hosts/daisy/examples/lichen-medium/bare-board-test/src/lichen-medium-bare-board-test.cpp b/hosts/daisy/examples/lichen-medium/bare-board-test/src/lichen-medium-bare-board-test.cpp index 1f8a40b..4c6ccca 100644 --- a/hosts/daisy/examples/lichen-medium/bare-board-test/src/lichen-medium-bare-board-test.cpp +++ b/hosts/daisy/examples/lichen-medium/bare-board-test/src/lichen-medium-bare-board-test.cpp @@ -67,7 +67,7 @@ void buildSignalGraph(struct sig_Allocator* allocator, harmonizer->inputs.freq = harmonizerFreqScale->outputs.main; harmonizer->inputs.mul = buttonValue->outputs.main; - sine = sig_dsp_Sine_new(allocator, context); + sine = sig_dsp_SineOscillator_new(allocator, context); sig_List_append(signals, sine, status); sine->inputs.freq = freq->outputs.main; diff --git a/hosts/daisy/examples/lichen-medium/sines/src/lichen-medium-sines.cpp b/hosts/daisy/examples/lichen-medium/sines/src/lichen-medium-sines.cpp index 07ea14b..2dfaae2 100644 --- a/hosts/daisy/examples/lichen-medium/sines/src/lichen-medium-sines.cpp +++ b/hosts/daisy/examples/lichen-medium/sines/src/lichen-medium-sines.cpp @@ -49,7 +49,7 @@ void buildSignalGraph(struct sig_SignalContext* context, freqSum->inputs.left = freqKnob->outputs.main; freqSum->inputs.right = freqCV->outputs.main; - osc = sig_dsp_Sine_new(&allocator, context); + osc = sig_dsp_SineOscillator_new(&allocator, context); sig_List_append(&signals, osc, status); osc->inputs.freq = freqSum->outputs.main; diff --git a/hosts/daisy/examples/patch_init/calibrator/src/patch-init-calibrator.cpp b/hosts/daisy/examples/patch_init/calibrator/src/patch-init-calibrator.cpp index fa96df1..df2f51a 100644 --- a/hosts/daisy/examples/patch_init/calibrator/src/patch-init-calibrator.cpp +++ b/hosts/daisy/examples/patch_init/calibrator/src/patch-init-calibrator.cpp @@ -54,7 +54,7 @@ void buildGraph(struct sig_SignalContext* context, struct sig_Status* status) { sig_List_append(&signals, sine1Voct, status); sine1Voct->inputs.source = cv1Calibrator->outputs.main; - sine1 = sig_dsp_Sine_new(&allocator, context); + sine1 = sig_dsp_SineOscillator_new(&allocator, context); sig_List_append(&signals, sine1, status); sine1->inputs.freq = sine1Voct->outputs.main; sine1->inputs.mul = ampScale->outputs.main; diff --git a/hosts/daisy/examples/versio/sines/src/signaletic-versio-sines.cpp b/hosts/daisy/examples/versio/sines/src/signaletic-versio-sines.cpp index aad6c0b..8db43b9 100644 --- a/hosts/daisy/examples/versio/sines/src/signaletic-versio-sines.cpp +++ b/hosts/daisy/examples/versio/sines/src/signaletic-versio-sines.cpp @@ -38,7 +38,7 @@ void buildSignalGraph(struct sig_SignalContext* context, freqCV->parameters.control = sig_host_CV_IN_1; freqCV->parameters.scale = 1760.0f; - osc = sig_dsp_Sine_new(&allocator, context); + osc = sig_dsp_SineOscillator_new(&allocator, context); sig_List_append(&signals, osc, status); osc->inputs.freq = freqCV->outputs.main; diff --git a/hosts/daisy/include/lichen-bifocals-device.hpp b/hosts/daisy/include/lichen-bifocals-device.hpp index ea34379..26ee359 100644 --- a/hosts/daisy/include/lichen-bifocals-device.hpp +++ b/hosts/daisy/include/lichen-bifocals-device.hpp @@ -22,6 +22,10 @@ enum { sig_host_CV_IN_5 }; +enum { + sig_host_TOGGLE_1 = 0 +}; + enum { sig_host_CV_OUT_1 = 0 }; @@ -45,13 +49,13 @@ namespace bifocals { static ADCChannelSpec ADC_CHANNEL_SPECS[NUM_ADC_CHANNELS] = { // Freq Knob/POT_2_CV_7/Pin C8 - Unipolar {patchsm::PIN_CV_7, INVERT}, - // Reso Knob/CV_IN_5/Pin C6 + // Gain Knob/CV_IN_5/Pin C6 {patchsm::PIN_CV_5, INVERT}, - // Gain Knob/POT_1_CV_6/Pin C7 + // Skew Knob/POT_1_CV_6/Pin C7 {patchsm::PIN_CV_6, INVERT}, - // Knob four/POT_4_CV_9/Pin A2 + // Shape/POT_4_CV_9/Pin A2 {patchsm::PIN_ADC_9, BI_TO_UNIPOLAR}, - // Knob five/POT_5_CV_10/Pin A3 + // Reso/POT_5_CV_10/Pin A3 {patchsm::PIN_ADC_10, BI_TO_UNIPOLAR}, // Shape CV/CV_IN_1/Pin C5 @@ -68,7 +72,7 @@ namespace bifocals { static const size_t NUM_BUTTONS = 1; static dsy_gpio_pin BUTTON_PINS[NUM_BUTTONS] = { - patchsm::PIN_D1 + patchsm::PIN_B7 }; static const size_t NUM_DAC_CHANNELS = 1; diff --git a/hosts/daisy/include/lichen-freddie-device.hpp b/hosts/daisy/include/lichen-freddie-device.hpp index 0410441..6c1b24d 100644 --- a/hosts/daisy/include/lichen-freddie-device.hpp +++ b/hosts/daisy/include/lichen-freddie-device.hpp @@ -47,8 +47,8 @@ namespace freddie { seed::PIN_D8, seed::PIN_D9, seed::PIN_D10, - seed::PIN_D11, - seed::PIN_D12, + seed::PIN_D29, + seed::PIN_D30, seed::PIN_D25, seed::PIN_D26, seed::PIN_D27 diff --git a/hosts/daisy/include/lichen-medium-device.hpp b/hosts/daisy/include/lichen-medium-device.hpp index 0d9a082..9e4c3f3 100644 --- a/hosts/daisy/include/lichen-medium-device.hpp +++ b/hosts/daisy/include/lichen-medium-device.hpp @@ -24,6 +24,14 @@ enum { sig_host_CV_IN_6 }; +enum { + sig_host_TOGGLE_1 = 0 +}; + +enum { + sig_host_TRISWITCH_1 = 0 +}; + enum { sig_host_CV_OUT_1 = 0 }; diff --git a/hosts/web/examples/oscillator/signaletic-oscillator-worklet.js b/hosts/web/examples/oscillator/signaletic-oscillator-worklet.js index a06b555..4def604 100644 --- a/hosts/web/examples/oscillator/signaletic-oscillator-worklet.js +++ b/hosts/web/examples/oscillator/signaletic-oscillator-worklet.js @@ -40,7 +40,7 @@ class SignaleticOscillator extends AudioWorkletProcessor { this.ampMod.parameters.value = 1.0; /** Carrier **/ - this.carrier = sig.dsp.Sine_new(this.allocator, this.signalContext); + this.carrier = sig.dsp.SineOscillator_new(this.allocator, this.signalContext); this.carrier.inputs.freq = this.freqMod.outputs.main; this.carrier.inputs.mul = this.ampMod.outputs.main; diff --git a/libsignaletic/examples/console/src/print-sine.c b/libsignaletic/examples/console/src/print-sine.c index 4cc1f97..8e02d85 100644 --- a/libsignaletic/examples/console/src/print-sine.c +++ b/libsignaletic/examples/console/src/print-sine.c @@ -37,7 +37,7 @@ int main(int argc, char *argv[]) { struct sig_SignalContext* context = sig_SignalContext_new(&allocator, &audioSettings); - struct sig_dsp_Oscillator* sine = sig_dsp_Sine_new(&allocator, context); + struct sig_dsp_Oscillator* sine = sig_dsp_SineOscillator_new(&allocator, context); struct sig_dsp_ConstantValue* freq = sig_dsp_ConstantValue_new(&allocator, context, 440.0f); struct sig_dsp_ConstantValue* amp = sig_dsp_ConstantValue_new(&allocator, @@ -53,7 +53,7 @@ int main(int argc, char *argv[]) { sig_dsp_ConstantValue_destroy(&allocator, freq); sig_dsp_ConstantValue_destroy(&allocator, amp); - sig_dsp_Sine_destroy(&allocator, sine); + sig_dsp_SineOscillator_destroy(&allocator, sine); sig_SignalContext_destroy(&allocator, context); return EXIT_SUCCESS; diff --git a/libsignaletic/include/libsignaletic.h b/libsignaletic/include/libsignaletic.h index 172afca..1ee79b6 100644 --- a/libsignaletic/include/libsignaletic.h +++ b/libsignaletic/include/libsignaletic.h @@ -124,9 +124,8 @@ float sig_randf(); float sig_fastTanhf(float x); /** - * @brief Linearly maps a value from one range to another. - * This implementation does not clamp the output if the value - * is outside of the specified current range. + * @brief Linearly maps a value from one range to another, + * clamping out of range values to the min and max. * * @param value the value to map * @param fromMin the minimum of the current range @@ -1078,7 +1077,7 @@ void sig_DelayLine_destroy(struct sig_Allocator* allocator, float sig_linearXFade(float left, float right, float mix); - +float sig_sineWavefolder(float x, float gain, float factor); // TODO: Should the signal argument at least be defined // as a struct sig_dsp_Signal*, rather than void*? @@ -1110,6 +1109,10 @@ void sig_dsp_Signal_destroy(struct sig_Allocator* allocator, #define sig_CONNECT_TO_UNITY(signal, inputName, context)\ signal->inputs.inputName = context->unity->outputs.main; +struct sig_dsp_Signal_SingleSourceInput { + float_array_ptr source; +}; + struct sig_dsp_Signal_SingleMonoOutput { float_array_ptr main; }; @@ -1195,14 +1198,9 @@ void sig_dsp_ConstantValue_destroy(struct sig_Allocator* allocator, struct sig_dsp_ConstantValue* self); - -struct sig_dsp_Abs_Inputs { - float_array_ptr source; -}; - struct sig_dsp_Abs { struct sig_dsp_Signal signal; - struct sig_dsp_Abs_Inputs inputs; + struct sig_dsp_Signal_SingleSourceInput inputs; struct sig_dsp_Signal_SingleMonoOutput outputs; }; @@ -1215,11 +1213,27 @@ void sig_dsp_Abs_destroy(struct sig_Allocator* allocator, struct sig_dsp_Abs* self); +struct sig_dsp_Clamp_Parameters { + float min; + float max; +}; -struct sig_dsp_ScaleOffset_Inputs { - float_array_ptr source; +struct sig_dsp_Clamp { + struct sig_dsp_Signal signal; + struct sig_dsp_Signal_SingleSourceInput inputs; + struct sig_dsp_Clamp_Parameters parameters; + struct sig_dsp_Signal_SingleMonoOutput outputs; }; +struct sig_dsp_Clamp* sig_dsp_Clamp_new( + struct sig_Allocator* allocator, struct sig_SignalContext* context); +void sig_dsp_Clamp_init(struct sig_dsp_Clamp* self, + struct sig_SignalContext* context); +void sig_dsp_Clamp_generate(void* signal); +void sig_dsp_Clamp_destroy(struct sig_Allocator* allocator, + struct sig_dsp_Clamp* self); + + struct sig_dsp_ScaleOffset_Parameters { float scale; float offset; @@ -1227,7 +1241,7 @@ struct sig_dsp_ScaleOffset_Parameters { struct sig_dsp_ScaleOffset { struct sig_dsp_Signal signal; - struct sig_dsp_ScaleOffset_Inputs inputs; + struct sig_dsp_Signal_SingleSourceInput inputs; struct sig_dsp_ScaleOffset_Parameters parameters; struct sig_dsp_Signal_SingleMonoOutput outputs; }; @@ -1241,6 +1255,20 @@ void sig_dsp_ScaleOffset_destroy(struct sig_Allocator* allocator, struct sig_dsp_ScaleOffset* self); +struct sig_dsp_Sine { + struct sig_dsp_Signal signal; + struct sig_dsp_Signal_SingleSourceInput inputs; + struct sig_dsp_Signal_SingleMonoOutput outputs; +}; + +struct sig_dsp_Sine* sig_dsp_Sine_new( + struct sig_Allocator* allocator, struct sig_SignalContext* context); +void sig_dsp_Sine_init(struct sig_dsp_Sine* self, + struct sig_SignalContext* context); +void sig_dsp_Sine_generate(void* signal); +void sig_dsp_Sine_destroy(struct sig_Allocator* allocator, + struct sig_dsp_Sine* self); + struct sig_dsp_BinaryOp_Inputs { float_array_ptr left; @@ -1533,12 +1561,12 @@ void sig_dsp_Oscillator_destroy(struct sig_Allocator* allocator, struct sig_dsp_Oscillator* self); -void sig_dsp_Sine_init(struct sig_dsp_Oscillator* self, +void sig_dsp_SineOscillator_init(struct sig_dsp_Oscillator* self, struct sig_SignalContext* context); -struct sig_dsp_Oscillator* sig_dsp_Sine_new(struct sig_Allocator* allocator, +struct sig_dsp_Oscillator* sig_dsp_SineOscillator_new(struct sig_Allocator* allocator, struct sig_SignalContext* context); -void sig_dsp_Sine_generate(void* signal); -void sig_dsp_Sine_destroy(struct sig_Allocator* allocator, +void sig_dsp_SineOscillator_generate(void* signal); +void sig_dsp_SineOscillator_destroy(struct sig_Allocator* allocator, struct sig_dsp_Oscillator* self); void sig_dsp_LFTriangle_init(struct sig_dsp_Oscillator* self, @@ -2083,7 +2111,7 @@ void sig_dsp_FourPoleFilter_Outputs_destroyAudioBlocks( /** - * @brief Miller Pucket's 24dB Moog-style ladder low pass filter. + * @brief Miller Puckette's 24dB Moog-style ladder low pass filter. * Imitates a Moog resonant filter by Runge-Kutte numerical integration * of a differential equation approximately describing the dynamics of * the circuit. @@ -2442,6 +2470,48 @@ void sig_dsp_Calibrator_generate(void* signal); void sig_dsp_Calibrator_destroy(struct sig_Allocator* allocator, struct sig_dsp_Calibrator* self); + +struct sig_dsp_SineWavefolder_Inputs { + float_array_ptr source; + float_array_ptr gain; + float_array_ptr factor; +}; + +struct sig_dsp_SineWavefolder { + struct sig_dsp_Signal signal; + struct sig_dsp_SineWavefolder_Inputs inputs; + struct sig_dsp_Signal_SingleMonoOutput outputs; +}; + +struct sig_dsp_SineWavefolder* sig_dsp_SineWavefolder_new( + struct sig_Allocator* allocator, struct sig_SignalContext* context); +void sig_dsp_SineWavefolder_init(struct sig_dsp_SineWavefolder* self, + struct sig_SignalContext* context); +void sig_dsp_SineWavefolder_generate(void* signal); +void sig_dsp_SineWavefolder_destroy(struct sig_Allocator* allocator, + struct sig_dsp_SineWavefolder* self); + + +struct sig_dsp_NoiseGate_Inputs { + float_array_ptr source; + float_array_ptr threshold; +}; + +struct sig_dsp_NoiseGate { + struct sig_dsp_Signal signal; + struct sig_dsp_NoiseGate_Inputs inputs; + struct sig_dsp_ScaleOffset_Parameters parameters; + struct sig_dsp_Signal_SingleMonoOutput outputs; +}; + +struct sig_dsp_NoiseGate* sig_dsp_NoiseGate_new( + struct sig_Allocator* allocator, struct sig_SignalContext* context); +void sig_dsp_NoiseGate_init(struct sig_dsp_NoiseGate* self, + struct sig_SignalContext* context); +void sig_dsp_NoiseGate_generate(void* signal); +void sig_dsp_NoiseGate_destroy(struct sig_Allocator* allocator, + struct sig_dsp_NoiseGate* self); + #ifdef __cplusplus } #endif diff --git a/libsignaletic/src/libsignaletic.c b/libsignaletic/src/libsignaletic.c index 898edd5..33f8916 100644 --- a/libsignaletic/src/libsignaletic.c +++ b/libsignaletic/src/libsignaletic.c @@ -71,8 +71,14 @@ float sig_randf() { inline float sig_fastTanhf(float x) { // From https://gist.github.com/ndonald2/534831b639b8c78d40279b5007e06e5b - if (x > 3.0f) return 1.0f; - if (x < -3.0f) return -1.0f; + if (x > 3.0f) { + return 1.0f; + } + + if (x < -3.0f) { + return -1.0f; + } + float x2 = x * x; return x * (27.0f + x2) / (27.0f + 9.0f * x2); } @@ -80,7 +86,11 @@ inline float sig_fastTanhf(float x) { // TODO: Unit tests. inline float sig_linearMap(float value, float fromMin, float fromMax, float toMin, float toMax) { - return (value - fromMin) * (toMax - toMin) / (fromMax - fromMin) + toMin; + float clamped = sig_clamp(value, fromMin, fromMax); + float mapped = (clamped - fromMin) * (toMax - toMin) / + (fromMax - fromMin) + toMin; + + return mapped; } extern inline uint16_t sig_unipolarToUint12(float sample) { @@ -950,6 +960,11 @@ inline float sig_linearXFade(float left, float right, float mix) { return sample; } +inline float sig_sineWavefolder(float x, float gain, float factor) { + float sample = x + gain * sinf(factor * x); + return sample; +} + void sig_dsp_Signal_init(void* signal, struct sig_SignalContext* context, sig_dsp_generateFn generate) { struct sig_dsp_Signal* self = (struct sig_dsp_Signal*) signal; @@ -1124,6 +1139,48 @@ void sig_dsp_Abs_destroy(struct sig_Allocator* allocator, +struct sig_dsp_Clamp* sig_dsp_Clamp_new( + struct sig_Allocator* allocator, struct sig_SignalContext* context) { + struct sig_dsp_Clamp* self = sig_MALLOC(allocator, + struct sig_dsp_Clamp); + sig_dsp_Signal_SingleMonoOutput_newAudioBlocks(allocator, + context->audioSettings, &self->outputs); + sig_dsp_Clamp_init(self, context); + + return self; +} + +void sig_dsp_Clamp_init(struct sig_dsp_Clamp* self, + struct sig_SignalContext* context) { + sig_dsp_Signal_init(self, context, *sig_dsp_Clamp_generate); + + self->parameters.min = 0.0f; + self->parameters.max = 1.0f; + + sig_CONNECT_TO_SILENCE(self, source, context); +} + +void sig_dsp_Clamp_generate(void* signal) { + struct sig_dsp_Clamp* self = (struct sig_dsp_Clamp*) signal; + float min = self->parameters.min; + float max = self->parameters.max; + + for (size_t i = 0; i < self->signal.audioSettings->blockSize; i++) { + float source = FLOAT_ARRAY(self->inputs.source)[i]; + float sample = sig_clamp(source, min, max); + FLOAT_ARRAY(self->outputs.main)[i] = sample; + } +} + +void sig_dsp_Clamp_destroy(struct sig_Allocator* allocator, + struct sig_dsp_Clamp* self) { + sig_dsp_Signal_SingleMonoOutput_destroyAudioBlocks(allocator, + &self->outputs); + sig_dsp_Signal_destroy(allocator, (void*) self); +} + + + struct sig_dsp_ScaleOffset* sig_dsp_ScaleOffset_new( struct sig_Allocator* allocator, struct sig_SignalContext* context) { struct sig_dsp_ScaleOffset* self = sig_MALLOC(allocator, @@ -1167,6 +1224,40 @@ void sig_dsp_ScaleOffset_destroy(struct sig_Allocator* allocator, } +struct sig_dsp_Sine* sig_dsp_Sine_new( + struct sig_Allocator* allocator, struct sig_SignalContext* context) { + struct sig_dsp_Sine* self = sig_MALLOC(allocator, + struct sig_dsp_Sine); + sig_dsp_Signal_SingleMonoOutput_newAudioBlocks(allocator, + context->audioSettings, &self->outputs); + sig_dsp_Sine_init(self, context); + + return self; +}; + +void sig_dsp_Sine_init(struct sig_dsp_Sine* self, + struct sig_SignalContext* context) { + sig_dsp_Signal_init(self, context, *sig_dsp_Sine_generate); + + sig_CONNECT_TO_SILENCE(self, source, context); +} + +void sig_dsp_Sine_generate(void* signal) { + struct sig_dsp_Sine* self = (struct sig_dsp_Sine*) signal; + + for (size_t i = 0; i < self->signal.audioSettings->blockSize; i++) { + float source = FLOAT_ARRAY(self->inputs.source)[i]; + FLOAT_ARRAY(self->outputs.main)[i] = sinf(source); + } +} + +void sig_dsp_Sine_destroy(struct sig_Allocator* allocator, + struct sig_dsp_Sine* self) { + sig_dsp_Signal_SingleMonoOutput_destroyAudioBlocks(allocator, + &self->outputs); + sig_dsp_Signal_destroy(allocator, (void*) self); +} + struct sig_dsp_BinaryOp* sig_dsp_BinaryOp_new(struct sig_Allocator* allocator, struct sig_SignalContext* context, sig_dsp_generateFn generate) { @@ -1692,22 +1783,22 @@ inline void sig_dsp_Oscillator_accumulatePhase( self->phaseAccumulator = sig_flooredfmodf(phase, 1.0f); } -void sig_dsp_Sine_init(struct sig_dsp_Oscillator* self, +void sig_dsp_SineOscillator_init(struct sig_dsp_Oscillator* self, struct sig_SignalContext* context) { - sig_dsp_Oscillator_init(self, context, *sig_dsp_Sine_generate); + sig_dsp_Oscillator_init(self, context, *sig_dsp_SineOscillator_generate); } -struct sig_dsp_Oscillator* sig_dsp_Sine_new(struct sig_Allocator* allocator, +struct sig_dsp_Oscillator* sig_dsp_SineOscillator_new(struct sig_Allocator* allocator, struct sig_SignalContext* context) { - return sig_dsp_Oscillator_new(allocator, context, *sig_dsp_Sine_generate); + return sig_dsp_Oscillator_new(allocator, context, *sig_dsp_SineOscillator_generate); } -void sig_dsp_Sine_destroy(struct sig_Allocator* allocator, +void sig_dsp_SineOscillator_destroy(struct sig_Allocator* allocator, struct sig_dsp_Oscillator* self) { sig_dsp_Oscillator_destroy(allocator, self); } -void sig_dsp_Sine_generate(void* signal) { +void sig_dsp_SineOscillator_generate(void* signal) { struct sig_dsp_Oscillator* self = (struct sig_dsp_Oscillator*) signal; for (size_t i = 0; i < self->signal.audioSettings->blockSize; i++) { @@ -2749,8 +2840,8 @@ struct sig_dsp_TwoOpFM* sig_dsp_TwoOpFM_new(struct sig_Allocator* allocator, struct sig_dsp_TwoOpFM); self->modulatorFrequency = sig_dsp_Mul_new(allocator, context); self->carrierPhaseOffset = sig_dsp_Add_new(allocator, context); - self->modulator = sig_dsp_Sine_new(allocator, context); - self->carrier = sig_dsp_Sine_new(allocator, context); + self->modulator = sig_dsp_SineOscillator_new(allocator, context); + self->carrier = sig_dsp_SineOscillator_new(allocator, context); sig_dsp_TwoOpFM_init(self, context); @@ -2797,8 +2888,8 @@ void sig_dsp_TwoOpFM_destroy(struct sig_Allocator* allocator, struct sig_dsp_TwoOpFM* self) { sig_dsp_Mul_destroy(allocator, self->modulatorFrequency); sig_dsp_Add_destroy(allocator, self->carrierPhaseOffset); - sig_dsp_Sine_destroy(allocator, self->carrier); - sig_dsp_Sine_destroy(allocator, self->modulator); + sig_dsp_SineOscillator_destroy(allocator, self->carrier); + sig_dsp_SineOscillator_destroy(allocator, self->modulator); sig_dsp_Signal_destroy(allocator, self); } @@ -3487,7 +3578,7 @@ void sig_dsp_Chorus_generate(void* signal) { // TODO: What kind of gain staging should we do here? // It seems likely that we can have up to 3x gain depending on // the values of feedbackGain, feedforwardGain, and blend. - FLOAT_ARRAY(self->outputs.main)[i] = sig_fastTanhf(output / 3.0f); + FLOAT_ARRAY(self->outputs.main)[i] = tanhf(output / 3.0f); } } @@ -3668,3 +3759,91 @@ void sig_dsp_Calibrator_destroy(struct sig_Allocator* allocator, &self->outputs); sig_dsp_Signal_destroy(allocator, self); } + +struct sig_dsp_SineWavefolder* sig_dsp_SineWavefolder_new( + struct sig_Allocator* allocator, struct sig_SignalContext* context) { + struct sig_dsp_SineWavefolder* self = sig_MALLOC(allocator, + struct sig_dsp_SineWavefolder); + sig_dsp_SineWavefolder_init(self, context); + sig_dsp_Signal_SingleMonoOutput_newAudioBlocks(allocator, + context->audioSettings, &self->outputs); + + return self; +} + +void sig_dsp_SineWavefolder_init(struct sig_dsp_SineWavefolder* self, + struct sig_SignalContext* context) { + sig_dsp_Signal_init(self, context, *sig_dsp_SineWavefolder_generate); + + sig_CONNECT_TO_SILENCE(self, source, context); + sig_CONNECT_TO_SILENCE(self, gain, context); + sig_CONNECT_TO_SILENCE(self, factor, context); +} + +void sig_dsp_SineWavefolder_generate(void* signal) { + struct sig_dsp_SineWavefolder* self = + (struct sig_dsp_SineWavefolder*) signal; + + for (size_t i = 0; i < self->signal.audioSettings->blockSize; i++) { + float source = FLOAT_ARRAY(self->inputs.source)[i]; + float gain = FLOAT_ARRAY(self->inputs.gain)[i]; + float factor = FLOAT_ARRAY(self->inputs.factor)[i]; + + float sample = sig_sineWavefolder(source, gain, factor); + + FLOAT_ARRAY(self->outputs.main)[i] = sample; + } +} + +void sig_dsp_SineWavefolder_destroy(struct sig_Allocator* allocator, + struct sig_dsp_SineWavefolder* self) { + sig_dsp_Signal_SingleMonoOutput_destroyAudioBlocks(allocator, + &self->outputs); + sig_dsp_Signal_destroy(allocator, self); +} + + +struct sig_dsp_NoiseGate* sig_dsp_NoiseGate_new( + struct sig_Allocator* allocator, struct sig_SignalContext* context) { + struct sig_dsp_NoiseGate* self = sig_MALLOC(allocator, + struct sig_dsp_NoiseGate); + sig_dsp_Signal_SingleMonoOutput_newAudioBlocks(allocator, + context->audioSettings, &self->outputs); + sig_dsp_NoiseGate_init(self, context); + + return self; +} + +void sig_dsp_NoiseGate_init(struct sig_dsp_NoiseGate* self, + struct sig_SignalContext* context) { + sig_dsp_Signal_init(self, context, *sig_dsp_NoiseGate_generate); + + struct sig_dsp_ScaleOffset_Parameters parameters = { + .scale = 1.0, + .offset= 0.0f + }; + self->parameters = parameters; + + sig_CONNECT_TO_SILENCE(self, source, context); +} + +void sig_dsp_NoiseGate_generate(void* signal) { + struct sig_dsp_NoiseGate* self = (struct sig_dsp_NoiseGate*) signal; + + float scale = self->parameters.scale; + float offset = self->parameters.offset; + for (size_t i = 0; i < self->signal.audioSettings->blockSize; i++) { + float source = FLOAT_ARRAY(self->inputs.source)[i]; + float threshold = FLOAT_ARRAY(self->inputs.threshold)[i]; + + float sample = source >= threshold ? source * scale + offset : 0.0f; + FLOAT_ARRAY(self->outputs.main)[i] = sample; + } +} + +void sig_dsp_NoiseGate_destroy(struct sig_Allocator* allocator, + struct sig_dsp_NoiseGate* self) { + sig_dsp_Signal_SingleMonoOutput_destroyAudioBlocks(allocator, + &self->outputs); + sig_dsp_Signal_destroy(allocator, (void*) self); +} diff --git a/libsignaletic/tests/test-libsignaletic.c b/libsignaletic/tests/test-libsignaletic.c index 2245e78..896f710 100644 --- a/libsignaletic/tests/test-libsignaletic.c +++ b/libsignaletic/tests/test-libsignaletic.c @@ -554,7 +554,7 @@ void destroyOscInputs(struct sig_Allocator* allocator, allocator->impl->free(allocator, sineInputs->add); } -void test_sig_dsp_Sine(void) { +void test_sig_dsp_SineOscillator(void) { // Generated from this program: /* #include @@ -597,7 +597,7 @@ void test_sig_dsp_Sine(void) { struct sig_SignalContext* mono441kContext = sig_SignalContext_new( &allocator, &mono441kAudioSettings); - struct sig_dsp_Oscillator* sine = sig_dsp_Sine_new(&allocator, + struct sig_dsp_Oscillator* sine = sig_dsp_SineOscillator_new(&allocator, mono441kContext); createOscInputs(&allocator, sine, 440.0f, 0.0f, 1.0f, 0.0f); @@ -609,11 +609,11 @@ void test_sig_dsp_Sine(void) { } destroyOscInputs(&allocator, &sine->inputs); - sig_dsp_Sine_destroy(&allocator, sine); + sig_dsp_SineOscillator_destroy(&allocator, sine); sig_SignalContext_destroy(&allocator, mono441kContext); } -void test_test_sig_dsp_Sine_isOffset(void) { +void test_test_sig_dsp_SineOscillator_isOffset(void) { float expected[BLOCK_SIZE] = { 1.00000000f, 1.06264830f, 1.12505054f, 1.18696141f, 1.24813783f, 1.30833936f, 1.36732960f, 1.42487669f, @@ -636,7 +636,7 @@ void test_test_sig_dsp_Sine_isOffset(void) { struct sig_SignalContext* mono441kContext = sig_SignalContext_new( &allocator, &mono441kAudioSettings); - struct sig_dsp_Oscillator* sine = sig_dsp_Sine_new(&allocator, + struct sig_dsp_Oscillator* sine = sig_dsp_SineOscillator_new(&allocator, mono441kContext); createOscInputs(&allocator, sine, 440.0f, 0.0f, 1.0f, 1.0f); @@ -647,12 +647,12 @@ void test_test_sig_dsp_Sine_isOffset(void) { sine->signal.audioSettings->blockSize); destroyOscInputs(&allocator, &sine->inputs); - sig_dsp_Sine_destroy(&allocator, sine); + sig_dsp_SineOscillator_destroy(&allocator, sine); sig_SignalContext_destroy(&allocator, mono441kContext); } -void test_sig_dsp_Sine_accumulatesPhase(void) { - struct sig_dsp_Oscillator* sine = sig_dsp_Sine_new(&allocator, +void test_sig_dsp_SineOscillator_accumulatesPhase(void) { + struct sig_dsp_Oscillator* sine = sig_dsp_SineOscillator_new(&allocator, context); createOscInputs(&allocator, sine, 440.0f, 0.0f, 1.0f, 0.0f); @@ -676,11 +676,11 @@ void test_sig_dsp_Sine_accumulatesPhase(void) { ); destroyOscInputs(&allocator, &sine->inputs); - sig_dsp_Sine_destroy(&allocator, sine); + sig_dsp_SineOscillator_destroy(&allocator, sine); } -void test_sig_dsp_Sine_phaseWrapsAt2PI(void) { - struct sig_dsp_Oscillator* sine = sig_dsp_Sine_new(&allocator, +void test_sig_dsp_SineOscillator_phaseWrapsAt2PI(void) { + struct sig_dsp_Oscillator* sine = sig_dsp_SineOscillator_new(&allocator, context); createOscInputs(&allocator, sine, 440.0f, 0.0f, 1.0f, 0.0f); @@ -695,7 +695,7 @@ void test_sig_dsp_Sine_phaseWrapsAt2PI(void) { ); destroyOscInputs(&allocator, &sine->inputs); - sig_dsp_Sine_destroy(&allocator, sine); + sig_dsp_SineOscillator_destroy(&allocator, sine); } void testDust(struct sig_dsp_Dust* dust, @@ -1285,10 +1285,10 @@ int main(void) { RUN_TEST(test_sig_dsp_ConstantValue); RUN_TEST(test_sig_dsp_TimedTriggerCounter); RUN_TEST(test_sig_dsp_Mul); - RUN_TEST(test_sig_dsp_Sine); - RUN_TEST(test_sig_dsp_Sine_accumulatesPhase); - RUN_TEST(test_sig_dsp_Sine_phaseWrapsAt2PI); - RUN_TEST(test_test_sig_dsp_Sine_isOffset); + RUN_TEST(test_sig_dsp_SineOscillator); + RUN_TEST(test_sig_dsp_SineOscillator_accumulatesPhase); + RUN_TEST(test_sig_dsp_SineOscillator_phaseWrapsAt2PI); + RUN_TEST(test_test_sig_dsp_SineOscillator_isOffset); RUN_TEST(test_sig_dsp_Dust); RUN_TEST(test_sig_dsp_ClockDetector_square); RUN_TEST(test_sig_dsp_ClockDetector_sine); diff --git a/libsignaletic/wasm/bindings/libsignaletic-web-bindings.idl b/libsignaletic/wasm/bindings/libsignaletic-web-bindings.idl index 47f095a..1081349 100644 --- a/libsignaletic/wasm/bindings/libsignaletic-web-bindings.idl +++ b/libsignaletic/wasm/bindings/libsignaletic-web-bindings.idl @@ -98,18 +98,27 @@ interface sig_dsp_ConstantValue { [Value] attribute sig_dsp_Signal_SingleMonoOutput outputs; }; -interface sig_dsp_Abs_Inputs { +interface sig_dsp_Signal_SingleSourceInput { attribute any source; }; interface sig_dsp_Abs { [Value] attribute sig_dsp_Signal signal; - [Value] attribute sig_dsp_Abs_Inputs inputs; + [Value] attribute sig_dsp_Signal_SingleSourceInput inputs; [Value] attribute sig_dsp_Signal_SingleMonoOutput outputs; }; -interface sig_dsp_ScaleOffset_Inputs { - attribute any source; + +interface sig_dsp_Clamp_Parameters { + attribute float min; + attribute float max; +}; + +interface sig_dsp_Clamp { + [Value] attribute sig_dsp_Signal signal; + [Value] attribute sig_dsp_Signal_SingleSourceInput inputs; + [Value] attribute sig_dsp_Clamp_Parameters; + [Value] attribute sig_dsp_Signal_SingleMonoOutput outputs; }; interface sig_dsp_ScaleOffset_Parameters { @@ -119,11 +128,18 @@ interface sig_dsp_ScaleOffset_Parameters { interface sig_dsp_ScaleOffset { [Value] attribute sig_dsp_Signal signal; - [Value] attribute sig_dsp_ScaleOffset_Inputs inputs; + [Value] attribute sig_dsp_Signal_SingleSourceInput inputs; [Value] attribute sig_dsp_ScaleOffset_Parameters parameters; [Value] attribute sig_dsp_Signal_SingleMonoOutput outputs; }; +interface sig_dsp_Sine { + [Value] attribute sig_dsp_Signal signal; + [Value] attribute sig_dsp_Signal_SingleSourceInput inputs; + [Value] attribute sig_dsp_Signal_SingleMonoOutput outputs; +}; + + interface sig_dsp_BinaryOp_Inputs { attribute any left; attribute any right; @@ -660,6 +676,18 @@ interface sig_dsp_LinearXFade { [Value] attribute sig_dsp_Signal_SingleMonoOutput outputs; }; +interface sig_dsp_SineWavefolder_Inputs { + attribute any source; + attribute any gain; + attribute any factor; +}; + +interface sig_dsp_Sinewavefolder { + [Value] attribute sig_dsp_Signal signal; + [Value] attribute sig_dsp_Sinewavefolder_Inputs inputs; + [Value] attribute sig_dsp_Signal_SingleMonoOutput outputs; +}; + interface sig_dsp_Calibrator_Inputs { attribute any source; attribute any gate; @@ -712,16 +740,25 @@ interface Signals { void ConstantValue_destroy(sig_Allocator allocator, sig_dsp_ConstantValue signal); - sig_dsp_Abs Abs_new(sig_Allocator allocator, sig_SignalContext context); + sig_dsp_Abs Abs_new(sig_Allocator allocator, + sig_SignalContext context); void Abs_init(sig_dsp_Abs signal, sig_SignalContext context); void Abs_generate(any signal); void Abs_destroy(sig_Allocator allocator, sig_dsp_Abs signal); - sig_dsp_ScaleOffset ScaleOffset_new(sig_Allocator allocator, sig_SignalContext context); + sig_dsp_ScaleOffset ScaleOffset_new(sig_Allocator allocator, + sig_SignalContext context); void ScaleOffset_init(sig_dsp_ScaleOffset signal, sig_SignalContext context); void ScaleOffset_generate(any signal); void ScaleOffset_destroy(sig_Allocator allocator, sig_dsp_ScaleOffset signal); + sig_dsp_Sine Sine_new(sig_Allocator allocator, + sig_SignalContext context); + void Sine_init(sig_dsp_Sine signal, sig_SignalContext context); + void Sine_generate(any signal); + void Sine_destroy(sig_Allocator allocator, + sig_dsp_Sine signal); + sig_dsp_BinaryOp Add_new(sig_Allocator allocator, sig_SignalContext context); void Add_init(sig_dsp_BinaryOp signal, sig_SignalContext context); @@ -788,11 +825,13 @@ interface Signals { void Oscillator_accumulatePhase(sig_dsp_Oscillator signal, unsigned long i); void Oscillator_destroy(sig_Allocator allocator, sig_dsp_Oscillator signal); - void Sine_init(sig_dsp_Oscillator signal, sig_SignalContext context); - sig_dsp_Oscillator Sine_new(sig_Allocator allocator, + void SineOscillator_init(sig_dsp_Oscillator signal, sig_SignalContext context); - void Sine_generate(any signal); - void Sine_destroy(sig_Allocator allocator, sig_dsp_Oscillator signal); + sig_dsp_Oscillator SineOscillator_new(sig_Allocator allocator, + sig_SignalContext context); + void SineOscillator_generate(any signal); + void SineOscillator_destroy(sig_Allocator allocator, + sig_dsp_Oscillator signal); void LFTriangle_init(sig_dsp_Oscillator signal, sig_SignalContext context); sig_dsp_Oscillator LFTriangle_new(sig_Allocator allocator, @@ -960,20 +999,41 @@ interface Signals { sig_dsp_LinearXFade LinearXFade_new(sig_Allocator allocator, sig_SignalContext context); - void LinearXFade_init(sig_dsp_LinearXFade signal, sig_SignalContext context); + void LinearXFade_init(sig_dsp_LinearXFade signal, + sig_SignalContext context); void LinearXFade_generate(any signal); - void LinearXFade_destroy(sig_Allocator allocator, sig_dsp_LinearXFade signal); + void LinearXFade_destroy(sig_Allocator allocator, + sig_dsp_LinearXFade signal); - void Calibrator_Node_init(sig_dsp_Calibrator_Node nodes, any targetValues, - unsigned long numNodes); + void Calibrator_Node_init(sig_dsp_Calibrator_Node nodes, + any targetValues, unsigned long numNodes); unsigned long Calibrator_locateIntervalForValue(float x, sig_dsp_Calibrator_Node nodes, unsigned long numNodes); - float Calibrator_fitValueToCalibrationData(float x, sig_dsp_Calibrator_Node nodes, unsigned long numNodes); + float Calibrator_fitValueToCalibrationData(float x, + sig_dsp_Calibrator_Node nodes, unsigned long numNodes); sig_dsp_Calibrator Calibrator_new(sig_Allocator allocator, sig_SignalContext context); - void Calibrator_init(sig_dsp_Calibrator signal, sig_SignalContext context); + void Calibrator_init(sig_dsp_Calibrator signal, + sig_SignalContext context); void Calibrator_generate(any signal); - void Calibrator_destroy(sig_Allocator allocator, sig_dsp_Calibrator signal); + void Calibrator_destroy(sig_Allocator allocator, + sig_dsp_Calibrator signal); + + sig_dsp_Sinewavefolder Sinewavefolder_new(sig_Allocator allocator, + sig_SignalContext context); + void Sinewavefolder_init(sig_dsp_Sinewavefolder signal, + sig_SignalContext context); + void Sinewavefolder_generate(any signal); + void Sinewavefolder_destroy(sig_Allocator allocator, + sig_dsp_Sinewavefolder signal); + + sig_dsp_NoiseGate sig_dsp_NoiseGate_new( + sig_Allocator allocator, sig_SignalContext context); + void sig_dsp_NoiseGate_init(sig_dsp_NoiseGate signal, + sig_SignalContext context); + void sig_dsp_NoiseGate_generate(any signal); + void sig_dsp_NoiseGate_destroy(sig_Allocator allocator, + sig_dsp_NoiseGate signal); }; interface Signaletic { @@ -1136,4 +1196,5 @@ interface Signaletic { void DelayLine_destroy(sig_Allocator allocator, sig_DelayLine delayLine); float linearXFade(float left, float right, float mix); + float sineWavefolder(float x, float gain, float factor); }; diff --git a/libsignaletic/wasm/bindings/src/libsignaletic-web.cpp b/libsignaletic/wasm/bindings/src/libsignaletic-web.cpp index 9e2b4d1..eb143c7 100644 --- a/libsignaletic/wasm/bindings/src/libsignaletic-web.cpp +++ b/libsignaletic/wasm/bindings/src/libsignaletic-web.cpp @@ -102,6 +102,26 @@ class Signals { return sig_dsp_Abs_destroy(allocator, self); } + struct sig_dsp_Clamp* Clamp_new( + struct sig_Allocator* allocator, + struct sig_SignalContext* context) { + return sig_dsp_Clamp_new(allocator, context); + } + + void Clamp_init(struct sig_dsp_Clamp* self, + struct sig_SignalContext* context) { + sig_dsp_Clamp_init(self, context); + } + + void Clamp_generate(void* signal) { + sig_dsp_Clamp_generate(signal); + } + + void Clamp_destroy(struct sig_Allocator* allocator, + struct sig_dsp_Clamp* self) { + return sig_dsp_Clamp_destroy(allocator, self); + } + struct sig_dsp_ScaleOffset* ScaleOffset_new( struct sig_Allocator* allocator, struct sig_SignalContext* context) { @@ -122,6 +142,28 @@ class Signals { return sig_dsp_ScaleOffset_destroy(allocator, self); } + + struct sig_dsp_Sine* Sine_new( + struct sig_Allocator* allocator, + struct sig_SignalContext* context) { + return sig_dsp_Sine_new(allocator, context); + } + + void Sine_init(struct sig_dsp_Sine* self, + struct sig_SignalContext* context) { + sig_dsp_Sine_init(self, context); + } + + void Sine_generate(void* signal) { + sig_dsp_Sine_generate(signal); + } + + void Sine_destroy(struct sig_Allocator* allocator, + struct sig_dsp_Sine* self) { + return sig_dsp_Sine_destroy(allocator, self); + } + + struct sig_dsp_BinaryOp* Add_new( struct sig_Allocator* allocator, struct sig_SignalContext* context) { @@ -335,23 +377,24 @@ class Signals { sig_dsp_Oscillator_destroy(allocator, self); } - struct sig_dsp_Oscillator* Sine_new(struct sig_Allocator* allocator, + struct sig_dsp_Oscillator* SineOscillator_new( + struct sig_Allocator* allocator, struct sig_SignalContext* context) { - return sig_dsp_Sine_new(allocator, context); + return sig_dsp_SineOscillator_new(allocator, context); } - void Sine_init(struct sig_dsp_Oscillator* self, + void SineOscillator_init(struct sig_dsp_Oscillator* self, struct sig_SignalContext* context) { - sig_dsp_Sine_init(self, context); + sig_dsp_SineOscillator_init(self, context); } - void Sine_generate(void* signal) { - sig_dsp_Sine_generate(signal); + void SineOscillator_generate(void* signal) { + sig_dsp_SineOscillator_generate(signal); } - void Sine_destroy(struct sig_Allocator* allocator, + void SineOscillator_destroy(struct sig_Allocator* allocator, struct sig_dsp_Oscillator* self) { - return sig_dsp_Sine_destroy(allocator, self); + return sig_dsp_SineOscillator_destroy(allocator, self); } struct sig_dsp_Oscillator* LFTriangle_new(struct sig_Allocator* allocator, @@ -954,6 +997,47 @@ class Signals { struct sig_dsp_Calibrator* self) { return sig_dsp_Calibrator_destroy(allocator, self); } + + struct sig_dsp_SineWavefolder* SineWavefolder_new( + struct sig_Allocator* allocator, + struct sig_SignalContext* context) { + return sig_dsp_SineWavefolder_new(allocator, context); + } + + void SineWavefolder_init(struct sig_dsp_SineWavefolder* self, + struct sig_SignalContext* context) { + sig_dsp_SineWavefolder_init(self, context); + } + + void SineWavefolder_generate(void* signal) { + sig_dsp_SineWavefolder_generate(signal); + } + + void SineWavefolder_destroy(struct sig_Allocator* allocator, + struct sig_dsp_SineWavefolder* self) { + return sig_dsp_SineWavefolder_destroy(allocator, self); + } + + + struct sig_dsp_NoiseGate* NoiseGate_new( + struct sig_Allocator* allocator, + struct sig_SignalContext* context) { + return sig_dsp_NoiseGate_new(allocator, context); + } + + void NoiseGate_init(struct sig_dsp_NoiseGate* self, + struct sig_SignalContext* context) { + sig_dsp_NoiseGate_init(self, context); + } + + void NoiseGate_generate(void* signal) { + sig_dsp_NoiseGate_generate(signal); + } + + void NoiseGate_destroy(struct sig_Allocator* allocator, + struct sig_dsp_NoiseGate* self) { + return sig_dsp_NoiseGate_destroy(allocator, self); + } }; class Signaletic { @@ -1471,6 +1555,10 @@ class Signaletic { return sig_linearXFade(left, right, mix); } + float sineWavefolder(float x, float gain, float factor) { + return sig_sineWavefolder(x, gain, factor); + } + void SingleMonoOutput_newAudioBlocks( struct sig_Allocator* allocator, struct sig_AudioSettings* audioSettings,