Three layers of modulation, and why one LFO is not enough
Long tails are where artificial reverbs give themselves away, and the culprit is almost always the modulation. Not its presence, its regularity.
The tell
Delay lines in a feedback network need some movement. Without it the tail settles into fixed resonances and rings. Practically every algorithmic reverb therefore modulates its delay lengths slightly.
The problem is what most of them modulate with: a single sine LFO per line. On a short decay you never notice. On a twelve second tail you hear it as a periodic sway, and once you have heard it you cannot stop hearing it. The tail stops sounding like a space and starts sounding like an effect.
Three sources instead of one
Every line in EvoraVerb combines three independent modulation sources, weighted:
- Main LFO, 60 per cent — between 0.15 and 0.64 Hz. This is the chorus-like movement that keeps resonances from settling. Its phase is offset by
i/8across the eight lines, so they never swing together. - Slow drift, 28 per cent — between 0.021 and 0.112 Hz. Roughly one cycle every ten to fifty seconds: too slow to hear as movement, fast enough that the tail is never quite the same twice. It reads as breathing.
- Smoothed noise, 12 per cent — not periodic at all. A cheap xorshift generator through a one-pole lowpass, which gives slow random wander in the character of tape wow.
That third layer is small and does the heaviest lifting. Two sine sources at different rates still produce a repeating pattern, just a longer one. Adding a non-periodic component means the modulation never repeats, and the ear stops finding a pattern to lock onto.
Combined depth peaks at about ten samples. At 48 kHz that is a fifth of a millisecond of pitch variation. Small enough that nothing detunes, large enough that the resonances never sit still.
Generating a sine without calling sin()
Three sources on eight lines is twenty-four oscillators running per sample. Calling std::sin that often is not free, so the LFOs use a different method.
The oscillator keeps a two-dimensional unit vector and rotates it by a fixed angle each sample, which takes four multiplies and two adds. The rotation angle is computed once, when the frequency is set.
Rotating repeatedly in floating point has a known flaw: rounding error accumulates and the vector slowly spirals inward or outward, so the amplitude drifts. The fix is a first-order magnitude correction applied every step, k = 1.5 - 0.5 * magnitude², which pulls the vector back onto the unit circle without a square root. The oscillator holds its amplitude indefinitely at a fraction of the cost of a trigonometric call.
Modulation moves, size does not
One boundary is worth stating clearly, because it looks inconsistent from the outside.
The modulation continuously slides the read position of each delay line. The base delay, the one that sets room size, never slides — it jumps between positions through a crossfade.
The reason is that sliding a delay read point through audio already stored in the buffer changes its pitch, exactly like a tape machine speeding up. At ten samples of modulation that pitch variation is inaudible, and it is the effect we are after. Across the full range of the size control it would be a glissando every time you touched the knob. Same mechanism, two very different magnitudes, two different solutions.