Reverberation the space between notes

How a handful of feedback delays and allpass filters — the comb-and-allpass reverberator of Schroeder, Moorer and Jezar's Freeverb — synthesise a room out of nothing but arithmetic.

Manfred R. SchroederJames A. MoorerJean-Marc JotJezar · Freeverb
Begin ↓

01Introduction

Strike a note in a cathedral and it does not stop when your hand leaves the string. Sound leaves the source, strikes stone, floor and vault, and returns as thousands of delayed, attenuated copies of itself — first as sparse, distinct early reflections that tell the ear the size and shape of the room, then as a dense, exponentially decaying wash of energy so thick that the individual echoes fuse into a single continuous tail. That tail is reverberation, and its length, colour and density are as much a part of recorded music as the instruments themselves.

Reverberation is a linear, time-invariant process: the room is completely described by its impulse response — the sound you would hear from a single, infinitely short click. Convolve any signal with that impulse response and you hear it as if played in the room. A perfect reverberator, then, is simply a filter with a very long, very dense impulse response. The engineering problem is that a real room's impulse response is seconds long: at 48 kHz that is hundreds of thousands of taps, far too many to compute directly one multiply at a time — at least it was in 1961, when Manfred Schroeder first asked whether a room could be faked rather than measured.

His answer, and the answer that still runs inside most mixing desks and plug-ins, is beautiful: do not store the impulse response at all. Instead build a small network of recursive delays — feedback comb filters that manufacture an exponentially decaying train of echoes, and allpass filters that smear those echoes in time without colouring their spectrum — and let the feedback do the work that hundreds of thousands of taps would otherwise do. A dozen delay lines, a handful of multiplies per sample, and a convincing hall appears. This page derives that network from first principles, builds a faithful Freeverb in your browser out of eight comb filters and four allpasses per channel, and measures its impulse response live so you can watch the tail it invents.

Everything here runs on synthesised or user-supplied audio in your browser. The reverb in the live demo is hand-written sample-by-sample in an AudioWorklet — the C++ and JavaScript you read below is the code you hear.

02A short history of artificial reverberation

Before reverberation could be computed it had to be built. The story runs from literal rooms, through vibrating metal, to the recursive filters that made a hall cost a few kilobytes of delay memory.

1930s
Echo chambers. Studios such as Abbey Road and Capitol built dedicated tiled rooms with a loudspeaker and microphone; the recording was played into the room and re-recorded. Gorgeous, but a whole room per reverb, and no two the same.
1939 / 1957
Spring and plate. Laurens Hammond patented the spring reverb (torsional waves travelling down a coiled spring) for his organs; in 1957 the German firm EMT released the EMT 140 plate — a suspended sheet of steel some 2 m long, driven by a transducer and read back by contact pickups, with a movable damping plate for decay time. The plate became the studio standard for two decades.
1961
Schroeder & Logan, "Colorless artificial reverberation." At Bell Labs, Manfred Schroeder proposes reverberation as a digital network of feedback comb filters and allpass filters, showing that the allpass gives echo density with a flat magnitude response — a "colourless" decay. This is the seed of every algorithmic reverb since.
1962
Schroeder, "Natural sounding artificial reverberation" (JAES). The canonical reference: parallel combs for the decaying tail, series allpasses for diffusion, and the mixing that avoids the fluttery, metallic quality of a single comb.
1976–79
Moorer. James A. Moorer refines Schroeder's design — a low-pass filter inside the comb feedback to model air and material absorption (high frequencies decay faster), and early-reflection taps derived from measured rooms. His 1979 Computer Music Journal paper is the practical blueprint.
1978–86
Lexicon. Digital hardware reverb reaches the studio: the Lexicon 224 (1978) and later the 480L set the sound of a decade of records, its "Random Hall" and "Concert Hall" programs built on elaborated Schroeder/Moorer networks with modulated delays.
1991
Jot & Chaigne — the Feedback Delay Network. Building on Stautner & Puckette (1982), Jean-Marc Jot formalises the FDN: a bank of delay lines coupled by a unitary (lossless) feedback matrix, with per-line attenuation set to give a specified, frequency-dependent RT60. This is the modern high-end reverb topology.
2000
Freeverb. "Jezar at Dreampoint" releases a tiny, public-domain Schroeder–Moorer reverb — 8 parallel combs (with damping) and 4 series allpasses per channel, a fixed stereo spread. Its clarity and free licence made it the reference implementation copied into countless projects, including JUCE's dsp::Reverb.
2001
Convolution goes mainstream. Audio Ease's Altiverb brings partitioned fast-convolution reverb to the desktop: sample a real hall's impulse response and convolve with it. Perfect realism, at the cost of a fixed, unmodulatable space.
2010s
The plug-in era. ValhallaDSP, FabFilter Pro-R, Eventide and others revive and modernise the algorithmic approach — FDNs with lush modulation, low CPU and infinite tweakability — proving Schroeder's fifty-year-old idea still outruns brute-force convolution for musical, adjustable reverb.

