I was testing an instrument change in Battuta, a MIDI editor I’m building. I asked it to make the second note a trumpet. The command succeeded, and inspecting the file showed the change. But when I played it back, that note was still a violin.
I use these reports to work on music with an AI agent. If the report treats an instrument change as settled, the next edit can start from something I never heard. Compare the two clips below: the first note is identical in both; the difference is in the second.
The second note should be a trumpet.
Each clip plays two notes, followed by their fading sound.
A / The second note stays a violin
What the file says
Same time, on separate tracks.
Neither instruction is placed before the other.
What this player does
- Starts the note as a violin
- Changes the instrument to trumpet
The note has already started.
It stays a violin.
B / The second note becomes a trumpet
What the file says
Same time, in a stated sequence.
The arrow means order, not a delay.
What this player does
- Changes the instrument to trumpet
- Starts the note as a trumpet
The change reaches the note.
It plays as a trumpet.
Measurements, MIDI files and reproduction
A’s audio is identical to the unedited input, sample for sample. The difference-signal peak between A and B is −17.1 dBFS, measured as the largest sample in A minus B. This is not a loudness difference or a comparison of players.
The check also handles edits that move or add a note against an instrument change on another track. Two tracks stating the same instrument do not conflict with each other; differing instrument values do. This compares channel state, not all possible audio behaviour.
Reconstructed on 6 September 2026 from Battuta’s issue #26 regression fixture. These are new renders, not the original debugging audio.
Two notes, C4 then E4, each 1 second, velocity 64. Channel 0; 960 ticks per quarter note; 120 BPM. Track and channel numbers are zero-based. A puts program 56 (trumpet) on track 2 at tick 1920; B puts it on track 1 before E4, at the same tick.
The fixture also has a carried-in cross-track program change at tick 0. It is preserved in both versions. On this renderer the first note uses the initial piano sound; the diagram isolates the edit at tick 1920.
FluidSynth 2.6.0, GeneralUser GS, 44.1 kHz stereo, 16-bit PCM. Shared synth gain 1.0; reverb and chorus off. No normalisation or time shift. Full release tails retained; the shorter render is padded with silence. Each clip is 4.46 seconds.
Battuta 0.1.2 refuses A unless this run explicitly allows t2:c0:s1920. B succeeds without that exception. Reproduction checks both behaviours and verifies that A still matches the unedited audio.
Input MIDI Unedited audio Edit A Edit B Measurements Refusal Reproduction script
The script requires Python with mido, FluidSynth, Battuta 0.1.2, and a soundfont supplied with --soundfont. Run it with --out pointing to a new directory. The measurement file records the soundfont checksum; the soundfont itself is not included.
The player started the note before changing the instrument
A MIDI file contains instructions for making sound, rather than a recording. “Start this note” and “change the instrument” are separate instructions. Their order matters: if the note starts first, it starts with the instrument already selected.
In this case, both instructions had the same timestamp. They were stored on different tracks, each with its own sequence of events. The file said when to execute them, but supplied no order between them across those tracks.
The fix puts the instrument change before the note in the same track. Both still happen at the same timestamp; the file now states which comes first. The MIDI format’s track structure makes that sequence expressible.
Battuta’s inspection had read the new instrument and treated it as the one the note would use. Correcting the report meant acknowledging the missing order. The next decision was whether finding it should stop the operation.
I kept the file readable, but stopped edits that repeat the problem
One response would be to choose an order and make playback follow it. That works inside a particular player. The edited file can leave that player, though, and the choice would not travel with it.
I made the missing order visible in the report instead of supplying a playback rule that would stay inside one player. Whether Battuta also stops depends on what it is being asked to do.
Open a file
The file already contains this problem.
Show it and keep reading
The music stays available for inspection.
Make an edit
The edit would write events with this problem.
Stop before writing
Explain where it happens and how to fix it.
Allow this location
The caller explicitly accepts it for this run.
Write with the exception
Inspection still shows the missing order.
When opening a file, refusing to read it would make the problem harder to diagnose. Inspection therefore shows where the order is missing and continues listing the music. It leaves the original events alone.
When editing, the tool can prevent itself from writing another instance of the problem. If the batch writes events with this cross-track dependency, it stops before creating an output file. The error identifies the events and suggests putting the instrument change on the notes’ track, or earlier than the notes.
The caller can allow the write; the order stays unresolved
Sometimes the caller wants to keep the arrangement: they know the intended player, or they are making a test file. They can explicitly allow that location for this run. That is how clip A was produced with the current version of Battuta, which otherwise refuses the edit.
The exception does not insert an order into the file. Inspection still reports the problem afterward. Accepting an ambiguity does not resolve it.
Allowing the write leaves the caller responsible for checking the intended player. The exception is useful for keeping a deliberate arrangement, but it supplies no new reason to trust the instrument shown by inspection. Moving the instrument change into an explicit sequence supplies the relationship that was missing.
With that sequence in the file, the second note in B plays as a trumpet on the tested renderer. The report and the audible edit now agree. I can bring that version into the next musical comparison and decide whether the phrase is one I want to keep.