diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..4e404bc --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "adpcm-pwm-dac" +version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..0f74483 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "adpcm-pwm-dac" +version = "0.1.0" +edition = "2024" + +[dependencies] diff --git a/README.md b/README.md deleted file mode 100644 index 2bd9d23..0000000 --- a/README.md +++ /dev/null @@ -1 +0,0 @@ -This branch is used by entomologist to track issues. \ No newline at end of file diff --git a/e4c811653781e69e40b63fd27a8c1e20/description b/e4c811653781e69e40b63fd27a8c1e20/description deleted file mode 100644 index 6524fee..0000000 --- a/e4c811653781e69e40b63fd27a8c1e20/description +++ /dev/null @@ -1,3 +0,0 @@ -fix scaling on DpcmDecoder::output_next() - -currently it scales to u8, but pwm outputs can use more! use usize! diff --git a/notes.md b/notes.md new file mode 100644 index 0000000..61faa61 --- /dev/null +++ b/notes.md @@ -0,0 +1,8 @@ +# ADPCM CODEC +ref: 1, 2 + + +# RESOURCES +1. [wikipedia: adpcm](https://en.wikipedia.org/wiki/Adaptive_differential_pulse-code_modulation) +2. [st-micro: an3413](https://www.st.com/resource/en/application_note/an3143-audio-software-codec-for-the-stm8s-stmicroelectronics.pdf) +3. [st-micro: an4453](file:///home/lex/Downloads/an4453-implementing-the-adpcm-algorithm-in-stm32l1xx-microcontrollers-stmicroelectronics.pdf) diff --git a/src/dac.rs b/src/dac.rs new file mode 100644 index 0000000..3053783 --- /dev/null +++ b/src/dac.rs @@ -0,0 +1,221 @@ +use crate::interface::DacInterface; + +pub enum Direction { + Up, + Down, +} + +pub struct DpcmSample { + direction: Direction, + // only 3 bits + step_count: u8, +} + +impl DpcmSample { + pub fn new(byte: u8, sub_index: usize) -> Self { + let data = if sub_index == 0 { + byte & 0x0F + } else { + byte >> 4 + }; + let direction = match data & 0x1 { + 0x1 => Direction::Up, + 0x0 => Direction::Down, + _ => Direction::Up, + }; + let step_count = data >> 1; + + Self { + direction, + step_count, + } + } +} + +pub struct Dac<'a, T: DacInterface> { + output: T, + data: Option<&'a [u8]>, + index: usize, +} + +impl<'a, T: DacInterface> Dac<'a, T> { + pub fn new(output: T) -> Self { + Self { + output, + data: None, + index: 0, + } + } + pub fn load_data(&mut self, data: &'a [u8]) { + self.data = Some(data); + } + pub fn seek_to_sample(&mut self, index: usize) { + self.index = index; + } + pub fn output_next(&mut self) { + if let Some(data) = self.data { + self.index = self.index + 1; + // reset the index to 0 if we roll over + if (self.index >= data.len()) { + self.index = 0; + } + self.output.write_amplitude(data[self.index]); + // self.output.write_amplitude(100); + } + } +} + +pub struct DpcmDac<'a, T: DacInterface> { + output: T, + data: Option<&'a [u8]>, + index: usize, + prev_amplitude: usize, + step_size: usize, + min: usize, + max: usize, +} + +impl<'a, T: DacInterface> DpcmDac<'a, T> { + pub fn new(output: T) -> Self { + Self { + output, + data: None, + index: 0, + prev_amplitude: 0x80, + step_size: 0x3, + min: 0x25, + max: 0xc8, + } + } + pub fn load_data(&mut self, data: &'a [u8]) { + self.data = Some(data); + } + pub fn seek_to_sample(&mut self, index: usize) { + self.index = index; + } + + // output the next sampe. returns true if there are samples remaining, false if there are no samples remaining. + pub fn output_next(&mut self) -> bool { + if let Some(data) = self.data { + let sub_index = 1 - self.index % 2; + + let sample = DpcmSample::new(data[self.index / 2], sub_index); + let new_amplitude = match sample.direction { + Direction::Down => self + .prev_amplitude + .saturating_sub(sample.step_count as usize * self.step_size) + .max(self.min), + Direction::Up => (self.prev_amplitude + + (sample.step_count as usize * self.step_size)) + .min(self.max), + }; + + // calculate normalized amplitude and write out + let normalized_amplitude = (new_amplitude - self.min) * 100 / (self.max - self.min); + self.output.write_amplitude(normalized_amplitude as u8); + self.prev_amplitude = new_amplitude; + + // increment the sample index + let mut samples_remaining = true; + self.index = self.index + 1; + + // reset the index to 0 if we roll over + if (self.index >= data.len() * 2) { + self.index = 0; + self.prev_amplitude = 0x74; + samples_remaining = false; + } + + // return whether or not we have samples remaining + samples_remaining + } else { + // if the sample failed to load, we (duh) have no more samples remaining + false + } + } + + pub fn disable_output(&mut self) { + self.output.disable(); + } +} + +pub struct DpcmDecoder<'a> { + data: Option<&'a [u8]>, + index: usize, + prev_amplitude: usize, + step_size: usize, + min: usize, + max: usize, +} + +impl<'a> DpcmDecoder<'a> { + pub fn new() -> Self { + Self { + data: None, + index: 0, + prev_amplitude: 0x0, + step_size: 0x3, + min: 0x50, + max: 0xaa, + } + } + pub fn load_data(&mut self, data: &'a [u8]) { + self.data = Some(data); + } + pub fn seek_to_sample(&mut self, index: usize) { + self.index = index; + } + + pub fn is_done(&self) -> bool { + if let Some(data) = self.data { + self.index >= (data.len() * 2) - 1 + } else { + false + } + } + + // output the next sample's amplitude + pub fn output_next(&mut self) -> usize { + if let Some(data) = self.data { + if self.index >= (data.len() * 2) - 10 { + return 0; + } + + let sub_index = 1 - self.index % 2; + + let sample = DpcmSample::new(data[self.index / 2], sub_index); + let new_amplitude = match sample.direction { + Direction::Down => self + .prev_amplitude + .saturating_sub(sample.step_count as usize * self.step_size) + .max(self.min), + Direction::Up => (self.prev_amplitude + + (sample.step_count as usize * self.step_size)) + .min(self.max), + }; + + // calculate normalized amplitude + // TODO: e4c811653781e69e40b63fd27a8c1e20 + let normalized_amplitude = + (new_amplitude.saturating_sub(self.min)) * 100 / (self.max - self.min); + self.prev_amplitude = new_amplitude; + + // increment the sample index + let mut samples_remaining = true; + self.index = self.index + 1; + + // reset the index to 0 if we roll over + // if (self.index >= data.len() * 2) { + // self.index = 0; + // self.prev_amplitude = 0x74; + // samples_remaining = false; + // } + + // return the normalized amplitude + return normalized_amplitude; + } else { + // otherwise just output 0 + 0 + } + } +} diff --git a/src/interface.rs b/src/interface.rs new file mode 100644 index 0000000..8224c4d --- /dev/null +++ b/src/interface.rs @@ -0,0 +1,5 @@ +pub trait DacInterface { + // write the amplitude (0->100%) + fn disable(&mut self); + fn write_amplitude(&mut self, amplitude: u8); +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..d34c0f8 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,3 @@ +#![no_std] +pub mod interface; +pub mod dac; \ No newline at end of file