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
251 lines
13 KiB
Markdown
251 lines
13 KiB
Markdown
# 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](../../port/MISSION.md). 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`](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_020`–`023`
|
||
at 44 100).
|
||
|
||
### Disc-wide
|
||
|
||
`tools/re-capture/bgm_wave_census.py`, full output in
|
||
[`../data/bgm-wave-census.txt`](../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_106`…`BGM_109` — are **not counterexamples**, they are the
|
||
known leading-region straddle ([`slb-data-offset.md`](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 1–4 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`](../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.93–0.94** in every
|
||
band, a different bank reads **0.001**, and wave 0 against itself misaligned by
|
||
1 s reads **0.004–0.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.9288–0.9380 |
|
||
|
||
So the estimator is **exact** at identity, and the 0.93–0.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 |
|
||
|---|---|---|---|
|
||
| 0–200 Hz | 0.169 | 0.321 | 71.4 % |
|
||
| 200 Hz–1 kHz | 0.184 | 0.221 | 24.8 % |
|
||
| **1–4 kHz** | **0.027** | **0.363** | 2.4 % |
|
||
| 4–12 kHz | 0.635 | 0.445 | 1.0 % |
|
||
| 16–24 kHz | 0.827 | 0.450 | 0.2 % |
|
||
|
||
❌ **"Wave 1 is wave 0 filtered" is dead.** A filter reads 0.936 at 1–4 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.221–0.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 16–24 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`](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_001`…`BGM_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`](sound-cue-table.md)), and its **32 BGM cues are named
|
||
`BGM_001` … `BGM_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`](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`](../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 decoded** — `BGM_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.
|