feat: CD-DA (Red Book) audio → WAV extraction for console-family ports #279

Merged
jqueuniet merged 0 commits from refs/pull/279/head into main 2026-07-20 14:57:26 +02:00
jqueuniet commented 2026-07-20 14:52:07 +02:00 (Migrated from codeberg.org)

Summary

Adds Red Book CD-DA (audio-track) → WAV extraction for the console-family
ports, closing the last per-port audio gap in Phase 3. The console editions
(Sega CD, Mac JP, FM-Towns, PC-9821) ship their soundtrack as raw CD audio
tracks — headerless 44.1 kHz / 16-bit / stereo PCM — rather than in
graphics.dat. Conversion is a passthrough (reinterpret the bytes as
interleaved i16 in the correct byte order and wrap in a WAV header); the one
non-trivial part is sample byte order, which is auto-detected.

What landed

  • dm2-wavpcm16_stereo_to_wav convenience (mirrors the existing mono
    helpers; the streaming stereo writer already existed for the OPL3 render).
  • dm2-music::cdda — new module: ByteOrder { Le, Be }, to_wav(raw, order) (trims any partial trailing frame, delegates to dm2-wav), and a
    detect_byte_order / byte_order_stats heuristic. The heuristic compares
    the lag-1 absolute-difference energy of both interpretations over a bounded
    probe window — real audio is far smoother sample-to-sample than its
    byte-swapped noise — and picks the lower one (ties/degenerate input → Le).
  • dm2-clidm2 convert --cdda [--byte-order auto|le|be] [--all] [--jobs N]. Raw CD-DA is headerless (no magic to sniff), so it takes an
    explicit --cdda path that bypasses the format-detection dispatch. Single
    file, or --all over a directory of track*.bin in parallel.
  • Tests — unit tests for WAV wrapping / passthrough / byte-swap / trim and
    for the detector on synthetic signals; skip-on-missing fixture + CLI
    integration tests over the staged audio.
  • Docsmusic.md (converter + byte-order handling, impl-order item #9
    flipped to done), sega-cd-us.md / mac-jp-cd.md (audio-track sections and
    extraction recipes), and ROADMAP.md (Phase 3 status).

Byte order — determined empirically

Byte order is a property of how a rip was extracted, not of the port. Running
the detector against the staged fixtures shows all four ports are
little-endian
(FM-Towns, PC-9821, Sega CD US, Mac JP), each with a decisive
230×–367× energy margin. Notably the Sega CD US Redump .bin (split with
bchunk, no -s) is standard LE — not the classic Sega/Redump byte-swap
case — so auto-detect defaults to LE and --byte-order remains the escape
hatch for any future rip that is swapped.

Scope / non-goals

Deliberately out of scope for this pass: routing integration (the
console-family CD.DAT music_id → audio-track mapping is empirical and not
encoded in any shipped file — still an open question), in-tool CHD/cue
splitting (the tool takes already-split per-track .bin), and lossy output.

The audio-track .bin fixtures are gitignored (large, copyrighted), so the
fixture-gated tests skip cleanly where they are absent.

## Summary Adds Red Book CD-DA (audio-track) → WAV extraction for the console-family ports, closing the last per-port audio gap in Phase 3. The console editions (Sega CD, Mac JP, FM-Towns, PC-9821) ship their soundtrack as raw CD audio tracks — headerless 44.1 kHz / 16-bit / stereo PCM — rather than in `graphics.dat`. Conversion is a passthrough (reinterpret the bytes as interleaved `i16` in the correct byte order and wrap in a WAV header); the one non-trivial part is sample byte order, which is auto-detected. ## What landed - **`dm2-wav`** — `pcm16_stereo_to_wav` convenience (mirrors the existing mono helpers; the streaming stereo writer already existed for the OPL3 render). - **`dm2-music::cdda`** — new module: `ByteOrder { Le, Be }`, `to_wav(raw, order)` (trims any partial trailing frame, delegates to `dm2-wav`), and a `detect_byte_order` / `byte_order_stats` heuristic. The heuristic compares the lag-1 absolute-difference energy of both interpretations over a bounded probe window — real audio is far smoother sample-to-sample than its byte-swapped noise — and picks the lower one (ties/degenerate input → `Le`). - **`dm2-cli`** — `dm2 convert --cdda [--byte-order auto|le|be] [--all] [--jobs N]`. Raw CD-DA is headerless (no magic to sniff), so it takes an explicit `--cdda` path that bypasses the format-detection dispatch. Single file, or `--all` over a directory of `track*.bin` in parallel. - **Tests** — unit tests for WAV wrapping / passthrough / byte-swap / trim and for the detector on synthetic signals; skip-on-missing fixture + CLI integration tests over the staged audio. - **Docs** — `music.md` (converter + byte-order handling, impl-order item #9 flipped to done), `sega-cd-us.md` / `mac-jp-cd.md` (audio-track sections and extraction recipes), and `ROADMAP.md` (Phase 3 status). ## Byte order — determined empirically Byte order is a property of how a rip was extracted, not of the port. Running the detector against the staged fixtures shows **all four ports are little-endian** (FM-Towns, PC-9821, Sega CD US, Mac JP), each with a decisive 230×–367× energy margin. Notably the Sega CD US Redump `.bin` (split with `bchunk`, no `-s`) is standard LE — not the classic Sega/Redump byte-swap case — so auto-detect defaults to LE and `--byte-order` remains the escape hatch for any future rip that is swapped. ## Scope / non-goals Deliberately out of scope for this pass: routing integration (the console-family `CD.DAT` `music_id` → audio-track mapping is empirical and not encoded in any shipped file — still an open question), in-tool CHD/cue splitting (the tool takes already-split per-track `.bin`), and lossy output. The audio-track `.bin` fixtures are gitignored (large, copyrighted), so the fixture-gated tests skip cleanly where they are absent.
Sign in to join this conversation.
No reviewers
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!279
No description provided.