Cookbook

Feedback delay DSP tutorial with a playable example

A delay line, a feedback path, and an index that wraps instead of faulting.

Anyone who wants an echo — or the beginnings of a reverb.

An echo is a conversation with an earlier sample. Write today's input into a circular buffer, then read the place where an older sample was stored. Feed some of that old sample back into the buffer and the conversation repeats.

The array provides the memory; a write position says where “now” is. Its wrapped indexing lets the position move around the buffer without copying any samples.

Controls#

ParameterDefaultRangeMeaning
time0.29 s0.005–0.65 sRequested spacing between echoes, limited by the buffer capacity.
feedback0.550–0.92Fraction of an echo written back for the next repeat.
mix0.460–1Dry/wet blend: 0 is the input, 1 is only the delayed signal.
level0.80–1Gain applied after the blend.

Both endpoints are mono. A short sound or an impulse makes the repeats easiest to follow.

The complete patch#

A delayPlayground
processor Echo
{
    input  stream float in;
    param  float time     = 0.29f [0.005f, 0.65f] smooth 30.0f;
    param  float feedback = 0.55f [0.0f, 0.92f]   smooth 20.0f;
    param  float mix      = 0.46f [0.0f, 1.0f]    smooth 20.0f;
    param  float level    = 0.8f  [0.0f, 1.0f]    smooth 20.0f;
    output stream float out;
    float line[65536];

    float pos = 0.0f;

    void main()
    {
        loop
        {
            let delaySamples = clamp(int(time * processor.frequency), 1, 65535);
            let w = int(pos);
            let r = w - delaySamples;

            let delayed = line[r];
            line[w] = in + delayed * min(feedback, 0.92f);
            let next = pos + 1.0f;
            pos = next > 65535.0f ? 0.0f : next;

            out <- (in * (1.0f - mix) + delayed * mix) * level;
            advance();
        }
    }
}

Follow one trip around the buffer#

  1. Convert time from seconds to a sample count using the current sample rate. Clamp it between 1 and 65,535 so it fits this buffer.
  2. Read line[w - delaySamples]. Wrapped indexing finds the older sample even when the subtraction is negative.
  3. Write in + delayed * feedback at the current position. An echo at feedback 0.5 returns next time at half its previous amplitude.
  4. Advance the write position, then blend the current input with the delayed sample. The buffer begins at zero, so there is no echo until it has history.

The array has 65,536 samples. At 96 kHz it holds about 683 ms, so the entire 5–650 ms control range fits at 44.1, 48 and 96 kHz. Clamping to its capacity prevents a long requested delay from wrapping into a short, unintended one.

Try it#

Start with feedback at 0 to hear one repeat. Raise it gradually to hear a tail develop. Then move time while audio is running: this example uses an integer read position, so smoothing the time control does not provide fractional-sample interpolation. The chorus shows how to interpolate a moving delay.

If it is silent at first: at mix = 1, the dry sound is removed. You must wait one delay time before the first input reaches the output.

Next: a reverb, which is a great many of these at once.