A from-scratch Autotune built in Python with NumPy only — no pitch-correction, filtering, or pitch-detection libraries. Every block in the pipeline (FIR filtering, STFT, note quantization, frequency shifting, overlap-add resynthesis) is implemented directly from signals-and-systems fundamentals.
Four seconds of the take. Blue is the singer's actual pitch drifting between notes; orange is where the system moves it. Grey lines are the equal-temperament note grid.
| Input (raw vocal take) | pre_autotune.wav |
| After bandpass filtering | audio/original_bandpassed.wav |
| After pitch correction | audio/autotuned_output.wav |
Test signal: a 24-second sung vocal take, 48 kHz, converted to mono.
The notebook renders inline audio players for each clip when you run it locally; the exported
.wav files above are the same audio, committed so you can listen without running anything.
.wav ──▶ mono ──▶ FFT ──▶ bandpass ──▶ STFT peak ──▶ nearest ──▶ frequency ──▶ overlap-add ──▶ .wav
normalize survey 10–1500 Hz pitch track note shift resynthesis
16-bit PCM samples are rescaled to [-1, 1] by dividing by 2^15, and stereo is collapsed to mono
by averaging channels.
A single rfft over the full take shows where the vocal energy actually lives and sets the filter
cutoffs.
Rather than calling a filter-design routine, the filter is a moving-average FIR kernel convolved
with the signal. A length-fs/f_c box filter is a lowpass at roughly f_c, so subtracting a
10 Hz lowpass from a 1500 Hz lowpass yields a 10–1500 Hz bandpass — enough to strip DC drift and
room rumble below, and hiss and upper harmonics above, without touching the fundamental.
The signal is windowed at fs/10 = 4800 samples (0.1 s), giving 10 Hz bin spacing, with a hop
of window/8 — 87.5% overlap, dense enough to follow vibrato and note transitions without
smearing. Per frame, the pitch estimate is the argmax of the FFT magnitude; frames whose peak falls
outside the passband are marked NaN and passed through untouched, so silence and breaths are not
"corrected" into noise.
A four-octave equal-temperament scale (MIDI 48–95, C3–B6) is generated from
f = 440 · 2^((m − 69)/12), and each detected pitch snaps to the nearest entry by absolute
frequency distance.
Each frame is multiplied by a cosine at the difference between target and detected frequency — amplitude modulation used as a frequency shift, the same property the course covers as the modulation theorem. Frames are windowed with a triangular envelope and summed back with overlap-add; that envelope is what removed the frame-boundary clicking present in earlier versions. The output is peak-normalized.
The same four seconds in the frequency domain. Before correction, the harmonic stack slides continuously as the singer moves between notes; after, the partials hold flat in steps. The faint vertical striping in the lower panel is the frame boundaries of the overlap-add, and the harmonic spacing no longer stays proportional — both consequences of the linear frequency shift described below.
- Detected pitch carries at most ±5 Hz of quantization error — half of one 10 Hz STFT bin — so the correction lands inside the ±5 Hz accuracy target set in the project proposal.
- Correction runs offline over a 24 s take; the pipeline is frame-based, so it maps cleanly onto a streaming implementation.
- The characteristic "snapped to the grid" Autotune sound is audible in the output clip.
Honest accounting of what this does and does not do:
- The shift is linear, not harmonic. Multiplying by a cosine shifts every frequency component by the same number of Hz, whereas a true pitch shift scales them all by the same ratio. Harmonic ratios are therefore not preserved, which is a real part of why the output has a metallic timbre. A phase-vocoder or PSOLA resynthesis would fix this and is the obvious next step.
- Argmax pitch detection picks the loudest partial, not the fundamental. On takes where a harmonic dominates, the tracker octave-jumps. Autocorrelation or a harmonic-product spectrum would be more robust.
- Ring modulation produces a mirror sideband that the bandpass does not fully suppress.
- Offline only. The name says real-time; the implementation is frame-by-frame batch processing.
- 10 Hz bin spacing is coarse at low pitches, where adjacent semitones are under 10 Hz apart.
pip install -r requirements.txt
jupyter notebook RAPTOR.ipynbRun the cells top to bottom — pre_autotune.wav sits beside the notebook, so it works as-is. To
try your own audio, drop a .wav in the same folder and change file_name in Step 1.
RAPTOR.ipynb the full pipeline, cell by cell, with plots
pre_autotune.wav input audio the notebook reads by default
audio/ exported filtered and pitch-corrected clips
figures/ plots from each pipeline stage
tools/make_figures.py regenerates figures 06-07 from the committed audio
docs/project-proposal.pdf original project proposal
Convolution · FIR filter design · DFT/FFT · short-time Fourier transform · time–frequency resolution tradeoff · amplitude modulation and the modulation theorem · windowing and overlap-add · equal-temperament tuning
MIT — see LICENSE.






