Skip to main content

midas_audio/codecs/
adpcm.rs

1use crate::codecs::tables::{ADPCM_PRED_DIFF_TABLE, ADPCM_STEP_SIZE_ADJ, ADPCM_STEP_SIZES};
2use std::fmt;
3
4#[derive(Debug, Clone, Copy, PartialEq)]
5pub enum AdpcmError {
6    InvalidMagic,
7    BufferTooSmall { expected: u16, actual: u16 },
8    ChecksumMismatch { expected: u8, actual: u8 },
9    ChannelMismatch { expected: u16, actual: u16 },
10    UnsupportedSampleType(u8),
11}
12
13impl fmt::Display for AdpcmError {
14    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
15        match self {
16            AdpcmError::InvalidMagic => write!(f, "Invalid ADP magic numbers"),
17            AdpcmError::BufferTooSmall { expected, actual } => {
18                write!(
19                    f,
20                    "Input buffer too small. Expected {expected}, got {actual}",
21                )
22            }
23            AdpcmError::ChecksumMismatch { expected, actual } => {
24                write!(
25                    f,
26                    "ADP header checksum mismatch. Expected {expected}, got {actual}",
27                )
28            }
29            AdpcmError::ChannelMismatch { expected, actual } => {
30                write!(
31                    f,
32                    "ADP channel mismatch. Header reports {actual}, state expects {expected}",
33                )
34            }
35            AdpcmError::UnsupportedSampleType(t) => write!(f, "Unsupported ADP sample type: {t}"),
36        }
37    }
38}
39
40impl std::error::Error for AdpcmError {}
41
42/// Defines the encoding format and channel layout for audio data.
43#[repr(u8)]
44#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45pub enum SampleType {
46    /// No sample type specified or invalid type.
47    None = 0,
48    /// 8-bit Unsigned PCM (Mono).
49    /// Samples are stored as single bytes with an offset of 128 (0 is -128, 128 is 0, 255 is 127).
50    PcmMono8 = 1,
51    /// 16-bit Signed PCM (Mono).
52    /// Samples are stored as two-byte little-endian integers.
53    PcmMono16 = 2,
54    /// 8-bit Unsigned PCM (Stereo).
55    /// Samples alternate between Left and Right channels, each as a single byte offset by 128.
56    PcmStereo8 = 3,
57    /// 16-bit Signed PCM (Stereo).
58    /// Samples alternate between Left and Right channels, each as two-byte little-endian integers.
59    PcmStereo16 = 4,
60    /// 4-bit ADPCM (Mono).
61    /// Adaptive Differential Pulse Code Modulation. Stores the difference between samples
62    /// using 4 bits per sample, packed two samples per byte.
63    AdpcmMono = 5,
64    /// 4-bit ADPCM (Stereo).
65    /// Adaptive Differential Pulse Code Modulation for two channels. Stores differences
66    /// using 4 bits per sample (1 byte contains one L and one R sample nibble).
67    AdpcmStereo = 6,
68}
69
70impl From<u8> for SampleType {
71    fn from(v: u8) -> Self {
72        match v {
73            1 => SampleType::PcmMono8,
74            2 => SampleType::PcmMono16,
75            3 => SampleType::PcmStereo8,
76            4 => SampleType::PcmStereo16,
77            5 => SampleType::AdpcmMono,
78            6 => SampleType::AdpcmStereo,
79            _ => SampleType::None,
80        }
81    }
82}
83
84/// Holds the persistent state for an audio channel during ADPCM encoding/decoding.
85#[derive(Debug, Clone, Copy, PartialEq, Default)]
86pub struct AdpcmChannelState {
87    /// The predicted 16-bit sample value.
88    pub pred_value: i32,
89    /// The index into the step size table (0-88).
90    pub step_size_index: i32,
91}
92
93use crate::types::Channels;
94
95/// Manages the state for ADPCM and PCM encoding/decoding within a stream.
96///
97/// This state handles the framing, headers, and adaptive predictor values for ADPCM.
98/// Although it is named `AdpcmState`, it supports all formats defined in [`SampleType`],
99/// including 8-bit and 16-bit PCM.
100pub struct AdpcmState {
101    pub channel_layout: Channels,
102    pub frame_length: usize,
103    pub channels: Vec<AdpcmChannelState>,
104    pub frame_bytes: usize,
105    pub header_bytes: usize,
106    pub header_size: usize,
107    pub encoding_type: SampleType,
108    pub sample_rate: u32,
109    // We handle header buffer logic slightly differently in Rust to avoid raw pointer casting,
110    // but the concept remains: we need to enforce frame alignment.
111    pub current_header: Option<Vec<u8>>,
112    pub low_nibble: bool,
113    pub pending_byte: u8,
114    pub current_channel: usize,
115}
116
117impl AdpcmState {
118    #[must_use]
119    pub fn new(channel_layout: Channels, frame_length: usize) -> Self {
120        let channels = vec![AdpcmChannelState::default(); channel_layout.count()];
121        let encoding_type = match channel_layout {
122            Channels::Stereo => SampleType::AdpcmStereo,
123            Channels::Mono => SampleType::AdpcmMono,
124        };
125        Self {
126            channel_layout,
127            frame_length,
128            channels,
129            frame_bytes: 0,
130            header_bytes: 0,
131            header_size: 0,
132            current_header: None,
133            low_nibble: false,
134            pending_byte: 0,
135            current_channel: 0,
136            encoding_type,
137            sample_rate: 22050,
138        }
139    }
140
141    #[must_use]
142    pub fn with_type(
143        channel_layout: Channels,
144        frame_length: usize,
145        encoding_type: SampleType,
146        sample_rate: u32,
147    ) -> Self {
148        let channels: Vec<AdpcmChannelState> =
149            vec![AdpcmChannelState::default(); channel_layout.count()];
150        Self {
151            channel_layout,
152            frame_length,
153            channels,
154            frame_bytes: 0,
155            header_bytes: 0,
156            header_size: 0,
157            current_header: None,
158            low_nibble: false,
159            pending_byte: 0,
160            current_channel: 0,
161            encoding_type,
162            sample_rate,
163        }
164    }
165
166    #[must_use]
167    pub fn get_header_size(channel_layout: Channels) -> usize {
168        6 + channel_layout.count() * 3
169    }
170
171    pub fn encode(&mut self, input: &[i16], output: &mut [u8]) -> (usize, usize) {
172        let mut input_used = 0;
173        let mut output_written = 0;
174        let mut input_idx = 0;
175        let mut output_idx = 0;
176
177        while output_idx < output.len() && input_idx < input.len() {
178            // Header writing logic
179            if self.frame_bytes == 0 {
180                if self.current_header.is_none() {
181                    // Build generic header (6 bytes)
182                    let mut header = Vec::new();
183                    header.push(0x17);
184                    header.push(0x42);
185                    header.extend_from_slice(&(self.frame_length as u16).to_le_bytes());
186                    header.push(0); // sum placeholder (idx 4)
187                    header.push(self.encoding_type as u8); // type (idx 5)
188
189                    // Channel headers (3 bytes each)
190                    for i in 0..self.channel_layout.count() {
191                        header
192                            .extend_from_slice(&(self.channels[i].pred_value as i16).to_le_bytes());
193                        header.push(self.channels[i].step_size_index as u8);
194                    }
195
196                    // Calculate checksum over entire header
197                    let mut sum: u8 = 0;
198                    for (i, b) in header.iter().enumerate() {
199                        if i != 4 {
200                            sum = sum.wrapping_add(*b);
201                        }
202                    }
203                    header[4] = sum;
204
205                    self.current_header = Some(header);
206                    self.header_bytes = self.current_header.as_ref().unwrap().len();
207                }
208
209                // Write header to output
210                if let Some(ref header) = self.current_header {
211                    let offset = header.len() - self.header_bytes;
212                    let available = output.len() - output_idx;
213                    let to_write = std::cmp::min(self.header_bytes, available);
214
215                    output[output_idx..output_idx + to_write]
216                        .copy_from_slice(&header[offset..offset + to_write]);
217
218                    output_idx += to_write;
219                    output_written += to_write;
220                    self.header_bytes -= to_write;
221
222                    if self.header_bytes == 0 {
223                        self.current_header = None;
224                        self.frame_bytes =
225                            self.frame_length - Self::get_header_size(self.channel_layout);
226                        self.low_nibble = false;
227                        self.pending_byte = 0;
228                        self.current_channel = 0;
229                    }
230                }
231
232                if output_idx >= output.len() {
233                    break;
234                }
235            }
236
237            // Encoding logic
238            let ch = self.current_channel;
239            let sample = input[input_idx];
240
241            // Check whether the decoder can proceed with this sample
242            let can_fit = match self.encoding_type {
243                SampleType::AdpcmMono | SampleType::AdpcmStereo => {
244                    self.low_nibble || output_idx < output.len()
245                }
246                SampleType::PcmMono16 | SampleType::PcmStereo16 => output_idx + 1 < output.len(),
247                SampleType::PcmMono8 | SampleType::PcmStereo8 => output_idx < output.len(),
248                _ => true,
249            };
250
251            if !can_fit {
252                break;
253            }
254
255            // Consume sample
256            input_idx += 1;
257            input_used += 2;
258
259            match self.encoding_type {
260                SampleType::AdpcmMono | SampleType::AdpcmStereo => {
261                    let chan = &mut self.channels[ch];
262
263                    let mut diff = i32::from(sample) - chan.pred_value;
264                    let mut qdiff = 0;
265
266                    if diff < 0 {
267                        qdiff = 8;
268                        diff = -diff;
269                    }
270
271                    let mut step_size = ADPCM_STEP_SIZES[chan.step_size_index as usize];
272                    let mut pred_diff = step_size / 8;
273
274                    if diff >= step_size {
275                        qdiff |= 4;
276                        diff -= step_size;
277                        pred_diff += step_size;
278                    }
279                    step_size /= 2;
280                    if diff >= step_size {
281                        qdiff |= 2;
282                        diff -= step_size;
283                        pred_diff += step_size;
284                    }
285                    step_size /= 2;
286                    if diff >= step_size {
287                        qdiff |= 1;
288                        pred_diff += step_size;
289                    }
290
291                    if (qdiff & 8) != 0 {
292                        chan.pred_value -= pred_diff;
293                    } else {
294                        chan.pred_value += pred_diff;
295                    }
296
297                    // Clip
298                    chan.pred_value = chan.pred_value.clamp(-32768, 32767);
299
300                    chan.step_size_index += ADPCM_STEP_SIZE_ADJ[(qdiff & 7) as usize];
301                    chan.step_size_index = chan.step_size_index.clamp(0, 88);
302
303                    if self.low_nibble {
304                        output[output_idx] = self.pending_byte | (qdiff as u8);
305                        output_idx += 1;
306                        output_written += 1;
307                        self.frame_bytes -= 1;
308                        self.low_nibble = false;
309                    } else {
310                        self.pending_byte = (qdiff as u8) << 4;
311                        self.low_nibble = true;
312                    }
313                }
314                SampleType::PcmMono16 | SampleType::PcmStereo16 => {
315                    let bytes = sample.to_le_bytes();
316                    output[output_idx] = bytes[0];
317                    output[output_idx + 1] = bytes[1];
318                    output_idx += 2;
319                    output_written += 2;
320                    self.frame_bytes -= 2;
321                }
322                SampleType::PcmMono8 | SampleType::PcmStereo8 => {
323                    // Convert i16 to u8 (offset 128)
324                    let unsigned = ((i32::from(sample) + 32768) >> 8) as u8;
325                    output[output_idx] = unsigned;
326                    output_idx += 1;
327                    output_written += 1;
328                    self.frame_bytes -= 1;
329                }
330                _ => {}
331            }
332
333            // Next channel
334            self.current_channel += 1;
335            if self.current_channel >= self.channel_layout.count() {
336                self.current_channel = 0;
337            }
338        }
339
340        (input_used, output_written)
341    }
342
343    pub fn decode(
344        &mut self,
345        input: &[u8],
346        output: &mut [i16],
347    ) -> Result<(usize, usize), AdpcmError> {
348        let mut input_used = 0;
349        let mut output_written = 0;
350        let mut input_idx = 0;
351        let mut output_idx = 0;
352
353        while input_idx < input.len() && output_idx < output.len() {
354            if self.frame_bytes == 0 {
355                let h_size = Self::get_header_size(self.channel_layout);
356                if input.len() - input_idx < h_size {
357                    break;
358                }
359
360                // Check magic: 0x17 0x42
361                if input[input_idx] != 0x17 || input[input_idx + 1] != 0x42 {
362                    if input_used > 0 {
363                        // Attempt to find next magic if there's enough data
364                        let mut found = false;
365                        let mut search_idx = input_idx + 1;
366                        while search_idx + 1 < input.len() {
367                            if input[search_idx] == 0x17 && input[search_idx + 1] == 0x42 {
368                                input_idx = search_idx;
369                                found = true;
370                                break;
371                            }
372                            search_idx += 1;
373                        }
374                        if !found {
375                            break;
376                        }
377                    } else {
378                        return Err(AdpcmError::InvalidMagic);
379                    }
380                }
381
382                // Header info
383                let frame_len =
384                    u16::from_le_bytes([input[input_idx + 2], input[input_idx + 3]]) as usize;
385                let read_sum = input[input_idx + 4];
386                let header_type = input[input_idx + 5];
387
388                // Validate checksum (sum mod 256 of entire header, except byte 4)
389                let mut calc_sum: u8 = 0;
390                for i in 0..h_size {
391                    if i != 4 {
392                        calc_sum = calc_sum.wrapping_add(input[input_idx + i]);
393                    }
394                }
395
396                if calc_sum != read_sum {
397                    return Err(AdpcmError::ChecksumMismatch {
398                        expected: calc_sum,
399                        actual: read_sum,
400                    });
401                }
402
403                let encoding_type = SampleType::from(header_type);
404                if encoding_type == SampleType::None {
405                    return Err(AdpcmError::UnsupportedSampleType(header_type));
406                }
407
408                self.frame_length = frame_len;
409                self.encoding_type = encoding_type;
410                // self.sample_rate = 22050; // REMOVED: Do not overwrite sample rate from header
411
412                let expected_channels = self.channel_layout.count();
413                let actual_channels = match encoding_type {
414                    SampleType::PcmMono8 | SampleType::PcmMono16 | SampleType::AdpcmMono => 1,
415                    SampleType::PcmStereo8 | SampleType::PcmStereo16 | SampleType::AdpcmStereo => 2,
416                    _ => 0,
417                };
418
419                if actual_channels != expected_channels {
420                    return Err(AdpcmError::ChannelMismatch {
421                        expected: expected_channels as u16,
422                        actual: actual_channels as u16,
423                    });
424                }
425
426                // Channel headers start at index 6
427                let mut ptr = input_idx + 6;
428                for i in 0..self.channel_layout.count() {
429                    let pred = i32::from(i16::from_le_bytes([input[ptr], input[ptr + 1]]));
430                    let step = i32::from(input[ptr + 2]);
431                    self.channels[i].pred_value = pred;
432                    self.channels[i].step_size_index = step.clamp(0, 88);
433                    ptr += 3;
434                }
435
436                input_idx += h_size;
437                input_used += h_size;
438                self.frame_bytes = self.frame_length - h_size;
439                self.low_nibble = false;
440                self.current_channel = 0;
441            }
442
443            let ch = self.current_channel;
444            if output_idx >= output.len() || self.frame_bytes == 0 {
445                break;
446            }
447
448            match self.encoding_type {
449                SampleType::AdpcmMono | SampleType::AdpcmStereo => {
450                    let byte = input[input_idx];
451
452                    let qdiff = if self.low_nibble {
453                        let val = byte & 0x0F;
454                        input_idx += 1;
455                        input_used += 1;
456                        self.frame_bytes -= 1;
457                        val
458                    } else {
459                        (byte >> 4) & 0x0F
460                    };
461                    self.low_nibble = !self.low_nibble;
462
463                    let chan = &mut self.channels[ch];
464                    let pred_diff = ADPCM_PRED_DIFF_TABLE[(usize::try_from(chan.step_size_index)
465                        .unwrap_or(0)
466                        * 16)
467                        + usize::from(qdiff)];
468
469                    chan.pred_value += pred_diff;
470                    chan.pred_value = chan.pred_value.clamp(-32768, 32767);
471
472                    output[output_idx] = i16::try_from(chan.pred_value).unwrap_or(0);
473                    output_idx += 1;
474                    output_written += 2; // bytes written
475
476                    chan.step_size_index += ADPCM_STEP_SIZE_ADJ[(qdiff & 7) as usize];
477                    chan.step_size_index = chan.step_size_index.clamp(0, 88);
478                }
479                SampleType::PcmMono16 | SampleType::PcmStereo16 => {
480                    if input_idx + 1 < input.len() {
481                        let val = i16::from_le_bytes([input[input_idx], input[input_idx + 1]]);
482                        output[output_idx] = val;
483                        output_idx += 1;
484                        output_written += 2;
485                        input_idx += 2;
486                        input_used += 2;
487                        self.frame_bytes -= 2;
488                    } else {
489                        break;
490                    }
491                }
492                SampleType::PcmMono8 | SampleType::PcmStereo8 => {
493                    if input_idx < input.len() {
494                        let b = input[input_idx];
495                        // Convert u8 (offset 128) back to i16
496                        let val = i16::try_from((i32::from(b) - 128) << 8).unwrap_or(0);
497                        output[output_idx] = val;
498                        output_idx += 1;
499                        output_written += 2;
500                        input_idx += 1;
501                        input_used += 1;
502                        self.frame_bytes -= 1;
503                    } else {
504                        break;
505                    }
506                }
507                _ => {}
508            }
509
510            // Next channel
511            self.current_channel += 1;
512            if self.current_channel >= self.channel_layout.count() {
513                self.current_channel = 0;
514            }
515        }
516
517        Ok((input_used, output_written))
518    }
519}