Eigenstate
Notes / Ansatz

Why my MIDI editor refuses to select the second bar

Writing the default 4/4 time signature into the file changes no notes or bar boundaries. It makes the same selection request succeed.

Revised Jason Shen

Ask for the second bar of a MIDI file, and Battuta may refuse even though the format defines where that bar is. Battuta is the command-line MIDI editor I am building. Its bar selector requires a time signature written in the file; the MIDI format supplies a default of 4/4 when none is written.

My original note called that default a guess. It confused a tool policy with a format rule. I had treated a stricter input requirement as evidence that the tool knew better. Requiring an explicit meter can make the source of an editing address easier to inspect, but it does not make the format’s default incorrect. To judge the policy, I needed to show what the extra declaration actually changed.

Corrected 7 September 2026: MIDI specifies the default of 4/4. Revised 9 September: added a historical CLI reconstruction and clarified that a duration exceeding Battuta’s counter does not, by itself, make a MIDI file corrupt.

Which notes belong to the second bar?

A MIDI file stores timed events, including when a note starts and stops. In this example, a quarter note spans 480 Ticks, the file’s timing units. A bar of 4/4 holds four quarter notes, or 1,920 Ticks; a bar of 3/4 holds three, or 1,440. Changing meter changes how the same timeline is divided into bars. It need not move a note.

The Standard MIDI File specification, § 2.2, makes 4/4 the interpretation when no time signature is supplied. The illustration follows that rule. The command results come from running constructed files through Battuta’s 27 August implementation, pinned in the sources below.

One passage, three files

Which notes start in the second bar?

Keep the six notes fixed. Change only the time signature written at the start of the file.

Time signature in the file

No time signature: the format supplies 4/4

Ticks 1,920–3,839End boundary: 3,840, excluded

Note / start Tick
04,320 Ticks
Note 10
Starts outside the second bar.
Note 21,440
Starts outside the second bar.
Note 31,920
Starts inside the second bar.
Note 42,880
Starts inside the second bar.
Note 53,360
Starts inside the second bar.
Note 63,840
Starts outside the second bar.

Dashed edges mark the format’s default second bar. Battuta does not use this selection.

Battuta refuses the bar selection

The format defines a range, but this selector requires a stated time signature. All six notes remain available to inspection without a bar range.

Download this MIDI file

Explicit 4/4: the second bar

Ticks 1,920–3,839End boundary: 3,840, excluded

Note / start Tick
04,320 Ticks
Note 10
Starts outside the second bar.
Note 21,440
Starts outside the second bar.
Note 31,920
Starts inside the second bar.
Note 42,880
Starts inside the second bar.
Note 53,360
Starts inside the second bar.
Note 63,840
Starts outside the second bar.

Solid edges mark the stated meter’s second bar.

Battuta returns notes 3, 4, 5

Writing 4/4 makes this request available. The bar boundaries are identical to the format’s default.

Download this MIDI file

Explicit 3/4: the second bar

Ticks 1,440–2,879End boundary: 2,880, excluded

Note / start Tick
04,320 Ticks
Note 10
Starts outside the second bar.
Note 21,440
Starts inside the second bar.
Note 31,920
Starts inside the second bar.
Note 42,880
Starts outside the second bar.
Note 53,360
Starts outside the second bar.
Note 63,840
Starts outside the second bar.

Solid edges mark the stated meter’s second bar.

Battuta returns notes 2, 3

The second bar now starts earlier. Note 4 starts exactly at its end and belongs to the next bar.

Download this MIDI file

Constructed MIDI files; results reproduced with Battuta’s 27 August version. Switching compares recorded results, not a live editor.

Battuta’s inspection assigns a note to the bar in which it starts. The start boundary is included; the end belongs to the next bar. In 4/4, the second bar therefore contains the notes starting at Ticks 1,920, 2,880 and 3,360. In 3/4, it contains those at 1,440 and 1,920. A note already sounding across a boundary would still belong to its starting bar for this inspection.

