Lo-Fi Drum Programming: The Science of Swing and Humanization
The Hook
You quantize your drums to the grid. They sound robotic. You add “swing” in your DAW. They sound mechanical. Real human drumming isn’t random—it follows a 1/f power law. The secret to lo-fi groove isn’t “imperfection”—it’s structured variance that mimics human motor control.
The Thesis
Lo-fi groove comes from controlled stochasticity, not randomness. Human timing variance follows a power-law distribution (1/f noise), not uniform randomness. Velocity variance correlates with timing. Replicating this statistically—not randomly—creates the “lazy” feel that defines lo-fi hip hop.
The Science
The 1/f Power Law in Human Timing
Hennig et al. (2011, PLoS ONE) analyzed professional drummers:
- Inter-onset intervals (IOIs) follow 1/f^β power spectrum (β ≈ 1)
- Long-range correlations: a late hit predicts the next hit will also be late
- Not random: white noise (β=0) sounds jittery; 1/f sounds “human”
Key finding: Human timing errors are fractal—correlated across timescales from milliseconds to minutes.
Velocity-Timing Coupling
Dixon et al. (2006, Music Perception):
- Early hits → higher velocity (anticipation energy)
- Late hits → lower velocity (relaxed)
- Correlation: r ≈ -0.3 to -0.5 (earlier = louder)
Lo-fi implication: Random velocity + random timing = wrong. Coupled variance = right.
Microtiming in Lo-Fi
Lo-fi aesthetic (Katz, 2010 Capturing Sound):
- Kick: slightly early (anticipation) → “push”
- Snare: slightly late (laid back) → “drag”
- Hi-hats: swung, with 1/f variance
DAW “swing” fails because it applies fixed offset (e.g., +10ms every other 16th). Real swing breathes—the offset varies with 1/f statistics.
The Protocol: Programmatic Humanization
The Algorithm (Pseudo-code)
import numpy as np
def humanize_pattern(onsets, velocities, bpm=90, style="lofi"):
"""
Apply 1/f humanization to quantized pattern.
"""
# 1. Generate 1/f noise for timing offsets
n = len(onsets)
# Generate 1/f noise via FFT method
white = np.random.randn(n)
fft = np.fft.rfft(white)
freqs = np.fft.rfftfreq(n)
# 1/f spectrum
fft[1:] /= freqs[1:] ** 0.5 # β=1
timing_noise = np.fft.irfft(fft).real[:n]
timing_noise = timing_noise / np.std(timing_noise) # normalize
# 2. Scale to musical values
ms_per_16th = 60000 / bpm / 4
timing_offset = timing_noise * (ms_per_16th * 0.08) # ±8% of 16th
# 2b. Style-specific bias
if style == "lofi":
# Kick early, snare late
for i, note in enumerate(notes):
if note == "kick": timing_offset[i] -= ms_per_16th * 0.03
if note == "snare": timing_offset[i] += ms_per_16th * 0.04
# 3. Coupled velocity variation
# Early = louder, late = softer
vel_noise = np.random.randn(n) * 0.15
vel_offset = -timing_noise * 0.4 + vel_noise # coupling + independent
# Apply
new_onsets = onsets + timing_offset
new_vels = np.clip(velocities * (1 + vel_offset), 1, 127)
return new_onsets.astype(int), new_vels.astype(int)
DAW Implementation (No Code)
Ableton/Logic/FL Studio:
- Groove Pool: Extract groove from real drum loop (not quantized)
- Apply with 30-50% amount — not 100%
- Per-element: Different groove for kick vs. snare vs. hats
- Velocity: Use “Velocity > Random” 10-20% + “Velocity > Scale” linked to timing
Manual method (any DAW):
- Duplicate MIDI track
- Nudge kick -10 to -20ms (early)
- Nudge snare +15 to +30ms (late)
- Hats: Random ±15ms per hit (use humanize function at 15%)
- Velocity: Kick +10, Snare -5, Hats ±15 random
The “Lo-Fi” Frequency Response
EQ curve for lo-fi drums:
- Kick: +3dB @ 60Hz, -3dB @ 200Hz, -6dB @ 4kHz
- Snare: -3dB @ 200Hz, +3dB @ 2.5kHz, -6dB @ 8kHz
- Hats: -6dB @ 200Hz, +3dB @ 6kHz, -12dB @ 12kHz (lo-fi rolloff)
Saturation: Soft clipper (tube/tape) at -6dB threshold → adds harmonics, reduces transients
The Counterpoint
- Over-humanization = sloppy, not groovy
- Genre matters: House/techno needs tighter timing
- Live drummer > any algorithm (but not always available)
- Listener adaptation: Brains adapt to grid; slight humanization > heavy
Practical Takeaway
This session: Take your quantized drum loop. Apply:
- Kick: -15ms, +5 velocity
- Snare: +20ms, -3 velocity
- Hats: ±15ms random, ±15% velocity random
- Add 1/f noise (or use groove template from real drummer)
- EQ: roll off >8kHz, saturate lightly
Compare to quantized. The groove appears.
Sources: Hennig et al. (2011) PLoS ONE; Dixon et al. (2006) Music Percept.; Kilchenmann & Senn (2011) J. Acoust. Soc. Am.; Katz (2010) Capturing Sound; Butterfield (2011) Jazz Perspectives; Ableton/Logic/FL Studio groove pool docs.