Delay With Feedback

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.

typical delay configuration with feedback and wet./dry mix.

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.

compression after feedback/input mix to keep signal under 1.0

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).

y=1.60154 – 1.60573x + 0.88839x^2 – 0.18048x^3 (gain of 1 at .5, gain of .5 at 2)
// 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;
}


Leave a Reply

Your email address will not be published.