diff --git a/hosts/daisy/examples/bluemchen/reverb/1962c/src/signaletic-bluemchen-1962c.cpp b/hosts/daisy/examples/bluemchen/reverb/1962c/src/signaletic-bluemchen-1962c.cpp index 0d54000..2d368ab 100644 --- a/hosts/daisy/examples/bluemchen/reverb/1962c/src/signaletic-bluemchen-1962c.cpp +++ b/hosts/daisy/examples/bluemchen/reverb/1962c/src/signaletic-bluemchen-1962c.cpp @@ -43,6 +43,7 @@ struct sig_daisy_AudioIn* audioIn; struct sig_daisy_FilteredCVIn* delayTimeScaleKnob; struct sig_daisy_FilteredCVIn* decayTimeKnob; struct sig_dsp_ConstantValue* apGain; +struct sig_dsp_ConstantValue* combLPFCoefficient; struct sig_DelayLine* dl1; struct sig_dsp_ConstantValue* c1DelayTime; struct sig_dsp_BinaryOp* c1ScaledDelayTime; @@ -62,7 +63,7 @@ struct sig_dsp_Comb* c4; struct sig_dsp_BinaryOp* sum1; struct sig_dsp_BinaryOp* sum2; struct sig_dsp_BinaryOp* sum3; -struct sig_dsp_ConstantValue* zeroPointTwoFive; +struct sig_dsp_ConstantValue* combGain; struct sig_dsp_BinaryOp* combScale; struct sig_DelayLine* dl5; struct sig_dsp_ConstantValue* ap1DelayTime; @@ -117,19 +118,20 @@ void buildSignalGraph(struct sig_SignalContext* context, delayTimeScaleKnob = sig_daisy_FilteredCVIn_new(&allocator, context, host); sig_List_append(&signals, delayTimeScaleKnob, status); delayTimeScaleKnob->parameters.control = sig_daisy_Bluemchen_CV_IN_KNOB_1; - delayTimeScaleKnob->parameters.scale = 99.99f; + delayTimeScaleKnob->parameters.scale = 99.999f; delayTimeScaleKnob->parameters.offset = 0.001f; // Lots of smoothing to help with the pitch shift that occurs // when modulating a delay line. - delayTimeScaleKnob->parameters.time = 0.5f; + delayTimeScaleKnob->parameters.time = 0.25f; decayTimeKnob = sig_daisy_FilteredCVIn_new(&allocator, context, host); sig_List_append(&signals, decayTimeKnob, status); decayTimeKnob->parameters.control = sig_daisy_Bluemchen_CV_IN_KNOB_2; - decayTimeKnob->parameters.scale = 99.99f; - decayTimeKnob->parameters.offset = 0.01f; + decayTimeKnob->parameters.scale = 99.999f; + decayTimeKnob->parameters.offset = 0.001f; apGain = sig_dsp_ConstantValue_new(&allocator, context, 0.7f); + combLPFCoefficient = sig_dsp_ConstantValue_new(&allocator, context, 0.55f); dl1 = sig_DelayLine_new(&delayLineAllocator, MAX_DELAY_LINE_LENGTH); c1DelayTime = sig_dsp_ConstantValue_new(&allocator, context, 0.0297f); @@ -143,6 +145,7 @@ void buildSignalGraph(struct sig_SignalContext* context, c1->inputs.source = audioIn->outputs.main; c1->inputs.decayTime = decayTimeKnob->outputs.main; c1->inputs.delayTime = c1ScaledDelayTime->outputs.main; + c1->inputs.lpfCoefficient = combLPFCoefficient->outputs.main; dl2 = sig_DelayLine_new(&delayLineAllocator, MAX_DELAY_LINE_LENGTH); c2DelayTime = sig_dsp_ConstantValue_new(&allocator, context, 0.0371f); @@ -156,6 +159,7 @@ void buildSignalGraph(struct sig_SignalContext* context, c2->inputs.source = audioIn->outputs.main; c2->inputs.decayTime = decayTimeKnob->outputs.main; c2->inputs.delayTime = c2ScaledDelayTime->outputs.main; + c2->inputs.lpfCoefficient = combLPFCoefficient->outputs.main; dl3 = sig_DelayLine_new(&delayLineAllocator, MAX_DELAY_LINE_LENGTH); c3DelayTime = sig_dsp_ConstantValue_new(&allocator, context, 0.0411f); @@ -169,6 +173,7 @@ void buildSignalGraph(struct sig_SignalContext* context, c3->inputs.source = audioIn->outputs.main; c3->inputs.decayTime = decayTimeKnob->outputs.main; c3->inputs.delayTime = c3ScaledDelayTime->outputs.main; + c3->inputs.lpfCoefficient = combLPFCoefficient->outputs.main; dl4 = sig_DelayLine_new(&delayLineAllocator, MAX_DELAY_LINE_LENGTH); c4DelayTime = sig_dsp_ConstantValue_new(&allocator, context, 0.0437f); @@ -182,6 +187,7 @@ void buildSignalGraph(struct sig_SignalContext* context, c4->inputs.source = audioIn->outputs.main; c4->inputs.decayTime = decayTimeKnob->outputs.main; c4->inputs.delayTime = c4ScaledDelayTime->outputs.main; + c4->inputs.lpfCoefficient = combLPFCoefficient->outputs.main; sum1 = sig_dsp_Add_new(&allocator, context); sig_List_append(&signals, sum1, status); @@ -198,11 +204,11 @@ void buildSignalGraph(struct sig_SignalContext* context, sum3->inputs.left = sum2->outputs.main; sum3->inputs.right = c4->outputs.main; - zeroPointTwoFive = sig_dsp_ConstantValue_new(&allocator, context, 0.25f); + combGain = sig_dsp_ConstantValue_new(&allocator, context, 0.2f); combScale = sig_dsp_Mul_new(&allocator, context); sig_List_append(&signals, combScale, status); combScale->inputs.left = sum3->outputs.main; - combScale->inputs.right = zeroPointTwoFive->outputs.main; + combScale->inputs.right = combGain->outputs.main; ap1DelayTime = sig_dsp_ConstantValue_new(&allocator, context, 0.09683f); dl5 = sig_DelayLine_new(&delayLineAllocator, MAX_DELAY_LINE_LENGTH); diff --git a/libsignaletic/include/libsignaletic.h b/libsignaletic/include/libsignaletic.h index 1b47c5e..e45f656 100644 --- a/libsignaletic/include/libsignaletic.h +++ b/libsignaletic/include/libsignaletic.h @@ -854,7 +854,9 @@ float sig_DelayLine_cubicReadAt(struct sig_DelayLine* self, float readPos); void sig_DelayLine_write(struct sig_DelayLine* self, float sample); -float sig_DelayLine_calcCombFeedback(float delayTime, float decayTime); +float sig_DelayLine_calcFeedbackGain(float delayTime, float decayTime); + +float sig_DelayLine_feedback(float sample, float read, float g); float sig_DelayLine_comb(struct sig_DelayLine* self, float sample, size_t readPos, float g); @@ -874,6 +876,7 @@ void sig_DelayLine_destroy(struct sig_Allocator* allocator, float sig_linearXFade(float left, float right, float mix); + // TODO: Should the signal argument at least be defined // as a struct sig_dsp_Signal*, rather than void*? // Either way, this is cast by the implementation to whatever @@ -2066,12 +2069,32 @@ void sig_dsp_DelayTap_generate(void* signal); void sig_dsp_DelayTap_destroy(struct sig_Allocator* allocator, struct sig_dsp_Delay* self); +struct sig_dsp_DelayWrite_Inputs { + float_array_ptr source; +}; + +struct sig_dsp_DelayWrite { + struct sig_dsp_Signal signal; + struct sig_dsp_DelayWrite_Inputs inputs; + struct sig_dsp_Signal_SingleMonoOutput outputs; + + struct sig_DelayLine* delayLine; +}; + +struct sig_dsp_DelayWrite* sig_dsp_DelayWrite_new( + struct sig_Allocator* allocator, struct sig_SignalContext* context); +void sig_dsp_DelayWrite_init(struct sig_dsp_DelayWrite* self, + struct sig_SignalContext* context); +void sig_dsp_DelayWrite_generate(void* signal); +void sig_dsp_DelayWrite_destroy(struct sig_Allocator* allocator, + struct sig_dsp_DelayWrite* self); struct sig_dsp_Comb_Inputs { float_array_ptr source; float_array_ptr delayTime; float_array_ptr decayTime; + float_array_ptr lpfCoefficient; }; struct sig_dsp_Comb { @@ -2080,6 +2103,7 @@ struct sig_dsp_Comb { struct sig_dsp_Signal_SingleMonoOutput outputs; struct sig_DelayLine* delayLine; + float previousSample; }; struct sig_dsp_Comb* sig_dsp_Comb_new( diff --git a/libsignaletic/src/libsignaletic.c b/libsignaletic/src/libsignaletic.c index 85c612d..3ac6455 100644 --- a/libsignaletic/src/libsignaletic.c +++ b/libsignaletic/src/libsignaletic.c @@ -659,7 +659,7 @@ void sig_DelayLine_init(struct sig_DelayLine* self) { inline float sig_DelayLine_readAt(struct sig_DelayLine* self, size_t readPos) { size_t idx = (self->writeIdx + readPos) % self->buffer->length; - return self->buffer->samples[idx]; + return FLOAT_ARRAY(self->buffer->samples)[idx]; } inline float sig_DelayLine_linearReadAt(struct sig_DelayLine* self, @@ -697,12 +697,12 @@ inline float sig_DelayLine_cubicReadAt(struct sig_DelayLine* self, inline void sig_DelayLine_write(struct sig_DelayLine* self, float sample) { size_t maxDelayLength = self->buffer->length; - self->buffer->samples[self->writeIdx] = sample; + FLOAT_ARRAY(self->buffer->samples)[self->writeIdx] = sample; self->writeIdx = (self->writeIdx - 1 + maxDelayLength) % maxDelayLength; } -inline float sig_DelayLine_calcCombFeedback(float delayTime, float decayTime) { - // Convert 60dB time in secs to g coefficient +inline float sig_DelayLine_calcFeedbackGain(float delayTime, float decayTime) { + // Convert 60dB time in secs to feedback gain (g) coefficient // (also why is the equation in Dodge and Jerse wrong?) if (delayTime <= 0.0f || decayTime <= 0.0f) { return 0.0f; @@ -711,9 +711,13 @@ inline float sig_DelayLine_calcCombFeedback(float delayTime, float decayTime) { return expf(sig_LOG0_001 * delayTime / decayTime); } +inline float sig_DelayLine_feedback(float sample, float read, float g) { + return sample + (g * read); +} + #define sig_DelayLine_comb_IMPL(self, sample, readPos, g, readFn) \ float read = readFn(self, readPos); \ - float toWrite = sample + (g * read); \ + float toWrite = sig_DelayLine_feedback(sample, read, g); \ sig_DelayLine_write(self, toWrite); \ return read @@ -2993,6 +2997,9 @@ void sig_dsp_Delay_generate(void* signal) { void sig_dsp_Delay_destroy(struct sig_Allocator* allocator, struct sig_dsp_Delay* self) { // Don't destroy the delay line; it isn't owned. + // FIXME: This leaks memory: the initial delay line + // that was allocated in _new is never freed! + // Don't destroy the delay line; it isn't owned. sig_dsp_Signal_SingleMonoOutput_destroyAudioBlocks(allocator, &self->outputs); sig_dsp_Signal_destroy(allocator, self); @@ -3039,6 +3046,48 @@ void sig_dsp_DelayTap_destroy(struct sig_Allocator* allocator, +struct sig_dsp_DelayWrite* sig_dsp_DelayWrite_new( + struct sig_Allocator* allocator, struct sig_SignalContext* context) { + struct sig_dsp_DelayWrite* self = sig_MALLOC(allocator, + struct sig_dsp_DelayWrite); + // TODO: Improve buffer management throughout Signaletic. + self->delayLine = sig_DelayLine_new(allocator, 0); + sig_dsp_DelayWrite_init(self, context); + sig_dsp_Signal_SingleMonoOutput_newAudioBlocks(allocator, + context->audioSettings, &self->outputs); + + return self; +} + +void sig_dsp_DelayWrite_init(struct sig_dsp_DelayWrite* self, + struct sig_SignalContext* context) { + sig_dsp_Signal_init(self, context, *sig_dsp_DelayWrite_generate); + + sig_CONNECT_TO_SILENCE(self, source, context); +} + +void sig_dsp_DelayWrite_generate(void* signal) { + struct sig_dsp_DelayWrite* self = (struct sig_dsp_DelayWrite*) signal; + + for (size_t i = 0; i < self->signal.audioSettings->blockSize; i++) { + float source = FLOAT_ARRAY(self->inputs.source)[i]; + sig_DelayLine_write(self->delayLine, source); + } + +} + +void sig_dsp_DelayWrite_destroy(struct sig_Allocator* allocator, + struct sig_dsp_DelayWrite* self) { + // FIXME: This leaks memory: the initial delay line + // that was allocated in _new is never freed! + // Don't destroy the delay line; it isn't owned. + sig_dsp_Signal_SingleMonoOutput_destroyAudioBlocks(allocator, + &self->outputs); + sig_dsp_Signal_destroy(allocator, self); +} + + + // TODO: Resolve duplication with sig_dsp_Delay struct sig_dsp_Comb* sig_dsp_Comb_new( struct sig_Allocator* allocator, struct sig_SignalContext* context) { @@ -3055,10 +3104,12 @@ struct sig_dsp_Comb* sig_dsp_Comb_new( void sig_dsp_Comb_init(struct sig_dsp_Comb* self, struct sig_SignalContext* context) { sig_dsp_Signal_init(self, context, *sig_dsp_Comb_generate); + self->previousSample = 0.0f; sig_CONNECT_TO_SILENCE(self, source, context); sig_CONNECT_TO_SILENCE(self, delayTime, context); sig_CONNECT_TO_SILENCE(self, decayTime, context); + sig_CONNECT_TO_SILENCE(self, lpfCoefficient, context); } void sig_dsp_Comb_generate(void* signal) { @@ -3069,19 +3120,21 @@ void sig_dsp_Comb_generate(void* signal) { float source = FLOAT_ARRAY(self->inputs.source)[i]; float decayTime = FLOAT_ARRAY(self->inputs.decayTime)[i]; float delayTime = FLOAT_ARRAY(self->inputs.delayTime)[i]; + float lpfCoefficient = FLOAT_ARRAY(self->inputs.lpfCoefficient)[i]; float readPos = (delayTime * self->signal.audioSettings->sampleRate); if (readPos >= maxDelayLength) { readPos = maxDelayLength - 1; } - if (delayTime <= 0.0f || decayTime <= 0.0f) { - FLOAT_ARRAY(self->outputs.main)[i] = source; - } else { - - float g = sig_DelayLine_calcCombFeedback(delayTime, decayTime); - FLOAT_ARRAY(self->outputs.main)[i] = sig_DelayLine_cubicComb( - self->delayLine, source, readPos, g); - } + delayTime = sig_fmaxf(delayTime, 0.00001); // Delay time can't be zero. + float read = sig_DelayLine_cubicReadAt(self->delayLine, readPos); + float outputSample = sig_filter_smooth(read, self->previousSample, + lpfCoefficient); + float g = sig_DelayLine_calcFeedbackGain(delayTime, decayTime); + float toWrite = sig_DelayLine_feedback(source, outputSample, g); + sig_DelayLine_write(self->delayLine, toWrite); + FLOAT_ARRAY(self->outputs.main)[i] = outputSample; + self->previousSample = outputSample; } }