Imported from foxtrotcharlie/music-skills (
plugins/music/skills/sheet-music-pdf-to-musescore/SKILL.md). Install upstream withnpx skills add foxtrotcharlie/music-skills --skill sheet-music-pdf-to-musescore. Copyright stays with the author.
Sheet music PDF → MuseScore
Optical music recognition (OMR) with Audiveris, then conversion to MuseScore.
Two principles govern everything here:
- Input quality dominates output quality. A bad scan costs far more in proofing than a rescan does. Assess before converting.
- OMR output is a draft, always. Your job ends with an honest report and a proofing checklist, never "here's your score".
When NOT to use
Audiveris targets printed Common Western Music Notation. Stop and say so if the input is handwritten manuscript, shape notes, or graphic/contemporary notation. Tablature is detected only behind an off-by-default switch and is never transcribed. Rasterise a page (step 1) to check before investing in a run.
Step 0 — Preflight
bash ${CLAUDE_SKILL_DIR}/scripts/preflight.sh
Checks Audiveris, OCR language data, MuseScore, poppler, python3 and pdfplumber,
and prints the AUDIVERIS, MSCORE and MUSIC_SKILLS_PYTHON paths to use.
Non-zero exit means stop — resolve the gaps first, using
references/install-macos.md.
Two traps it exists to catch: missing OCR language data fails silently (no lyrics, tempo text, or chord names, and no error), and Audiveris has shipped no language data in any installer since 5.5.
Step 1 — Assess the PDF
pdfinfo score.pdf
pdfimages -list score.pdf
pdftoppm -png -r 150 -f 1 -l 1 score.pdf /tmp/page # then Read the PNG
Read for: born-digital vs scanned (no images, or one small logo, means vector notation — the best case; one full-page image per page means a scan); resolution against the ~20px-between-staff-lines target; page count, since each page becomes one sheet; and skew or clipping, which is worth rescanning rather than fighting.
Rasterising a page is also the fastest way to see instrumentation, staff count,
polyphony density, and whether there are lyrics — use it to set expectations and
flag out-of-scope input early. Full guidance, DPI bounds, and rescue techniques
for unrepeatable scans: references/audiveris-cli.md.
Step 2 — Sample first, then commit
On anything longer than a few pages, never run the whole score first. Convert pages that show the hard parts, not the title page:
"$AUDIVERIS" -batch -transcribe -export -save \
-constant org.audiveris.omr.sheet.BookManager.useCompression=false \
-sheets 2-4 -output ~/Desktop/omr-out -- ~/Desktop/score.pdf
-save writes the .omr project file — always include it. Without it, any
later GUI correction means re-running the whole pipeline from scratch. The
useCompression=false constant emits readable .xml instead of zipped .mxl.
If the page shows guitar chord diagrams, add the two switches in
references/audiveris-cli.md — the fretboard X/O grids otherwise generate
phantom staccatos and dynamics. Tested: 5 of 6 phantoms removed, no real data
lost. Only do this when step 1 showed the score has no genuine articulations.
Flags, constants, pipeline steps, and -sheets range gotchas:
references/audiveris-cli.md.
Runs take minutes per sheet. A foreground Bash call is capped at 15 minutes,
so run the full pass in the background rather than reaching for a longer
timeout — a killed run wastes all of it. Then read the console output:
Audiveris logs per-sheet warnings that point straight at the damage. If GRID
failed or no staves were found, go back to step 1 — that's an input problem, not
something to fix downstream.
Step 3 — Inspect and repair the MusicXML
First, repair the OCR'd text. Every Audiveris export needs this, so do it before anything else:
python3 ${CLAUDE_SKILL_DIR}/scripts/clean_omr_text.py \
~/Desktop/omr-out/score.xml ~/Desktop/omr-out/clean.xml --title "Song Title"
It rebuilds the title block and strips text artefacts. Audiveris emits duplicate
<creator>/<rights> elements that MuseScore stacks at one anchor, and credits
with no type, which MuseScore then places using raw OMR pixel coordinates — so
the header and footer overlap into an unreadable pile. Guitar fretboard grids add
dozens of junk credits (X 0 O, >O<, 3fr), chord symbols get misfiled as
page credits, and any purchase or order line in the source PDF is carried
through. On a real 6-page chart: 80 credits reduced to 4, and 4 misread
chord names recovered as real harmony.
Junk is stripped from in-bar text directions as well as from the credits,
because a fretboard fragment left inside a bar renders as bold text across the
staff. Scanned input needs this most: a born-digital grid OCRs cleanly as
X 0 O or 3fr, but a photographed one comes back as letter/digit hybrids like
C0 0, Co 0 and 0En;70, which no exact-match character class catches. Those
are identified by being short, containing a digit, and being mostly grid
glyphs — all three conditions, so that poco (no digit), 8va (too few grid
glyphs) and Tacet (neither) all survive. Widening this filter is the dangerous
direction: dropping real expression text is silent loss, and worse than leaving
junk visible.
Note that <work-title> and <creator> populate metadata only and render
nothing — visible text needs typed credits with positions. The script is
idempotent, so re-running is safe.
python3 ${CLAUDE_SKILL_DIR}/scripts/inspect_musicxml.py ~/Desktop/omr-out/clean.xml
Reports parts, per-part measure counts (a part out of step with its siblings
is the single most diagnostic signal of dropped or invented measures),
signatures including mid-score changes, divisions, transpositions, and which
features drive the proofing checklist. Reads .xml, .musicxml, and .mxl.
It also checks that every bar actually fills its time signature — the one
defect nothing else here catches. Audiveris exports a structurally valid file
whose bars do not add up; MuseScore then opens it with a "File is corrupted"
dialog, or crashes trying to export it, while the zip, the note count and every
other red flag say the file is fine. Misread tuplets are the usual cause, so a
triplet-heavy chart is where to expect it: a photographed Tom Waits chart came
back with 13 over-full and 8 short bars out of 46, and nothing reported a
problem until this check existed. An over-full bar is unambiguously corrupt; a
short one is softer, so pickups and final bars are excluded. Bar durations are
read by walking <note>/<backup>/<forward> as MusicXML defines them —
summing note durations instead double-counts every multi-staff measure, because
both staves are one stream separated by a <backup>.
Do not hand-grep for <key>/<time> — those elements are often pretty-printed
across lines, so a line-oriented grep silently reports nothing and invents a
missing-signature bug.
If the PDF is born-digital, recover the chord symbols from its text layer instead of trusting OCR:
python3 ${CLAUDE_SKILL_DIR}/scripts/chords_from_pdf.py score.pdf --compare ~/Desktop/omr-out/score.xml
OMR rasterises the page and throws away perfect text. Tesseract then reads
Fsus2 as FsusZ, Audiveris rejects the unparseable name and demotes it to a
plain text annotation, and the chord vanishes from the harmony data. On a real
chart this cost 51 of 67 chord symbols. The script prints the exact
ground-truth list per page plus what OMR lost, so chord symbols get retyped from
a verified list rather than squinting at the PDF. It exits 1 on a scanned PDF
(no text layer), where OCR is the only route.
Then place every chord symbol in the right bar and on the right beat:
python3 ${CLAUDE_SKILL_DIR}/scripts/place_chords_from_pdf.py \
score.pdf ~/Desktop/omr-out/clean.xml ~/Desktop/omr-out/chorded.xml
A born-digital PDF is not just text with coordinates — it is the whole engraving.
Staff lines and barlines are vector strokes, noteheads are glyphs at known
positions. So the bar a chord belongs to is read, not estimated: staff lines
group into staves, staves into systems, and each system's barlines give its real
bar boundaries. On a real 6-page chart that reproduced the score's own layout
exactly — 23 systems, 70 bars — and put all 67 chord symbols in the correct
bars, against 16 from OMR. Use --dry-run to review the per-bar table first.
The beat comes from matching each bar's note columns against that measure's onsets, counting onsets across every part: a chord sits above whichever note sounds under it, which is often a piano note while the voice rests. Counting only the top part's onsets makes the two disagree, and where the counts coincide anyway the chord is mapped to the wrong column with full confidence. Where the counts cannot be reconciled the beat falls back to proportional position across the real bar, snapped to a real onset — the script reports how many took each route.
Needs pdfplumber; preflight reports the interpreter that has it, and the
script re-runs itself under that interpreter if it can find one. Without it,
placement falls back to pdftotext and equal-width bars, which is wrong in any
bar holding more than one chord — bars are not equal width, and a lyric
overhanging the final barline inflates the extent. It says which route it took.
Two further traps it handles: harmony must be cleared from every part, or a
leftover chord in a lower part renders a second time above its own staff; and a
wide symbol like Fsus2 is left-aligned to its note and so can start a fraction
before the barline of the bar it belongs to.
--lyrics score.xml does the same for sung text, splitting problems into
misreads (absent from the text layer) and wrong case (right letters,
wrong capitalisation), each with the correct spelling. On the same chart that
found 4 errors in 158 syllables — 'HOW.' for "now.", 'WOT].' for "won.",
'feted' for "fered", and 'neVer' for "never" — far cheaper than proofreading
by eye. Case matters on its own: comparing case-insensitively hides neVer
completely, and a lyric syllable can hide inside an all-caps title
(FALL inside FALLING), so titles are excluded from the case comparison.
Compare the output against the printed score. Know where to stop: signatures, structural metadata, part names, and transpositions are fair game to patch in XML. Wholesale rewriting of note content is not — hand-editing pitches produces a subtly worse file. Structurally bad recognition belongs in the Audiveris GUI, not in XML surgery.
Step 4 — MusicXML → .mscz
bash ${CLAUDE_SKILL_DIR}/scripts/to_mscz.sh ~/Desktop/omr-out/score.xml ~/Desktop/score.mscz
Never judge this step by the exit code. MuseScore writes a complete, valid
.mscz and then intermittently aborts with exit 134. This is a known
crash-on-quit bug; the fix is merged upstream and slated for 4.7.5, but 4.7.4
is the latest release, so there is no version to upgrade to yet. Validating the
artifact — the zip reads, contains a .mscx, holds notes — is the only
mitigation, and the wrapper does it.
That validation is structural only. It says nothing about whether the bars
add up, so the wrapper will report ok on a file MuseScore then refuses as
corrupt — and a corrupt score is also what makes mscore crash without writing
anything when you try to render it back for proofing. Run
inspect_musicxml.py (step 3) and clear its rhythm flags before trusting a
green line here.
Do not reach for QT_QPA_PLATFORM=offscreen to suppress it — macOS ships no
offscreen Qt plugin, so MuseScore fails to start and writes nothing at all.
Converter mode starts a real Qt application rather than a true headless process,
so expect a brief app launch. .mscz is a convenience anyway: MuseScore opens
MusicXML natively. Two import flags are worth knowing for OMR input —
--musicxml-infer-text-type and --musicxml-use-default-font; "import layout"
and "import system and page breaks" remain GUI-only.
Step 5 — Report honestly
Report what you observed — log warnings, measure-count mismatches, what the sample pages looked like — and calibrate expectations to it. A 150 DPI scan of a dense piano score needs real proofing; say so rather than implying it's ready to play.
Then hand over the proofing checklist from
references/limitations-and-proofing.md, trimmed to what this score actually
contains, plus the paths to the MusicXML, the .omr, and the .mscz. Keep the
.omr — it's what makes iterative correction cheap.
When recognition is structurally damaged, send the user to the Audiveris GUI
rather than MuseScore: corrections there propagate through all downstream
interpretation. Triage table and documented limitations:
references/limitations-and-proofing.md.