diff --git a/docs/port/HANDOFF.md b/docs/port/HANDOFF.md index 1c12fd1..0dccc7a 100644 --- a/docs/port/HANDOFF.md +++ b/docs/port/HANDOFF.md @@ -36,7 +36,7 @@ authored version can be deleted. | Q7 | transitions | βœ… answered | a **fade through black**, drawn by the screen's own last-painting `.prm` quad. Fade-in ramp is **decoded** from its keyframes; the ~0.4 s fade-out is **measured** (not in the file) β€” [`screen-transitions.md`](../re/screen-transitions.md) | | Q8 | menu audio bindings | ❔ open | cue table complete, event binding is not | | Q9 | video binding + playback rules | 🟑 partial | `ADV.wmv` is the boot intro; new-game intro unidentified | -| Q10 | music-bank sub-wave roles (intro+loop?) | ❔ open | we concatenate blindly today | +| Q10 | music-bank sub-wave roles (intro+loop?) | βœ… answered | **two stems of one performance, played together** β€” sample-synchronous, equal duration, 32/32 banks. **Concatenating is wrong.** Not a seamless loop either β€” [`structures/bgm-two-stems.md`](../re/structures/bgm-two-stems.md) | | S1 | Ready Room go/no-go | ❔ open | probe not run | ## Already settled β€” the port can rely on these today @@ -140,11 +140,22 @@ authored version can be deleted. reader must allow a short read there and only there. * **Voice downmixes to mono, music does not.** The left-channel downmix is correct for spoken lines and discards half a music mix. -* **A music bank has several sub-waves and we glue them together.** `BGM_001` - is 10 KB + 4.47 MB + 4.67 MB, concatenated into one 347 s track. Nobody has - established whether those are intro + loop, two variations, or two halves β€” - see Q10. Do not build menu looping on the concatenated track until it is - answered. +* **A music bank is TWO STEMS THAT PLAY TOGETHER β€” do not concatenate.** + βœ… The "10 KB + 4.47 MB + 4.67 MB" reading was wrong: the 10 KB is the bank + header, and a bank is exactly **two waves of identical duration** (32/32 banks + on the disc). `BGM_001`'s two are **sample-synchronous** β€” transient + correlation peaks at lag 0.00 s over Β±5 s, and both stop at the same + millisecond, 167.663 s. Wave 1 is quieter, far more L/R-decorrelated and has + almost no bass, so it reads as a surround-rear pair or a second intensity + layer β€” 🟑 which of those is unsettled, and `ChannelMask` is `0x0002` on both, + so the file will not say. Today's 347 s concatenation plays the piece twice, + the second time as a bass-less stem. + ❔ **And it is not a seamless loop**: `BGM_001` fades out at 167.663 s and is + followed by 6.15 s of silence, with no loop-point field identified. A menu loop + is authored. + ❔ **Which bank is the menu's music is not on the disc** β€” all 32 BGM cues are + named `BGM_001`…`BGM_109`, no semantic name in `SOUNDS`, `FILES` or the bank + headers. The port is choosing a track. * **`JNGL_001.slb` does not decode.** One bank in 9 519; its payload is not a whole number of XMA1 packets from any known data offset. diff --git a/docs/re/INDEX.md b/docs/re/INDEX.md index 90957d4..7148af9 100644 --- a/docs/re/INDEX.md +++ b/docs/re/INDEX.md @@ -103,6 +103,7 @@ files, which is how the same ground got covered twice. | [`structures/slb-data-offset.md`](structures/slb-data-offset.md) | `.slb` data offset = `(cumulative start of the `.pNN` segment) mod 2048`; the leading bytes are the previous bank's audio, not a header | βœ… CONFIRMED β€” exact for 8 783/8 783 banks, 0 mismatches; the four values (1392/1468/1600/1728) are the running sums of the five segment sizes mod 2048, which supersedes the earlier `first_riff % 2048` scan heuristic. Decoding at the right offset yields more audio in 85 of 140 sampled banks (median 70Γ—), 54 identical controls. Wave boundaries are exact β€” `seek` magic at `data_at + declared_size`, **7 620/7 620** β€” and `Channels` must be read from `RIFF+49` (2.12 % are stereo) | | [`structures/sound-pak-contents.md`](structures/sound-pak-contents.md) | Census of `sound.pak`, and the limit of the leading-region rule | βœ… CONFIRMED, 5 135/5 135 names hash into the TOC, **9 519/9 519** entries accounted for, and a full 4 114-bank manifest (408.3 min of audio) computed from PsuedoBytesPerSec without decoding; ⚠️ leading-region rule holds for 1 571/4 382 eng and 0/5 100 jpn | | [`structures/sound-cue-table.md`](structures/sound-cue-table.md) | The cue index in `tables.pak` β€” message id -> cue -> sound id -> `.slb` bank | βœ… CONFIRMED, 1 326/1 338 script message ids bind to a bank; SOUNDS and FILES agree on the same 12 absentees, 0 orphan files | +| [`structures/bgm-two-stems.md`](structures/bgm-two-stems.md) | A music bank is two stems of one performance, played together | βœ… decoded structure, 32/32 banks two waves of equal duration (4 explained by the leading-region straddle); stems measured sample-synchronous; 🟑 rear-pair vs intensity-layer unsettled; ❔ no bank names a screen | | [`structures/cutscene-message-table.md`](structures/cutscene-message-table.md) | Cutscene dialogue β€” speaker, portrait, on-screen seconds, audio cue per page | βœ… CONFIRMED, field count = 9Β·PageCount+2 for all 7 PageCounts, 1 252/1 252 caption keys match, 138 ids close both ways | | [`structures/preset-message-rules.md`](structures/preset-message-rules.md) | The reactive-chatter rule table β€” 64 events, the (message, Yes/No) pair payload, the `Sperkers` roster | βœ… CONFIRMED, the seven fields read off `sub_82213980`, phase tables merge non-additively (224 variants discarded); 8 firing sites share one entry, Probability is a per-cent roll against rand*100.0; Yes = one-shot line; queue is Priority-ordered and capped at 128; the cooldown is Interval + Fluctuation*rand held in the lingering queue node; the 14 player-facing events are voiced by the player set alone (13/15 Katana tables, 0/129 others) (three are seconds Γ—60, the Yes/No element is a u32 mask, hence the 32 clamp); MessageCountΒ·2 == positional count in 9 216/9 216; five naming routes union to 144/144 (six names predicted from the speaker roster before hashing, control 0/4); 2 388/2 405 message ids bind to a cue | | [`structures/mission-scoring.md`](structures/mission-scoring.md) | Mission scoring and the S/A/B/C/D rank thresholds β€” 24 tables x 3 difficulties | βœ… CONFIRMED, one 22-field schema over 72 records; difficulty moves 10 of 22 fields and never the RankScore thresholds; βœ… all 24 named stage\StageParameter_S.tbl (+ _Tutorial shared by six, + _Test) | diff --git a/docs/re/METHOD.md b/docs/re/METHOD.md index 0c542a3..9c206db 100644 --- a/docs/re/METHOD.md +++ b/docs/re/METHOD.md @@ -152,3 +152,11 @@ agent's loop prompt, i.e. nowhere durable. See [`README.md`](README.md) for the keeps rising long after the fade is over β€” 1.47 s against a declared 0.97 s on one screen. Time the fade from where the frame is *pure black*, and take the ramp itself from the keyframes. +* **A `sound.pak` entry is not a bank.** Entry windows straddle bank boundaries + (the leading-region effect), so a naive "walk the RIFFs in this entry" gives + the tail of the previous bank, then one of your own, then the *next* bank's + header. It looks like a bank with the wrong number of waves. The tell is a + `BANK id=` that is not the entry's own number, and the fix is the `seek` packet + counts β€” they pin each wave's join exactly, so a straddle can be realigned + rather than thrown away. 4 of 32 BGM banks look like counterexamples until this + is applied. diff --git a/docs/re/REFUTED.md b/docs/re/REFUTED.md index e5fb155..f29e720 100644 --- a/docs/re/REFUTED.md +++ b/docs/re/REFUTED.md @@ -119,6 +119,20 @@ neighbourhood, not just the line. * "a set-difference over file names can see reuse" β†’ it cannot; **join per USER**. Per-pak copies are Γ—6. + +## Audio + +* "`BGM_001.slb` is three sub-waves (10 KB + 4.47 MB + 4.67 MB)" β†’ the 10 KB is + the **bank header**; a bank is **two** waves. +* "a music bank's two waves might be intro + loop, two variations, or two halves" + β†’ they are **two stems of one performance, played together** β€” equal duration + in 32/32 banks, and sample-synchronous. + [`structures/bgm-two-stems.md`](structures/bgm-two-stems.md) +* "`BGM_106`–`BGM_109` break the two-wave rule" β†’ they are the leading-region + straddle; realigned across entry boundaries they obey it. +* "the cue table names which BGM belongs to which screen" β†’ all 32 BGM cues are + numeric (`BGM_001`…`BGM_109`). + ## Units, weapons, effects and assets * "`Generic` (394) is the unit datasheet" β†’ refuted. diff --git a/docs/re/data/bgm-wave-census.txt b/docs/re/data/bgm-wave-census.txt new file mode 100644 index 0000000..246dd79 --- /dev/null +++ b/docs/re/data/bgm-wave-census.txt @@ -0,0 +1,35 @@ +bank waves per-wave (bytes, bytes/s, Hz, seconds) +BGM_001.slb 2 4466688 25697 48000 173.82s | 4673536 26887 48000 173.82s EQUAL-LENGTH +BGM_002.slb 2 4382720 26376 48000 166.16s | 4667392 28090 48000 166.16s EQUAL-LENGTH +BGM_003.slb 2 3555328 26201 48000 135.69s | 3414016 25159 48000 135.70s EQUAL-LENGTH +BGM_004.slb 2 6311936 32874 48000 192.00s | 5787648 30143 48000 192.01s EQUAL-LENGTH +BGM_005.slb 2 4005888 28013 48000 143.00s | 3917824 27397 48000 143.00s EQUAL-LENGTH +BGM_006.slb 2 3946496 26377 48000 149.62s | 3915776 26172 48000 149.62s EQUAL-LENGTH +BGM_007.slb 2 7565312 29269 48000 258.48s | 8099840 31337 48000 258.48s EQUAL-LENGTH +BGM_008.slb 2 3258368 29947 48000 108.80s | 3397632 31227 48000 108.80s EQUAL-LENGTH +BGM_009.slb 2 3819520 25636 48000 148.99s | 3614720 24262 48000 148.99s EQUAL-LENGTH +BGM_010.slb 2 4190208 28457 48000 147.25s | 4100096 27845 48000 147.25s EQUAL-LENGTH +BGM_011.slb 2 4222976 25232 48000 167.37s | 4311040 25758 48000 167.37s EQUAL-LENGTH +BGM_012.slb 2 5562368 31605 48000 176.00s | 5404672 30709 48000 176.00s EQUAL-LENGTH +BGM_013.slb 2 6539264 29904 48000 218.68s | 6457344 29530 48000 218.67s EQUAL-LENGTH +BGM_014.slb 2 6656000 31438 48000 211.72s | 6948864 32821 48000 211.72s EQUAL-LENGTH +BGM_015.slb 2 8017920 29230 48000 274.30s | 7974912 29074 48000 274.30s EQUAL-LENGTH +BGM_016.slb 2 3549184 28341 48000 125.23s | 2775040 22159 48000 125.23s EQUAL-LENGTH +BGM_017.slb 2 5353472 30690 48000 174.44s | 5062656 29023 48000 174.44s EQUAL-LENGTH +BGM_018.slb 2 3620864 26878 48000 134.71s | 2920448 21679 48000 134.71s EQUAL-LENGTH +BGM_019.slb 2 6834176 30665 48000 222.87s | 6713344 30123 48000 222.86s EQUAL-LENGTH +BGM_020.slb 2 4804608 29592 44100 162.36s | 4675584 28798 44100 162.36s EQUAL-LENGTH +BGM_021.slb 2 7780352 28034 44100 277.53s | 7694336 27724 44100 277.53s EQUAL-LENGTH +BGM_022.slb 2 5545984 28006 44100 198.03s | 5867520 29630 44100 198.03s EQUAL-LENGTH +BGM_023.slb 2 4962304 28001 44100 177.22s | 5044224 28463 44100 177.22s EQUAL-LENGTH +BGM_101.slb 2 4800512 27481 48000 174.68s | 2635776 15089 48000 174.68s EQUAL-LENGTH +BGM_102.slb 2 1150976 30703 48000 37.49s | 1269760 33872 48000 37.49s EQUAL-LENGTH +BGM_103.slb 2 3876864 44181 48000 87.75s | 3930112 44788 48000 87.75s EQUAL-LENGTH +BGM_104.slb 2 2672640 31503 48000 84.84s | 2674688 31527 48000 84.84s EQUAL-LENGTH +BGM_105.slb 2 3581952 35879 48000 99.83s | 3850240 38566 48000 99.84s EQUAL-LENGTH +BGM_106.slb 2 4098048 45389 48000 90.29s | 2383872 36575 48000 65.18s +BGM_107.slb 2 2418688 37109 48000 65.18s | 3696640 28676 48000 128.91s +BGM_108.slb 2 3696640 28676 48000 128.91s | 2887680 35432 48000 81.50s +BGM_109.slb 4 2932736 35985 48000 81.50s | 112640 13377 48000 8.42s | 225280 14228 48000 15.83s | 235520 14795 48000 15.92s + +32 BGM banks; 28 are exactly two waves of equal duration diff --git a/docs/re/structures/bgm-two-stems.md b/docs/re/structures/bgm-two-stems.md new file mode 100644 index 0000000..e503543 --- /dev/null +++ b/docs/re/structures/bgm-two-stems.md @@ -0,0 +1,115 @@ +# 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**. diff --git a/tools/re-capture/bgm_wave_census.py b/tools/re-capture/bgm_wave_census.py new file mode 100755 index 0000000..76db133 --- /dev/null +++ b/tools/re-capture/bgm_wave_census.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 +"""Every BGM bank's sub-wave structure: count, bytes, PsuedoBytesPerSec, seconds. + +Static, disc-only. See docs/re/structures/bgm-two-stems.md.""" +import sys, struct +sys.path.insert(0,"/work/Syplheed-Reborn/tools/re-capture") +exec(open("/work/Syplheed-Reborn/tools/re-capture/slb_segment_phase.py").read().split("def cmd_phases")[0]) +pak=Pak("/work/sylph_extract/dat/sound.pak") +def bps(b, riff_at): + # PsuedoBytesPerSec at RIFF+0x20 (u32 LE), SampleRate at +0x24 + return struct.unpack_from("5s} per-wave (bytes, bytes/s, Hz, seconds)") +same=0 +for name,k,ds in rows: + txt=" | ".join(f"{d:8d} {pb:6d} {sr:5d} {sec:7.2f}s" for d,pb,sr,sec in ds) + eq = len(ds)==2 and abs(ds[0][3]-ds[1][3])<0.05 + same += eq + print(f"{name:14s} {k:5d} {txt} {'EQUAL-LENGTH' if eq else ''}") +print(f"\n{len(rows)} BGM banks; {same} are exactly two waves of equal duration")