A simple procedural reverb implemented in plain C++ using a comb filter. Intended as a learning resource for understanding how digital reverb works from first principles — no libraries, no black boxes, just the DSP.
Reverb simulates the way sound bounces around a physical space. Rather than recording a real room's impulse response (convolution reverb), procedural reverb approximates this behaviour algorithmically, making it lightweight and suitable for real-time audio processing.
This implementation uses a single feedback comb filter, which is the fundamental building block of most reverb algorithms.
input ──►──────────────────────────────────────► output
│ ▲
▼ │
[ delay buffer (N samples) ] ──► × decayFactor ──►
On every sample:
- The oldest sample in the delay buffer (N samples ago) is read
- It is multiplied by
decayFactorto attenuate it, simulating energy loss - This attenuated value is added to the incoming input (feedback)
- The combined value is written back into the buffer
- The combined value is returned as output
Repeating this on every sample produces a series of exponentially decaying echoes — which our ears interpret as reverberation.
The buffer itself is a circular buffer: a fixed-size array with a write index that wraps around. This means no memory is ever allocated or freed during audio processing, which is critical for real-time performance.
| Parameter | Type | Range | Description |
|---|---|---|---|
decayFactor |
float |
(0.0, 1.0) | How much each reflection attenuates. Near 1.0 = long tail, near 0.0 = very dry. |
delayLength |
size_t |
> 0 | Number of samples to delay. Controls perceived room size. |
Choosing a delay length:
At a 44100Hz sample rate, a useful rule of thumb is:
| Delay Length | Approximate Time | Perceived Size |
|---|---|---|
| 2205 | ~50ms | Small room |
| 4410 | ~100ms | Medium room |
| 11025 | ~250ms | Large hall |
| 22050 | ~500ms | Cathedral |
| File | Description |
|---|---|
ProceduralReverb.h |
Class declaration and documentation |
ProceduralReverb.cpp |
Comb filter implementation |
// ~100ms delay at 44100Hz, moderate decay
Sherbert::ProceduralReverb reverb(0.75f, 4410);
float output = reverb.ProcessSample(input);ProcessSample returns the fully wet signal. Blend it with the dry input yourself to control the mix:
const float wetAmount = 0.4f; // 0.0 = fully dry, 1.0 = fully wet
float wet = reverb.ProcessSample(input);
float output = (wetAmount * wet) + ((1.0f - wetAmount) * input);Call reset() when playback stops or the effect is bypassed to prevent stale samples bleeding into the next session:
reverb.reset();Both parameters can be changed at runtime. Note that either setter internally calls reset() to avoid artefacts from the buffer containing samples recorded at the old settings:
reverb.setDecayFactor(0.9f); // Longer tail
reverb.setDelayLength(11025); // Larger room| Method | Description |
|---|---|
ProceduralReverb(decayFactor, delayLength) |
Construct with initial parameters. |
ProcessSample(input) |
Process one sample. Call once per sample in your audio loop. |
reset() |
Clear the delay buffer and reset the write head. |
setDecayFactor(value) |
Update decay factor at runtime. Calls reset(). |
setDelayLength(value) |
Update delay length at runtime. Calls reset(). |
getDecayFactor() |
Returns current decay factor. |
getDelayLength() |
Returns current delay length in samples. |
This is an intentionally minimal implementation for learning purposes. A single comb filter is a good starting point but has limitations worth knowing about:
Metallic colouration: a single comb filter produces a characteristic "metallic" or "pitched" resonance because the echoes are evenly spaced. Real reverb algorithms (like the Schroeder reverberator) use multiple comb filters at prime-number delay lengths to break up this regularity.
No early reflections: real rooms produce a burst of distinct early reflections before the diffuse reverb tail begins. This implementation goes straight to the tail.
No diffusion: allpass filters are typically chained after comb filters to scatter the echo density and produce a smoother, more natural tail.
If you want to explore further, the natural next steps from here are:
- Multiple parallel comb filters (Schroeder reverberator)
- Allpass filter diffusion stages
- Separate early reflection delay lines
- High-frequency damping (a low-pass filter in the feedback path)