Skip to main content
Glama
David7ce
by David7ce

audio2score-mcp

Turn a recorded audio file into an editable music score: audio → MIDI → MusicXML.

MusicXML is the target because it's the "SVG of music notation" — an open, text-based format any notation app (MuseScore, Sibelius, Guitar Pro, Finale, Dorico) can open, edit, and re-export without owning the pipeline that produced it. This project only produces the .mid and .musicxml files; opening, editing, and exporting to anything else (PDF, audio, tab) happens in whichever notation app you choose, by hand — see "Formats this connects to" below for why this project deliberately doesn't wrap that part.

Two ways to run it: as plain CLI scripts, or as an MCP server exposing the same steps as tools Claude can call.

What's here

  • transcribe.py — audio file → MIDI, via Spotify's basic-pitch

  • to_score.py — MIDI file → MusicXML, via music21

  • score_to_notes.py — a score file (MIDI, MusicXML, or anything music21 can read) → JSON note array in daw-mcp's batch_set_notes format

  • mcp_server.py — MCP server wrapping all three as tools (transcribe_audio, midi_to_score, score_to_notes)

Each step is a separate, real artifact on disk — not a hidden intermediate. The pause between MIDI and notation is deliberate: automatic transcription is lossy, so the raw MIDI is worth a look (or a manual fix) before it becomes a score.

score_to_notes.py is intentionally format-agnostic, not MIDI-specific - music21.converter.parse() handles MIDI and MusicXML identically, so feeding it a .mid from transcribe.py or an .mxl from an external OMR tool (see "Formats this connects to") takes the same code path. There is no separate MusicXML→MIDI or MusicXML→PDF tool in this project - once a .musicxml exists, any notation app already opens and exports it, so building that here would just duplicate what's already installed.

Related MCP server: whisper-mcp

Where files go

Recommended: drop input files in workspace/ (repo-local, gitignored - see .gitignore - nothing placed here ever gets committed, inputs included). There's no hard requirement though - any path works. Every output lands next to its input file, same base name, different extension:

