Browser-based real-time autotune. No backend, no dependencies — one HTML file and one AudioWorklet.
Live: silvertune.live
Microphone input is fed into an AudioWorklet running a hand-rolled YIN pitch detector. Detected pitch is snapped to the nearest note in the selected key/scale, and a 2-tap grain shifter (Hann-windowed COLA, phases 0 and ½) pitch-shifts the audio on the fly. A canvas piano visualises the detected note (blue) and the corrected target (white/pressed). Keys outside the active scale are masked out.
The companion app mode connects via WebSocket (ws://127.0.0.1:2747) and runs the same YIN + grain-shift stack natively using miniaudio, letting you use the browser just as a UI while processing goes through your real audio interface.
| Control | Range | What it does |
|---|---|---|
| Key | C – B | Root key for the scale |
| Scale | Chromatic / Major / Minor | Scale to snap to; Chromatic disables correction |
| Speed | 0 – 100 ms | Exponential glide time to the target pitch ratio |
| Hold | 0 – 200 ms | How long a note must be held before correction commits |
| Wide | 0 – 100% | Stereo doubler blend (slightly detuned second grain) |
| ▣ (bottom right) | — | Minimal mode — hides the canvas visualiser |
The companion bridges the browser UI to your audio interface. It runs the full audio pipeline natively (miniaudio) and streams pitch reports back to the page over a local WebSocket.
ws://127.0.0.1:2747
Messages sent to the companion (JSON):
{ "speed_ms": 0, "hold_ms": 0, "key_idx": 0, "scale_idx": 1, "wide": 0.0 }Messages received from the companion (JSON):
{ "type": "pitch", "detectedNote": 60, "correctedNote": 60, "rms": 0.02 }Linux / macOS
chmod +x silvertune-companion-linux # or -macos
./silvertune-companion-linuxmacOS — if your DAW won't scan the plugin
xattr -rd com.apple.quarantine Silvertune.clapWindows — run silvertune-companion-windows.exe directly.
Microphone
│
▼
AudioWorkletNode (worklet.js — dedicated audio thread)
├─ YIN pitch detection BUF=1024 HALF=512 HOP=32
├─ Scale quantization
└─ 2-tap grain shifter grainSize=256, phases 0 / 0.5, Hann window
│
├──▶ actx.destination (live monitor)
└──▶ MediaStreamDestination (recording)
Main thread (index.html)
├─ Canvas render loop (requestAnimationFrame)
│ ├─ Animated silver/white blob background
│ ├─ Sonar rings + orb flashes on note events
│ └─ Piano visualisation (OffscreenCanvas cache for inactive keys)
└─ XMB-style navigation (FX · Sound · Rec · Settings)
Companion (companion/src/)
├─ miniaudio audio I/O
├─ Hand-rolled YIN (HOP_SIZE=128)
├─ GrainShifter (BUF=4096)
├─ Hand-rolled WebSocket server (port 2747)
└─ Single-instance PID lock
YIN difference function computed every HOP samples on a rolling circular buffer. Parabolic interpolation refines the period estimate. A stays_locked hysteresis (±0.4 semitones) prevents robo-vibrato on sustained notes while still tracking note changes. The Hold timer requires a new note to be stable for at least hold_ms before the correction ratio commits.
Two overlapping grains at phase offsets 0 and 0.5 of the grain window. Each grain reads back at a position scaled by (1 - pitchRatio) and is windowed with a Hann envelope. The 0.5-offset pair sums to unity gain with no normalisation needed (COLA). A second slightly-detuned shifter feeds the Wide doubler.
python3 -m http.server 8080
# open http://localhost:8080AudioWorklets require a secure context (HTTPS or localhost).
projector.html is a standalone fullscreen canvas showing only the visualiser (blobs + piano). Designed for stage use — open it on a second screen while running the main UI on your primary display. It receives pitch data from the same WebSocket connection.
GitHub Actions deploys to GitHub Pages on every push to master. Custom domain via CNAME.