Sheet music to a performance
An agent recipe: take a notation file, validate it, fix problems in the score model, and render a performance. Run with ferraus 0.1.0; JSON is trimmed. The plain-language version is the tutorial.
Printed scores (PDF or page images) go through score read first; the steps below then
apply to its MusicXML. Recognition needs the two oemer models (MIT, about 110 MB; pass -y
or run ferraus models pull omr/oemer omr/oemer-seg once), and PDFs need pdftoppm or
mutool on PATH (macOS sips reads the first page only).
0. Recognize a printed score
ferraus score read minuet.pdf -o minuet.musicxml --review --json -y
{"ok": true, "command": "score read",
"models": [{"name": "omr/oemer", "version": "checkpoints-2021-11-25", "sha256": "37512e85..."},
{"name": "omr/oemer-seg", "version": "checkpoints-2021-11-25", "sha256": "ed2e1a86..."}],
"warnings": [],
"result": {"engine": "builtin", "parts": 1, "measures": 16, "check_ok": true, "failing_measures": [],
"recognition": {"pages": [1], "rasterizer": "pdftoppm", "staff_space_px": [13.8], "issues": []},
"review": {"ok": true, "notes": 77,
"issues": [{"severity": "warning", "code": "unsupported", "message": "lyrics and text are not read"}, ...,
{"severity": "info", "code": "low_confidence", "part": "P1", "measure": 10,
"message": "confidence 0.50: duration doubled to fill the measure"}],
"low_confidence": [{"part": "P1", "measure": 10, "confidence": 0.5,
"reasons": ["duration doubled to fill the measure"]}]}}}
Decide:
check_ok: falsewithfailing_measures: those measures' durations do not add up. Look at them withscore to-jsonand fix them (step 2).review.low_confidence: measures the reader was unsure about, with the reason.duration doubled / halved / dot added to fill the measuremeans a duration was corrected so the measure adds up; check that note against the page.uncertain noteheadmeans the network was unsure; check the pitch.recognition.issues(also inwarningsasomr_issue): page-level doubts, such as a time signature that was inferred from the measures instead of read.- The
unsupportedwarnings are always present for the builtin engine: lyrics, tuplets, grace notes, ties, slurs, dynamics, articulations, repeats, and a second voice on a staff are not read. A score that relies on them needs fixing by hand (or--engine audiveris).
1. Validate
ferraus score check tune.abc --json
{"ok": true, "command": "score check",
"result": {"ok": true, "errors": 0, "warnings": 0, "parts": 1, "measures": 8, "notes": 30,
"issues": [], "failing_measures": []}}
Decide: if failing_measures is not empty, each issue names the measure, voice, and what
is wrong (for example, a measure whose durations do not add up to the time signature). Fix
those before performing; --strict turns warnings into a failing exit code.
2. Read and edit the score model
ferraus score to-json tune.abc -o tune.score.json
{"version": 1, "title": "Ode to Joy (excerpt)", "composer": "Beethoven", "divisions": 2,
"parts": [{"id": "P1", "name": "Voice 1", "program": 0,
"staves": [{"number": 1, "measures": [{"number": 1, "key": {"fifths": 2, "mode": "major"},
"time": {"beats": 4, "beat_type": 4}, "clef": {"sign": "G", "line": 2}, "tempo": [{"offset": 0, "bpm": 96.0}],
"voices": [{"number": 1, "events": [
{"duration": 2, "notes": [{"pitch": {"step": "F", "alter": 1, "octave": 4}, "midi": 66}], "dynamic": "mf"}, ...]}]}]}]}]}
duration is in divisions per quarter note (here 2, so 2 = a quarter note). Edit pitches,
durations, dynamics, or the tempo, then write it back:
ferraus score from-json tune.score.json -o tune.fixed.musicxml
For a few targeted corrections, an edit list is shorter than the whole model:
ferraus score read tune.musicxml --fix edits.json -o tune.fixed.musicxml --review
where edits.json is [{"measure": 4, "voice": 1, "note": 1, "set": {"pitch": "F#4"}}].
3. Perform
ferraus score perform tune.abc --instrument piano -o tune.wav --json
{"ok": true, "command": "score perform",
"outputs": [{"path": "tune.musicxml", "role": "musicxml"}, {"path": "tune.mid", "role": "midi"},
{"path": "tune.wav", "role": "audio", "duration_s": 22.0}],
"result": {"engine": "none (abc input)",
"performance": {"duration_s": 20.0, "notes": 30, "measures_played": 8, "order": [1, 2, 3, 4, 5, 6, 7, 8], "tempo_bpm": 96.0, "tracks": 1},
"render": {"instrument": "builtin:piano", ...}}}
Checks: performance.order is the measure sequence after unrolling repeats and endings;
compare it with the form you expect. performance.tempo_bpm is the tempo used (--tempo
overrides the score). render.instrument shows what played it.
Variations: --instrument-map "P1=piano,P2=strings" per part, --humanize 30% --seed 7 for
a less mechanical but reproducible take, --swing 50%, --click on for a metronome track,
ferraus score play for the MIDI alone.