Adding explicit 4/4 changes none of those boundaries compared with the format default. What changes is whether Battuta accepts the request. The refusal establishes that the file did not state its meter. It does not establish that the default would select the wrong notes.

The extra declaration has a cost

The selector’s original implementation already acknowledged the MIDI default and deliberately declined to use it. Requiring a stated meter puts that choice in the file, where a human or another tool can inspect it. But a file that relies on a valid default now needs an additional declaration before this operation is available. Writing “4/4” also cannot prove that the musician intended 4/4.

A selector could instead accept the specified default and report that it had used it. That would preserve the distinction between stated and defaulted meter with less setup. The comparison gives me a reason to distinguish those options; it supplies no reader or usage evidence that the stricter choice produces better edits. The implemented policy remains a choice to explain, rather than a correction to the format.

Changing meter mid-file is a separate capability limit. This selector handles one stated meter from Tick 0 throughout the file. It refuses a first signature stated later or a different signature further on; repeating the same signature is allowed. A meter map could handle changes, while blindly extending the first meter would put later bar boundaries in the wrong places.

Playback has another explicit-input policy. MIDI holds instructions for sound; a soundfont supplies samples to render them. mid play uses the --rig path, otherwise BATTUTA_SOUNDFONT, and refuses if neither supplies one. This makes the sample-bank choice visible. A documented fallback could also be attributable, and a named path alone does not freeze its contents, the synthesiser version or its settings. The instrument-change audio case shows why inspecting a file and listening to its rendering remain different checks.

An overflow needs a different explanation

The original note also used a duration overflow to argue for refusing inputs. That case belongs to a different decision: what to do when the tool cannot represent a valid total. MIDI stores the gap before each event as a delta time, at most 268,435,455 Ticks. Battuta accumulated those gaps in an unsigned 32-bit counter, whose maximum is 4,294,967,295. Legal individual gaps can add up beyond that counter.

Sixteen maximum gaps total 4,294,967,280 Ticks and still fit. Seventeen total 4,563,402,735. The unchecked release build wrapped that second total to 268,435,439, then reported it as the file’s length with exit code zero. The reconstruction reproduces that false answer; after the fix, the same file is refused at the parsing boundary.

The constructed file contains legal delta times. Its length exceeds this implementation’s range; it is the wrapped answer that is wrong. Widening the representation would be another solution. I kept the existing counter and added checked accumulation, so an unsupported length would stop at the boundary instead of becoming a plausible result downstream.

Source versions, command outputs and reproduction

The MIDI examples were constructed for this revision, not taken from a creative session. Only the initial time signature differs between the three passage files. The diagram shows note durations to scale and computes bar boundaries at build time. Switching files reveals recorded results, not a live Rust execution.

The bar-selector implementation record acknowledges the format default. The reconstruction uses e2b1b25, 27 August 2026: meter requirements, note membership, and soundfont resolution. It is historical evidence, not a claim about every later version.

The recorded command outputs include the complete refusal text. The old messages call defaulting a wrong answer and suggest an excessively long file is almost always corrupt. This Note does not endorse those explanations. The results establish the refusals; neither message establishes that the input lacks a valid interpretation.

The overflow comparison uses the fix’s parent, a236fae, built in release mode. Its historical record also reports a debug panic; the downloadable comparison records the release failure and the fixed debug build. The 16-gap and 17-gap inputs contain text events and an end-of-track event, not music.

Download the Python reproduction script. It uses the standard library to generate all five files and checks the CLI results when given the appropriate binaries. With a local checkout at each source revision:

# At e2b1b25:
cargo build --locked --bin mid
# At a236fae:
cargo build --locked --release --bin mid
# Then, using the absolute paths to those two binaries:
python3 reproduce.py --out /tmp/refusal-fixtures \
  --mid /path/to/e2b1b25/target/debug/mid \
  --before-mid /path/to/a236fae/target/release/mid

No soundfont or audio renderer is needed for these inspection runs.