Repair an existing music transcription by treating the best human transcription as the musical model and isolated stems as evidence that tests individual claims inside it. Every rule below has a shipped failure behind it.
Start from the human transcription. Use stems to test claims inside it. A human transcription establishes the grid, the meter, the rhythmic spelling, the instrument vocabulary and the phrase structure. Audio decides whether the written events happened. When the two disagree, investigate that specific disagreement.
The failure this replaces. A repair placed 164 toms using a printed chart for the count and stem onsets for the timing. Ten toms in one measure landed at 11.23, 11.55, 11.94, 12.85, 13.78, 14.74, 15.73, 16.01, 16.45 and 18.63 sixteenths. Two pairs sat under a third of a sixteenth apart, so the writer subdivided to 32nds and stacked High Tom on Hi-Mid Tom. Across 40 tom measures, 30 held a placement over 0.20 of a sixteenth off grid. It was rewritten by hand as plain descending sixteenths 26 minutes later.
tr_ledger.py keys prints every element and property name
with counts. A missing key is evidence only after this runs. Searching <Ghost>
and finding zero started the sweep that stripped 3,842 ghosted noteheads off sixteen live
tabs. The element is <AntiAccent>.CONFIRMED,
no stem timing authorizes an edit.LOCAL_VALIDATED, which is not a submitted revision.| Trap | What it looked like | What it was | The refusal |
|---|---|---|---|
| No null at all | 45.5% match | 43.4% against the same audio reversed, 35.5% random | the tab carried the idiom and density of the music and almost none of its events |
| Dense detector | 64.0% match | 58% chance baseline | above a 35% null the gate demands 25 points, not 5 |
| Periodic annotation | 100% match, z looks fine | rotation null 94% | a rotation of a metronome is nearly the metronome. UNUSABLE, route to landmarks |
The landmark route is what actually carries a repetitive part. It requires a stated selection rule, at least 8 landmarks, 50% coverage, chance occupancy computed from the real detected set, a rotation null, a binomial p value, and the list of landmarks that failed. A selection rule chosen after seeing what matched is refused, because the landmarks would then have been chosen because they matched.
The suite tests whether an answer was earned. Producing an answer from deliberately inadequate evidence fails even when the code runs clean.
| # | Fixture | Required behaviour | Result |
|---|---|---|---|
| 1 | tom onsets carried as raw seconds | refuse, name the off-grid count | 3/7 off grid |
| 2 | two toms 3 Hz apart | UNRESOLVED for every event | calib distance 0.68 |
| 3 | a 6-second crash decay | one attack | 1 of 3 candidates |
| 4 | pedal chicks missing in three bars | find all three | bars 2, 5, 9 |
| 5 | a ghost figure in 6 of 10 bars | sweep the other four as PROPOSED | bars 6–9 |
| 6 | wrong recording, same tempo and density | refuse | 36% vs 23% null |
| 7 | three tempo changes | read all three | 84–132 bpm |
| 8 | a track named Drums, all notes off the GM map | not a drum staff | 100% off map |
| 9 | a 4.2 px staff space | UNRESOLVED without calibration | refused |
| 10 | an edit touching an unrelated track | regression fails | midi 43 → 45 caught |
| 11 | a bar where only the ride plays | protected from a silence sweep | bar 1 protected |
| 12 | a 50 ms refractory at 170 bpm on 32nds | refuse the detector setting | 1/32 = 44.1 ms |
| 13 | a validation record for an older hash | not current | hash mismatch |
| 14 | an unanswered recording-identity check | refuse the whole map | 6 unanswered |
| 15 | a cymbal proposal | less machine credit than a kick | F1 0.481 vs 0.931 |
An edit with no evidence fields cannot enter the ledger. A PROPOSED edit
cannot jump to VALIDATED. Promotion is refused on a failed regression, on a
non-CONFIRMED alignment, and on a stale hash. Two tabs agreeing at 99.5% count
as one source. An unmeasured source pair is reported rather than assumed independent. An
iCloud placeholder is refused as an asset.
One ranking that answers every question is the wrong shape.
| Question | Who decides |
|---|---|
| Meter, barring, phrase structure | publisher or academic score, then specialist chart |
| Written subdivision and rhythmic spelling | human transcription, then validated timing |
| Does this stroke exist in the take | isolated stem, then full mix, then the score |
| Which drum sounded | isolated stem, then score, then chart |
| How loud relative to its neighbours | isolated stem, then score |
| Which articulation is intended | score, then chart, then stem |
| What a notation symbol means | the notation standard, via /notation-evidence-gate |
So an isolated snare stem outranks a human transcription on whether one particular strike exists. A professional score outranks raw onset timing on whether a late strike belongs on the written sixteenth.
Benchmark F1 at a 50 ms one-to-one protocol on MDB Drums. Treating separation quality as one number lets a cymbal proposal inherit a kick's credibility.
| Class | F1 | Tier |
|---|---|---|
| kick | 0.931 | strong |
| snare | 0.760 | usable |
| hi-hat | 0.650 | weak |
| toms | — | weak, commonly one combined stem |
| cymbal | 0.481 | weak, dense broadband |
On GP bars 99 to 103 of a coda marked p / pp and "cymbal on dome",
cymbal_events(instrument="ride")returned zero fresh attacks against 75 written events. Nothing in the return value said the detector had failed, so the zero read as an absence and very nearly became a deletion.
Two repairs were tried on a reproduction fixture of twelve quiet dome pings under a loud crash bed. Both failed, and the failure is the finding.
| Route | Best operating point | Verdict |
|---|---|---|
| novelty threshold sweep, 3 bands x 5 deltas x 2 refractory settings | recall 0.92 at precision 0.11, or precision 0.60 at recall 0.25 | unusable |
| spectral template matched to hand-labelled strokes from the same stem | held-out gains +0.003 to +0.015 against random instants −0.027 to +0.015, fully overlapping | unusable |
A quiet dome ping far under a loud resonant bed is not recoverable from that stem. Three refusals shipped in place of a tuned number.
The bell profile returns
CANDIDATE_ONLY. No downstream step may read one of its hits as an attack.
Under 15 percent of what the notation writes
across 8 or more events returns DETECTOR_FAILURE. The silent zero becomes a
loud refusal, and the eye wins.
Holds out half the labelled strokes against
random instants clear of every label. NOT_SEPARABLE means stop sweeping and
get a better-isolated stem.
Each of these failed on the first run, and each failure changed the design rather than the expectation.
A metronomic fixture could not be
CONFIRMED by a rotation null, and it should not be. Periodicity is now measured
off the annotation's own intervals and routed to landmarks unconditionally.
A hardcoded 0.6 s decay against a 1.4 s ring predicted a fall the cymbal never takes, and noise scored as five attacks where four existed. The constant is now measured on the recording.
Two toms 2 Hz apart were labelled confidently because within-class spread was tiny. Every call is now gated on whether the calibration itself separates, with a measurement floor per feature.
T=~/.claude/skills/transcription-repair
python3 $T/tr_ledger.py keys FILE # key census. Run FIRST of all
python3 $T/tr_ledger.py diff A B # note-INSTANCE diff, 7 conflict classes
python3 $T/tr_ledger.py regress ORIG EDIT --scope-track 3
python3 $T/tr_audio.py onsets STEM.wav --instrument snare --out on.txt
python3 $T/tr_audio.py align --notated n.txt --detected on.txt --duration 372.4
python3 $T/tr_audio.py cymbal RIDE.wav # NEW_ATTACK / DECAY_ONLY / UNRESOLVED
python3 $T/tr_audio.py toms TOMS.wav --calib calib.json
python3 $T/tr_audio.py leak --target TOM.wav --others SNARE.wav --at 91.44
python3 $T/tr_score.py grid L.json # the chart's own subdivision vocabulary
python3 $T/tr_score.py hands L.json # two hands, two feet, ornament guards
python3 $T/tr_score.py ghosts L.json --lane 38
python3 $T/tr_score.py pedal L.json
python3 $T/tr_gate.py tempo SCORE.gp # EVERY tempo event
python3 $T/tr_gate.py track SCORE.gp # is this staff actually drums
python3 $T/tr_gate.py promote --ledger E.jsonl --original A.gp --edited B.gp \
--scope-track 3
$T/run_selftests.sh # all 4 suites, exit 0 required
Completion is a saved, scoped artifact at an exact hash whose relevant track passed the whole validation suite, with an edit ledger explaining every changed instance. Successful script execution is not completion, since exit 0 proves only that a tool ran.
A finished repair reads bar by bar. Another musician can reconstruct why every changed event changed. Uncertain events stay visibly unresolved. Expect fewer edits than an unconstrained audio-to-MIDI pass would make. Existing expert work survives unless specific evidence justifies changing it, which is the design target rather than a shortfall against it.
Skill: ~/.claude/skills/transcription-repair/ ·
4,344 lines of Python, 692 lines of SKILL.md, 404 lines of reference ·
SKILL.md, tr_ledger.py, tr_audio.py,
tr_score.py, tr_gate.py, tr_verify.py, tr_edit_lock.py, run_selftests.sh,
reference/research-dossier.md, reference/gp-percussion.md
Composes with: /notation-evidence-gate ·
/songsterr-drum-repair · /songsterr-upload ·
/stems-to-guitar-pro-drums · /impossible-guitar-parts ·
/composite-stem-alignment
Numbers on this page are measured. Benchmark F1 figures are cited from
published drum-transcription evaluation and carried as priors on machine evidence only.
Provenance is marked per fact in reference/research-dossier.md.