Three classic audio effects built from short delay lines:
These are simple enough to study sample-by-sample, but they are also the building blocks of many larger audio effects.
Delay line / echo
Delay/echo reads an older sample from a circular delay line and mixes it with the current input:
\[s[n] = x[n] + feedback \cdot s[n-D]
\]
The output mixes dry input with the delayed state:
\[y[n] = dry \cdot x[n] + wet \cdot s[n-D]
\]
where \(D\) is the delay in samples and \(|feedback| < 1\).
Reading the formula in C:
double d = delay[idx];
out[n] = dry * in[n] + wet * d;
delay[idx] = in[n] + feedback * d;
idx = (idx + 1) % delay_samples;
API:
unsigned delay_samples, double feedback,
double dry, double wet);
void MD_delay_echo(const double *in, double *out, unsigned N, unsigned delay_samples, double feedback, double dry, double wet)
Delay line / echo effect using a circular buffer with feedback.
Quick example:
Audio (before/after):
Before (dry click train)
After (echo: delay=11025, feedback=0.45, dry=1.0, wet=0.6)
Spectrograms (before/after):
Tremolo
Tremolo is amplitude modulation by a low-frequency oscillator (LFO). The gain is:
\[g[n] = (1-depth) + depth \cdot \frac{1 + \sin(2\pi f_{LFO}n/f_s)}{2}
\]
and output is:
\[y[n] = g[n] \cdot x[n]
\]
So gain moves between \(1-depth\) and \(1\).
Reading the formula in C:
double lfo = 0.5 * (1.0 + sin(2.0 * M_PI * rate_hz * n / sample_rate));
double gain = (1.0 - depth) + depth * lfo;
out[n] = in[n] * gain;
API:
void MD_tremolo(
const double *in,
double *out,
unsigned N,
double rate_hz, double depth, double sample_rate);
void MD_tremolo(const double *in, double *out, unsigned N, double rate_hz, double depth, double sample_rate)
Tremolo effect (amplitude modulation by a sinusoidal LFO).
Quick example:
MD_tremolo(trem_src, trem_out, N, 5.0, 0.8, sample_rate);
Audio (before/after):
Before (dry sine, 220 Hz)
After (tremolo: rate=5.0 Hz, depth=0.8)
Spectrograms (before/after):
Comb-filter reverb
A feedback comb filter reuses a delayed copy of its own output:
\[c[n] = x[n] + feedback \cdot c[n-D]
\]
Then we blend dry input and comb output:
\[y[n] = dry \cdot x[n] + wet \cdot c[n]
\]
The repeated, closely spaced echoes create a reverb-like resonant tail.
Reading the formula in C:
double delayed = comb[idx];
double c = in[n] + feedback * delayed;
comb[idx] = c;
out[n] = dry * in[n] + wet * c;
idx = (idx + 1) % delay_samples;
API:
unsigned delay_samples, double feedback,
double dry, double wet);
void MD_comb_reverb(const double *in, double *out, unsigned N, unsigned delay_samples, double feedback, double dry, double wet)
Comb-filter reverb (feedback comb filter with dry/wet mix).
Quick example:
Audio (before/after):
Before (dry decaying tone burst)
After (comb reverb: delay=1323, feedback=0.75, dry=0.7, wet=0.6)
Spectrograms (before/after):
Verification tips
- Delay echo impulse input should repeat every delay_samples, decaying by feedback^m.
- Tremolo with depth = 0 should exactly match the input.
- Comb reverb with dry = 0 and impulse input should produce a geometric series at delay multiples.
API reference