Skip to content

Commit

Permalink
lichen-community-systemsgh-67: Adds low pass filtering to sig_dsp_Comb.
Browse files Browse the repository at this point in the history
Updates Shroeder comb/allpass example with LPF.
  • Loading branch information
colinbdclark committed Dec 27, 2023
1 parent 9d08c00 commit 1ceee7f
Show file tree
Hide file tree
Showing 3 changed files with 104 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -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);
Expand Down
26 changes: 25 additions & 1 deletion libsignaletic/include/libsignaletic.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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
Expand Down Expand Up @@ -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 {
Expand All @@ -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(
Expand Down
79 changes: 66 additions & 13 deletions libsignaletic/src/libsignaletic.c
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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;
Expand All @@ -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

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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) {
Expand All @@ -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) {
Expand All @@ -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;
}
}

Expand Down

0 comments on commit 1ceee7f

Please sign in to comment.