From 50aba16d2ac691b245486c73f30b41ce38820ac3 Mon Sep 17 00:00:00 2001 From: Woyten Date: Sun, 4 Dec 2022 20:43:00 +0100 Subject: [PATCH] Finalize templates implementation and documentation --- .vscode/settings.json | 1 + microwave/README.md | 206 +++++++++++++++++++++++------ microwave/src/assets.rs | 147 +++++++++++--------- microwave/src/bench.rs | 6 +- microwave/src/magnetron/effects.rs | 9 +- microwave/src/magnetron/mod.rs | 26 ++-- microwave/src/magnetron/source.rs | 34 ++--- microwave/src/main.rs | 21 ++- microwave/src/synth.rs | 21 +-- 9 files changed, 311 insertions(+), 160 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index d8d74d1e..e3aea3cb 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -87,6 +87,7 @@ "surjective", "sysex", "tenney", + "themself", "tielesch", "touchpad", "ungroup", diff --git a/microwave/README.md b/microwave/README.md index 3c06d36e..718fff2d 100644 --- a/microwave/README.md +++ b/microwave/README.md @@ -16,6 +16,8 @@ Make xenharmonic music and explore musical tunings. 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. +The built-in modular synthesis engine does not use any fixed architecture and can be customized to react to all sorts of input events. + # Demo - [XĂȘnerie (15-EDO)](https://youtu.be/0PczKDrOdUA) @@ -64,22 +66,24 @@ This should spawn a window displaying a virtual keyboard. Use your touch screen, ![](https://github.com/Woyten/tune/raw/master/microwave/screenshot.png) -## MIDI In/Out +## MIDI In -To enable playback via an external MIDI device you need to specify the name of the output device and a tuning method. The available tuning methods are `full`, `full-rt`, `octave-1`, `octave-1-rt`, `octave-2`, `octave-2-rt`, `fine-tuning` and `pitch-bend`. +To listen for events originating from an external MIDI device you need to specify the name of the input device: ```bash microwave devices # List MIDI devices -microwave run --midi-out name-of-my-device --tun-method octave-1 -microwave run --midi-in "name of my device" --tun-method octave-1 # If the device name contains spaces +microwave run --midi-in name-of-my-device +microwave run --midi-in "name of my device" # If the device name contains spaces ``` -To listen for events coming from a external MIDI device you only need to specify the name of the input device: +## MIDI Out + +To enable playback through an external MIDI device you need to specify the name of the output device *and* a tuning method. The available tuning methods are `full`, `full-rt`, `octave-1`, `octave-1-rt`, `octave-2`, `octave-2-rt`, `fine-tuning` and `pitch-bend`. ```bash microwave devices # List MIDI devices -microwave run --midi-in name-of-my-device -microwave run --midi-in "name of my device" # If the device name contains spaces +microwave run --midi-out name-of-my-device --tun-method octave-1 +microwave run --midi-in "name of my device" --tun-method octave-1 # If the device name contains spaces ``` ## Soundfont Files @@ -104,11 +108,126 @@ microwave run help On startup, `microwave` tries to locate a config file specified by the `--cfg-loc` parameter or the `MICROWAVE_CFG_LOC` environment variable. If no such file is found `microwave` will create a default config file with predefined waveforms and effects for you. -### `waveforms` section +### LF Sources + +Almost all waveform and effect parameters are real numbers that can update in real-time. To keep the waveforms engine performant updates are usually evaluated at a much lower rate than the audio sampling rate. LF sources, therefore, add control and expressiveness to your playing but aren't well suited for spectral modulation. + +To define LF sources the following data types can be used: + +- Numbers, e.g. + ```yml + frequency: 440.0 + ``` +- Strings referring to a named template, e.g. + ```yml + frequency: WaveformPitch + ``` +- Nested LF source expressions, e.g. + ```yml + frequency: { Mul: [ 2.0, WaveformPitch ] } + ``` + or (using indented style) + ```yml + frequency: + Mul: + - 2.0 + - WaveformPitch + ``` + +Unfortunately, no detailed LF source documentation is available yet. However, the example config, `microwave`'s error messages and basic YAML knowledge should enable you to find valid LF source expressions. -The `waveforms` section of the config file defines waveform render stages to be applied sequentially when a key is pressed. +### `waveform_templates` Section -You can combine those stages to create the tailored sound you wish for. The following example config defines a clavinettish sounding waveform that I discovered by accident: +The purpose of the `waveform_templates` section of the config file is to define the most important LF sources s.t. they do not have to be redefined over and over again. + +#### Example: Using Pitch-Bend Events + +Looking at the initially created config file the following template definition can be found: + +```yml +waveform_templates: + - name: WaveformPitch + value: + Mul: + - Property: + kind: WaveformPitch + - Semitones: + Controller: + kind: PitchBend + map0: 0.0 + map1: 2.0 +``` + +The given fragment defines a template with name `WaveformPitch` which provides values by reading the waveform's `WaveformPitch` property and multiplying it with the pitch-bend wheel's value in whole tones. + +This means reacting to pitch-bend events is not a hardcoded feature of `microwave` but a behavior that the user can define by themself! + +#### Example: Using the Damper Pedal + +A second important template from the initial config file is the following one: + +```yml +waveform_templates: + - name: Fadeout + value: + Controller: + kind: Damper + map0: + Property: + kind: OffVelocitySet + map1: 0.0 +``` + +The given template provides a value describing to what extent a waveform is supposed to be faded out. It works in the following way: + +While a key is pressed down, `OffVelocitySet ` resolves to 0.0. As a result, `Controller`, as well, resolves to 0.0, regardless of the damper pedal state. Therefore, the waveform remains stable. + +As soon as a key is released, `OffVelocitySet` will resolve to 1.0. Now, `Controller` will interpolate between 0.0 (damper pedal pressed) and 1.0 (damper pedal released). Ergo, the waveform will fade out unless the damper pedal is pressed. + +Like in the example before, reacting to the damper pedal is not a hardcoded feature built into `microwave` but customizable behavior. + +#### How to Access Templates + +Just provide the name of the template as a single string argument. Examples: + +```yml +frequency: WaveformPitch +``` + +```yml +fadeout: Fadeout +``` + +### `waveform_envelopes` Section + +Every waveform needs to refer to an envelope defined in the `waveform_envelopes` section of the config file. Envelopes transfer the result of the waveform's `AudioOut` buffer to the main audio pipeline and limit the waveform's lifetime. + +An envelope definition typically looks like the following: + +```yml +waveform_envelopes: + - name: Piano + amplitude: Velocity + fadeout: Fadeout + attack_time: 0.01 + decay_rate: 1.0 + release_time: 0.25 +``` + +with + +- `name`: The name of the envelope. +- `amplitude`: The amplitude factor to apply to the `AudioOut` buffer. It makes sense to use `Velocity` as a value but the user can choose whatever LF source expression they find useful. +- `fadeout`: Defines the amount by which the waveform should fade out. **Important:** If this value is set to constant 0.0 the waveform will never fade out and continue to consume CPU resources, eventually leading to an overload of the audio thread. +- `attack_time`: The linear attack time in seconds. +- `decay_rate`: The exponential decay rate in 1/seconds (inverse half-life) after the attack phase is over. +- `release_time`: The linear release time in seconds. The waveform is considered exhausted as soon as the integral over `fadeout / release_time * dt` reaches 1.0. + +### `waveforms` Section + +The `waveforms` section of the config file defines the waveform render stages to be applied sequentially when a waveform is triggered. + +You can mix and match as many stages as you want to create the tailored sound you wish for. The following example config defines a clavinettish sounding waveform that I discovered by accident: ```yml waveforms: @@ -152,7 +271,11 @@ While rendering the sound three stages are applied: To create your own waveforms use the default config file as a starting point and try editing it by trial-and-error. Let `microwave`'s error messages guide you to find valid configurations. -### `effects` section +### `effect_templates` Section + +This section is completely analogous to the `waveform_templates` section but it is dedicated to work in combination with the following `effects` section. + +### `effects` Section The `effects` section of the config file defines the effects to be applied sequentially after the waveforms have been rendered. @@ -165,25 +288,25 @@ effects: gain: Controller: kind: Sound9 - from: 0.0 - to: 0.5 + map0: 0.0 + map1: 0.5 rotation_radius: 20.0 speed: Controller: kind: Sound10 - from: 1.0 - to: 7.0 - acceleration: 7.0 - deceleration: 14.0 + map0: 1.0 + map1: 7.0 + acceleration: 6.0 + deceleration: 12.0 ``` The given config defines the following properties: -- A fixed delay buffer size of 100000 samples -- An input gain ranging from 0.0 to 0.5. The input gain can be controlled via the F9 key or MIDI CCN 78. -- A rotation radius of 20 cm -- A target rotation speed ranging from 1 Hz to 7 Hz. The speed can be controlled via the F10 key or MIDI CCN 79. -- The speaker accelerates (decelerates) at 7 (14) Hz/s. +- `buffer_size`: A fixed delay buffer size of 100000 samples +- `gain`: An input gain ranging from 0.0 to 0.5. The input gain can be controlled via the F9 key or MIDI CCN 78. +- `rotation_radius`: A rotation radius of 20 cm +- `speed`: A target rotation speed ranging from 1 Hz to 7 Hz. The speed can be controlled via the F10 key or MIDI CCN 79. +- `{acc,dec}eleration`: The speaker accelerates (decelerates) at 6 (12) Hz/s. ## Live Interactions @@ -192,38 +315,39 @@ You can live-control your waveforms with your mouse pointer, touch pad or any MI The following example stage defines a resonating low-pass filter whose resonance frequency can be controlled with a MIDI modulation wheel/lever from 0 to 10,000 Hz. ```yml -stages: - - Filter: - kind: LowPass2 - resonance: - Controller: - kind: Modulation - from: 0.0 - to: 10000.0 - quality: 5.0 - in_buffer: 0 - out_buffer: AudioOut - out_level: 1.0 +Filter: + kind: LowPass2 + resonance: + Controller: + kind: Modulation + map0: 0.0 + map1: 10000.0 + quality: 5.0 + in_buffer: 0 + out_buffer: AudioOut + out_level: 1.0 ``` -If you want to use the mouse's vertical axis for sound control use the Breath controller. +If you want to use the mouse's vertical axis for sound control use the `Breath` controller. ```yml resonance: Controller: - kind: Breath - from: 0.0 + map0: Breath + map1: 0.0 to: 10000.0 ``` -If you want to use the touchpad for polyphonic sound control use the KeyPressure property. +If you want to use the touchpad for polyphonic sound control use the `KeyPressure` property. ```yml resonance: Linear: - input: KeyPressure - from: 0.0 - to: 10000.0 + input: + Property: + kind: KeyPressure + map0: 0.0 + map1: 10000.0 ``` # Feature List diff --git a/microwave/src/assets.rs b/microwave/src/assets.rs index 1022f085..4e0d65f7 100644 --- a/microwave/src/assets.rs +++ b/microwave/src/assets.rs @@ -1,6 +1,7 @@ use std::{fs::File, path::Path}; use magnetron::envelope::EnvelopeSpec; +use serde::{Deserialize, Serialize}; use tune_cli::{CliError, CliResult}; use crate::{ @@ -10,45 +11,58 @@ use crate::{ filter::{Filter, FilterKind, RingModulator}, oscillator::{Modulation, OscillatorKind, OscillatorSpec}, signal::{SignalKind, SignalSpec}, - source::{LfSource, LfSourceExpr}, + source::{LfSource, LfSourceExpr, NoAccess}, waveguide::{Reflectance, WaveguideSpec}, - AudioSpec, InBufferSpec, NamedEnvelopeSpec, OutBufferSpec, OutSpec, StageSpec, - TemplateSpec, WaveformProperty, WaveformSpec, + InBufferSpec, NamedEnvelopeSpec, OutBufferSpec, OutSpec, StageSpec, TemplateSpec, + WaveformProperty, WaveformSpec, }, }; -pub fn load_config(location: &Path) -> CliResult { - if location.exists() { - println!("[INFO] Loading config file `{}`", location.display()); - let file = File::open(location)?; - serde_yaml::from_reader(file) - .map_err(|err| CliError::CommandError(format!("Could not deserialize file: {}", err))) - } else { - println!( - "[INFO] Config file not found. Creating `{}`", - location.display() - ); - let waveforms = get_builtin_waveforms(); - let file = File::create(location)?; - serde_yaml::to_writer(file, &waveforms) - .map_err(|err| CliError::CommandError(format!("Could not serialize file: {}", err)))?; - Ok(waveforms) +#[derive(Deserialize, Serialize)] +pub struct MicrowaveConfig { + pub waveform_templates: Vec>>, + pub waveform_envelopes: Vec>>, + pub waveforms: Vec>>, + pub effect_templates: Vec>>, + pub effects: Vec>>, +} + +impl MicrowaveConfig { + pub fn load(location: &Path) -> CliResult { + if location.exists() { + println!("[INFO] Loading config file `{}`", location.display()); + let file = File::open(location)?; + serde_yaml::from_reader(file).map_err(|err| { + CliError::CommandError(format!("Could not deserialize file: {}", err)) + }) + } else { + println!( + "[INFO] Config file not found. Creating `{}`", + location.display() + ); + let waveforms = get_builtin_waveforms(); + let file = File::create(location)?; + serde_yaml::to_writer(file, &waveforms).map_err(|err| { + CliError::CommandError(format!("Could not serialize file: {}", err)) + })?; + Ok(waveforms) + } } } -pub fn get_builtin_waveforms() -> AudioSpec { - let templates = vec![ +pub fn get_builtin_waveforms() -> MicrowaveConfig { + let waveform_templates = vec![ TemplateSpec { name: "WaveformPitch".to_owned(), - spec: LfSourceExpr::Property { + value: LfSourceExpr::Property { kind: WaveformProperty::WaveformPitch, } .wrap() * LfSourceExpr::Semitones( LfSourceExpr::Controller { kind: LiveParameter::PitchBend, - from: LfSource::Value(0.0), - to: LfSource::Value(2.0), + map0: LfSource::Value(0.0), + map1: LfSource::Value(2.0), } .wrap(), ) @@ -56,15 +70,15 @@ pub fn get_builtin_waveforms() -> AudioSpec { }, TemplateSpec { name: "WaveformPeriod".to_owned(), - spec: LfSourceExpr::Property { + value: LfSourceExpr::Property { kind: WaveformProperty::WaveformPeriod, } .wrap() * LfSourceExpr::Semitones( LfSourceExpr::Controller { kind: LiveParameter::PitchBend, - from: LfSource::Value(0.0), - to: LfSource::Value(-2.0), + map0: LfSource::Value(0.0), + map1: LfSource::Value(-2.0), } .wrap(), ) @@ -72,40 +86,40 @@ pub fn get_builtin_waveforms() -> AudioSpec { }, TemplateSpec { name: "Velocity".to_owned(), - spec: LfSourceExpr::Property { + value: LfSourceExpr::Property { kind: WaveformProperty::Velocity, } .wrap(), }, TemplateSpec { name: "KeyPressure".to_owned(), - spec: LfSourceExpr::Property { + value: LfSourceExpr::Property { kind: WaveformProperty::KeyPressure, } .wrap(), }, TemplateSpec { name: "OffVelocity".to_owned(), - spec: LfSourceExpr::Property { + value: LfSourceExpr::Property { kind: WaveformProperty::OffVelocity, } .wrap(), }, TemplateSpec { name: "Fadeout".to_owned(), - spec: LfSourceExpr::Controller { + value: LfSourceExpr::Controller { kind: LiveParameter::Damper, - from: LfSourceExpr::Property { + map0: LfSourceExpr::Property { kind: WaveformProperty::OffVelocitySet, } .wrap(), - to: LfSource::Value(0.0), + map1: LfSource::Value(0.0), } .wrap(), }, ]; - let envelopes = vec![ + let waveform_envelopes = vec![ NamedEnvelopeSpec { name: "Organ".to_owned(), spec: EnvelopeSpec { @@ -339,8 +353,8 @@ pub fn get_builtin_waveforms() -> AudioSpec { kind: FilterKind::LowPass2 { resonance: LfSourceExpr::Linear { input: LfSource::template("KeyPressure"), - from: LfSource::Value(500.0), - to: LfSource::Value(10000.0), + map0: LfSource::Value(500.0), + map1: LfSource::Value(10000.0), } .wrap(), quality: LfSource::Value(3.0), @@ -732,8 +746,8 @@ pub fn get_builtin_waveforms() -> AudioSpec { out_buffer: OutBufferSpec::Buffer(0), out_level: LfSourceExpr::Linear { input: LfSource::template("Velocity"), - from: LfSource::Value(220.0), - to: LfSource::Value(880.0), + map0: LfSource::Value(220.0), + map1: LfSource::Value(880.0), } .wrap(), }, @@ -945,8 +959,8 @@ pub fn get_builtin_waveforms() -> AudioSpec { frequency: LfSource::template("WaveformPitch"), cutoff: LfSourceExpr::Controller { kind: LiveParameter::Breath, - from: LfSource::Value(2000.0), - to: LfSource::Value(5000.0), + map0: LfSource::Value(2000.0), + map1: LfSource::Value(5000.0), } .wrap(), reflectance: Reflectance::Negative, @@ -981,8 +995,8 @@ pub fn get_builtin_waveforms() -> AudioSpec { frequency: LfSource::template("WaveformPitch"), cutoff: LfSourceExpr::Controller { kind: LiveParameter::Breath, - from: LfSource::Value(2000.0), - to: LfSource::Value(5000.0), + map0: LfSource::Value(2000.0), + map1: LfSource::Value(5000.0), } .wrap(), reflectance: Reflectance::Negative, @@ -1011,8 +1025,8 @@ pub fn get_builtin_waveforms() -> AudioSpec { frequency: LfSource::template("WaveformPitch"), cutoff: LfSourceExpr::Controller { kind: LiveParameter::Breath, - from: LfSource::Value(2000.0), - to: LfSource::Value(5000.0), + map0: LfSource::Value(2000.0), + map1: LfSource::Value(5000.0), } .wrap(), reflectance: Reflectance::Negative, @@ -1050,8 +1064,8 @@ pub fn get_builtin_waveforms() -> AudioSpec { frequency: LfSource::template("WaveformPitch"), cutoff: LfSourceExpr::Controller { kind: LiveParameter::Breath, - from: LfSource::Value(2000.0), - to: LfSource::Value(5000.0), + map0: LfSource::Value(2000.0), + map1: LfSource::Value(5000.0), } .wrap(), reflectance: Reflectance::Positive, @@ -1112,8 +1126,8 @@ pub fn get_builtin_waveforms() -> AudioSpec { frequency: LfSource::template("WaveformPitch"), cutoff: LfSourceExpr::Controller { kind: LiveParameter::Breath, - from: LfSource::Value(2000.0), - to: LfSource::Value(6000.0), + map0: LfSource::Value(2000.0), + map1: LfSource::Value(6000.0), } .wrap(), reflectance: Reflectance::Positive, @@ -1150,8 +1164,8 @@ pub fn get_builtin_waveforms() -> AudioSpec { out_buffer: OutBufferSpec::Buffer(0), out_level: LfSourceExpr::Controller { kind: LiveParameter::Breath, - from: LfSource::Value(0.2), - to: LfSource::Value(1.0), + map0: LfSource::Value(0.2), + map1: LfSource::Value(1.0), } .wrap(), }, @@ -1344,8 +1358,8 @@ pub fn get_builtin_waveforms() -> AudioSpec { frequency: LfSource::template("WaveformPitch"), cutoff: LfSourceExpr::Controller { kind: LiveParameter::Breath, - from: LfSource::Value(2000.0), - to: LfSource::Value(5000.0), + map0: LfSource::Value(2000.0), + map1: LfSource::Value(5000.0), } .wrap(), reflectance: Reflectance::Negative, @@ -1359,13 +1373,15 @@ pub fn get_builtin_waveforms() -> AudioSpec { }, ]; + let effect_templates = vec![]; + let effects = vec![ EffectSpec::Echo(EchoSpec { buffer_size: 100000, gain: LfSourceExpr::Controller { kind: LiveParameter::Sound7, - from: LfSource::Value(0.0), - to: LfSource::Value(1.0), + map0: LfSource::Value(0.0), + map1: LfSource::Value(1.0), } .wrap(), delay_time: LfSource::Value(0.5), @@ -1376,8 +1392,8 @@ pub fn get_builtin_waveforms() -> AudioSpec { buffer_size: 100000, gain: LfSourceExpr::Controller { kind: LiveParameter::Sound8, - from: LfSource::Value(0.0), - to: LfSource::Value(0.5), + map0: LfSource::Value(0.0), + map1: LfSource::Value(0.5), } .wrap(), allpasses: vec![ @@ -1404,26 +1420,27 @@ pub fn get_builtin_waveforms() -> AudioSpec { buffer_size: 100000, gain: LfSourceExpr::Controller { kind: LiveParameter::Sound9, - from: LfSource::Value(0.0), - to: LfSource::Value(0.5), + map0: LfSource::Value(0.0), + map1: LfSource::Value(0.5), } .wrap(), rotation_radius: LfSource::Value(20.0), speed: LfSourceExpr::Controller { kind: LiveParameter::Sound10, - from: LfSource::Value(1.0), - to: LfSource::Value(7.0), + map0: LfSource::Value(1.0), + map1: LfSource::Value(7.0), } .wrap(), - acceleration: LfSource::Value(7.0), - deceleration: LfSource::Value(14.0), + acceleration: LfSource::Value(6.0), + deceleration: LfSource::Value(12.0), }), ]; - AudioSpec { - templates, - envelopes, + MicrowaveConfig { + waveform_templates, + waveform_envelopes, waveforms, + effect_templates, effects, } } diff --git a/microwave/src/bench.rs b/microwave/src/bench.rs index fb8c1a90..13fef963 100644 --- a/microwave/src/bench.rs +++ b/microwave/src/bench.rs @@ -24,13 +24,13 @@ pub fn run_benchmark() -> CliResult<()> { full_spec.waveforms.shuffle(&mut rand::thread_rng()); let templates = full_spec - .templates + .waveform_templates .into_iter() - .map(|spec| (spec.name, spec.spec)) + .map(|spec| (spec.name, spec.value)) .collect(); let envelopes = full_spec - .envelopes + .waveform_envelopes .into_iter() .map(|spec| (spec.name, spec.spec)) .collect(); diff --git a/microwave/src/magnetron/effects.rs b/microwave/src/magnetron/effects.rs index c3f10cbf..048faef9 100644 --- a/microwave/src/magnetron/effects.rs +++ b/microwave/src/magnetron/effects.rs @@ -1,4 +1,4 @@ -use std::{cmp::Ordering, collections::HashMap, f64::consts::TAU}; +use std::{cmp::Ordering, f64::consts::TAU}; use magnetron::{ automation::{Automation, AutomationContext, AutomationSpec}, @@ -19,9 +19,10 @@ pub enum EffectSpec { RotarySpeaker(RotarySpeakerSpec), } -impl EffectSpec { - pub fn create(&self) -> Box> { - let creator = Creator::new(HashMap::new(), HashMap::new()); +impl Spec for EffectSpec { + type Created = Box>; + + fn use_creator(&self, creator: &Creator) -> Self::Created { match self { EffectSpec::Echo(spec) => Box::new(creator.create(spec)), EffectSpec::SchroederReverb(spec) => Box::new(creator.create(spec)), diff --git a/microwave/src/magnetron/mod.rs b/microwave/src/magnetron/mod.rs index 08367808..8f9476d5 100644 --- a/microwave/src/magnetron/mod.rs +++ b/microwave/src/magnetron/mod.rs @@ -8,14 +8,11 @@ use magnetron::{ }; use serde::{Deserialize, Serialize}; -use crate::control::LiveParameter; - use self::{ - effects::EffectSpec, filter::{Filter, RingModulator}, oscillator::OscillatorSpec, signal::SignalSpec, - source::{LfSource, NoAccess, StorageAccess}, + source::StorageAccess, waveguide::WaveguideSpec, }; @@ -28,18 +25,10 @@ pub mod signal; pub mod source; pub mod waveguide; -#[derive(Deserialize, Serialize)] -pub struct AudioSpec { - pub templates: Vec>>, - pub envelopes: Vec>>, - pub waveforms: Vec>>, - pub effects: Vec>>, -} - #[derive(Clone, Deserialize, Serialize)] pub struct TemplateSpec { pub name: String, - pub spec: A, + pub value: A, } #[derive(Clone, Deserialize, Serialize)] @@ -192,9 +181,12 @@ mod tests { use assert_approx_eq::assert_approx_eq; use magnetron::{spec::Creator, Magnetron}; - use crate::{assets::get_builtin_waveforms, control::LiveParameterStorage}; + use crate::{ + assets::get_builtin_waveforms, + control::{LiveParameter, LiveParameterStorage}, + }; - use super::*; + use super::{source::LfSource, *}; const NUM_SAMPLES: usize = 44100; const SAMPLE_WIDTH_SECS: f64 = 1.0 / 44100.0; @@ -568,9 +560,9 @@ mod tests { ) -> Creator> { Creator::new( get_builtin_waveforms() - .templates + .waveform_templates .into_iter() - .map(|spec| (spec.name, spec.spec)) + .map(|spec| (spec.name, spec.value)) .collect(), HashMap::from([("test envelope".to_owned(), spec)]), ) diff --git a/microwave/src/magnetron/source.rs b/microwave/src/magnetron/source.rs index ebce1685..f7b873b6 100644 --- a/microwave/src/magnetron/source.rs +++ b/microwave/src/magnetron/source.rs @@ -101,8 +101,8 @@ pub enum LfSourceExpr { Mul(LfSource, LfSource), Linear { input: LfSource, - from: LfSource, - to: LfSource, + map0: LfSource, + map1: LfSource, }, Oscillator { kind: OscillatorKind, @@ -123,8 +123,8 @@ pub enum LfSourceExpr { }, Controller { kind: C, - from: LfSource, - to: LfSource, + map0: LfSource, + map1: LfSource, }, } @@ -155,9 +155,9 @@ impl Spec> for LfSource LfSource::Expr(expr) => match &**expr { LfSourceExpr::Add(a, b) => creator.create_automation((a, b), |_, (a, b)| a + b), LfSourceExpr::Mul(a, b) => creator.create_automation((a, b), |_, (a, b)| a * b), - LfSourceExpr::Linear { input, from, to } => { + LfSourceExpr::Linear { input, map0, map1 } => { let mut value = creator.create(input); - create_scaled_value_automation(creator, from, to, move |context| { + create_scaled_value_automation(creator, map0, map1, move |context| { context.read(&mut value) }) } @@ -210,9 +210,9 @@ impl Spec> for LfSource }, ) } - LfSourceExpr::Controller { kind, from, to } => { + LfSourceExpr::Controller { kind, map0, map1 } => { let mut kind = kind.clone(); - create_scaled_value_automation(creator, from, to, move |context| { + create_scaled_value_automation(creator, map0, map1, move |context| { kind.access(&context.payload.1) }) } @@ -337,8 +337,8 @@ Filter: resonance: Controller: kind: Modulation - from: 0.0 - to: + map0: 0.0 + map1: quality: 5.0 in_buffer: 0 out_buffer: AudioOut @@ -357,8 +357,8 @@ Filter: resonance: Controller: kind: Modulation - from: 0.0 - to: 10000 + map0: 0.0 + map1: 10000 quality: 5.0 in_buffer: 0 out_buffer: AudioOut @@ -377,8 +377,8 @@ Filter: resonance: Controller: kind: Modulation - from: 0.0 - to: AnyNameWorks + map0: 0.0 + map1: AnyNameWorks quality: 5.0 in_buffer: 0 out_buffer: AudioOut @@ -399,7 +399,7 @@ Filter: }; let template_name = if let LfSourceExpr::Controller { - to: LfSource::Template(template_name), + map1: LfSource::Template(template_name), .. } = *expr { @@ -419,8 +419,8 @@ Filter: resonance: Controller: kind: Modulation - from: 0.0 - to: + map0: 0.0 + map1: InvalidExpr: quality: 5.0 in_buffer: 0 diff --git a/microwave/src/main.rs b/microwave/src/main.rs index 3e9aea89..b5b97752 100644 --- a/microwave/src/main.rs +++ b/microwave/src/main.rs @@ -16,6 +16,8 @@ mod view; use std::{cell::RefCell, env, io, path::PathBuf, sync::mpsc}; +use ::magnetron::spec::Creator; +use assets::MicrowaveConfig; use audio::{AudioModel, AudioOptions, AudioStage}; use clap::Parser; use control::{LiveParameter, LiveParameterMapper, LiveParameterStorage, ParameterValue}; @@ -406,12 +408,25 @@ fn create_model_from_run_options(kbm: Kbm, options: RunOptions) -> CliResult = waveforms.effects.iter().map(|spec| spec.create()).collect(); + let mut config = MicrowaveConfig::load(&options.waveforms_file_location)?; + + let effect_templates = config + .effect_templates + .drain(..) + .map(|spec| (spec.name, spec.value)) + .collect(); + + let creator = Creator::new(effect_templates, Default::default()); + + let effects: Vec<_> = config + .effects + .iter() + .map(|spec| creator.create(spec)) + .collect(); let (waveform_backend, waveform_synth) = synth::create( info_send.clone(), - waveforms, + config, options.num_waveform_buffers, options.audio.out_buffer_size, sample_rate_hz_f64, diff --git a/microwave/src/synth.rs b/microwave/src/synth.rs index 09a7c6da..ed70d5d7 100644 --- a/microwave/src/synth.rs +++ b/microwave/src/synth.rs @@ -18,18 +18,19 @@ use tune::{ }; use crate::{ + assets::MicrowaveConfig, audio::AudioStage, control::{LiveParameter, LiveParameterStorage, ParameterValue}, magnetron::{ source::{LfSource, StorageAccess}, - AudioSpec, WaveformProperty, WaveformSpec, + WaveformProperty, WaveformSpec, }, piano::Backend, }; pub fn create( info_sender: Sender, - waveforms: AudioSpec, + config: MicrowaveConfig, num_buffers: usize, buffer_size: u32, sample_rate_hz: f64, @@ -48,20 +49,20 @@ pub fn create( let (send, recv) = mpsc::channel(); - let templates = waveforms - .templates + let templates = config + .waveform_templates .into_iter() - .map(|spec| (spec.name, spec.spec)) + .map(|spec| (spec.name, spec.value)) .collect(); - let envelope_names: Vec<_> = waveforms - .envelopes + let envelope_names: Vec<_> = config + .waveform_envelopes .iter() .map(|spec| spec.name.to_owned()) .collect(); - let envelopes: HashMap<_, _> = waveforms - .envelopes + let envelopes: HashMap<_, _> = config + .waveform_envelopes .into_iter() .map(|spec| (spec.name, spec.spec)) .collect(); @@ -70,7 +71,7 @@ pub fn create( WaveformBackend { messages: send, info_sender, - waveforms: waveforms.waveforms, + waveforms: config.waveforms, curr_waveform: 0, curr_envelope: envelope_names.len(), // curr_envelope == num_envelopes means default envelope envelope_names,