06 · Fixing a beat map by hand
Chapter 05 detected problems and stopped there — detection is the math layer's responsibility, fixing is a judgement call that belongs above it. This chapter is the other half: the user repairs a mostly-correct map by hand.
It is also where the two layers built separately finally operate together. Chapter 02's pin() lives in tempo space — pairs of barPositionOf lives in meter space — bar index, position-in-bar, count of beats per bar. An accidental extra beat in a 4/4 bar is invisible in pure tempo terms — nothing about the tempo curve says "this bar has 5 beats." Only the meter overlay flags it. Every repair operation in this chapter speaks to both layers: it rebuilds the tempo markers and re-derives which absolute beat indices are downbeats, because deleting or inserting shifts those indices.
repair.js -- hand-edit operations for a beat map
Chapter 05 detects problems in beat-tracker output and deliberately stops there: the math layer reports anomalies, but fixing them is a judgement call that belongs above it. Chapter 06 is the other half of that pair -- the user repairs a mostly-correct map by hand, with the math layer doing only what it must to keep edits valid.
This is also where the two layers built separately finally operate together. Chapter 02's pin() lives in tempo space ({beat, second} pairs and the warp map); chapter 04's barPositionOf lives in meter space (bar index, position-in-bar, count of beats per bar). An accidental extra beat in a 4/4 bar is INVISIBLE in pure tempo terms -- nothing about the tempo curve says "this bar has 5 beats." Only the meter overlay flags it. So every repair op here speaks to BOTH layers: the operation rebuilds the tempo markers AND re-derives which absolute beat indices are downbeats, because deleting or inserting shifts those indices.
Three repair operations match the three error types beat_this makes:
- extra/doubled beat -> deleteBeat
- dropped/missing beat -> insertBeat
- mistimed beat -> moveBeat (a thin wrapper over ch.02's pin)
Three error types, three operations
Real beat-tracker output makes three kinds of mistake, each with its own repair:
| Error | What the bar looks like | Repair |
|---|---|---|
| Extra / doubled beat | bar has too many beats | deleteBeat |
| Dropped / missing | bar has too few beats | insertBeat |
| Mistimed beat | right count, wrong time | moveBeat |
moveBeat is exactly chapter 02's pin() interaction — drag a marker to re-pin a beat at a new audio second, with the same monotonicity guard. deleteBeat and insertBeat are new, and both have to recompute the absolute indices of downbeats afterwards — a deleted beat shifts every downstream downbeat one index earlier, and an inserted beat shifts them one later. This is the subtle bug a naive implementation misses: the data is correct in time but wrong about which beat is the downbeat.
Live validation as the editing feedback loop
The editor shades every bar by the result of validateAgainstMeter(markers, downbeatIndices, expectedBeatsPerBar). Bars whose beat count matches the target glow green; bars with the wrong count turn red and show the actual count next to the expected. The user knows they've made a successful edit because a bar visibly flips colour. When every real bar is correct (the pickup and trailing partial bar get a free pass — they're defined to be short), an all bars correct ✓ badge appears in the summary.
Try the demo
Open the full-screen standalone demo ↗ — adds .beats upload and download (your file stays in your browser).
The default state is a 4/4 fixture with three deliberate errors so all three repair operations are exercisable in 30 seconds:
- Bar 2 has an extra beat (5 beats — expected 4) → select it and press delete.
- Bar 3 has a missing beat (3 beats — expected 4) → click the empty space in the gap to insert.
- The last beat of bar 4 is mistimed (about 40 ms early) → drag it.
When all three are fixed, the editor lights up green and the badge appears.
Upload → fix → download
The standalone demo also accepts a .beats file via the file input. Your file stays in your browser — parsing is fully client-side, no network. After you've hand-corrected the map, the "download corrected .beats" button writes out the same TSV format beat_this produces, so the result is a drop-in replacement for the file you uploaded. Round-trip:
parse → hand-edit → exportBeatsTsv → drop into your projectThe .beats parser and writer both live in @warp-math/beats-io — one workspace shared between chapter 03 (which reads the format to drive Web Audio playback) and chapter 06 (which both reads and writes). The format definition exists in exactly one place so the two chapters can't drift apart on it.
A real repair, end to end
The chapters earn their keep on real tracker output. otherside.beats (512 beats, bundled with chapter 08's demo) appears to contain a 5/4 bar around 30 s — but the gaps tell the truth:
idx 55 t=29.66 gap=0.540 ← on the ~0.52 s grid
idx 56 t=29.76 gap=0.100 ← ghost beat (implied 600 BPM)
idx 57 t=30.20 gap=0.440The "5/4 bar" is a ghost beat — a spurious detection. Each chapter contributes exactly the piece it was built for:
- Chapter 05 detects —
flagAnomaliesfirestoo-fast(600 BPM, "doubled beat?") on segment 55. - This chapter localizes —
validateAgainstMeternames one bad bar: 5 beats, rows 53–57. - This chapter repairs —
deleteBeatremoves the ghost. Which endpoint of the flagged gap is spurious is a judgement call (the flag names a gap, not a beat): pick the one whose removal best restores the local tempo — here idx 56, since 29.66 sits exactly on the grid and 29.76 doesn't. beats-ioexports —exportBeatsTsvround-trips a clean file.- Chapter 08 proves it —
gridPlanFromBeatson the repaired file collapses from three meter entries to a single 4/4; the phantom 5/4 is gone. Compare the otherside and otherside (repaired) samples in the chapter 08 demo and watch the meter-entry count change.
scar_tissue.beats carries the other labeling defect: its final beat is marked beatInBar=1, conjuring a phantom 1-beat bar. No beat is wrong — only the label is. That repair is relabelDownbeat, the fourth operation: a pure meter-layer edit that passes the tempo markers through by reference, because nothing in tempo space changed. Chapter 05 sees nothing wrong with this file (the beat times are perfect) — the meter overlay is the only witness, which is this chapter's founding observation coming full circle.
The whole pipeline runs as a worked example in 08-grid-follows-file/repair-pipeline.test.js, and the bundled otherside-repaired.beats is asserted byte-for-byte to be the pipeline's own output.
What this chapter is NOT
- No auto-repair. The validator shows what's wrong; it never changes the data on its own. An auto-corrector would undercut both this chapter and chapter 05's framing, which is that fixing is a judgement call the human needs to make.
- No audio editing. Hand-edits move the markers, not the underlying audio. If you load an audio clip for playback, the grid moves over unchanged samples — same as in chapter 04.
.beatsonly. No MIDI, no Ableton ASD, no exotic formats. The whole point is to round-trip withbeat_thisoutput cleanly.
Try it locally
cd 06-repair
npm run dev