03The feedback comb filter

The atom of algorithmic reverb is a delay line fed back on itself. Take the input, add a delayed and attenuated copy of the output, and you have the feedback comb filter:

$$y[n] = x[n] + g\,y[n-D]$$

Here $D$ is the delay in samples and $g$ (with $|g|<1$) the feedback gain. A single impulse in produces an impulse out at $n=0$, then a copy of gain $g$ at $n=D$, $g^2$ at $2D$, $g^3$ at $3D$ — a geometric train of echoes spaced $D$ samples apart. Taking the $z$-transform of both sides, $Y(z) = X(z) + g\,z^{-D}Y(z)$, gives the transfer function

$$H(z) = \frac{Y(z)}{X(z)} = \frac{1}{1 - g\,z^{-D}}.$$

The poles sit where $z^{D} = g$, i.e. at $D$ equally spaced radii $|z| = g^{1/D}$ — a ring of resonances that, in the magnitude response, look like the teeth of a comb (hence the name), spaced $f_s/D$ hertz apart. A single comb on its own sounds metallic and hollow, precisely because those teeth are so regular: it rings on a pitch of $f_s/D$.

Setting the decay time (RT60)

The echoes decay by a factor $g$ every $D$ samples. The reverberation time RT60 is, by definition, the time for the level to fall by 60 dB, i.e. by a factor of $10^{-3}$ (since $-60\,\mathrm{dB} = 20\log_{10}10^{-3}$). In $RT_{60}$ seconds there are $RT_{60}f_s/D$ round trips, so we require $g^{\,RT_{60}f_s/D} = 10^{-3}$, which rearranges to the gain we must dial in for a target decay:

$$g = 10^{\,-3D/(RT_{60}\,f_s)}.$$

Note the crucial consequence: a longer delay $D$ needs a higher gain for the same RT60, and different comb lengths reach 60 dB of decay at exactly the same time — which is what lets several combs of different lengths share a single, coherent decay.

feedback comb — impulse train + magnitude comb
teeth

04The Schroeder allpass

Combs give decay but colour the sound. Schroeder's second building block gives density without colour. The allpass filter combines a feedforward and a feedback path around the same delay:

$$y[n] = -g\,x[n] + x[n-D] + g\,y[n-D].$$

Its transfer function is

$$H(z) = \frac{-g + z^{-D}}{1 - g\,z^{-D}}.$$

The magic is in the numerator and denominator: the numerator's coefficients are the denominator's, reversed. Evaluate on the unit circle $z=e^{j\omega}$ and the numerator is the complex conjugate of the denominator times $z^{-D}$, so their magnitudes are identical and

$$|H(e^{j\omega})| = 1 \quad\text{for all }\omega.$$

The filter passes every frequency at unity gain — hence "allpass" — but its phase, and therefore its group delay, varies with frequency. An impulse fed in comes out not as a single spike but as a dense, dispersed cluster of echoes: the allpass multiplies echo density without adding any spectral colouration. Schroeder's insight was to place several allpasses in series after the combs, each one exploding every echo into a burst, so that echo density grows multiplicatively toward the thousands-per-second the ear expects of a real room.

Schroeder allpass — flat magnitude, dispersive impulse response

05Freeverb — Schroeder and Moorer, made practical

Freeverb, written around 2000 by "Jezar at Dreampoint" and released to the public domain, is the canonical modern Schroeder reverb. Per channel it is eight feedback comb filters in parallel, their outputs summed, followed by four allpass filters in series. Two refinements over the 1962 original make it sound like a room rather than a machine.

Damping: a low-pass inside the feedback

In a real hall, high frequencies are absorbed faster than low ones — by the air, by soft furnishings, by the audience — so the tail grows darker as it decays. Moorer's fix, adopted by Freeverb, is to put a one-pole low-pass filter inside the comb's feedback loop. Each pass round the delay loses a little more treble:

$$f[n] = (1-d)\,y[n-D] + d\,f[n-1], \qquad y[n] = x[n] + g\,f[n].$$

