Skip to content

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:

\[\text{buffer}[\text{writePos} \mathbin{\&} 4095] = x\]

2. Wow Modulation

Three sine LFOs run at irrational rate ratios to prevent exact periodicity:

\[\text{wow} = 0.5 \sin(\varphi_0) + 0.3 \sin(\varphi_1) + 0.2 \sin(\varphi_2)\]

Phase increments per sample:

\[\Delta\varphi_0 = \frac{2\pi \cdot r_w}{f_s}, \quad \Delta\varphi_1 = \frac{2\pi \cdot r_w \cdot 1.31}{f_s}, \quad \Delta\varphi_2 = \frac{2\pi \cdot r_w \cdot 0.73}{f_s}\]

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:

\[\text{flutter} = 0.5 \sin(\psi_0) + 0.3 \sin(\psi_1) + 0.2 \cdot n_{\text{filtered}}\]

Phase increments:

\[\Delta\psi_0 = \frac{2\pi \cdot r_f}{f_s}, \quad \Delta\psi_1 = \frac{2\pi \cdot r_f \cdot 1.47}{f_s}\]

The noise component uses xorshift32 white noise passed through a one-pole lowpass at 20 Hz:

\[\alpha_n = 1 - e^{-2\pi \cdot 20 / f_s}\]
\[n_{\text{filtered}}[n] = n_{\text{filtered}}[n-1] + \alpha_n \cdot (\text{noise}[n] - n_{\text{filtered}}[n-1])\]

4. Modulated Read Position

The read position combines a nominal delay with both modulation components:

\[d_{\text{total}} = 20 + d_w \cdot \text{wow} \cdot 10 + d_f \cdot \text{flutter} \cdot 3\]

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:

\[\text{readPos} = \text{writePos} - 1 - d_{\text{total}}\]
\[i = \lfloor \text{readPos} \rfloor, \quad f = \text{readPos} - i\]
\[y_{\text{wet}} = \text{buffer}[i] + f \cdot (\text{buffer}[i+1] - \text{buffer}[i])\]

6. Dry/Wet Mix

\[y = x + (y_{\text{wet}} - x) \cdot \frac{\text{mix}}{100}\]

Core Equations

\[\text{wow} = 0.5\sin(\varphi_0) + 0.3\sin(1.31\varphi_1) + 0.2\sin(0.73\varphi_2)\]
\[\text{flutter} = 0.5\sin(\psi_0) + 0.3\sin(1.47\psi_1) + 0.2 \cdot n_{\text{LP}}\]
\[d = 20 + d_w \cdot \text{wow} \cdot 10 + d_f \cdot \text{flutter} \cdot 3\]
\[y = \text{lerp}(\text{buffer}, \text{writePos} - d)\]

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::array with 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]