extracted", and the tool was in the build all along Two iterations ago I closed Q8 by declaring the SE audio undecodable: Static.slb has no wave boundaries, there is no XACT container anywhere, and I said the index "exists only at runtime" as though that put it out of reach. The disc half of that stands. The conclusion did not. This build of Canary carries a cvar called xma_param_probe, added by this project, whose own comment says it logs each XMA stream's parameters and head bytes so raw sound.pak entries can be matched to real decode params. It has been sitting in the startup CONFIG DUMP of every log I have read this session. Run with it, driving the main menu: a d-pad move spawns a new mono 48 kHz stream of 4 packets / 8192 bytes, and B spawns a different one of 2 packets / 4096 bytes. Searching their logged head bytes in Static.slb finds each at exactly one offset -- 0x1ec0 and 0x0ec0 -- and the two are contiguous, 0x0ec0 + 4096 = 0x1ec0. So the bank is a packed run of whole 2048-byte packets with no delimiters, which is precisely why the seek scan found nothing: there is nothing to find. A wave is (offset, packet count) and nothing else. That splits Q8's binding cleanly. Event -> WAVE is now measured: the port can have the audio. Event -> cue NAME is still a name match on the authors' identifiers, and the page says so. The same run settled something for Q10 too. Sitting on the main menu, TWO stereo 48 kHz streams were decoding simultaneously. bgm-two-stems.md said that observation was what it needed and that this container could not make it; it can, and a music bank's two waves are now measured as simultaneous rather than only inferred. METHOD gets the general lesson, because it cost two iterations: check what instrumentation the local build already has before declaring a question blocked on tooling.
137 lines
6.6 KiB
Markdown
137 lines
6.6 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 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** — 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.
|
||
|
||
**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.** `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**.
|
||
|
||
## ✅ 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. And the two byte
|
||
sizes do not match any bank's declared wave sizes in
|
||
[`../data/bgm-wave-census.txt`](../data/bgm-wave-census.txt), so *which* bank the
|
||
menu plays is still not established; the game hands the decoder a window, not the
|
||
whole declared wave.
|