From d06329a7259f126987355045281cbdf292258710 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sun, 27 Jul 2025 10:52:45 -0600 Subject: [PATCH 1/9] repo initialization + commit notes --- notes.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 notes.md 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) From ecf47e998eb7e1107521277aef6a7e80ba977ee9 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sun, 27 Jul 2025 10:53:59 -0600 Subject: [PATCH 2/9] create entomologist issue branch --- README.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..2bd9d23 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +This branch is used by entomologist to track issues. \ No newline at end of file From 8502ed75bf8d3e8df1eb5f1b5576079f55c6e13e Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sun, 27 Jul 2025 14:58:02 -0600 Subject: [PATCH 3/9] initial commit of dac files --- .gitignore | 1 + Cargo.lock | 7 +++++++ Cargo.toml | 6 ++++++ src/dac.rs | 33 +++++++++++++++++++++++++++++++++ src/interface.rs | 4 ++++ src/lib.rs | 3 +++ 6 files changed, 54 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src/dac.rs create mode 100644 src/interface.rs create mode 100644 src/lib.rs 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/src/dac.rs b/src/dac.rs new file mode 100644 index 0000000..32ead8f --- /dev/null +++ b/src/dac.rs @@ -0,0 +1,33 @@ +use crate::interface::DacInterface; +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); + } + } +} \ No newline at end of file diff --git a/src/interface.rs b/src/interface.rs new file mode 100644 index 0000000..ea18854 --- /dev/null +++ b/src/interface.rs @@ -0,0 +1,4 @@ +pub trait DacInterface { + // write the amplitude (0->100%) + 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 From e6114d2c521ef3a769bd2a52411e3a938b566908 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sun, 10 Aug 2025 13:09:25 -0600 Subject: [PATCH 4/9] implment DPCM --- src/dac.rs | 101 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) diff --git a/src/dac.rs b/src/dac.rs index 32ead8f..f1efe6d 100644 --- a/src/dac.rs +++ b/src/dac.rs @@ -1,4 +1,38 @@ 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]>, @@ -30,4 +64,71 @@ impl<'a, T: DacInterface> Dac<'a, T> { // 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: 0x19, + max: 0xe6, + } + } + 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 + } + } } \ No newline at end of file From 714715b4aae512d1384b7387e3d2f695ea9b348e Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sun, 10 Aug 2025 17:53:34 -0600 Subject: [PATCH 5/9] add better interface handling --- src/dac.rs | 4 ++++ src/interface.rs | 1 + 2 files changed, 5 insertions(+) diff --git a/src/dac.rs b/src/dac.rs index f1efe6d..f3fa49f 100644 --- a/src/dac.rs +++ b/src/dac.rs @@ -131,4 +131,8 @@ impl<'a, T: DacInterface> DpcmDac<'a, T> { false } } + + pub fn disable_output(&mut self) { + self.output.disable(); + } } \ No newline at end of file diff --git a/src/interface.rs b/src/interface.rs index ea18854..8224c4d 100644 --- a/src/interface.rs +++ b/src/interface.rs @@ -1,4 +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 From bda34e6bef1d7875d7739e0a96f0af5f9aee5cf3 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sat, 16 Aug 2025 17:00:51 -0600 Subject: [PATCH 6/9] create new issue e4c811653781e69e40b63fd27a8c1e20 --- e4c811653781e69e40b63fd27a8c1e20/description | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 e4c811653781e69e40b63fd27a8c1e20/description diff --git a/e4c811653781e69e40b63fd27a8c1e20/description b/e4c811653781e69e40b63fd27a8c1e20/description new file mode 100644 index 0000000..6524fee --- /dev/null +++ b/e4c811653781e69e40b63fd27a8c1e20/description @@ -0,0 +1,3 @@ +fix scaling on DpcmDecoder::output_next() + +currently it scales to u8, but pwm outputs can use more! use usize! From c8d33b3c41bf4b53ff76837a9557f7960f508df5 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sat, 16 Aug 2025 17:02:39 -0600 Subject: [PATCH 7/9] add raw dpcmdecoder --- src/dac.rs | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/src/dac.rs b/src/dac.rs index f3fa49f..19773a5 100644 --- a/src/dac.rs +++ b/src/dac.rs @@ -135,4 +135,70 @@ impl<'a, T: DacInterface> DpcmDac<'a, T> { 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: 0x80, + step_size: 0x3, + min: 0x19, + max: 0xe6, + } + } + 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 sample's amplitude + pub fn output_next(&mut self) -> usize { + 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 + // TODO: e4c811653781e69e40b63fd27a8c1e20 + let normalized_amplitude = (new_amplitude - 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 + } + } } \ No newline at end of file From ba25b7c89f4deb52426d97fd35eb13496f183775 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Sat, 16 Aug 2025 21:40:14 -0600 Subject: [PATCH 8/9] cursed additions --- src/dac.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/dac.rs b/src/dac.rs index 19773a5..b315303 100644 --- a/src/dac.rs +++ b/src/dac.rs @@ -169,6 +169,10 @@ impl<'a> DpcmDecoder<'a> { // 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) { + return 0; + } + let sub_index = 1 - self.index % 2; let sample = DpcmSample::new(data[self.index / 2], sub_index); @@ -188,11 +192,11 @@ impl<'a> DpcmDecoder<'a> { 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; - } + // if (self.index >= data.len() * 2) { + // self.index = 0; + // self.prev_amplitude = 0x74; + // samples_remaining = false; + // } // return the normalized amplitude return normalized_amplitude; From 99ce71e8d03e382b51732db7d0771349c51c7f48 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Thu, 6 Nov 2025 19:11:29 -0700 Subject: [PATCH 9/9] updates from testing --- src/dac.rs | 71 ++++++++++++++++++++++++++++++++---------------------- 1 file changed, 42 insertions(+), 29 deletions(-) diff --git a/src/dac.rs b/src/dac.rs index b315303..3053783 100644 --- a/src/dac.rs +++ b/src/dac.rs @@ -5,7 +5,6 @@ pub enum Direction { Down, } - pub struct DpcmSample { direction: Direction, // only 3 bits @@ -60,8 +59,8 @@ impl<'a, T: DacInterface> Dac<'a, T> { if (self.index >= data.len()) { self.index = 0; } - self.output.write_amplitude(data[self.index]); - // self.output.write_amplitude(100); + self.output.write_amplitude(data[self.index]); + // self.output.write_amplitude(100); } } } @@ -84,8 +83,8 @@ impl<'a, T: DacInterface> DpcmDac<'a, T> { index: 0, prev_amplitude: 0x80, step_size: 0x3, - min: 0x19, - max: 0xe6, + min: 0x25, + max: 0xc8, } } pub fn load_data(&mut self, data: &'a [u8]) { @@ -102,31 +101,34 @@ impl<'a, T: DacInterface> DpcmDac<'a, T> { 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), + 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; - + 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) { + 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 { + } else { // if the sample failed to load, we (duh) have no more samples remaining false } @@ -137,8 +139,6 @@ impl<'a, T: DacInterface> DpcmDac<'a, T> { } } - - pub struct DpcmDecoder<'a> { data: Option<&'a [u8]>, index: usize, @@ -153,10 +153,10 @@ impl<'a> DpcmDecoder<'a> { Self { data: None, index: 0, - prev_amplitude: 0x80, + prev_amplitude: 0x0, step_size: 0x3, - min: 0x19, - max: 0xe6, + min: 0x50, + max: 0xaa, } } pub fn load_data(&mut self, data: &'a [u8]) { @@ -166,26 +166,39 @@ impl<'a> DpcmDecoder<'a> { 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) { - return 0; + 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), + 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 - self.min) * 100 / (self.max - self.min); - self.prev_amplitude = new_amplitude; - + 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; @@ -196,13 +209,13 @@ impl<'a> DpcmDecoder<'a> { // self.index = 0; // self.prev_amplitude = 0x74; // samples_remaining = false; - // } + // } - // return the normalized amplitude + // return the normalized amplitude return normalized_amplitude; } else { // otherwise just output 0 0 } } -} \ No newline at end of file +}