Here $d$ is the damping coefficient ($0\le d<1$). At $d=0$ the comb is the plain recursion of §3; as $d\to1$ the feedback path becomes an integrator and high frequencies are stripped away within a few round trips, giving a warm, fast-decaying top end over a longer low-frequency tail. Because the low-pass is recursive and nested in the loop, its effect compounds — precisely mimicking cumulative air and boundary absorption.

Parallel combs, then series allpasses

The eight combs use mutually incommensurate delay lengths (Freeverb's classic tunings, in samples at 44.1 kHz, are 1116, 1188, 1277, 1356, 1422, 1491, 1557 and 1617). Because their comb teeth fall at different frequencies, summing them fills in the gaps and cancels the metallic single-comb pitch, while the shared $g$ keeps their decays aligned to one RT60. The sum then passes through four series allpasses (delays 556, 441, 341, 225 samples, feedback $g=0.5$) which multiply echo density into a smooth diffuse tail.

$$y = \text{Allpass}_4\!\Big(\text{Allpass}_3\!\big(\text{Allpass}_2\!\big(\text{Allpass}_1(\textstyle\sum_{i=1}^{8}\text{Comb}_i(x))\big)\big)\Big)$$

Stereo via a fixed stereospread

To decorrelate the two channels — the key to a wide, enveloping image — Freeverb runs a second bank of combs and allpasses for the right channel whose delay lengths are all longer by a fixed stereospread of 23 samples. Same algorithm, slightly different room, and the two ears hear subtly different tails. A width control then cross-blends the two channels.

Room size

Sets the comb feedback $g$ (Freeverb: $g = 0.7 + 0.28\,\text{size}$). Higher → longer RT60.

Damping

Sets $d$ (Freeverb: $d = 0.4\,\text{damp}$). Higher → darker, faster-decaying tail.

Width

Cross-mix of the two channels' wet signals; 1 = fully stereo, 0 = mono.

Freeze. Drive $g\to1$ and $d\to0$ while muting the input, and the combs neither gain nor lose energy: the tail sustains forever — an infinite reverb "hold", useful as a drone or pad. The live demo below offers exactly this.

06Beyond Schroeder: feedback delay networks and convolution

The comb-and-allpass network is efficient and musical but structurally simple, and its echo pattern can sound a little regular on transient-rich material. Two later approaches address realism from opposite directions.

Feedback delay networks (Jot / Stautner–Puckette)

Rather than isolated combs, an FDN couples $N$ delay lines through a single $N\times N$ feedback matrix $\mathbf{A}$. With delay-line states $\mathbf{s}$, input vector $\mathbf{b}$ and output vector $\mathbf{c}$:

$$\mathbf{s}(n{+}\mathbf{m}) = \mathbf{A}\,\mathbf{s}(n) + \mathbf{b}\,x(n), \qquad y(n) = \mathbf{c}^{\mathsf T}\mathbf{s}(n) + d\,x(n).$$

If $\mathbf{A}$ is unitary (lossless) the network conserves energy and never colours the sound; decay is then introduced deliberately by attaching a per-line attenuation $g_i = 10^{-3 m_i/(RT_{60}f_s)}$ — the same RT60 law as §3, now applied to each of $N$ lines. Stautner & Puckette (1982) first proposed the matrix-coupled structure; Jot & Chaigne (1991) showed how to choose $\mathbf{A}$ and the attenuations for an exact, frequency-dependent RT60, giving the smooth, dense, controllable tails of high-end reverbs. A Householder or Hadamard matrix is a common lossless choice, mixing every delay line into every other on each pass and multiplying density far faster than series allpasses alone.

Convolution reverb

The opposite philosophy: abandon synthesis and use a measured impulse response of a real space. The output is the exact convolution $y[n] = \sum_k h[k]\,x[n-k]$. Done directly this is ruinously expensive for a multi-second $h$, so convolution reverbs use partitioned overlap-add FFT convolution (Gardner's low-latency scheme): the impulse response is split into blocks, each transformed once, and the running convolution becomes cheap frequency-domain multiplies. The result is indistinguishable from the sampled room — but that room is frozen. You cannot lengthen its decay, move its walls or modulate it without artefacts, which is exactly what the algorithmic FDN and Freeverb designs let you do freely.

ApproachRealismCostAdjustable?
Schroeder / FreeverbGood, can flutterVery lowFully (size, damp, width)
FDN (Jot)Excellent, denseLow–moderateFully, incl. per-band RT60
ConvolutionExact (it is a real room)High (FFT)Barely — the room is fixed

07Live demo — a hand-written Freeverb

This reverb is real. Eight parallel damped combs and four series allpasses per channel — Freeverb's exact delay tunings, scaled to your device's sample rate — run sample-by-sample inside an AudioWorklet. Nothing here is a native ConvolverNode: the code in §09 is the code you are hearing.

◈ Violet Hall — Algorithmic Reverb DSP & MUSIC · FREEVERB CORE
Room Size
0.72
Damping
0.50
Width
1.00
Pre-delay
20 ms
Mix
35%
Out L / R
idle

Loop = a synthesised marimba phrase played with gaps so the tail is audible · Mic = live input (allow permission) · File = drag-drop or click "File" to load your own · A/B compares the wet reverb against the dry input · Freeze holds the tail indefinitely.

Press Play to start the audio engine (browsers require a gesture before sound). Drag any knob vertically; double-click to reset. On touch devices drag with a finger.

08Under the hood — measuring what it built

Three views into the running network. The first two are computed from the same comb/allpass arithmetic as the live demo, re-run offline on a single impulse whenever you move a knob; the third animates the signal flow through the topology.

Measured impulse response & decay envelope

Feed a single sample of value 1 into the reverb and record what comes back: the impulse response. Its exponential envelope is the reverb tail. Adjust Room Size or Damping in the demo above and watch the tail lengthen or darken here in real time.

impulse response — press Play and move the Room Size / Damping knobs

Energy-decay curve (Schroeder integration)

Schroeder's own 1965 method for measuring RT60: integrate the squared impulse response backwards in time to get a smooth decay curve, $\mathrm{EDC}(t)=\int_t^{\infty} h^2(\tau)\,d\tau$, and read the slope in decibels. The dashed line marks the −60 dB point — the estimated RT60 of the network as currently tuned.

energy-decay curve — backward-integrated, in dB

The network, animated

Signal enters, fans out to the eight parallel combs (each a delay with a damped feedback loop), sums, then passes down the four series allpasses to the output. Particles trace the flow.

8 parallel combs → summing node → 4 series allpasses → output

09In JUCE / C++

JUCE ships a ready reverb — juce::dsp::Reverb — which is itself a Freeverb port, so the mapping to the JavaScript above is exact. Its Parameters struct exposes the same controls.

#include <juce_dsp/juce_dsp.h>

class ReverbProcessor
{
public:
    void prepare (const juce::dsp::ProcessSpec& spec)
    {
        reverb.prepare (spec);                 // sample rate, block size, channels
        reverb.setParameters (params);
    }

    void setRoomSize (float s) { params.roomSize   = s; reverb.setParameters (params); }
    void setDamping  (float d) { params.damping    = d; reverb.setParameters (params); }
    void setWidth    (float w) { params.width      = w; reverb.setParameters (params); }
    void setMix      (float m) { params.wetLevel   = m;
                                 params.dryLevel   = 1.0f - m;
                                 reverb.setParameters (params); }
    void setFreeze   (bool f)  { params.freezeMode = f ? 1.0f : 0.0f;
                                 reverb.setParameters (params); }

    void process (juce::AudioBuffer<float>& buffer)
    {
        juce::dsp::AudioBlock<float>        block (buffer);
        juce::dsp::ProcessContextReplacing<float> ctx (block);
        reverb.process (ctx);                  // stereo Freeverb in place
    }

private:
    juce::dsp::Reverb            reverb;
    juce::dsp::Reverb::Parameters params { /*roomSize*/ 0.72f, /*damping*/ 0.5f,
                                           /*wetLevel*/ 0.35f, /*dryLevel*/ 0.65f,
                                           /*width*/ 1.0f,     /*freezeMode*/ 0.0f };
};

juce::dsp::Reverb wraps the Freeverb comb/allpass network — the Parameters map one-to-one onto the knobs in §07.

To show the arithmetic explicitly — and to map directly onto the JavaScript worklet — here is a hand-rolled damped comb and Schroeder allpass, the two primitives Freeverb is built from:

// One damped feedback comb: y[n] = x[n] + g * lowpass(y[n-D])
struct Comb
{
    std::vector<float> buf;
    int   idx = 0;
    float store = 0.0f;      // one-pole low-pass state (damping)
    float feedback = 0.84f;  // g  = 0.7 + 0.28 * roomSize
    float damp1 = 0.2f;      // d  = 0.4 * damping
    float damp2 = 0.8f;      // 1 - damp1

    void setDamp (float d) { damp1 = d; damp2 = 1.0f - d; }
    void resize  (int n)   { buf.assign ((size_t) n, 0.0f); idx = 0; store = 0.0f; }

    inline float process (float x) noexcept
    {
        float y = buf[(size_t) idx];
        store   = y * damp2 + store * damp1;         // low-pass in the loop
        buf[(size_t) idx] = x + store * feedback;    // write back with feedback
        if (++idx >= (int) buf.size()) idx = 0;
        return y;
    }
};

// One Schroeder allpass: y[n] = -g*x[n] + x[n-D] + g*y[n-D]
struct Allpass
{
    std::vector<float> buf;
    int   idx = 0;
    float feedback = 0.5f;

    void resize (int n) { buf.assign ((size_t) n, 0.0f); idx = 0; }

    inline float process (float x) noexcept
    {
        float bufout = buf[(size_t) idx];
        float y      = -x + bufout;                  // magnitude-flat output
        buf[(size_t) idx] = x + bufout * feedback;
        if (++idx >= (int) buf.size()) idx = 0;
        return y;
    }
};

// Freeverb's 44.1 kHz tunings (scale by sampleRate/44100 at prepare()):
static constexpr int combTuning[8]    = { 1116,1188,1277,1356,1422,1491,1557,1617 };
static constexpr int allpassTuning[4] = { 556, 441, 341, 225 };
static constexpr int stereoSpread     = 23;   // added to the right channel's delays

The hand-rolled primitives — identical maths to the JavaScript AudioWorklet driving the demo above.

For sampled-space (convolution) reverb, JUCE provides juce::dsp::Convolution, which loads an impulse-response file and performs partitioned, zero-latency FFT convolution:

juce::dsp::Convolution convolution;

void prepareIR (const juce::dsp::ProcessSpec& spec)
{
    convolution.prepare (spec);
    convolution.loadImpulseResponse (BinaryData::hall_wav, BinaryData::hall_wavSize,
                                     juce::dsp::Convolution::Stereo::yes,
                                     juce::dsp::Convolution::Trim::yes, 0);
}
// convolution.process (ctx);  // exact reproduction of the sampled room

10Comparison, references & related work

Comb vs allpass vs FDN — what each contributes

ElementRoleMagnitude responseDifference equation
Feedback combExponential echo train (the decay)Peaky "comb teeth"$y=x+g\,y[n{-}D]$
Damped combDecay with treble loss (Moorer)Comb teeth, rolled-off top$y=x+g\!\cdot\!\text{LP}(y[n{-}D])$
Schroeder allpassEcho density / diffusionFlat (unity)$y=-gx+x[n{-}D]+g\,y[n{-}D]$
FDNDense, coupled, tunable tailFlat if $\mathbf A$ unitary$\mathbf s(n{+}\mathbf m)=\mathbf A\mathbf s+\mathbf b x$
ConvolutionExact sampled roomThe room's own$y=\sum_k h[k]x[n{-}k]$

References

  1. M. R. Schroeder and B. F. Logan, "Colorless artificial reverberation," IRE Transactions on Audio, AU-9(6), 209–214, 1961.
  2. M. R. Schroeder, "Natural sounding artificial reverberation," Journal of the Audio Engineering Society, 10(3), 219–223, 1962.
  3. M. R. Schroeder, "New method of measuring reverberation time," Journal of the Acoustical Society of America, 37, 409–412, 1965.
  4. J. A. Moorer, "About this reverberation business," Computer Music Journal, 3(2), 13–28, 1979.
  5. J. Stautner and M. Puckette, "Designing multi-channel reverberators," Computer Music Journal, 6(1), 52–65, 1982.
  6. J.-M. Jot and A. Chaigne, "Digital delay networks for designing artificial reverberators," 90th AES Convention, preprint 3030, 1991.
  7. W. G. Gardner, "Efficient convolution without input–output delay," Journal of the Audio Engineering Society, 43(3), 127–136, 1995.
  8. J. O. Smith, Physical Audio Signal Processing (PASP), online book, CCRMA, Stanford — chapters on Schroeder reverberators and FDNs. ccrma.stanford.edu/~jos/pasp/
  9. "Jezar at Dreampoint," Freeverb source and notes, public domain, c. 2000 (mirrored in the Freeverb3 project and JUCE's dsp::Reverb).
  10. JUCE Framework, juce::dsp::Reverb and juce::dsp::Convolution class documentation. juce.com