From b527e48419f28fb0b56690ea023222a2b18756e8 Mon Sep 17 00:00:00 2001 From: Jeron Aldaron Lau Date: Sat, 4 May 2024 21:27:14 -0500 Subject: [PATCH] Draft new `tree` API --- examples/piano.rs | 58 ++++++++++++-- examples/sine.rs | 12 +-- src/lib.rs | 3 + src/next.rs | 54 ++++++++++++- src/tree.rs | 192 ++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 305 insertions(+), 14 deletions(-) create mode 100644 src/tree.rs diff --git a/examples/piano.rs b/examples/piano.rs index d98de8b..2db5bbe 100644 --- a/examples/piano.rs +++ b/examples/piano.rs @@ -1,11 +1,7 @@ //! A Minor on an Electric Piano -use fon::chan::Ch16; -use fon::{Audio, Frame}; -use twang::noise::White; -use twang::ops::Gain; -use twang::osc::Sine; -use twang::Synth; +use fon::{chan::Ch16, Audio, Frame}; +use twang::next::{Synth, Wave}; mod wav; @@ -18,6 +14,7 @@ const PITCHES: [f32; 3] = [220.0, 220.0 * 32.0 / 27.0, 220.0 * 3.0 / 2.0]; /// Volume of the piano const VOLUME: f32 = 1.0 / 3.0; +/* // State of the synthesizer. #[derive(Default)] struct Processors { @@ -25,9 +22,56 @@ struct Processors { white: White, // 10 harmonics for 3 pitches. piano: [[Sine; 10]; 3], +} */ +const GAINS: &[Wave; 10] = &[ + Wave::sig(HARMONICS[0]), + Wave::sig(HARMONICS[1]), + Wave::sig(HARMONICS[2]), + Wave::sig(HARMONICS[3]), + Wave::sig(HARMONICS[4]), + Wave::sig(HARMONICS[5]), + Wave::sig(HARMONICS[6]), + Wave::sig(HARMONICS[7]), + Wave::sig(HARMONICS[8]), + Wave::sig(HARMONICS[9]), +]; + +/// Play a note on the piano from a sine wave +const fn piano(sine: &'static Wave) -> [Wave<'static>; 10] { + [ + sine.amp(&GAINS[0]), + sine.amp(&GAINS[1]), + sine.amp(&GAINS[2]), + sine.amp(&GAINS[3]), + sine.amp(&GAINS[4]), + sine.amp(&GAINS[5]), + sine.amp(&GAINS[6]), + sine.amp(&GAINS[7]), + sine.amp(&GAINS[8]), + sine.amp(&GAINS[9]), + ] } fn main() { + // Define waveform + const FIRST: Wave = Wave::mix(&piano(&Wave::sig(PITCHES[0]).sine())); + const THIRD: Wave = Wave::mix(&piano(&Wave::sig(PITCHES[1]).sine())); + const FIFTH: Wave = Wave::mix(&piano(&Wave::sig(PITCHES[2]).sine())); + const PIANO: Wave = + Wave::mix(&[FIRST, THIRD, FIFTH]).amp(&Wave::sig(VOLUME)); + + // Initialize audio, and create synthesizer + let mut audio = Audio::::with_silence(48_000, 48_000 * 5); + let mut synth = Synth::new(PIANO); + + // Synthesize 5 seconds of audio + synth.stream(audio.sink(), &[]); + + // Plot synthesized audio, and write to a WAV file + // plot::write(&audio); + wav::write(audio, "piano.wav").expect("Failed to write WAV file"); + + /* // Initialize audio let mut audio = Audio::::with_silence(48_000, 48_000 * 5); // Create audio processors @@ -53,5 +97,5 @@ fn main() { // Synthesize 5 seconds of audio synth.stream(audio.sink()); // Write synthesized audio to WAV file - wav::write(audio, "piano.wav").expect("Failed to write WAV file"); + wav::write(audio, "piano.wav").expect("Failed to write WAV file"); */ } diff --git a/examples/sine.rs b/examples/sine.rs index 7914e02..6fea491 100644 --- a/examples/sine.rs +++ b/examples/sine.rs @@ -1,15 +1,17 @@ use fon::{chan::Ch16, Audio, Frame}; -use twang::next::{Synth, Wave}; +use twang::tree::{Synth, Sine, Hz, Wave}; mod wav; -fn main() { - // Define waveform - const SINE: Wave = Wave::sig(440.0).sine(); +// Define waveform +const fn waveform() -> impl Wave { + Sine(Hz(440.0)) +} +fn main() { // Initialize audio, and create synthesizer let mut audio = Audio::::with_silence(48_000, 48_000 * 5); - let mut synth = Synth::new(SINE); + let mut synth = Synth::new(waveform()); // Synthesize 5 seconds of audio synth.stream(audio.sink(), &[]); diff --git a/src/lib.rs b/src/lib.rs index 4985815..4fe9844 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -105,6 +105,7 @@ variant_size_differences )] +extern crate std; // FIXME: for debugging extern crate alloc; mod math; @@ -117,5 +118,7 @@ pub mod osc; pub mod file; // FIXME pub mod next; +// FIXME +pub mod tree; pub use synth::Synth; diff --git a/src/next.rs b/src/next.rs index 21e7553..f6c8b02 100644 --- a/src/next.rs +++ b/src/next.rs @@ -233,6 +233,37 @@ enum Node<'a> { File(&'a [u8]), } +impl Node<'_> { + /// Recursively count the number of oscillators + fn count_osc(&self) -> usize { + use Node::*; + match *self { + Sig(_) => 0, + Mix(nodes) => { + let mut count = 0; + for node in nodes { + count += node.count_osc(); + } + count + } + Sine { hz } => 1 + hz.count_osc(), + Ramp { hz, curve } => 1 + hz.count_osc() + curve.count_osc(), + Pulse { hz, duty, alias } => { + 1 + hz.count_osc() + duty.count_osc() + alias.count_osc() + } + Mul(nodes) => { + let mut count = 0; + for node in nodes { + count += node.count_osc(); + } + count + } + Amp(a, b) => a.count_osc() + b.count_osc(), + File(_) => todo!(), + } + } +} + /// A parameterized waveform. /// /// For all oscillators, there is a built-in phase offset so that the first @@ -388,6 +419,7 @@ impl<'a> Wave<'a> { Self(Node::Mix(Self::as_nodes(nodes))) } + /// Get node slice from wave slice // Safe transmute because of `repr(transparent)` #[allow(unsafe_code)] const fn as_nodes(nodes: &'a [Self]) -> &'a [Node<'a>] { @@ -413,11 +445,17 @@ pub struct Synth<'a> { impl<'a> Synth<'a> { /// Create a new synthesizer for a parameterized waveform. pub fn new(wave: Wave<'a>) -> Self { - let phase = Vec::from([0.0]); //FIXME: Calculate size based on osc count + let phase = { + let mut phase = Vec::new(); + phase.resize(wave.0.count_osc(), 0.0); + phase + }; let stack = Vec::from([[0.0; 32]]); let index = 32; let wave = Some(wave); + // panic!("{}", phase.len()); + Synth { wave, phase, @@ -573,7 +611,19 @@ impl<'a> Synth<'a> { } } Node::Mul(nodes) => todo!("{nodes:?}"), - Node::Amp(main, amp) => todo!("{main:?} {amp:?}"), + Node::Amp(main, amp) => { + self.stack.push([0.0; 32]); + self.node(amp, delta, osc); + let amp = self.stack.pop().unwrap(); + + *osc += 1; + self.node(main, delta, osc); + for (main, amp) in + self.stack.last_mut().unwrap().iter_mut().zip(amp.iter()) + { + *main *= amp; + } + } Node::File(bytes) => todo!("{:?}", bytes), } } diff --git a/src/tree.rs b/src/tree.rs new file mode 100644 index 0000000..24a8815 --- /dev/null +++ b/src/tree.rs @@ -0,0 +1,192 @@ +macro_rules! const_postfix_waveform { + () => { + const fn sine(self) -> Sine { + Sine(self) + } + }; + ($type:ty) => { + impl $type { + const_postfix_waveform!(); + } + }; + ($type:ty, $($generic:ident),+) => { + impl<$($generic),+> $type { + const_postfix_waveform!(); + } + }; +} + +const_postfix_waveform!(Hz); +const_postfix_waveform!(Line); +const_postfix_waveform!(Sine, T); + +use core::{time::Duration, f32::consts}; + +#[derive(Debug)] +pub struct Chunk([f32; 32]); + +impl Chunk { + #[inline(always)] + fn for_each_sample(&mut self, f: impl FnMut(&mut f32)) { + self.0.iter_mut().for_each(f); + } + + #[inline(always)] + fn offset(&mut self, amt: f32) { + self.for_each_sample(|sample| *sample += amt); + } + + #[inline(always)] + fn amplify(&mut self, amt: f32) { + self.for_each_sample(|sample| *sample *= amt); + } + + #[inline(always)] + fn cosine(&mut self) { + self.for_each_sample(|sample| *sample = libm::cosf(*sample)); + } + + #[inline(always)] + fn invert(&mut self) { + self.for_each_sample(|sample| *sample = -*sample); + } +} + +pub trait Wave { + /// Synthesize a chunk of audio. + /// + /// - `elapsed` is nanoseconds since the start of synthesis (up to about + /// 1169 years) + /// - `interval` is nanoseconds in the chunk's interval + #[must_use] + fn synthesize(&self, elapsed: u64, interval: u64) -> Chunk; +} + +impl Wave for &T +where + T: Wave, +{ + fn synthesize(&self, elapsed: u64, interval: u64) -> Chunk { + (**self).synthesize(elapsed, interval) + } +} + +/// Constant signal +#[derive(Debug)] +pub struct Line(pub f32); + +impl Wave for Line { + fn synthesize(&self, _elapsed: u64, _interval: u64) -> Chunk { + Chunk([self.0; 32]) + } +} + +/// Fixed frequency phase counter +/// +/// Produces a sawtooth wave +#[derive(Debug)] +pub struct Hz(pub f32); + +impl Wave for Hz { + fn synthesize(&self, elapsed: u64, interval: u64) -> Chunk { + let hz_32 = (self.0 * 32.0) as u64; + let phase = 32_000_000_000 / hz_32; + let offset = elapsed % phase; + let mut i = 0; + let mut chunk = Chunk([0.0; 32]); + + chunk.for_each_sample(|sample| { + let place = i * interval / 32 + offset; + + *sample = (place as f32 / phase as f32) % 1.0; + i += 1; + }); + chunk.amplify(-2.0); + chunk.offset(1.0); + chunk + } +} + +/// Sine wave +/// +/// Takes phase (-1 to 1) as input +#[derive(Debug)] +pub struct Sine(pub I); + +impl Wave for Sine +where + I: Wave, +{ + fn synthesize(&self, elapsed: u64, interval: u64) -> Chunk { + let mut chunk = self.0.synthesize(elapsed, interval); + + chunk.amplify(consts::PI); + chunk.cosine(); + chunk.invert(); + chunk + } +} + +#[derive(Debug)] +pub struct Synth { + wave: W, + elapsed: u64, + cursor: usize, + chunk: Chunk, +} + +impl Synth +where W: Wave +{ + pub fn new(wave: W) -> Self { + Self { + wave, + elapsed: 0, + cursor: 32, + chunk: Chunk([0.0; 32]), + } + } + + /// Run synthesis with user parameters, streaming output into the provided + /// [`Sink`] + pub fn stream(&mut self, mut sink: K, params: &[f32]) + where + Ch: fon::chan::Channel + From, + K: fon::Sink, + { + let sample_rate: u32 = sink.sample_rate().into(); + let synth_iter = SynthIter(self, sample_rate); + + sink.sink_with(&mut synth_iter.map(|x| x.to())); + } + + fn synthesize(&mut self, sample_rate: u32) -> f32 { + if self.cursor == 32 { + self.cursor = 0; + + let interval = 32_000_000_000 / u64::from(sample_rate); + + self.chunk = self.wave.synthesize(self.elapsed, interval); + self.elapsed += interval; + } + + let cursor = self.cursor; + + self.cursor += 1; + self.chunk.0[cursor] + } +} + +struct SynthIter<'a, W>(&'a mut Synth, u32); + +impl Iterator for SynthIter<'_, W> +where W: Wave +{ + type Item = fon::Frame; + + fn next(&mut self) -> Option { + let Self(synth, sample_rate) = self; + + Some(synth.synthesize(*sample_rate).into()) + } +}