#!/usr/bin/env python3 """Extract one wave out of a `.slb` bank as a standalone XMA1 RIFF. For banks with no internal delimiters -- `Static.slb`, which is a packed run of whole 2048-byte XMA packets -- a wave is defined only by (offset, packet count). Those come from the running game: launch Canary with `--xma_param_probe=true`, trigger the sound, and the log prints the stream's packet count and first 32 bytes; search those bytes in the bank to get the offset. See docs/re/menu-audio-cues.md. slb_extract_wave.py [channels] [rate] [out.riff] slb_extract_wave.py Static.slb 0x1ec0 4 # the d-pad cursor cue Decode with: ffmpeg -i out.riff out.wav """ import os, struct, sys from disc import disc_root sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) exec(open(os.path.join(os.path.dirname(os.path.abspath(__file__)), "slb_segment_phase.py")).read().split("def cmd_phases")[0]) XMA1_PACKET = 2048 def synth_fmt(channels, mask, rate): """The 32-byte XMAWAVEFORMAT ffmpeg's `xma1` decoder wants.""" f = b"fmt " + struct.pack(" 3 else 1 rate = int(a[4]) if len(a) > 4 else 48000 out = a[5] if len(a) > 5 else f"{name.split('.')[0]}_{off:#x}.riff" disc = disc_root() pak = Pak(os.path.join(disc, "dat", "sound.pak")) i = pak.find(name) if i is None: print(f"{name}: not in sound.pak"); return 2 b = pak.read(i) data = b[off:off + pkts * XMA1_PACKET] if len(data) < pkts * XMA1_PACKET: print(f"warning: short read -- {len(data)} of {pkts * XMA1_PACKET} bytes") open(out, "wb").write(riff(synth_fmt(ch, 1 if ch == 1 else 2, rate), data)) print(f"{out}: {len(data)} bytes, {pkts} packets, {ch}ch {rate}Hz") return 0 if __name__ == "__main__": raise SystemExit(main())