0
mirror of https://github.com/torvalds/GuitarPedal.git synced 2026-08-13 12:36:34 +00:00
Files
Linus Torvalds e6a1991e7f Documentation: say which meter the reverb's cpu figures came from
They were taken with the audio load meter timing its idle spin against a
1MHz timer, where one tick is 4.8% of a sample period - so each of the
four figures is a rounding of that.  The meter counts cycles now and
re-measures the reverb at 22.1% against the 22.4% quoted, which is
inside the old instrument's resolution and changes nothing the section
says.

Not re-measured beyond that.  The before-and-after rows would mean
reflashing three superseded builds to move numbers by a few tenths, and
the point of those rows is the shape - that correctness cost 28.3% and
control-rating the LFOs gave most of it back - which is far larger than
the error.

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2026-08-11 12:27:11 -07:00

194 lines
8.7 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Reverb `[REVERB]`
Freeverb — Schroeder-Moorer, by Jezar at Dreampoint, public domain. Eight
parallel feedback comb filters fed by the input, summed into four series
allpasses. Two controls: **Room** (0.70..0.98) is the feedback gain shared by
all eight combs, and **Damp** (0.1..0.5) is a one-pole lowpass inside each
comb's feedback loop. Defaults are 0.88 and 0.25, and it returns wet only, at
an 18% default mix.
Each comb's read pointer is swept ±6 samples by one of four slow LFOs, which is
the Lexicon trick for stopping a long tail ringing on a fixed set of
frequencies.
## Room is the decay time, and it is not linear in the knob
T60 is measured the standard way: Schroeder backward integration of the tail
after a gated noise burst, fitted from 5 dB to 25 dB and tripled.
```mermaid
%%{init: {'themeVariables': {'xyChart': {'plotColorPalette': '#0072b2'}}}}%%
xychart-beta
title "Decay time against the Room control"
x-axis "Room, comb feedback gain" [0.7, 0.76, 0.81, 0.87, 0.92, 0.98]
y-axis "T60, seconds" 0 --> 10
line [0.52, 0.65, 0.89, 1.32, 2.4, 9.08]
```
Half a second to nine seconds, so the whole travel does something — but look at
the shape. The knob is linear in the feedback gain, and decay time goes as
roughly 1/(1g), so it runs away at the top: **the first 80% of the pot covers
0.52 to 2.4 seconds, and the last 20% covers 2.4 to 9.1.**
That is not merely a steep curve, it is a curve whose steep end is where the
interesting settings live. It is the same shape of defect the compressor's Ratio
had — a control whose useful range is piled at one end of its travel — and it is
worth knowing about before reaching for the knob, because the top two
centimetres are where a room turns into a cathedral.
The default 0.88 gives 1.32 s, which is a plausible medium room and sits at
mid-travel where the control is still well behaved.
## Damp tilts the decay; Room scales it
Damp is a lowpass in the feedback path, so it does not shorten the tail evenly —
each pass round the loop takes a little more top off, and the highs run out
first. That is what a real room does, because air and soft furnishings both
absorb treble faster than bass.
```mermaid
%%{init: {'themeVariables': {'xyChart': {'plotColorPalette': '#0072b2, #d55e00, #009e73'}}}}%%
xychart-beta
title "T60 by octave. Damp 0.10 blue, 0.30 orange, 0.50 green"
x-axis "Hz" [125, 250, 500, 1000, 2000, 4000]
y-axis "T60, seconds" 0 --> 2
line [1.7, 1.85, 1.77, 1.69, 1.47, 1.13]
line [1.7, 1.84, 1.71, 1.6, 1.24, 0.78]
line [1.69, 1.81, 1.59, 1.47, 0.91, 0.48]
```
The three lines are on top of each other at 125 Hz — 1.70, 1.70, 1.69 — and a
factor of 2.4 apart at 4 kHz. Damp does nothing whatever to the bottom of the
spectrum, by construction, and that is the correct behaviour rather than a
limitation.
Room, by contrast, scales the whole thing:
```mermaid
%%{init: {'themeVariables': {'xyChart': {'plotColorPalette': '#0072b2, #d55e00, #009e73'}}}}%%
xychart-beta
title "T60 by octave. Room 0.70 blue, 0.87 orange, 0.98 green"
x-axis "Hz" [125, 250, 500, 1000, 2000, 4000]
y-axis "T60, seconds" 0 --> 11
line [0.95, 0.59, 0.59, 0.58, 0.56, 0.46]
line [1.58, 1.68, 1.5, 1.51, 1.21, 0.82]
line [10.82, 10.12, 9.6, 7.68, 4.62, 1.79]
```
Note that the green line still slopes — at Room 0.98 the tail is 10.8 s at
125 Hz and 1.8 s at 4 kHz, a six-to-one tilt, because the damping compounds over
far more trips round the loop. So the two controls are not independent: a longer
room is also a darker one, at the same Damp setting.
## The modulation, and why the tail wanders
Eight fixed combs ring on a fixed set of frequencies, and a note that lands on
one of them sits there and hums. Sweeping each read pointer ±6 samples walks
those resonances instead — ±0.5% on a comb of 1215 samples — so nothing has a
fixed frequency to sit on.
You can see it directly. A steady 440 Hz tone, 100% wet, half-second windows —
the first 5.5 seconds of a 85-second capture:
```mermaid
%%{init: {'themeVariables': {'xyChart': {'plotColorPalette': '#0072b2'}}}}%%
xychart-beta
title "Wet level of a steady tone, wandering as the combs detune"
x-axis "seconds" [0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5, 5.0]
y-axis "dBFS" -28 --> -24
line [-25.08, -26.57, -25.92, -24.96, -26.08, -25.12, -25.73, -26.11, -26.86, -25.24, -25.96]
```
The input never changes and the output wanders. That wander is the effect
working: it is the comb peaks sliding under a fixed tone.
**How far it wanders depends entirely on how long you watch**, and that is worth
stating because the first attempt at this got it wrong. The excerpt above covers
1.9 dB. Over the full 85 seconds it is **5.9 dB peak to peak**, with a standard
deviation of 1.08 dB and a p5..p95 spread of 3.4 dB. The slowest of the four
LFOs has a period of 4.8 seconds and they are spaced so as not to share one, so
a short capture does not sample the range — it samples whichever corner of it
the recording happened to start in. An eight-second capture reported 2.3 dB,
which is less than half the truth.
The chart is an excerpt rather than the whole run for a reason that is about
drawing rather than about reverb: eleven points is what the axis can label, and
eleven points spread over 85 seconds would put the window well above every rate
being looked at and average the wobble into a flat line.
The four LFOs run at 0.21, 0.31, 0.46 and 0.67 Hz — spaced about 3:2 so they do
not beat into a common period — and they are phase accumulators, which matters
more than it sounds. They used to be quadrature phasors rotated by a fixed
step, and nothing renormalised them: three of the four spiralled outwards and
one decayed to a tenth of its amplitude in ten minutes, so the reverb slowly
became the unmodulated version of itself and, left on overnight, walked its read
pointer out of the comb buffer entirely.
**The modulated read is interpolated.** Truncating it to a whole sample makes
the pointer jump, and a jump is a step discontinuity sprayed into the tail
continuously by eight combs at four rates. Measured by band-limiting the input
to 1 kHz and looking above 4 kHz, where a reverb that is time-invariant apart
from a sub-hertz modulation cannot legitimately put anything:
```
input above 4 kHz -78.3 dB of its total
output above 4 kHz -82.2 dB of its total
```
The output has *less* up there than the input did. It manufactures nothing.
Before the read was interpolated it manufactured 34.5 dB above what went in.
## What it costs
This is the expensive effect, and unlike everything else on this page the number
comes off the pedal rather than the bench — `Validation/measure-load.py`, which
reads the load meter over MIDI telemetry. Steps out of 127 of the sample period,
averaged over four boots:
| | reverb routed | share of the sample period |
|---|---|---|
| empty chain | — | 9.4 % |
| **reverb, as it is now** | **28.42 steps** | **22.4 %** |
| before the LFO and interpolation fixes | 26.15 | 20.6 % |
| with those fixes but a per-frame LFO | 33.54 | 26.4 % |
So one reverb is a fifth of the audio budget, which makes it comfortably the
most expensive effect here and the one to think about before stacking things
behind it.
**Those four figures were taken with the load meter as it was**, timing the
audio loop's idle spin with a 1 MHz timer — one tick of which is 4.8% of a
sample period, so each of them is a rounding of that. The meter counts cpu
cycles now, and re-measured it puts the reverb at **22.1%**, which is inside
the old instrument's resolution and does not move anything said here. The
before-and-after rows have not been re-measured, because doing so means
reflashing three superseded builds to correct figures by a few tenths.
The middle row is the honest cost of correctness: fixing the drifting LFOs and
interpolating the comb reads added 28.3%. Running the LFOs at control rate —
exact once every 32 frames, a straight line between — gave most of that back,
and the effect now costs 8.7% more than the broken version rather than 28.3%.
The remaining work is the comb read itself, which is irreducibly per-frame
because it reads audio.
## Reproducing this
```
cd Validation
make bench # a stale bench measures a pedal you no longer have
./analyse-reverb.py
```
The cpu figures are not from that and will not be reproduced by it. They need
the pedal:
```
./measure-load.py -b 4
```
which sets routing live over SysEx, saves nothing, and reloads the scene when it
finishes. Read the note at the top of it before believing a single reading:
the empty-chain baseline wanders by a couple of steps between boots while the
routed reading holds still, so the number to compare between firmwares is the
routed absolute, averaged over several boots, and never a one-shot difference.