rakata_formats/wav/
pcm.rs

1//! PCM decoding helpers for WAV data.
2//!
3//! This module provides utilities to decode raw PCM sample data from
4//! [`super::Wav`] payloads into normalized floating-point samples.
5
6use super::{Wav, WavEncoding};
7use thiserror::Error;
8
9/// Errors that can occur during PCM decoding.
10#[derive(Debug, Error)]
11pub enum PcmError {
12    /// The WAV data is not in a supported PCM format.
13    #[error("unsupported encoding: {0:?}")]
14    UnsupportedEncoding(Option<WavEncoding>),
15
16    /// The bit depth is not supported for the given encoding.
17    #[error("unsupported bit depth: {0}")]
18    UnsupportedBitDepth(u16),
19
20    /// The data length is not aligned with the block size.
21    #[error("data length {0} is not a multiple of block alignment {1}")]
22    MisalignedData(usize, usize),
23}
24
25/// Decodes the audio data into a vector of normalized `f32` samples.
26///
27/// Samples are interleaved (e.g., L, R, L, R for stereo).
28/// Normalized samples are in the range `[-1.0, 1.0]`.
29pub fn decode_pcm_as_float(wav: &Wav) -> Result<Vec<f32>, PcmError> {
30    let encoding = wav.encoding.known();
31
32    if encoding != Some(WavEncoding::Pcm) {
33        return Err(PcmError::UnsupportedEncoding(encoding));
34    }
35
36    let channels = usize::from(wav.channels);
37    if channels == 0 {
38        return Ok(Vec::new());
39    }
40
41    let bits = wav.bits_per_sample;
42    let bytes_per_sample = usize::from(bits / 8);
43    let block_align = channels * bytes_per_sample;
44
45    if !wav.data.len().is_multiple_of(block_align) {
46        return Err(PcmError::MisalignedData(wav.data.len(), block_align));
47    }
48
49    let num_samples = wav.data.len() / bytes_per_sample;
50    let mut samples = Vec::with_capacity(num_samples);
51
52    match bits {
53        8 => {
54            // 8-bit PCM is unsigned: 0..255, silence at 128.
55            // Range: [0, 255] -> [-1.0, 1.0]
56            for &byte in &wav.data {
57                let sample = (f32::from(byte) - 128.0) / 128.0;
58                samples.push(sample);
59            }
60        }
61        16 => {
62            // 16-bit PCM is signed little-endian: -32768..32767.
63            // Range: [-32768, 32767] -> [-1.0, 1.0]
64            for chunk in wav.data.chunks_exact(2) {
65                let sample_i16 = i16::from_le_bytes([chunk[0], chunk[1]]);
66                let sample = f32::from(sample_i16) / 32768.0;
67                samples.push(sample);
68            }
69        }
70        _ => return Err(PcmError::UnsupportedBitDepth(bits)),
71    }
72
73    Ok(samples)
74}
75
76#[cfg(test)]
77mod tests {
78    use super::*;
79    use crate::wav::{WavEncodingCode, WavType, WavWaveMetadata};
80
81    fn make_wav(bits: u16, data: Vec<u8>) -> Wav {
82        Wav::new_wave(
83            WavType::Vo,
84            WavWaveMetadata {
85                encoding: WavEncodingCode::from(WavEncoding::Pcm),
86                channels: 1,
87                sample_rate: 44100,
88                bytes_per_sec: 44100 * (u32::from(bits) / 8),
89                block_align: bits / 8,
90                bits_per_sample: bits,
91            },
92            data,
93        )
94    }
95
96    #[test]
97    fn test_decode_8bit_pcm() {
98        // 128 is silence (0.0), 0 is -1.0, 255 is approx 1.0
99        let data = vec![128, 0, 255];
100        let wav = make_wav(8, data);
101        let samples = decode_pcm_as_float(&wav).unwrap();
102
103        assert_eq!(samples.len(), 3);
104        assert!((samples[0] - 0.0).abs() < 1e-5);
105        assert!((samples[1] - -1.0).abs() < 1e-5);
106        assert!((samples[2] - 0.9921875).abs() < 1e-5);
107    }
108
109    #[test]
110    fn test_decode_16bit_pcm() {
111        // 0 is silence, i16::MIN is -1.0, i16::MAX is approx 1.0
112        let data = vec![
113            0x00, 0x00, // 0
114            0x00, 0x80, // -32768 (i16::MIN)
115            0xFF, 0x7F, // 32767 (i16::MAX)
116        ];
117        let wav = make_wav(16, data);
118        let samples = decode_pcm_as_float(&wav).unwrap();
119
120        assert_eq!(samples.len(), 3);
121        assert!((samples[0] - 0.0).abs() < 1e-5);
122        assert!((samples[1] - -1.0).abs() < 1e-5);
123        assert!((samples[2] - 0.9999695).abs() < 1e-5);
124    }
125
126    #[test]
127    fn test_unsupported_encoding() {
128        let mut wav = make_wav(16, vec![]);
129        wav.encoding = WavEncodingCode::from(WavEncoding::Mp3); // Not PCM
130        let err = decode_pcm_as_float(&wav).unwrap_err();
131        assert!(matches!(err, PcmError::UnsupportedEncoding(_)));
132    }
133}