Files
Sylpheed/docs/re/structures/bgm-two-stems.md
sylph-decoder b410c94445 re: run the identity control on the coherence estimator -- it is exact, and the refutation strengthens
sylpheed-port disqualified a difference instrument of their own and named the rule:
before asking whether an instrument can measure a difference, ask whether it
returns zero for no difference. Mine had never had that test -- its positive
control was a filtered copy at 0.94, which I had taken as the ceiling.

Identity reads 1.0000 in every band, and a linear filter with NO delay also reads
1.0000. The 0.94 was entirely the 12 ms delay's windowing cost. So the ceiling for
a filtered copy is 1.0 and the measured 0.027 midrange is further from it than the
original control implied.

It also adds an argument the first pass missed: a delay depresses coherence
uniformly (0.9288..0.9380 flat), while the measurement is 0.027 midrange against
0.83 at HF. The shape is inconsistent with a delayed filtered copy too, which was
the remaining route by which a rear pair could have produced it.

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

237 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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](../../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 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`](../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`](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`](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.