I made the comparison in Battuta, my command-line MIDI editor, describe changes in a form I can follow through a piece of music. It names the note, places it at a bar and beat, and says what changed. The same tool gives an AI agent structured data with the exact values needed to make another edit.
A MIDI file holds instructions for playing notes, including their pitches, starting times, durations and velocities. Velocity describes how strongly a note is struck. In the eight-bar file I use as a fixture, the melody opens on A4 at velocity 50. Try lowering it to G4, reducing its velocity to 40, or both.
One note, two changes
What happens to the opening A4?
Choose a pitch change, a velocity change, or both. Follow the first note and read the comparison with the original.
Original
Opening note: A4 · velocity 50
First two bars of the melody. The filled note is the opening; its number is strike velocity. The next two notes stay in place.
Describe the change · 120-tick matching tolerance
no differences This version has the same notes as the original.
Exact identities only · 0-tick matching tolerance
no differences This version has the same notes as the original.
Lower pitch
Opening note: G4 · velocity 50
First two bars of the melody. The filled note is the opening; its number is strike velocity. The dashed outline keeps its original pitch visible.
Describe the change · 120-tick matching tolerance
changed bar 1 beat 1 track 1 A4 transposed to G4 The comparison pairs the two identities and describes one transposed note.
Exact identities only · 0-tick matching tolerance
added bar 1 beat 1 track 1 G4 velocity 50 duration 1435
removed bar 1 beat 1 track 1 A4 velocity 50 duration 1435 Changing pitch changes the note’s identity. This view lists a removal and an addition.
Smaller strike velocity
Opening note: A4 · velocity 40
First two bars of the melody. The filled note is the opening; its number is strike velocity. The next two notes stay in place.
Describe the change · 120-tick matching tolerance
changed bar 1 beat 1 track 1 A4 velocity 50 -> 40 Velocity is not part of the identity. Both views describe the same note changing.
Exact identities only · 0-tick matching tolerance
changed bar 1 beat 1 track 1 A4 velocity 50 -> 40 Velocity is not part of the identity. Both views describe the same note changing.
Both changes
Opening note: G4 · velocity 40
First two bars of the melody. The filled note is the opening; its number is strike velocity. The dashed outline keeps its original pitch visible.
Describe the change · 120-tick matching tolerance
changed bar 1 beat 1 track 1 A4 transposed to G4, velocity 50 -> 40 The comparison pairs the two identities and describes one transposed note.
Exact identities only · 0-tick matching tolerance
added bar 1 beat 1 track 1 G4 velocity 40 duration 1435
removed bar 1 beat 1 track 1 A4 velocity 50 duration 1435 Changing pitch changes the note’s identity. This view lists a removal and an addition.
Each recorded edit produces a separate MIDI file. The original stays available, and the comparison places the change at a specific point in the music. Listening can then address whether it suits the phrase.
Giving the reader a place to point
The early inspection output used values such as pitch 66 start 5760. Those are useful coordinates for a program. Reading them as music requires a translation: which note is pitch 66, and where does time 5760 fall?
MIDI measures musical time in ticks. A file states how many ticks make a quarter note; its time signature supplies the grouping into bars and beats. Battuta uses those statements to place each note, and displays a pitch name beside its numeric identity. With the naming convention the tool uses, pitch 66 is F#4.
I also changed the order of the human listing. It follows the music through time, bringing notes struck together onto adjacent lines, instead of finishing one track before starting the next. An agent requesting JSON still gets the file’s track order and exact identifiers. Both views refer to the same notes.
That distinction matters in a chord. The fixture contains an F#3 and an A3 starting together on track 2, at bar 7 beat 2. Both initially have velocity 38. Changing the F#3 to 60 gives this row:
changed bar 7 beat 2 track 2 F#3 velocity 38 -> 60
The pitch earns its place because the bar, beat and track would identify either note. The report gives me enough information to find the particular part of the chord that changed.
What allows the report to say “transposed”
A useful description also needs a way to connect the before and after. Battuta identifies a note using its track, channel, pitch and starting tick, with an occurrence number for otherwise identical notes. Changing the pitch therefore changes the identifier. Neither file contains a permanent label saying that its A4 and the other file’s G4 are the same note.
The comparison first matches exact identities. It then tries to pair unmatched notes on the same track whose starts fall within a stated time tolerance. That second pass lets it describe a note as moved or transposed. The default tolerance is a sixteenth note, which is 120 ticks in this fixture. Each comparison reports the value it used.
The pairing is an inference about the relationship between the files. It does not recover the actual editing history. I made it possible to decline that inference with --tolerance 0. In the example above, select a pitch change and switch to “Exact identities only”: the report lists a removal and an addition. A velocity change alone keeps the note’s identity, so both views describe one changed note.
The exact view keeps different identities separate; the default view proposes a connection that makes the change easier to discuss. The reader can choose the grouping without changing either file. Durations in these reports remain in ticks.
Keeping the explanation close to the edit
These descriptions are produced by the command itself. They do not depend on an agent composing a plausible account afterward. The Rust library returns structured facts and comparison results; the command-line interface chooses their wording. That lets the human view use musical positions while an agent retains the values it needs to address an edit precisely.
At bar 7 beat 2, the report can now put the F#3 in front of me. I can keep the stronger strike, restore its previous velocity, or try another value while leaving the A3 beside it alone. The next decision has a place in the music.
Examples and source code
The examples use Battuta’s eight-bar MIDI fixture. The four interactive cases and both matching modes were reproduced on 10 September 2026 with a fresh build of source revision 7b5a0a8. The original file was unchanged. The chord example was separately checked with the repository’s local binary. These are execution examples, with no listening preference or reader-study result attached.
Recorded results include the note coordinates and comparison output. The reproduction files include the script, MIDI inputs and instructions. The script takes an explicit Battuta binary, soundfont and a new output directory.
The comparison tests give the requests and expected rows, including the chord example. The design of bounded matching explains the tolerance. The readable-output implementation records the change from tick coordinates to musical positions.