1use std::io::Read;
4
5use super::{
6 binary, Wav, WavEncodingCode, WavError, WavType, WavWaveMetadata, WavWrapperKind,
7 DATA_CHUNK_ID, FMT_CHUNK_ID, RIFF_MAGIC, SFX_HEADER_SIZE, WAVE_MAGIC,
8};
9
10#[cfg_attr(
17 feature = "tracing",
18 tracing::instrument(level = "debug", skip(reader))
19)]
20pub fn read_wav<R: Read>(reader: &mut R) -> Result<Wav, WavError> {
21 let mut bytes = Vec::new();
22 reader.read_to_end(&mut bytes)?;
23 read_wav_from_bytes(&bytes)
24}
25
26#[cfg_attr(
37 feature = "tracing",
38 tracing::instrument(level = "debug", skip(bytes), fields(bytes_len = bytes.len()))
39)]
40pub fn read_wav_from_bytes(bytes: &[u8]) -> Result<Wav, WavError> {
41 let file_len = u64::try_from(bytes.len()).expect("a slice's length fits in u64");
42 let kind = classify_wav(bytes, file_len).ok_or_else(|| {
43 WavError::InvalidHeader("no RIFF at the start of the file or at 0x1D6".into())
44 })?;
45
46 match kind {
47 WavWrapperKind::Standard => parse_wave_payload(bytes, WavType::Standard),
48 WavWrapperKind::SfxHeader => parse_wave_payload(&bytes[SFX_HEADER_SIZE..], WavType::Sfx),
49 WavWrapperKind::Mp3InWav => {
50 let mp3 = riff_chunk_end(bytes)
51 .and_then(|end| usize::try_from(end).ok())
52 .and_then(|end| bytes.get(end..))
53 .ok_or_else(|| {
54 WavError::InvalidHeader("MP3-in-WAV payload offset is unreadable".into())
55 })?;
56 Ok(Wav::new_mp3(WavType::Vo, mp3.to_vec()))
57 }
58 }
59}
60
61pub const WAV_CLASSIFY_LEN: usize = SFX_HEADER_SIZE + RIFF_MAGIC.len();
64
65pub fn classify_wav(head: &[u8], file_len: u64) -> Option<WavWrapperKind> {
87 let needed =
88 usize::try_from(file_len).map_or(WAV_CLASSIFY_LEN, |len| len.min(WAV_CLASSIFY_LEN));
89 debug_assert!(
90 head.len() >= needed,
91 "classify_wav needs the file's first {needed} bytes and was given {}",
92 head.len()
93 );
94 if head.starts_with(&RIFF_MAGIC) {
95 return Some(match riff_chunk_end(head) {
96 Some(end) if end < file_len => WavWrapperKind::Mp3InWav,
97 _ => WavWrapperKind::Standard,
98 });
99 }
100 let at_prefix_end = head.get(SFX_HEADER_SIZE..WAV_CLASSIFY_LEN)?;
103 (at_prefix_end == RIFF_MAGIC).then_some(WavWrapperKind::SfxHeader)
104}
105
106fn riff_chunk_end(bytes: &[u8]) -> Option<u64> {
109 binary::read_u32(bytes, 4)
110 .ok()
111 .map(|size| u64::from(size) + 8)
112}
113
114fn parse_wave_payload(bytes: &[u8], wav_type: WavType) -> Result<Wav, WavError> {
115 if bytes.len() < 12 {
116 return Err(WavError::InvalidHeader(
117 "WAVE payload shorter than 12-byte RIFF header".into(),
118 ));
119 }
120 if !bytes.starts_with(&RIFF_MAGIC) {
121 return Err(WavError::InvalidHeader(
122 "missing RIFF magic after wrapper normalization".into(),
123 ));
124 }
125 if bytes[8..12] != WAVE_MAGIC {
126 return Err(WavError::InvalidHeader(
127 "missing WAVE magic in RIFF header".into(),
128 ));
129 }
130
131 let mut cursor = 12usize;
132 let mut fmt_fields: Option<(WavEncodingCode, u16, u32, u32, u16, u16)> = None;
133 let mut data_chunk: Option<Vec<u8>> = None;
134
135 while cursor + 8 <= bytes.len() {
136 let chunk_id = [
137 bytes[cursor],
138 bytes[cursor + 1],
139 bytes[cursor + 2],
140 bytes[cursor + 3],
141 ];
142 let chunk_size_u32 = binary::read_u32(bytes, cursor + 4).map_err(|_| {
143 WavError::InvalidChunk(format!(
144 "unable to read chunk size at offset {}",
145 cursor + 4
146 ))
147 })?;
148 let chunk_size = usize::try_from(chunk_size_u32).map_err(|_| {
149 WavError::InvalidChunk(format!("chunk size {chunk_size_u32} does not fit in usize"))
150 })?;
151 cursor += 8;
152
153 let chunk_end = cursor.checked_add(chunk_size).ok_or_else(|| {
154 WavError::InvalidChunk(format!("chunk size overflow at chunk offset {cursor}"))
155 })?;
156 if chunk_end > bytes.len() {
157 return Err(WavError::InvalidChunk(format!(
158 "chunk at offset {} exceeds file bounds",
159 cursor - 8
160 )));
161 }
162 let chunk_data = &bytes[cursor..chunk_end];
163
164 if chunk_id == FMT_CHUNK_ID {
165 if chunk_data.len() < 16 {
166 return Err(WavError::InvalidChunk(
167 "`fmt ` chunk shorter than 16 bytes".into(),
168 ));
169 }
170
171 let encoding =
172 WavEncodingCode::from_raw(u16::from_le_bytes([chunk_data[0], chunk_data[1]]));
173 let channels = u16::from_le_bytes([chunk_data[2], chunk_data[3]]);
174 let sample_rate =
175 u32::from_le_bytes([chunk_data[4], chunk_data[5], chunk_data[6], chunk_data[7]]);
176 let bytes_per_sec =
177 u32::from_le_bytes([chunk_data[8], chunk_data[9], chunk_data[10], chunk_data[11]]);
178 let block_align = u16::from_le_bytes([chunk_data[12], chunk_data[13]]);
179 let bits_per_sample = u16::from_le_bytes([chunk_data[14], chunk_data[15]]);
180 fmt_fields = Some((
181 encoding,
182 channels,
183 sample_rate,
184 bytes_per_sec,
185 block_align,
186 bits_per_sample,
187 ));
188 } else if chunk_id == DATA_CHUNK_ID {
189 data_chunk = Some(chunk_data.to_vec());
190 break;
191 }
192
193 cursor = chunk_end;
194 if chunk_size % 2 == 1 && cursor < bytes.len() {
195 cursor += 1;
196 }
197 }
198
199 let (encoding, channels, sample_rate, bytes_per_sec, block_align, bits_per_sample) =
200 fmt_fields.ok_or_else(|| WavError::InvalidChunk("missing required `fmt ` chunk".into()))?;
201 let data =
202 data_chunk.ok_or_else(|| WavError::InvalidChunk("missing required `data` chunk".into()))?;
203
204 Ok(Wav::new_wave(
205 wav_type,
206 WavWaveMetadata {
207 encoding,
208 channels,
209 sample_rate,
210 bytes_per_sec,
211 block_align,
212 bits_per_sample,
213 },
214 data,
215 ))
216}
217
218#[cfg(test)]
219mod tests {
220 use super::*;
221 use crate::binary::{DecodeBinary, EncodeBinary};
222 use crate::wav::{
223 write_wav_to_vec, write_wav_to_vec_with_options, WavAudioFormat, WavEncoding, WavWriteMode,
224 WavWriteOptions, SFX_MAGIC,
225 };
226
227 fn sample_wave() -> Wav {
228 Wav::new_wave(
229 WavType::Standard,
230 WavWaveMetadata {
231 encoding: WavEncodingCode::from(WavEncoding::Pcm),
232 channels: 1,
233 sample_rate: 22_050,
234 bytes_per_sec: 44_100,
235 block_align: 2,
236 bits_per_sample: 16,
237 },
238 vec![1, 2, 3, 4, 5, 6],
239 )
240 }
241
242 #[test]
243 fn roundtrip_clean_wave_payload() {
244 let wav = sample_wave();
245 let bytes = write_wav_to_vec_with_options(
246 &wav,
247 WavWriteOptions {
248 mode: WavWriteMode::Clean,
249 },
250 )
251 .expect("write clean WAV");
252 let parsed = read_wav_from_bytes(&bytes).expect("read clean WAV");
253
254 assert_eq!(parsed.audio_format, WavAudioFormat::Wave);
255 assert_eq!(parsed.encoding.known(), Some(WavEncoding::Pcm));
256 assert_eq!(parsed.channels, 1);
257 assert_eq!(parsed.sample_rate, 22_050);
258 assert_eq!(parsed.data, wav.data);
259 assert_eq!(parsed.wav_type, WavType::Standard);
261 }
262
263 #[test]
264 fn writer_is_deterministic_for_synthetic_wave() {
265 let wav = sample_wave();
266 let first = write_wav_to_vec(&wav).expect("first write should succeed");
267 let second = write_wav_to_vec(&wav).expect("second write should succeed");
268 assert_eq!(first, second);
269 }
270
271 #[test]
272 fn roundtrip_game_sfx_wrapper() {
273 let mut wav = sample_wave();
274 wav.wav_type = WavType::Sfx;
275
276 let bytes = write_wav_to_vec(&wav).expect("write game WAV");
277 assert!(bytes.starts_with(&SFX_MAGIC));
278
279 let parsed = read_wav_from_bytes(&bytes).expect("read SFX WAV");
280 assert_eq!(parsed.wav_type, WavType::Sfx);
281 assert_eq!(parsed.audio_format, WavAudioFormat::Wave);
282 assert_eq!(parsed.data, wav.data);
283 }
284
285 #[test]
286 fn wrapped_sfx_is_detected_without_the_vanilla_prefix() {
287 let mut wav = sample_wave();
292 wav.wav_type = WavType::Sfx;
293 let mut bytes = write_wav_to_vec(&wav).expect("write game WAV");
294 bytes[0..4].copy_from_slice(b"\x00\x11\x22\x33");
295 assert!(!bytes.starts_with(&SFX_MAGIC));
296
297 let parsed = read_wav_from_bytes(&bytes).expect("read SFX WAV with a foreign prefix");
298 assert_eq!(parsed.wav_type, WavType::Sfx);
299 assert_eq!(parsed.data, wav.data);
300 }
301
302 #[test]
303 fn a_file_with_riff_nowhere_is_not_treated_as_wrapped() {
304 let bytes = vec![0u8; SFX_HEADER_SIZE + 64];
307 let err = read_wav_from_bytes(&bytes);
308 assert!(err.is_err(), "a file with no RIFF anywhere must not parse");
309 }
310
311 fn riff_then(riff_size: u32, tail: &[u8]) -> Vec<u8> {
313 let mut bytes = Vec::new();
314 bytes.extend_from_slice(&RIFF_MAGIC);
315 bytes.extend_from_slice(&riff_size.to_le_bytes());
316 bytes.extend_from_slice(&WAVE_MAGIC);
317 bytes.resize(usize::try_from(riff_size).expect("test size fits") + 8, 0);
318 bytes.extend_from_slice(tail);
319 bytes
320 }
321
322 fn classify(bytes: &[u8]) -> Option<WavWrapperKind> {
323 classify_wav(bytes, u64::try_from(bytes.len()).expect("test length fits"))
324 }
325
326 #[test]
327 fn mp3_in_wav_payload_starts_where_the_riff_chunk_ends() {
328 let mp3_payload = vec![0x49, 0x44, 0x33, 0x04, 0x00];
331 let wrapped = riff_then(36, &mp3_payload);
332
333 let parsed = read_wav_from_bytes(&wrapped).expect("read MP3-in-WAV");
334 assert_eq!(parsed.wav_type, WavType::Vo);
335 assert_eq!(parsed.audio_format, WavAudioFormat::Mp3);
336 assert_eq!(parsed.encoding.known(), Some(WavEncoding::Mp3));
337 assert_eq!(parsed.data, mp3_payload);
338 }
339
340 #[test]
341 fn classify_tells_the_three_forms_apart() {
342 let plain = write_wav_to_vec(&sample_wave()).expect("write plain");
343 let mut prefixed_wav = sample_wave();
344 prefixed_wav.wav_type = WavType::Sfx;
345 let prefixed = write_wav_to_vec(&prefixed_wav).expect("write prefixed");
346 let mp3 = riff_then(50, &[0xFF, 0xFB]);
347
348 assert_eq!(classify(&plain), Some(WavWrapperKind::Standard));
349 assert_eq!(classify(&prefixed), Some(WavWrapperKind::SfxHeader));
350 assert_eq!(classify(&mp3), Some(WavWrapperKind::Mp3InWav));
351 assert_eq!(classify(&[0_u8; WAV_CLASSIFY_LEN]), None);
352 }
353
354 #[test]
355 fn a_riff_chunk_reaching_past_the_file_is_plain() {
356 let mut truncated = riff_then(64, &[]);
359 truncated.truncate(40);
360 assert_eq!(classify(&truncated), Some(WavWrapperKind::Standard));
361 }
362
363 #[test]
364 fn a_second_riff_at_byte_twenty_is_mp3_in_wav() {
365 let inner = write_wav_to_vec(&sample_wave()).expect("write plain");
368 assert_eq!(
369 classify(&riff_then(12, &inner)),
370 Some(WavWrapperKind::Mp3InWav)
371 );
372 }
373
374 #[test]
375 #[cfg(debug_assertions)]
376 #[should_panic(expected = "classify_wav needs the file's first")]
377 fn classify_refuses_a_head_shorter_than_its_prefix() {
378 let _ = classify_wav(&[0_u8; 16], 4096);
379 }
380
381 #[test]
382 fn classify_reads_no_further_than_its_prefix() {
383 let mut prefixed_wav = sample_wave();
384 prefixed_wav.wav_type = WavType::Sfx;
385 let mut prefixed = write_wav_to_vec(&prefixed_wav).expect("write prefixed");
386 prefixed.extend_from_slice(&[0_u8; 1024]);
387 let mp3 = riff_then(50, &[0xFF; 1024]);
388
389 for bytes in [prefixed, mp3] {
390 let len = u64::try_from(bytes.len()).expect("test length fits");
391 assert_eq!(
392 classify_wav(&bytes[..WAV_CLASSIFY_LEN], len),
393 classify_wav(&bytes, len)
394 );
395 }
396 }
397
398 #[test]
399 fn clean_mode_mp3_writes_raw_payload() {
400 let wav = Wav::new_mp3(WavType::Vo, vec![0x01, 0x02, 0x03]);
401 let bytes = write_wav_to_vec_with_options(
402 &wav,
403 WavWriteOptions {
404 mode: WavWriteMode::Clean,
405 },
406 )
407 .expect("write clean MP3");
408 assert_eq!(bytes, vec![0x01, 0x02, 0x03]);
409 }
410
411 #[test]
412 fn rejects_non_riff_wave_payload() {
413 let err = read_wav_from_bytes(b"not-riff").expect_err("must fail");
414 assert!(matches!(err, WavError::InvalidHeader(_)));
415 }
416
417 #[test]
418 fn rejects_truncated_header() {
419 let err = read_wav_from_bytes(&RIFF_MAGIC).expect_err("must fail");
420 assert!(matches!(err, WavError::InvalidHeader(_)));
421 }
422
423 #[test]
424 fn rejects_missing_fmt_chunk() {
425 let mut bytes = Vec::new();
426 bytes.extend_from_slice(&RIFF_MAGIC);
427 bytes.extend_from_slice(&(12_u32 + 8 + 4).to_le_bytes());
428 bytes.extend_from_slice(&WAVE_MAGIC);
429 bytes.extend_from_slice(&DATA_CHUNK_ID);
430 bytes.extend_from_slice(&(4_u32).to_le_bytes());
431 bytes.extend_from_slice(&[0, 1, 2, 3]);
432
433 let err = read_wav_from_bytes(&bytes).expect_err("must fail");
434 assert!(matches!(err, WavError::InvalidChunk(_)));
435 }
436
437 #[test]
438 fn rejects_truncated_chunk_payload() {
439 let mut bytes = Vec::new();
440 bytes.extend_from_slice(&RIFF_MAGIC);
441 bytes.extend_from_slice(&(12_u32 + 8 + 16).to_le_bytes());
442 bytes.extend_from_slice(&WAVE_MAGIC);
443 bytes.extend_from_slice(&FMT_CHUNK_ID);
444 bytes.extend_from_slice(&(16_u32).to_le_bytes());
445 bytes.extend_from_slice(&[1, 0, 1, 0]); let err = read_wav_from_bytes(&bytes).expect_err("must fail");
448 assert!(matches!(err, WavError::InvalidChunk(_)));
449 }
450
451 #[test]
452 fn decode_encode_traits_roundtrip() {
453 let mut wav = sample_wave();
454 wav.wav_type = WavType::Sfx;
455
456 let bytes = wav.encode_binary().expect("encode");
457 let decoded = Wav::decode_binary(&bytes).expect("decode");
458
459 assert_eq!(decoded.wav_type, WavType::Sfx);
460 assert_eq!(decoded.audio_format, WavAudioFormat::Wave);
461 assert_eq!(decoded.data, wav.data);
462 }
463}