Files
Sylpheed/docs/re/structures/bgm-two-stems.md
sylph-decoder 9bacd69f6c re: bgm-two-stems.md said the menu's bank is undecodable from the disc, and its own page decodes it
The Status line and the section heading both read 'undecodable from the disc' while
a later section of the same page decodes it three ways, one of them static from the
executable -- GamePart_Title's handler does li r5, 1103. A reader who stops at the
top concludes the opposite of what the page establishes.

The surviving content is the reason the CUE TABLE cannot answer it: 32 BGM cues
named by number with no screen name, with SOUNDS, FILES and the bank headers all
searched. A negative about one search location, written as a negative about the
disc -- the same method-versus-subject error as the SE-audio heading, in the first
line a reader sees.

It propagated: the port's BLOCKED.md carries 'which BGM the menu plays -- not on
the disc' in the same words.

Found by applying sylpheed-port's 'get the category right' discipline to my own
noisy impossibility sweep, after measuring what the false positives actually were
rather than assuming they were infrastructural.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wuu56cE8vJGTBtn1ppsk8v
2026-08-31 02:22:28 +00:00

13 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 was recorded here as undecodable from the disc🔴 that was wrong and is corrected below: it is decoded, BGM_103, named by the executable itself. The negative was true of the cue table, not of the disc.

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

⚠️ Our own reader disagreed with this page until 2026-08-29, and the page was right. slb::to_xma_riffs was emitting that 10 240-byte header as a third sub-wave, so sound_bank_riffs("BGM_103.slb") returned three — which the port caught while exporting the menu music. The header is not a wave (it decodes to 0.009 s and is 99.1 % zero); the cause was a modulus that assumes a bank header is shorter than one 2048-byte packet, and it is fixed with a disc-wide 28/28 check — slb-bank-header-not-a-wave.md — 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.

One of the two is now narrowed: wave 1 is NOT a filtered copy of wave 0

measured 2026-08-30../data/bgm-stem-coherence.txt, tools/re-capture/bgm_stem_coherence.py, on BGM_103, the menu's bank.

Magnitude-squared coherence is ~1 wherever one signal is a linear filter of the other. Controlled first: a real filter of wave 0 reads 0.930.94 in every band, a different bank reads 0.001, and wave 0 against itself misaligned by 1 s reads 0.0040.057.

2026-08-31 — the identity control, which I had not run, and it strengthens this. sylpheed-port's rule after a disqualified instrument of their own: before asking whether an instrument can measure a difference, ask whether it returns zero for no difference. Applied here:

coherence, all bands
wave 0 against itself 1.0000
a linear filter of it, no delay 1.0000 / 0.9999
the same filter + 12 ms delay 0.92880.9380

So the estimator is exact at identity, and the 0.930.94 above is entirely the delay's windowing cost, not the estimator's ceiling. The true ceiling for a filtered copy is 1.0, which puts the measured 0.027 midrange further from it than the original control implied.

📌 It also adds an argument the first pass did not make: a delay depresses coherence uniformly across bands (0.9288…0.9380 is flat). The measurement is not flat — 0.027 in the midrange against 0.83 at HF. So the observed shape is inconsistent with a delayed filtered copy as well as an undelayed one, which was the remaining way a rear pair could have produced it.

band w0 vs w1 w0's own L vs R energy in w0
0200 Hz 0.169 0.321 71.4 %
200 Hz1 kHz 0.184 0.221 24.8 %
14 kHz 0.027 0.363 2.4 %
412 kHz 0.635 0.445 1.0 %
1624 kHz 0.827 0.450 0.2 %

"Wave 1 is wave 0 filtered" is dead. A filter reads 0.936 at 14 kHz; the measurement reads 0.027.

📌 The frequency structure is inverted relative to any mic-pair or reverb model: coherence rises with frequency while energy falls. A rear pair or a reverb return decorrelates fastest at HF. Whatever is coherent lives in bands holding ~1.3 % of the energy; the bands holding 96 % of it read 0.169 and 0.184.

