Audio Processing Science: Reverb Algorithms, Time-Stretching, and Browser Audio Engineering

Data engineer who loves building high-performance data and web-related tools. Creator of SlowedReverbMaker.net, implementing browser-side digital signal processing (DSP) to democratize audio editing.
There is a persistent assumption that browser-based audio tools are toys — that real processing happens in a DAW and a web page is doing some approximation of it.
That has not been true for a while. The Web Audio API gives a browser the same primitives a DAW plugin works with: a signal graph, convolution, biquad filters, sample-accurate scheduling, and 32-bit float precision throughout. The maths is the maths. A convolution is not less of a convolution because it is running in JavaScript.
What is genuinely different is that browser audio has its own failure modes, and they are not the ones people expect. This post covers how the processing actually works, and then a specific bug in this site's own engine — found by measurement, not by ear — that was quietly truncating exports for anyone who used a negative pitch setting.
Try this while you read: Slowed Reverb Generator
Slow songs, add reverb, preview, and export MP3 or WAV. Free, in your browser, no signup.
1. The Processing Graph
Web Audio is built around a directed graph of nodes. You create sources, effects, and a destination, connect them, and audio flows through. The graph is declarative — you describe the signal path rather than writing a per-sample loop — and the browser runs it on a dedicated real-time thread.
The nodes this site's engine uses are a fair sample of the whole API:
- AudioBufferSourceNode — plays decoded audio. Carries `playbackRate` and `detune`, which is where the speed and pitch effects come from.
- ConvolverNode — applies an impulse response. This is the reverb.
- BiquadFilterNode — second-order filters. Configured as `lowshelf` at 150 Hz for the bass boost, and cascaded as `lowpass` for the muffled-room effect.
- GainNode — level control. Two of them set the wet/dry balance.
- PannerNode — with `panningModel` set to HRTF, this places a source at a direction in space rather than sliding it between channels. It is what makes the 8D effect binaural rather than a stereo trick.
- OfflineAudioContext — renders the whole graph faster than real time, which is how export works without making you listen to the track.
1a. Precision
Internal processing is 32-bit floating point. The mantissa gives roughly 144 dB of resolution at any given moment, and the exponent extends the representable range to something on the order of 1500 dB overall.
The practical meaning of that is narrower than it sounds. It does not make the audio better than a 24-bit DAW; it means intermediate values in the graph cannot realistically overflow or lose resolution while being passed between nodes. Once you export to a 16-bit WAV, you are back to about 96 dB of dynamic range like everything else. The float precision protects the process, not the product.
2. Convolution Reverb
Convolution is the most computationally serious thing in the graph and the most conceptually simple.
An impulse response is a recording of how a space answers an instantaneous burst of sound. Convolving audio with it means, conceptually, stamping a scaled copy of the entire impulse response at every sample of the input and summing all the copies. Each sound gets its own decaying tail and the overlapping tails become reverb.
Done literally that is enormously expensive — a two-second impulse response at 44.1 kHz is 88,200 multiply-accumulate operations per input sample. Real implementations avoid this by working in the frequency domain, where convolution becomes multiplication, using partitioned overlap-add so the latency stays low. That is an implementation detail rather than a difference in result.
This site's reverb is convolution-based but uses a synthesised impulse response rather than a recorded one: decaying noise shaped by an exponential envelope. That is a deliberate middle path. Recorded impulse responses are more realistic but fix the decay time at whatever the real room had, so changing it means stretching a recording. A synthesised one gives the dense, natural tail that convolution produces while leaving decay as a free parameter — which matters when the whole point of the tool is a decay slider.
On whether browser and DAW convolution differ: given the same impulse response and input, they compute the same operation, and any difference sits at the level of floating-point rounding and FFT partitioning rather than anything audible. It is not accurate to say the outputs are bit-for-bit identical, because implementations partition differently. It is accurate to say the difference is far below the noise floor of the 16-bit file you will export.
3. Varispeed and WSOLA
There are two ways to change how fast audio plays, and this engine contains both.
Varispeed reads the samples at a different rate. It is what `playbackRate` does, it is exactly what a turntable does at a different RPM, and it is what DJ Screw was doing physically. Pitch and tempo move together because they are the same operation. Its great virtue is that it is exact — no sample is invented and nothing is spliced, so it cannot generate artefacts. It is the default across this site's tools for that reason.
WSOLA — Waveform Similarity Overlap-Add — changes duration without moving pitch. It cuts the audio into overlapping windows and lays them back down at different spacing, but rather than cutting at fixed intervals it searches nearby for the offset where the waveform best correlates with what has already been written, so the splices land where the signal is most self-similar. That search is why it works at all; splicing at arbitrary points produces obvious clicks.
The site implements WSOLA for the 432 Hz converter, where it is combined with a varispeed shift of the same ratio so the tempo change cancels and only the pitch shift remains. That is the standard way to build tempo-preserving pitch shifting.
The trade-off is honest and unavoidable: WSOLA has to decide where to overlap, and on transient-heavy material — drums, plucked strings, consonants — those decisions are audible as flamming or smearing. Varispeed has no such failure because it makes no decisions. You are choosing between an exact result whose tempo you do not control and an approximate result whose tempo you do.
4. A Real Bug, Found by Measuring
Here is the part that is specific to this engine, and a decent illustration of why audio bugs are hard to catch by ear.
Speed and pitch are exposed as two separate controls. Underneath, they are set on the same `AudioBufferSourceNode` — speed as `playbackRate`, pitch as `detune`. The Web Audio specification combines them: the effective rate is `playbackRate × 2^(detune/1200)`. They multiply.
That means the two controls were never independent, which is a design quirk. But it also meant the export code was wrong. It sized the output buffer as `duration / speed`, ignoring detune entirely — reasonable-looking code that is correct only when pitch happens to be zero.
Measured against a Node implementation of the Web Audio API, the consequences were concrete. A pitch shift of −12 semitones halves the effective rate, so the audio needs twice the room; the render allocated the original duration and half the track was cut off. The flagship 'deep' preset, at 0.75x speed with −2 semitones, actually plays at 0.668x and was losing about 11% off the end of every export. Positive shifts had the mirror problem, padding silence onto the end.
Nobody reported it, and that is the instructive part. A slowed edit that stops early does not sound broken — it sounds like the file ended. The bug was invisible to listening and obvious the moment anyone measured output length against expected length.
The fix was to route every duration and playhead calculation through one function computing the true combined rate, and the regression test asserts that a full-scale tone survives to the very end of the render at seven different speed and pitch combinations. It passes at 100% of expected length in all seven.
4a. Why This Generalises
The broader lesson is that audio bugs divide into two classes, and only one of them is catchable by listening.
Bugs that change the character of the sound — a wrong filter frequency, a bad crossfade, an inverted channel — announce themselves. You hear them immediately because you know what the material is supposed to sound like.
Bugs that change the extent of the sound — length, silence, truncation, a tail that never arrived — are nearly silent, because there is no reference to compare against. The only defence is measurement: render a known signal, assert something about the output that must be true, and let the assertion fail rather than waiting for a user to notice.
This is very doable in a browser context, which is not obvious. The audio engine here has no imports and no DOM dependencies, so it can be loaded directly in Node with a Web Audio implementation polyfilled in, and rendered buffers can be asserted on numerically. Every quantitative claim on this site's blog is produced that way.
5. Measurement: True Peak and Loudness
Two measurements in the engine are worth describing because they are commonly implemented wrong.
True peak exists because a file whose highest sample is below full scale can still exceed it on playback. Samples are measurements of a waveform, not the waveform itself, and the reconstructed curve between them can go higher than any individual point. The engine estimates this by reconstructing at four times the sample rate with a windowed-sinc interpolator, only near candidate peaks since anything well below cannot become the maximum. On a deliberately awkward signal — a cosine at exactly a quarter of the sample rate, phase-offset so samples straddle each crest — the sample peak measures −3.01 dBFS while the true peak reaches 0.00 dBFS. Three decibels of signal exist that no sample records.
Integrated loudness follows ITU-R BS.1770: filter the audio to approximate the ear's frequency response, then measure energy with a two-stage gate that ignores silence and very quiet passages. The engine derives the K-weighting filter coefficients analytically rather than hard-coding the published 48 kHz values, so the weighting stays correct whatever rate a file happens to decode at — a detail that matters because browsers decode to the audio context's rate, not the file's.
The reason both matter is that they measure genuinely different things. Three tones normalised to identical 0 dBFS peaks measured −3.3, −9.3, and +0.3 LUFS at 1 kHz, 40 Hz, and 10 kHz respectively. Same peak, six decibels apart in loudness, because the ear weights the midrange far more heavily than deep bass.
6. Psychoacoustics: What We Can and Cannot Claim
Three things change at once in a slowed reverb edit: tempo, pitch, and space. It is tempting to explain the emotional result with tidy neuroscience — a specific BPM range switching on a specific brain network, a semitone drop lighting up the reward centre — and you will find those claims widely repeated. We cannot find studies supporting them, so we are not going to make them.
What is well supported is that music is an unusually powerful cue for autobiographical memory, and that music-evoked nostalgia is a genuinely mixed emotion rather than a straightforwardly pleasant one. Reverb is on firmer ground still: the ratio between direct and reflected sound is one of the documented cues the auditory system uses to judge distance, which is why a long decay reads as a large space rather than merely as an effect.
There is one mechanism we can state with confidence because it is acoustics rather than neuroscience: varispeed scales formants along with the fundamental. Formants are resonances fixed by the size of a singer's vocal tract and normally stay put when they change note, so moving them implies a differently sized body. Slowed down, that reads as cavernous and heavy; sped up, it reads as childlike. That is a real, mechanical explanation for part of the emotional character, and it does not require any claims about brain regions.
- Tempo, pitch and space change together — the combination is what listeners respond to.
- Music is a strong autobiographical memory cue; the nostalgia it evokes is joy and sadness at once.
- Direct-to-reverberant ratio is a documented distance cue — long decay reads as a big room.
- Formant scaling is a genuine acoustic mechanism behind the 'deeper' or 'brighter' character.
- Claims tying specific BPM or semitone values to named brain regions are not well sourced.
7. Gain Staging in a Browser Chain
Every effect in the chain adds level, and the additions compound in ways that are easy to miss.
A bass boost adds gain directly. Reverb adds signal on top of the dry track, and where one note's tail overlaps the next note's attack those overlaps sum to peaks higher than either sound alone. Slowing the track moves energy into the low end, where the ear discounts it — so the result sounds less loud than it measures, which invites further boosting. The three effects push in the same direction and only one of them is obvious.
The reliable approach is to know your headroom before processing rather than discovering it afterwards. Measure the file, treat the gap to 0 dBFS as a budget, and spend it deliberately. If a source is already mastered close to the ceiling, reduce before boosting rather than boosting into a wall and turning down the clipped result — the flattening is baked into the samples at that point and nothing recovers it.
- Measure headroom before processing; it is a property of the file, not of the settings.
- Boost conservatively: +2 to +4 dB bass is a safe default, 35–45% reverb wet.
- Preview the loudest section, not the intro.
- Leave extra margin when exporting MP3 — the reconstructed waveform can exceed the samples.
- Export WAV whenever anything downstream will encode again.
The Short Version
A browser has the primitives to do this properly: the same convolution, the same filters, the same float precision. The quality ceiling is set by your source file and your settings, not by the platform.
What browser audio does have is its own class of bug — the kind that changes how much audio comes out rather than how it sounds, and which listening will not catch. The only defence is to measure, which is also the only way to write about any of it honestly.
Method
The truncation bug, the render-length regression, the true-peak overshoot, and the loudness figures were all measured by importing this site's `audio-engine.ts` directly into Node with `node-web-audio-api` providing the Web Audio implementation, and asserting on rendered buffers. The engine has no imports or DOM dependencies, which is what makes this possible.
Specific figures: the −12 semitone case truncated to 50% of required length before the fix and renders 100% after; the 0.75x/−2 st preset has an effective rate of 0.668x against a sized rate of 0.75x, an 11% shortfall. The inter-sample example is a cosine at exactly one quarter of the sample rate with a 45-degree phase offset, which produces a 3.01 dB overshoot analytically as well as by measurement. Loudness figures are integrated LUFS per ITU-R BS.1770 on four-second tones.
Note that a Node implementation of the Web Audio API is not necessarily identical to any given browser's, particularly in resampling behaviour. Claims about the specification's arithmetic hold everywhere; claims about implementation behaviour were verified in one implementation only.