At a glance

Magic
pQES (bytes 70 51 45 53) at the track's own offset 0
Where on disc
Behind a VAB inside scene-VAB-prefixed streaming entries, never at the entry's start; 92 tracks. The music_01 bank map is piecewise (sound-test track i = extraction 988+i for i <= 67, 990+i above) - see music tracks
Header
15 bytes (Legaia variant) - u32 big-endian version; PPQN always 480
Parser
crates/seq (binary seq); playback engine-audio::Sequencer
Confidence
Confirmed - public PsyQ shape; the divergences are pinned by the retail loader and by round-BPM arithmetic across the corpus (how we know)
Used by
Audio stack, Media player, Asset viewer

Why your MIDI tool chokes

Two divergences from the PsyQ documentation
  1. The version field is a u32 big-endian where the documentation says u16, shifting every later field by two bytes. A 13-byte-header parser reads the wrong PPQN and tempo.
  2. Meta events carry no length byte. MIDI writes a tempo change as FF 51 03 tt tt tt; SEQ writes FF 51 tt tt tt. Read a phantom length byte and the first tempo override is lost - and since every retail track ships a 240 BPM placeholder header tempo that the first body event corrects, everything plays ~3x too fast.
Header (15 bytes) magic pQES +0 version u32 BE = 1 +4 PPQN u16 BE = 480 +8 tempo u24 BE us/qn +0xA time sig num, denom +0xD events ... delta, status, data +0xF Set Tempo meta event - no length byte: FF 51 tt tt tt MIDI would insert 03 here - SEQ does not
The two bytes that differ from the PsyQ documentation: the widened version word, and the absent meta-event length.

Event stream

Each event is a variable-length delta time, a status byte and its data - standard MIDI encoding, with running status (a first byte below 0x80 reuses the previous status). Channel is the low nibble.

StatusEventDataRetail use
0x9nNote Onkey, velocityThe note event; velocity == 0 is Note Off (0x8n is never used)
0xBnControl Changecontroller, valueCC7 volume and CC10 pan are dynamic and busy; CC99 carries loop markers
0xCnProgram ChangeprogramSelects a VAB program slot
0xEnPitch BendLSB, MSBHeavy use - thousands of events; scaled over the tone's own pbmin/pbmax
0xAn / 0xDnAftertouch2 / 1Absent from retail data
FF 51Set Tempo3 bytes, u24 BENo length prefix; overrides future events only
FF 2FEnd of TracknoneTwo bytes total; retail writes a trailing 00 the parser never reads

Any other meta type has an unknown fixed length, so the parser (like the retail reader) cannot skip it and ends the track there.

Loop markers

Looping is encoded as ordinary control changes: controller 99 value 20 = Loop Start, value 30 = Loop Forever (jump back to the last Loop Start). 88 of 92 tracks carry them. The engine Sequencer rewinds to the event after the marker and resets its sample clock, so the loop re-fires on the same sample offset every pass; an end-of-track after a Loop Start also rewinds.

Tempo math

Per-tick duration is tempo / ppqn microseconds. The engine clock keeps every term integer - it accumulates in units of sample × ppqn × 1 000 000 and fires a delta-d event once the accumulator reaches d × tempo × 44100 - so long tracks never drift. legaia_seq::us_per_tick returns the float form for inspection only.

Edge cases the engine reproduces

  • Program change to an unused VAB slot. Retail's bank-open writes a running used-program counter into each slot, so a change to an unused slot aliases onto the next used slot's tone page (see VAB). Eight such changes exist across four entries; two are audible (PROT 868 program 5, PROT 996 program 19) and the engine aliases them the same way. Past the last used slot retail reads garbage; the engine stays silent there.
  • Truncation. An unknown meta type or a system status byte (0xF0..=0xFE) stops parsing; the parser appends a synthetic End of Track and marks the track truncated so players and parity oracles can tell it from a clean end.
Details: the one non-clean track (PROT 1045)

The stream is a valid, complete track. A few notes before its real end the parser drifts by exactly one byte inside a dense run of running-status notes (near offset ~4900, beside a non-round-BPM tempo event). Latched one byte ahead, it reads the closing fade's volume CCs as deltas plus note data, walks through the real FF 2F 00, and halts on a 0xF4 eleven bytes past the true end. Every other track in the corpus ends cleanly.

Where the data lives and how it is played

SEQ payloads ride behind a VAB inside the scene streaming containers (scene bundles), so the bytes sit at a non-zero offset. The field VM's BGM opcode (0x35) writes a slot that the BGM loader resolves through the CDNAME map to the containing entry; playback goes through the PsyQ SsSeqOpen family (audio stack).

seq info    <PATH>    # header summary + event-type histogram
seq events  <PATH>    # disassemble every event in source order
seq json    <PATH>    # full parse as JSON

The engine side is legaia_engine_audio::Sequencer: a parsed Seq plus a loaded VabBank driving the from-scratch SPU model.

How we know

ClaimFunction / addressWhat it proves
Version checkFUN_80062410 (SsAPI loader, SCUS)Reads the version word and rejects != 1
No meta length byteCorpus arithmeticRead raw, every retail tempo lands on a round BPM (65, 70, 80, 128, 130, 140, 150, 160, 170); a phantom length byte scatters them - real_seq_stream_integrity
Event usage, loop markers, pitch bendDisc-wide sweep of every SEQ-bearing entryreal_seq_expressive_events, real_seq_program_change_coverage
BGM slot resolution_DAT_8007BAC8FUN_800243F0Field-VM BGM slot resolved through CDNAME to the streaming entry
Program-slot aliasingFUN_80068D94 (bank open)Used-program counter stored per slot, read back as tone-page index

See also