workspace/song.mp3          <- you put this here (any format basic-pitch/librosa reads: mp3, wav, ogg, flac...)
workspace/song.mid          <- transcribe_audio writes this
workspace/song.musicxml     <- midi_to_score writes this (open in MuseScore/Guitar Pro/Sibelius/Finale/Dorico)
workspace/song.notes.json   <- score_to_notes writes this (feed into daw-mcp's batch_set_notes)

To view: open the .mid or .musicxml directly in whatever notation app you have - nothing here launches one for you. If MuseScore calls the .musicxml "corrupted," see the polyphony caveat below before assuming the file is broken.

Setup

Requires Python 3.11 specifically — basic-pitch pulls in TensorFlow 2.15, whose wheels stop at cp311; 3.12 and 3.13 will fail to resolve. The resulting venv is ~2GB (full TensorFlow, not a lighter backend).

uv venv --python 3.11 venv
uv pip install -r requirements.txt --python venv/Scripts/python.exe

Dependencies are pinned exactly (basic-pitch==0.4.0, music21==10.5.0, setuptools==65.5.0, mcp==2.0.0) — this project has no automated test suite, so a fresh environment matching exactly what was verified is the substitute. setuptools specifically is pinned because newer versions break a transitive resampy import that basic-pitch needs.

Usage: CLI

venv/Scripts/python.exe transcribe.py "C:\path\to\song.mp3"
# -> C:\path\to\song.mid

venv/Scripts/python.exe to_score.py "C:\path\to\song.mid"
# -> C:\path\to\song.musicxml

venv/Scripts/python.exe score_to_notes.py "C:\path\to\song.mid"
# -> C:\path\to\song.notes.json  (daw-mcp's batch_set_notes format - also
#    takes a .musicxml/.mxl directly, e.g. from OMR, no separate step needed)

Output always lands next to the input, same base filename, different extension. All three scripts refuse to overwrite an existing output file — delete or move it first if you want to re-run. Errors (missing input, a library failure) print a clear message to stderr and exit non-zero; nothing fails silently.

Run only the tool(s) your actual goal needs - don't chain all three by default. Each tool produces exactly one file; running more than you need just adds files nobody asked for.

Goal

Run

Files produced

View/edit a recording as notation

transcribe_audiomidi_to_score

.mid, .musicxml

Get a recording's notes into daw-mcp

transcribe_audioscore_to_notes

.mid, .notes.json (skip midi_to_score - not needed for this goal)

Get scanned/typeset sheet music into daw-mcp

Audiveris (external, see "Formats this connects to") → score_to_notes on the .mxl

.mxl, .notes.json (no MIDI step at all)

View/edit scanned sheet music as notation

Audiveris only

.mxl - already MusicXML, open it directly, no tool here needed

.mid in the first two rows isn't really "output" so much as an unavoidable checkpoint - basic-pitch can only emit MIDI, and it's worth a look before trusting what comes after it (see "Known issue" below on why).

Worked example

A real run, not a hypothetical one. Input: a synthetic mono WAV, a C major arpeggio (C4-E4-G4-C5, quarter notes with a short decaying envelope so onsets are clean) - "real audio" in the sense this project cares about (an actual waveform on disk, not hand-typed MIDI), just synthesized instead of recorded, so the transcript is reproducible without a copyrighted file lying around in a public repo.

$ venv/Scripts/python.exe transcribe.py c_major_arpeggio.wav
WARNING:root:Coremltools is not installed. ...
WARNING:root:tflite-runtime is not installed. ...
WARNING:root:onnxruntime is not installed. ...
Wrote c_major_arpeggio.mid

$ venv/Scripts/python.exe to_score.py c_major_arpeggio.mid
Wrote c_major_arpeggio.musicxml

$ venv/Scripts/python.exe score_to_notes.py c_major_arpeggio.mid
Wrote c_major_arpeggio.notes.json

The three WARNING:root lines are basic-pitch noting that optional backends (CoreML, TFLite, ONNX) aren't installed - harmless, TensorFlow is the backend actually used, and this is exactly what transcribe.py v1.1.1 now correctly hides without corrupting mcp_server.py's stdout stream (see CHANGELOG.md) - it only lands on the terminal, not the MCP protocol channel.

c_major_arpeggio.notes.json, the daw-mcp-ready output:

[[0.0, 60, 83, 1.0], [1.25, 64, 80, 1.0], [2.3333, 67, 80, 1.0], [3.5, 72, 78, 0.5], [4.0, 72, 78, 0.5]]

Four notes went in (C4, E4, G4, C5); basic-pitch correctly detected pitch and velocity for all four (60/64/67/72, matching the arpeggio exactly) but split the last note (C5) into two consecutive entries instead of one - the decaying envelope's tail apparently read as a second onset. This is the automatic-transcription lossiness the "What's here" section above warns about, caught in the wild on the very first note that had a naturalistic (non-flat) volume shape: check the .mid before trusting the .musicxml/.notes.json blindly, especially around sustained or decaying notes.

c_major_arpeggio.musicxml opens cleanly in any notation app (verified well-formed: correct MusicXML 4.0 DOCTYPE, <step>/<octave> pitches for C4/E4/G4/G4/C5/C5/C5 - the split C5 shows up as tied notes across a measure boundary, which is standard MusicXML for a note that doesn't fit in one measure, not a second bug).

Usage: MCP server

Registered in Claude Code's config as audio2score — restart Claude Code after a fresh install for it to appear (MCP servers load at startup).

Three tools, mirroring the three scripts exactly:

  • transcribe_audio(audio_path) → returns the .mid path

  • midi_to_score(midi_path) → returns the .musicxml path

  • score_to_notes(score_path) → returns the .notes.json path (daw-mcp's batch_set_notes note-array format; accepts MIDI or MusicXML)

Same behavior as the CLI underneath (same overwrite guard, same errors) — the MCP server is a thin wrapper, not a different implementation.

One thing to know if a call seems to hang: if a transcribe_audio call appears to time out or gets cancelled, the transcription may still be running in the background and will finish writing the .mid file regardless. A retry will then hit the overwrite guard ("already exists") even though the first call looked like it never succeeded. This isn't a bug — check whether the .mid already exists before retrying.

To register the server yourself elsewhere, add this to your MCP config (mcpServers), using absolute paths for both fields — the client launches stdio servers without a defined working directory, so relative paths won't resolve:

"audio2score": {
  "type": "stdio",
  "command": "<absolute path to>\\venv\\Scripts\\python.exe",
  "args": ["<absolute path to>\\mcp_server.py"],
  "env": {}
}

What this doesn't do

  • No score/MIDI → audio, PDF, or tab output, no MusicXML → MIDI conversion either — see "Formats this connects to" below for why and what to use instead

  • No stem separation or multi-instrument splitting

  • No automated test suite by design — verification is always a real run against real audio

Formats this connects to

Once a .musicxml exists, this project deliberately stops - every notation app already opens MusicXML natively and exports whatever's needed (PDF, audio, tab, MIDI) from its own menu. Building automated wrappers around those exports was tried and mostly reverted (see CHANGELOG.md v1.2.0 through v2.0.0 for the full back-and-forth) - the one direction still worth automating turned out to be none of them, once score_to_notes.py was confirmed to accept MusicXML directly with no separate conversion step.

Direction

Use

Notes

MusicXML → PDF, audio, tab, MIDI

MuseScore Studio or Guitar Pro, opened normally

Not wrapped here on purpose - see above. (MuseScore's CLI converter mode, -j job.json, genuinely can automate PDF export reliably if you want it for your own scripting - just isn't built into this project)

PDF (scanned/typeset sheet music) → MusicXML

Audiveris (C:\Program Files\Audiveris\Audiveris.exe): Audiveris.exe -batch -export -output "<folder>" "<input>.pdf"

-batch genuinely skips its GUI. Tested on 3 real PDFs: 2 clean one-page scores exported correctly (one with a minor time-signature warning); a 24-page guitar tab book hit real internal Audiveris crashes (NullPointerException/IndexOutOfBoundsException in its rhythm analysis) on several pages - OMR reliability drops fast on complex, multi-page, or tab-heavy input

PDF → daw-mcp's note format

Audiveris (above) → this project's score_to_notes.py, directly on the .mxl

Two steps, both real and tested end-to-end on actual sheet music - no MIDI conversion needed in between

Treat OMR output with at least as much suspicion as basic-pitch's audio transcription - check the intermediate .musicxml before trusting it, and don't expect Audiveris to succeed on every PDF (see the tab-book failure above).

Known issue: MuseScore can reject a transcribed .musicxml as "corrupted"

Heavily polyphonic transcriptions can produce a .musicxml that MuseScore Studio refuses to open, calling it "corrupted." Root cause: music21's own MusicXML writer omits the <voice> tag on some <note> elements when a piece needs many simultaneous voices (5+) - verified on a real 45-second recording that transcribed into dense, often-overlapping notes (a side effect of basic-pitch picking up harmonics/artifacts on real audio, not a clean single melodic line). Confirmed this is music21's writer, not this project's code: to_score.py is a two-line parse() + write() call with no note/voice logic of its own, and explicitly calling score.makeNotation() before writing doesn't fix it either. Re-parsing the same file with music21 itself only warns (Cannot put in an element with a missing voice tag) and recovers by defaulting those notes to voice 1 - MuseScore's importer is simply stricter and rejects outright instead of tolerating it. Workaround: click "Open anyway" - it loads fine, just with those specific notes in voice 1 instead of their originally-detected voice, a minor layout quirk, not lost data. Not seen on clean, low-polyphony input (a hand-authored melody MIDI transcribed and re-verified with zero voice-tag issues) - this is specific to messy, dense, real-audio-transcription output.

F
license - not found
Not graded
quality - not tested
B
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Servers

  • A
    license
    A
    quality
    C
    maintenance
    MCP server that provides a transcribe_audio tool to convert voice messages from channels into text using OpenAI Whisper, enabling Claude Code to process audio attachments.
    1
    MIT
  • -
    license
    Not graded
    quality
    Not graded
    maintenance
    MCP server for vibe coding with music, enabling format conversion (LilyPond, MusicXML, MIDI, ABC, etc.), audio-to-sheet transcription, and transposition with robust fallback outputs.
    1

View all related MCP servers

Related MCP Connectors

  • MCP server for Producer/Riffusion AI music generation

  • Generate AI music via the Lacuna Music API from MCP clients like Claude Desktop & Code.

  • MCP server for Suno AI music generation, lyrics, and covers

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/David7ce/audio2score-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server