From 9db5d76c122f0af43f0d0436b976b8c4d24180c3 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Mon, 27 Oct 2025 16:55:32 -0600 Subject: [PATCH 1/9] simple wavetable --- .gitignore | 2 ++ Cargo.toml | 6 ++++++ README.md | 10 ++++++++++ src/lib.rs | 2 ++ src/wavetable.rs | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 68 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 README.md create mode 100644 src/lib.rs create mode 100644 src/wavetable.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..96ef6c0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..b330586 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "wavetable-synth" +version = "0.1.0" +edition = "2024" + +[dependencies] diff --git a/README.md b/README.md new file mode 100644 index 0000000..d00e421 --- /dev/null +++ b/README.md @@ -0,0 +1,10 @@ +# WAVETABLE SYNTH +synthesizes sounds using a wavetable. + +## REQUIREMENTS +`no_std` compatible + + +## MODULES +### `wavetable` +trait definitions for a wavetable manager diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..08af373 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,2 @@ +#![no_std] +mod wavetable; diff --git a/src/wavetable.rs b/src/wavetable.rs new file mode 100644 index 0000000..b101ce3 --- /dev/null +++ b/src/wavetable.rs @@ -0,0 +1,48 @@ +pub trait Wavetable { + type OutputType; + /// get next sample + fn next(&mut self) -> Self::OutputType; +} + +pub struct SimpleWavetable<'a> { + // byte array for waveform + table: &'a [u8], + // index in the waveform + index: usize, +} + +impl<'a> Wavetable for SimpleWavetable<'a> { + type OutputType = u8; + fn next(&mut self) -> Self::OutputType { + let value = self.table[self.index]; + self.index += 1; + if self.index > self.table.len() { + self.index = 0; + } + value + } +} + +impl<'a> SimpleWavetable<'a> { + pub fn new(table: &'a [u8]) -> Self { + Self { table, index: 0 } + } +} + +// TESTS +#[cfg(test)] +pub mod test { + use super::*; + + #[test] + fn wavetable_t1() { + let table_data: [u8; 2] = [0, 255]; + let mut w = SimpleWavetable::new(&table_data); + for point in table_data { + assert!(w.next() == point); + } + } +} + +// TODO: +// - interpolated wavetable From e9a9ee2264072fe180f7f87be002d58b8d04ad15 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Mon, 27 Oct 2025 16:55:33 -0600 Subject: [PATCH 2/9] allow wavetable to be generic over data width --- src/wavetable.rs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/wavetable.rs b/src/wavetable.rs index b101ce3..5105607 100644 --- a/src/wavetable.rs +++ b/src/wavetable.rs @@ -4,15 +4,17 @@ pub trait Wavetable { fn next(&mut self) -> Self::OutputType; } -pub struct SimpleWavetable<'a> { +/// non-interpolated, simple wavetable that produces the next sample +/// every time [`Wavetable::next()`] is called. +pub struct SimpleWavetable<'a, T: Copy> { // byte array for waveform - table: &'a [u8], + table: &'a [T], // index in the waveform index: usize, } -impl<'a> Wavetable for SimpleWavetable<'a> { - type OutputType = u8; +impl<'a, T: Copy> Wavetable for SimpleWavetable<'a, T> { + type OutputType = T; fn next(&mut self) -> Self::OutputType { let value = self.table[self.index]; self.index += 1; @@ -23,8 +25,8 @@ impl<'a> Wavetable for SimpleWavetable<'a> { } } -impl<'a> SimpleWavetable<'a> { - pub fn new(table: &'a [u8]) -> Self { +impl<'a, T: Copy> SimpleWavetable<'a, T> { + pub fn new(table: &'a [T]) -> Self { Self { table, index: 0 } } } @@ -36,8 +38,8 @@ pub mod test { #[test] fn wavetable_t1() { - let table_data: [u8; 2] = [0, 255]; - let mut w = SimpleWavetable::new(&table_data); + let table_data = [0, 255]; + let mut w = SimpleWavetable::::new(&table_data); for point in table_data { assert!(w.next() == point); } From 4d7f1d9c32b38630afec88b87d5a4b4d6aa7a883 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Mon, 27 Oct 2025 16:56:11 -0600 Subject: [PATCH 3/9] simple wavetable --- .gitignore | 2 ++ Cargo.toml | 6 ++++++ README.md | 10 ++++++++++ src/lib.rs | 2 ++ src/wavetable.rs | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 68 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 README.md create mode 100644 src/lib.rs create mode 100644 src/wavetable.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..96ef6c0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..b330586 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "wavetable-synth" +version = "0.1.0" +edition = "2024" + +[dependencies] diff --git a/README.md b/README.md new file mode 100644 index 0000000..d00e421 --- /dev/null +++ b/README.md @@ -0,0 +1,10 @@ +# WAVETABLE SYNTH +synthesizes sounds using a wavetable. + +## REQUIREMENTS +`no_std` compatible + + +## MODULES +### `wavetable` +trait definitions for a wavetable manager diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..08af373 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,2 @@ +#![no_std] +mod wavetable; diff --git a/src/wavetable.rs b/src/wavetable.rs new file mode 100644 index 0000000..b101ce3 --- /dev/null +++ b/src/wavetable.rs @@ -0,0 +1,48 @@ +pub trait Wavetable { + type OutputType; + /// get next sample + fn next(&mut self) -> Self::OutputType; +} + +pub struct SimpleWavetable<'a> { + // byte array for waveform + table: &'a [u8], + // index in the waveform + index: usize, +} + +impl<'a> Wavetable for SimpleWavetable<'a> { + type OutputType = u8; + fn next(&mut self) -> Self::OutputType { + let value = self.table[self.index]; + self.index += 1; + if self.index > self.table.len() { + self.index = 0; + } + value + } +} + +impl<'a> SimpleWavetable<'a> { + pub fn new(table: &'a [u8]) -> Self { + Self { table, index: 0 } + } +} + +// TESTS +#[cfg(test)] +pub mod test { + use super::*; + + #[test] + fn wavetable_t1() { + let table_data: [u8; 2] = [0, 255]; + let mut w = SimpleWavetable::new(&table_data); + for point in table_data { + assert!(w.next() == point); + } + } +} + +// TODO: +// - interpolated wavetable From e43186ee4bf328892c359f796b5e3398aa038734 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Mon, 27 Oct 2025 16:56:11 -0600 Subject: [PATCH 4/9] allow wavetable to be generic over data width --- src/wavetable.rs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/wavetable.rs b/src/wavetable.rs index b101ce3..5105607 100644 --- a/src/wavetable.rs +++ b/src/wavetable.rs @@ -4,15 +4,17 @@ pub trait Wavetable { fn next(&mut self) -> Self::OutputType; } -pub struct SimpleWavetable<'a> { +/// non-interpolated, simple wavetable that produces the next sample +/// every time [`Wavetable::next()`] is called. +pub struct SimpleWavetable<'a, T: Copy> { // byte array for waveform - table: &'a [u8], + table: &'a [T], // index in the waveform index: usize, } -impl<'a> Wavetable for SimpleWavetable<'a> { - type OutputType = u8; +impl<'a, T: Copy> Wavetable for SimpleWavetable<'a, T> { + type OutputType = T; fn next(&mut self) -> Self::OutputType { let value = self.table[self.index]; self.index += 1; @@ -23,8 +25,8 @@ impl<'a> Wavetable for SimpleWavetable<'a> { } } -impl<'a> SimpleWavetable<'a> { - pub fn new(table: &'a [u8]) -> Self { +impl<'a, T: Copy> SimpleWavetable<'a, T> { + pub fn new(table: &'a [T]) -> Self { Self { table, index: 0 } } } @@ -36,8 +38,8 @@ pub mod test { #[test] fn wavetable_t1() { - let table_data: [u8; 2] = [0, 255]; - let mut w = SimpleWavetable::new(&table_data); + let table_data = [0, 255]; + let mut w = SimpleWavetable::::new(&table_data); for point in table_data { assert!(w.next() == point); } From 96e26ce493e3a884a5f402a686aca75fda54a3c3 Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Mon, 27 Oct 2025 16:56:11 -0600 Subject: [PATCH 5/9] WIP synthesizer --- src/lib.rs | 4 ++++ src/synthesizer.rs | 21 +++++++++++++++++++++ 2 files changed, 25 insertions(+) create mode 100644 src/synthesizer.rs diff --git a/src/lib.rs b/src/lib.rs index 08af373..caac1e1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,2 +1,6 @@ #![no_std] +mod synthesizer; mod wavetable; + +//TODO: +// * patch mod w/ trait Input and trait Output(?) diff --git a/src/synthesizer.rs b/src/synthesizer.rs new file mode 100644 index 0000000..9979397 --- /dev/null +++ b/src/synthesizer.rs @@ -0,0 +1,21 @@ +use crate::wavetable::Wavetable; + +pub trait Synthesizer {} + +pub struct SimpleWavetableSynthesizer { + wavetable: W, + clock_freq_hz: usize, + output_freq_hz: usize, + enable: bool, +} + +impl SimpleWavetableSynthesizer { + pub fn new(wavetable: W, clock_freq_hz: usize) -> Self { + Self { + wavetable, + clock_freq_hz, + output_freq_hz: 0, + enable: false, + } + } +} From 9417e6edbf590f3fe777e39ad76efc21a024e01d Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Mon, 27 Oct 2025 21:09:24 -0600 Subject: [PATCH 6/9] flesh out synthesizer module --- src/lib.rs | 4 ++-- src/synthesizer.rs | 34 +++++++++++++++++++++++++++++++++- src/wavetable.rs | 13 +++++++++---- 3 files changed, 44 insertions(+), 7 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index caac1e1..70b9fe0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,6 @@ #![no_std] -mod synthesizer; -mod wavetable; +pub mod synthesizer; +pub mod wavetable; //TODO: // * patch mod w/ trait Input and trait Output(?) diff --git a/src/synthesizer.rs b/src/synthesizer.rs index 9979397..3372632 100644 --- a/src/synthesizer.rs +++ b/src/synthesizer.rs @@ -7,6 +7,10 @@ pub struct SimpleWavetableSynthesizer { clock_freq_hz: usize, output_freq_hz: usize, enable: bool, + counter: usize, + clock_per_sample: usize, + current_output: ::OutputType, + output_flag: bool, } impl SimpleWavetableSynthesizer { @@ -14,8 +18,36 @@ impl SimpleWavetableSynthesizer { Self { wavetable, clock_freq_hz, - output_freq_hz: 0, + output_freq_hz: 1, enable: false, + counter: 0, + clock_per_sample: 0, + current_output: ::OutputType::default(), + output_flag: false, } } + + pub fn has_new_output(&self) -> bool { + self.output_flag + } + + pub fn get_output(&mut self) -> ::OutputType { + self.output_flag = false; + self.current_output + } + + pub fn set_freq(&mut self, freq_hz: usize) { + self.output_freq_hz = freq_hz; + // probably a better way to do this with modulos or something... + self.clock_per_sample = self.clock_freq_hz / self.output_freq_hz / self.wavetable.size(); + } + + pub fn tick(&mut self) { + if self.counter == self.clock_per_sample { + self.counter = 0; + self.current_output = self.wavetable.next(); + self.output_flag = true; + } + self.counter += 1; + } } diff --git a/src/wavetable.rs b/src/wavetable.rs index 5105607..e7e9e67 100644 --- a/src/wavetable.rs +++ b/src/wavetable.rs @@ -1,7 +1,8 @@ pub trait Wavetable { - type OutputType; + type OutputType: Default + Copy; /// get next sample fn next(&mut self) -> Self::OutputType; + fn size(&self) -> usize; } /// non-interpolated, simple wavetable that produces the next sample @@ -13,19 +14,23 @@ pub struct SimpleWavetable<'a, T: Copy> { index: usize, } -impl<'a, T: Copy> Wavetable for SimpleWavetable<'a, T> { +impl<'a, T: Copy + Default> Wavetable for SimpleWavetable<'a, T> { type OutputType = T; fn next(&mut self) -> Self::OutputType { let value = self.table[self.index]; self.index += 1; - if self.index > self.table.len() { + if self.index > self.table.len() - 1 { self.index = 0; } value } + + fn size(&self) -> usize { + self.table.len() + } } -impl<'a, T: Copy> SimpleWavetable<'a, T> { +impl<'a, T: Copy + Default> SimpleWavetable<'a, T> { pub fn new(table: &'a [T]) -> Self { Self { table, index: 0 } } From aa57096e4b560ee4f65149ddf5ef6c4c8f82e4ce Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Tue, 28 Oct 2025 15:33:51 -0600 Subject: [PATCH 7/9] set up for better async handling --- src/synthesizer.rs | 12 ++++++------ src/wavetable.rs | 11 +++++++---- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/synthesizer.rs b/src/synthesizer.rs index 3372632..265c903 100644 --- a/src/synthesizer.rs +++ b/src/synthesizer.rs @@ -7,7 +7,7 @@ pub struct SimpleWavetableSynthesizer { clock_freq_hz: usize, output_freq_hz: usize, enable: bool, - counter: usize, + pub counter: usize, clock_per_sample: usize, current_output: ::OutputType, output_flag: bool, @@ -33,7 +33,7 @@ impl SimpleWavetableSynthesizer { pub fn get_output(&mut self) -> ::OutputType { self.output_flag = false; - self.current_output + self.wavetable.get_value() } pub fn set_freq(&mut self, freq_hz: usize) { @@ -43,11 +43,11 @@ impl SimpleWavetableSynthesizer { } pub fn tick(&mut self) { - if self.counter == self.clock_per_sample { - self.counter = 0; - self.current_output = self.wavetable.next(); + if self.counter >= self.clock_per_sample { + self.wavetable.next(); self.output_flag = true; + self.counter = 0; } - self.counter += 1; + self.counter = self.counter.wrapping_add(1) } } diff --git a/src/wavetable.rs b/src/wavetable.rs index e7e9e67..b934e48 100644 --- a/src/wavetable.rs +++ b/src/wavetable.rs @@ -1,7 +1,8 @@ pub trait Wavetable { type OutputType: Default + Copy; /// get next sample - fn next(&mut self) -> Self::OutputType; + fn next(&mut self); + fn get_value(&self) -> Self::OutputType; fn size(&self) -> usize; } @@ -16,13 +17,15 @@ pub struct SimpleWavetable<'a, T: Copy> { impl<'a, T: Copy + Default> Wavetable for SimpleWavetable<'a, T> { type OutputType = T; - fn next(&mut self) -> Self::OutputType { - let value = self.table[self.index]; + fn next(&mut self) { self.index += 1; if self.index > self.table.len() - 1 { self.index = 0; } - value + } + + fn get_value(&self) -> Self::OutputType { + self.table[self.index] } fn size(&self) -> usize { From e6ca9c7ed8b9af193333be793983df5e48cb961a Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Tue, 28 Oct 2025 18:58:08 -0600 Subject: [PATCH 8/9] cursed fix to element count (hardcode my behated) --- src/synthesizer.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/synthesizer.rs b/src/synthesizer.rs index 265c903..dc1594b 100644 --- a/src/synthesizer.rs +++ b/src/synthesizer.rs @@ -39,7 +39,8 @@ impl SimpleWavetableSynthesizer { pub fn set_freq(&mut self, freq_hz: usize) { self.output_freq_hz = freq_hz; // probably a better way to do this with modulos or something... - self.clock_per_sample = self.clock_freq_hz / self.output_freq_hz / self.wavetable.size(); + // FIXME: use the length of the square table.. or whatever wavetable tbh + self.clock_per_sample = self.clock_freq_hz / self.output_freq_hz / 2; } pub fn tick(&mut self) { From 30033e1438c25aed4ea506cdbd69cc4ceffa0b4e Mon Sep 17 00:00:00 2001 From: sigil-03 Date: Thu, 6 Nov 2025 19:11:51 -0700 Subject: [PATCH 9/9] updates from testing --- src/synthesizer.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/synthesizer.rs b/src/synthesizer.rs index dc1594b..fa448ca 100644 --- a/src/synthesizer.rs +++ b/src/synthesizer.rs @@ -40,7 +40,7 @@ impl SimpleWavetableSynthesizer { self.output_freq_hz = freq_hz; // probably a better way to do this with modulos or something... // FIXME: use the length of the square table.. or whatever wavetable tbh - self.clock_per_sample = self.clock_freq_hz / self.output_freq_hz / 2; + self.clock_per_sample = self.clock_freq_hz / self.output_freq_hz / self.wavetable.size(); } pub fn tick(&mut self) {