Tever
EN

Tutorial · DIY Lab

MIDI vs MusicXML in Python: what survives and what doesn't

An experiment with music21 and pretty_midi: the same phrase with fingering, a slur, and a dynamic marking, exported to both MIDI and MusicXML. What survives in each format, with real figures.

Desk with two Python code windows side by side: on the left an editor with the music21 script and a score view with a slur, fingering, and mp dynamic marking highlighted in orange; on the right a hex view of a tiny binary MIDI file, on a technical lab bench

MIDI is the format anyone who has ever played an electronic keyboard knows, even if only by name. MusicXML is much less known, and yet one question comes up as soon as you need more than “just make it sound”: what if what you need is the score, not the sound? Fingering, slurs, dynamic markings —everything a performer reads and a computer may need to keep—. This tutorial doesn’t explain each format again —that’s what What MIDI is? and What MusicXML is? are for— it demonstrates it instead: it builds the same musical phrase once, exports it to both formats, and checks, with real data, what survives in each one.

The experiment

The idea is simple: write a short phrase with music21 —fingering on each note, a slur, and a dynamic marking included— and export it twice, once to MusicXML and once to MIDI. Then read each file back and see what’s left.

from music21 import stream, note, meter, tempo, dynamics, articulations, spanner, clef

s = stream.Part()
s.append(clef.TrebleClef())
s.append(meter.TimeSignature('4/4'))
s.append(tempo.MetronomeMark(number=100))

n1 = note.Note('C5', quarterLength=1)
n1.articulations.append(articulations.Fingering(1))
n2 = note.Note('E5', quarterLength=1)
n2.articulations.append(articulations.Fingering(3))
n3 = note.Note('G5', quarterLength=0.5)
n3.articulations.append(articulations.Fingering(5))
n4 = note.Note('E5', quarterLength=0.5)
n4.articulations.append(articulations.Fingering(3))

n1.dynamic = dynamics.Dynamic('mp')
slur = spanner.Slur(n3, n4)

s.append(n1); s.append(n2); s.append(n3); s.append(n4)
s.insert(0, dynamics.Dynamic('mp'))
s.insert(0, slur)

s.write('musicxml', fp='ejemplo.musicxml')
s.write('midi', fp='ejemplo.mid')

Four notes —C5, E5, G5, E5—, each with its indicated finger, the last two slurred together, and the whole phrase marked mezzo-piano. Nothing exotic: it’s exactly the kind of information a performer expects to see printed on a score.

Score of the example phrase rendered from the actual MusicXML generated by the code: four notes in treble clef with fingering 1, 3, 5, 3, a slur between the last two, and the mp dynamic marking
The example phrase as it ends up written in the MusicXML generated by the code above —fingering, slur, and dynamic included—, rendered here so you can see it the way a performer would.

Reading the MIDI back

With pretty_midi you can open ejemplo.mid and list the notes exactly as they were encoded:

pitch=72 start=0.00 end=0.60 velocity=81   (C5)
pitch=76 start=0.60 end=1.20 velocity=81   (E5)
pitch=79 start=1.20 end=1.50 velocity=81   (G5)
pitch=76 start=1.50 end=1.80 velocity=81   (E5)

Four note events, each with a pitch, a start and end instant in seconds, and a velocity —81 for all four, the result of translating the phrase’s single mp dynamic into a fixed MIDI value—. This is exactly what you’d expect from MIDI: instructions for when something sounds, and how hard. No trace of the fingering, the slur, or the fact that an “mp” marking was ever there: that information has nowhere to live in the protocol. It plays just as well, but it was lost for good the moment it was saved.

Reading the MusicXML back

With music21.converter.parse on ejemplo.musicxml, the same phrase returns much more:

C5: fingering=[1]
E5: fingering=[3]
G5: fingering=[5]
E5: fingering=[3]
Slurs explicitly encoded: 2
Dynamic markings: ['mp']

All four fingerings are intact, the slur is still encoded as such —not as a coincidence of timing, but as an explicit <slur> element— and the “mp” marking is still “mp”, not a velocity number. Everything MIDI had nowhere to store, MusicXML keeps, because it describes the score, not the performance.

A fragment of the generated XML shows why: each of those elements is a readable tag, not a protocol number.

<dynamics default-x="-36" default-y="-80">
  <mp />
</dynamics>
...
<fingering alternate="no" substitution="no">1</fingering>
...
<slur number="1" type="start" />

What the bytes say

The difference also shows up, literally, in the size and shape of each file. ejemplo.mid weighs 99 bytes; ejemplo.musicxml, 3560 bytes —36 times more for the same four-note phrase—. That’s not inefficiency: it’s the difference between storing only playback instructions and storing a full description of the written notation.

The first bytes of the MIDI file, in hexadecimal, are pure binary:

4d54 6864 0000 0006 0001 0002 2760 4d54
726b 0000 0014 00ff 5103 0927 c000 ff58

Without a parser in between, this says nothing. MusicXML, by contrast, is XML text that anyone can open in an editor and read —the fragments above (<dynamics>, <fingering>, <slur>) make sense with no context beyond the tag’s own name—.

Comparison table for the experiment

MIDI (ejemplo.mid)MusicXML (ejemplo.musicxml)
Notes (pitch, timing)✔ Kept✔ Kept
Fingering✘ Lost✔ Kept ([1], [3], [5], [3])
Slur✘ Lost (only contiguous timing)✔ Kept as explicit <slur>
Dynamic (“mp”)Approximated as velocity=81✔ Kept as text (mp)
Size99 bytes3560 bytes
Human-readable✘ Binary✔ XML text

Why it matters in practice

This isn’t a format curiosity: it decides which tool to use depending on what needs to be preserved. To play back, sequence, or drive a synthesiser, MIDI is enough, and lighter. For notation, teaching —a fingering a student needs to see printed— or archiving a score meant to be edited later, MIDI falls short by design, not by neglect: it never promised to keep that.

It’s also why an automatic music transcription system has to choose its output format carefully depending on the intended use: MIDI if the goal is for the transcription to sound, MusicXML if the goal is for someone to read it, correct it, or use it to teach. In my own doctoral research I work with piano as my corpus, and this same dilemma comes up as soon as an AMT system finishes deciding which notes sounded: the next step is which format to deliver that result in, and it’s not a neutral decision.

References

The references this article draws on, and where to read further: