From 28a845b230d3b07d22923a8a536b6ef6df2f54aa Mon Sep 17 00:00:00 2001 From: Woyten Date: Sat, 25 Jun 2022 16:13:44 +0200 Subject: [PATCH] Extract magnetron crate --- .vscode/settings.json | 1 + Cargo.lock | 8 + Cargo.toml | 4 +- fluid-xenth/Cargo.toml | 2 +- fluid-xenth/README.md | 2 +- magnetron/Cargo.toml | 17 + magnetron/README.md | 12 + magnetron/src/automation.rs | 73 +++ magnetron/src/buffer.rs | 158 +++++++ magnetron/src/lib.rs | 98 ++++ magnetron/src/spec.rs | 104 +++++ .../envelope.rs => magnetron/src/waveform.rs | 35 ++ microwave/Cargo.toml | 3 +- microwave/README.md | 4 +- microwave/src/assets.rs | 3 +- microwave/src/bench.rs | 7 +- microwave/src/magnetron/control.rs | 17 - microwave/src/magnetron/filter.rs | 10 +- microwave/src/magnetron/mod.rs | 435 ++++++------------ microwave/src/magnetron/oscillator.rs | 10 +- microwave/src/magnetron/signal.rs | 7 +- microwave/src/magnetron/source.rs | 37 +- microwave/src/magnetron/spec.rs | 104 ----- microwave/src/magnetron/waveform.rs | 193 -------- microwave/src/magnetron/waveguide.rs | 7 +- microwave/src/main.rs | 2 +- microwave/src/synth.rs | 30 +- tune-cli/Cargo.toml | 2 +- 28 files changed, 720 insertions(+), 665 deletions(-) create mode 100644 magnetron/Cargo.toml create mode 100644 magnetron/README.md create mode 100644 magnetron/src/automation.rs create mode 100644 magnetron/src/buffer.rs create mode 100644 magnetron/src/lib.rs create mode 100644 magnetron/src/spec.rs rename microwave/src/magnetron/envelope.rs => magnetron/src/waveform.rs (72%) delete mode 100644 microwave/src/magnetron/control.rs delete mode 100644 microwave/src/magnetron/spec.rs delete mode 100644 microwave/src/magnetron/waveform.rs diff --git a/.vscode/settings.json b/.vscode/settings.json index 8d3da16b..1b68cc95 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -6,6 +6,7 @@ "antiarcheotonic", "antidiatonic", "archeotonic", + "automations", "backends", "bedoginning", "bindgen", diff --git a/Cargo.lock b/Cargo.lock index 678ca0fa..867b0fa4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1647,6 +1647,13 @@ dependencies = [ "libc", ] +[[package]] +name = "magnetron" +version = "0.1.0" +dependencies = [ + "assert_approx_eq", +] + [[package]] name = "malloc_buf" version = "0.0.6" @@ -1726,6 +1733,7 @@ dependencies = [ "cpal", "fluid-xenth", "hound", + "magnetron", "midir", "nannou", "rand 0.8.5", diff --git a/Cargo.toml b/Cargo.toml index d7ca3ae5..349a35e5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ readme = "README.md" keywords = ["microtonal", "midi", "scales", "synthesizer", "tuning"] license = "MIT" edition = "2021" -rust-version = "1.56" +rust-version = "1.61" [dependencies] @@ -16,4 +16,4 @@ rust-version = "1.56" assert_approx_eq = "1.1.0" [workspace] -members = ["fluid-xenth", "microwave", "tune-cli", "tune-web"] +members = ["fluid-xenth", "magnetron", "microwave", "tune-cli", "tune-web"] diff --git a/fluid-xenth/Cargo.toml b/fluid-xenth/Cargo.toml index dbca2b24..924baed6 100644 --- a/fluid-xenth/Cargo.toml +++ b/fluid-xenth/Cargo.toml @@ -9,7 +9,7 @@ keywords = ["fluid", "microtonal", "soundfont", "synthesizer", "tuning"] categories = ["multimedia", "multimedia::audio"] license = "MIT" edition = "2021" -rust-version = "1.56" +rust-version = "1.61" [features] sf3 = ["fluidlite/with-sf3"] diff --git a/fluid-xenth/README.md b/fluid-xenth/README.md index dd106277..d630f48f 100644 --- a/fluid-xenth/README.md +++ b/fluid-xenth/README.md @@ -7,7 +7,7 @@ Stop making music with notes. Use pitches. # Overview -`fluid-xenth` is a microtonal wrapper around [FluidLite](https://crates.io/crates/fluidlite). It uses the JIT live-retuning concept implemented in [tune](https://github.com/Woyten/tune) to enable arbitrary-pitch playback. +`fluid-xenth` is a microtonal wrapper around [FluidLite](https://crates.io/crates/fluidlite). It uses the AOT / JIT live-retuning concepts implemented in [tune](https://github.com/Woyten/tune) to enable arbitrary-pitch playback. # Getting Started diff --git a/magnetron/Cargo.toml b/magnetron/Cargo.toml new file mode 100644 index 00000000..6f8d5b0e --- /dev/null +++ b/magnetron/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "magnetron" +version = "0.1.0" +authors = ["Woyten "] +description = "Create your own modular microtonal synthesizer from reusable building blocks." +repository = "https://github.com/Woyten/tune/tree/master/magnetron" +readme = "README.md" +keywords = ["effects", "microtonal", "modular", "sound", "synthesizer"] +categories = ["multimedia", "multimedia::audio"] +license = "MIT" +edition = "2021" +rust-version = "1.61" + +[dependencies] + +[dev-dependencies] +assert_approx_eq = "1.1.0" diff --git a/magnetron/README.md b/magnetron/README.md new file mode 100644 index 00000000..92f5b1b1 --- /dev/null +++ b/magnetron/README.md @@ -0,0 +1,12 @@ +Create your own modular microtonal synthesizer from reusable building blocks. + +# Resources + +- [Changelog](https://github.com/Woyten/tune/releases) +- [API documentation](https://docs.rs/magnetron) + +# Overview + +`magnetron` will contain reusable and tested core components of the microtonal synthesizer [`microwave`](https://github.com/Woyten/tune/tree/master/microwave). + +So far, `magnetron` provides a very basic architecture for creating custom audio processors and automations. Over time, the design will solidify and predefined audio processors and automations will be added. diff --git a/magnetron/src/automation.rs b/magnetron/src/automation.rs new file mode 100644 index 00000000..1c5d6429 --- /dev/null +++ b/magnetron/src/automation.rs @@ -0,0 +1,73 @@ +use std::marker::PhantomData; + +use crate::{spec::Spec, waveform::WaveformState}; + +pub struct Automation { + pub(crate) automation_fn: Box) -> f64 + Send>, +} + +pub struct AutomationContext<'a, S> { + pub render_window_secs: f64, + pub state: &'a WaveformState, + pub storage: &'a S, +} + +impl<'a, S> AutomationContext<'a, S> { + pub fn read>(&self, value: &mut V) -> V::Value { + value.use_context(self) + } +} + +impl AutomatedValue for Automation { + type Storage = S; + type Value = f64; + + fn use_context(&mut self, context: &AutomationContext) -> Self::Value { + (self.automation_fn)(context) + } +} + +pub trait AutomatedValue { + type Storage; + type Value; + + fn use_context(&mut self, context: &AutomationContext) -> Self::Value; +} + +impl> AutomatedValue for PhantomData { + type Storage = A::Storage; + type Value = (); + + fn use_context(&mut self, _context: &AutomationContext) -> Self::Value {} +} + +impl> AutomatedValue for (A1, A2) { + type Storage = A1::Storage; + type Value = (A1::Value, A2::Value); + + fn use_context(&mut self, context: &AutomationContext) -> Self::Value { + (context.read(&mut self.0), context.read(&mut self.1)) + } +} + +impl< + A1: AutomatedValue, + A2: AutomatedValue, + A3: AutomatedValue, + > AutomatedValue for (A1, A2, A3) +{ + type Storage = A1::Storage; + type Value = (A1::Value, A2::Value, A3::Value); + + fn use_context(&mut self, context: &AutomationContext) -> Self::Value { + ( + context.read(&mut self.0), + context.read(&mut self.1), + context.read(&mut self.2), + ) + } +} + +pub trait AutomationSpec: Spec> { + type Storage: 'static; +} diff --git a/magnetron/src/buffer.rs b/magnetron/src/buffer.rs new file mode 100644 index 00000000..7e0b4edd --- /dev/null +++ b/magnetron/src/buffer.rs @@ -0,0 +1,158 @@ +use std::{iter, mem, sync::Arc}; + +pub struct BufferWriter { + pub(crate) sample_width_secs: f64, + pub(crate) readable: ReadableBuffers, + pub(crate) writeable: WaveformBuffer, +} + +impl BufferWriter { + pub fn sample_width_secs(&self) -> f64 { + self.sample_width_secs + } + + pub fn read_0_and_write( + &mut self, + out_buffer: OutBuffer, + out_level: f64, + mut f: impl FnMut() -> f64, + ) { + self.read_n_and_write(out_buffer, |_, write_access| { + write_access.write(iter::repeat_with(|| f() * out_level)) + }); + } + + pub fn read_1_and_write( + &mut self, + in_buffer: InBuffer, + out_buffer: OutBuffer, + out_level: f64, + mut f: impl FnMut(f64) -> f64, + ) { + self.read_n_and_write(out_buffer, |read_access, write_access| { + write_access.write( + read_access + .read(in_buffer) + .iter() + .map(|&src| f(src) * out_level), + ) + }); + } + + pub fn read_2_and_write( + &mut self, + in_buffers: (InBuffer, InBuffer), + out_buffer: OutBuffer, + out_level: f64, + mut f: impl FnMut(f64, f64) -> f64, + ) { + self.read_n_and_write(out_buffer, |read_access, write_access| { + write_access.write( + read_access + .read(in_buffers.0) + .iter() + .zip(read_access.read(in_buffers.1)) + .map(|(&src_0, &src_1)| f(src_0, src_1) * out_level), + ) + }); + } + + fn read_n_and_write( + &mut self, + out_buffer: OutBuffer, + mut rw_access_fn: impl FnMut(&ReadableBuffers, &mut WaveformBuffer), + ) { + self.readable.swap(out_buffer, &mut self.writeable); + rw_access_fn(&self.readable, &mut self.writeable); + self.readable.swap(out_buffer, &mut self.writeable); + } +} + +#[derive(Copy, Clone, Debug)] +pub enum InBuffer { + Buffer(usize), + AudioIn, +} + +#[derive(Copy, Clone, Debug)] +pub enum OutBuffer { + Buffer(usize), + AudioOut, +} + +pub(crate) struct ReadableBuffers { + pub audio_in: WaveformBuffer, + pub intermediate: Vec, + pub audio_out: WaveformBuffer, + pub mix: WaveformBuffer, +} + +impl ReadableBuffers { + fn swap(&mut self, buffer_a: OutBuffer, buffer_b: &mut WaveformBuffer) { + let buffer_a = match buffer_a { + OutBuffer::Buffer(index) => self.intermediate.get_mut(index).unwrap_or_else(|| { + panic!( + "Index {} out of range. Please allocate more waveform buffers.", + index + ) + }), + OutBuffer::AudioOut => &mut self.audio_out, + }; + mem::swap(buffer_a, buffer_b); + } + + fn read(&self, in_buffer: InBuffer) -> &[f64] { + match in_buffer { + InBuffer::Buffer(index) => &self.intermediate[index], + InBuffer::AudioIn => &self.audio_in, + } + .read() + } +} + +#[derive(Clone)] +pub(crate) struct WaveformBuffer { + pub storage: Vec, + pub len: usize, + pub dirty: bool, + pub zeros: Arc<[f64]>, +} + +impl WaveformBuffer { + pub fn new(zeros: Arc<[f64]>) -> Self { + Self { + storage: vec![0.0; zeros.len()], + len: 0, + dirty: false, + zeros, + } + } + + pub fn clear(&mut self, len: usize) { + self.len = len; + self.dirty = true; + } + + pub fn read(&self) -> &[f64] { + match self.dirty { + true => &self.zeros[..self.len], + false => &self.storage[..self.len], + } + } + + pub fn write(&mut self, items: impl Iterator) { + match self.dirty { + true => { + for (dest, src) in self.storage[..self.len].iter_mut().zip(items) { + *dest = src + } + self.dirty = false; + } + false => { + for (dest, src) in self.storage[..self.len].iter_mut().zip(items) { + *dest += src + } + } + } + } +} diff --git a/magnetron/src/lib.rs b/magnetron/src/lib.rs new file mode 100644 index 00000000..c623a1d0 --- /dev/null +++ b/magnetron/src/lib.rs @@ -0,0 +1,98 @@ +pub mod automation; +pub mod buffer; +pub mod spec; +pub mod waveform; + +use std::{iter, sync::Arc}; + +use automation::{AutomationContext, AutomationSpec}; +use buffer::{BufferWriter, ReadableBuffers, WaveformBuffer}; +use waveform::Waveform; + +pub struct Magnetron { + buffers: BufferWriter, +} + +impl Magnetron { + pub fn new(sample_width_secs: f64, num_buffers: usize, buffer_size: usize) -> Self { + let zeros = Arc::<[f64]>::from(vec![0.0; buffer_size]); + Self { + buffers: BufferWriter { + sample_width_secs, + readable: ReadableBuffers { + audio_in: WaveformBuffer::new(zeros.clone()), + intermediate: vec![WaveformBuffer::new(zeros.clone()); num_buffers], + audio_out: WaveformBuffer::new(zeros.clone()), + mix: WaveformBuffer::new(zeros.clone()), + }, + writeable: WaveformBuffer::new(zeros), // Empty Vec acting as a placeholder + }, + } + } + + pub fn clear(&mut self, len: usize) { + self.buffers.readable.audio_in.clear(len); + self.buffers.readable.mix.clear(len); + } + + pub fn set_audio_in(&mut self, mut buffer_content: impl FnMut() -> f64) { + self.buffers + .readable + .audio_in + .write(iter::from_fn(|| Some(buffer_content()))); + } + + pub fn write( + &mut self, + waveform: &mut Waveform, + storage: &A::Storage, + note_suspension: f64, + ) { + let buffers = &mut self.buffers; + + let len = buffers.readable.mix.len; + for buffer in &mut buffers.readable.intermediate { + buffer.clear(len); + } + buffers.readable.audio_out.clear(len); + + let state = &mut waveform.state; + + let render_window_secs = buffers.sample_width_secs * len as f64; + let context = AutomationContext { + render_window_secs, + state, + storage, + }; + + for stage in &mut waveform.stages { + stage.render(buffers, &context); + } + + let out_buffer = buffers.readable.audio_out.read(); + + let from_amplitude = waveform + .envelope + .get_value(state.secs_since_pressed, state.secs_since_released); + + state.secs_since_pressed += render_window_secs; + state.secs_since_released += render_window_secs * (1.0 - note_suspension); + + let to_amplitude = waveform + .envelope + .get_value(state.secs_since_pressed, state.secs_since_released); + + let mut curr_amplitude = from_amplitude; + let slope = (to_amplitude - from_amplitude) / len as f64; + + buffers.readable.mix.write(out_buffer.iter().map(|src| { + let result = src * curr_amplitude * state.velocity; + curr_amplitude = (curr_amplitude + slope).clamp(0.0, 1.0); + result + })); + } + + pub fn mix(&self) -> &[f64] { + self.buffers.readable.mix.read() + } +} diff --git a/magnetron/src/spec.rs b/magnetron/src/spec.rs new file mode 100644 index 00000000..75dff5f1 --- /dev/null +++ b/magnetron/src/spec.rs @@ -0,0 +1,104 @@ +use std::{collections::HashMap, marker::PhantomData}; + +use crate::{ + automation::{AutomatedValue, Automation, AutomationContext, AutomationSpec}, + waveform::{Envelope, Stage}, + BufferWriter, +}; + +pub struct Creator { + envelope_map: HashMap, +} + +impl Creator { + pub fn new(envelope_map: HashMap) -> Self { + Self { envelope_map } + } + + pub fn create(&self, spec: S) -> S::Created { + spec.use_creator(self) + } + + pub fn create_envelope(&self, envelop_name: &str) -> Option { + self.envelope_map.get(envelop_name).cloned() + } + + pub fn create_stage( + &self, + input: S, + mut stage_fn: impl FnMut(&mut BufferWriter, ::Value) + + Send + + 'static, + ) -> Stage + where + S::Created: AutomatedValue + Send + 'static, + { + let mut input = self.create(input); + Stage { + stage_fn: Box::new(move |buffers, context| stage_fn(buffers, context.read(&mut input))), + } + } + + pub fn create_automation( + &self, + input: S, + mut automation_fn: impl FnMut( + &AutomationContext<::Storage>, + ::Value, + ) -> f64 + + Send + + 'static, + ) -> Automation<::Storage> + where + S::Created: AutomatedValue + Send + 'static, + { + let mut input = self.create(input); + Automation { + automation_fn: Box::new(move |context| { + automation_fn(context, context.read(&mut input)) + }), + } + } +} + +pub trait Spec { + type Created; + + fn use_creator(&self, creator: &Creator) -> Self::Created; +} + +impl Spec for PhantomData { + type Created = PhantomData; + + fn use_creator(&self, _creator: &Creator) -> Self::Created { + PhantomData + } +} + +impl Spec for &S { + type Created = S::Created; + + fn use_creator(&self, creator: &Creator) -> Self::Created { + S::use_creator(self, creator) + } +} + +impl Spec for (S1, S2) { + type Created = (S1::Created, S2::Created); + + fn use_creator(&self, creator: &Creator) -> Self::Created { + (creator.create(&self.0), creator.create(&self.1)) + } +} + +impl Spec for (S1, S2, S3) { + type Created = (S1::Created, S2::Created, S3::Created); + + fn use_creator(&self, creator: &Creator) -> Self::Created { + ( + creator.create(&self.0), + creator.create(&self.1), + creator.create(&self.2), + ) + } +} diff --git a/microwave/src/magnetron/envelope.rs b/magnetron/src/waveform.rs similarity index 72% rename from microwave/src/magnetron/envelope.rs rename to magnetron/src/waveform.rs index 7878bb12..1bd40680 100644 --- a/microwave/src/magnetron/envelope.rs +++ b/magnetron/src/waveform.rs @@ -1,3 +1,28 @@ +use crate::{ + automation::{AutomationContext, AutomationSpec}, + buffer::BufferWriter, +}; + +pub struct Waveform { + pub envelope: Envelope, + pub stages: Vec>, + pub state: WaveformState, +} + +pub struct WaveformState { + pub pitch_hz: f64, + pub velocity: f64, + pub secs_since_pressed: f64, + pub secs_since_released: f64, +} + +impl Waveform { + pub fn is_active(&self) -> bool { + self.envelope.is_active(self.state.secs_since_released) + } +} + +#[derive(Clone)] pub struct Envelope { pub attack_time: f64, pub release_time: f64, @@ -24,6 +49,16 @@ impl Envelope { } } +pub struct Stage { + pub(crate) stage_fn: Box) + Send>, +} + +impl Stage { + pub fn render(&mut self, buffers: &mut BufferWriter, context: &AutomationContext) { + (self.stage_fn)(buffers, context); + } +} + #[cfg(test)] mod tests { use assert_approx_eq::assert_approx_eq; diff --git a/microwave/Cargo.toml b/microwave/Cargo.toml index fd0a915b..3bc97b0d 100644 --- a/microwave/Cargo.toml +++ b/microwave/Cargo.toml @@ -9,7 +9,7 @@ keywords = ["microtonal", "midi", "modular", "piano", "synthesizer"] categories = ["multimedia", "multimedia::audio"] license = "MIT" edition = "2021" -rust-version = "1.56" +rust-version = "1.61" [features] sf3 = ["fluid-xenth/sf3"] @@ -22,6 +22,7 @@ clap = { version = "3.0.6", features = ["derive", "env"] } cpal = "0.13.1" fluid-xenth = { version = "0.3.0", path = "../fluid-xenth" } hound = "3.4.0" +magnetron = { version = "0.1.0", path = "../magnetron" } midir = "0.8.0" nannou = "0.18.0" rand = { version = "0.8.3", features = ["small_rng"] } diff --git a/microwave/README.md b/microwave/README.md index 0a0bbbea..e64c3503 100644 --- a/microwave/README.md +++ b/microwave/README.md @@ -10,9 +10,9 @@ Make xenharmonic music and explore musical tunings. `microwave` is a microtonal modular waveform synthesizer with soundfont rendering capabilities based on: - [tune](https://github.com/Woyten/tune) – a microtonal library +- [magnetron](https://github.com/Woyten/tune) – a modular synthesizer architecture +- [fluid-xenth](https://github.com/Woyten/tune/tree/master/fluid-xenth) – a microtonal soundfont renderer - [Nannou](https://nannou.cc/) – a UI framework -- [FluidLite](https://crates.io/crates/fluidlite) – a soundfont renderer -- [fluid-xenth](https://github.com/Woyten/tune/tree/master/fluid-xenth) – a microtonal wrapper around FluidLite It features a virtual piano UI enabling you to play polyphonic microtonal melodies with your touch screen, computer keyboard, MIDI keyboard or mouse. The UI provides information about pitches and just intervals in custom tuning systems. diff --git a/microwave/src/assets.rs b/microwave/src/assets.rs index aded5886..b434222b 100644 --- a/microwave/src/assets.rs +++ b/microwave/src/assets.rs @@ -8,9 +8,8 @@ use crate::{ oscillator::{Modulation, Oscillator, OscillatorKind}, signal::{SignalKind, SignalSpec}, source::{LfSource, LfSourceExpr, LfSourceUnit}, - spec::{EnvelopeSpec, StageSpec, WaveformSpec, WaveformsSpec}, - waveform::{InBufferSpec, OutBufferSpec, OutSpec}, waveguide::{Reflectance, WaveguideSpec}, + EnvelopeSpec, InBufferSpec, OutBufferSpec, OutSpec, StageSpec, WaveformSpec, WaveformsSpec, }, synth::LiveParameter, }; diff --git a/microwave/src/bench.rs b/microwave/src/bench.rs index b5625678..7d43f2cc 100644 --- a/microwave/src/bench.rs +++ b/microwave/src/bench.rs @@ -1,5 +1,6 @@ use std::{collections::BTreeMap, env, fs::File, io::Write, path::Path, thread, time::Instant}; +use magnetron::{spec::Creator, Magnetron}; use rand::prelude::SliceRandom; use serde::{Deserialize, Serialize}; use tune::pitch::Pitch; @@ -7,7 +8,7 @@ use tune_cli::{CliError, CliResult}; use crate::{ assets, - magnetron::{source::LfSource, spec::WaveformSpec, waveform::Creator, Magnetron}, + magnetron::{source::LfSource, WaveformSpec}, synth::{LiveParameter, LiveParameterStorage}, }; @@ -26,7 +27,7 @@ pub fn run_benchmark() -> CliResult<()> { let envelope_map = full_spec .envelopes .into_iter() - .map(|spec| (spec.name.clone(), spec)) + .map(|spec| (spec.name.clone(), spec.create_envelope())) .collect(); let creator = Creator::new(envelope_map); @@ -79,7 +80,7 @@ fn run_benchmark_for_waveform( .push(time_consumption * 1000.0); // Make sure all elements are evaluated and not optimized away - report.control = (report.control + magnetron.total().iter().sum::()).recip(); + report.control = (report.control + magnetron.mix().iter().sum::()).recip(); } pub fn analyze_benchmark() -> CliResult<()> { diff --git a/microwave/src/magnetron/control.rs b/microwave/src/magnetron/control.rs deleted file mode 100644 index 3089f7fe..00000000 --- a/microwave/src/magnetron/control.rs +++ /dev/null @@ -1,17 +0,0 @@ -use super::{AutomatedValue, AutomationContext}; - -pub trait Controller: AutomatedValue + Clone + Send + 'static {} - -#[derive(Clone)] -pub enum NoControl {} - -impl AutomatedValue for NoControl { - type Value = f64; - type Storage = (); - - fn use_context(&mut self, _context: &AutomationContext) -> Self::Value { - unreachable!("NoControl is inhabitable") - } -} - -impl Controller for NoControl {} diff --git a/microwave/src/magnetron/filter.rs b/microwave/src/magnetron/filter.rs index d6e46a96..093bfec2 100644 --- a/microwave/src/magnetron/filter.rs +++ b/microwave/src/magnetron/filter.rs @@ -1,11 +1,13 @@ use std::f64::consts::TAU; +use magnetron::{ + automation::AutomationSpec, + spec::{Creator, Spec}, + waveform::Stage, +}; use serde::{Deserialize, Serialize}; -use super::{ - waveform::{AutomationSpec, Creator, InBufferSpec, Spec, Stage}, - OutSpec, -}; +use super::{InBufferSpec, OutSpec}; #[derive(Deserialize, Serialize)] pub struct Filter { diff --git a/microwave/src/magnetron/mod.rs b/microwave/src/magnetron/mod.rs index 48033baa..2ba7213e 100644 --- a/microwave/src/magnetron/mod.rs +++ b/microwave/src/magnetron/mod.rs @@ -1,323 +1,177 @@ -use std::{iter, marker::PhantomData, mem, sync::Arc}; - -use waveform::WaveformState; +use magnetron::{ + automation::AutomationSpec, + buffer::{InBuffer, OutBuffer}, + spec::{Creator, Spec}, + waveform::{Envelope, Stage, Waveform, WaveformState}, +}; +use serde::{Deserialize, Serialize}; +use tune::pitch::Pitch; use self::{ - control::Controller, - waveform::{AutomationSpec, OutSpec, Waveform}, + filter::{Filter, RingModulator}, + oscillator::Oscillator, + signal::SignalSpec, + waveguide::WaveguideSpec, }; mod functions; mod util; -pub mod control; pub mod effects; -pub mod envelope; pub mod filter; pub mod oscillator; pub mod signal; pub mod source; -pub mod spec; -pub mod waveform; pub mod waveguide; -pub struct Magnetron { - buffers: BufferWriter, +#[derive(Deserialize, Serialize)] +pub struct WaveformsSpec { + pub envelopes: Vec, + pub waveforms: Vec>, } -impl Magnetron { - pub fn new(sample_width_secs: f64, num_buffers: usize, buffer_size: usize) -> Self { - let zeros = Arc::<[f64]>::from(vec![0.0; buffer_size]); - Self { - buffers: BufferWriter { - sample_width_secs, - readable: ReadableBuffers { - audio_in: WaveformBuffer::new(zeros.clone()), - intermediate: vec![WaveformBuffer::new(zeros.clone()); num_buffers], - audio_out: WaveformBuffer::new(zeros.clone()), - total: WaveformBuffer::new(zeros.clone()), - }, - writeable: WaveformBuffer::new(zeros), // Empty Vec acting as a placeholder - }, - } - } - - pub fn clear(&mut self, len: usize) { - self.buffers.readable.audio_in.clear(len); - self.buffers.readable.total.clear(len); - } - - pub fn set_audio_in(&mut self, mut buffer_content: impl FnMut() -> f64) { - self.buffers - .readable - .audio_in - .write(iter::from_fn(|| Some(buffer_content()))); - } - - pub fn write( - &mut self, - waveform: &mut Waveform, - storage: &A::Storage, - note_suspension: f64, - ) -> bool { - let buffers = &mut self.buffers; - - let len = buffers.readable.total.len; - for buffer in &mut buffers.readable.intermediate { - buffer.clear(len); - } - buffers.readable.audio_out.clear(len); - - let state = &mut waveform.state; - - let render_window_secs = buffers.sample_width_secs * len as f64; - let context = AutomationContext { - render_window_secs, - state, - storage, - }; +#[derive(Clone, Deserialize, Serialize)] +pub struct EnvelopeSpec { + pub name: String, + pub attack_time: f64, + pub release_time: f64, + pub decay_rate: f64, +} - for stage in &mut waveform.stages { - stage.render(buffers, &context); +impl EnvelopeSpec { + pub fn create_envelope(&self) -> Envelope { + Envelope { + attack_time: self.attack_time, + release_time: self.release_time, + decay_rate: self.decay_rate, } - - let out_buffer = buffers.readable.audio_out.read(); - - let from_amplitude = waveform - .envelope - .get_value(state.secs_since_pressed, state.secs_since_released); - - state.secs_since_pressed += render_window_secs; - state.secs_since_released += render_window_secs * (1.0 - note_suspension); - - let to_amplitude = waveform - .envelope - .get_value(state.secs_since_pressed, state.secs_since_released); - - let mut curr_amplitude = from_amplitude; - let slope = (to_amplitude - from_amplitude) / len as f64; - - buffers.readable.total.write(out_buffer.iter().map(|src| { - let result = src * curr_amplitude * state.velocity; - curr_amplitude = (curr_amplitude + slope).clamp(0.0, 1.0); - result - })); - - waveform.envelope.is_active(state.secs_since_released) - } - - pub fn total(&self) -> &[f64] { - self.buffers.readable.total.read() } } -pub struct BufferWriter { - sample_width_secs: f64, - readable: ReadableBuffers, - writeable: WaveformBuffer, +#[derive(Deserialize, Serialize)] +pub struct WaveformSpec { + pub name: String, + pub envelope: String, + pub stages: Vec>, } -impl BufferWriter { - pub fn sample_width_secs(&self) -> f64 { - self.sample_width_secs - } - - pub fn read_0_and_write( - &mut self, - out_buffer: OutBuffer, - out_level: f64, - mut f: impl FnMut() -> f64, - ) { - self.read_n_and_write(out_buffer, |_, write_access| { - write_access.write(iter::repeat_with(|| f() * out_level)) - }); - } - - pub fn read_1_and_write( - &mut self, - in_buffer: InBuffer, - out_buffer: OutBuffer, - out_level: f64, - mut f: impl FnMut(f64) -> f64, - ) { - self.read_n_and_write(out_buffer, |read_access, write_access| { - write_access.write( - read_access - .read(in_buffer) - .iter() - .map(|&src| f(src) * out_level), - ) - }); - } - - pub fn read_2_and_write( - &mut self, - in_buffers: (InBuffer, InBuffer), - out_buffer: OutBuffer, - out_level: f64, - mut f: impl FnMut(f64, f64) -> f64, - ) { - self.read_n_and_write(out_buffer, |read_access, write_access| { - write_access.write( - read_access - .read(in_buffers.0) - .iter() - .zip(read_access.read(in_buffers.1)) - .map(|(&src_0, &src_1)| f(src_0, src_1) * out_level), - ) - }); - } - - fn read_n_and_write( - &mut self, - out_buffer: OutBuffer, - mut rw_access_fn: impl FnMut(&ReadableBuffers, &mut WaveformBuffer), - ) { - self.readable.swap(out_buffer, &mut self.writeable); - rw_access_fn(&self.readable, &mut self.writeable); - self.readable.swap(out_buffer, &mut self.writeable); +impl WaveformSpec { + pub fn with_pitch_and_velocity(&self, pitch: Pitch, velocity: f64) -> CreateWaveformSpec { + CreateWaveformSpec { + envelope: &self.envelope, + stages: &self.stages, + pitch, + velocity, + } } } -#[derive(Copy, Clone, Debug)] -pub enum InBuffer { - Buffer(usize), - AudioIn, +pub struct CreateWaveformSpec<'a, A> { + pub envelope: &'a str, + pub stages: &'a [StageSpec], + pub pitch: Pitch, + pub velocity: f64, } -#[derive(Copy, Clone, Debug)] -pub enum OutBuffer { - Buffer(usize), - AudioOut, +impl<'a, A: AutomationSpec> Spec for CreateWaveformSpec<'a, A> { + type Created = Option>; + + fn use_creator(&self, creator: &Creator) -> Self::Created { + Some(Waveform { + envelope: creator.create_envelope(self.envelope)?, + stages: self + .stages + .iter() + .map(|spec| creator.create(spec)) + .collect(), + state: WaveformState { + pitch_hz: self.pitch.as_hz(), + velocity: self.velocity, + secs_since_pressed: 0.0, + secs_since_released: 0.0, + }, + }) + } } -struct ReadableBuffers { - audio_in: WaveformBuffer, - intermediate: Vec, - audio_out: WaveformBuffer, - total: WaveformBuffer, +#[derive(Deserialize, Serialize)] +pub enum StageSpec { + Oscillator(Oscillator), + Signal(SignalSpec), + Waveguide(WaveguideSpec), + Filter(Filter), + RingModulator(RingModulator), } -impl ReadableBuffers { - fn swap(&mut self, buffer_a: OutBuffer, buffer_b: &mut WaveformBuffer) { - let buffer_a = match buffer_a { - OutBuffer::Buffer(index) => self.intermediate.get_mut(index).unwrap_or_else(|| { - panic!( - "Index {} out of range. Please allocate more waveform buffers.", - index - ) - }), - OutBuffer::AudioOut => &mut self.audio_out, - }; - mem::swap(buffer_a, buffer_b); - } +impl Spec for StageSpec { + type Created = Stage; - fn read(&self, in_buffer: InBuffer) -> &[f64] { - match in_buffer { - InBuffer::Buffer(index) => &self.intermediate[index], - InBuffer::AudioIn => &self.audio_in, + fn use_creator(&self, creator: &Creator) -> Self::Created { + match self { + StageSpec::Oscillator(spec) => creator.create(spec), + StageSpec::Signal(spec) => creator.create(spec), + StageSpec::Waveguide(spec) => creator.create(spec), + StageSpec::Filter(spec) => creator.create(spec), + StageSpec::RingModulator(spec) => creator.create(spec), } - .read() } } -#[derive(Clone)] -struct WaveformBuffer { - storage: Vec, - len: usize, - dirty: bool, - zeros: Arc<[f64]>, +#[derive(Deserialize, Serialize)] +#[serde(untagged)] +pub enum InBufferSpec { + Buffer(usize), + AudioIn(AudioIn), } -impl WaveformBuffer { - fn new(zeros: Arc<[f64]>) -> Self { - Self { - storage: vec![0.0; zeros.len()], - len: 0, - dirty: false, - zeros, - } - } +// Single variant enum for nice serialization +#[derive(Deserialize, Serialize)] +pub enum AudioIn { + AudioIn, +} - fn clear(&mut self, len: usize) { - self.len = len; - self.dirty = true; +impl InBufferSpec { + pub fn audio_in() -> Self { + Self::AudioIn(AudioIn::AudioIn) } - fn read(&self) -> &[f64] { - match self.dirty { - true => &self.zeros[..self.len], - false => &self.storage[..self.len], + pub fn buffer(&self) -> InBuffer { + match self { + InBufferSpec::Buffer(buffer) => InBuffer::Buffer(*buffer), + InBufferSpec::AudioIn(AudioIn::AudioIn) => InBuffer::AudioIn, } } - - fn write(&mut self, items: impl Iterator) { - match self.dirty { - true => { - for (dest, src) in self.storage[..self.len].iter_mut().zip(items) { - *dest = src - } - self.dirty = false; - } - false => { - for (dest, src) in self.storage[..self.len].iter_mut().zip(items) { - *dest += src - } - } - } - } -} - -pub struct AutomationContext<'a, S> { - pub render_window_secs: f64, - pub state: &'a WaveformState, - pub storage: &'a S, } -impl<'a, S> AutomationContext<'a, S> { - pub fn read>(&self, value: &mut V) -> V::Value { - value.use_context(self) - } +#[derive(Deserialize, Serialize)] +pub struct OutSpec { + pub out_buffer: OutBufferSpec, + pub out_level: A, } -pub trait AutomatedValue { - type Storage; - type Value; - - fn use_context(&mut self, context: &AutomationContext) -> Self::Value; +#[derive(Deserialize, Serialize)] +#[serde(untagged)] +pub enum OutBufferSpec { + Buffer(usize), + AudioOut(AudioOut), } -impl AutomatedValue for PhantomData { - type Storage = C::Storage; - type Value = (); - - fn use_context(&mut self, _context: &AutomationContext) -> Self::Value {} +// Single variant enum for nice serialization +#[derive(Deserialize, Serialize)] +pub enum AudioOut { + AudioOut, } -impl> AutomatedValue for (A1, A2) { - type Storage = A1::Storage; - type Value = (A1::Value, A2::Value); - - fn use_context(&mut self, context: &AutomationContext) -> Self::Value { - (context.read(&mut self.0), context.read(&mut self.1)) +impl OutBufferSpec { + pub fn audio_out() -> Self { + Self::AudioOut(AudioOut::AudioOut) } -} -impl< - A1: AutomatedValue, - A2: AutomatedValue, - A3: AutomatedValue, - > AutomatedValue for (A1, A2, A3) -{ - type Storage = A1::Storage; - type Value = (A1::Value, A2::Value, A3::Value); - - fn use_context(&mut self, context: &AutomationContext) -> Self::Value { - ( - context.read(&mut self.0), - context.read(&mut self.1), - context.read(&mut self.2), - ) + pub fn buffer(&self) -> OutBuffer { + match self { + OutBufferSpec::Buffer(buffer) => OutBuffer::Buffer(*buffer), + OutBufferSpec::AudioOut(AudioOut::AudioOut) => OutBuffer::AudioOut, + } } } @@ -326,20 +180,19 @@ mod tests { use std::{collections::HashMap, f64::consts::TAU}; use assert_approx_eq::assert_approx_eq; + use magnetron::{ + spec::Creator, + waveform::{Envelope, Waveform}, + Magnetron, + }; use tune::pitch::Pitch; - use crate::{ - magnetron::waveform::{InBufferSpec, OutBufferSpec}, - synth::LiveParameter, - }; + use crate::synth::LiveParameter; use super::{ - control::NoControl, filter::RingModulator, oscillator::{Modulation, Oscillator, OscillatorKind}, - source::{LfSource, LfSourceUnit}, - spec::{EnvelopeSpec, StageSpec, WaveformSpec}, - waveform::{Creator, OutSpec}, + source::{LfSource, LfSourceUnit, NoControl}, *, }; @@ -367,16 +220,16 @@ Filter: fn clear_and_resize_buffers() { let mut buffers = magnetron(); - assert_eq!(buffers.total(), &[0f64; 0]); + assert_eq!(buffers.mix(), &[0f64; 0]); buffers.clear(128); - assert_eq!(buffers.total(), &[0f64; 128]); + assert_eq!(buffers.mix(), &[0f64; 128]); buffers.clear(256); - assert_eq!(buffers.total(), &[0f64; 256]); + assert_eq!(buffers.mix(), &[0f64; 256]); buffers.clear(64); - assert_eq!(buffers.total(), &[0f64; 64]); + assert_eq!(buffers.mix(), &[0f64; 64]); } #[test] @@ -385,10 +238,10 @@ Filter: let mut waveform = create_waveform(&spec(vec![]), Pitch::from_hz(440.0), 1.0); buffers.clear(NUM_SAMPLES); - assert_eq!(buffers.total(), &[0.0; NUM_SAMPLES]); + assert_eq!(buffers.mix(), &[0.0; NUM_SAMPLES]); buffers.write(&mut waveform, &(), 1.0); - assert_eq!(buffers.total(), &[0f64; NUM_SAMPLES]); + assert_eq!(buffers.mix(), &[0f64; NUM_SAMPLES]); } #[test] @@ -409,13 +262,13 @@ Filter: ); buffers.clear(NUM_SAMPLES); - assert_eq!(buffers.total(), &[0.0; NUM_SAMPLES]); + assert_eq!(buffers.mix(), &[0.0; NUM_SAMPLES]); buffers.write(&mut waveform, &(), 1.0); assert_buffer_total_is(&buffers, |t| (TAU * 440.0 * t).sin()); buffers.clear(128); - assert_eq!(buffers.total(), &[0f64; 128]); + assert_eq!(buffers.mix(), &[0f64; 128]); } #[test] @@ -436,7 +289,7 @@ Filter: let mut waveform2 = create_waveform(&spec, Pitch::from_hz(660.0), 0.8); buffers.clear(NUM_SAMPLES); - assert_eq!(buffers.total(), &[0.0; NUM_SAMPLES]); + assert_eq!(buffers.mix(), &[0.0; NUM_SAMPLES]); buffers.write(&mut waveform1, &(), 1.0); assert_buffer_total_is(&buffers, |t| 0.7 * (440.0 * TAU * t).sin()); @@ -477,7 +330,7 @@ Filter: let mut waveform = create_waveform(&spec, Pitch::from_hz(550.0), 1.0); buffers.clear(NUM_SAMPLES); - assert_eq!(buffers.total(), &[0.0; NUM_SAMPLES]); + assert_eq!(buffers.mix(), &[0.0; NUM_SAMPLES]); buffers.write(&mut waveform, &(), 1.0); assert_buffer_total_is(&buffers, { @@ -520,7 +373,7 @@ Filter: let mut waveform = create_waveform(&spec, Pitch::from_hz(550.0), 1.0); buffers.clear(NUM_SAMPLES); - assert_eq!(buffers.total(), &[0.0; NUM_SAMPLES]); + assert_eq!(buffers.mix(), &[0.0; NUM_SAMPLES]); buffers.write(&mut waveform, &(), 1.0); assert_buffer_total_is(&buffers, |t| { @@ -564,7 +417,7 @@ Filter: let mut waveform = create_waveform(&spec, Pitch::from_hz(440.0), 1.0); buffers.clear(NUM_SAMPLES); - assert_eq!(buffers.total(), &[0.0; NUM_SAMPLES]); + assert_eq!(buffers.mix(), &[0.0; NUM_SAMPLES]); buffers.write(&mut waveform, &(), 1.0); assert_buffer_total_is(&buffers, |t| { @@ -589,16 +442,14 @@ Filter: pitch: Pitch, velocity: f64, ) -> Waveform> { - let mut envelope_map = HashMap::new(); - envelope_map.insert( + let envelope_map = HashMap::from([( spec.envelope.to_owned(), - EnvelopeSpec { - name: spec.envelope.to_owned(), + Envelope { attack_time: -1e-10, release_time: 1e-10, decay_rate: 0.0, }, - ); + )]); Creator::new(envelope_map) .create(spec.with_pitch_and_velocity(pitch, velocity)) .unwrap() @@ -606,7 +457,7 @@ Filter: fn assert_buffer_total_is(buffers: &Magnetron, mut f: impl FnMut(f64) -> f64) { let mut time = 0.0; - for sample in buffers.total() { + for sample in buffers.mix() { assert_approx_eq!(sample, f(time)); time += SAMPLE_WIDTH_SECS; } diff --git a/microwave/src/magnetron/oscillator.rs b/microwave/src/magnetron/oscillator.rs index 36f348f0..8c48a956 100644 --- a/microwave/src/magnetron/oscillator.rs +++ b/microwave/src/magnetron/oscillator.rs @@ -1,9 +1,11 @@ +use magnetron::{ + automation::AutomationSpec, + spec::{Creator, Spec}, + waveform::Stage, +}; use serde::{Deserialize, Serialize}; -use super::{ - functions, - waveform::{AutomationSpec, Creator, InBufferSpec, OutSpec, Spec, Stage}, -}; +use super::{functions, InBufferSpec, OutSpec}; #[derive(Deserialize, Serialize)] pub struct Oscillator { diff --git a/microwave/src/magnetron/signal.rs b/microwave/src/magnetron/signal.rs index af387a3c..15dd2133 100644 --- a/microwave/src/magnetron/signal.rs +++ b/microwave/src/magnetron/signal.rs @@ -1,7 +1,12 @@ +use magnetron::{ + automation::AutomationSpec, + spec::{Creator, Spec}, + waveform::Stage, +}; use nannou::rand::prelude::*; use serde::{Deserialize, Serialize}; -use super::waveform::{AutomationSpec, Creator, OutSpec, Spec, Stage}; +use super::OutSpec; #[derive(Serialize, Deserialize)] pub struct SignalSpec { diff --git a/microwave/src/magnetron/source.rs b/microwave/src/magnetron/source.rs index b5e893b9..e6f6d385 100644 --- a/microwave/src/magnetron/source.rs +++ b/microwave/src/magnetron/source.rs @@ -4,32 +4,33 @@ use std::{ ops::{Add, Mul}, }; +use magnetron::{ + automation::{AutomatedValue, Automation, AutomationContext, AutomationSpec}, + spec::{Creator, Spec}, +}; use serde::{ de::{self, value::MapAccessDeserializer, IntoDeserializer, Visitor}, Deserialize, Deserializer, Serialize, }; -use super::{ - control::Controller, - functions, - oscillator::OscillatorKind, - waveform::{AutomationSpec, Creator, Spec}, - AutomatedValue, AutomationContext, -}; +use super::{functions, oscillator::OscillatorKind}; -pub struct Automation { - pub automation_fn: Box) -> f64 + Send>, -} +pub trait Controller: AutomatedValue + Clone + Send + 'static {} + +#[derive(Clone)] +pub enum NoControl {} -impl AutomatedValue for Automation { - type Storage = S; +impl AutomatedValue for NoControl { type Value = f64; + type Storage = (); - fn use_context(&mut self, context: &AutomationContext) -> f64 { - (self.automation_fn)(context) + fn use_context(&mut self, _context: &AutomationContext) -> Self::Value { + unreachable!("NoControl is inhabitable") } } +impl Controller for NoControl {} + #[derive(Clone, Serialize)] #[serde(untagged)] pub enum LfSource { @@ -150,12 +151,10 @@ impl Spec for LfSource { } LfSource::Unit(unit) => match unit { LfSourceUnit::WaveformPitch => creator - .create_automation(PhantomData::, move |context, ()| { - context.state.pitch.as_hz() - }), + .create_automation(PhantomData::, move |context, ()| context.state.pitch_hz), LfSourceUnit::WaveformPeriod => creator .create_automation(PhantomData::, move |context, ()| { - context.state.pitch.as_hz().recip() + context.state.pitch_hz.recip() }), }, LfSource::Expr(expr) => match &**expr { @@ -305,7 +304,7 @@ impl Mul for LfSource { #[cfg(test)] mod tests { - use crate::{magnetron::spec::StageSpec, synth::LiveParameter}; + use crate::{magnetron::StageSpec, synth::LiveParameter}; use super::LfSource; diff --git a/microwave/src/magnetron/spec.rs b/microwave/src/magnetron/spec.rs deleted file mode 100644 index 28d72429..00000000 --- a/microwave/src/magnetron/spec.rs +++ /dev/null @@ -1,104 +0,0 @@ -use serde::{Deserialize, Serialize}; -use tune::pitch::Pitch; - -use super::{ - envelope::Envelope, - filter::{Filter, RingModulator}, - oscillator::Oscillator, - signal::SignalSpec, - waveform::{AutomationSpec, Creator, Spec, Stage, Waveform, WaveformState}, - waveguide::WaveguideSpec, -}; - -#[derive(Deserialize, Serialize)] -pub struct WaveformsSpec { - pub envelopes: Vec, - pub waveforms: Vec>, -} - -#[derive(Clone, Deserialize, Serialize)] -pub struct EnvelopeSpec { - pub name: String, - pub attack_time: f64, - pub release_time: f64, - pub decay_rate: f64, -} - -impl EnvelopeSpec { - pub fn create_envelope(&self) -> Envelope { - Envelope { - attack_time: self.attack_time, - release_time: self.release_time, - decay_rate: self.decay_rate, - } - } -} - -#[derive(Deserialize, Serialize)] -pub struct WaveformSpec { - pub name: String, - pub envelope: String, - pub stages: Vec>, -} - -impl WaveformSpec { - pub fn with_pitch_and_velocity(&self, pitch: Pitch, velocity: f64) -> CreateWaveformSpec { - CreateWaveformSpec { - envelope: &self.envelope, - stages: &self.stages, - pitch, - velocity, - } - } -} - -pub struct CreateWaveformSpec<'a, A> { - pub envelope: &'a str, - pub stages: &'a [StageSpec], - pub pitch: Pitch, - pub velocity: f64, -} - -impl<'a, A: AutomationSpec> Spec for CreateWaveformSpec<'a, A> { - type Created = Option>; - - fn use_creator(&self, creator: &Creator) -> Self::Created { - Some(Waveform { - envelope: creator.create_envelope(self.envelope)?, - stages: self - .stages - .iter() - .map(|spec| creator.create(spec)) - .collect(), - state: WaveformState { - pitch: self.pitch, - velocity: self.velocity, - secs_since_pressed: 0.0, - secs_since_released: 0.0, - }, - }) - } -} - -#[derive(Deserialize, Serialize)] -pub enum StageSpec { - Oscillator(Oscillator), - Signal(SignalSpec), - Waveguide(WaveguideSpec), - Filter(Filter), - RingModulator(RingModulator), -} - -impl Spec for StageSpec { - type Created = Stage; - - fn use_creator(&self, creator: &Creator) -> Self::Created { - match self { - StageSpec::Oscillator(spec) => creator.create(spec), - StageSpec::Signal(spec) => creator.create(spec), - StageSpec::Waveguide(spec) => creator.create(spec), - StageSpec::Filter(spec) => creator.create(spec), - StageSpec::RingModulator(spec) => creator.create(spec), - } - } -} diff --git a/microwave/src/magnetron/waveform.rs b/microwave/src/magnetron/waveform.rs deleted file mode 100644 index f527b83f..00000000 --- a/microwave/src/magnetron/waveform.rs +++ /dev/null @@ -1,193 +0,0 @@ -use std::{collections::HashMap, marker::PhantomData}; - -use serde::{Deserialize, Serialize}; -use tune::pitch::Pitch; - -use super::{ - envelope::Envelope, source::Automation, spec::EnvelopeSpec, AutomatedValue, AutomationContext, - BufferWriter, InBuffer, OutBuffer, -}; - -pub struct Waveform { - pub envelope: Envelope, - pub stages: Vec>, - pub state: WaveformState, -} - -pub struct WaveformState { - pub pitch: Pitch, - pub velocity: f64, - pub secs_since_pressed: f64, - pub secs_since_released: f64, -} - -pub struct Stage { - stage_fn: Box) + Send>, -} - -impl Stage { - pub fn render(&mut self, buffers: &mut BufferWriter, context: &AutomationContext) { - (self.stage_fn)(buffers, context); - } -} - -pub struct Creator { - envelope_map: HashMap, -} - -impl Creator { - pub fn new(envelope_map: HashMap) -> Self { - Self { envelope_map } - } - - pub fn create(&self, spec: S) -> S::Created { - spec.use_creator(self) - } - - pub fn create_envelope(&self, envelop_name: &str) -> Option { - self.envelope_map - .get(envelop_name) - .map(EnvelopeSpec::create_envelope) - } - - pub fn create_stage( - &self, - input: S, - mut stage_fn: impl FnMut(&mut BufferWriter, ::Value) - + Send - + 'static, - ) -> Stage - where - S::Created: AutomatedValue + Send + 'static, - { - let mut input = self.create(input); - Stage { - stage_fn: Box::new(move |buffers, context| stage_fn(buffers, context.read(&mut input))), - } - } - - pub fn create_automation( - &self, - input: S, - mut automation_fn: impl FnMut( - &AutomationContext<::Storage>, - ::Value, - ) -> f64 - + Send - + 'static, - ) -> Automation<::Storage> - where - S::Created: AutomatedValue + Send + 'static, - { - let mut input = self.create(input); - Automation { - automation_fn: Box::new(move |context| { - automation_fn(context, context.read(&mut input)) - }), - } - } -} - -pub trait Spec { - type Created; - - fn use_creator(&self, creator: &Creator) -> Self::Created; -} - -impl Spec for PhantomData { - type Created = PhantomData; - - fn use_creator(&self, _creator: &Creator) -> Self::Created { - PhantomData - } -} - -impl Spec for &S { - type Created = S::Created; - - fn use_creator(&self, creator: &Creator) -> Self::Created { - S::use_creator(self, creator) - } -} - -impl Spec for (S1, S2) { - type Created = (S1::Created, S2::Created); - - fn use_creator(&self, creator: &Creator) -> Self::Created { - (creator.create(&self.0), creator.create(&self.1)) - } -} - -impl Spec for (S1, S2, S3) { - type Created = (S1::Created, S2::Created, S3::Created); - - fn use_creator(&self, creator: &Creator) -> Self::Created { - ( - creator.create(&self.0), - creator.create(&self.1), - creator.create(&self.2), - ) - } -} - -pub trait AutomationSpec: Spec> { - type Storage: 'static; -} - -#[derive(Deserialize, Serialize)] -#[serde(untagged)] -pub enum InBufferSpec { - Buffer(usize), - AudioIn(AudioIn), -} - -// Single variant enum for nice serialization -#[derive(Deserialize, Serialize)] -pub enum AudioIn { - AudioIn, -} - -impl InBufferSpec { - pub fn audio_in() -> Self { - Self::AudioIn(AudioIn::AudioIn) - } - - pub fn buffer(&self) -> InBuffer { - match self { - InBufferSpec::Buffer(buffer) => InBuffer::Buffer(*buffer), - InBufferSpec::AudioIn(AudioIn::AudioIn) => InBuffer::AudioIn, - } - } -} - -#[derive(Deserialize, Serialize)] -pub struct OutSpec { - pub out_buffer: OutBufferSpec, - pub out_level: A, -} - -#[derive(Deserialize, Serialize)] -#[serde(untagged)] -pub enum OutBufferSpec { - Buffer(usize), - AudioOut(AudioOut), -} - -// Single variant enum for nice serialization -#[derive(Deserialize, Serialize)] -pub enum AudioOut { - AudioOut, -} - -impl OutBufferSpec { - pub fn audio_out() -> Self { - Self::AudioOut(AudioOut::AudioOut) - } - - pub fn buffer(&self) -> OutBuffer { - match self { - OutBufferSpec::Buffer(buffer) => OutBuffer::Buffer(*buffer), - OutBufferSpec::AudioOut(AudioOut::AudioOut) => OutBuffer::AudioOut, - } - } -} diff --git a/microwave/src/magnetron/waveguide.rs b/microwave/src/magnetron/waveguide.rs index 16fabdbb..b99fc23c 100644 --- a/microwave/src/magnetron/waveguide.rs +++ b/microwave/src/magnetron/waveguide.rs @@ -1,8 +1,13 @@ +use magnetron::{ + automation::AutomationSpec, + spec::{Creator, Spec}, + waveform::Stage, +}; use serde::{Deserialize, Serialize}; use super::{ util::{CombFilter, Interaction, OnePoleLowPass, SoftClip}, - waveform::{AutomationSpec, Creator, InBufferSpec, OutSpec, Spec, Stage}, + InBufferSpec, OutSpec, }; #[derive(Deserialize, Serialize)] diff --git a/microwave/src/main.rs b/microwave/src/main.rs index 60ffa541..9e63adc6 100644 --- a/microwave/src/main.rs +++ b/microwave/src/main.rs @@ -15,10 +15,10 @@ mod view; use std::{env, io, path::PathBuf, process, sync::mpsc}; +use crate::magnetron::effects::{DelayOptions, ReverbOptions, RotaryOptions}; use audio::{AudioModel, AudioOptions}; use clap::Parser; use keyboard::KeyboardLayout; -use magnetron::effects::{DelayOptions, ReverbOptions, RotaryOptions}; use model::{Model, SourceId}; use nannou::{app::App, wgpu::Backends}; use piano::{Backend, NoAudio, PianoEngine}; diff --git a/microwave/src/synth.rs b/microwave/src/synth.rs index 6804bdce..6aebb40c 100644 --- a/microwave/src/synth.rs +++ b/microwave/src/synth.rs @@ -5,6 +5,12 @@ use std::{ sync::mpsc::{self, Receiver, Sender}, }; +use magnetron::{ + automation::{AutomatedValue, AutomationContext}, + spec::Creator, + waveform::Waveform, + Magnetron, +}; use ringbuf::Consumer; use serde::{Deserialize, Serialize}; use tune::{ @@ -16,11 +22,8 @@ use tune_cli::{CliError, CliResult}; use crate::{ assets, magnetron::{ - control::Controller, - source::LfSource, - spec::WaveformSpec, - waveform::{Creator, Waveform}, - AutomatedValue, AutomationContext, Magnetron, + source::{Controller, LfSource}, + WaveformSpec, }, piano::Backend, }; @@ -56,7 +59,7 @@ pub fn create( let envelope_map: HashMap<_, _> = waveforms .envelopes .iter() - .map(|spec| (spec.name.clone(), spec.clone())) + .map(|spec| (spec.name.clone(), spec.create_envelope())) .collect(); if envelope_map.len() != num_envelopes { @@ -300,16 +303,11 @@ impl WaveformSynth { self.state.storage.key_pressure = waveform.1; self.state .magnetron - .write(&mut waveform.0, &self.state.storage, note_suspension) + .write(&mut waveform.0, &self.state.storage, note_suspension); + waveform.0.is_active() }); - for (&out, target) in self - .state - .magnetron - .total() - .iter() - .zip(buffer.chunks_mut(2)) - { + for (&out, target) in self.state.magnetron.mix().iter().zip(buffer.chunks_mut(2)) { if let [left, right] = target { *left += out / 10.0; *right += out / 10.0; @@ -328,7 +326,7 @@ impl SynthState { } Lifecycle::UpdatePitch { pitch } => { if let Some(waveform) = self.playing.get_mut(&WaveformState::Stable(id)) { - waveform.0.state.pitch = pitch; + waveform.0.state.pitch_hz = pitch.as_hz(); } } Lifecycle::UpdatePressure { pressure } => { @@ -356,7 +354,7 @@ impl SynthState { for (state, waveform) in &mut self.playing { match state { WaveformState::Stable(_) => { - waveform.0.state.pitch = waveform.0.state.pitch * pitch_bend_difference + waveform.0.state.pitch_hz *= pitch_bend_difference.as_float() } WaveformState::Fading(_) => {} } diff --git a/tune-cli/Cargo.toml b/tune-cli/Cargo.toml index 04269e87..eaf708cf 100644 --- a/tune-cli/Cargo.toml +++ b/tune-cli/Cargo.toml @@ -8,7 +8,7 @@ readme = "README.md" keywords = ["microtonal", "midi", "scales", "synthesizer", "tuning"] license = "MIT" edition = "2021" -rust-version = "1.56" +rust-version = "1.61" [dependencies] clap = { version = "3.0.6", features = ["derive"] }