Claude Code plus Python for auditing and repairing Guitar Pro tabs, with Guitar Pro for human verification.
Jacks-iMac · x86_64 · macOS 13.7.8 · built and measured 2026-08-28
Four premises in the proposed setup do not hold on this machine, and the fixes cost less work than the original plan. This page carries the corrections, the live inventory, the repo that got built, and the one thing still blocked.
Read the actual interpreter, architecture, package versions, and app list. Most of the proposed stack was present before this session, and one proposed step would have reintroduced a known crash. done
Twenty five validators live in ~/.claude/skills/impossible-guitar-parts/, tuned against recorded regression cases. The new repo calls them and never forks them. Ships doctor.py, validate_song.py, env.sh, and a pytest suite. done
validate_song.py "Mundo Cani" runs end to end against the real corpus and surfaces a finding the plan did not anticipate. This page is the showcase. done
uname -m returns x86_64 on macOS 13.7.8. The Apple Silicon rationale for pinning Basic Pitch to Python 3.10 does not apply here.
~/venvs/audio_midi_311 holds basic-pitch 0.4.0 on Python 3.11.15 with onnxruntime 1.23.2. The constraint on this machine is the model format. The Python version is not what breaks. Basic Pitch defaults to:
/Users/drwu/venvs/audio_midi_311/lib/python3.11/site-packages/
basic_pitch/saved_models/icassp_2022/nmp.mlpackage
That CoreML package SIGFPEs on this Mac. nmp.onnx ships in the same directory, so no download is needed. env.sh pins BASIC_PITCH_MODEL_TYPE=onnx, and a pytest case fails if that pin is ever dropped.
Python 3.13.6 at /usr/local/bin/python3 carried pyguitarpro, lxml, numpy, scipy, librosa, soundfile, audioread, mido, pretty_midi, pandas, matplotlib, pydub, and music21. Only pytest was missing, now installed at 9.1.1. Building a fresh 3.12 venv would have duplicated thirteen working packages. Demucs 4.0.1 is installed in ~/venvs/demucs on Python 3.12.13 with torch 2.2.2.
validate_song.py mostly existsThe gates for pitch preservation, impossible spans, string collisions, ownership, octave doubling, tuning, and candidate versus source statistics are written and tuned. Writing a second set from scratch would have thrown away the recorded regression cases. The new script is an orchestrator over them, roughly 130 lines.
| Component | Proposed | Found on the machine | Action |
|---|---|---|---|
| Homebrew | install | 6.0.15 | present |
| git | install | 2.39.2 (Apple) | present |
| ffmpeg | install | 8.1.1 | present |
| uv | install | 0.11.29 | present |
| Claude Code | brew cask | ~/.local/bin/claude | present |
| Python 3.12 validator env | new venv | 3.13.6 with 13 of 14 pkgs | used in place |
| pytest | in the venv | absent | installed 9.1.1 |
| Python 3.10 Basic Pitch env | new venv | 3.11.15, basic-pitch 0.4.0 | reused, ONNX pinned |
| Demucs | new venv | 4.0.1, torch 2.2.2 | present |
| Sonic Visualiser | install | absent | installed 5.2.1 |
| Guitar Pro 8 | install | absent | needs your password |
| MuseScore 4 | auxiliary | absent | left out, optional |
Repo at ~/Music/guitar-transcription, git initialised, one commit.
| File | Does | |||||||
|---|---|---|---|---|---|---|---|---|
| env.sh | Pins the three interpreters and BASIC_PITCH_MODEL_TYPE=onnx. Source it before anything. | |||||||
| doctor.py | 33 live checks across CLI tools, packages, both transcription environments, the GUI apps, the six gates, and the corpus. Exits non zero on a hard failure. | |||||||
| validate_song.py | Takes a path or part of a song title, resolves the candidate and its guitar stem out of the corpus, runs the gates, prints one verdict, writes JSON to reports/. | |||||||
| octave_probe.py | Tests the 87/88 harmonic hypothesis. Known-answer self test, count matched control, wrong time control, two anchor map health gate. Refuses to publish a verdict on a suspect map. | |||||||
| beatmap_fit.py | Derives offset and tempo from the stem by onset correlation. Thresholds calibrated against positive and negative controls. Refuses a map that cannot beat both. | |||||||
| tests/test_workstation.py | The rules as tests. Package imports, the ONNX pin, gate presence, the human tab regression anchor, and a check that no pitch exceeds a 22 fret neck. |
| Gate | Verdict | What it found |
|---|---|---|
| playability | review | 585 notes, 539 attacks, hand skip 42 percent, SHIFT x4, severe JUMP x7, isolated octave x6, ownership suspect x9, tuning evidence flagged. BPM read as 172. |
| octave copy and note loss | pass | 0.0 percent copy rate on both guitar staves. Neither staff is the other at fret plus twelve. |
| audio accuracy | not run | Needs a validated beat map. The stem was resolved automatically and is ready. |
| duration and tie | not run | Same requirement. |
The existing two staff split of this song put 585 notes on Guitar and 1 note on Guitar 2. The split ran and produced a file, and the file does almost nothing. The 42 percent hand skip and the nine ownership flags remain on the first staff. Exit zero and a written file prove only that the tool ran. Neither measures whether the output is good.
Another session is editing that same tree right now. The delivery README changed on disk mid read. Nothing here writes into ~/Projects/_outputs/APPLESEED-DELIVERY-2026-08-28/, and coordinating before any repair pass is the safe move.
The proposal routes through a two guitar ownership step. On this album that step was measured on 2026-08-11 and it does not separate. Both players fall within about 1 dB of each other in the stereo image where roughly 12 dB would be needed to assign a note. Ownership scored at chance across all 46 songs. The workable route is the one the corpus README describes: split an unplayable beat into two hands by register and reach, change no pitches, and treat the audio as evidence for pitch rather than for authorship.
Task 2 in the corpus README: 19 bad beats hold a pitch that does not exist on a 22 fret
guitar, only MIDI 87 and 88, across five songs. The suspicion is that the transcriber heard the
second harmonic of a note an octave below and wrote the harmonic as the note.
octave_probe.py tests that. It measures band energy at the written pitch against
the band an octave below, in the guitar stem, at each note's own time.
Two synthetic signals with a known answer, run on every invocation. The probe refuses to read audio if either fails.
SELF TEST (synthetic signals, known answer) [ok] written_pitch_is_real ratio 6051095.288 [ok] octave_below_with_harmonic ratio 0.400 => the measure separates the two cases
This needs no audio and no time map, so it holds regardless of everything below.
| Song | Count | Written position |
|---|---|---|
| 30 Degrees 3 Am | 1 | string 5, fret 23, pitch 87 |
| Barrier Islands (Do We Remain) | 4 | string 5, fret 24, pitch 88 |
| Sentence | 7 | string 5, fret 24, pitch 88 |
| Convict | 3 | string 5, fret 23 x2 and fret 24 x1 |
| Confession | 4 | string 5, fret 24, pitch 88 |
| Total | 19 | 88 on str5/fret24 x16, 87 on str5/fret23 x3 |
Every one is on string 5, the high E, at fret 23 or 24. Not one appears on any other string. Since 87 and 88 have no legal position on a 22 fret neck, the writer had nowhere to put them and fell back to arithmetic fret on the top string. That makes this one systematic fallback firing 19 times rather than 19 independent mishearings, and it points the investigation at the pitch detector rather than the fingering.
On Sentence, with the score tempo of 200 bpm at offset 0:
map anchor 1 notated onsets to nearest real attack: median 88 ms
SAME map pointed 11 s off the mark: median 83 ms
-> MAP DOES NOT BEAT ITS CONTROL
map anchor 2 notated end 153.5 s vs audio 177.5 s (13.5% drift)
MAP SUSPECT
Pointing the map eleven seconds away from the music aligns slightly better than pointing it at the music. A map that cannot beat its own wrong time control does not locate notes, so the per note ratios below carry no information. The wrong time ratio control agrees: median 1.31 against 1.122 for the flagged notes.
The probe prints MAP SUSPECT and withholds the verdict. That is the gate doing its job.
The existing 04_TABLES/octave_test.json shows offenders at median 2.17 against a
decoy control at 1.77, which is very likely the same problem: with a broken time map,
offenders and decoys measure the same thing.
A validated beat map for these five songs, real offset and real tempo anchored at two
musical events, rather than the score tempo at offset zero. octave_probe.py takes
--offset and --bpm, so the re run costs minutes once those exist.
beatmap_fit.py derives offset and tempo per song by cross correlating the
notated onset train against the stem's onset strength envelope. It then refuses the fit
unless the fitted map covers more real attacks than two controls: the same map shifted
eleven seconds, and random times of the same count.
The first version invented a rule and a positive control caught it. Feeding the fitter a synthetic recording built from a known map, it recovered that map to 0.1 bpm and 3 ms, and the invented rule rejected it anyway. A second attempt used the lag z score to SELECT the tempo, which preferred a compressed train and chose 246 bpm over the true 172. Selection now uses the correlation peak, which recovers the truth, and the z score is computed afterwards purely as the acceptance statistic.
Three trials, each with a positive control (the tab that made the audio) and a negative control (a different rhythm of the same density against the same audio):
lag z positive 6.29 - 7.87 negative 2.63 - 2.77 lift positive 1.163 - 1.366 negative 0.819 - 0.961
A fourth positive scored z 4.38, so the bar sits at z 4.0 and lift 1.08: under every positive observed, over every negative observed.
| Song | Score bpm | Fitted bpm | Offset | z | Lift vs random | Lift vs shifted | Notated / attacks | Verdict |
|---|---|---|---|---|---|---|---|---|
| Sentence | 200 | 191.25 | 1.161 s | 4.39 | 1.321 | 1.267 | 898 / 467 | accepted |
| 30 Degrees 3 Am | 161 | 161.20 | 13.421 s | 5.23 | 1.010 | 0.998 | 1374 / 517 | refused |
| Barrier Islands (Do We Remain) | 148 | 147.60 | 3.611 s | 2.95 | 0.949 | 0.970 | 1573 / 347 | refused |
| Convict | 184 | 178.05 | 30.174 s | 5.85 | 1.443 | 0.944 | 742 / 253 | refused |
| Confession | 174 | 158.05 | 11.424 s | 3.87 | 0.948 | 1.078 | 2702 / 891 | refused |
Look at the last column. These tabs carry 1.9 to 4.5 times as many note onsets as the guitar stem has detectable attacks. Barrier Islands notates 1573 onsets against 347 attacks. When a notation is that much denser than the recording, almost any alignment puts a fifth of its onsets near something, so the map cannot be told apart from a random one. Only Sentence, at 1.9x, is sparse enough to align.
That density is consistent with the two guitar merge already documented: two players written onto one staff produces roughly twice the onsets a single stem shows.
With the accepted map, all seven flagged notes re measured. The probe now takes its map
verdict from beatmap_fit.py rather than carrying a second threshold of its own.
| Bar | Time | Position | E written | E octave below | Ratio | Reading |
|---|---|---|---|---|---|---|
| 51 | 58.34 s | str5 fret24 | 13.249 | 24.126 | 0.549 | octave below, weak |
| 73 | 83.51 s | str5 fret24 | 135.757 | 105.778 | 1.283 | ambiguous |
| 90 | 104.06 s | str5 fret24 | 8.981 | 20.826 | 0.431 | octave below |
| 92 | 106.10 s | str5 fret24 | 127.707 | 123.135 | 1.037 | ambiguous |
| 108 | 125.08 s | str5 fret24 | 20.741 | 7.316 | 2.835 | written pitch |
| 110 | 127.67 s | str5 fret24 | 52.557 | 87.766 | 0.599 | octave below, weak |
| 111 | 128.14 s | str5 fret24 | 51.282 | 91.059 | 0.563 | octave below, weak |
flagged notes median ratio 0.599 count-matched other notes median ratio 7.083 same notes, wrong time median ratio 1.066
Ordinary notes in this file put roughly seven times more energy at the written pitch than an octave below. The flagged notes invert that, at 0.599. Pointed at the wrong time the same seven notes read 1.066, so the effect is time locked rather than a property of the spectrum at large. On the earlier broken map the flagged median was 1.122 against a wrong time control of 1.31, which is why the probe withheld the verdict then.
Four of the seven point to a note an octave below, two are ambiguous, one matches the written pitch. Since MIDI 88 cannot be played anywhere on a 22 fret guitar, the one reading at 2.835 cannot be a guitar fundamental at that pitch either; the likelier explanations are another instrument bleeding into the stem, or a partial of a different note. The majority evidence supports the harmonic hypothesis on this song, and one song is not five.
The Homebrew cask ships a pkg installer, so it prompts for a password that I will not enter on your behalf. Sonic Visualiser installed cleanly because its cask is a plain app bundle. Run this yourself and doctor.py goes to zero hard failures:
brew install --cask guitar-pro
Optional, if the auxiliary conversion path is wanted: brew install --cask musescore. Keeping it away from the authoritative editing path stays correct, since conversion can drop Guitar Pro specific detail.