Adding feedback to a delay is fairly straightforward: output = input + delayouput * feedback , where feedback ranges from 0.0 to 1.0 (and often over 1.0). Adding a feedback path is what creates multiple echoes, with a naturally exponential decay. A typical implementation is to send the delay output to an input/output mix, and apply feedback gain before the delay output is mixed with the input. In this configuration, mix determines the balance of the delay and the input, and feedback controls the decay of each echo.
Note that delay output * feedback + input, has a gain of 1.0 + feedback. This will drive the delay into clipping when there are input signals over 0.5. There are a number of options to control the gain (keep it at 1.0): gain reduction based on feedback, compression, saturation, or a combination of the these. Simple gain reduction would place a gain element before the delay input so that the overall gain is reduced as the feedback increases.
delayinput = (input + delayoutput * feedback) * (1.0/(1.0 + feedback))This is the cleanest sounding approach, but is sometimes felt to be too “clinical”. A combination of compressor and saturator is more typically used.
This compressor will measure the level before the delay input and apply a gain lower than 1.0 to control the signal. This gain can be applied before the delay input or to the feedback signal.
Finally, when implementing any feedback network, there is a danger of DC offset building up. If using feedback higher than 1.0 this is especially likely. A DC blocking filter (a high pass at a subsonic frequency) in the feedback path should be tuned to remove any DC offset without negatively affecting low frequencies.
Implementation
The addition of feedback and wet/dry mix to a delay is fairly simple. Saturation can be added before the delay input. A compressor can be implemented as it was in the earlier section, but in the case where the threshold is fixed and the amount of gain reduction needed is known, a gain table can be kept in memory, or a polynomial can be used to calculate gain. This polynomial can be created by curve fitting through the known gain reduction points (polyfit in MatLab can do this, as can several online solvers).
// this code uses the earlier linear interpolation method long writePointer, delayMask; float *delayBuffer; float delayTime; float sampleRate = 48000.0f; float peak; void DelayInit(long delaySize) { delayBuffer = new float[delaySize]; delayMask = delaySize - 1; writePointer = 0; delayTime = 0.0f; peak = 0.0f; } void DelayTerm(void) { delete[] delayBuffer; } void Delay(float *input, float *output, long samples, float time, float feedback, float mix) { float readPointer; long readPointerLong, i; float fraction; float x0, x1, out, in, rect; delayTimeInc; // find increment to smooth out delayTime delayTimeInc = (time - delayTime)/samples; for(i = 0; i < samples; i++) { in = *(input+i); // linear interpolated read readPointer = writePointer + (delayTime*sampleRate); readPointerLong = (long)readPointer; fraction = readPointer - readPointerLong; // both points need to be masked to keep them within the delay buffer x0 = *(delayBuffer + (readPointerLong & bufferMask)); x1 = *(delayBuffer + ((readPointerLong + 1) & bufferMask)); out = x1 + (x2 - x1) * fraction; // wet/dry mix *(output+i) = out * mix + in * (1.0 - mix); // add feedback output to input rect = in = in + feedback * out; // rectify input for simple peak detection if(rect < 0.0) rect = rect * -1.0; // if the signal is over the peak, use one pole filter for quick fade of peak to signal if(peak < rect) peak = peak + (rect - peak) * 0.9f; // otherwise fade peak down slowly else peak *= 0.9999f; // polynomial compression // if feedback goes up to 1.0, a maximum peak of 2.0 is possible if(peak > 2.0) peak = 2.0; else if(peak < 0.5f) peak = 0.5f; in = in * (1.601539 - (1.605725 * peak) + (0.8883899 * peak * peak) - (0.180484 * peak * peak * peak)); // now it can go in the delay *(delaybuffer + writePointer) = in; // move the write pointer and wrap it by masking writePointer--; writePointer &= delayMask; // smooth the delayTime with the increment delayTime = delayTime + delayTimeInc; } delayTime = time; }