This repository has been archived on 2026-09-16. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
Syplheed-Reborn/docs/re/structures/bgm-two-stems.md
Sylpheed RE agent 2c4005795b port: audit the handoff -- four stale statements, a broken link, and the
residue in one place

Every MISSION row is answered, so this iteration checked the deliverable
itself rather than opening new scope: an answer that is not reachable
from HANDOFF.md has not been delivered, and I have been editing that page
by surgical replacement for a dozen iterations.

Four things were wrong or stale:

- the Q5 row still said initial focus was 2x TUTORIAL, 1x NEW GAME while
  its own bullet said four boots -- I updated the body last time and not
  the row;
- the Q2 bullet still described the other four main-menu destinations as
  "likely by name -- an inference, not a measurement", which Q4 has since
  MEASURED, and it listed GP_MISSION_SELECT as a main-menu destination
  when MISSION SELECT lives under EXTRAS;
- the Q1 bullet claimed that if the game presents at 60 Hz "nothing else
  on this page changes". That is now false: Q7's declared fade-in lengths
  are keyframe-derived and would halve. The same bullet also missed that
  the transition capture is a SECOND independent measurement supporting
  1 unit = 1/60 s -- the quad is declared black for 12 units, 0.20 s
  under the conversion, and the capture measured the plateau at
  0.17-0.23 s. Recorded as corroboration, not proof, because the plateau
  spans two screens' fades and cannot separate them;
- bgm-two-stems.md linked ../port/MISSION.md from inside structures/,
  which resolves nowhere.

Also named the two videos actually in scope, since "3.3 GB of video, two
files are in scope" is not much use without the filenames.

And added a What Is Still Open section. With every row green the residue
was scattered across eight pages; it is now one table with why each item
is stuck. Two entries share a single unblocker -- an emulator whose audio
path can be observed -- and they are the only ones that would change a
port decision.
2026-08-28 19:02:49 +00:00

5.5 KiB
Raw Blame History

A music bank is two stems of one performance, played together

Status: CONFIRMED for the structure — decoded, with a disc-wide check over all 32 BGM banks. 🟡 for the role of the second wave — measured by signal analysis, and the two readings that survive are named below. which bank is the menu's music is undecodable from the disc, with the reach stated.

Answers MISSION Q10. It also withdraws the premise: the handoff said BGM_001.slb is three sub-waves of 10 KB / 4.47 MB / 4.67 MB. The 10 KB is the bank header, not a wave.

The structure — decoded

BGM_001.slb   (9 178 040 B)
      0   BANK  id=1001  data_size=9175992  hdr = 5 blocks (10240 B)  16bit/2ch
  10240   RIFF  data@14336    size=4466688  (2181 packets)   25697 B/s → 173.82 s
4481024   seek  2181 packets
4491264   RIFF  data@4495360  size=4673536  (2282 packets)   26887 B/s → 173.82 s
9168896   seek  2282 packets

A bank is a 10 240-byte header and then exactly two waves, and the two always have the same duration — different byte sizes and different bitrates, same number of seconds. Duration is data_size / PsuedoBytesPerSec (the u32 at RIFF+0x20; RIFF+0x24 is the sample rate, 48 000 Hz except BGM_020023 at 44 100).

Disc-wide

tools/re-capture/bgm_wave_census.py, full output in ../data/bgm-wave-census.txt:

28 of the 32 BGM entries read straight off their own pak entry as two waves of equal duration — equal to 0.01 s, over lengths from 37 s to 277 s.

The other four — BGM_106BGM_109 — are not counterexamples, they are the known leading-region straddle (slb-data-offset.md): those entry windows start mid-bank, so an entry contains the tail of the previous bank, one whole wave of its own, and the BANK header of the next one. slb_segment_phase.py bank shows it directly — the entry named BGM_107.slb contains BANK id=1108. Realigning across the boundaries with the seek packet counts, which pin each join exactly (bank 1107: a seek for 1164 packets and a 2 383 872-byte wave = 1164 packets), restores the same shape:

bank wave 0 wave 1
1107 2 383 872 B → 65.18 s 2 418 688 B → 65.18 s
1108 3 696 640 B → 128.91 s 3 696 640 B → 128.91 s
1109 2 887 680 B → 81.50 s 2 932 736 B → 81.50 s

Bank 1106's wave 0 begins in unclaimed bytes before its entry and is known only by its seek count (1 718 packets), so it is 🟡 rather than measured.

So: intro + loop is dead, and so is two halves of one piece. Both would give unequal durations; 32 banks give equal ones.

The role of the second wave — measured

Decoded BGM_001's two waves to PCM and compared them.

wave 0 wave 1
duration 173.809 s 173.809 s
first sound / last sound 0.001 s / 167.663 s 0.003 s / 167.663 s
RMS 6 824 3 961
L↔R correlation 0.61 0.14
energy below 200 Hz 16.5 % 6.7 %
energy 14 kHz 31.1 % 42.5 %

They are sample-synchronous. Transient-envelope cross-correlation searched over ±5 s peaks at lag +0.00 s (0.5624), and both waves stop at the same millisecond, 167.663 s. Two takes, two halves or two alternates would not do that. They are two stems of one performance and are meant to sound at the same time.

Therefore concatenating them is wrong, and not subtly: it plays the piece twice, the second time as a bass-less secondary stem.

🟡 Which kind of second stem — two readings survive

  • The rear pair of a 4-channel mix. Fits every number: quieter, far more decorrelated between L and R, and with the bass managed away to the front pair.
  • A second intensity layer to be mixed in or crossfaded.

Not settled, and the obvious field does not settle it. ChannelMask is 0x0002 on both waves, and sound-slb.md already records that this game writes meaningless channel metadata (movie voices declare 2 channels over mono content). What would settle it is a runtime observation — whether the game submits both waves to the mixer at once — which needs an emulator with audio this container does not have.

Which bank is the menu's music — undecodable, and here is the reach

The cue table binds cue names to sound ids and banks (sound-cue-table.md), and its 32 BGM cues are named BGM_001BGM_109 — pure numbers, no TITLE, no MENU, no semantic name anywhere. Looked in: the SOUNDS record (5 798 cues), FILES (5 135 bank paths), and the bank headers themselves (a BANK id that simply repeats the cue number, id=1001 for BGM_001). Nothing names a screen.

So the menu↔bank binding has to come from the running game — the same route Q8's event bindings need — and until then the port is choosing a track, not transcribing one.

What the port needs to know

  • a music bank is two waves that play together, not one track and not a sequence — do not concatenate;
  • both are full length; align them at sample 0;
  • the track is not a seamless loop. BGM_001 fades out and is followed by 6.15 s of silence (last sound 167.663 s of 173.809 s; the final second before silence is at RMS 168 against 4 788 at the head). Looping the wave as stored gives a fade-out and six seconds of nothing every cycle. No loop-point field has been identified in the XMA header, so a menu loop is authored.