dm2-graphics: console-family SFX missed entirely — 2-byte dtSnd header at fixed 5500 Hz (inline word bit 0x20) #187

Closed
opened 2026-07-05 07:47:54 +02:00 by jqueuniet · 1 comment
jqueuniet commented 2026-07-05 07:47:54 +02:00 (Migrated from codeberg.org)

Problem

Sfx::parse (crates/dm2-graphics/src/sfx.rs) requires a 6-byte header whose bytes [2..6] equal 08 01 00 00, and reads the sample rate from bytes [0..2]. That matches only the ports where bit 0x20 of the (0, 0, 0x0b, 0) inline feature word is set (DOS, Mac US, Amiga — 0x7b/0x7b/0x2b).

The engine dispatches on that bit ([SKPROJECT] v5/bgdat.cpp:946-966): when it is clear, dtSnd payloads have a 2-byte header (skipped, content unused) and play at a fixed rate of 0x157c = 5500 Hz. Mac JP / Sega CD / FM-Towns / PC-98x1 / IBM PS/V all have the bit clear, so iter_sfx yields zero SFX there — silently.

Evidence the console records are real PCM sound effects:

  • Unique dtSnd payload counts: Mac JP 66, Sega CD US 51, FM-Towns 47 (PC-9801 only 10 — see below).
  • Lag-1 autocorrelation of the bodies (signed interpretation): median 0.90 on Mac JP, 0.89 on Sega CD — the same profile as the confirmed DOS SFX (0.87). These are sampled audio, not tables or code.

Two documentation claims are refuted:

  • sfx.rs module doc: "DM2 stores 107 SFX inside graphics.dat on every port (DOS, Mac, Amiga, Sega CD). They are byte-identical across ports" — 107 holds only for DOS / Mac US / Amiga; the 2-byte-header ports have their own (smaller, non-identical) sets.
  • docs/formats/graphics-dat.md § "Sound effects (SFX)": "The console-family ports … do not carry SFX in graphics.dat … SFX on the console family live elsewhere on the disc — most likely inside SKUL.FTL" — they are right there in graphics.dat, cls3 = 0x02, 2-byte header, 5500 Hz.

Bonus decode of the "marker": the four bytes are not an opaque constant. DM2_47eb_00a4 (bgdat.cpp:851-883) treats byte 3 bit 0 as a "signed PCM, XOR 0x80 at load" latch and word [4:6] as the conversion-state field — which independently confirms the crate's signed→unsigned WAV conversion for the 6-byte-header ports.

Caveat

PC-9801's 10 dtSnd records do not look like PCM (lag-1 ≈ 0, tiny sizes) — likely FM/beeper data for its sound hardware; fine to keep those undecoded. FM-Towns bodies sit between (median 0.62, values centred on 0x80) and may use an unsigned or different convention; worth a listen before committing.

Fix direction

Key SFX discovery on the ENT1 record (cls3 = 0x02, as the engine does) plus the archive's inline-word bit 0x20 to pick the header form: bit set → current 6-byte parse; bit clear → 2-byte header, rate 5500 Hz, body from offset 2 (signed PCM, same XOR-0x80 WAV conversion — the engine's load-time conversion for these ports is latched in the allocation header, not the payload). Update iter_sfx, dm2 info SFX counts, dm2 extract, the module doc, and graphics-dat.md § SFX.

Probe: scratch/probe_console_sfx.py (gitignored); exports two Mac JP samples as scratch/macjp_snd_*.wav for listening.

## Problem `Sfx::parse` (`crates/dm2-graphics/src/sfx.rs`) requires a 6-byte header whose bytes `[2..6]` equal `08 01 00 00`, and reads the sample rate from bytes `[0..2]`. That matches only the ports where bit `0x20` of the `(0, 0, 0x0b, 0)` inline feature word is set (DOS, Mac US, Amiga — `0x7b`/`0x7b`/`0x2b`). The engine dispatches on that bit (`[SKPROJECT] v5/bgdat.cpp:946-966`): when it is **clear**, dtSnd payloads have a **2-byte header** (skipped, content unused) and play at a **fixed rate of 0x157c = 5500 Hz**. Mac JP / Sega CD / FM-Towns / PC-98x1 / IBM PS/V all have the bit clear, so `iter_sfx` yields **zero** SFX there — silently. Evidence the console records are real PCM sound effects: - Unique dtSnd payload counts: Mac JP 66, Sega CD US 51, FM-Towns 47 (PC-9801 only 10 — see below). - Lag-1 autocorrelation of the bodies (signed interpretation): median **0.90** on Mac JP, **0.89** on Sega CD — the same profile as the confirmed DOS SFX (0.87). These are sampled audio, not tables or code. Two documentation claims are refuted: - `sfx.rs` module doc: "DM2 stores 107 SFX inside `graphics.dat` on every port (DOS, Mac, Amiga, Sega CD). They are byte-identical across ports" — 107 holds only for DOS / Mac US / Amiga; the 2-byte-header ports have their own (smaller, non-identical) sets. - `docs/formats/graphics-dat.md` § "Sound effects (SFX)": "The console-family ports … do **not** carry SFX in `graphics.dat` … SFX on the console family live elsewhere on the disc — most likely inside `SKUL.FTL`" — they are right there in `graphics.dat`, cls3 = 0x02, 2-byte header, 5500 Hz. Bonus decode of the "marker": the four bytes are not an opaque constant. `DM2_47eb_00a4` (`bgdat.cpp:851-883`) treats byte 3 bit 0 as a "signed PCM, XOR 0x80 at load" latch and word `[4:6]` as the conversion-state field — which independently confirms the crate's signed→unsigned WAV conversion for the 6-byte-header ports. ## Caveat PC-9801's 10 dtSnd records do **not** look like PCM (lag-1 ≈ 0, tiny sizes) — likely FM/beeper data for its sound hardware; fine to keep those undecoded. FM-Towns bodies sit between (median 0.62, values centred on 0x80) and may use an unsigned or different convention; worth a listen before committing. ## Fix direction Key SFX discovery on the ENT1 record (`cls3 = 0x02`, as the engine does) plus the archive's inline-word bit 0x20 to pick the header form: bit set → current 6-byte parse; bit clear → 2-byte header, rate 5500 Hz, body from offset 2 (signed PCM, same XOR-0x80 WAV conversion — the engine's load-time conversion for these ports is latched in the allocation header, not the payload). Update `iter_sfx`, `dm2 info` SFX counts, `dm2 extract`, the module doc, and graphics-dat.md § SFX. Probe: `scratch/probe_console_sfx.py` (gitignored); exports two Mac JP samples as `scratch/macjp_snd_*.wav` for listening.
jqueuniet commented 2026-07-06 06:22:42 +02:00 (Migrated from codeberg.org)

Fixed by #208 (merged into main).

Fixed by #208 (merged into `main`).
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
jqueuniet/dm2-tools#187
No description provided.