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.
116 lines
5.5 KiB
Markdown
116 lines
5.5 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**.
|