📌 In the midrange the two waves are 13× further apart than the two channels of one wave — 0.027 against 0.363.

🔴 But the same control kills the clean answer, so this does NOT settle the 🟡. L vs R within one wave is genuinely one performance in two channels, and it reads only 0.2210.497 — nowhere near 0.94. So in this material "same performance" does not imply high coherence, and my positive control was the wrong model of the rear-pair reading: a real 4-channel mix's rear pair is not a linear filter of its front pair. The tool tests for linear filtering, and neither surviving reading requires it. Stated here rather than discovered later.

⚠️ Reach: one bank, one 60 s window, mono-summed. The 1624 kHz reading of 0.827 is unexplained and is not generic codec behaviour — the different-bank control reads 0.002 in that same band.

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

🔴 SUPERSEDED by this page's own "The menu's music is BGM_103" section below. The heading is false as written: the bank is determined on the disc — GamePart_Title's phase handler does li r5, 1103 — so a reader who stops here concludes the opposite of what the page establishes.

What survives is the reason the cue table cannot answer it: its 32 BGM cues are named BGM_001BGM_109, pure numbers, with no screen name anywhere, and SOUNDS, FILES and the bank headers were all searched. That is a negative about one search location, and it was written as a negative about the disc.

⚠️ It propagated: the port's BLOCKED.md carries "which BGM the menu plays — not on the disc" in the same words.

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 as stored. 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 end-to-end gives a fade-out and six seconds of nothing every cycle.

    🔴 But "no loop-point field has been identified … so a menu loop is authored" is REFUTED (2026-08-30), and this bullet said it for days after. There is no loop point in the file header; there is one in the XMA decoder context, written at runtime via XMASetLoopData, and Xenia already logs it. For BGM_103 it is [9.44 s, 71.31 s], cycling every 61.87 s — measured by watching three wraps, so the game never reaches the fade at all. That is why the stored tail looks unusable: it is never played. menu-bgm-loop-fields-conflict.md

    ⚠️ Reach: measured on BGM_103 only. Whether every bank carries loop bounds is untested — but "the format has nowhere to put one" is dead.

Confirmed at runtime — two stereo streams decode at once

The page above said the "played together" reading needed "a runtime observation of whether the game submits both waves to the mixer at once", and that this container could not do it. It can: --xma_param_probe=true logs every XMA stream the decoder is handed.

Sitting on the main menu, two stereo 48 kHz streams were decoding simultaneously — 1 893 packets / 3 876 864 B and 1 919 packets / 3 930 112 B. Two stereo streams alive at the same moment on a screen with one piece of music playing is the direct observation this page was missing: a music bank's two waves are simultaneous, not sequential. Concatenating them is wrong, now measured as well as inferred.

🟡 It still does not separate the two readings — surround-rear pair versus a second intensity layer — because both predict simultaneity.

The menu's music is BGM_103 — three independent routes agree

An earlier version of this section said the two observed byte sizes "do not match any bank's declared wave sizes". That was wrong — I had checked only the BGM_0xx rows. They match BGM_103 exactly, and the code names it:

route evidence
static, code GamePart_Title's phase handler sub_821C5580 does li r5, 1103 into a sound call (sub_8217ACF8, with r4 = 4) — cue 1103 = BGM_103
static, disc ../data/bgm-wave-census.txt: BGM_103.slb = two waves of 3 876 864 and 3 930 112 bytes, 87.75 s each
runtime the XMA probe at the main menu saw two stereo 48 kHz streams decoding simultaneously, of 3 876 864 and 3 930 112 bytes

Byte-for-byte on both waves. So:

  • which bank the menu plays is decodedBGM_103, from the executable, since the cue table's BGM entries are numeric and name no screen;
  • and the two-stems-play-together finding is now confirmed on a named bank, with the runtime stream sizes equal to the bank's declared wave sizes — so the game hands the decoder the whole wave, not a window, which is the opposite of what the earlier note guessed.