This repository has been archived on 2026-09-16. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
Syplheed-Reborn/docs/re/structures/bgm-two-stems.md
Sylpheed RE agent b3c8632006 re: a music bank is two stems that play together -- not intro+loop, and
not something to concatenate

Q10, and it starts by withdrawing the question's own premise. BGM_001 is
not three sub-waves of 10 KB / 4.47 MB / 4.67 MB: the 10 KB is the bank
header. A bank is exactly TWO waves, and across all 32 BGM banks on the
disc the two always have the SAME duration -- equal to 0.01 s over
lengths from 37 s to 277 s. That alone kills intro+loop and kills two
halves of one piece, both of which require unequal lengths.

Four banks appear to break the rule and do not: BGM_106-109 are the known
leading-region straddle, and the giveaway is that the entry named
BGM_107.slb contains BANK id=1108. The seek packet counts pin each join
exactly, so they realign to the same two-equal-waves shape rather than
being dropped as noise. That trap goes in METHOD.

Then the roles, by decoding both waves to PCM. They are
sample-synchronous: transient-envelope correlation searched over +/-5 s
peaks at lag +0.00 s, and both waves stop at the same millisecond,
167.663 s. Two stems of one performance, meant to sound at once. Wave 1
is quieter, has almost no bass and is far more L/R-decorrelated, which
reads as a surround-rear pair or a second intensity layer -- I cannot
separate those two from the file, and say so: ChannelMask is 0x0002 on
both, and this game's channel metadata is already documented as
meaningless.

Two things the port needs that are NOT on the disc, both marked as
authored: the track is not a seamless loop (BGM_001 fades out and is
followed by 6.15 s of silence, no loop-point field found), and nothing
names which bank the menu plays -- all 32 BGM cues are numeric.
2026-08-28 18:13:10 +00:00

116 lines
5.4 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** — 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**.