A simple single-tap digital delay line implemented in plain C++, intended as a learning resource for understanding the circular buffer; the foundational data structure behind almost every time-based audio effect.
A delay line stores incoming audio samples in a fixed-size buffer and reads them back after a configurable time, producing an echo. The wet/dry mix controls how much of the delayed signal is blended into the output. This same structure underpins chorus, flanger, reverb, and the Haas effect; all of which are variations on the same principle.
| File | Description |
|---|---|
Delay.h / .cpp |
Single-tap delay line with wet/dry mix |
main.cpp |
Example: applies the effect to a 440Hz sine wave at three different delay and mix settings |
The delay is implemented as a circular buffer - a fixed-size array with a write index that wraps around when it reaches the end. On every sample:
- The sample at
writeIndexis read — this is the sample that was writtendelayInSamplesframes ago - The new input sample is written into
writeIndex writeIndexadvances by one, wrapping at the buffer boundary- The dry and delayed samples are blended by
wetMixand returned
output = (1.0 - wetMix) * input + wetMix * delayedSample
writeIndex: 0 1 2 3 4 5 ... N-1 0 1 ...
↑
read oldest sample here, write new sample here, then advance
Because the buffer size is fixed at construction, no memory is ever allocated or freed during processing, which is critical for real-time audio.
| Parameter | Type | Range | Description |
|---|---|---|---|
delayInMilliseconds |
int |
> 0 | Time between dry signal and echo. |
wetMix |
float |
0.0 – 1.0 | Blend between dry and delayed signal. 0.0 = fully dry, 1.0 = fully wet. |
sampleRate |
float |
> 0.0 | Audio sample rate in Hz. Used to convert ms to samples. |
Delay time and perceived effect:
| Delay Time | Perceptual Effect |
|---|---|
| 1 – 10ms | Phase/comb filtering — not perceived as echo |
| 10 – 40ms | Haas effect range — stereo widening (see Audio Haas Effect) |
| 40 – 100ms | Pre-delay / ambience — adds space without a distinct echo |
| 100 – 500ms | Slap-back echo — common in rockabilly and vocal production |
| 500ms+ | Distinct repeating echo |
// 500ms delay, equal wet/dry mix, at 44100Hz
Sherbert::Delay delay(500, 0.5f, 44100.0f);
float output = delay.ProcessSample(input);// Only the delayed signal is heard — silence for the first delayInSamples frames
delay.setWetMix(1.0f);// setDelayInMilliseconds calls reset() internally to avoid buffer artefacts
delay.setDelayInMilliseconds(250);
// setWetMix does not require a reset
delay.setWetMix(0.3f);delay.reset();| Method | Description |
|---|---|
Delay(delayInMs, wetMix, sampleRate) |
Construct with initial parameters. |
ProcessSample(input) |
Process one sample. Returns the blended dry/wet output. |
reset() |
Clears the delay buffer and resets the write index. |
setDelayInMilliseconds(value) |
Update delay time. Calls reset() internally. |
setWetMix(value) |
Update wet/dry mix. Range: 0.0 – 1.0. |
getDelayInMilliseconds() |
Returns current delay time in ms. |
getWetMix() |
Returns current wet mix value. |
getSampleRate() |
Returns the sample rate set at construction. |
This is an intentionally minimal single-tap delay for learning purposes. Real-world delay effects build on this foundation with additional features:
Feedback: a feedback delay feeds a portion of the output back into the input, producing multiple decaying repeats rather than a single echo. This is the most common next step and only requires one additional line: input += feedbackSample * feedbackAmount before writing to the buffer.
Modulation — modulating the delay time with an LFO produces chorus (very short delay, slow LFO) or flanger (very short delay, fast LFO) effects. This is the same LFO structure used in the phaser implementation.
No filtering in the feedback path — most hardware delay units apply a low-pass filter to the feedback signal, so each repeat gets progressively darker. This simulates the high-frequency absorption of real acoustic spaces.
If you want to explore further, the natural next steps from here are:
- Adding a
feedbackparameter (input += output * feedbackbefore processing) - Adding an LFO to modulate
delayInSamplesfor chorus/flanger - A low-pass filter in the feedback path for tape-style decay
- Multiple tap points in the buffer for multi-tap delay patterns

