How to add multiple echos with dsp::DelayLine

I’ve been fooling around with the dsp module, and I’m not sure I fully understand how to effectively use the DelayLine to create a delay with multiple echos. My processBlock method (below) is quite simplistic, but works for a single delay.

Now I understand that its just essentially just a wrapper over a circular buffer with some handy math functions, but lets say I wanted to get 3 - 5 echoes gradually reducing in volume. Should I be keeping track of this manually in a separate buffer, or should I create additional delayLines with separate sample delay values and simply add the result from the pop sample into the main buffer using different gains? I feel like this method would use up additional memory unnecessarily. What is the standard for this sort of thing?

void BeatDelayAudioProcessor::processBlock (juce::AudioBuffer<float>& buffer, juce::MidiBuffer& midiMessages)
{
    juce::ScopedNoDenormals noDenormals;
    auto totalNumInputChannels  = getTotalNumInputChannels();
    auto totalNumOutputChannels = getTotalNumOutputChannels();

    for (auto i = totalNumInputChannels; i < totalNumOutputChannels; ++i)
        buffer.clear (i, 0, buffer.getNumSamples());

    float time = *apvts.getRawParameterValue("time");
    float samples = (samplerate * 2) * time;

    const int bufferLength = buffer.getNumSamples();

    for (int channel = 0; channel < totalNumInputChannels; ++channel)
    {
        auto* data = buffer.getWritePointer(channel);

        for (int i = 0; i < bufferLength; i++)
        {
            delayLine->pushSample(channel, data[i]);
            data[i] += delayLine->popSample(channel, samples, false);
        }
    }
}

Hey buddy.

You need to have a circular buffer with read and write indexes. The difference between them would be the delay length in samples.

To get diminishing echoes:

  • First get the input from process block and store it in xN;

  • then you read what is currently in the circular buffer read index into yN;

  • Then, you apply some feedback value on yN, to represent the level of volume decay you want to get.

  • Then, write back xN + feedback*yN back into the circular buffer write index.

  • increment both read and write indexes, wrapping around 0 when needed

Note: this will work only for a constant delay time value. For real-time changes in delay time, it will produce heavy artifacts. Once you have a constant time delay line working properly, start thinking on how to combine 2 delay lines for smoothing and crossfading as you tweak the time knob.

1 Like

Thanks for the details - fiddled around with it this morning and got it to work. I’ve noticed the artifacts as well when playing with the time value, I’ll see if I can figure the rest out. Cheers!

    const int bufferLength = buffer.getNumSamples();
    float time = (*apvts.getRawParameterValue("time")) * sampleRate;
    float feedback = *apvts.getRawParameterValue("feedback");
    
    for (int channel = 0; channel < totalNumInputChannels; ++channel)
    {
        auto* data = buffer.getWritePointer(channel);

        for (int i = 0; i < bufferLength; i++)
        {
            data[i] += (delayLine->popSample(channel, time)) * feedback;
            delayLine->pushSample(channel, data[i]);
        }
    }