rakata_formats/wav/
writer.rs

1//! WAV binary writer.
2
3use std::io::Write;
4
5use super::{
6    Wav, WavAudioFormat, WavError, WavType, WavWriteMode, WavWriteOptions, DATA_CHUNK_ID,
7    FMT_CHUNK_ID, RIFF_MAGIC, SFX_HEADER_SIZE, SFX_MAGIC, VO_HEADER_SIZE, WAVE_MAGIC,
8};
9
10/// Writes WAV data to a writer using [`WavWriteMode::Game`].
11#[cfg_attr(
12    feature = "tracing",
13    tracing::instrument(level = "debug", skip(writer, wav))
14)]
15pub fn write_wav<W: Write>(writer: &mut W, wav: &Wav) -> Result<(), WavError> {
16    write_wav_with_options(writer, wav, WavWriteOptions::default())
17}
18
19/// Writes WAV data to a writer with explicit options.
20#[cfg_attr(
21    feature = "tracing",
22    tracing::instrument(level = "debug", skip(writer, wav, options))
23)]
24pub fn write_wav_with_options<W: Write>(
25    writer: &mut W,
26    wav: &Wav,
27    options: WavWriteOptions,
28) -> Result<(), WavError> {
29    let clean_payload = match wav.audio_format {
30        WavAudioFormat::Wave => build_clean_wave_payload(wav)?,
31        WavAudioFormat::Mp3 => wav.data.clone(),
32    };
33
34    let out = match options.mode {
35        WavWriteMode::Clean => clean_payload,
36        WavWriteMode::Game => obfuscate_for_game(&clean_payload, wav.wav_type),
37    };
38    writer.write_all(&out)?;
39    Ok(())
40}
41
42/// Serializes WAV data to a byte vector using [`WavWriteMode::Game`].
43#[cfg_attr(feature = "tracing", tracing::instrument(level = "debug", skip(wav)))]
44pub fn write_wav_to_vec(wav: &Wav) -> Result<Vec<u8>, WavError> {
45    write_wav_to_vec_with_options(wav, WavWriteOptions::default())
46}
47
48/// Serializes WAV data to a byte vector with explicit options.
49#[cfg_attr(
50    feature = "tracing",
51    tracing::instrument(level = "debug", skip(wav, options))
52)]
53pub fn write_wav_to_vec_with_options(
54    wav: &Wav,
55    options: WavWriteOptions,
56) -> Result<Vec<u8>, WavError> {
57    let mut out = Vec::new();
58    write_wav_with_options(&mut out, wav, options)?;
59    Ok(out)
60}
61
62fn build_clean_wave_payload(wav: &Wav) -> Result<Vec<u8>, WavError> {
63    let data_size = u32::try_from(wav.data.len())
64        .map_err(|_| WavError::InvalidChunk("audio payload exceeds 4GiB RIFF limit".into()))?;
65
66    let block_align = if wav.block_align != 0 {
67        wav.block_align
68    } else {
69        default_block_align(wav.channels, wav.bits_per_sample)?
70    };
71    let bytes_per_sec = if wav.bytes_per_sec != 0 {
72        wav.bytes_per_sec
73    } else {
74        wav.sample_rate
75            .checked_mul(u32::from(block_align))
76            .ok_or_else(|| {
77                WavError::InvalidChunk("sample_rate * block_align overflows u32".into())
78            })?
79    };
80
81    let fmt_chunk_size: u32 = 16;
82    let riff_size = 4_u32
83        .checked_add(8)
84        .and_then(|value| value.checked_add(fmt_chunk_size))
85        .and_then(|value| value.checked_add(8))
86        .and_then(|value| value.checked_add(data_size))
87        .ok_or_else(|| WavError::InvalidChunk("RIFF size overflows u32".into()))?;
88
89    let mut out = Vec::with_capacity(
90        usize::try_from(8_u64 + u64::from(riff_size))
91            .unwrap_or(0)
92            .max(usize::from(44_u16)),
93    );
94    out.extend_from_slice(&RIFF_MAGIC);
95    out.extend_from_slice(&riff_size.to_le_bytes());
96    out.extend_from_slice(&WAVE_MAGIC);
97    out.extend_from_slice(&FMT_CHUNK_ID);
98    out.extend_from_slice(&fmt_chunk_size.to_le_bytes());
99    out.extend_from_slice(&wav.encoding.raw().to_le_bytes());
100    out.extend_from_slice(&wav.channels.to_le_bytes());
101    out.extend_from_slice(&wav.sample_rate.to_le_bytes());
102    out.extend_from_slice(&bytes_per_sec.to_le_bytes());
103    out.extend_from_slice(&block_align.to_le_bytes());
104    out.extend_from_slice(&wav.bits_per_sample.to_le_bytes());
105    out.extend_from_slice(&DATA_CHUNK_ID);
106    out.extend_from_slice(&data_size.to_le_bytes());
107    out.extend_from_slice(&wav.data);
108
109    Ok(out)
110}
111
112fn default_block_align(channels: u16, bits_per_sample: u16) -> Result<u16, WavError> {
113    let bytes_per_sample = bits_per_sample / 8;
114    channels
115        .checked_mul(bytes_per_sample)
116        .ok_or_else(|| WavError::InvalidChunk("channels * bytes_per_sample overflows u16".into()))
117}
118
119fn obfuscate_for_game(clean_payload: &[u8], wav_type: WavType) -> Vec<u8> {
120    // TODO(wav-decode): Keep codec-level decode/resample helpers out of this container module;
121    // add optional PCM/ADPCM decoding in higher-level playback/tooling APIs.
122    match wav_type {
123        WavType::Standard => clean_payload.to_vec(),
124        WavType::Sfx => {
125            let mut out = Vec::with_capacity(SFX_HEADER_SIZE + clean_payload.len());
126            out.resize(SFX_HEADER_SIZE, 0);
127            out[0..4].copy_from_slice(&SFX_MAGIC);
128            out.extend_from_slice(clean_payload);
129            out
130        }
131        WavType::Vo => {
132            let mut out = Vec::with_capacity(VO_HEADER_SIZE + clean_payload.len());
133            out.resize(VO_HEADER_SIZE, 0);
134            out[0..4].copy_from_slice(&RIFF_MAGIC);
135            out.extend_from_slice(clean_payload);
136            out
137        }
138    }
139}