Wow and Flutter
Tier: Color | ComponentType: 28 | Params: 5
Modulated delay read position simulating tape and vinyl speed instability artifacts.
Overview
WowAndFlutter recreates the pitch and timing variations inherent in analog tape machines and vinyl turntables. "Wow" refers to slow, periodic speed fluctuations (typically below 5 Hz) caused by eccentric reels or warped platters, while "flutter" describes faster, more erratic variations (typically 1--30 Hz) from capstan irregularities, roller vibration, and tape tension changes.
The implementation uses a short circular delay buffer with a modulated read position. The modulation signal is built from multiple sine LFOs at irrational frequency ratios, ensuring the combined waveform never repeats exactly. The wow component uses three LFOs weighted to produce a complex, slowly drifting pitch variation. The flutter component uses two faster LFOs plus low-pass filtered noise, capturing the quasi-random character of mechanical flutter.
At low depth settings, the effect adds subtle warmth and "alive" quality to static signals. At higher depths, the pitch wobble becomes clearly audible, evoking worn cassettes or damaged vinyl. Because the wow and flutter rates are independent, you can dial in combinations ranging from gentle tape-like drift to aggressive warped-record effects.
File Locations
| Path | |
|---|---|
| Header | Sources/FolioDSP/include/FolioDSP/Color/WowAndFlutter.h |
| Implementation | Sources/FolioDSP/src/Color/WowAndFlutter.cpp |
| Tests | Tests/FolioDSPTests/WowAndFlutterTests.swift |
| Bridge | Sources/FolioDSPBridge/src/FolioDSPBridge.mm (WowAndFlutterBridge) |
Parameters
| Index | Name | Description | Min | Max | Default Min | Default Max | Default | Unit |
|---|---|---|---|---|---|---|---|---|
| 0 | Wow Depth | Amplitude of slow pitch modulation | 0.0 | 1.0 | 0.0 | 1.0 | 0.3 | |
| 1 | Flutter Depth | Amplitude of fast pitch modulation | 0.0 | 1.0 | 0.0 | 1.0 | 0.2 | |
| 2 | Wow Rate | Base frequency of the wow LFO cluster | 0.05 | 5.0 | 0.1 | 3.0 | 0.5 | Hz |
| 3 | Flutter Rate | Base frequency of the flutter LFO cluster | 1.0 | 30.0 | 3.0 | 15.0 | 7.0 | Hz |
| 4 | Mix | Dry/wet blend | 0 | 100 | 0 | 100 | 100 | % |
Processing Algorithm
The process() function executes these steps for each input sample:
1. Buffer Write
The input sample is written into a 4096-sample circular delay buffer using power-of-2 mask wrapping:
2. Wow Modulation
Three sine LFOs run at irrational rate ratios to prevent exact periodicity:
Phase increments per sample:
where \(r_w\) is the wow rate in Hz. The ratios 1.31 and 0.73 are irrational-like values that ensure the combined waveform has a very long period before repeating.
3. Flutter Modulation
Two faster sine LFOs plus low-pass filtered noise:
Phase increments:
The noise component uses xorshift32 white noise passed through a one-pole lowpass at 20 Hz:
4. Modulated Read Position
The read position combines a nominal delay with both modulation components:
where \(d_w\) and \(d_f\) are the wow and flutter depth parameters. The nominal delay of 20 samples provides headroom for modulation excursions. Wow modulates up to \(\pm 10\) samples and flutter up to \(\pm 3\) samples, reflecting their relative perceptual magnitudes.
5. Linear Interpolation Read
The fractional read position is resolved with linear interpolation:
6. Dry/Wet Mix
Core Equations
Snapshot Fields
| Field | Type | Range | Unit | Description |
|---|---|---|---|---|
| Input Level | Float | 0--1 | Smoothed input amplitude | |
| Output Level | Float | 0--1 | Smoothed output amplitude | |
| Wow Value | Float | -1--1 | Current wow modulation signal | |
| Flutter Value | Float | -1--1 | Current flutter modulation signal | |
| Total Modulation | Float | -1--1 | Combined wow + flutter delay offset |
Implementation Notes
- Circular buffer uses a 4096-sample
std::arraywith power-of-2 mask wrapping (writePos & 0xFFF), avoiding modulo operations. - LFO rate ratios (1.31, 0.73 for wow; 1.47 for flutter) are chosen to be non-integer and non-simple-fraction values, ensuring the combined modulation has a very long effective period.
- Noise LP cutoff at 20 Hz constrains the random flutter component to low frequencies, preventing it from introducing audible artifacts above the flutter range.
- Total delay is clamped to
[0.5, bufferSize - 2]to ensure valid interpolation indices. - Level tracking uses exponential smoothing with coefficient 0.01 for display-rate visualization.
- All parameters use
std::atomic<float>for lock-free thread safety. - Snapshot emission is decimated to ~60 fps (every 735 samples at 44.1 kHz).
Equation Summary
y = buf[readPos + wow + flutter]