├── .github └── FUNDING.yml ├── .gitignore ├── Cargo.toml ├── LICENSE.txt ├── README.md ├── _typos.toml ├── examples ├── README.md └── midi_control.rs ├── src ├── chords │ ├── chord_bank.rs │ └── mod.rs ├── downsampler │ └── mod.rs ├── drums │ ├── analog_bass_drum.rs │ ├── analog_snare_drum.rs │ ├── hihat.rs │ ├── mod.rs │ ├── synthetic_bass_drum.rs │ └── synthetic_snare_drum.rs ├── engine │ ├── additive_engine.rs │ ├── bass_drum_engine.rs │ ├── chord_engine.rs │ ├── fm_engine.rs │ ├── grain_engine.rs │ ├── hihat_engine.rs │ ├── mod.rs │ ├── modal_engine.rs │ ├── noise_engine.rs │ ├── particle_engine.rs │ ├── snare_drum_engine.rs │ ├── speech_engine.rs │ ├── string_engine.rs │ ├── swarm_engine.rs │ ├── virtual_analog_engine.rs │ ├── waveshaping_engine.rs │ └── wavetable_engine.rs ├── engine2 │ ├── arpeggiator.rs │ ├── chiptune_engine.rs │ ├── mod.rs │ ├── phase_distortion_engine.rs │ ├── six_op_engine.rs │ ├── string_machine_engine.rs │ ├── virtual_analog_vcf_engine.rs │ └── wave_terrain_engine.rs ├── envelope.rs ├── fm │ ├── algorithms.rs │ ├── dx_units.rs │ ├── envelope.rs │ ├── lfo.rs │ ├── mod.rs │ ├── operator.rs │ ├── patch.rs │ └── voice.rs ├── fx │ ├── diffuser.rs │ ├── ensemble.rs │ ├── low_pass_gate.rs │ ├── mod.rs │ ├── overdrive.rs │ └── sample_rate_reducer.rs ├── lib.rs ├── noise │ ├── clocked_noise.rs │ ├── dust.rs │ ├── mod.rs │ ├── particle.rs │ └── smooth_random_generator.rs ├── oscillator │ ├── formant_oscillator.rs │ ├── grainlet_oscillator.rs │ ├── harmonic_oscillator.rs │ ├── mod.rs │ ├── nes_triangle_oscillator.rs │ ├── oscillator.rs │ ├── sine_oscillator.rs │ ├── string_synth_oscillator.rs │ ├── super_square_oscillator.rs │ ├── variable_saw_oscillator.rs │ ├── variable_shape_oscillator.rs │ ├── vosim_oscillator.rs │ ├── wavetable_oscillator.rs │ └── z_oscillator.rs ├── physical_modelling │ ├── mod.rs │ ├── modal_voice.rs │ ├── resonator.rs │ ├── string.rs │ └── string_voice.rs ├── resources │ ├── fm.rs │ ├── fold.rs │ ├── lpc.rs │ ├── mod.rs │ ├── sine.rs │ ├── stiffness.rs │ ├── svf.rs │ ├── sysex.rs │ ├── waves.rs │ └── waveshape.rs ├── speech │ ├── lpc_speech_synth.rs │ ├── lpc_speech_synth_controller.rs │ ├── lpc_speech_synth_phonemes.rs │ ├── lpc_speech_synth_words.rs │ ├── mod.rs │ ├── naive_speech_synth.rs │ └── sam_speech_synth.rs ├── utils │ ├── atan.rs │ ├── cosine_oscillator.rs │ ├── delay_line.rs │ ├── filter.rs │ ├── hysteresis_quantizer.rs │ ├── limiter.rs │ ├── mod.rs │ ├── parameter_interpolator.rs │ ├── polyblep.rs │ ├── random.rs │ ├── rsqrt.rs │ └── units.rs └── voice.rs └── tests ├── all_engines.rs ├── drums.rs ├── engines ├── additive_engine.rs ├── bass_drum_engine.rs ├── chiptune_engine.rs ├── chord_engine.rs ├── fm_engine.rs ├── grain_engine.rs ├── hihat_engine.rs ├── mod.rs ├── modal_engine.rs ├── noise_engine.rs ├── particle_engine.rs ├── phase_distortion_engine.rs ├── six_op_engine.rs ├── snare_drum_engine.rs ├── speech_engine.rs ├── string_engine.rs ├── string_machine_engine.rs ├── swarm_engine.rs ├── virtual_analog_engine.rs ├── virtual_analog_vcf_engine.rs ├── wave_terrain_engine.rs ├── waveshaping_engine.rs └── wavetable_engine.rs ├── fx.rs ├── modulation.rs ├── noise.rs ├── oscillators.rs ├── physical_modelling.rs ├── voice.rs └── wav_writer.rs /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | custom: https://www.buymeacoffee.com/sourcebox 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /Cargo.lock 3 | /out 4 | /.vscode 5 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mi-plaits-dsp" 3 | version = "0.1.0" 4 | edition = "2021" 5 | authors = ["Oliver Rockstedt "] 6 | description = "Port of DSP code from Mutable Instruments Plaits module" 7 | repository = "https://github.com/sourcebox/mi-plaits-dsp-rs" 8 | license = "MIT" 9 | keywords = ["audio", "dsp", "synthesizer"] 10 | categories = ["no-std", "multimedia::audio"] 11 | 12 | [dependencies] 13 | num-traits = { version = "0.2", default-features = false, features = ["libm"] } 14 | spin = { version = "0.10.0", features = ["once"] } 15 | 16 | [dev-dependencies] 17 | hound = "3.5.1" 18 | log = "0.4.26" 19 | simple_logger = "5.0.0" 20 | audio-midi-shell = { git = "https://github.com/sourcebox/audio-midi-shell-rs" } 21 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Oliver Rockstedt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mi-plaits-dsp 2 | 3 | Native Rust port of the DSP code used by the [Mutable Instruments Plaits](https://mutable-instruments.net/modules/plaits/) Eurorack module. 4 | 5 | This port is based on firmware release 1.2 as published at . 6 | 7 | ## Background 8 | 9 | `Plaits` is a Eurorack module released by Mutable Instruments in 2018. It is a macro oscillator featuring 24 different engines. Please refer to the [original user manual](https://mutable-instruments.net/modules/plaits/manual/) for further details. 10 | 11 | Since the original firmware as well as other design files like schematics were released under permissive open sources licenses, a number of derivatives of this module were made by several people. Also, alternative firmware versions exist for the module and parts of the DSP code were even used in unrelated commercial products. 12 | 13 | ## Goals 14 | 15 | The goal of this crate is to give access to the algorithms to the Rust community, not to set a foundation for replicating the original hardware. As a result, no hardware dependent parts are included. Nevertheless, the crate is `no_std`, so use on embedded hardware is possible. 16 | 17 | The major motivation behind this port is: 18 | 19 | - To provide building blocks that can be used independently 20 | - Make performance comparisons to the original C++ code and improve it 21 | 22 | The APIs used in this crate are kept close to the original ones intentionally, resulting in a number of clippy warnings that have been suppressed. 23 | 24 | ## Tests 25 | 26 | Run `cargo test` to run a number of integration tests that produce `WAV` files in the `./out` directory. 27 | 28 | ## License 29 | 30 | Published under the MIT license. All contributions to this project must be provided under the same license conditions. 31 | 32 | Author: Oliver Rockstedt 33 | Original author: Emilie Gillet 34 | 35 | ## Donations 36 | 37 | If you like to support my work, you can [buy me a coffee.](https://www.buymeacoffee.com/sourcebox) 38 | 39 | Buy Me A Coffee 40 | -------------------------------------------------------------------------------- /_typos.toml: -------------------------------------------------------------------------------- 1 | [default] 2 | extend-ignore-identifiers-re = ["(?i)Formant"] 3 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | ## midi_control 4 | 5 | Receives messages from all available MIDI inputs and plays up to 8 voices having its parameters controlled via control change. 6 | 7 | | CC# | Parameter | 8 | |-----|---------------------------------| 9 | | 21 | Engine selection | 10 | | 22 | Harmonics | 11 | | 23 | Timbre | 12 | | 24 | Morph | 13 | | 25 | Blend between OUT / AUX signal | 14 | | 26 | Internal envelope decay | 15 | | 27 | Low pass gate color | 16 | | 28 | Output volume | 17 | -------------------------------------------------------------------------------- /src/chords/mod.rs: -------------------------------------------------------------------------------- 1 | //! Chords module. 2 | 3 | pub mod chord_bank; 4 | -------------------------------------------------------------------------------- /src/downsampler/mod.rs: -------------------------------------------------------------------------------- 1 | //! FIR Downsampler. 2 | 3 | // Based on MIT-licensed code (c) 2021 by Emilie Gillet (emilie.o.gillet@gmail.com) 4 | 5 | const FIR_HALF_SIZE: usize = 4; 6 | const FIR_COEFFICIENT: [f32; FIR_HALF_SIZE] = [0.02442415, 0.09297315, 0.16712938, 0.21547332]; 7 | 8 | #[derive(Debug)] 9 | pub struct Downsampler<'a> { 10 | head: f32, 11 | tail: f32, 12 | state: &'a mut f32, 13 | } 14 | 15 | impl<'a> Downsampler<'a> { 16 | pub fn new(state: &'a mut f32) -> Self { 17 | Self { 18 | head: *state, 19 | tail: 0.0, 20 | state, 21 | } 22 | } 23 | 24 | #[inline] 25 | pub fn accumulate(&mut self, i: usize, sample: f32) { 26 | self.head += sample * FIR_COEFFICIENT[3 - (i & 3)]; 27 | self.tail += sample * FIR_COEFFICIENT[i & 3]; 28 | } 29 | 30 | #[inline] 31 | pub fn read(&mut self) -> f32 { 32 | let value = self.head; 33 | self.head = self.tail; 34 | self.tail = 0.0; 35 | 36 | value 37 | } 38 | } 39 | 40 | impl Drop for Downsampler<'_> { 41 | fn drop(&mut self) { 42 | *self.state = self.head; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/drums/mod.rs: -------------------------------------------------------------------------------- 1 | //! Drum synthesis models. 2 | 3 | pub mod analog_bass_drum; 4 | pub mod analog_snare_drum; 5 | pub mod hihat; 6 | pub mod synthetic_bass_drum; 7 | pub mod synthetic_snare_drum; 8 | -------------------------------------------------------------------------------- /src/engine/bass_drum_engine.rs: -------------------------------------------------------------------------------- 1 | //! Analog bass drum model. 2 | //! 3 | //! Behavioral simulation of circuits from classic drum machines. 4 | //! 5 | //! Engine parameters: 6 | //! - *HARMONICS:* attack sharpness and amount of overdrive. 7 | //! - *TIMBRE:* brightness. 8 | //! - *MORPH:* decay time. 9 | //! 10 | //! Outputs: 11 | //! - *OUT* signal: bridged T-network excited by a nicely shaped pulse. 12 | //! - *AUX* signal: frequency-modulated triangle VCO, turned into a sine with a pair of diodes, 13 | //! and shaped by a dirty VCA. 14 | //! 15 | //! Without any signal patched to the *TRIG* input, a continuous tone is produced. 16 | 17 | // Based on MIT-licensed code (c) 2016 by Emilie Gillet (emilie.o.gillet@gmail.com) 18 | 19 | use super::{note_to_frequency, Engine, EngineParameters, TriggerState}; 20 | use crate::drums::analog_bass_drum::AnalogBassDrum; 21 | use crate::drums::synthetic_bass_drum::SyntheticBassDrum; 22 | use crate::fx::overdrive::Overdrive; 23 | 24 | #[derive(Debug, Default)] 25 | pub struct BassDrumEngine { 26 | analog_bass_drum: AnalogBassDrum, 27 | synthetic_bass_drum: SyntheticBassDrum, 28 | 29 | overdrive: Overdrive, 30 | } 31 | 32 | impl BassDrumEngine { 33 | pub fn new() -> Self { 34 | Self::default() 35 | } 36 | } 37 | 38 | impl Engine for BassDrumEngine { 39 | fn init(&mut self) { 40 | self.analog_bass_drum.init(); 41 | self.synthetic_bass_drum.init(); 42 | self.overdrive.init(); 43 | } 44 | 45 | #[inline] 46 | fn render( 47 | &mut self, 48 | parameters: &EngineParameters, 49 | out: &mut [f32], 50 | aux: &mut [f32], 51 | _already_enveloped: &mut bool, 52 | ) { 53 | let f0 = note_to_frequency(parameters.note); 54 | 55 | let attack_fm_amount = f32::min(parameters.harmonics * 4.0, 1.0); 56 | let self_fm_amount = (parameters.harmonics * 4.0 - 1.0).clamp(0.0, 1.0); 57 | let drive = 58 | f32::max(parameters.harmonics * 2.0 - 1.0, 0.0) * f32::max(1.0 - 16.0 * f0, 0.0); 59 | 60 | let sustain = matches!(parameters.trigger, TriggerState::Unpatched); 61 | let trigger = matches!(parameters.trigger, TriggerState::RisingEdge); 62 | 63 | self.analog_bass_drum.render( 64 | sustain, 65 | trigger, 66 | parameters.accent, 67 | f0, 68 | parameters.timbre, 69 | parameters.morph, 70 | attack_fm_amount, 71 | self_fm_amount, 72 | out, 73 | ); 74 | 75 | self.overdrive.process(0.5 + 0.5 * drive, out); 76 | 77 | self.synthetic_bass_drum.render( 78 | sustain, 79 | trigger, 80 | parameters.accent, 81 | f0, 82 | parameters.timbre, 83 | parameters.morph, 84 | if sustain { 85 | parameters.harmonics 86 | } else { 87 | 0.4 - 0.25 * parameters.morph * parameters.morph 88 | }, 89 | f32::min(parameters.harmonics * 2.0, 1.0), 90 | f32::max(parameters.harmonics * 2.0 - 1.0, 0.0), 91 | aux, 92 | ); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/engine/grain_engine.rs: -------------------------------------------------------------------------------- 1 | //! Granular formant oscillator. 2 | //! 3 | //! Simulation of formants and filtered waveforms through the multiplication, addition and 4 | //! synchronization of segments of sine waves. 5 | //! 6 | //! Engine parameters: 7 | //! - *HARMONICS:* frequency ratio between formant 1 and 2. 8 | //! - *TIMBRE:* formant frequency. 9 | //! - *MORPH:* formant width and shape. This controls the shape of the window by which a sum 10 | //! of two synchronized sine oscillators is multiplied. 11 | //! 12 | //! *AUX* signal: simulation of filtered waveforms by windowed sine waves - 13 | //! a recreation of Braids’ Z*** models. *HARMONICS* controls the filter type (peaking, LP, BP, HP), 14 | //! with smooth variation from one response to another. 15 | 16 | // Based on MIT-licensed code (c) 2016 by Emilie Gillet (emilie.o.gillet@gmail.com) 17 | 18 | use super::{note_to_frequency, Engine, EngineParameters}; 19 | use crate::oscillator::grainlet_oscillator::GrainletOscillator; 20 | use crate::oscillator::z_oscillator::ZOscillator; 21 | use crate::utils::filter::{FilterMode, FrequencyApproximation, OnePole}; 22 | use crate::utils::units::semitones_to_ratio; 23 | 24 | #[derive(Debug, Default)] 25 | pub struct GrainEngine { 26 | grainlet: [GrainletOscillator; 2], 27 | z_oscillator: ZOscillator, 28 | dc_blocker: [OnePole; 2], 29 | } 30 | 31 | impl GrainEngine { 32 | pub fn new() -> Self { 33 | Self::default() 34 | } 35 | } 36 | 37 | impl Engine for GrainEngine { 38 | fn init(&mut self) { 39 | self.grainlet[0].init(); 40 | self.grainlet[1].init(); 41 | self.z_oscillator.init(); 42 | self.dc_blocker[0].init(); 43 | self.dc_blocker[1].init(); 44 | } 45 | 46 | #[inline] 47 | fn render( 48 | &mut self, 49 | parameters: &EngineParameters, 50 | out: &mut [f32], 51 | aux: &mut [f32], 52 | _already_enveloped: &mut bool, 53 | ) { 54 | let root = parameters.note; 55 | let f0 = note_to_frequency(root); 56 | 57 | let f1 = note_to_frequency(24.0 + 84.0 * parameters.timbre); 58 | let ratio = semitones_to_ratio(-24.0 + 48.0 * parameters.harmonics); 59 | let carrier_bleed = if parameters.harmonics < 0.5 { 60 | 1.0 - 2.0 * parameters.harmonics 61 | } else { 62 | 0.0 63 | }; 64 | let carrier_bleed_fixed = carrier_bleed * (2.0 - carrier_bleed); 65 | let carrier_shape = 0.33 + (parameters.morph - 0.33) * f32::max(1.0 - f0 * 24.0, 0.0); 66 | 67 | self.grainlet[0].render(f0, f1, carrier_shape, carrier_bleed_fixed, out); 68 | self.grainlet[1].render(f0, f1 * ratio, carrier_shape, carrier_bleed_fixed, aux); 69 | self.dc_blocker[0].set_f(0.3 * f0, FrequencyApproximation::Dirty); 70 | 71 | for (out_sample, aux_sample) in out.iter_mut().zip(aux.iter()) { 72 | *out_sample = 73 | self.dc_blocker[0].process(*out_sample + *aux_sample, FilterMode::HighPass); 74 | } 75 | 76 | let cutoff = note_to_frequency(root + 96.0 * parameters.timbre); 77 | self.z_oscillator 78 | .render(f0, cutoff, parameters.morph, parameters.harmonics, aux); 79 | 80 | self.dc_blocker[1].set_f(0.3 * f0, FrequencyApproximation::Dirty); 81 | self.dc_blocker[1].process(aux[0], FilterMode::HighPass); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/engine/hihat_engine.rs: -------------------------------------------------------------------------------- 1 | //! Analog hi-hat model. 2 | //! 3 | //! A bunch of square oscillators generating a harsh, metallic tone. 4 | //! 5 | //! Engine parameters: 6 | //! - *HARMONICS:* balance of the metallic and filtered noise. 7 | //! - *TIMBRE:* high-pass filter cutoff. 8 | //! - *MORPH:* decay time. 9 | //! 10 | //! Outputs: 11 | //! - *OUT* signal: 6 square oscillators and a dirty transistor VCA 12 | //! - *AUX* signal: uses three pairs of square oscillators ringmodulating each other, 13 | //! and a clean, linear VCA 14 | 15 | // Based on MIT-licensed code (c) 2016 by Emilie Gillet (emilie.o.gillet@gmail.com) 16 | 17 | use alloc::boxed::Box; 18 | use alloc::vec; 19 | 20 | use super::{note_to_frequency, Engine, EngineParameters, TriggerState}; 21 | use crate::drums::hihat::{Hihat, NoiseType, VcaType}; 22 | 23 | #[derive(Debug)] 24 | pub struct HihatEngine { 25 | hi_hat_1: Hihat, 26 | hi_hat_2: Hihat, 27 | 28 | temp_buffer_1: Box<[f32]>, 29 | temp_buffer_2: Box<[f32]>, 30 | } 31 | 32 | impl HihatEngine { 33 | pub fn new(block_size: usize) -> Self { 34 | Self { 35 | hi_hat_1: Hihat::default(), 36 | hi_hat_2: Hihat::default(), 37 | temp_buffer_1: vec![0.0; block_size].into_boxed_slice(), 38 | temp_buffer_2: vec![0.0; block_size].into_boxed_slice(), 39 | } 40 | } 41 | } 42 | 43 | impl Engine for HihatEngine { 44 | fn init(&mut self) { 45 | self.hi_hat_1.init(); 46 | self.hi_hat_2.init(); 47 | } 48 | 49 | #[inline] 50 | fn render( 51 | &mut self, 52 | parameters: &EngineParameters, 53 | out: &mut [f32], 54 | aux: &mut [f32], 55 | _already_enveloped: &mut bool, 56 | ) { 57 | let f0 = note_to_frequency(parameters.note); 58 | 59 | let sustain = matches!(parameters.trigger, TriggerState::Unpatched); 60 | let trigger = matches!(parameters.trigger, TriggerState::RisingEdge); 61 | 62 | self.hi_hat_1.render( 63 | sustain, 64 | trigger, 65 | parameters.accent, 66 | f0, 67 | parameters.timbre, 68 | parameters.morph, 69 | parameters.harmonics, 70 | &mut self.temp_buffer_1, 71 | &mut self.temp_buffer_2, 72 | out, 73 | NoiseType::Square, 74 | VcaType::Swing, 75 | true, 76 | false, 77 | ); 78 | 79 | self.hi_hat_2.render( 80 | sustain, 81 | trigger, 82 | parameters.accent, 83 | f0, 84 | parameters.timbre, 85 | parameters.morph, 86 | parameters.harmonics, 87 | &mut self.temp_buffer_1, 88 | &mut self.temp_buffer_2, 89 | aux, 90 | NoiseType::RingMod, 91 | VcaType::Linear, 92 | false, 93 | true, 94 | ); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/engine/mod.rs: -------------------------------------------------------------------------------- 1 | //! Top-level module for the engines. 2 | 3 | // Based on MIT-licensed code (c) 2016 by Emilie Gillet (emilie.o.gillet@gmail.com) 4 | 5 | pub mod additive_engine; 6 | pub mod bass_drum_engine; 7 | pub mod chord_engine; 8 | pub mod fm_engine; 9 | pub mod grain_engine; 10 | pub mod hihat_engine; 11 | pub mod modal_engine; 12 | pub mod noise_engine; 13 | pub mod particle_engine; 14 | pub mod snare_drum_engine; 15 | pub mod speech_engine; 16 | pub mod string_engine; 17 | pub mod swarm_engine; 18 | pub mod virtual_analog_engine; 19 | pub mod waveshaping_engine; 20 | pub mod wavetable_engine; 21 | 22 | use crate::utils::units::semitones_to_ratio; 23 | use crate::A0; 24 | 25 | pub trait Engine { 26 | fn init(&mut self); 27 | 28 | fn reset(&mut self) {} 29 | 30 | fn render( 31 | &mut self, 32 | parameters: &EngineParameters, 33 | out: &mut [f32], 34 | aux: &mut [f32], 35 | already_enveloped: &mut bool, 36 | ); 37 | } 38 | 39 | #[derive(Debug, Default)] 40 | pub struct EngineParameters { 41 | /// Trigger signal state 42 | pub trigger: TriggerState, 43 | 44 | /// Pitch in semitones 45 | /// Range: -119.0 - 120.0 46 | pub note: f32, 47 | 48 | /// Sweeps the spectral content from dark/sparse to bright/dense. 49 | /// Range: 0.0 - 1.0 50 | pub timbre: f32, 51 | 52 | /// Lateral timbral variations. 53 | /// Range: 0.0 - 1.0 54 | pub morph: f32, 55 | 56 | /// Frequency spread or the balance between the various constituents of the tone. 57 | /// Range: 0.0 - 1.0 58 | pub harmonics: f32, 59 | 60 | /// Level setting 61 | /// Range: 0.0 - 1.0 62 | pub accent: f32, 63 | } 64 | 65 | #[derive(Debug, PartialEq, Eq)] 66 | pub enum TriggerState { 67 | Low = 0, 68 | RisingEdge = 1, 69 | Unpatched = 2, 70 | High = 4, 71 | } 72 | 73 | impl Default for TriggerState { 74 | fn default() -> Self { 75 | Self::Low 76 | } 77 | } 78 | 79 | #[inline] 80 | pub fn note_to_frequency(mut midi_note: f32) -> f32 { 81 | midi_note -= 9.0; 82 | midi_note = midi_note.clamp(-128.0, 127.0); 83 | 84 | A0 * 0.25 * semitones_to_ratio(midi_note) 85 | } 86 | -------------------------------------------------------------------------------- /src/engine/modal_engine.rs: -------------------------------------------------------------------------------- 1 | //! Modal resonator. 2 | //! 3 | //! Engine parameters: 4 | //! - *HARMONICS:* amount of inharmonicity, or material selection. 5 | //! - *TIMBRE:* excitation brightness and dust density. 6 | //! - *MORPH:* decay time (energy absorption). 7 | //! 8 | //! *AUX* signal: raw exciter signal. 9 | //! 10 | //! When the *TRIG* input is not patched, the resonator is excited by dust (particle) noise. 11 | //! Otherwise, the resonator is excited by a short burst of filtered white noise, 12 | //! or by a low-pass filtered click. 13 | 14 | // Based on MIT-licensed code (c) 2016 by Emilie Gillet (emilie.o.gillet@gmail.com) 15 | 16 | use alloc::boxed::Box; 17 | use alloc::vec; 18 | 19 | use super::{note_to_frequency, Engine, EngineParameters, TriggerState}; 20 | use crate::physical_modelling::modal_voice::ModalVoice; 21 | use crate::utils::one_pole; 22 | 23 | #[derive(Debug)] 24 | pub struct ModalEngine { 25 | voice: ModalVoice, 26 | harmonics_lp: f32, 27 | 28 | temp_buffer_1: Box<[f32]>, 29 | temp_buffer_2: Box<[f32]>, 30 | } 31 | 32 | impl ModalEngine { 33 | pub fn new(block_size: usize) -> Self { 34 | Self { 35 | voice: ModalVoice::default(), 36 | harmonics_lp: 0.0, 37 | temp_buffer_1: vec![0.0; block_size].into_boxed_slice(), 38 | temp_buffer_2: vec![0.0; block_size].into_boxed_slice(), 39 | } 40 | } 41 | } 42 | 43 | impl Engine for ModalEngine { 44 | fn init(&mut self) { 45 | self.harmonics_lp = 0.0; 46 | self.reset(); 47 | } 48 | 49 | fn reset(&mut self) { 50 | self.voice.init(); 51 | } 52 | 53 | #[inline] 54 | fn render( 55 | &mut self, 56 | parameters: &EngineParameters, 57 | out: &mut [f32], 58 | aux: &mut [f32], 59 | _already_enveloped: &mut bool, 60 | ) { 61 | out.fill(0.0); 62 | aux.fill(0.0); 63 | 64 | one_pole(&mut self.harmonics_lp, parameters.harmonics, 0.01); 65 | 66 | let sustain = matches!(parameters.trigger, TriggerState::Unpatched); 67 | let trigger = matches!(parameters.trigger, TriggerState::RisingEdge); 68 | 69 | self.voice.render( 70 | sustain, 71 | trigger, 72 | parameters.accent, 73 | note_to_frequency(parameters.note), 74 | self.harmonics_lp, 75 | parameters.timbre, 76 | parameters.morph, 77 | &mut self.temp_buffer_1, 78 | &mut self.temp_buffer_2, 79 | out, 80 | aux, 81 | ); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/engine/particle_engine.rs: -------------------------------------------------------------------------------- 1 | //! Particle noise. 2 | //! 3 | //! Dust noise processed by networks of all-pass or band-pass filters. 4 | //! 5 | //! Engine parameters: 6 | //! - *HARMONICS:* amount of frequency randomization. 7 | //! - *TIMBRE:* particle density. 8 | //! - *MORPH:* filter type - reverberating all-pass network before 12 o’clock, 9 | //! then increasingly resonant band-pass filters. 10 | //! 11 | //! *AUX* signal: raw dust noise. 12 | 13 | // Based on MIT-licensed code (c) 2016 by Emilie Gillet (emilie.o.gillet@gmail.com) 14 | 15 | use alloc::boxed::Box; 16 | use alloc::vec; 17 | 18 | #[allow(unused_imports)] 19 | use num_traits::float::Float; 20 | 21 | use super::{note_to_frequency, Engine, EngineParameters, TriggerState}; 22 | use crate::fx::diffuser::Diffuser; 23 | use crate::noise::particle::Particle; 24 | use crate::utils::filter::{FilterMode, FrequencyApproximation, Svf}; 25 | use crate::utils::units::semitones_to_ratio; 26 | 27 | const NUM_PARTICLES: usize = 6; 28 | 29 | #[derive(Debug)] 30 | pub struct ParticleEngine { 31 | particle: [Particle; NUM_PARTICLES], 32 | diffuser: Diffuser, 33 | post_filter: Svf, 34 | 35 | temp_buffer: Box<[f32]>, 36 | } 37 | 38 | impl ParticleEngine { 39 | pub fn new(block_size: usize) -> Self { 40 | Self { 41 | particle: core::array::from_fn(|_| Particle::new()), 42 | diffuser: Diffuser::new(), 43 | post_filter: Svf::new(), 44 | temp_buffer: vec![0.0; block_size].into_boxed_slice(), 45 | } 46 | } 47 | } 48 | 49 | impl Engine for ParticleEngine { 50 | fn init(&mut self) { 51 | for particle in &mut self.particle { 52 | particle.init(); 53 | } 54 | self.diffuser.init(); 55 | self.post_filter.init(); 56 | self.reset(); 57 | } 58 | 59 | fn reset(&mut self) { 60 | self.diffuser.reset(); 61 | } 62 | 63 | #[inline] 64 | fn render( 65 | &mut self, 66 | parameters: &EngineParameters, 67 | out: &mut [f32], 68 | aux: &mut [f32], 69 | _already_enveloped: &mut bool, 70 | ) { 71 | let f0 = note_to_frequency(parameters.note); 72 | let density_sqrt = note_to_frequency(60.0 + parameters.timbre * parameters.timbre * 72.0); 73 | let density = density_sqrt * density_sqrt * (1.0 / NUM_PARTICLES as f32); 74 | let gain = 1.0 / density; 75 | let q_sqrt = semitones_to_ratio(if parameters.morph >= 0.5 { 76 | (parameters.morph - 0.5) * 120.0 77 | } else { 78 | 0.0 79 | }); 80 | let q = 0.5 + q_sqrt * q_sqrt; 81 | let spread = 48.0 * parameters.harmonics * parameters.harmonics; 82 | let raw_diffusion_sqrt = 2.0 * (parameters.morph - 0.5).abs(); 83 | let raw_diffusion = raw_diffusion_sqrt * raw_diffusion_sqrt; 84 | let diffusion = if parameters.morph < 0.5 { 85 | raw_diffusion 86 | } else { 87 | 0.0 88 | }; 89 | let sync = matches!(parameters.trigger, TriggerState::RisingEdge); 90 | 91 | out.fill(0.0); 92 | aux.fill(0.0); 93 | 94 | for particle in &mut self.particle { 95 | particle.render(sync, density, gain, f0, spread, q, out, aux); 96 | } 97 | 98 | self.post_filter 99 | .set_f_q(f32::min(f0, 0.49), 0.5, FrequencyApproximation::Dirty); 100 | self.post_filter 101 | .process_buffer(out, &mut self.temp_buffer, FilterMode::LowPass); 102 | 103 | out.copy_from_slice(&self.temp_buffer); 104 | 105 | self.diffuser 106 | .process(0.8 * diffusion * diffusion, 0.5 * diffusion + 0.25, out); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/engine/snare_drum_engine.rs: -------------------------------------------------------------------------------- 1 | //! Analog snare drum model. 2 | //! 3 | //! Engine parameters: 4 | //! - *HARMONICS:* balance of the harmonic and noisy components. 5 | //! - *TIMBRE:* balance between the different modes of the drum. 6 | //! - *MORPH:* decay time. 7 | //! 8 | //! Outputs: 9 | //! - *OUT* signal: bunch of bridged T-networks, one for each mode of 10 | //! the shell, excited by a nicely shaped pulse; plus some band-pass filtered noise. 11 | //! - *AUX* signal: pair of frequency-modulated sine VCO, mixed with high-pass filtered noise. 12 | 13 | // Based on MIT-licensed code (c) 2016 by Emilie Gillet (emilie.o.gillet@gmail.com) 14 | 15 | use super::{note_to_frequency, Engine, EngineParameters, TriggerState}; 16 | use crate::drums::analog_snare_drum::AnalogSnareDrum; 17 | use crate::drums::synthetic_snare_drum::SyntheticSnareDrum; 18 | 19 | #[derive(Debug, Default)] 20 | pub struct SnareDrumEngine { 21 | analog_snare_drum: AnalogSnareDrum, 22 | synthetic_snare_drum: SyntheticSnareDrum, 23 | } 24 | 25 | impl SnareDrumEngine { 26 | pub fn new() -> Self { 27 | Self::default() 28 | } 29 | } 30 | 31 | impl Engine for SnareDrumEngine { 32 | fn init(&mut self) { 33 | self.analog_snare_drum.init(); 34 | self.synthetic_snare_drum.init(); 35 | } 36 | 37 | #[inline] 38 | fn render( 39 | &mut self, 40 | parameters: &EngineParameters, 41 | out: &mut [f32], 42 | aux: &mut [f32], 43 | _already_enveloped: &mut bool, 44 | ) { 45 | let f0 = note_to_frequency(parameters.note); 46 | 47 | let sustain = matches!(parameters.trigger, TriggerState::Unpatched); 48 | let trigger = matches!(parameters.trigger, TriggerState::RisingEdge); 49 | 50 | self.analog_snare_drum.render( 51 | sustain, 52 | trigger, 53 | parameters.accent, 54 | f0, 55 | parameters.timbre, 56 | parameters.morph, 57 | parameters.harmonics, 58 | out, 59 | ); 60 | 61 | self.synthetic_snare_drum.render( 62 | sustain, 63 | trigger, 64 | parameters.accent, 65 | f0, 66 | parameters.timbre, 67 | parameters.morph, 68 | parameters.harmonics, 69 | aux, 70 | ); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/engine/string_engine.rs: -------------------------------------------------------------------------------- 1 | //! Inharmonic string modeling. 2 | //! 3 | //! Engine parameters: 4 | //! - *HARMONICS:* amount of inharmonicity, or material selection. 5 | //! - *TIMBRE:* excitation brightness and dust density. 6 | //! - *MORPH:* decay time (energy absorption). 7 | //! 8 | //! *AUX* signal: raw exciter signal. 9 | //! 10 | //! When the *TRIG* input is not patched, the string is excited by dust (particle) noise. 11 | //! Otherwise, the string is excited by a short burst of filtered white noise, 12 | //! or by a low-pass filtered click. 13 | 14 | // Based on MIT-licensed code (c) 2016 by Emilie Gillet (emilie.o.gillet@gmail.com) 15 | 16 | use alloc::boxed::Box; 17 | use alloc::vec; 18 | 19 | use super::{note_to_frequency, Engine, EngineParameters, TriggerState}; 20 | use crate::physical_modelling::string_voice::StringVoice; 21 | use crate::utils::delay_line::DelayLine; 22 | 23 | const NUM_STRINGS: usize = 3; 24 | 25 | #[derive(Debug)] 26 | pub struct StringEngine { 27 | voice: [StringVoice; NUM_STRINGS], 28 | 29 | f0: [f32; NUM_STRINGS], 30 | f0_delay: DelayLine, 31 | active_string: usize, 32 | 33 | temp_buffer_1: Box<[f32]>, 34 | temp_buffer_2: Box<[f32]>, 35 | } 36 | 37 | impl StringEngine { 38 | pub fn new(block_size: usize) -> Self { 39 | Self { 40 | voice: core::array::from_fn(|_| StringVoice::new()), 41 | f0: [0.0; NUM_STRINGS], 42 | f0_delay: DelayLine::::new(), 43 | active_string: 0, 44 | temp_buffer_1: vec![0.0; block_size].into_boxed_slice(), 45 | temp_buffer_2: vec![0.0; block_size].into_boxed_slice(), 46 | } 47 | } 48 | } 49 | 50 | impl Engine for StringEngine { 51 | fn init(&mut self) { 52 | for voice in &mut self.voice { 53 | voice.init(); 54 | } 55 | self.f0 = [0.0; NUM_STRINGS]; 56 | self.active_string = NUM_STRINGS - 1; 57 | self.reset(); 58 | } 59 | 60 | fn reset(&mut self) { 61 | self.f0_delay.reset(); 62 | for voice in &mut self.voice { 63 | voice.reset(); 64 | } 65 | } 66 | 67 | #[inline] 68 | fn render( 69 | &mut self, 70 | parameters: &EngineParameters, 71 | out: &mut [f32], 72 | aux: &mut [f32], 73 | _already_enveloped: &mut bool, 74 | ) { 75 | let sustain = matches!(parameters.trigger, TriggerState::Unpatched); 76 | let trigger = matches!(parameters.trigger, TriggerState::RisingEdge); 77 | 78 | if trigger { 79 | // 8 in original firmware version. 80 | // 05.01.18: mic.w: problem with microbrute. 81 | self.f0[self.active_string] = self.f0_delay.read_with_delay(14); 82 | self.active_string = (self.active_string + 1) % NUM_STRINGS; 83 | } 84 | 85 | let f0 = note_to_frequency(parameters.note); 86 | self.f0[self.active_string] = f0; 87 | self.f0_delay.write(f0); 88 | 89 | out.fill(0.0); 90 | aux.fill(0.0); 91 | 92 | for i in 0..NUM_STRINGS { 93 | self.voice[i].render( 94 | sustain && i == self.active_string, 95 | trigger && i == self.active_string, 96 | parameters.accent, 97 | self.f0[i], 98 | parameters.harmonics, 99 | parameters.timbre * parameters.timbre, 100 | parameters.morph, 101 | &mut self.temp_buffer_1, 102 | &mut self.temp_buffer_2, 103 | out, 104 | aux, 105 | ); 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/engine2/arpeggiator.rs: -------------------------------------------------------------------------------- 1 | //! Arpeggiator. 2 | 3 | // Based on MIT-licensed code (c) 2021 by Emilie Gillet (emilie.o.gillet@gmail.com) 4 | 5 | use crate::utils::random; 6 | 7 | #[derive(Debug, Default, PartialEq, Eq)] 8 | pub enum ArpeggiatorMode { 9 | #[default] 10 | Up, 11 | 12 | Down, 13 | UpDown, 14 | Random, 15 | Last, 16 | } 17 | 18 | impl From for ArpeggiatorMode 19 | where 20 | T: Into, 21 | { 22 | fn from(value: T) -> Self { 23 | match value.into() { 24 | 1 => ArpeggiatorMode::Down, 25 | 2 => ArpeggiatorMode::UpDown, 26 | 3 => ArpeggiatorMode::Random, 27 | 4 => ArpeggiatorMode::Last, 28 | _ => ArpeggiatorMode::Up, 29 | } 30 | } 31 | } 32 | 33 | #[derive(Debug, Default)] 34 | pub struct Arpeggiator { 35 | mode: ArpeggiatorMode, 36 | range: i32, 37 | 38 | note: i32, 39 | octave: i32, 40 | direction: i32, 41 | } 42 | 43 | impl Arpeggiator { 44 | pub fn new() -> Self { 45 | Self::default() 46 | } 47 | 48 | pub fn init(&mut self) { 49 | self.mode = ArpeggiatorMode::Up; 50 | self.reset(); 51 | } 52 | 53 | pub fn reset(&mut self) { 54 | self.note = 0; 55 | self.octave = 0; 56 | self.direction = 1; 57 | } 58 | 59 | pub fn set_mode(&mut self, mode: ArpeggiatorMode) { 60 | self.mode = mode; 61 | } 62 | 63 | pub fn set_range(&mut self, range: i32) { 64 | self.range = range; 65 | } 66 | 67 | pub fn note(&self) -> i32 { 68 | self.note 69 | } 70 | 71 | pub fn octave(&self) -> i32 { 72 | self.octave 73 | } 74 | 75 | pub fn clock(&mut self, num_notes: i32) { 76 | if num_notes == 0 { 77 | return; 78 | } 79 | 80 | if num_notes == 1 && self.range == 1 { 81 | self.note = 0; 82 | self.octave = 0; 83 | return; 84 | } 85 | 86 | if self.mode == ArpeggiatorMode::Random { 87 | loop { 88 | let w = random::get_word(); 89 | let octave = ((w >> 4) as i32) % self.range; 90 | let note = ((w >> 20) as i32) % num_notes; 91 | if octave != self.octave || note != self.note { 92 | self.octave = octave; 93 | self.note = note; 94 | break; 95 | } 96 | } 97 | return; 98 | } 99 | 100 | if self.mode == ArpeggiatorMode::Up { 101 | self.direction = 1; 102 | } 103 | 104 | if self.mode == ArpeggiatorMode::Down { 105 | self.direction = -1; 106 | } 107 | 108 | self.note += self.direction; 109 | 110 | let mut done = false; 111 | 112 | while !done { 113 | done = true; 114 | 115 | if self.note >= num_notes || self.note < 0 { 116 | self.octave += self.direction; 117 | self.note = if self.direction > 0 { 0 } else { num_notes - 1 }; 118 | } 119 | 120 | if self.octave >= self.range || self.octave < 0 { 121 | self.octave = if self.direction > 0 { 122 | 0 123 | } else { 124 | self.range - 1 125 | }; 126 | if self.mode == ArpeggiatorMode::UpDown { 127 | self.direction = -self.direction; 128 | self.note = if self.direction > 0 { 1 } else { num_notes - 2 }; 129 | self.octave = if self.direction > 0 { 130 | 0 131 | } else { 132 | self.range - 1 133 | }; 134 | done = false; 135 | } 136 | } 137 | } 138 | 139 | self.note = self.note.max(0); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/engine2/mod.rs: -------------------------------------------------------------------------------- 1 | //! Engines added in firmware 1.2 2 | 3 | pub mod arpeggiator; 4 | pub mod chiptune_engine; 5 | pub mod phase_distortion_engine; 6 | pub mod six_op_engine; 7 | pub mod string_machine_engine; 8 | pub mod virtual_analog_vcf_engine; 9 | pub mod wave_terrain_engine; 10 | -------------------------------------------------------------------------------- /src/engine2/phase_distortion_engine.rs: -------------------------------------------------------------------------------- 1 | //! Phase distortion and phase modulation with an asymmetric triangle as the modulator. 2 | //! 3 | //! Engine parameters: 4 | //! - *HARMONICS:* distortion frequency. 5 | //! - *TIMBRE:* distortion amount. 6 | //! - *MORPH:* distortion asymmetry. 7 | //! 8 | //! *OUT* signal: carrier is sync'ed (phase distortion). 9 | //! *AUX* signal: carrier is free-running (phase modulation)). 10 | 11 | // Based on MIT-licensed code (c) 2021 by Emilie Gillet (emilie.o.gillet@gmail.com) 12 | 13 | use alloc::boxed::Box; 14 | use alloc::vec; 15 | 16 | use crate::engine::{note_to_frequency, Engine, EngineParameters}; 17 | use crate::oscillator::sine_oscillator::sine; 18 | use crate::oscillator::variable_shape_oscillator::VariableShapeOscillator; 19 | use crate::resources::fm::LUT_FM_FREQUENCY_QUANTIZER; 20 | use crate::utils::interpolate; 21 | use crate::utils::units::semitones_to_ratio; 22 | 23 | #[derive(Debug)] 24 | pub struct PhaseDistortionEngine { 25 | shaper: VariableShapeOscillator, 26 | modulator: VariableShapeOscillator, 27 | 28 | temp_buffer_1: Box<[f32]>, 29 | temp_buffer_2: Box<[f32]>, 30 | } 31 | 32 | impl PhaseDistortionEngine { 33 | pub fn new(block_size: usize) -> Self { 34 | Self { 35 | shaper: VariableShapeOscillator::new(), 36 | modulator: VariableShapeOscillator::new(), 37 | temp_buffer_1: vec![0.0; block_size * 2].into_boxed_slice(), 38 | temp_buffer_2: vec![0.0; block_size * 2].into_boxed_slice(), 39 | } 40 | } 41 | } 42 | 43 | impl Engine for PhaseDistortionEngine { 44 | fn init(&mut self) { 45 | self.shaper.init(); 46 | self.modulator.init(); 47 | } 48 | 49 | fn render( 50 | &mut self, 51 | parameters: &EngineParameters, 52 | out: &mut [f32], 53 | aux: &mut [f32], 54 | _already_enveloped: &mut bool, 55 | ) { 56 | let f0 = 0.5 * note_to_frequency(parameters.note); 57 | let modulator_f = f32::min( 58 | 0.25, 59 | f0 * semitones_to_ratio(interpolate( 60 | &LUT_FM_FREQUENCY_QUANTIZER, 61 | parameters.harmonics, 62 | 128.0, 63 | )), 64 | ); 65 | let pw = 0.5 + parameters.morph * 0.49; 66 | let amount = 8.0 * parameters.timbre * parameters.timbre * (1.0 - modulator_f * 3.8); 67 | 68 | // Upsample by 2x 69 | let synced = &mut self.temp_buffer_1; 70 | let free_running = &mut self.temp_buffer_2; 71 | self.shaper 72 | .render(f0, modulator_f, pw, 0.0, amount, synced, true, true); 73 | self.modulator 74 | .render(f0, modulator_f, pw, 0.0, amount, free_running, false, true); 75 | 76 | for (n, (out_sample, aux_sample)) in out.iter_mut().zip(aux.iter_mut()).enumerate() { 77 | // Naive 0.5x downsampling. 78 | *out_sample = 0.5 * sine(synced[n * 2] + 0.25); 79 | *out_sample += 0.5 * sine(synced[n * 2 + 1] + 0.25); 80 | 81 | *aux_sample = 0.5 * sine(free_running[n * 2] + 0.25); 82 | *aux_sample += 0.5 * sine(free_running[n * 2 + 1] + 0.25); 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/envelope.rs: -------------------------------------------------------------------------------- 1 | //! Envelope for the internal LPG. 2 | 3 | // Based on MIT-licensed code (c) 2016 by Emilie Gillet (emilie.o.gillet@gmail.com) 4 | 5 | #[derive(Debug, Default)] 6 | pub struct LpgEnvelope { 7 | vactrol_state: f32, 8 | gain: f32, 9 | frequency: f32, 10 | hf_bleed: f32, 11 | ramp_up: bool, 12 | } 13 | 14 | impl LpgEnvelope { 15 | pub fn new() -> Self { 16 | Self::default() 17 | } 18 | 19 | pub fn init(&mut self) { 20 | self.vactrol_state = 0.0; 21 | self.gain = 1.0; 22 | self.frequency = 0.5; 23 | self.hf_bleed = 0.0; 24 | self.ramp_up = false; 25 | } 26 | 27 | pub fn trigger(&mut self) { 28 | self.ramp_up = true; 29 | } 30 | 31 | #[inline] 32 | pub fn process_ping(&mut self, attack: f32, short_decay: f32, decay_tail: f32, hf: f32) { 33 | if self.ramp_up { 34 | self.vactrol_state += attack; 35 | if self.vactrol_state >= 1.0 { 36 | self.vactrol_state = 1.0; 37 | self.ramp_up = false; 38 | } 39 | } 40 | self.process_lp( 41 | if self.ramp_up { 42 | self.vactrol_state 43 | } else { 44 | 0.0 45 | }, 46 | short_decay, 47 | decay_tail, 48 | hf, 49 | ); 50 | } 51 | 52 | #[inline] 53 | pub fn process_lp(&mut self, level: f32, short_decay: f32, decay_tail: f32, hf: f32) { 54 | let vactrol_input = level; 55 | let vactrol_error = vactrol_input - self.vactrol_state; 56 | let vactrol_state_2 = self.vactrol_state * self.vactrol_state; 57 | let vactrol_state_4 = vactrol_state_2 * vactrol_state_2; 58 | let tail = 1.0 - self.vactrol_state; 59 | let tail_2 = tail * tail; 60 | let vactrol_coefficient = if vactrol_error > 0.0 { 61 | 0.6 62 | } else { 63 | short_decay + (1.0 - vactrol_state_4) * decay_tail 64 | }; 65 | self.vactrol_state += vactrol_coefficient * vactrol_error; 66 | 67 | self.gain = self.vactrol_state; 68 | self.frequency = 0.003 + 0.3 * vactrol_state_4 + hf * 0.04; 69 | self.hf_bleed = (tail_2 + (1.0 - tail_2) * hf) * hf * hf; 70 | } 71 | 72 | #[inline] 73 | pub fn gain(&self) -> f32 { 74 | self.gain 75 | } 76 | 77 | #[inline] 78 | pub fn frequency(&self) -> f32 { 79 | self.frequency 80 | } 81 | 82 | #[inline] 83 | pub fn hf_bleed(&self) -> f32 { 84 | self.hf_bleed 85 | } 86 | } 87 | 88 | #[derive(Debug, Default)] 89 | pub struct DecayEnvelope { 90 | value: f32, 91 | } 92 | 93 | impl DecayEnvelope { 94 | pub fn new() -> Self { 95 | Self::default() 96 | } 97 | 98 | pub fn init(&mut self) { 99 | self.value = 0.0; 100 | } 101 | 102 | pub fn trigger(&mut self) { 103 | self.value = 1.0; 104 | } 105 | 106 | #[inline] 107 | pub fn process(&mut self, decay: f32) { 108 | self.value *= 1.0 - decay; 109 | } 110 | 111 | #[inline] 112 | pub fn value(&self) -> f32 { 113 | self.value 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/fm/mod.rs: -------------------------------------------------------------------------------- 1 | //! Modules for the 6-operator FM synth. 2 | 3 | pub mod algorithms; 4 | pub mod dx_units; 5 | pub mod envelope; 6 | pub mod lfo; 7 | pub mod operator; 8 | pub mod patch; 9 | pub mod voice; 10 | -------------------------------------------------------------------------------- /src/fm/operator.rs: -------------------------------------------------------------------------------- 1 | //! FM Operator. 2 | 3 | // Based on MIT-licensed code (c) 2021 by Emilie Gillet (emilie.o.gillet@gmail.com) 4 | 5 | use core::cell::RefCell; 6 | 7 | use crate::oscillator::sine_oscillator::sine_pm; 8 | 9 | #[derive(Debug, Default)] 10 | pub struct Operator { 11 | pub phase: u32, 12 | pub amplitude: f32, 13 | } 14 | 15 | impl Operator { 16 | pub fn new() -> Self { 17 | Self::default() 18 | } 19 | 20 | pub fn reset(&mut self) { 21 | self.phase = 0; 22 | self.amplitude = 0.0; 23 | } 24 | } 25 | 26 | pub enum ModulationSource { 27 | External = -2, 28 | None = -1, 29 | Feedback = 0, 30 | } 31 | 32 | pub type RenderFn = fn( 33 | ops: &mut [Operator], 34 | f: &[f32], 35 | a: &[f32], 36 | fb_state: &mut [f32], 37 | fb_amount: i32, 38 | modulation: &RefCell<&mut [f32]>, 39 | out: &RefCell<&mut [f32]>, 40 | ); 41 | 42 | #[allow(clippy::too_many_arguments)] 43 | pub fn render_operators( 44 | ops: &mut [Operator], 45 | f: &[f32], 46 | a: &[f32], 47 | fb_state: &mut [f32], 48 | fb_amount: i32, 49 | modulation: &RefCell<&mut [f32]>, 50 | out: &RefCell<&mut [f32]>, 51 | ) { 52 | let mut frequency = [0u32; N]; 53 | let mut phase = [0u32; N]; 54 | let mut amplitude = [0.0; N]; 55 | let mut amplitude_increment = [0.0; N]; 56 | 57 | let scale = 1.0 / out.borrow().len() as f32; 58 | 59 | for i in 0..N { 60 | frequency[i] = (f32::min(f[i], 0.5) * 4294967296.0) as u32; 61 | phase[i] = ops[i].phase; 62 | amplitude[i] = ops[i].amplitude; 63 | amplitude_increment[i] = (f32::min(a[i], 4.0) - amplitude[i]) * scale; 64 | } 65 | 66 | if MODULATION_SOURCE >= ModulationSource::Feedback as i32 { 67 | let fb_scale = if fb_amount != 0 { 68 | (1 << fb_amount) as f32 / 512.0 69 | } else { 70 | 0.0 71 | }; 72 | 73 | let mut previous_0 = fb_state[0]; 74 | let mut previous_1 = fb_state[1]; 75 | 76 | for out_sample in out.borrow_mut().iter_mut() { 77 | let mut pm = (previous_0 + previous_1) * fb_scale; 78 | 79 | for i in 0..N { 80 | phase[i] = phase[i].wrapping_add(frequency[i]); 81 | pm = sine_pm(phase[i], pm) * amplitude[i]; 82 | amplitude[i] += amplitude_increment[i]; 83 | if i == MODULATION_SOURCE as usize { 84 | previous_1 = previous_0; 85 | previous_0 = pm; 86 | } 87 | } 88 | 89 | if ADDITIVE { 90 | *out_sample += pm; 91 | } else { 92 | *out_sample = pm; 93 | } 94 | 95 | for i in 0..N { 96 | ops[i].phase = phase[i]; 97 | ops[i].amplitude = amplitude[i]; 98 | } 99 | 100 | fb_state[0] = previous_0; 101 | fb_state[1] = previous_1; 102 | } 103 | } else if MODULATION_SOURCE == ModulationSource::External as i32 { 104 | let size = out.borrow().len(); 105 | 106 | for i in 0..size { 107 | let mut pm = modulation.borrow()[i]; 108 | 109 | for i in 0..N { 110 | phase[i] = phase[i].wrapping_add(frequency[i]); 111 | pm = sine_pm(phase[i], pm) * amplitude[i]; 112 | amplitude[i] += amplitude_increment[i]; 113 | } 114 | 115 | if ADDITIVE { 116 | out.borrow_mut()[i] += pm; 117 | } else { 118 | out.borrow_mut()[i] = pm; 119 | } 120 | 121 | for i in 0..N { 122 | ops[i].phase = phase[i]; 123 | ops[i].amplitude = amplitude[i]; 124 | } 125 | } 126 | } else { 127 | for out_sample in out.borrow_mut().iter_mut() { 128 | let mut pm = 0.0; 129 | 130 | for i in 0..N { 131 | phase[i] = phase[i].wrapping_add(frequency[i]); 132 | pm = sine_pm(phase[i], pm) * amplitude[i]; 133 | amplitude[i] += amplitude_increment[i]; 134 | } 135 | 136 | if ADDITIVE { 137 | *out_sample += pm; 138 | } else { 139 | *out_sample = pm; 140 | } 141 | 142 | for i in 0..N { 143 | ops[i].phase = phase[i]; 144 | ops[i].amplitude = amplitude[i]; 145 | } 146 | } 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/fm/patch.rs: -------------------------------------------------------------------------------- 1 | //! DX7 patch. 2 | 3 | // Based on MIT-licensed code (c) 2021 by Emilie Gillet (emilie.o.gillet@gmail.com) 4 | 5 | pub const SYX_SIZE: usize = 128; 6 | 7 | #[derive(Debug, Default, Clone, PartialEq, Eq)] 8 | pub struct Patch { 9 | pub op: [Operator; 6], 10 | pub pitch_envelope: Envelope, 11 | pub algorithm: u8, 12 | pub feedback: u8, 13 | pub reset_phase: u8, 14 | pub modulations: ModulationParameters, 15 | pub transpose: u8, 16 | pub name: [u8; 10], 17 | pub active_operators: u8, 18 | } 19 | 20 | impl Patch { 21 | pub fn new() -> Self { 22 | Self::default() 23 | } 24 | 25 | pub fn unpack(&mut self, data: &[u8]) { 26 | for (i, op) in self.op.iter_mut().enumerate() { 27 | let op_data = &data[(i * 17)..]; 28 | 29 | for (j, (rate, level)) in op 30 | .envelope 31 | .rate 32 | .iter_mut() 33 | .zip(op.envelope.level.iter_mut()) 34 | .enumerate() 35 | { 36 | *rate = u8::min(op_data[j] & 0x7F, 99); 37 | *level = u8::min(op_data[4 + j] & 0x7F, 99); 38 | } 39 | 40 | op.keyboard_scaling.break_point = u8::min(op_data[8] & 0x7F, 99); 41 | op.keyboard_scaling.left_depth = u8::min(op_data[9] & 0x7F, 99); 42 | op.keyboard_scaling.right_depth = u8::min(op_data[10] & 0x7F, 99); 43 | op.keyboard_scaling.left_curve = op_data[11] & 0x03; 44 | op.keyboard_scaling.right_curve = (op_data[11] >> 2) & 0x03; 45 | 46 | op.rate_scaling = op_data[12] & 0x07; 47 | op.amp_mod_sensitivity = op_data[13] & 0x03; 48 | op.velocity_sensitivity = (op_data[13] >> 2) & 0x07; 49 | op.level = u8::min(op_data[14] & 0x7F, 99); 50 | op.mode = op_data[15] & 0x01; 51 | op.coarse = (op_data[15] >> 1) & 0x1F; 52 | op.fine = u8::min(op_data[16] & 0x7F, 99); 53 | op.detune = u8::min((op_data[12] >> 3) & 0x0F, 14); 54 | } 55 | 56 | for (j, (rate, level)) in self 57 | .pitch_envelope 58 | .rate 59 | .iter_mut() 60 | .zip(self.pitch_envelope.level.iter_mut()) 61 | .enumerate() 62 | { 63 | *rate = u8::min(data[102 + j] & 0x7F, 99); 64 | *level = u8::min(data[106 + j] & 0x7F, 99); 65 | } 66 | 67 | self.algorithm = data[110] & 0x1F; 68 | self.feedback = data[111] & 0x07; 69 | self.reset_phase = (data[111] >> 3) & 0x01; 70 | 71 | self.modulations.rate = u8::min(data[112] & 0x7F, 99); 72 | self.modulations.delay = u8::min(data[113] & 0x7F, 99); 73 | self.modulations.pitch_mod_depth = u8::min(data[114] & 0x7F, 99); 74 | self.modulations.amp_mod_depth = u8::min(data[115] & 0x7F, 99); 75 | self.modulations.reset_phase = data[116] & 0x01; 76 | self.modulations.waveform = u8::min((data[116] >> 1) & 0x07, 5); 77 | self.modulations.pitch_mod_sensitivity = data[116] >> 4; 78 | 79 | self.transpose = u8::min(data[117] & 0x7F, 48); 80 | 81 | for (i, c) in self.name.iter_mut().enumerate() { 82 | *c = data[118 + i] & 0x7F; 83 | } 84 | 85 | self.active_operators = 0x3F; 86 | } 87 | } 88 | 89 | #[derive(Debug, Default, Clone, PartialEq, Eq)] 90 | pub struct Envelope { 91 | pub rate: [u8; 4], 92 | pub level: [u8; 4], 93 | } 94 | 95 | #[derive(Debug, Default, Clone, PartialEq, Eq)] 96 | pub struct KeyboardScaling { 97 | pub left_depth: u8, 98 | pub right_depth: u8, 99 | pub left_curve: u8, 100 | pub right_curve: u8, 101 | pub break_point: u8, 102 | } 103 | 104 | #[derive(Debug, Default, Clone, PartialEq, Eq)] 105 | pub struct Operator { 106 | pub envelope: Envelope, 107 | pub keyboard_scaling: KeyboardScaling, 108 | 109 | pub rate_scaling: u8, 110 | pub amp_mod_sensitivity: u8, 111 | pub velocity_sensitivity: u8, 112 | pub level: u8, 113 | 114 | pub mode: u8, 115 | pub coarse: u8, 116 | pub fine: u8, // x frequency by 1 + 0.01 x fine 117 | pub detune: u8, 118 | } 119 | 120 | #[derive(Debug, Default, Clone, PartialEq, Eq)] 121 | pub struct ModulationParameters { 122 | pub delay: u8, 123 | pub rate: u8, 124 | pub pitch_mod_depth: u8, 125 | pub amp_mod_depth: u8, 126 | pub reset_phase: u8, 127 | pub waveform: u8, 128 | pub pitch_mod_sensitivity: u8, 129 | } 130 | -------------------------------------------------------------------------------- /src/fx/diffuser.rs: -------------------------------------------------------------------------------- 1 | //! Granular diffuser. 2 | 3 | // Based on MIT-licensed code (c) 2016 by Emilie Gillet (emilie.o.gillet@gmail.com) 4 | 5 | use super::{DataFormat12Bit, FxContext, FxEngine}; 6 | use crate::utils::delay_line::DelayLine; 7 | use crate::SAMPLE_RATE; 8 | 9 | #[derive(Debug, Default)] 10 | pub struct Diffuser { 11 | ap1: DelayLine, 12 | ap2: DelayLine, 13 | ap3: DelayLine, 14 | ap4: DelayLine, 15 | dapa: DelayLine, 16 | dapb: DelayLine, 17 | del: DelayLine, 18 | 19 | engine: FxEngine<8192, DataFormat12Bit>, 20 | lp_decay: f32, 21 | } 22 | 23 | impl Diffuser { 24 | pub fn new() -> Self { 25 | Self { 26 | ap1: DelayLine::new(), 27 | ap2: DelayLine::new(), 28 | ap3: DelayLine::new(), 29 | ap4: DelayLine::new(), 30 | dapa: DelayLine::new(), 31 | dapb: DelayLine::new(), 32 | del: DelayLine::new(), 33 | 34 | engine: FxEngine::new(), 35 | lp_decay: 0.0, 36 | } 37 | } 38 | 39 | pub fn init(&mut self) { 40 | self.engine.set_lfo_frequency(0.3 / SAMPLE_RATE); 41 | self.lp_decay = 0.0; 42 | } 43 | 44 | pub fn reset(&mut self) { 45 | self.engine.clear(); 46 | } 47 | 48 | pub fn clear(&mut self) { 49 | self.ap1.reset(); 50 | self.ap2.reset(); 51 | self.ap3.reset(); 52 | self.ap4.reset(); 53 | self.dapa.reset(); 54 | self.dapb.reset(); 55 | self.del.reset(); 56 | self.engine.clear(); 57 | } 58 | 59 | #[inline] 60 | pub fn process(&mut self, amount: f32, rt: f32, in_out: &mut [f32]) { 61 | let mut c = FxContext::new(); 62 | 63 | let kap = 0.625; 64 | let klp = 0.75; 65 | let mut lp = self.lp_decay; 66 | 67 | for in_out_sample in in_out.iter_mut() { 68 | self.engine.start(&mut c); 69 | 70 | c.read(*in_out_sample); 71 | c.read_line(&mut self.ap1, kap); 72 | c.write_all_pass(&mut self.ap1, -kap); 73 | c.read_line(&mut self.ap2, kap); 74 | c.write_all_pass(&mut self.ap2, -kap); 75 | c.read_line(&mut self.ap3, kap); 76 | c.write_all_pass(&mut self.ap3, -kap); 77 | c.interpolate(&mut self.ap4, 400.0, 43.0, kap); 78 | c.write_all_pass(&mut self.ap4, -kap); 79 | c.interpolate(&mut self.del, 3070.0, 340.0, rt); 80 | c.lp(&mut lp, klp); 81 | c.read_line(&mut self.dapa, -kap); 82 | c.write_all_pass(&mut self.dapa, kap); 83 | c.read_line(&mut self.dapb, kap); 84 | c.write_all_pass(&mut self.dapb, -kap); 85 | c.write_line(&mut self.del, 2.0); 86 | 87 | let mut wet = 0.0; 88 | c.write_with_scale(&mut wet, 0.0); 89 | 90 | *in_out_sample += amount * (wet - *in_out_sample); 91 | } 92 | 93 | self.lp_decay = lp; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/fx/ensemble.rs: -------------------------------------------------------------------------------- 1 | //! Ensemble FX. 2 | 3 | // Based on MIT-licensed code (c) 2014 by Emilie Gillet (emilie.o.gillet@gmail.com) 4 | 5 | use super::{DataFormat32Bit, FxContext, FxEngine}; 6 | use crate::oscillator::sine_oscillator::sine_raw; 7 | use crate::utils::delay_line::DelayLine; 8 | 9 | #[derive(Debug, Default)] 10 | pub struct Ensemble { 11 | line_l: DelayLine, 12 | line_r: DelayLine, 13 | 14 | engine: FxEngine<1024, DataFormat32Bit>, 15 | 16 | amount: f32, 17 | depth: f32, 18 | 19 | phase_1: u32, 20 | phase_2: u32, 21 | } 22 | 23 | impl Ensemble { 24 | pub fn new() -> Self { 25 | Self { 26 | line_l: DelayLine::new(), 27 | line_r: DelayLine::new(), 28 | 29 | engine: FxEngine::new(), 30 | 31 | amount: 0.0, 32 | depth: 0.0, 33 | 34 | phase_1: 0, 35 | phase_2: 0, 36 | } 37 | } 38 | 39 | pub fn init(&mut self) { 40 | self.phase_1 = 0; 41 | self.phase_2 = 0; 42 | } 43 | 44 | pub fn reset(&mut self) { 45 | self.engine.clear(); 46 | } 47 | 48 | pub fn clear(&mut self) { 49 | self.line_l.reset(); 50 | self.line_r.reset(); 51 | self.engine.clear(); 52 | } 53 | 54 | #[inline] 55 | pub fn process(&mut self, left: &mut [f32], right: &mut [f32]) { 56 | let mut c = FxContext::new(); 57 | 58 | for (left_sample, right_sample) in left.iter_mut().zip(right.iter_mut()) { 59 | self.engine.start(&mut c); 60 | let dry_amount = 1.0 - self.amount * 0.5; 61 | 62 | // Update LFO. 63 | let one_third = 1417339207; 64 | let two_third = 2834678415; 65 | 66 | self.phase_1 = self.phase_1.wrapping_add(67289); // 0.75 Hz 67 | self.phase_2 = self.phase_2.wrapping_add(589980); // 6.57 Hz 68 | let slow_0 = sine_raw(self.phase_1); 69 | let slow_120 = sine_raw(self.phase_1.wrapping_add(one_third)); 70 | let slow_240 = sine_raw(self.phase_1.wrapping_add(two_third)); 71 | let fast_0 = sine_raw(self.phase_2); 72 | let fast_120 = sine_raw(self.phase_2.wrapping_add(one_third)); 73 | let fast_240 = sine_raw(self.phase_2.wrapping_add(two_third)); 74 | 75 | // Max deviation: 176 76 | let a = self.depth * 160.0; 77 | let b = self.depth * 16.0; 78 | 79 | let mod_1 = slow_0 * a + fast_0 * b; 80 | let mod_2 = slow_120 * a + fast_120 * b; 81 | let mod_3 = slow_240 * a + fast_240 * b; 82 | 83 | let mut wet = 0.0; 84 | 85 | // Sum L & R channel to send to chorus line. 86 | c.read_with_scale(*left_sample, 1.0); 87 | c.write_line(&mut self.line_l, 0.0); 88 | c.read_with_scale(*right_sample, 1.0); 89 | c.write_line(&mut self.line_r, 0.0); 90 | 91 | c.interpolate(&mut self.line_l, mod_1 + 192.0, 0.0, 0.33); 92 | c.interpolate(&mut self.line_l, mod_2 + 192.0, 0.0, 0.33); 93 | c.interpolate(&mut self.line_r, mod_3 + 192.0, 0.0, 0.33); 94 | c.write(&mut wet); 95 | *left_sample = wet * self.amount + *left_sample * dry_amount; 96 | 97 | c.interpolate(&mut self.line_r, mod_1 + 192.0, 0.0, 0.33); 98 | c.interpolate(&mut self.line_r, mod_2 + 192.0, 0.0, 0.33); 99 | c.interpolate(&mut self.line_l, mod_3 + 192.0, 0.0, 0.33); 100 | c.write(&mut wet); 101 | *right_sample = wet * self.amount + *right_sample * dry_amount; 102 | } 103 | } 104 | 105 | #[inline] 106 | pub fn set_amount(&mut self, amount: f32) { 107 | self.amount = amount; 108 | } 109 | 110 | #[inline] 111 | pub fn set_depth(&mut self, depth: f32) { 112 | self.depth = depth; 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/fx/low_pass_gate.rs: -------------------------------------------------------------------------------- 1 | //! Approximative low pass gate. 2 | 3 | // Based on MIT-licensed code (c) 2014 by Emilie Gillet (emilie.o.gillet@gmail.com) 4 | 5 | use crate::utils::clip_16; 6 | use crate::utils::filter::{FilterMode, FrequencyApproximation, Svf}; 7 | use crate::utils::parameter_interpolator::ParameterInterpolator; 8 | 9 | #[derive(Debug, Default)] 10 | pub struct LowPassGate { 11 | previous_gain: f32, 12 | filter: Svf, 13 | } 14 | 15 | impl LowPassGate { 16 | pub fn new() -> Self { 17 | Self::default() 18 | } 19 | 20 | pub fn init(&mut self) { 21 | self.previous_gain = 0.0; 22 | self.filter.init(); 23 | } 24 | 25 | #[inline] 26 | pub fn process_replacing( 27 | &mut self, 28 | gain: f32, 29 | frequency: f32, 30 | hf_bleed: f32, 31 | in_out: &mut [f32], 32 | ) { 33 | let mut gain_modulation = 34 | ParameterInterpolator::new(&mut self.previous_gain, gain, in_out.len()); 35 | self.filter 36 | .set_f_q(frequency, 0.4, FrequencyApproximation::Dirty); 37 | 38 | for in_out_sample in in_out.iter_mut() { 39 | let s = *in_out_sample * gain_modulation.next(); 40 | let lp = self.filter.process(s, FilterMode::LowPass); 41 | *in_out_sample = lp + (s - lp) * hf_bleed; 42 | } 43 | } 44 | 45 | #[inline] 46 | pub fn process_to_i16( 47 | &mut self, 48 | gain: f32, 49 | frequency: f32, 50 | hf_bleed: f32, 51 | in_: &[f32], 52 | out: &mut [i16], 53 | stride: usize, 54 | ) { 55 | let mut gain_modulation = 56 | ParameterInterpolator::new(&mut self.previous_gain, gain, out.len()); 57 | self.filter 58 | .set_f_q(frequency, 0.4, FrequencyApproximation::Dirty); 59 | 60 | for (in_sample, out_sample) in in_.iter().zip(out.iter_mut().step_by(stride)) { 61 | let s = *in_sample * gain_modulation.next(); 62 | let lp = self.filter.process(s, FilterMode::LowPass); 63 | *out_sample = clip_16(1 + (lp + (s - lp) * hf_bleed) as i32) as i16; 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/fx/overdrive.rs: -------------------------------------------------------------------------------- 1 | //! Distortion/overdrive. 2 | 3 | // Based on MIT-licensed code (c) 2014 by Emilie Gillet (emilie.o.gillet@gmail.com) 4 | 5 | use crate::utils::parameter_interpolator::ParameterInterpolator; 6 | use crate::utils::soft_clip; 7 | 8 | #[derive(Debug, Default)] 9 | pub struct Overdrive { 10 | pre_gain: f32, 11 | post_gain: f32, 12 | } 13 | 14 | impl Overdrive { 15 | pub fn new() -> Self { 16 | Self::default() 17 | } 18 | 19 | pub fn init(&mut self) { 20 | self.pre_gain = 0.0; 21 | self.post_gain = 0.0; 22 | } 23 | 24 | #[inline] 25 | pub fn process(&mut self, drive: f32, in_out: &mut [f32]) { 26 | let drive_2 = drive * drive; 27 | let pre_gain_a = drive * 0.5; 28 | let pre_gain_b = drive_2 * drive_2 * drive * 24.0; 29 | let pre_gain = pre_gain_a + (pre_gain_b - pre_gain_a) * drive_2; 30 | let drive_squashed = drive * (2.0 - drive); 31 | let post_gain = 1.0 / soft_clip(0.33 + drive_squashed * (pre_gain - 0.33)); 32 | 33 | let mut pre_gain_modulation = 34 | ParameterInterpolator::new(&mut self.pre_gain, pre_gain, in_out.len()); 35 | 36 | let mut post_gain_modulation = 37 | ParameterInterpolator::new(&mut self.post_gain, post_gain, in_out.len()); 38 | 39 | for in_out_sample in in_out.iter_mut() { 40 | let pre = pre_gain_modulation.next() * *in_out_sample; 41 | *in_out_sample = soft_clip(pre) * post_gain_modulation.next(); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![doc = include_str!("../README.md")] 2 | #![cfg_attr(not(test), no_std)] 3 | 4 | extern crate alloc; 5 | 6 | pub mod chords; 7 | pub mod downsampler; 8 | pub mod drums; 9 | pub mod engine; 10 | pub mod engine2; 11 | pub mod envelope; 12 | pub mod fm; 13 | pub mod fx; 14 | pub mod noise; 15 | pub mod oscillator; 16 | pub mod physical_modelling; 17 | pub mod resources; 18 | pub mod speech; 19 | pub mod utils; 20 | pub mod voice; 21 | 22 | /// Audio sample rate in Hz. 23 | pub const SAMPLE_RATE: f32 = 48000.0; 24 | 25 | /// Normalized frequency of note A0. 26 | pub const A0: f32 = (440.0 / 8.0) / SAMPLE_RATE; 27 | -------------------------------------------------------------------------------- /src/noise/clocked_noise.rs: -------------------------------------------------------------------------------- 1 | //! Noise processed by a sample and hold running at a target frequency. 2 | 3 | // Based on MIT-licensed code (c) 2016 by Emilie Gillet (emilie.o.gillet@gmail.com) 4 | 5 | use crate::utils::parameter_interpolator::ParameterInterpolator; 6 | use crate::utils::polyblep::{next_blep_sample, this_blep_sample}; 7 | use crate::utils::random; 8 | 9 | #[derive(Debug, Default)] 10 | pub struct ClockedNoise { 11 | // Oscillator state. 12 | phase: f32, 13 | sample: f32, 14 | next_sample: f32, 15 | 16 | // For interpolation of parameters. 17 | frequency: f32, 18 | } 19 | 20 | impl ClockedNoise { 21 | pub fn new() -> Self { 22 | Self::default() 23 | } 24 | 25 | pub fn init(&mut self) { 26 | self.phase = 0.0; 27 | self.sample = 0.0; 28 | self.next_sample = 0.0; 29 | self.frequency = 0.001; 30 | } 31 | 32 | #[inline] 33 | pub fn render(&mut self, sync: bool, mut frequency: f32, out: &mut [f32]) { 34 | frequency = frequency.clamp(0.0, 1.0); 35 | 36 | let mut fm = ParameterInterpolator::new(&mut self.frequency, frequency, out.len()); 37 | 38 | let mut next_sample = self.next_sample; 39 | let mut sample = self.sample; 40 | 41 | if sync { 42 | self.phase = 1.0; 43 | } 44 | 45 | for out_sample in out.iter_mut() { 46 | let mut this_sample = next_sample; 47 | next_sample = 0.0; 48 | 49 | let frequency = fm.next(); 50 | let raw_sample = random::get_float() * 2.0 - 1.0; 51 | let raw_amount = 4.0 * (frequency - 0.25); 52 | let raw_amount = raw_amount.clamp(0.0, 1.0); 53 | 54 | self.phase += frequency; 55 | 56 | if self.phase >= 1.0 { 57 | self.phase -= 1.0; 58 | let t = self.phase / frequency; 59 | let new_sample = raw_sample; 60 | let discontinuity = new_sample - sample; 61 | this_sample += discontinuity * this_blep_sample(t); 62 | next_sample += discontinuity * next_blep_sample(t); 63 | sample = new_sample; 64 | } 65 | next_sample += sample; 66 | *out_sample = this_sample + raw_amount * (raw_sample - this_sample); 67 | } 68 | 69 | self.next_sample = next_sample; 70 | self.sample = sample; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/noise/dust.rs: -------------------------------------------------------------------------------- 1 | //! Randomly clocked samples. 2 | 3 | // Based on MIT-licensed code (c) 2016 by Emilie Gillet (emilie.o.gillet@gmail.com) 4 | 5 | use crate::utils::random; 6 | 7 | #[inline] 8 | pub fn dust(frequency: f32) -> f32 { 9 | let inv_frequency = 1.0 / frequency; 10 | let u = random::get_float(); 11 | 12 | if u < frequency { 13 | u * inv_frequency 14 | } else { 15 | 0.0 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/noise/mod.rs: -------------------------------------------------------------------------------- 1 | //! Noise generators. 2 | 3 | pub mod clocked_noise; 4 | pub mod dust; 5 | pub mod particle; 6 | pub mod smooth_random_generator; 7 | -------------------------------------------------------------------------------- /src/noise/particle.rs: -------------------------------------------------------------------------------- 1 | //! Random impulse train processed by a resonant filter. 2 | 3 | // Based on MIT-licensed code (c) 2016 by Emilie Gillet (emilie.o.gillet@gmail.com) 4 | 5 | use crate::utils::filter::{FilterMode, FrequencyApproximation, Svf}; 6 | use crate::utils::random; 7 | use crate::utils::sqrt; 8 | use crate::utils::units::semitones_to_ratio; 9 | 10 | #[derive(Debug, Default)] 11 | pub struct Particle { 12 | pre_gain: f32, 13 | filter: Svf, 14 | } 15 | 16 | impl Particle { 17 | pub fn new() -> Self { 18 | Self::default() 19 | } 20 | 21 | pub fn init(&mut self) { 22 | self.pre_gain = 0.0; 23 | self.filter.init(); 24 | } 25 | 26 | #[allow(clippy::too_many_arguments)] 27 | #[inline] 28 | pub fn render( 29 | &mut self, 30 | sync: bool, 31 | density: f32, 32 | gain: f32, 33 | frequency: f32, 34 | spread: f32, 35 | q: f32, 36 | out: &mut [f32], 37 | aux: &mut [f32], 38 | ) { 39 | let mut u = random::get_float(); 40 | if sync { 41 | u = density; 42 | } 43 | let mut can_radomize_frequency = true; 44 | 45 | for (out_sample, aux_sample) in out.iter_mut().zip(aux.iter_mut()) { 46 | let mut s = 0.0; 47 | if u <= density { 48 | s = u * gain; 49 | if can_radomize_frequency { 50 | let u = 2.0 * random::get_float() - 1.0; 51 | let f = f32::min(semitones_to_ratio(spread * u) * frequency, 0.25); 52 | self.pre_gain = 0.5 / sqrt(q * f * sqrt(density)); 53 | self.filter.set_f_q(f, q, FrequencyApproximation::Dirty); 54 | // Keep the cutoff constant for this whole block. 55 | can_radomize_frequency = false; 56 | } 57 | } 58 | *aux_sample += s; 59 | *out_sample += self.filter.process(self.pre_gain * s, FilterMode::BandPass); 60 | u = random::get_float(); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/noise/smooth_random_generator.rs: -------------------------------------------------------------------------------- 1 | //! Smooth random generator for the internal modulations. 2 | 3 | // Based on MIT-licensed code (c) 2016 by Emilie Gillet (emilie.o.gillet@gmail.com) 4 | 5 | use crate::utils::random; 6 | 7 | #[derive(Debug, Default)] 8 | pub struct SmoothRandomGenerator { 9 | phase: f32, 10 | from: f32, 11 | interval: f32, 12 | } 13 | 14 | impl SmoothRandomGenerator { 15 | pub fn new() -> Self { 16 | Self::default() 17 | } 18 | 19 | pub fn init(&mut self) { 20 | self.phase = 0.0; 21 | self.from = 0.0; 22 | self.interval = 0.0; 23 | } 24 | 25 | #[inline] 26 | pub fn render(&mut self, frequency: f32) -> f32 { 27 | self.phase += frequency; 28 | 29 | if self.phase >= 1.0 { 30 | self.phase -= 1.0; 31 | self.from += self.interval; 32 | self.interval = random::get_float() * 2.0 - 1.0 - self.from; 33 | } 34 | 35 | let t = self.phase * self.phase * (3.0 - 2.0 * self.phase); 36 | 37 | self.from + self.interval * t 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/oscillator/formant_oscillator.rs: -------------------------------------------------------------------------------- 1 | //! Sinewave with aliasing-free phase reset. 2 | 3 | // Based on MIT-licensed code (c) 2016 by Emilie Gillet (emilie.o.gillet@gmail.com) 4 | 5 | use crate::oscillator::oscillator::MAX_FREQUENCY; 6 | use crate::oscillator::sine_oscillator::sine; 7 | use crate::utils::parameter_interpolator::ParameterInterpolator; 8 | use crate::utils::polyblep::this_blep_sample; 9 | 10 | #[derive(Debug, Default)] 11 | pub struct FormantOscillator { 12 | // Oscillator state. 13 | carrier_phase: f32, 14 | formant_phase: f32, 15 | next_sample: f32, 16 | 17 | // For interpolation of parameters. 18 | carrier_frequency: f32, 19 | formant_frequency: f32, 20 | phase_shift: f32, 21 | } 22 | 23 | impl FormantOscillator { 24 | pub fn new() -> Self { 25 | Self::default() 26 | } 27 | 28 | pub fn init(&mut self) { 29 | self.carrier_phase = 0.0; 30 | self.formant_phase = 0.0; 31 | self.next_sample = 0.0; 32 | 33 | self.carrier_frequency = 0.0; 34 | self.formant_frequency = 0.01; 35 | self.phase_shift = 0.0; 36 | } 37 | 38 | #[inline] 39 | pub fn render( 40 | &mut self, 41 | mut carrier_frequency: f32, 42 | mut formant_frequency: f32, 43 | phase_shift: f32, 44 | out: &mut [f32], 45 | ) { 46 | if carrier_frequency >= MAX_FREQUENCY { 47 | carrier_frequency = MAX_FREQUENCY; 48 | } 49 | if formant_frequency >= MAX_FREQUENCY { 50 | formant_frequency = MAX_FREQUENCY; 51 | } 52 | 53 | let mut carrier_fm = 54 | ParameterInterpolator::new(&mut self.carrier_frequency, carrier_frequency, out.len()); 55 | let mut formant_fm = 56 | ParameterInterpolator::new(&mut self.formant_frequency, formant_frequency, out.len()); 57 | let mut pm = ParameterInterpolator::new(&mut self.phase_shift, phase_shift, out.len()); 58 | 59 | let mut next_sample = self.next_sample; 60 | 61 | for out_sample in out.iter_mut() { 62 | let mut this_sample = next_sample; 63 | next_sample = 0.0; 64 | 65 | let carrier_frequency = carrier_fm.next(); 66 | let formant_frequency = formant_fm.next(); 67 | 68 | self.carrier_phase += carrier_frequency; 69 | 70 | if self.carrier_phase >= 1.0 { 71 | self.carrier_phase -= 1.0; 72 | let reset_time = self.carrier_phase / carrier_frequency; 73 | 74 | let formant_phase_at_reset = 75 | self.formant_phase + (1.0 - reset_time) * formant_frequency; 76 | let before = sine(formant_phase_at_reset + pm.subsample(1.0 - reset_time)); 77 | let after = sine(0.0 + pm.subsample(1.0)); 78 | let discontinuity = after - before; 79 | this_sample += discontinuity * this_blep_sample(reset_time); 80 | next_sample += discontinuity * this_blep_sample(reset_time); 81 | self.formant_phase = reset_time * formant_frequency; 82 | } else { 83 | self.formant_phase += formant_frequency; 84 | if self.formant_phase >= 1.0 { 85 | self.formant_phase -= 1.0; 86 | } 87 | } 88 | 89 | let phase_shift = pm.next(); 90 | next_sample += sine(self.formant_phase + phase_shift); 91 | 92 | *out_sample = this_sample; 93 | } 94 | 95 | self.next_sample = next_sample; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/oscillator/harmonic_oscillator.rs: -------------------------------------------------------------------------------- 1 | //! Harmonic oscillator based on Chebyshev polynomials. 2 | //! 3 | //! Works well for a small number of harmonics. For the higher order harmonics, 4 | //! we need to reinitialize the recurrence by computing two high harmonics. 5 | 6 | // Based on MIT-licensed code (c) 2016 by Emilie Gillet (emilie.o.gillet@gmail.com) 7 | 8 | use crate::oscillator::sine_oscillator::{sine, sine_no_wrap}; 9 | use crate::utils::parameter_interpolator::{ParameterInterpolator, SimpleParameterInterpolator}; 10 | 11 | #[derive(Debug)] 12 | pub struct HarmonicOscillator { 13 | // Oscillator state. 14 | phase: f32, 15 | 16 | // For interpolation of parameters. 17 | frequency: f32, 18 | amplitude: [f32; NUM_HARMONICS], 19 | } 20 | 21 | impl Default for HarmonicOscillator { 22 | fn default() -> Self { 23 | Self { 24 | phase: 0.0, 25 | frequency: 0.0, 26 | amplitude: [0.0; NUM_HARMONICS], 27 | } 28 | } 29 | } 30 | 31 | impl HarmonicOscillator { 32 | pub fn new() -> Self { 33 | Self::default() 34 | } 35 | 36 | pub fn init(&mut self) { 37 | self.phase = 0.0; 38 | self.frequency = 0.0; 39 | for elem in self.amplitude.iter_mut() { 40 | *elem = 0.0; 41 | } 42 | } 43 | 44 | #[inline] 45 | pub fn render( 46 | &mut self, 47 | mut frequency: f32, 48 | amplitudes: &[f32], 49 | out: &mut [f32], 50 | first_harmonic_index: usize, 51 | ) { 52 | if frequency >= 0.5 { 53 | frequency = 0.5; 54 | } 55 | 56 | let mut am: [SimpleParameterInterpolator; NUM_HARMONICS] = 57 | [SimpleParameterInterpolator::default(); NUM_HARMONICS]; 58 | let mut fm = ParameterInterpolator::new(&mut self.frequency, frequency, out.len()); 59 | 60 | let a = &mut self.amplitude; 61 | 62 | for i in 0..NUM_HARMONICS { 63 | let mut f = frequency * (first_harmonic_index + i) as f32; 64 | if f >= 0.5 { 65 | f = 0.5; 66 | } 67 | am[i].init(a[i], amplitudes[i] * (1.0 - f * 2.0), out.len()); 68 | } 69 | 70 | for out_sample in out.iter_mut() { 71 | self.phase += fm.next(); 72 | if self.phase >= 1.0 { 73 | self.phase -= 1.0; 74 | } 75 | let two_x = 2.0 * sine_no_wrap(self.phase); 76 | let mut previous; 77 | let mut current; 78 | if first_harmonic_index == 1 { 79 | previous = 1.0; 80 | current = two_x * 0.5; 81 | } else { 82 | let k = first_harmonic_index as f32; 83 | previous = sine(self.phase * (k - 1.0) + 0.25); 84 | current = sine(self.phase * k); 85 | } 86 | 87 | let mut sum = 0.0; 88 | for (i, am) in am.iter().enumerate().take(NUM_HARMONICS) { 89 | { 90 | sum += am.update(&mut self.amplitude[i]) * current; 91 | let temp = current; 92 | current = two_x * current - previous; 93 | previous = temp; 94 | } 95 | if first_harmonic_index == 1 { 96 | *out_sample = sum; 97 | } else { 98 | *out_sample += sum; 99 | } 100 | } 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/oscillator/mod.rs: -------------------------------------------------------------------------------- 1 | //! Collection of oscillators. 2 | 3 | #[allow(clippy::module_inception)] 4 | pub mod oscillator; 5 | 6 | pub mod formant_oscillator; 7 | pub mod grainlet_oscillator; 8 | pub mod harmonic_oscillator; 9 | pub mod nes_triangle_oscillator; 10 | pub mod sine_oscillator; 11 | pub mod string_synth_oscillator; 12 | pub mod super_square_oscillator; 13 | pub mod variable_saw_oscillator; 14 | pub mod variable_shape_oscillator; 15 | pub mod vosim_oscillator; 16 | pub mod wavetable_oscillator; 17 | pub mod z_oscillator; 18 | -------------------------------------------------------------------------------- /src/oscillator/vosim_oscillator.rs: -------------------------------------------------------------------------------- 1 | //! Two sinewaves multiplied by and sync'ed to a carrier. 2 | 3 | // Based on MIT-licensed code (c) 2016 by Emilie Gillet (emilie.o.gillet@gmail.com) 4 | 5 | use crate::oscillator::oscillator::MAX_FREQUENCY; 6 | use crate::oscillator::sine_oscillator::sine; 7 | use crate::utils::parameter_interpolator::ParameterInterpolator; 8 | 9 | #[derive(Debug, Default)] 10 | pub struct VosimOscillator { 11 | // Oscillator state. 12 | carrier_phase: f32, 13 | formant_1_phase: f32, 14 | formant_2_phase: f32, 15 | 16 | // For interpolation of parameters. 17 | carrier_frequency: f32, 18 | formant_1_frequency: f32, 19 | formant_2_frequency: f32, 20 | carrier_shape: f32, 21 | } 22 | 23 | impl VosimOscillator { 24 | pub fn new() -> Self { 25 | Self::default() 26 | } 27 | 28 | pub fn init(&mut self) { 29 | self.carrier_phase = 0.0; 30 | self.formant_1_phase = 0.0; 31 | self.formant_2_phase = 0.0; 32 | 33 | self.carrier_frequency = 0.0; 34 | self.formant_1_frequency = 0.0; 35 | self.formant_2_frequency = 0.0; 36 | self.carrier_shape = 0.0; 37 | } 38 | 39 | #[inline] 40 | pub fn render( 41 | &mut self, 42 | mut carrier_frequency: f32, 43 | mut formant_frequency_1: f32, 44 | mut formant_frequency_2: f32, 45 | carrier_shape: f32, 46 | out: &mut [f32], 47 | ) { 48 | if carrier_frequency >= MAX_FREQUENCY { 49 | carrier_frequency = MAX_FREQUENCY; 50 | } 51 | if formant_frequency_1 >= MAX_FREQUENCY { 52 | formant_frequency_1 = MAX_FREQUENCY; 53 | } 54 | if formant_frequency_2 >= MAX_FREQUENCY { 55 | formant_frequency_2 = MAX_FREQUENCY; 56 | } 57 | 58 | let mut f0_modulation = 59 | ParameterInterpolator::new(&mut self.carrier_frequency, carrier_frequency, out.len()); 60 | let mut f1_modulation = ParameterInterpolator::new( 61 | &mut self.formant_1_frequency, 62 | formant_frequency_1, 63 | out.len(), 64 | ); 65 | let mut f2_modulation = ParameterInterpolator::new( 66 | &mut self.formant_2_frequency, 67 | formant_frequency_2, 68 | out.len(), 69 | ); 70 | let mut carrier_shape_modulation = 71 | ParameterInterpolator::new(&mut self.carrier_shape, carrier_shape, out.len()); 72 | 73 | for out_sample in out.iter_mut() { 74 | let f0 = f0_modulation.next(); 75 | let f1 = f1_modulation.next(); 76 | let f2 = f2_modulation.next(); 77 | 78 | self.carrier_phase += carrier_frequency; 79 | if self.carrier_phase >= 1.0 { 80 | self.carrier_phase -= 1.0; 81 | let reset_time = self.carrier_phase / f0; 82 | self.formant_1_phase = reset_time * f1; 83 | self.formant_2_phase = reset_time * f2; 84 | } else { 85 | self.formant_1_phase += f1; 86 | if self.formant_1_phase >= 1.0 { 87 | self.formant_1_phase -= 1.0; 88 | } 89 | self.formant_2_phase += f2; 90 | if self.formant_2_phase >= 1.0 { 91 | self.formant_2_phase -= 1.0; 92 | } 93 | } 94 | 95 | let carrier = sine(self.carrier_phase * 0.5 + 0.25) + 1.0; 96 | let reset_phase = 0.75 - 0.25 * carrier_shape_modulation.next(); 97 | let reset_amplitude = sine(reset_phase); 98 | let formant_0 = sine(self.formant_1_phase + reset_phase) - reset_amplitude; 99 | let formant_1 = sine(self.formant_2_phase + reset_phase) - reset_amplitude; 100 | *out_sample = carrier * (formant_0 + formant_1) * 0.25 + reset_amplitude; 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/physical_modelling/mod.rs: -------------------------------------------------------------------------------- 1 | //! Physical modelling components. 2 | 3 | pub mod modal_voice; 4 | pub mod resonator; 5 | pub mod string; 6 | pub mod string_voice; 7 | -------------------------------------------------------------------------------- /src/physical_modelling/modal_voice.rs: -------------------------------------------------------------------------------- 1 | //! Simple modal synthesis voice with a mallet exciter: click -> LPF -> resonator. 2 | //! 3 | //! The click is replaced by continuous white noise when the trigger input 4 | //! of the module is not patched. 5 | 6 | // Based on MIT-licensed code (c) 2016 by Emilie Gillet (emilie.o.gillet@gmail.com) 7 | 8 | use super::resonator::{Resonator, ResonatorSvf, MAX_NUM_MODES}; 9 | use crate::noise::dust::dust; 10 | use crate::utils::filter::FilterMode; 11 | use crate::utils::units::semitones_to_ratio; 12 | 13 | #[derive(Debug, Default)] 14 | pub struct ModalVoice { 15 | excitation_filter: ResonatorSvf<1>, 16 | resonator: Resonator, 17 | } 18 | 19 | impl ModalVoice { 20 | pub fn new() -> Self { 21 | Self::default() 22 | } 23 | 24 | pub fn init(&mut self) { 25 | self.excitation_filter.init(); 26 | self.resonator.init(0.015, MAX_NUM_MODES); 27 | } 28 | 29 | #[allow(clippy::too_many_arguments)] 30 | #[inline] 31 | pub fn render( 32 | &mut self, 33 | sustain: bool, 34 | trigger: bool, 35 | accent: f32, 36 | f0: f32, 37 | structure: f32, 38 | mut brightness: f32, 39 | mut damping: f32, 40 | temp: &mut [f32], 41 | temp_2: &mut [f32], 42 | out: &mut [f32], 43 | aux: &mut [f32], 44 | ) { 45 | let density = brightness * brightness; 46 | 47 | brightness += 0.25 * accent * (1.0 - brightness); 48 | damping += 0.25 * accent * (1.0 - damping); 49 | 50 | let range = if sustain { 36.0 } else { 60.0 }; 51 | let f = if sustain { 4.0 * f0 } else { 2.0 * f0 }; 52 | let cutoff = f32::min( 53 | f * semitones_to_ratio((brightness * (2.0 - brightness) - 0.5) * range), 54 | 0.499, 55 | ); 56 | let q = if sustain { 0.7 } else { 1.5 }; 57 | 58 | // Synthesize excitation signal. 59 | if sustain { 60 | let dust_f = 0.00005 + 0.99995 * density * density; 61 | for sample_temp in temp.iter_mut() { 62 | *sample_temp = dust(dust_f) * (4.0 - dust_f * 3.0) * accent; 63 | } 64 | } else { 65 | for temp_sample in temp.iter_mut() { 66 | *temp_sample = 0.0; 67 | } 68 | if trigger { 69 | let attenuation = 1.0 - damping * 0.5; 70 | let amplitude = (0.12 + 0.08 * accent) * attenuation; 71 | temp[0] = amplitude * semitones_to_ratio(cutoff * cutoff * 24.0) / cutoff; 72 | } 73 | } 74 | 75 | self.excitation_filter.process( 76 | core::slice::from_ref(&cutoff), 77 | core::slice::from_ref(&q), 78 | core::slice::from_ref(&1.0), 79 | temp, 80 | temp_2, 81 | FilterMode::LowPass, 82 | false, 83 | ); 84 | 85 | for (aux_sample, temp_2_sample) in aux.iter_mut().zip(temp_2.iter()) { 86 | *aux_sample += *temp_2_sample; 87 | } 88 | 89 | self.resonator 90 | .process(f0, structure, brightness, damping, temp_2, out); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/physical_modelling/string_voice.rs: -------------------------------------------------------------------------------- 1 | //! Extended Karplus-Strong, with all the niceties from Rings. 2 | 3 | // Based on MIT-licensed code (c) 2016 by Emilie Gillet (emilie.o.gillet@gmail.com) 4 | 5 | use super::string::String; 6 | use crate::noise::dust::dust; 7 | use crate::utils::filter::{FilterMode, FrequencyApproximation, Svf}; 8 | use crate::utils::random; 9 | use crate::utils::units::semitones_to_ratio; 10 | 11 | #[derive(Debug)] 12 | pub struct StringVoice { 13 | excitation_filter: Svf, 14 | string: String, 15 | remaining_noise_samples: usize, 16 | } 17 | 18 | impl Default for StringVoice { 19 | fn default() -> Self { 20 | Self::new() 21 | } 22 | } 23 | 24 | impl StringVoice { 25 | pub fn new() -> Self { 26 | Self { 27 | excitation_filter: Svf::default(), 28 | string: String::new(), 29 | remaining_noise_samples: 0, 30 | } 31 | } 32 | 33 | pub fn init(&mut self) { 34 | self.excitation_filter.init(); 35 | self.remaining_noise_samples = 0; 36 | self.reset(); 37 | } 38 | 39 | pub fn reset(&mut self) { 40 | self.string.reset(); 41 | } 42 | 43 | #[allow(clippy::too_many_arguments)] 44 | #[inline] 45 | pub fn render( 46 | &mut self, 47 | sustain: bool, 48 | trigger: bool, 49 | accent: f32, 50 | f0: f32, 51 | structure: f32, 52 | mut brightness: f32, 53 | mut damping: f32, 54 | temp: &mut [f32], 55 | temp_2: &mut [f32], 56 | out: &mut [f32], 57 | aux: &mut [f32], 58 | ) { 59 | let density = brightness * brightness; 60 | 61 | brightness += 0.25 * accent * (1.0 - brightness); 62 | damping += 0.25 * accent * (1.0 - damping); 63 | 64 | // Synthesize excitation signal. 65 | if trigger || sustain { 66 | let range = 72.0; 67 | let f = 4.0 * f0; 68 | let cutoff = f32::min( 69 | f * semitones_to_ratio((brightness * (2.0 - brightness) - 0.5) * range), 70 | 0.499, 71 | ); 72 | let q = if sustain { 1.0 } else { 0.5 }; 73 | self.remaining_noise_samples = (1.0 / f0) as usize; 74 | self.excitation_filter 75 | .set_f_q(cutoff, q, FrequencyApproximation::Dirty); 76 | } 77 | 78 | if sustain { 79 | let dust_f = 0.00005 + 0.99995 * density * density; 80 | 81 | for sample_temp in temp.iter_mut() { 82 | *sample_temp = dust(dust_f) * (8.0 - dust_f * 6.0) * accent; 83 | } 84 | } else if self.remaining_noise_samples > 0 { 85 | let mut noise_samples = usize::min(self.remaining_noise_samples, out.len()); 86 | self.remaining_noise_samples -= noise_samples; 87 | let mut tail = out.len() - noise_samples; 88 | let mut start_index = 0; 89 | while noise_samples > 0 { 90 | temp[start_index] = 2.0 * random::get_float() - 1.0; 91 | start_index += 1; 92 | noise_samples -= 1; 93 | } 94 | while tail > 0 { 95 | temp[start_index] = 0.0; 96 | start_index += 1; 97 | tail -= 1; 98 | } 99 | } else { 100 | for sample_temp in temp.iter_mut() { 101 | *sample_temp = 0.0; 102 | } 103 | } 104 | 105 | self.excitation_filter 106 | .process_buffer(temp, temp_2, FilterMode::LowPass); 107 | 108 | for (aux_sample, temp_sample) in aux.iter_mut().zip(temp_2.iter()) { 109 | *aux_sample += *temp_sample; 110 | } 111 | 112 | let non_linearity = if structure < 0.24 { 113 | (structure - 0.24) * 4.166 114 | } else if structure > 0.26 { 115 | (structure - 0.26) * 1.35135 116 | } else { 117 | 0.0 118 | }; 119 | self.string 120 | .process(f0, non_linearity, brightness, damping, temp_2, out); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/resources/fm.rs: -------------------------------------------------------------------------------- 1 | //! FM data lookup table. 2 | 3 | // Based on MIT-licensed code (c) 2016 by Emilie Gillet (emilie.o.gillet@gmail.com) 4 | 5 | #![allow(clippy::excessive_precision)] 6 | 7 | pub static LUT_FM_FREQUENCY_QUANTIZER: [f32; 130] = [ 8 | -1.200000000e+01, 9 | -1.200000000e+01, 10 | -1.200000000e+01, 11 | -1.184000000e+01, 12 | -1.184000000e+01, 13 | -1.184000000e+01, 14 | -1.111000000e+01, 15 | -1.038000000e+01, 16 | -9.650000000e+00, 17 | -8.920000000e+00, 18 | -8.190000000e+00, 19 | -7.460000000e+00, 20 | -6.730000000e+00, 21 | -6.000000000e+00, 22 | -6.000000000e+00, 23 | -6.000000000e+00, 24 | -5.545511612e+00, 25 | -5.091023223e+00, 26 | -4.636534835e+00, 27 | -4.182046446e+00, 28 | -4.182046446e+00, 29 | -4.182046446e+00, 30 | -3.659290641e+00, 31 | -3.136534835e+00, 32 | -2.613779029e+00, 33 | -2.091023223e+00, 34 | -1.568267417e+00, 35 | -1.045511612e+00, 36 | -5.227558058e-01, 37 | 0.000000000e+00, 38 | 0.000000000e+00, 39 | 0.000000000e+00, 40 | 1.600000000e-01, 41 | 1.600000000e-01, 42 | 1.600000000e-01, 43 | 8.900000000e-01, 44 | 1.620000000e+00, 45 | 2.350000000e+00, 46 | 3.080000000e+00, 47 | 3.810000000e+00, 48 | 4.540000000e+00, 49 | 5.270000000e+00, 50 | 6.000000000e+00, 51 | 6.000000000e+00, 52 | 6.000000000e+00, 53 | 6.454488388e+00, 54 | 6.908976777e+00, 55 | 7.363465165e+00, 56 | 7.817953554e+00, 57 | 7.817953554e+00, 58 | 7.817953554e+00, 59 | 8.285529931e+00, 60 | 8.753106309e+00, 61 | 9.220682687e+00, 62 | 9.688259065e+00, 63 | 9.688259065e+00, 64 | 9.688259065e+00, 65 | 1.026619430e+01, 66 | 1.084412953e+01, 67 | 1.142206477e+01, 68 | 1.200000000e+01, 69 | 1.200000000e+01, 70 | 1.200000000e+01, 71 | 1.216000000e+01, 72 | 1.216000000e+01, 73 | 1.216000000e+01, 74 | 1.262977500e+01, 75 | 1.309955001e+01, 76 | 1.356932501e+01, 77 | 1.403910002e+01, 78 | 1.403910002e+01, 79 | 1.403910002e+01, 80 | 1.490761987e+01, 81 | 1.577613972e+01, 82 | 1.664465957e+01, 83 | 1.751317942e+01, 84 | 1.751317942e+01, 85 | 1.751317942e+01, 86 | 1.800000000e+01, 87 | 1.800000000e+01, 88 | 1.800000000e+01, 89 | 1.850977500e+01, 90 | 1.901955001e+01, 91 | 1.901955001e+01, 92 | 1.901955001e+01, 93 | 1.981795355e+01, 94 | 1.981795355e+01, 95 | 1.981795355e+01, 96 | 2.066386428e+01, 97 | 2.150977500e+01, 98 | 2.150977500e+01, 99 | 2.150977500e+01, 100 | 2.213233125e+01, 101 | 2.275488750e+01, 102 | 2.337744375e+01, 103 | 2.400000000e+01, 104 | 2.400000000e+01, 105 | 2.400000000e+01, 106 | 2.450977500e+01, 107 | 2.501955001e+01, 108 | 2.501955001e+01, 109 | 2.501955001e+01, 110 | 2.547403840e+01, 111 | 2.592852679e+01, 112 | 2.638301517e+01, 113 | 2.683750356e+01, 114 | 2.683750356e+01, 115 | 2.683750356e+01, 116 | 2.735032035e+01, 117 | 2.786313714e+01, 118 | 2.786313714e+01, 119 | 2.786313714e+01, 120 | 2.839735285e+01, 121 | 2.893156857e+01, 122 | 2.946578428e+01, 123 | 3.000000000e+01, 124 | 3.000000000e+01, 125 | 3.000000000e+01, 126 | 3.075000000e+01, 127 | 3.150000000e+01, 128 | 3.225000000e+01, 129 | 3.300000000e+01, 130 | 3.375000000e+01, 131 | 3.450000000e+01, 132 | 3.525000000e+01, 133 | 3.600000000e+01, 134 | 3.600000000e+01, 135 | 3.600000000e+01, 136 | 3.600000000e+01, 137 | 3.600000000e+01, 138 | ]; 139 | -------------------------------------------------------------------------------- /src/resources/lpc.rs: -------------------------------------------------------------------------------- 1 | //! LPC excitation pulse lookup table. 2 | 3 | // Based on MIT-licensed code (c) 2016 by Emilie Gillet (emilie.o.gillet@gmail.com) 4 | 5 | pub const LUT_LPC_EXCITATION_PULSE_SIZE: usize = 640; 6 | 7 | pub static LUT_LPC_EXCITATION_PULSE: [i8; 640] = [ 8 | 0, 0, 0, 0, 1, 1, 1, 2, 3, 3, 4, 5, 6, 7, 8, 9, 10, 12, 13, 15, 17, 19, 21, 23, 25, 28, 30, 33, 9 | 36, 38, 41, 44, 48, 51, 54, 57, 61, 64, 68, 71, 75, 78, 82, 85, 89, 92, 95, 99, 102, 105, 108, 10 | 110, 113, 115, 118, 120, 121, 123, 124, 125, 126, 127, 127, 127, 127, 126, 125, 124, 122, 121, 11 | 118, 116, 113, 110, 107, 104, 100, 96, 92, 87, 83, 78, 73, 68, 63, 58, 53, 47, 42, 37, 31, 26, 12 | 21, 16, 11, 6, 1, -3, -8, -12, -16, -20, -23, -26, -29, -32, -35, -37, -39, -40, -42, -43, -43, 13 | -44, -44, -44, -44, -44, -43, -42, -41, -40, -39, -38, -36, -35, -33, -32, -30, -29, -27, -26, 14 | -24, -23, -22, -20, -19, -18, -18, -17, -16, -16, -16, -16, -16, -16, -17, -17, -18, -19, -20, 15 | -21, -23, -24, -26, -27, -29, -30, -32, -34, -35, -37, -39, -40, -42, -43, -44, -46, -47, -48, 16 | -48, -49, -49, -50, -50, -50, -49, -49, -48, -47, -46, -45, -44, -42, -41, -39, -37, -35, -33, 17 | -31, -29, -27, -25, -23, -20, -18, -16, -14, -13, -11, -9, -8, -7, -6, -5, -4, -3, -3, -3, -3, 18 | -3, -4, -5, -6, -7, -9, -10, -12, -14, -16, -18, -21, -23, -26, -29, -32, -34, -37, -40, -43, 19 | -46, -49, -51, -54, -56, -59, -61, -63, -65, -67, -68, -70, -71, -72, -72, -73, -73, -73, -73, 20 | -72, -72, -71, -70, -69, -67, -66, -64, -63, -61, -59, -57, -54, -52, -50, -48, -45, -43, -41, 21 | -39, -37, -35, -33, -31, -29, -27, -26, -25, -23, -22, -21, -20, -20, -19, -19, -19, -18, -18, 22 | -18, -19, -19, -19, -20, -20, -21, -21, -22, -22, -23, -23, -24, -24, -25, -25, -25, -26, -26, 23 | -26, -25, -25, -25, -24, -24, -23, -22, -21, -20, -19, -18, -16, -15, -13, -12, -10, -8, -6, 24 | -4, -3, -1, 1, 3, 5, 7, 8, 10, 12, 13, 15, 16, 17, 18, 19, 20, 21, 21, 22, 22, 22, 22, 22, 22, 25 | 22, 22, 21, 21, 20, 19, 19, 18, 17, 16, 15, 14, 14, 13, 12, 11, 10, 10, 9, 9, 8, 8, 8, 8, 8, 8, 26 | 8, 8, 8, 9, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 21, 22, 23, 24, 26, 27, 28, 29, 30, 31, 27 | 32, 33, 33, 34, 35, 35, 35, 36, 36, 36, 36, 35, 35, 35, 34, 34, 33, 33, 32, 31, 30, 30, 29, 28, 28 | 27, 26, 25, 25, 24, 23, 22, 22, 21, 20, 20, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 20, 20, 21, 29 | 21, 22, 22, 23, 23, 24, 25, 25, 26, 27, 27, 28, 29, 29, 29, 30, 30, 30, 31, 31, 31, 31, 30, 30, 30 | 30, 29, 29, 28, 28, 27, 26, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 15, 14, 13, 12, 12, 31 | 11, 10, 10, 10, 9, 9, 9, 9, 9, 9, 9, 9, 10, 10, 10, 11, 11, 12, 12, 12, 13, 13, 14, 14, 14, 15, 32 | 15, 15, 15, 15, 15, 15, 15, 15, 15, 14, 14, 14, 13, 12, 12, 11, 10, 9, 8, 8, 7, 6, 5, 4, 3, 2, 33 | 1, 0, -1, -2, -3, -4, -4, -5, -6, -6, -7, -7, -8, -8, -8, -9, -9, -9, -9, -9, -9, -9, -9, -8, 34 | -8, -8, -7, -7, -7, -6, -6, -5, -5, -5, -4, -4, -4, -3, -3, -3, -2, -2, -2, -2, -1, -1, -1, -1, 35 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, 36 | -2, -2, -2, -2, -2, -2, -2, -2, -1, -1, -1, -1, -1, -1, 0, 0, 0, 0, 0, 37 | ]; 38 | -------------------------------------------------------------------------------- /src/resources/mod.rs: -------------------------------------------------------------------------------- 1 | //! Resources definitions. 2 | 3 | // Based on MIT-licensed code (c) 2016 by Emilie Gillet (emilie.o.gillet@gmail.com) 4 | 5 | pub mod fm; 6 | pub mod fold; 7 | pub mod lpc; 8 | pub mod sine; 9 | pub mod stiffness; 10 | pub mod svf; 11 | pub mod sysex; 12 | pub mod waves; 13 | pub mod waveshape; 14 | -------------------------------------------------------------------------------- /src/resources/stiffness.rs: -------------------------------------------------------------------------------- 1 | //! Stiffness lookup table. 2 | 3 | // Based on MIT-licensed code (c) 2016 by Emilie Gillet (emilie.o.gillet@gmail.com) 4 | 5 | #![allow(clippy::excessive_precision)] 6 | 7 | pub static LUT_STIFFNESS: [f32; 65] = [ 8 | -6.250000000e-02, 9 | -5.859375000e-02, 10 | -5.468750000e-02, 11 | -5.078125000e-02, 12 | -4.687500000e-02, 13 | -4.296875000e-02, 14 | -3.906250000e-02, 15 | -3.515625000e-02, 16 | -3.125000000e-02, 17 | -2.734375000e-02, 18 | -2.343750000e-02, 19 | -1.953125000e-02, 20 | -1.562500000e-02, 21 | -1.171875000e-02, 22 | -7.812500000e-03, 23 | -3.906250000e-03, 24 | 0.000000000e+00, 25 | 0.000000000e+00, 26 | 0.000000000e+00, 27 | 0.000000000e+00, 28 | 1.009582073e-03, 29 | 2.416076364e-03, 30 | 4.002252878e-03, 31 | 5.791066350e-03, 32 | 7.808404022e-03, 33 | 1.008346028e-02, 34 | 1.264915914e-02, 35 | 1.554263074e-02, 36 | 1.880574864e-02, 37 | 2.248573583e-02, 38 | 2.663584813e-02, 39 | 3.131614488e-02, 40 | 3.659435812e-02, 41 | 4.254687278e-02, 42 | 4.925983210e-02, 43 | 5.683038428e-02, 44 | 6.536808837e-02, 45 | 7.499649981e-02, 46 | 8.585495846e-02, 47 | 9.810060511e-02, 48 | 1.119106556e-01, 49 | 1.274849653e-01, 50 | 1.450489216e-01, 51 | 1.648567056e-01, 52 | 1.871949702e-01, 53 | 2.123869891e-01, 54 | 2.407973346e-01, 55 | 2.728371538e-01, 56 | 3.089701187e-01, 57 | 3.497191360e-01, 58 | 3.956739150e-01, 59 | 4.474995013e-01, 60 | 5.059459012e-01, 61 | 5.718589358e-01, 62 | 6.461924814e-01, 63 | 7.300222738e-01, 64 | 8.245614757e-01, 65 | 9.311782340e-01, 66 | 1.000037649e+00, 67 | 1.005639154e+00, 68 | 1.048005353e+00, 69 | 1.183990632e+00, 70 | 1.457101344e+00, 71 | 2.000000000e+00, 72 | 2.000000000e+00, 73 | ]; 74 | -------------------------------------------------------------------------------- /src/speech/lpc_speech_synth_phonemes.rs: -------------------------------------------------------------------------------- 1 | //! Phonemes table 2 | //! 3 | //! This is a table with vowels (a, e, i, o, u) and a random selection of 4 | //! consonnants for the LPC10 speech synth. 5 | 6 | // Based on MIT-licensed code (c) 2016 by Emilie Gillet (emilie.o.gillet@gmail.com) 7 | 8 | use super::lpc_speech_synth::LpcSpeechSynthFrame; 9 | use super::lpc_speech_synth_controller::NUM_PHONEMES; 10 | 11 | pub const PHONEMES: [LpcSpeechSynthFrame; NUM_PHONEMES] = [ 12 | LpcSpeechSynthFrame::new(192, 80, -18368, 11584, 52, 29, 23, 14, -17, 79, 37, 4), 13 | LpcSpeechSynthFrame::new(192, 80, -14528, 1536, 38, 29, 11, 14, -41, 79, 57, 4), 14 | LpcSpeechSynthFrame::new(192, 80, 14528, 9216, 25, -54, -70, 36, 19, 79, 57, 22), 15 | LpcSpeechSynthFrame::new(192, 80, -14528, -13440, 38, 57, 57, 14, -53, 7, 37, 77), 16 | LpcSpeechSynthFrame::new(192, 80, -26368, 4160, 11, 15, -1, 36, -41, 31, 77, 22), 17 | LpcSpeechSynthFrame::new(15, 0, 5184, 9216, -29, -12, 0, 0, 0, 0, 0, 0), 18 | LpcSpeechSynthFrame::new(10, 0, 27968, 17856, 25, 43, -24, -20, -53, 55, -4, -51), 19 | LpcSpeechSynthFrame::new(128, 160, 14528, -3712, -43, -26, -24, -20, -53, 55, -4, -51), 20 | LpcSpeechSynthFrame::new(128, 160, 10048, 11584, -16, 15, 0, 0, 0, 0, 0, 0), 21 | LpcSpeechSynthFrame::new(224, 100, 18368, -13440, -97, -26, -12, -53, -41, 7, 57, 32), 22 | LpcSpeechSynthFrame::new(192, 80, -10048, 9216, -70, 15, 34, -20, -17, 31, -24, 22), 23 | LpcSpeechSynthFrame::new(96, 160, -18368, 17856, -29, -12, -35, 3, -5, 7, 37, 22), 24 | LpcSpeechSynthFrame::new(64, 80, -21632, -6272, -83, 29, 57, 3, -5, 7, 16, 32), 25 | LpcSpeechSynthFrame::new(192, 80, 0, -1088, 11, -26, -24, -9, -5, 55, 37, 22), 26 | LpcSpeechSynthFrame::new(64, 80, 21632, -17536, -97, 85, 57, -20, -17, 31, -4, 59), 27 | ]; 28 | -------------------------------------------------------------------------------- /src/speech/mod.rs: -------------------------------------------------------------------------------- 1 | //! Speech synthesis in various flavors. 2 | 3 | pub mod lpc_speech_synth; 4 | pub mod lpc_speech_synth_controller; 5 | pub mod lpc_speech_synth_phonemes; 6 | pub mod lpc_speech_synth_words; 7 | pub mod naive_speech_synth; 8 | pub mod sam_speech_synth; 9 | -------------------------------------------------------------------------------- /src/utils/atan.rs: -------------------------------------------------------------------------------- 1 | //! Fast arc-tangent routines. 2 | 3 | // Based on MIT-licensed code (c) 2014 by Olivier Gillet (ol.gillet@gmail.com) 4 | 5 | #[allow(unused_imports)] 6 | use num_traits::float::FloatCore; 7 | 8 | #[inline] 9 | pub fn fast_atan2(y: f32, x: f32) -> u16 { 10 | const SIGN_MASK: u32 = 0x80000000; 11 | const B: f32 = 0.596227; 12 | let ux_s = SIGN_MASK & (x as u32); 13 | let uy_s = SIGN_MASK & (y as u32); 14 | let offset = (((!ux_s & uy_s) >> 29) | (ux_s >> 30)) << 14; 15 | let bxy_a = (B * x * y).abs(); 16 | let num = bxy_a + y * y; 17 | let atan_1q = num / (x * x + bxy_a + num); 18 | let uatan_2q = (ux_s ^ uy_s) | (atan_1q as u32); 19 | (uatan_2q * 16384 + offset) as u16 20 | } 21 | -------------------------------------------------------------------------------- /src/utils/cosine_oscillator.rs: -------------------------------------------------------------------------------- 1 | //! Cosine oscillator. 2 | //! 3 | //! Generates a cosine between 0.0 and 1.0 with minimal 4 | //! CPU use. 5 | 6 | // Based on MIT-licensed code (c) 2014 by Olivier Gillet (ol.gillet@gmail.com) 7 | 8 | #[allow(unused_imports)] 9 | use num_traits::float::Float; 10 | 11 | pub enum CosineOscillatorMode { 12 | Approximate, 13 | Exact, 14 | } 15 | 16 | impl Default for CosineOscillatorMode { 17 | fn default() -> Self { 18 | Self::Approximate 19 | } 20 | } 21 | 22 | #[derive(Debug, Default)] 23 | pub struct CosineOscillator { 24 | y1: f32, 25 | y0: f32, 26 | iir_coefficient: f32, 27 | initial_amplitude: f32, 28 | } 29 | 30 | impl CosineOscillator { 31 | pub fn new() -> Self { 32 | Self::default() 33 | } 34 | 35 | pub fn init(&mut self, frequency: f32, mode: CosineOscillatorMode) { 36 | match mode { 37 | CosineOscillatorMode::Approximate => { 38 | self.init_approximate(frequency); 39 | } 40 | CosineOscillatorMode::Exact => { 41 | self.iir_coefficient = 2.0 * (2.0 * core::f32::consts::PI * frequency).cos(); 42 | self.initial_amplitude = self.iir_coefficient * 0.25; 43 | } 44 | } 45 | 46 | self.start(); 47 | } 48 | 49 | fn init_approximate(&mut self, frequency: f32) { 50 | let mut sign = 16.0; 51 | let mut frequency = frequency - 0.25; 52 | 53 | if frequency < 0.0 { 54 | frequency = -frequency; 55 | } else if frequency > 0.5 { 56 | frequency -= 0.5; 57 | } else { 58 | sign = -16.0; 59 | } 60 | 61 | self.iir_coefficient = sign * frequency * (1.0 - 2.0 * frequency); 62 | self.initial_amplitude = self.iir_coefficient * 0.25; 63 | } 64 | 65 | #[inline] 66 | fn start(&mut self) { 67 | self.y1 = self.initial_amplitude; 68 | self.y0 = 0.5; 69 | } 70 | 71 | #[inline] 72 | pub fn value(&self) -> f32 { 73 | self.y1 + 0.5 74 | } 75 | 76 | #[inline] 77 | #[allow(clippy::should_implement_trait)] 78 | pub fn next(&mut self) -> f32 { 79 | let temp = self.y0; 80 | self.y0 = self.iir_coefficient * self.y0 - self.y1; 81 | self.y1 = temp; 82 | 83 | temp + 0.5 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/utils/delay_line.rs: -------------------------------------------------------------------------------- 1 | //! Delay line. 2 | 3 | // Based on MIT-licensed code (c) 2014 by Olivier Gillet (ol.gillet@gmail.com) 4 | 5 | use num_traits::{FromPrimitive, Num, Signed, ToPrimitive}; 6 | 7 | #[derive(Debug)] 8 | pub struct DelayLine { 9 | write_ptr: usize, 10 | delay: usize, 11 | line: [T; MAX_DELAY], 12 | } 13 | 14 | impl Default for DelayLine 15 | where 16 | T: Copy + Default + Num + Signed + FromPrimitive + ToPrimitive, 17 | { 18 | fn default() -> Self { 19 | Self::new() 20 | } 21 | } 22 | 23 | impl DelayLine 24 | where 25 | T: Copy + Default + Num + Signed + FromPrimitive + ToPrimitive, 26 | { 27 | pub fn new() -> Self { 28 | Self { 29 | write_ptr: 0, 30 | delay: 1, 31 | line: [T::zero(); MAX_DELAY], 32 | } 33 | } 34 | 35 | pub fn init(&mut self) { 36 | self.reset(); 37 | } 38 | 39 | pub fn reset(&mut self) { 40 | for elem in self.line.iter_mut() { 41 | *elem = T::zero(); 42 | } 43 | self.delay = 1; 44 | self.write_ptr = 0; 45 | } 46 | 47 | pub fn max_delay(&self) -> usize { 48 | MAX_DELAY 49 | } 50 | 51 | #[inline] 52 | pub fn set_delay(&mut self, delay: usize) { 53 | self.delay = delay; 54 | } 55 | 56 | #[inline] 57 | pub fn write(&mut self, sample: T) { 58 | self.line[self.write_ptr] = sample; 59 | self.write_ptr = (self.write_ptr + MAX_DELAY - 1) % MAX_DELAY; 60 | } 61 | 62 | #[inline] 63 | pub fn allpass(&mut self, sample: T, delay: usize, coefficient: T) -> T { 64 | let read = self.line[(self.write_ptr + delay) % MAX_DELAY]; 65 | let write = sample + coefficient * read; 66 | self.write(write); 67 | 68 | -write * coefficient + read 69 | } 70 | 71 | #[inline] 72 | pub fn write_read(&mut self, sample: T, delay: f32) -> T { 73 | self.write(sample); 74 | self.read_with_delay_frac(delay) 75 | } 76 | 77 | #[inline] 78 | pub fn read(&self) -> T { 79 | self.line[(self.write_ptr + self.delay) % MAX_DELAY] 80 | } 81 | 82 | #[inline] 83 | pub fn read_with_delay(&self, delay: usize) -> T { 84 | self.line[(self.write_ptr + delay) % MAX_DELAY] 85 | } 86 | 87 | #[inline] 88 | pub fn read_with_delay_frac(&self, delay: f32) -> T { 89 | let delay_integral = delay as usize; 90 | let delay_fractional = delay - (delay_integral as f32); 91 | let a = self.line[(self.write_ptr + delay_integral) % MAX_DELAY]; 92 | let b = self.line[(self.write_ptr + delay_integral + 1) % MAX_DELAY]; 93 | 94 | let frac = (b - a).to_f32().unwrap_or_default() * delay_fractional; 95 | 96 | T::from_f32(a.to_f32().unwrap_or_default() + frac).unwrap_or_default() 97 | } 98 | 99 | #[inline] 100 | pub fn read_hermite(&self, delay: f32) -> T { 101 | let delay_integral = delay as usize; 102 | let delay_fractional = delay - (delay_integral as f32); 103 | let t = self.write_ptr + delay_integral + MAX_DELAY; 104 | let xm1 = self.line[(t - 1) % MAX_DELAY]; 105 | let x0 = self.line[(t) % MAX_DELAY]; 106 | let x1 = self.line[(t + 1) % MAX_DELAY]; 107 | let x2 = self.line[(t + 2) % MAX_DELAY]; 108 | let c = T::from_f32((x1 - xm1).to_f32().unwrap_or_default() * 0.5).unwrap_or_default(); 109 | let v = x0 - x1; 110 | let w = c + v; 111 | let a = T::from_f32( 112 | (w + v).to_f32().unwrap_or_default() + ((x2 - x0).to_f32().unwrap_or_default() * 0.5), 113 | ) 114 | .unwrap_or_default(); 115 | let b_neg = w + a; 116 | let f = delay_fractional; 117 | 118 | T::from_f32( 119 | (((a.to_f32().unwrap_or_default() * f) - b_neg.to_f32().unwrap_or_default()) * f 120 | + c.to_f32().unwrap_or_default()) 121 | * f 122 | + x0.to_f32().unwrap_or_default(), 123 | ) 124 | .unwrap_or_default() 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/utils/hysteresis_quantizer.rs: -------------------------------------------------------------------------------- 1 | //! Hysteresis quantizer. 2 | //! 3 | //! Quantize a float in [0, 1] to an integer in [0, num_steps[. Apply hysteresis 4 | //! to prevent jumps near the decision boundary. 5 | 6 | // Based on MIT-licensed code (c) 2015 by Olivier Gillet (ol.gillet@gmail.com) 7 | 8 | #[derive(Debug, Default)] 9 | pub struct HysteresisQuantizer { 10 | quantized_value: i32, 11 | } 12 | 13 | impl HysteresisQuantizer { 14 | pub fn new() -> Self { 15 | Self::default() 16 | } 17 | 18 | pub fn init(&mut self) { 19 | self.quantized_value = 0; 20 | } 21 | 22 | #[inline] 23 | pub fn process_with_default(&mut self, value: f32, num_steps: usize) -> i32 { 24 | self.process_with_hysteresis(value, num_steps, 0.25) 25 | } 26 | 27 | #[inline] 28 | pub fn process_with_hysteresis( 29 | &mut self, 30 | value: f32, 31 | num_steps: usize, 32 | hysteresis: f32, 33 | ) -> i32 { 34 | self.process(0, value, num_steps, hysteresis) 35 | } 36 | 37 | #[inline] 38 | pub fn process(&mut self, base: i32, value: f32, num_steps: usize, hysteresis: f32) -> i32 { 39 | let mut value = value * (num_steps - 1) as f32; 40 | value += base as f32; 41 | let hysteresis_feedback = if value > (self.quantized_value as f32) { 42 | -hysteresis 43 | } else { 44 | hysteresis 45 | }; 46 | let q = ((value + hysteresis_feedback + 0.5) as i32).clamp(0, (num_steps - 1) as i32); 47 | self.quantized_value = q; 48 | 49 | q 50 | } 51 | } 52 | 53 | #[derive(Debug, Default)] 54 | pub struct HysteresisQuantizer2 { 55 | num_steps: i32, 56 | hysteresis: f32, 57 | scale: f32, 58 | offset: f32, 59 | quantized_value: i32, 60 | } 61 | 62 | impl HysteresisQuantizer2 { 63 | pub fn new() -> Self { 64 | Self::default() 65 | } 66 | 67 | pub fn init(&mut self, num_steps: i32, hysteresis: f32, symmetric: bool) { 68 | self.num_steps = num_steps; 69 | self.hysteresis = hysteresis; 70 | 71 | self.scale = (if symmetric { num_steps - 1 } else { num_steps }) as f32; 72 | self.offset = if symmetric { 0.0 } else { -0.5 }; 73 | 74 | self.quantized_value = 0; 75 | } 76 | 77 | #[inline] 78 | pub fn process(&mut self, value: f32) -> i32 { 79 | self.process_with_base(0, value) 80 | } 81 | 82 | #[inline] 83 | pub fn process_with_base(&mut self, base: i32, mut value: f32) -> i32 { 84 | value *= self.scale; 85 | value += self.offset; 86 | value += base as f32; 87 | 88 | let hysteresis_sign = if value > self.quantized_value as f32 { 89 | -1.0 90 | } else { 91 | 1.0 92 | }; 93 | let q = 94 | ((value + hysteresis_sign * self.hysteresis + 0.5) as i32).clamp(0, self.num_steps - 1); 95 | self.quantized_value = q; 96 | 97 | q 98 | } 99 | 100 | #[inline] 101 | pub fn num_steps(&self) -> i32 { 102 | self.num_steps 103 | } 104 | 105 | #[inline] 106 | pub fn quantized_value(&self) -> i32 { 107 | self.quantized_value 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/utils/limiter.rs: -------------------------------------------------------------------------------- 1 | //! Limiter. 2 | 3 | // Based on MIT-licensed code (c) 2015 by Olivier Gillet (ol.gillet@gmail.com) 4 | 5 | use super::slope; 6 | 7 | #[allow(unused_imports)] 8 | use num_traits::float::FloatCore; 9 | 10 | #[derive(Debug, Default)] 11 | pub struct Limiter { 12 | peak: f32, 13 | } 14 | 15 | impl Limiter { 16 | pub fn new() -> Self { 17 | Self::default() 18 | } 19 | 20 | pub fn init(&mut self) { 21 | self.peak = 0.5; 22 | } 23 | 24 | #[inline] 25 | pub fn process(&mut self, pre_gain: f32, in_out: &mut [f32]) { 26 | for sample in in_out.iter_mut() { 27 | let s = *sample * pre_gain; 28 | slope(&mut self.peak, s.abs(), 0.05, 0.00002); 29 | let gain = if self.peak <= 1.0 { 30 | 1.0 31 | } else { 32 | 1.0 / self.peak 33 | }; 34 | *sample = s * gain * 0.8; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/utils/mod.rs: -------------------------------------------------------------------------------- 1 | //! Utility functions. 2 | //! 3 | //! This module contains ports of functions that were used in several Mutable Instruments 4 | //! devices in common and were made for the STM32 platform. 5 | 6 | pub mod atan; 7 | pub mod cosine_oscillator; 8 | pub mod delay_line; 9 | pub mod filter; 10 | pub mod hysteresis_quantizer; 11 | pub mod limiter; 12 | pub mod parameter_interpolator; 13 | pub mod polyblep; 14 | pub mod random; 15 | pub mod rsqrt; 16 | pub mod units; 17 | 18 | #[allow(unused_imports)] 19 | use num_traits::float::Float; 20 | 21 | #[inline] 22 | pub fn interpolate(table: &[f32], mut index: f32, size: f32) -> f32 { 23 | index = index.clamp(0.0, 1.0); 24 | index *= size; 25 | let index_integral = index as usize; 26 | let index_fractional = index - (index_integral as f32); 27 | let a = table[index_integral]; 28 | let b = table[index_integral + 1]; 29 | 30 | a + (b - a) * index_fractional 31 | } 32 | 33 | #[inline] 34 | pub fn interpolate_hermite(table: &[f32], mut index: f32, size: f32) -> f32 { 35 | index = index.clamp(0.0, 1.0); 36 | index *= size; 37 | let index_integral = index as usize; 38 | let index_fractional = index - (index_integral as f32); 39 | let xm1 = table[index_integral - 1]; 40 | let x0 = table[index_integral]; 41 | let x1 = table[index_integral + 1]; 42 | let x2 = table[index_integral + 2]; 43 | let c = (x1 - xm1) * 0.5; 44 | let v = x0 - x1; 45 | let w = c + v; 46 | let a = w + v + (x2 - x0) * 0.5; 47 | let b_neg = w + a; 48 | let f = index_fractional; 49 | 50 | (((a * f) - b_neg) * f + c) * f + x0 51 | } 52 | 53 | #[inline] 54 | pub fn interpolate_wrap(table: &[f32], mut index: f32, size: f32) -> f32 { 55 | index -= (index as i32) as f32; 56 | index = index.clamp(0.0, 1.0); 57 | index *= size; 58 | let index_integral = index as usize; 59 | let index_fractional = index - (index_integral as f32); 60 | let a = table[index_integral]; 61 | let b = table[index_integral + 1]; 62 | 63 | a + (b - a) * index_fractional 64 | } 65 | 66 | #[inline] 67 | pub fn one_pole(out: &mut f32, in_: f32, coefficient: f32) { 68 | *out += (coefficient) * ((in_) - *out); 69 | } 70 | 71 | #[inline] 72 | pub fn slope(out: &mut f32, in_: f32, positive: f32, negative: f32) { 73 | let error = in_ - *out; 74 | *out += if error > 0.0 { 75 | positive 76 | } else { 77 | negative * error 78 | }; 79 | } 80 | 81 | #[inline] 82 | pub fn slew(out: &mut f32, in_: f32, delta: f32) { 83 | let mut error = (in_) - *out; 84 | let d = delta; 85 | if error > d { 86 | error = d; 87 | } else if error < -d { 88 | error = -d; 89 | } 90 | *out += error; 91 | } 92 | 93 | #[inline] 94 | pub fn crossfade(a: f32, b: f32, fade: f32) -> f32 { 95 | a + (b - a) * fade 96 | } 97 | 98 | #[inline] 99 | pub fn soft_limit(x: f32) -> f32 { 100 | x * (27.0 + x * x) / (27.0 + 9.0 * x * x) 101 | } 102 | 103 | #[inline] 104 | pub fn soft_clip(x: f32) -> f32 { 105 | if x < -3.0 { 106 | -1.0 107 | } else if x > 3.0 { 108 | 1.0 109 | } else { 110 | soft_limit(x) 111 | } 112 | } 113 | 114 | #[inline] 115 | pub fn clip_16(x: i32) -> i32 { 116 | x.clamp(-32768, 32767) 117 | } 118 | 119 | #[inline] 120 | pub fn sqrt(x: f32) -> f32 { 121 | f32::sqrt(x) 122 | } 123 | -------------------------------------------------------------------------------- /src/utils/parameter_interpolator.rs: -------------------------------------------------------------------------------- 1 | //! Linear interpolation of parameters in rendering loops. 2 | 3 | // Based on MIT-licensed code (c) 2015 by Olivier Gillet (ol.gillet@gmail.com) 4 | 5 | /// Original implementation keeping a mutable reference to the interpolated value. 6 | #[derive(Debug)] 7 | pub struct ParameterInterpolator<'a> { 8 | state: &'a mut f32, 9 | value: f32, 10 | increment: f32, 11 | } 12 | 13 | impl<'a> ParameterInterpolator<'a> { 14 | pub fn new(state: &'a mut f32, new_value: f32, size: usize) -> Self { 15 | let v = *state; 16 | Self { 17 | state, 18 | value: v, 19 | increment: (new_value - v) / (size as f32), 20 | } 21 | } 22 | 23 | pub fn new_with_step(state: &'a mut f32, new_value: f32, step: f32) -> Self { 24 | let v = *state; 25 | Self { 26 | state, 27 | value: v, 28 | increment: (new_value - v) * step, 29 | } 30 | } 31 | 32 | pub fn init(&mut self, state: &'a mut f32, new_value: f32, size: usize) { 33 | let v = *state; 34 | self.state = state; 35 | self.value = v; 36 | self.increment = (new_value - v) / (size as f32); 37 | } 38 | 39 | #[inline] 40 | #[allow(clippy::should_implement_trait)] 41 | pub fn next(&mut self) -> f32 { 42 | self.value += self.increment; 43 | self.value 44 | } 45 | 46 | #[inline] 47 | pub fn subsample(&self, t: f32) -> f32 { 48 | self.value + self.increment * t 49 | } 50 | } 51 | 52 | impl Drop for ParameterInterpolator<'_> { 53 | fn drop(&mut self) { 54 | *self.state = self.value; 55 | } 56 | } 57 | 58 | /// Simplified version of the interpolator not keeping any references to avoid borrowing issues. 59 | #[derive(Debug, Default, Copy, Clone)] 60 | pub struct SimpleParameterInterpolator { 61 | increment: f32, 62 | } 63 | 64 | impl SimpleParameterInterpolator { 65 | pub fn new(value: f32, new_value: f32, size: usize) -> Self { 66 | Self { 67 | increment: (new_value - value) / (size as f32), 68 | } 69 | } 70 | 71 | pub fn init(&mut self, value: f32, new_value: f32, size: usize) { 72 | self.increment = (new_value - value) / (size as f32) 73 | } 74 | 75 | #[inline] 76 | pub fn update(&self, value: &mut f32) -> f32 { 77 | *value += self.increment; 78 | *value 79 | } 80 | 81 | #[inline] 82 | pub fn subsample(&self, value: f32, t: f32) -> f32 { 83 | value + self.increment * t 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/utils/polyblep.rs: -------------------------------------------------------------------------------- 1 | //! Polynomial approximation of band-limited step for band-limited waveform 2 | //! synthesis. 3 | 4 | // Based on MIT-licensed code (c) 2017 by Emilie Gillet (emilie.o.gillet@gmail.com) 5 | 6 | #[inline] 7 | pub fn this_blep_sample(t: f32) -> f32 { 8 | 0.5 * t * t 9 | } 10 | 11 | #[inline] 12 | pub fn next_blep_sample(t: f32) -> f32 { 13 | let t = 1.0 - t; 14 | -0.5 * t * t 15 | } 16 | 17 | #[inline] 18 | pub fn next_integrated_blep_sample(t: f32) -> f32 { 19 | let t1 = 0.5 * t; 20 | let t2 = t1 * t1; 21 | let t4 = t2 * t2; 22 | 0.1875 - t1 + 1.5 * t2 - t4 23 | } 24 | 25 | #[inline] 26 | pub fn this_integrated_blep_sample(t: f32) -> f32 { 27 | next_integrated_blep_sample(1.0 - t) 28 | } 29 | -------------------------------------------------------------------------------- /src/utils/random.rs: -------------------------------------------------------------------------------- 1 | //! Fast 16-bit pseudo random number generator. 2 | 3 | // Based on MIT-licensed code (c) 2012 by Olivier Gillet (ol.gillet@gmail.com) 4 | 5 | use core::sync::atomic::{AtomicU32, Ordering}; 6 | 7 | static RNG_STATE: AtomicU32 = AtomicU32::new(0x21); 8 | 9 | #[inline] 10 | fn state() -> u32 { 11 | RNG_STATE.load(Ordering::Relaxed) 12 | } 13 | 14 | #[inline] 15 | pub fn seed(seed: u32) { 16 | RNG_STATE.store(seed, Ordering::Relaxed); 17 | } 18 | 19 | #[inline] 20 | pub fn get_word() -> u32 { 21 | RNG_STATE.store( 22 | RNG_STATE 23 | .load(Ordering::Relaxed) 24 | .wrapping_mul(1664525) 25 | .wrapping_add(1013904223), 26 | Ordering::Relaxed, 27 | ); 28 | state() 29 | } 30 | 31 | #[inline] 32 | pub fn get_sample() -> i16 { 33 | (get_word() >> 16) as i16 34 | } 35 | 36 | #[inline] 37 | pub fn get_float() -> f32 { 38 | get_word() as f32 / 4294967296.0 39 | } 40 | -------------------------------------------------------------------------------- /src/utils/rsqrt.rs: -------------------------------------------------------------------------------- 1 | //! Fast reciprocal of square-root routines. 2 | 3 | // Based on MIT-licensed code (c) 2014 by Olivier Gillet (ol.gillet@gmail.com) 4 | 5 | /// Inverse square root approximation as implemented by John Carmack. 6 | #[inline] 7 | pub fn fast_rsqrt_carmack(x: f32) -> f32 { 8 | if x > 0.0 { 9 | const THREEHALFS: f32 = 1.5; 10 | 11 | let mut y = x; 12 | let mut i = y.to_bits(); 13 | i = 0x5f3759df - (i >> 1); 14 | y = f32::from_bits(i); 15 | let x2 = x * 0.5; 16 | 17 | y * (THREEHALFS - (x2 * y * y)) 18 | } else { 19 | f32::INFINITY 20 | } 21 | } 22 | 23 | #[cfg(test)] 24 | mod tests { 25 | use super::*; 26 | 27 | #[test] 28 | fn test_carmack() { 29 | for n in 0..100 { 30 | let x = n as f32; 31 | let result = fast_rsqrt_carmack(x); 32 | let expected = 1.0 / x.sqrt(); 33 | let delta = ((result - expected) / expected).abs(); 34 | 35 | if delta.is_nan() { 36 | assert!(result.is_infinite()); 37 | assert!(expected.is_infinite()); 38 | } else { 39 | assert!( 40 | delta < 0.005, 41 | "x= {x:.3}, result={result:.3}, expected={expected:.3}, delta={delta:.3}" 42 | ); 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /tests/all_engines.rs: -------------------------------------------------------------------------------- 1 | //! Tests for the engines 2 | 3 | mod engines; 4 | mod modulation; 5 | mod wav_writer; 6 | -------------------------------------------------------------------------------- /tests/engines/additive_engine.rs: -------------------------------------------------------------------------------- 1 | //! Tests for additive engine 2 | 3 | use mi_plaits_dsp::engine::*; 4 | use mi_plaits_dsp::SAMPLE_RATE; 5 | 6 | use crate::modulation; 7 | use crate::wav_writer; 8 | 9 | const BLOCK_SIZE: usize = 24; 10 | 11 | #[test] 12 | fn additive_engine_harmonics() { 13 | let mut engine = additive_engine::AdditiveEngine::new(); 14 | let mut out = [0.0; BLOCK_SIZE]; 15 | let mut aux = [0.0; BLOCK_SIZE]; 16 | let mut wav_data = Vec::new(); 17 | let mut wav_data_aux = Vec::new(); 18 | 19 | engine.init(); 20 | 21 | let duration = 2.0; 22 | let blocks = (duration * SAMPLE_RATE / (BLOCK_SIZE as f32)) as usize; 23 | let mut already_enveloped = false; 24 | 25 | for n in 0..blocks { 26 | let parameters = EngineParameters { 27 | trigger: if n == 0 { 28 | TriggerState::RisingEdge 29 | } else { 30 | TriggerState::Low 31 | }, 32 | note: 48.0, 33 | timbre: 0.2, 34 | morph: 0.5, 35 | harmonics: modulation::ramp_up(n, blocks), 36 | accent: 1.0, 37 | }; 38 | 39 | engine.render(¶meters, &mut out, &mut aux, &mut already_enveloped); 40 | wav_data.extend_from_slice(&out); 41 | wav_data_aux.extend_from_slice(&aux); 42 | } 43 | 44 | wav_writer::write("engines/additive/additive_harmonics.wav", &wav_data).ok(); 45 | wav_writer::write("engines/additive/additive_harmonics_aux.wav", &wav_data_aux).ok(); 46 | } 47 | 48 | #[test] 49 | fn additive_engine_timbre() { 50 | let mut engine = additive_engine::AdditiveEngine::new(); 51 | let mut out = [0.0; BLOCK_SIZE]; 52 | let mut aux = [0.0; BLOCK_SIZE]; 53 | let mut wav_data = Vec::new(); 54 | let mut wav_data_aux = Vec::new(); 55 | 56 | engine.init(); 57 | 58 | let duration = 2.0; 59 | let blocks = (duration * SAMPLE_RATE / (BLOCK_SIZE as f32)) as usize; 60 | let mut already_enveloped = false; 61 | 62 | for n in 0..blocks { 63 | let parameters = EngineParameters { 64 | trigger: if n == 0 { 65 | TriggerState::RisingEdge 66 | } else { 67 | TriggerState::Low 68 | }, 69 | note: 48.0, 70 | timbre: modulation::ramp_up(n, blocks), 71 | morph: 0.5, 72 | harmonics: 0.5, 73 | accent: 1.0, 74 | }; 75 | 76 | engine.render(¶meters, &mut out, &mut aux, &mut already_enveloped); 77 | wav_data.extend_from_slice(&out); 78 | wav_data_aux.extend_from_slice(&aux); 79 | } 80 | 81 | wav_writer::write("engines/additive/additive_timbre.wav", &wav_data).ok(); 82 | wav_writer::write("engines/additive/additive_timbre_aux.wav", &wav_data_aux).ok(); 83 | } 84 | 85 | #[test] 86 | fn additive_engine_morph() { 87 | let mut engine = additive_engine::AdditiveEngine::new(); 88 | let mut out = [0.0; BLOCK_SIZE]; 89 | let mut aux = [0.0; BLOCK_SIZE]; 90 | let mut wav_data = Vec::new(); 91 | let mut wav_data_aux = Vec::new(); 92 | 93 | engine.init(); 94 | 95 | let duration = 2.0; 96 | let blocks = (duration * SAMPLE_RATE / (BLOCK_SIZE as f32)) as usize; 97 | let mut already_enveloped = false; 98 | 99 | for n in 0..blocks { 100 | let parameters = EngineParameters { 101 | trigger: if n == 0 { 102 | TriggerState::RisingEdge 103 | } else { 104 | TriggerState::Low 105 | }, 106 | note: 48.0, 107 | timbre: 0.2, 108 | morph: modulation::ramp_up(n, blocks), 109 | harmonics: 0.5, 110 | accent: 1.0, 111 | }; 112 | 113 | engine.render(¶meters, &mut out, &mut aux, &mut already_enveloped); 114 | wav_data.extend_from_slice(&out); 115 | wav_data_aux.extend_from_slice(&aux); 116 | } 117 | 118 | wav_writer::write("engines/additive/additive_morph.wav", &wav_data).ok(); 119 | wav_writer::write("engines/additive/additive_morph_aux.wav", &wav_data_aux).ok(); 120 | } 121 | -------------------------------------------------------------------------------- /tests/engines/bass_drum_engine.rs: -------------------------------------------------------------------------------- 1 | //! Tests for bass drum engine 2 | 3 | use mi_plaits_dsp::engine::*; 4 | use mi_plaits_dsp::SAMPLE_RATE; 5 | 6 | use crate::modulation; 7 | use crate::wav_writer; 8 | 9 | const BLOCK_SIZE: usize = 24; 10 | 11 | #[test] 12 | fn bass_drum_engine_harmonics() { 13 | let mut engine = bass_drum_engine::BassDrumEngine::new(); 14 | let mut out = [0.0; BLOCK_SIZE]; 15 | let mut aux = [0.0; BLOCK_SIZE]; 16 | let mut wav_data = Vec::new(); 17 | let mut wav_data_aux = Vec::new(); 18 | 19 | engine.init(); 20 | 21 | let duration = 2.0; 22 | let blocks = (duration * SAMPLE_RATE / (BLOCK_SIZE as f32)) as usize; 23 | let mut already_enveloped = false; 24 | 25 | for n in 0..blocks { 26 | let parameters = EngineParameters { 27 | trigger: if n % (blocks / 5) == 0 { 28 | TriggerState::RisingEdge 29 | } else { 30 | TriggerState::Low 31 | }, 32 | note: 48.0, 33 | timbre: 0.5, 34 | morph: 0.5, 35 | harmonics: modulation::ramp_up(n, blocks), 36 | accent: 1.0, 37 | }; 38 | 39 | engine.render(¶meters, &mut out, &mut aux, &mut already_enveloped); 40 | wav_data.extend_from_slice(&out); 41 | wav_data_aux.extend_from_slice(&aux); 42 | } 43 | 44 | wav_writer::write("engines/bass_drum/bass_drum_harmonics.wav", &wav_data).ok(); 45 | wav_writer::write( 46 | "engines/bass_drum/bass_drum_harmonics_aux.wav", 47 | &wav_data_aux, 48 | ) 49 | .ok(); 50 | } 51 | 52 | #[test] 53 | fn bass_drum_engine_timbre() { 54 | let mut engine = bass_drum_engine::BassDrumEngine::new(); 55 | let mut out = [0.0; BLOCK_SIZE]; 56 | let mut aux = [0.0; BLOCK_SIZE]; 57 | let mut wav_data = Vec::new(); 58 | let mut wav_data_aux = Vec::new(); 59 | 60 | engine.init(); 61 | 62 | let duration = 2.0; 63 | let blocks = (duration * SAMPLE_RATE / (BLOCK_SIZE as f32)) as usize; 64 | let mut already_enveloped = false; 65 | 66 | for n in 0..blocks { 67 | let parameters = EngineParameters { 68 | trigger: if n % (blocks / 5) == 0 { 69 | TriggerState::RisingEdge 70 | } else { 71 | TriggerState::Low 72 | }, 73 | note: 48.0, 74 | timbre: modulation::ramp_up(n, blocks), 75 | morph: 0.5, 76 | harmonics: 0.5, 77 | accent: 1.0, 78 | }; 79 | 80 | engine.render(¶meters, &mut out, &mut aux, &mut already_enveloped); 81 | wav_data.extend_from_slice(&out); 82 | wav_data_aux.extend_from_slice(&aux); 83 | } 84 | 85 | wav_writer::write("engines/bass_drum/bass_drum_timbre.wav", &wav_data).ok(); 86 | wav_writer::write("engines/bass_drum/bass_drum_timbre_aux.wav", &wav_data_aux).ok(); 87 | } 88 | 89 | #[test] 90 | fn bass_drum_engine_morph() { 91 | let mut engine = bass_drum_engine::BassDrumEngine::new(); 92 | let mut out = [0.0; BLOCK_SIZE]; 93 | let mut aux = [0.0; BLOCK_SIZE]; 94 | let mut wav_data = Vec::new(); 95 | let mut wav_data_aux = Vec::new(); 96 | 97 | engine.init(); 98 | 99 | let duration = 2.0; 100 | let blocks = (duration * SAMPLE_RATE / (BLOCK_SIZE as f32)) as usize; 101 | let mut already_enveloped = false; 102 | 103 | for n in 0..blocks { 104 | let parameters = EngineParameters { 105 | trigger: if n % (blocks / 5) == 0 { 106 | TriggerState::RisingEdge 107 | } else { 108 | TriggerState::Low 109 | }, 110 | note: 48.0, 111 | timbre: 0.5, 112 | morph: modulation::ramp_up(n, blocks), 113 | harmonics: 0.5, 114 | accent: 1.0, 115 | }; 116 | 117 | engine.render(¶meters, &mut out, &mut aux, &mut already_enveloped); 118 | wav_data.extend_from_slice(&out); 119 | wav_data_aux.extend_from_slice(&aux); 120 | } 121 | 122 | wav_writer::write("engines/bass_drum/bass_drum_morph.wav", &wav_data).ok(); 123 | wav_writer::write("engines/bass_drum/bass_drum_morph_aux.wav", &wav_data_aux).ok(); 124 | } 125 | -------------------------------------------------------------------------------- /tests/engines/chiptune_engine.rs: -------------------------------------------------------------------------------- 1 | //! Tests for chiptune engine 2 | 3 | use mi_plaits_dsp::engine::*; 4 | use mi_plaits_dsp::engine2::*; 5 | use mi_plaits_dsp::SAMPLE_RATE; 6 | 7 | use crate::modulation; 8 | use crate::wav_writer; 9 | 10 | const BLOCK_SIZE: usize = 24; 11 | 12 | #[test] 13 | fn chiptune_engine_harmonics() { 14 | let mut engine = chiptune_engine::ChiptuneEngine::new(); 15 | let mut out = [0.0; BLOCK_SIZE]; 16 | let mut aux = [0.0; BLOCK_SIZE]; 17 | let mut wav_data = Vec::new(); 18 | let mut wav_data_aux = Vec::new(); 19 | 20 | engine.init(); 21 | 22 | let duration = 2.0; 23 | let blocks = (duration * SAMPLE_RATE / (BLOCK_SIZE as f32)) as usize; 24 | let mut already_enveloped = false; 25 | 26 | for n in 0..blocks { 27 | let parameters = EngineParameters { 28 | trigger: if n % (blocks / 5) == 0 { 29 | TriggerState::RisingEdge 30 | } else { 31 | TriggerState::Low 32 | }, 33 | note: 48.0, 34 | timbre: 0.5, 35 | morph: 0.5, 36 | harmonics: modulation::ramp_up(n, blocks), 37 | accent: 1.0, 38 | }; 39 | 40 | engine.render(¶meters, &mut out, &mut aux, &mut already_enveloped); 41 | wav_data.extend_from_slice(&out); 42 | wav_data_aux.extend_from_slice(&aux); 43 | } 44 | 45 | wav_writer::write("engines/chiptune/chiptune_harmonics.wav", &wav_data).ok(); 46 | wav_writer::write("engines/chiptune/chiptune_harmonics_aux.wav", &wav_data_aux).ok(); 47 | } 48 | 49 | #[test] 50 | fn chiptune_engine_timbre() { 51 | let mut engine = chiptune_engine::ChiptuneEngine::new(); 52 | let mut out = [0.0; BLOCK_SIZE]; 53 | let mut aux = [0.0; BLOCK_SIZE]; 54 | let mut wav_data = Vec::new(); 55 | let mut wav_data_aux = Vec::new(); 56 | 57 | engine.init(); 58 | 59 | let duration = 2.0; 60 | let blocks = (duration * SAMPLE_RATE / (BLOCK_SIZE as f32)) as usize; 61 | let mut already_enveloped = false; 62 | 63 | for n in 0..blocks { 64 | let parameters = EngineParameters { 65 | trigger: if n % (blocks / 5) == 0 { 66 | TriggerState::RisingEdge 67 | } else { 68 | TriggerState::Low 69 | }, 70 | note: 48.0, 71 | timbre: modulation::ramp_up(n, blocks), 72 | morph: 0.5, 73 | harmonics: 0.5, 74 | accent: 1.0, 75 | }; 76 | 77 | engine.render(¶meters, &mut out, &mut aux, &mut already_enveloped); 78 | wav_data.extend_from_slice(&out); 79 | wav_data_aux.extend_from_slice(&aux); 80 | } 81 | 82 | wav_writer::write("engines/chiptune/chiptune_timbre.wav", &wav_data).ok(); 83 | wav_writer::write("engines/chiptune/chiptune_timbre_aux.wav", &wav_data_aux).ok(); 84 | } 85 | 86 | #[test] 87 | fn chiptune_engine_morph() { 88 | let mut engine = chiptune_engine::ChiptuneEngine::new(); 89 | let mut out = [0.0; BLOCK_SIZE]; 90 | let mut aux = [0.0; BLOCK_SIZE]; 91 | let mut wav_data = Vec::new(); 92 | let mut wav_data_aux = Vec::new(); 93 | 94 | engine.init(); 95 | 96 | let duration = 2.0; 97 | let blocks = (duration * SAMPLE_RATE / (BLOCK_SIZE as f32)) as usize; 98 | let mut already_enveloped = false; 99 | 100 | for n in 0..blocks { 101 | let parameters = EngineParameters { 102 | trigger: if n % (blocks / 5) == 0 { 103 | TriggerState::RisingEdge 104 | } else { 105 | TriggerState::Low 106 | }, 107 | note: 48.0, 108 | timbre: 0.5, 109 | morph: modulation::ramp_up(n, blocks), 110 | harmonics: 0.5, 111 | accent: 1.0, 112 | }; 113 | 114 | engine.render(¶meters, &mut out, &mut aux, &mut already_enveloped); 115 | wav_data.extend_from_slice(&out); 116 | wav_data_aux.extend_from_slice(&aux); 117 | } 118 | 119 | wav_writer::write("engines/chiptune/chiptune_morph.wav", &wav_data).ok(); 120 | wav_writer::write("engines/chiptune/chiptune_morph_aux.wav", &wav_data_aux).ok(); 121 | } 122 | -------------------------------------------------------------------------------- /tests/engines/chord_engine.rs: -------------------------------------------------------------------------------- 1 | //! Tests for chord engine 2 | 3 | use mi_plaits_dsp::engine::*; 4 | use mi_plaits_dsp::SAMPLE_RATE; 5 | 6 | use crate::modulation; 7 | use crate::wav_writer; 8 | 9 | const BLOCK_SIZE: usize = 24; 10 | 11 | #[test] 12 | fn chord_engine_harmonics() { 13 | let mut engine = chord_engine::ChordEngine::new(); 14 | let mut out = [0.0; BLOCK_SIZE]; 15 | let mut aux = [0.0; BLOCK_SIZE]; 16 | let mut wav_data = Vec::new(); 17 | let mut wav_data_aux = Vec::new(); 18 | 19 | engine.init(); 20 | 21 | let duration = 2.0; 22 | let blocks = (duration * SAMPLE_RATE / (BLOCK_SIZE as f32)) as usize; 23 | let mut already_enveloped = false; 24 | 25 | for n in 0..blocks { 26 | let parameters = EngineParameters { 27 | trigger: if n == 0 { 28 | TriggerState::RisingEdge 29 | } else { 30 | TriggerState::Low 31 | }, 32 | note: 48.0, 33 | timbre: 0.5, 34 | morph: 0.5, 35 | harmonics: modulation::ramp_up(n, blocks), 36 | accent: 1.0, 37 | }; 38 | 39 | engine.render(¶meters, &mut out, &mut aux, &mut already_enveloped); 40 | wav_data.extend_from_slice(&out); 41 | wav_data_aux.extend_from_slice(&aux); 42 | } 43 | 44 | wav_writer::write("engines/chord/chord_harmonics.wav", &wav_data).ok(); 45 | wav_writer::write("engines/chord/chord_harmonics_aux.wav", &wav_data_aux).ok(); 46 | } 47 | 48 | #[test] 49 | fn chord_engine_timbre() { 50 | let mut engine = chord_engine::ChordEngine::new(); 51 | let mut out = [0.0; BLOCK_SIZE]; 52 | let mut aux = [0.0; BLOCK_SIZE]; 53 | let mut wav_data = Vec::new(); 54 | let mut wav_data_aux = Vec::new(); 55 | 56 | engine.init(); 57 | 58 | let duration = 2.0; 59 | let blocks = (duration * SAMPLE_RATE / (BLOCK_SIZE as f32)) as usize; 60 | let mut already_enveloped = false; 61 | 62 | for n in 0..blocks { 63 | let parameters = EngineParameters { 64 | trigger: if n == 0 { 65 | TriggerState::RisingEdge 66 | } else { 67 | TriggerState::Low 68 | }, 69 | note: 48.0, 70 | timbre: modulation::ramp_up(n, blocks), 71 | morph: 0.5, 72 | harmonics: 0.5, 73 | accent: 1.0, 74 | }; 75 | 76 | engine.render(¶meters, &mut out, &mut aux, &mut already_enveloped); 77 | wav_data.extend_from_slice(&out); 78 | wav_data_aux.extend_from_slice(&aux); 79 | } 80 | 81 | wav_writer::write("engines/chord/chord_timbre.wav", &wav_data).ok(); 82 | wav_writer::write("engines/chord/chord_timbre_aux.wav", &wav_data_aux).ok(); 83 | } 84 | 85 | #[test] 86 | fn chord_engine_morph() { 87 | let mut engine = chord_engine::ChordEngine::new(); 88 | let mut out = [0.0; BLOCK_SIZE]; 89 | let mut aux = [0.0; BLOCK_SIZE]; 90 | let mut wav_data = Vec::new(); 91 | let mut wav_data_aux = Vec::new(); 92 | 93 | engine.init(); 94 | 95 | let duration = 2.0; 96 | let blocks = (duration * SAMPLE_RATE / (BLOCK_SIZE as f32)) as usize; 97 | let mut already_enveloped = false; 98 | 99 | for n in 0..blocks { 100 | let parameters = EngineParameters { 101 | trigger: if n == 0 { 102 | TriggerState::RisingEdge 103 | } else { 104 | TriggerState::Low 105 | }, 106 | note: 48.0, 107 | timbre: 0.5, 108 | morph: modulation::ramp_up(n, blocks), 109 | harmonics: 0.5, 110 | accent: 1.0, 111 | }; 112 | 113 | engine.render(¶meters, &mut out, &mut aux, &mut already_enveloped); 114 | wav_data.extend_from_slice(&out); 115 | wav_data_aux.extend_from_slice(&aux); 116 | } 117 | 118 | wav_writer::write("engines/chord/chord_morph.wav", &wav_data).ok(); 119 | wav_writer::write("engines/chord/chord_morph_aux.wav", &wav_data_aux).ok(); 120 | } 121 | -------------------------------------------------------------------------------- /tests/engines/fm_engine.rs: -------------------------------------------------------------------------------- 1 | //! Tests for fm engine 2 | 3 | use mi_plaits_dsp::engine::*; 4 | use mi_plaits_dsp::SAMPLE_RATE; 5 | 6 | use crate::modulation; 7 | use crate::wav_writer; 8 | 9 | const BLOCK_SIZE: usize = 24; 10 | 11 | #[test] 12 | fn fm_engine_harmonics() { 13 | let mut engine = fm_engine::FmEngine::new(); 14 | let mut out = [0.0; BLOCK_SIZE]; 15 | let mut aux = [0.0; BLOCK_SIZE]; 16 | let mut wav_data = Vec::new(); 17 | let mut wav_data_aux = Vec::new(); 18 | 19 | engine.init(); 20 | 21 | let duration = 2.0; 22 | let blocks = (duration * SAMPLE_RATE / (BLOCK_SIZE as f32)) as usize; 23 | let mut already_enveloped = false; 24 | 25 | for n in 0..blocks { 26 | let parameters = EngineParameters { 27 | trigger: if n == 0 { 28 | TriggerState::RisingEdge 29 | } else { 30 | TriggerState::Low 31 | }, 32 | note: 48.0, 33 | timbre: 0.5, 34 | morph: 0.5, 35 | harmonics: modulation::ramp_up(n, blocks), 36 | accent: 1.0, 37 | }; 38 | 39 | engine.render(¶meters, &mut out, &mut aux, &mut already_enveloped); 40 | wav_data.extend_from_slice(&out); 41 | wav_data_aux.extend_from_slice(&aux); 42 | } 43 | 44 | wav_writer::write("engines/fm/fm_harmonics.wav", &wav_data).ok(); 45 | wav_writer::write("engines/fm/fm_harmonics_aux.wav", &wav_data_aux).ok(); 46 | } 47 | 48 | #[test] 49 | fn fm_engine_timbre() { 50 | let mut engine = fm_engine::FmEngine::new(); 51 | let mut out = [0.0; BLOCK_SIZE]; 52 | let mut aux = [0.0; BLOCK_SIZE]; 53 | let mut wav_data = Vec::new(); 54 | let mut wav_data_aux = Vec::new(); 55 | 56 | engine.init(); 57 | 58 | let duration = 2.0; 59 | let blocks = (duration * SAMPLE_RATE / (BLOCK_SIZE as f32)) as usize; 60 | let mut already_enveloped = false; 61 | 62 | for n in 0..blocks { 63 | let parameters = EngineParameters { 64 | trigger: if n == 0 { 65 | TriggerState::RisingEdge 66 | } else { 67 | TriggerState::Low 68 | }, 69 | note: 48.0, 70 | timbre: modulation::ramp_up(n, blocks), 71 | morph: 0.5, 72 | harmonics: 0.5, 73 | accent: 1.0, 74 | }; 75 | 76 | engine.render(¶meters, &mut out, &mut aux, &mut already_enveloped); 77 | wav_data.extend_from_slice(&out); 78 | wav_data_aux.extend_from_slice(&aux); 79 | } 80 | 81 | wav_writer::write("engines/fm/fm_timbre.wav", &wav_data).ok(); 82 | wav_writer::write("engines/fm/fm_timbre_aux.wav", &wav_data_aux).ok(); 83 | } 84 | 85 | #[test] 86 | fn fm_engine_morph() { 87 | let mut engine = fm_engine::FmEngine::new(); 88 | let mut out = [0.0; BLOCK_SIZE]; 89 | let mut aux = [0.0; BLOCK_SIZE]; 90 | let mut wav_data = Vec::new(); 91 | let mut wav_data_aux = Vec::new(); 92 | 93 | engine.init(); 94 | 95 | let duration = 2.0; 96 | let blocks = (duration * SAMPLE_RATE / (BLOCK_SIZE as f32)) as usize; 97 | let mut already_enveloped = false; 98 | 99 | for n in 0..blocks { 100 | let parameters = EngineParameters { 101 | trigger: if n == 0 { 102 | TriggerState::RisingEdge 103 | } else { 104 | TriggerState::Low 105 | }, 106 | note: 48.0, 107 | timbre: 0.5, 108 | morph: modulation::ramp_up(n, blocks), 109 | harmonics: 0.5, 110 | accent: 1.0, 111 | }; 112 | 113 | engine.render(¶meters, &mut out, &mut aux, &mut already_enveloped); 114 | wav_data.extend_from_slice(&out); 115 | wav_data_aux.extend_from_slice(&aux); 116 | } 117 | 118 | wav_writer::write("engines/fm/fm_morph.wav", &wav_data).ok(); 119 | wav_writer::write("engines/fm/fm_morph_aux.wav", &wav_data_aux).ok(); 120 | } 121 | -------------------------------------------------------------------------------- /tests/engines/grain_engine.rs: -------------------------------------------------------------------------------- 1 | //! Tests for grain engine 2 | 3 | use mi_plaits_dsp::engine::*; 4 | use mi_plaits_dsp::SAMPLE_RATE; 5 | 6 | use crate::modulation; 7 | use crate::wav_writer; 8 | 9 | const BLOCK_SIZE: usize = 24; 10 | 11 | #[test] 12 | fn grain_engine_harmonics() { 13 | let mut engine = grain_engine::GrainEngine::new(); 14 | let mut out = [0.0; BLOCK_SIZE]; 15 | let mut aux = [0.0; BLOCK_SIZE]; 16 | let mut wav_data = Vec::new(); 17 | let mut wav_data_aux = Vec::new(); 18 | 19 | engine.init(); 20 | 21 | let duration = 2.0; 22 | let blocks = (duration * SAMPLE_RATE / (BLOCK_SIZE as f32)) as usize; 23 | let mut already_enveloped = false; 24 | 25 | for n in 0..blocks { 26 | let parameters = EngineParameters { 27 | trigger: if n == 0 { 28 | TriggerState::RisingEdge 29 | } else { 30 | TriggerState::Low 31 | }, 32 | note: 48.0, 33 | timbre: 0.5, 34 | morph: 0.5, 35 | harmonics: modulation::ramp_up(n, blocks), 36 | accent: 1.0, 37 | }; 38 | 39 | engine.render(¶meters, &mut out, &mut aux, &mut already_enveloped); 40 | wav_data.extend_from_slice(&out); 41 | wav_data_aux.extend_from_slice(&aux); 42 | } 43 | 44 | wav_writer::write("engines/grain/grain_harmonics.wav", &wav_data).ok(); 45 | wav_writer::write("engines/grain/grain_harmonics_aux.wav", &wav_data_aux).ok(); 46 | } 47 | 48 | #[test] 49 | fn grain_engine_timbre() { 50 | let mut engine = grain_engine::GrainEngine::new(); 51 | let mut out = [0.0; BLOCK_SIZE]; 52 | let mut aux = [0.0; BLOCK_SIZE]; 53 | let mut wav_data = Vec::new(); 54 | let mut wav_data_aux = Vec::new(); 55 | 56 | engine.init(); 57 | 58 | let duration = 2.0; 59 | let blocks = (duration * SAMPLE_RATE / (BLOCK_SIZE as f32)) as usize; 60 | let mut already_enveloped = false; 61 | 62 | for n in 0..blocks { 63 | let parameters = EngineParameters { 64 | trigger: if n == 0 { 65 | TriggerState::RisingEdge 66 | } else { 67 | TriggerState::Low 68 | }, 69 | note: 48.0, 70 | timbre: modulation::ramp_up(n, blocks), 71 | morph: 0.5, 72 | harmonics: 0.5, 73 | accent: 1.0, 74 | }; 75 | 76 | engine.render(¶meters, &mut out, &mut aux, &mut already_enveloped); 77 | wav_data.extend_from_slice(&out); 78 | wav_data_aux.extend_from_slice(&aux); 79 | } 80 | 81 | wav_writer::write("engines/grain/grain_timbre.wav", &wav_data).ok(); 82 | wav_writer::write("engines/grain/grain_timbre_aux.wav", &wav_data_aux).ok(); 83 | } 84 | 85 | #[test] 86 | fn grain_engine_morph() { 87 | let mut engine = grain_engine::GrainEngine::new(); 88 | let mut out = [0.0; BLOCK_SIZE]; 89 | let mut aux = [0.0; BLOCK_SIZE]; 90 | let mut wav_data = Vec::new(); 91 | let mut wav_data_aux = Vec::new(); 92 | 93 | engine.init(); 94 | 95 | let duration = 2.0; 96 | let blocks = (duration * SAMPLE_RATE / (BLOCK_SIZE as f32)) as usize; 97 | let mut already_enveloped = false; 98 | 99 | for n in 0..blocks { 100 | let parameters = EngineParameters { 101 | trigger: if n == 0 { 102 | TriggerState::RisingEdge 103 | } else { 104 | TriggerState::Low 105 | }, 106 | note: 48.0, 107 | timbre: 0.5, 108 | morph: modulation::ramp_up(n, blocks), 109 | harmonics: 0.5, 110 | accent: 1.0, 111 | }; 112 | 113 | engine.render(¶meters, &mut out, &mut aux, &mut already_enveloped); 114 | wav_data.extend_from_slice(&out); 115 | wav_data_aux.extend_from_slice(&aux); 116 | } 117 | 118 | wav_writer::write("engines/grain/grain_morph.wav", &wav_data).ok(); 119 | wav_writer::write("engines/grain/grain_morph_aux.wav", &wav_data_aux).ok(); 120 | } 121 | -------------------------------------------------------------------------------- /tests/engines/hihat_engine.rs: -------------------------------------------------------------------------------- 1 | //! Tests for hihat engine 2 | 3 | use mi_plaits_dsp::engine::*; 4 | use mi_plaits_dsp::SAMPLE_RATE; 5 | 6 | use crate::modulation; 7 | use crate::wav_writer; 8 | 9 | const BLOCK_SIZE: usize = 24; 10 | 11 | #[test] 12 | fn hihat_engine_harmonics() { 13 | let mut engine = hihat_engine::HihatEngine::new(BLOCK_SIZE); 14 | let mut out = [0.0; BLOCK_SIZE]; 15 | let mut aux = [0.0; BLOCK_SIZE]; 16 | let mut wav_data = Vec::new(); 17 | let mut wav_data_aux = Vec::new(); 18 | 19 | engine.init(); 20 | 21 | let duration = 2.0; 22 | let blocks = (duration * SAMPLE_RATE / (BLOCK_SIZE as f32)) as usize; 23 | let mut already_enveloped = false; 24 | 25 | for n in 0..blocks { 26 | let parameters = EngineParameters { 27 | trigger: if n % (blocks / 5) == 0 { 28 | TriggerState::RisingEdge 29 | } else { 30 | TriggerState::Low 31 | }, 32 | note: 48.0, 33 | timbre: 0.5, 34 | morph: 0.5, 35 | harmonics: modulation::ramp_up(n, blocks), 36 | accent: 1.0, 37 | }; 38 | 39 | engine.render(¶meters, &mut out, &mut aux, &mut already_enveloped); 40 | wav_data.extend_from_slice(&out); 41 | wav_data_aux.extend_from_slice(&aux); 42 | } 43 | 44 | wav_writer::write("engines/hihat/hihat_harmonics.wav", &wav_data).ok(); 45 | wav_writer::write("engines/hihat/hihat_harmonics_aux.wav", &wav_data_aux).ok(); 46 | } 47 | 48 | #[test] 49 | fn hihat_engine_timbre() { 50 | let mut engine = hihat_engine::HihatEngine::new(BLOCK_SIZE); 51 | let mut out = [0.0; BLOCK_SIZE]; 52 | let mut aux = [0.0; BLOCK_SIZE]; 53 | let mut wav_data = Vec::new(); 54 | let mut wav_data_aux = Vec::new(); 55 | 56 | engine.init(); 57 | 58 | let duration = 2.0; 59 | let blocks = (duration * SAMPLE_RATE / (BLOCK_SIZE as f32)) as usize; 60 | let mut already_enveloped = false; 61 | 62 | for n in 0..blocks { 63 | let parameters = EngineParameters { 64 | trigger: if n % (blocks / 5) == 0 { 65 | TriggerState::RisingEdge 66 | } else { 67 | TriggerState::Low 68 | }, 69 | note: 48.0, 70 | timbre: modulation::ramp_up(n, blocks), 71 | morph: 0.5, 72 | harmonics: 0.5, 73 | accent: 1.0, 74 | }; 75 | 76 | engine.render(¶meters, &mut out, &mut aux, &mut already_enveloped); 77 | wav_data.extend_from_slice(&out); 78 | wav_data_aux.extend_from_slice(&aux); 79 | } 80 | 81 | wav_writer::write("engines/hihat/hihat_timbre.wav", &wav_data).ok(); 82 | wav_writer::write("engines/hihat/hihat_timbre_aux.wav", &wav_data_aux).ok(); 83 | } 84 | 85 | #[test] 86 | fn hihat_engine_morph() { 87 | let mut engine = hihat_engine::HihatEngine::new(BLOCK_SIZE); 88 | let mut out = [0.0; BLOCK_SIZE]; 89 | let mut aux = [0.0; BLOCK_SIZE]; 90 | let mut wav_data = Vec::new(); 91 | let mut wav_data_aux = Vec::new(); 92 | 93 | engine.init(); 94 | 95 | let duration = 2.0; 96 | let blocks = (duration * SAMPLE_RATE / (BLOCK_SIZE as f32)) as usize; 97 | let mut already_enveloped = false; 98 | 99 | for n in 0..blocks { 100 | let parameters = EngineParameters { 101 | trigger: if n % (blocks / 5) == 0 { 102 | TriggerState::RisingEdge 103 | } else { 104 | TriggerState::Low 105 | }, 106 | note: 48.0, 107 | timbre: 0.5, 108 | morph: modulation::ramp_up(n, blocks), 109 | harmonics: 0.5, 110 | accent: 1.0, 111 | }; 112 | 113 | engine.render(¶meters, &mut out, &mut aux, &mut already_enveloped); 114 | wav_data.extend_from_slice(&out); 115 | wav_data_aux.extend_from_slice(&aux); 116 | } 117 | 118 | wav_writer::write("engines/hihat/hihat_morph.wav", &wav_data).ok(); 119 | wav_writer::write("engines/hihat/hihat_morph_aux.wav", &wav_data_aux).ok(); 120 | } 121 | -------------------------------------------------------------------------------- /tests/engines/mod.rs: -------------------------------------------------------------------------------- 1 | mod additive_engine; 2 | mod bass_drum_engine; 3 | mod chiptune_engine; 4 | mod chord_engine; 5 | mod fm_engine; 6 | mod grain_engine; 7 | mod hihat_engine; 8 | mod modal_engine; 9 | mod noise_engine; 10 | mod particle_engine; 11 | mod phase_distortion_engine; 12 | mod six_op_engine; 13 | mod snare_drum_engine; 14 | mod speech_engine; 15 | mod string_engine; 16 | mod string_machine_engine; 17 | mod swarm_engine; 18 | mod virtual_analog_engine; 19 | mod virtual_analog_vcf_engine; 20 | mod wave_terrain_engine; 21 | mod waveshaping_engine; 22 | mod wavetable_engine; 23 | -------------------------------------------------------------------------------- /tests/engines/modal_engine.rs: -------------------------------------------------------------------------------- 1 | //! Tests for modal engine 2 | 3 | use mi_plaits_dsp::engine::*; 4 | use mi_plaits_dsp::SAMPLE_RATE; 5 | 6 | use crate::modulation; 7 | use crate::wav_writer; 8 | 9 | const BLOCK_SIZE: usize = 24; 10 | 11 | #[test] 12 | fn modal_engine_harmonics() { 13 | let mut engine = modal_engine::ModalEngine::new(BLOCK_SIZE); 14 | let mut out = [0.0; BLOCK_SIZE]; 15 | let mut aux = [0.0; BLOCK_SIZE]; 16 | let mut wav_data = Vec::new(); 17 | let mut wav_data_aux = Vec::new(); 18 | 19 | engine.init(); 20 | 21 | let duration = 2.0; 22 | let blocks = (duration * SAMPLE_RATE / (BLOCK_SIZE as f32)) as usize; 23 | let mut already_enveloped = false; 24 | 25 | for n in 0..blocks { 26 | let parameters = EngineParameters { 27 | trigger: if n % (blocks / 5) == 0 { 28 | TriggerState::RisingEdge 29 | } else { 30 | TriggerState::Low 31 | }, 32 | note: 48.0, 33 | timbre: 0.5, 34 | morph: 0.5, 35 | harmonics: modulation::ramp_up(n, blocks), 36 | accent: 1.0, 37 | }; 38 | 39 | engine.render(¶meters, &mut out, &mut aux, &mut already_enveloped); 40 | wav_data.extend_from_slice(&out); 41 | wav_data_aux.extend_from_slice(&aux); 42 | } 43 | 44 | wav_writer::write("engines/modal/modal_harmonics.wav", &wav_data).ok(); 45 | wav_writer::write("engines/modal/modal_harmonics_aux.wav", &wav_data_aux).ok(); 46 | } 47 | 48 | #[test] 49 | fn modal_engine_timbre() { 50 | let mut engine = modal_engine::ModalEngine::new(BLOCK_SIZE); 51 | let mut out = [0.0; BLOCK_SIZE]; 52 | let mut aux = [0.0; BLOCK_SIZE]; 53 | let mut wav_data = Vec::new(); 54 | let mut wav_data_aux = Vec::new(); 55 | 56 | engine.init(); 57 | 58 | let duration = 2.0; 59 | let blocks = (duration * SAMPLE_RATE / (BLOCK_SIZE as f32)) as usize; 60 | let mut already_enveloped = false; 61 | 62 | for n in 0..blocks { 63 | let parameters = EngineParameters { 64 | trigger: if n % (blocks / 5) == 0 { 65 | TriggerState::RisingEdge 66 | } else { 67 | TriggerState::Low 68 | }, 69 | note: 48.0, 70 | timbre: modulation::ramp_up(n, blocks), 71 | morph: 0.5, 72 | harmonics: 0.5, 73 | accent: 1.0, 74 | }; 75 | 76 | engine.render(¶meters, &mut out, &mut aux, &mut already_enveloped); 77 | wav_data.extend_from_slice(&out); 78 | wav_data_aux.extend_from_slice(&aux); 79 | } 80 | 81 | wav_writer::write("engines/modal/modal_timbre.wav", &wav_data).ok(); 82 | wav_writer::write("engines/modal/modal_timbre_aux.wav", &wav_data_aux).ok(); 83 | } 84 | 85 | #[test] 86 | fn modal_engine_morph() { 87 | let mut engine = modal_engine::ModalEngine::new(BLOCK_SIZE); 88 | let mut out = [0.0; BLOCK_SIZE]; 89 | let mut aux = [0.0; BLOCK_SIZE]; 90 | let mut wav_data = Vec::new(); 91 | let mut wav_data_aux = Vec::new(); 92 | 93 | engine.init(); 94 | 95 | let duration = 2.0; 96 | let blocks = (duration * SAMPLE_RATE / (BLOCK_SIZE as f32)) as usize; 97 | let mut already_enveloped = false; 98 | 99 | for n in 0..blocks { 100 | let parameters = EngineParameters { 101 | trigger: if n % (blocks / 5) == 0 { 102 | TriggerState::RisingEdge 103 | } else { 104 | TriggerState::Low 105 | }, 106 | note: 48.0, 107 | timbre: 0.5, 108 | morph: modulation::ramp_up(n, blocks), 109 | harmonics: 0.5, 110 | accent: 1.0, 111 | }; 112 | 113 | engine.render(¶meters, &mut out, &mut aux, &mut already_enveloped); 114 | wav_data.extend_from_slice(&out); 115 | wav_data_aux.extend_from_slice(&aux); 116 | } 117 | 118 | wav_writer::write("engines/modal/modal_morph.wav", &wav_data).ok(); 119 | wav_writer::write("engines/modal/modal_morph_aux.wav", &wav_data_aux).ok(); 120 | } 121 | -------------------------------------------------------------------------------- /tests/engines/noise_engine.rs: -------------------------------------------------------------------------------- 1 | //! Tests for noise engine 2 | 3 | use mi_plaits_dsp::engine::*; 4 | use mi_plaits_dsp::SAMPLE_RATE; 5 | 6 | use crate::modulation; 7 | use crate::wav_writer; 8 | 9 | const BLOCK_SIZE: usize = 24; 10 | 11 | #[test] 12 | fn noise_engine_harmonics() { 13 | let mut engine = noise_engine::NoiseEngine::new(BLOCK_SIZE); 14 | let mut out = [0.0; BLOCK_SIZE]; 15 | let mut aux = [0.0; BLOCK_SIZE]; 16 | let mut wav_data = Vec::new(); 17 | let mut wav_data_aux = Vec::new(); 18 | 19 | engine.init(); 20 | 21 | let duration = 2.0; 22 | let blocks = (duration * SAMPLE_RATE / (BLOCK_SIZE as f32)) as usize; 23 | let mut already_enveloped = false; 24 | 25 | for n in 0..blocks { 26 | let parameters = EngineParameters { 27 | trigger: if n == 0 { 28 | TriggerState::RisingEdge 29 | } else { 30 | TriggerState::Low 31 | }, 32 | note: 48.0, 33 | timbre: 0.5, 34 | morph: 0.5, 35 | harmonics: modulation::ramp_up(n, blocks), 36 | accent: 1.0, 37 | }; 38 | 39 | engine.render(¶meters, &mut out, &mut aux, &mut already_enveloped); 40 | wav_data.extend_from_slice(&out); 41 | wav_data_aux.extend_from_slice(&aux); 42 | } 43 | 44 | wav_writer::write("engines/noise/noise_harmonics.wav", &wav_data).ok(); 45 | wav_writer::write("engines/noise/noise_harmonics_aux.wav", &wav_data_aux).ok(); 46 | } 47 | 48 | #[test] 49 | fn noise_engine_timbre() { 50 | let mut engine = noise_engine::NoiseEngine::new(BLOCK_SIZE); 51 | let mut out = [0.0; BLOCK_SIZE]; 52 | let mut aux = [0.0; BLOCK_SIZE]; 53 | let mut wav_data = Vec::new(); 54 | let mut wav_data_aux = Vec::new(); 55 | 56 | engine.init(); 57 | 58 | let duration = 2.0; 59 | let blocks = (duration * SAMPLE_RATE / (BLOCK_SIZE as f32)) as usize; 60 | let mut already_enveloped = false; 61 | 62 | for n in 0..blocks { 63 | let parameters = EngineParameters { 64 | trigger: if n == 0 { 65 | TriggerState::RisingEdge 66 | } else { 67 | TriggerState::Low 68 | }, 69 | note: 48.0, 70 | timbre: modulation::ramp_up(n, blocks), 71 | morph: 0.5, 72 | harmonics: 0.5, 73 | accent: 1.0, 74 | }; 75 | 76 | engine.render(¶meters, &mut out, &mut aux, &mut already_enveloped); 77 | wav_data.extend_from_slice(&out); 78 | wav_data_aux.extend_from_slice(&aux); 79 | } 80 | 81 | wav_writer::write("engines/noise/noise_timbre.wav", &wav_data).ok(); 82 | wav_writer::write("engines/noise/noise_timbre_aux.wav", &wav_data_aux).ok(); 83 | } 84 | 85 | #[test] 86 | fn noise_engine_morph() { 87 | let mut engine = noise_engine::NoiseEngine::new(BLOCK_SIZE); 88 | let mut out = [0.0; BLOCK_SIZE]; 89 | let mut aux = [0.0; BLOCK_SIZE]; 90 | let mut wav_data = Vec::new(); 91 | let mut wav_data_aux = Vec::new(); 92 | 93 | engine.init(); 94 | 95 | let duration = 2.0; 96 | let blocks = (duration * SAMPLE_RATE / (BLOCK_SIZE as f32)) as usize; 97 | let mut already_enveloped = false; 98 | 99 | for n in 0..blocks { 100 | let parameters = EngineParameters { 101 | trigger: if n == 0 { 102 | TriggerState::RisingEdge 103 | } else { 104 | TriggerState::Low 105 | }, 106 | note: 48.0, 107 | timbre: 0.5, 108 | morph: modulation::ramp_up(n, blocks), 109 | harmonics: 0.5, 110 | accent: 1.0, 111 | }; 112 | 113 | engine.render(¶meters, &mut out, &mut aux, &mut already_enveloped); 114 | wav_data.extend_from_slice(&out); 115 | wav_data_aux.extend_from_slice(&aux); 116 | } 117 | 118 | wav_writer::write("engines/noise/noise_morph.wav", &wav_data).ok(); 119 | wav_writer::write("engines/noise/noise_morph_aux.wav", &wav_data_aux).ok(); 120 | } 121 | -------------------------------------------------------------------------------- /tests/engines/particle_engine.rs: -------------------------------------------------------------------------------- 1 | //! Tests for noise engine 2 | 3 | use mi_plaits_dsp::engine::*; 4 | use mi_plaits_dsp::SAMPLE_RATE; 5 | 6 | use crate::modulation; 7 | use crate::wav_writer; 8 | 9 | const BLOCK_SIZE: usize = 24; 10 | 11 | #[test] 12 | fn particle_engine_harmonics() { 13 | let mut engine = particle_engine::ParticleEngine::new(BLOCK_SIZE); 14 | let mut out = [0.0; BLOCK_SIZE]; 15 | let mut aux = [0.0; BLOCK_SIZE]; 16 | let mut wav_data = Vec::new(); 17 | let mut wav_data_aux = Vec::new(); 18 | 19 | engine.init(); 20 | 21 | let duration = 2.0; 22 | let blocks = (duration * SAMPLE_RATE / (BLOCK_SIZE as f32)) as usize; 23 | let mut already_enveloped = false; 24 | 25 | for n in 0..blocks { 26 | let parameters = EngineParameters { 27 | trigger: if n % (blocks / 5) == 0 { 28 | TriggerState::RisingEdge 29 | } else { 30 | TriggerState::Low 31 | }, 32 | note: 48.0, 33 | timbre: 0.5, 34 | morph: 0.5, 35 | harmonics: modulation::ramp_up(n, blocks), 36 | accent: 1.0, 37 | }; 38 | 39 | engine.render(¶meters, &mut out, &mut aux, &mut already_enveloped); 40 | wav_data.extend_from_slice(&out); 41 | wav_data_aux.extend_from_slice(&aux); 42 | } 43 | 44 | wav_writer::write("engines/particle/particle_harmonics.wav", &wav_data).ok(); 45 | wav_writer::write("engines/particle/particle_harmonics_aux.wav", &wav_data_aux).ok(); 46 | } 47 | 48 | #[test] 49 | fn particle_engine_timbre() { 50 | let mut engine = particle_engine::ParticleEngine::new(BLOCK_SIZE); 51 | let mut out = [0.0; BLOCK_SIZE]; 52 | let mut aux = [0.0; BLOCK_SIZE]; 53 | let mut wav_data = Vec::new(); 54 | let mut wav_data_aux = Vec::new(); 55 | 56 | engine.init(); 57 | 58 | let duration = 2.0; 59 | let blocks = (duration * SAMPLE_RATE / (BLOCK_SIZE as f32)) as usize; 60 | let mut already_enveloped = false; 61 | 62 | for n in 0..blocks { 63 | let parameters = EngineParameters { 64 | trigger: if n % (blocks / 5) == 0 { 65 | TriggerState::RisingEdge 66 | } else { 67 | TriggerState::Low 68 | }, 69 | note: 48.0, 70 | timbre: modulation::ramp_up(n, blocks), 71 | morph: 0.5, 72 | harmonics: 0.5, 73 | accent: 1.0, 74 | }; 75 | 76 | engine.render(¶meters, &mut out, &mut aux, &mut already_enveloped); 77 | wav_data.extend_from_slice(&out); 78 | wav_data_aux.extend_from_slice(&aux); 79 | } 80 | 81 | wav_writer::write("engines/particle/particle_timbre.wav", &wav_data).ok(); 82 | wav_writer::write("engines/particle/particle_timbre_aux.wav", &wav_data_aux).ok(); 83 | } 84 | 85 | #[test] 86 | fn particle_engine_morph() { 87 | let mut engine = particle_engine::ParticleEngine::new(BLOCK_SIZE); 88 | let mut out = [0.0; BLOCK_SIZE]; 89 | let mut aux = [0.0; BLOCK_SIZE]; 90 | let mut wav_data = Vec::new(); 91 | let mut wav_data_aux = Vec::new(); 92 | 93 | engine.init(); 94 | 95 | let duration = 2.0; 96 | let blocks = (duration * SAMPLE_RATE / (BLOCK_SIZE as f32)) as usize; 97 | let mut already_enveloped = false; 98 | 99 | for n in 0..blocks { 100 | let parameters = EngineParameters { 101 | trigger: if n % (blocks / 5) == 0 { 102 | TriggerState::RisingEdge 103 | } else { 104 | TriggerState::Low 105 | }, 106 | note: 48.0, 107 | timbre: 0.5, 108 | morph: modulation::ramp_up(n, blocks), 109 | harmonics: 0.5, 110 | accent: 1.0, 111 | }; 112 | 113 | engine.render(¶meters, &mut out, &mut aux, &mut already_enveloped); 114 | wav_data.extend_from_slice(&out); 115 | wav_data_aux.extend_from_slice(&aux); 116 | } 117 | 118 | wav_writer::write("engines/particle/particle_morph.wav", &wav_data).ok(); 119 | wav_writer::write("engines/particle/particle_morph_aux.wav", &wav_data_aux).ok(); 120 | } 121 | -------------------------------------------------------------------------------- /tests/engines/phase_distortion_engine.rs: -------------------------------------------------------------------------------- 1 | //! Tests for phase distortion engine 2 | 3 | use mi_plaits_dsp::engine::*; 4 | use mi_plaits_dsp::engine2::*; 5 | use mi_plaits_dsp::SAMPLE_RATE; 6 | 7 | use crate::modulation; 8 | use crate::wav_writer; 9 | 10 | const BLOCK_SIZE: usize = 24; 11 | 12 | #[test] 13 | fn phase_distortion_engine_harmonics() { 14 | let mut engine = phase_distortion_engine::PhaseDistortionEngine::new(BLOCK_SIZE); 15 | let mut out = [0.0; BLOCK_SIZE]; 16 | let mut aux = [0.0; BLOCK_SIZE]; 17 | let mut wav_data = Vec::new(); 18 | let mut wav_data_aux = Vec::new(); 19 | 20 | engine.init(); 21 | 22 | let duration = 2.0; 23 | let blocks = (duration * SAMPLE_RATE / (BLOCK_SIZE as f32)) as usize; 24 | let mut already_enveloped = false; 25 | 26 | for n in 0..blocks { 27 | let parameters = EngineParameters { 28 | trigger: if n == 0 { 29 | TriggerState::RisingEdge 30 | } else { 31 | TriggerState::Low 32 | }, 33 | note: 48.0, 34 | timbre: 0.5, 35 | morph: 0.5, 36 | harmonics: modulation::ramp_up(n, blocks), 37 | accent: 1.0, 38 | }; 39 | 40 | engine.render(¶meters, &mut out, &mut aux, &mut already_enveloped); 41 | wav_data.extend_from_slice(&out); 42 | wav_data_aux.extend_from_slice(&aux); 43 | } 44 | 45 | wav_writer::write( 46 | "engines/phase_distortion/phase_distortion_harmonics.wav", 47 | &wav_data, 48 | ) 49 | .ok(); 50 | wav_writer::write( 51 | "engines/phase_distortion/phase_distortion_harmonics_aux.wav", 52 | &wav_data_aux, 53 | ) 54 | .ok(); 55 | } 56 | 57 | #[test] 58 | fn phase_distortion_engine_timbre() { 59 | let mut engine = phase_distortion_engine::PhaseDistortionEngine::new(BLOCK_SIZE); 60 | let mut out = [0.0; BLOCK_SIZE]; 61 | let mut aux = [0.0; BLOCK_SIZE]; 62 | let mut wav_data = Vec::new(); 63 | let mut wav_data_aux = Vec::new(); 64 | 65 | engine.init(); 66 | 67 | let duration = 2.0; 68 | let blocks = (duration * SAMPLE_RATE / (BLOCK_SIZE as f32)) as usize; 69 | let mut already_enveloped = false; 70 | 71 | for n in 0..blocks { 72 | let parameters = EngineParameters { 73 | trigger: if n == 0 { 74 | TriggerState::RisingEdge 75 | } else { 76 | TriggerState::Low 77 | }, 78 | note: 48.0, 79 | timbre: modulation::ramp_up(n, blocks), 80 | morph: 0.5, 81 | harmonics: 0.5, 82 | accent: 1.0, 83 | }; 84 | 85 | engine.render(¶meters, &mut out, &mut aux, &mut already_enveloped); 86 | wav_data.extend_from_slice(&out); 87 | wav_data_aux.extend_from_slice(&aux); 88 | } 89 | 90 | wav_writer::write( 91 | "engines/phase_distortion/phase_distortion_timbre.wav", 92 | &wav_data, 93 | ) 94 | .ok(); 95 | wav_writer::write( 96 | "engines/phase_distortion/phase_distortion_timbre_aux.wav", 97 | &wav_data_aux, 98 | ) 99 | .ok(); 100 | } 101 | 102 | #[test] 103 | fn phase_distortion_engine_morph() { 104 | let mut engine = phase_distortion_engine::PhaseDistortionEngine::new(BLOCK_SIZE); 105 | let mut out = [0.0; BLOCK_SIZE]; 106 | let mut aux = [0.0; BLOCK_SIZE]; 107 | let mut wav_data = Vec::new(); 108 | let mut wav_data_aux = Vec::new(); 109 | 110 | engine.init(); 111 | 112 | let duration = 2.0; 113 | let blocks = (duration * SAMPLE_RATE / (BLOCK_SIZE as f32)) as usize; 114 | let mut already_enveloped = false; 115 | 116 | for n in 0..blocks { 117 | let parameters = EngineParameters { 118 | trigger: if n == 0 { 119 | TriggerState::RisingEdge 120 | } else { 121 | TriggerState::Low 122 | }, 123 | note: 48.0, 124 | timbre: 0.5, 125 | morph: modulation::ramp_up(n, blocks), 126 | harmonics: 0.5, 127 | accent: 1.0, 128 | }; 129 | 130 | engine.render(¶meters, &mut out, &mut aux, &mut already_enveloped); 131 | wav_data.extend_from_slice(&out); 132 | wav_data_aux.extend_from_slice(&aux); 133 | } 134 | 135 | wav_writer::write( 136 | "engines/phase_distortion/phase_distortion_morph.wav", 137 | &wav_data, 138 | ) 139 | .ok(); 140 | wav_writer::write( 141 | "engines/phase_distortion/phase_distortion_morph_aux.wav", 142 | &wav_data_aux, 143 | ) 144 | .ok(); 145 | } 146 | -------------------------------------------------------------------------------- /tests/engines/six_op_engine.rs: -------------------------------------------------------------------------------- 1 | //! Tests for six op engine 2 | 3 | use mi_plaits_dsp::engine::*; 4 | use mi_plaits_dsp::engine2::*; 5 | use mi_plaits_dsp::resources::sysex::SYX_BANK_0; 6 | use mi_plaits_dsp::SAMPLE_RATE; 7 | 8 | use crate::modulation; 9 | use crate::wav_writer; 10 | 11 | const BLOCK_SIZE: usize = 24; 12 | 13 | #[test] 14 | fn six_op_engine_harmonics() { 15 | let mut engine = six_op_engine::SixOpEngine::new(BLOCK_SIZE); 16 | let mut out = [0.0; BLOCK_SIZE]; 17 | let mut aux = [0.0; BLOCK_SIZE]; 18 | let mut wav_data = Vec::new(); 19 | let mut wav_data_aux = Vec::new(); 20 | 21 | engine.init(); 22 | engine.load_syx_bank(&SYX_BANK_0); 23 | 24 | let duration = 2.0; 25 | let blocks = (duration * SAMPLE_RATE / (BLOCK_SIZE as f32)) as usize; 26 | let mut already_enveloped = false; 27 | 28 | for n in 0..blocks { 29 | let parameters = EngineParameters { 30 | trigger: TriggerState::Unpatched, 31 | note: 48.0, 32 | timbre: 0.5, 33 | morph: 0.5, 34 | harmonics: modulation::ramp_up(n, blocks), 35 | accent: 1.0, 36 | }; 37 | 38 | engine.render(¶meters, &mut out, &mut aux, &mut already_enveloped); 39 | wav_data.extend_from_slice(&out); 40 | wav_data_aux.extend_from_slice(&aux); 41 | } 42 | 43 | wav_writer::write("engines/six_op/six_op_harmonics.wav", &wav_data).ok(); 44 | wav_writer::write("engines/six_op/six_op_harmonics_aux.wav", &wav_data_aux).ok(); 45 | } 46 | 47 | #[test] 48 | fn six_op_engine_timbre() { 49 | let mut engine = six_op_engine::SixOpEngine::new(BLOCK_SIZE); 50 | let mut out = [0.0; BLOCK_SIZE]; 51 | let mut aux = [0.0; BLOCK_SIZE]; 52 | let mut wav_data = Vec::new(); 53 | let mut wav_data_aux = Vec::new(); 54 | 55 | engine.init(); 56 | engine.load_syx_bank(&SYX_BANK_0); 57 | 58 | let duration = 2.0; 59 | let blocks = (duration * SAMPLE_RATE / (BLOCK_SIZE as f32)) as usize; 60 | let mut already_enveloped = false; 61 | 62 | for n in 0..blocks { 63 | let parameters = EngineParameters { 64 | trigger: TriggerState::Unpatched, 65 | note: 48.0, 66 | timbre: modulation::ramp_up(n, blocks), 67 | morph: 0.5, 68 | harmonics: 0.5, 69 | accent: 1.0, 70 | }; 71 | 72 | engine.render(¶meters, &mut out, &mut aux, &mut already_enveloped); 73 | wav_data.extend_from_slice(&out); 74 | wav_data_aux.extend_from_slice(&aux); 75 | } 76 | 77 | wav_writer::write("engines/six_op/six_op_timbre.wav", &wav_data).ok(); 78 | wav_writer::write("engines/six_op/six_op_timbre_aux.wav", &wav_data_aux).ok(); 79 | } 80 | 81 | #[test] 82 | fn six_op_engine_morph() { 83 | let mut engine = six_op_engine::SixOpEngine::new(BLOCK_SIZE); 84 | let mut out = [0.0; BLOCK_SIZE]; 85 | let mut aux = [0.0; BLOCK_SIZE]; 86 | let mut wav_data = Vec::new(); 87 | let mut wav_data_aux = Vec::new(); 88 | 89 | engine.init(); 90 | engine.load_syx_bank(&SYX_BANK_0); 91 | 92 | let duration = 2.0; 93 | let blocks = (duration * SAMPLE_RATE / (BLOCK_SIZE as f32)) as usize; 94 | let mut already_enveloped = false; 95 | 96 | for n in 0..blocks { 97 | let parameters = EngineParameters { 98 | trigger: TriggerState::Unpatched, 99 | note: 48.0, 100 | timbre: 0.5, 101 | morph: modulation::ramp_up(n, blocks), 102 | harmonics: 0.5, 103 | accent: 1.0, 104 | }; 105 | 106 | engine.render(¶meters, &mut out, &mut aux, &mut already_enveloped); 107 | wav_data.extend_from_slice(&out); 108 | wav_data_aux.extend_from_slice(&aux); 109 | } 110 | 111 | wav_writer::write("engines/six_op/six_op_morph.wav", &wav_data).ok(); 112 | wav_writer::write("engines/six_op/six_op_morph_aux.wav", &wav_data_aux).ok(); 113 | } 114 | -------------------------------------------------------------------------------- /tests/engines/snare_drum_engine.rs: -------------------------------------------------------------------------------- 1 | //! Tests for snare drum engine 2 | 3 | use mi_plaits_dsp::engine::*; 4 | use mi_plaits_dsp::SAMPLE_RATE; 5 | 6 | use crate::modulation; 7 | use crate::wav_writer; 8 | 9 | const BLOCK_SIZE: usize = 24; 10 | 11 | #[test] 12 | fn snare_drum_engine_harmonics() { 13 | let mut engine = snare_drum_engine::SnareDrumEngine::new(); 14 | let mut out = [0.0; BLOCK_SIZE]; 15 | let mut aux = [0.0; BLOCK_SIZE]; 16 | let mut wav_data = Vec::new(); 17 | let mut wav_data_aux = Vec::new(); 18 | 19 | engine.init(); 20 | 21 | let duration = 2.0; 22 | let blocks = (duration * SAMPLE_RATE / (BLOCK_SIZE as f32)) as usize; 23 | let mut already_enveloped = false; 24 | 25 | for n in 0..blocks { 26 | let parameters = EngineParameters { 27 | trigger: if n % (blocks / 5) == 0 { 28 | TriggerState::RisingEdge 29 | } else { 30 | TriggerState::Low 31 | }, 32 | note: 48.0, 33 | timbre: 0.5, 34 | morph: 0.5, 35 | harmonics: modulation::ramp_up(n, blocks), 36 | accent: 1.0, 37 | }; 38 | 39 | engine.render(¶meters, &mut out, &mut aux, &mut already_enveloped); 40 | wav_data.extend_from_slice(&out); 41 | wav_data_aux.extend_from_slice(&aux); 42 | } 43 | 44 | wav_writer::write("engines/snare_drum/snare_drum_harmonics.wav", &wav_data).ok(); 45 | wav_writer::write( 46 | "engines/snare_drum/snare_drum_harmonics_aux.wav", 47 | &wav_data_aux, 48 | ) 49 | .ok(); 50 | } 51 | 52 | #[test] 53 | fn snare_drum_engine_timbre() { 54 | let mut engine = snare_drum_engine::SnareDrumEngine::new(); 55 | let mut out = [0.0; BLOCK_SIZE]; 56 | let mut aux = [0.0; BLOCK_SIZE]; 57 | let mut wav_data = Vec::new(); 58 | let mut wav_data_aux = Vec::new(); 59 | 60 | engine.init(); 61 | 62 | let duration = 2.0; 63 | let blocks = (duration * SAMPLE_RATE / (BLOCK_SIZE as f32)) as usize; 64 | let mut already_enveloped = false; 65 | 66 | for n in 0..blocks { 67 | let parameters = EngineParameters { 68 | trigger: if n % (blocks / 5) == 0 { 69 | TriggerState::RisingEdge 70 | } else { 71 | TriggerState::Low 72 | }, 73 | note: 48.0, 74 | timbre: modulation::ramp_up(n, blocks), 75 | morph: 0.5, 76 | harmonics: 0.5, 77 | accent: 1.0, 78 | }; 79 | 80 | engine.render(¶meters, &mut out, &mut aux, &mut already_enveloped); 81 | wav_data.extend_from_slice(&out); 82 | wav_data_aux.extend_from_slice(&aux); 83 | } 84 | 85 | wav_writer::write("engines/snare_drum/snare_drum_timbre.wav", &wav_data).ok(); 86 | wav_writer::write( 87 | "engines/snare_drum/snare_drum_timbre_aux.wav", 88 | &wav_data_aux, 89 | ) 90 | .ok(); 91 | } 92 | 93 | #[test] 94 | fn snare_drum_engine_morph() { 95 | let mut engine = snare_drum_engine::SnareDrumEngine::new(); 96 | let mut out = [0.0; BLOCK_SIZE]; 97 | let mut aux = [0.0; BLOCK_SIZE]; 98 | let mut wav_data = Vec::new(); 99 | let mut wav_data_aux = Vec::new(); 100 | 101 | engine.init(); 102 | 103 | let duration = 2.0; 104 | let blocks = (duration * SAMPLE_RATE / (BLOCK_SIZE as f32)) as usize; 105 | let mut already_enveloped = false; 106 | 107 | for n in 0..blocks { 108 | let parameters = EngineParameters { 109 | trigger: if n % (blocks / 5) == 0 { 110 | TriggerState::RisingEdge 111 | } else { 112 | TriggerState::Low 113 | }, 114 | note: 48.0, 115 | timbre: 0.5, 116 | morph: modulation::ramp_up(n, blocks), 117 | harmonics: 0.5, 118 | accent: 1.0, 119 | }; 120 | 121 | engine.render(¶meters, &mut out, &mut aux, &mut already_enveloped); 122 | wav_data.extend_from_slice(&out); 123 | wav_data_aux.extend_from_slice(&aux); 124 | } 125 | 126 | wav_writer::write("engines/snare_drum/snare_drum_morph.wav", &wav_data).ok(); 127 | wav_writer::write("engines/snare_drum/snare_drum_morph_aux.wav", &wav_data_aux).ok(); 128 | } 129 | -------------------------------------------------------------------------------- /tests/engines/speech_engine.rs: -------------------------------------------------------------------------------- 1 | //! Tests for speech engine 2 | 3 | use mi_plaits_dsp::engine::*; 4 | use mi_plaits_dsp::SAMPLE_RATE; 5 | 6 | use crate::modulation; 7 | use crate::wav_writer; 8 | 9 | const BLOCK_SIZE: usize = 24; 10 | 11 | #[test] 12 | fn speech_engine_harmonics() { 13 | let mut engine = speech_engine::SpeechEngine::new(BLOCK_SIZE); 14 | let mut out = [0.0; BLOCK_SIZE]; 15 | let mut aux = [0.0; BLOCK_SIZE]; 16 | let mut wav_data = Vec::new(); 17 | let mut wav_data_aux = Vec::new(); 18 | 19 | engine.init(); 20 | 21 | let duration = 2.0; 22 | let blocks = (duration * SAMPLE_RATE / (BLOCK_SIZE as f32)) as usize; 23 | let mut already_enveloped = false; 24 | 25 | for n in 0..blocks { 26 | let parameters = EngineParameters { 27 | trigger: if n % (blocks / 5) == 0 { 28 | TriggerState::RisingEdge 29 | } else { 30 | TriggerState::Low 31 | }, 32 | note: 48.0, 33 | timbre: 0.5, 34 | morph: 0.5, 35 | harmonics: modulation::ramp_up(n, blocks), 36 | accent: 1.0, 37 | }; 38 | 39 | engine.render(¶meters, &mut out, &mut aux, &mut already_enveloped); 40 | wav_data.extend_from_slice(&out); 41 | wav_data_aux.extend_from_slice(&aux); 42 | } 43 | 44 | wav_writer::write("engines/speech/speech_harmonics.wav", &wav_data).ok(); 45 | wav_writer::write("engines/speech/speech_harmonics_aux.wav", &wav_data_aux).ok(); 46 | } 47 | 48 | #[test] 49 | fn speech_engine_timbre() { 50 | let mut engine = speech_engine::SpeechEngine::new(BLOCK_SIZE); 51 | let mut out = [0.0; BLOCK_SIZE]; 52 | let mut aux = [0.0; BLOCK_SIZE]; 53 | let mut wav_data = Vec::new(); 54 | let mut wav_data_aux = Vec::new(); 55 | 56 | engine.init(); 57 | 58 | let duration = 2.0; 59 | let blocks = (duration * SAMPLE_RATE / (BLOCK_SIZE as f32)) as usize; 60 | let mut already_enveloped = false; 61 | 62 | for n in 0..blocks { 63 | let parameters = EngineParameters { 64 | trigger: if n % (blocks / 5) == 0 { 65 | TriggerState::RisingEdge 66 | } else { 67 | TriggerState::Low 68 | }, 69 | note: 48.0, 70 | timbre: modulation::ramp_up(n, blocks), 71 | morph: 0.5, 72 | harmonics: 0.5, 73 | accent: 1.0, 74 | }; 75 | 76 | engine.render(¶meters, &mut out, &mut aux, &mut already_enveloped); 77 | wav_data.extend_from_slice(&out); 78 | wav_data_aux.extend_from_slice(&aux); 79 | } 80 | 81 | wav_writer::write("engines/speech/speech_timbre.wav", &wav_data).ok(); 82 | wav_writer::write("engines/speech/speech_timbre_aux.wav", &wav_data_aux).ok(); 83 | } 84 | 85 | #[test] 86 | fn speech_engine_morph() { 87 | let mut engine = speech_engine::SpeechEngine::new(BLOCK_SIZE); 88 | let mut out = [0.0; BLOCK_SIZE]; 89 | let mut aux = [0.0; BLOCK_SIZE]; 90 | let mut wav_data = Vec::new(); 91 | let mut wav_data_aux = Vec::new(); 92 | 93 | engine.init(); 94 | 95 | let duration = 2.0; 96 | let blocks = (duration * SAMPLE_RATE / (BLOCK_SIZE as f32)) as usize; 97 | let mut already_enveloped = false; 98 | 99 | for n in 0..blocks { 100 | let parameters = EngineParameters { 101 | trigger: if n % (blocks / 5) == 0 { 102 | TriggerState::RisingEdge 103 | } else { 104 | TriggerState::Low 105 | }, 106 | note: 48.0, 107 | timbre: 0.5, 108 | morph: modulation::ramp_up(n, blocks), 109 | harmonics: 0.5, 110 | accent: 1.0, 111 | }; 112 | 113 | engine.render(¶meters, &mut out, &mut aux, &mut already_enveloped); 114 | wav_data.extend_from_slice(&out); 115 | wav_data_aux.extend_from_slice(&aux); 116 | } 117 | 118 | wav_writer::write("engines/speech/speech_morph.wav", &wav_data).ok(); 119 | wav_writer::write("engines/speech/speech_morph_aux.wav", &wav_data_aux).ok(); 120 | } 121 | -------------------------------------------------------------------------------- /tests/engines/string_engine.rs: -------------------------------------------------------------------------------- 1 | //! Tests for string engine 2 | 3 | use mi_plaits_dsp::engine::*; 4 | use mi_plaits_dsp::SAMPLE_RATE; 5 | 6 | use crate::modulation; 7 | use crate::wav_writer; 8 | 9 | const BLOCK_SIZE: usize = 24; 10 | 11 | #[test] 12 | fn string_engine_harmonics() { 13 | let mut engine = string_engine::StringEngine::new(BLOCK_SIZE); 14 | let mut out = [0.0; BLOCK_SIZE]; 15 | let mut aux = [0.0; BLOCK_SIZE]; 16 | let mut wav_data = Vec::new(); 17 | let mut wav_data_aux = Vec::new(); 18 | 19 | engine.init(); 20 | 21 | let duration = 2.0; 22 | let blocks = (duration * SAMPLE_RATE / (BLOCK_SIZE as f32)) as usize; 23 | let mut already_enveloped = false; 24 | 25 | for n in 0..blocks { 26 | let parameters = EngineParameters { 27 | trigger: if n % (blocks / 5) == 0 { 28 | TriggerState::RisingEdge 29 | } else { 30 | TriggerState::Low 31 | }, 32 | note: 48.0, 33 | timbre: 0.5, 34 | morph: 0.5, 35 | harmonics: modulation::ramp_up(n, blocks), 36 | accent: 1.0, 37 | }; 38 | 39 | engine.render(¶meters, &mut out, &mut aux, &mut already_enveloped); 40 | wav_data.extend_from_slice(&out); 41 | wav_data_aux.extend_from_slice(&aux); 42 | } 43 | 44 | wav_writer::write("engines/string/string_harmonics.wav", &wav_data).ok(); 45 | wav_writer::write("engines/string/string_harmonics_aux.wav", &wav_data_aux).ok(); 46 | } 47 | 48 | #[test] 49 | fn string_engine_timbre() { 50 | let mut engine = string_engine::StringEngine::new(BLOCK_SIZE); 51 | let mut out = [0.0; BLOCK_SIZE]; 52 | let mut aux = [0.0; BLOCK_SIZE]; 53 | let mut wav_data = Vec::new(); 54 | let mut wav_data_aux = Vec::new(); 55 | 56 | engine.init(); 57 | 58 | let duration = 2.0; 59 | let blocks = (duration * SAMPLE_RATE / (BLOCK_SIZE as f32)) as usize; 60 | let mut already_enveloped = false; 61 | 62 | for n in 0..blocks { 63 | let parameters = EngineParameters { 64 | trigger: if n % (blocks / 5) == 0 { 65 | TriggerState::RisingEdge 66 | } else { 67 | TriggerState::Low 68 | }, 69 | note: 48.0, 70 | timbre: modulation::ramp_up(n, blocks), 71 | morph: 0.5, 72 | harmonics: 0.5, 73 | accent: 1.0, 74 | }; 75 | 76 | engine.render(¶meters, &mut out, &mut aux, &mut already_enveloped); 77 | wav_data.extend_from_slice(&out); 78 | wav_data_aux.extend_from_slice(&aux); 79 | } 80 | 81 | wav_writer::write("engines/string/string_timbre.wav", &wav_data).ok(); 82 | wav_writer::write("engines/string/string_timbre_aux.wav", &wav_data_aux).ok(); 83 | } 84 | 85 | #[test] 86 | fn string_engine_morph() { 87 | let mut engine = string_engine::StringEngine::new(BLOCK_SIZE); 88 | let mut out = [0.0; BLOCK_SIZE]; 89 | let mut aux = [0.0; BLOCK_SIZE]; 90 | let mut wav_data = Vec::new(); 91 | let mut wav_data_aux = Vec::new(); 92 | 93 | engine.init(); 94 | 95 | let duration = 2.0; 96 | let blocks = (duration * SAMPLE_RATE / (BLOCK_SIZE as f32)) as usize; 97 | let mut already_enveloped = false; 98 | 99 | for n in 0..blocks { 100 | let parameters = EngineParameters { 101 | trigger: if n % (blocks / 5) == 0 { 102 | TriggerState::RisingEdge 103 | } else { 104 | TriggerState::Low 105 | }, 106 | note: 48.0, 107 | timbre: 0.5, 108 | morph: modulation::ramp_up(n, blocks), 109 | harmonics: 0.5, 110 | accent: 1.0, 111 | }; 112 | 113 | engine.render(¶meters, &mut out, &mut aux, &mut already_enveloped); 114 | wav_data.extend_from_slice(&out); 115 | wav_data_aux.extend_from_slice(&aux); 116 | } 117 | 118 | wav_writer::write("engines/string/string_morph.wav", &wav_data).ok(); 119 | wav_writer::write("engines/string/string_morph_aux.wav", &wav_data_aux).ok(); 120 | } 121 | -------------------------------------------------------------------------------- /tests/engines/string_machine_engine.rs: -------------------------------------------------------------------------------- 1 | //! Tests for string machine engine 2 | 3 | use mi_plaits_dsp::engine::*; 4 | use mi_plaits_dsp::engine2::*; 5 | use mi_plaits_dsp::SAMPLE_RATE; 6 | 7 | use crate::modulation; 8 | use crate::wav_writer; 9 | 10 | const BLOCK_SIZE: usize = 24; 11 | 12 | #[test] 13 | fn string_machine_engine_harmonics() { 14 | let mut engine = string_machine_engine::StringMachineEngine::new(); 15 | let mut out = [0.0; BLOCK_SIZE]; 16 | let mut aux = [0.0; BLOCK_SIZE]; 17 | let mut wav_data = Vec::new(); 18 | let mut wav_data_aux = Vec::new(); 19 | 20 | engine.init(); 21 | 22 | let duration = 2.0; 23 | let blocks = (duration * SAMPLE_RATE / (BLOCK_SIZE as f32)) as usize; 24 | let mut already_enveloped = false; 25 | 26 | for n in 0..blocks { 27 | let parameters = EngineParameters { 28 | trigger: if n == 0 { 29 | TriggerState::RisingEdge 30 | } else { 31 | TriggerState::Low 32 | }, 33 | note: 48.0, 34 | timbre: 0.5, 35 | morph: 0.5, 36 | harmonics: modulation::ramp_up(n, blocks), 37 | accent: 1.0, 38 | }; 39 | 40 | engine.render(¶meters, &mut out, &mut aux, &mut already_enveloped); 41 | wav_data.extend_from_slice(&out); 42 | wav_data_aux.extend_from_slice(&aux); 43 | } 44 | 45 | wav_writer::write( 46 | "engines/string_machine/string_machine_harmonics.wav", 47 | &wav_data, 48 | ) 49 | .ok(); 50 | wav_writer::write( 51 | "engines/string_machine/string_machine_harmonics_aux.wav", 52 | &wav_data_aux, 53 | ) 54 | .ok(); 55 | } 56 | 57 | #[test] 58 | fn string_machine_engine_timbre() { 59 | let mut engine = string_machine_engine::StringMachineEngine::new(); 60 | let mut out = [0.0; BLOCK_SIZE]; 61 | let mut aux = [0.0; BLOCK_SIZE]; 62 | let mut wav_data = Vec::new(); 63 | let mut wav_data_aux = Vec::new(); 64 | 65 | engine.init(); 66 | 67 | let duration = 2.0; 68 | let blocks = (duration * SAMPLE_RATE / (BLOCK_SIZE as f32)) as usize; 69 | let mut already_enveloped = false; 70 | 71 | for n in 0..blocks { 72 | let parameters = EngineParameters { 73 | trigger: if n == 0 { 74 | TriggerState::RisingEdge 75 | } else { 76 | TriggerState::Low 77 | }, 78 | note: 48.0, 79 | timbre: modulation::ramp_up(n, blocks), 80 | morph: 0.5, 81 | harmonics: 0.5, 82 | accent: 1.0, 83 | }; 84 | 85 | engine.render(¶meters, &mut out, &mut aux, &mut already_enveloped); 86 | wav_data.extend_from_slice(&out); 87 | wav_data_aux.extend_from_slice(&aux); 88 | } 89 | 90 | wav_writer::write( 91 | "engines/string_machine/string_machine_timbre.wav", 92 | &wav_data, 93 | ) 94 | .ok(); 95 | wav_writer::write( 96 | "engines/string_machine/string_machine_timbre_aux.wav", 97 | &wav_data_aux, 98 | ) 99 | .ok(); 100 | } 101 | 102 | #[test] 103 | fn string_machine_engine_morph() { 104 | let mut engine = string_machine_engine::StringMachineEngine::new(); 105 | let mut out = [0.0; BLOCK_SIZE]; 106 | let mut aux = [0.0; BLOCK_SIZE]; 107 | let mut wav_data = Vec::new(); 108 | let mut wav_data_aux = Vec::new(); 109 | 110 | engine.init(); 111 | 112 | let duration = 2.0; 113 | let blocks = (duration * SAMPLE_RATE / (BLOCK_SIZE as f32)) as usize; 114 | let mut already_enveloped = false; 115 | 116 | for n in 0..blocks { 117 | let parameters = EngineParameters { 118 | trigger: if n == 0 { 119 | TriggerState::RisingEdge 120 | } else { 121 | TriggerState::Low 122 | }, 123 | note: 48.0, 124 | timbre: 0.5, 125 | morph: modulation::ramp_up(n, blocks), 126 | harmonics: 0.5, 127 | accent: 1.0, 128 | }; 129 | 130 | engine.render(¶meters, &mut out, &mut aux, &mut already_enveloped); 131 | wav_data.extend_from_slice(&out); 132 | wav_data_aux.extend_from_slice(&aux); 133 | } 134 | 135 | wav_writer::write("engines/string_machine/string_machine_morph.wav", &wav_data).ok(); 136 | wav_writer::write( 137 | "engines/string_machine/string_machine_morph_aux.wav", 138 | &wav_data_aux, 139 | ) 140 | .ok(); 141 | } 142 | -------------------------------------------------------------------------------- /tests/engines/swarm_engine.rs: -------------------------------------------------------------------------------- 1 | //! Tests for swarm engine 2 | 3 | use mi_plaits_dsp::engine::*; 4 | use mi_plaits_dsp::SAMPLE_RATE; 5 | 6 | use crate::modulation; 7 | use crate::wav_writer; 8 | 9 | const BLOCK_SIZE: usize = 24; 10 | 11 | #[test] 12 | fn swarm_engine_harmonics() { 13 | let mut engine = swarm_engine::SwarmEngine::new(); 14 | let mut out = [0.0; BLOCK_SIZE]; 15 | let mut aux = [0.0; BLOCK_SIZE]; 16 | let mut wav_data = Vec::new(); 17 | let mut wav_data_aux = Vec::new(); 18 | 19 | engine.init(); 20 | 21 | let duration = 2.0; 22 | let blocks = (duration * SAMPLE_RATE / (BLOCK_SIZE as f32)) as usize; 23 | let mut already_enveloped = false; 24 | 25 | for n in 0..blocks { 26 | let parameters = EngineParameters { 27 | trigger: if n == 0 { 28 | TriggerState::RisingEdge 29 | } else { 30 | TriggerState::Low 31 | }, 32 | note: 48.0, 33 | timbre: 0.5, 34 | morph: 0.5, 35 | harmonics: modulation::ramp_up(n, blocks), 36 | accent: 1.0, 37 | }; 38 | 39 | engine.render(¶meters, &mut out, &mut aux, &mut already_enveloped); 40 | wav_data.extend_from_slice(&out); 41 | wav_data_aux.extend_from_slice(&aux); 42 | } 43 | 44 | wav_writer::write("engines/swarm/swarm_harmonics.wav", &wav_data).ok(); 45 | wav_writer::write("engines/swarm/swarm_harmonics_aux.wav", &wav_data_aux).ok(); 46 | } 47 | 48 | #[test] 49 | fn swarm_engine_timbre() { 50 | let mut engine = swarm_engine::SwarmEngine::new(); 51 | let mut out = [0.0; BLOCK_SIZE]; 52 | let mut aux = [0.0; BLOCK_SIZE]; 53 | let mut wav_data = Vec::new(); 54 | let mut wav_data_aux = Vec::new(); 55 | 56 | engine.init(); 57 | 58 | let duration = 2.0; 59 | let blocks = (duration * SAMPLE_RATE / (BLOCK_SIZE as f32)) as usize; 60 | let mut already_enveloped = false; 61 | 62 | for n in 0..blocks { 63 | let parameters = EngineParameters { 64 | trigger: if n == 0 { 65 | TriggerState::RisingEdge 66 | } else { 67 | TriggerState::Low 68 | }, 69 | note: 48.0, 70 | timbre: modulation::ramp_up(n, blocks), 71 | morph: 0.5, 72 | harmonics: 0.5, 73 | accent: 1.0, 74 | }; 75 | 76 | engine.render(¶meters, &mut out, &mut aux, &mut already_enveloped); 77 | wav_data.extend_from_slice(&out); 78 | wav_data_aux.extend_from_slice(&aux); 79 | } 80 | 81 | wav_writer::write("engines/swarm/swarm_timbre.wav", &wav_data).ok(); 82 | wav_writer::write("engines/swarm/swarm_timbre_aux.wav", &wav_data_aux).ok(); 83 | } 84 | 85 | #[test] 86 | fn swarm_engine_morph() { 87 | let mut engine = swarm_engine::SwarmEngine::new(); 88 | let mut out = [0.0; BLOCK_SIZE]; 89 | let mut aux = [0.0; BLOCK_SIZE]; 90 | let mut wav_data = Vec::new(); 91 | let mut wav_data_aux = Vec::new(); 92 | 93 | engine.init(); 94 | 95 | let duration = 2.0; 96 | let blocks = (duration * SAMPLE_RATE / (BLOCK_SIZE as f32)) as usize; 97 | let mut already_enveloped = false; 98 | 99 | for n in 0..blocks { 100 | let parameters = EngineParameters { 101 | trigger: if n == 0 { 102 | TriggerState::RisingEdge 103 | } else { 104 | TriggerState::Low 105 | }, 106 | note: 48.0, 107 | timbre: 0.5, 108 | morph: modulation::ramp_up(n, blocks), 109 | harmonics: 0.5, 110 | accent: 1.0, 111 | }; 112 | 113 | engine.render(¶meters, &mut out, &mut aux, &mut already_enveloped); 114 | wav_data.extend_from_slice(&out); 115 | wav_data_aux.extend_from_slice(&aux); 116 | } 117 | 118 | wav_writer::write("engines/swarm/swarm_morph.wav", &wav_data).ok(); 119 | wav_writer::write("engines/swarm/swarm_morph_aux.wav", &wav_data_aux).ok(); 120 | } 121 | -------------------------------------------------------------------------------- /tests/engines/virtual_analog_engine.rs: -------------------------------------------------------------------------------- 1 | //! Tests for virtual analog engine 2 | 3 | use mi_plaits_dsp::engine::*; 4 | use mi_plaits_dsp::SAMPLE_RATE; 5 | 6 | use crate::modulation; 7 | use crate::wav_writer; 8 | 9 | const BLOCK_SIZE: usize = 24; 10 | 11 | #[test] 12 | fn virtual_analog_engine_harmonics() { 13 | let mut engine = virtual_analog_engine::VirtualAnalogEngine::new(BLOCK_SIZE); 14 | let mut out = [0.0; BLOCK_SIZE]; 15 | let mut aux = [0.0; BLOCK_SIZE]; 16 | let mut wav_data = Vec::new(); 17 | let mut wav_data_aux = Vec::new(); 18 | 19 | engine.init(); 20 | 21 | let duration = 2.0; 22 | let blocks = (duration * SAMPLE_RATE / (BLOCK_SIZE as f32)) as usize; 23 | let mut already_enveloped = false; 24 | 25 | for n in 0..blocks { 26 | let parameters = EngineParameters { 27 | trigger: if n == 0 { 28 | TriggerState::RisingEdge 29 | } else { 30 | TriggerState::Low 31 | }, 32 | note: 48.0, 33 | timbre: 0.5, 34 | morph: 0.5, 35 | harmonics: modulation::ramp_up(n, blocks), 36 | accent: 1.0, 37 | }; 38 | 39 | engine.render(¶meters, &mut out, &mut aux, &mut already_enveloped); 40 | wav_data.extend_from_slice(&out); 41 | wav_data_aux.extend_from_slice(&aux); 42 | } 43 | 44 | wav_writer::write( 45 | "engines/virtual_analog/virtual_analog_harmonics.wav", 46 | &wav_data, 47 | ) 48 | .ok(); 49 | wav_writer::write( 50 | "engines/virtual_analog/virtual_analog_harmonics_aux.wav", 51 | &wav_data_aux, 52 | ) 53 | .ok(); 54 | } 55 | 56 | #[test] 57 | fn virtual_analog_engine_timbre() { 58 | let mut engine = virtual_analog_engine::VirtualAnalogEngine::new(BLOCK_SIZE); 59 | let mut out = [0.0; BLOCK_SIZE]; 60 | let mut aux = [0.0; BLOCK_SIZE]; 61 | let mut wav_data = Vec::new(); 62 | let mut wav_data_aux = Vec::new(); 63 | 64 | engine.init(); 65 | 66 | let duration = 2.0; 67 | let blocks = (duration * SAMPLE_RATE / (BLOCK_SIZE as f32)) as usize; 68 | let mut already_enveloped = false; 69 | 70 | for n in 0..blocks { 71 | let parameters = EngineParameters { 72 | trigger: if n == 0 { 73 | TriggerState::RisingEdge 74 | } else { 75 | TriggerState::Low 76 | }, 77 | note: 48.0, 78 | timbre: modulation::ramp_up(n, blocks), 79 | morph: 0.5, 80 | harmonics: 0.5, 81 | accent: 1.0, 82 | }; 83 | 84 | engine.render(¶meters, &mut out, &mut aux, &mut already_enveloped); 85 | wav_data.extend_from_slice(&out); 86 | wav_data_aux.extend_from_slice(&aux); 87 | } 88 | 89 | wav_writer::write( 90 | "engines/virtual_analog/virtual_analog_timbre.wav", 91 | &wav_data, 92 | ) 93 | .ok(); 94 | wav_writer::write( 95 | "engines/virtual_analog/virtual_analog_timbre_aux.wav", 96 | &wav_data_aux, 97 | ) 98 | .ok(); 99 | } 100 | 101 | #[test] 102 | fn virtual_analog_engine_morph() { 103 | let mut engine = virtual_analog_engine::VirtualAnalogEngine::new(BLOCK_SIZE); 104 | let mut out = [0.0; BLOCK_SIZE]; 105 | let mut aux = [0.0; BLOCK_SIZE]; 106 | let mut wav_data = Vec::new(); 107 | let mut wav_data_aux = Vec::new(); 108 | 109 | engine.init(); 110 | 111 | let duration = 2.0; 112 | let blocks = (duration * SAMPLE_RATE / (BLOCK_SIZE as f32)) as usize; 113 | let mut already_enveloped = false; 114 | 115 | for n in 0..blocks { 116 | let parameters = EngineParameters { 117 | trigger: if n == 0 { 118 | TriggerState::RisingEdge 119 | } else { 120 | TriggerState::Low 121 | }, 122 | note: 48.0, 123 | timbre: 0.5, 124 | morph: modulation::ramp_up(n, blocks), 125 | harmonics: 0.5, 126 | accent: 1.0, 127 | }; 128 | 129 | engine.render(¶meters, &mut out, &mut aux, &mut already_enveloped); 130 | wav_data.extend_from_slice(&out); 131 | wav_data_aux.extend_from_slice(&aux); 132 | } 133 | 134 | wav_writer::write("engines/virtual_analog/virtual_analog_morph.wav", &wav_data).ok(); 135 | wav_writer::write( 136 | "engines/virtual_analog/virtual_analog_morph_aux.wav", 137 | &wav_data_aux, 138 | ) 139 | .ok(); 140 | } 141 | -------------------------------------------------------------------------------- /tests/engines/virtual_analog_vcf_engine.rs: -------------------------------------------------------------------------------- 1 | //! Tests for virtual analog vcf engine 2 | 3 | use mi_plaits_dsp::engine::*; 4 | use mi_plaits_dsp::engine2::*; 5 | use mi_plaits_dsp::SAMPLE_RATE; 6 | 7 | use crate::modulation; 8 | use crate::wav_writer; 9 | 10 | const BLOCK_SIZE: usize = 24; 11 | 12 | #[test] 13 | fn virtual_analog_vcf_engine_harmonics() { 14 | let mut engine = virtual_analog_vcf_engine::VirtualAnalogVcfEngine::new(); 15 | let mut out = [0.0; BLOCK_SIZE]; 16 | let mut aux = [0.0; BLOCK_SIZE]; 17 | let mut wav_data = Vec::new(); 18 | let mut wav_data_aux = Vec::new(); 19 | 20 | engine.init(); 21 | 22 | let duration = 2.0; 23 | let blocks = (duration * SAMPLE_RATE / (BLOCK_SIZE as f32)) as usize; 24 | let mut already_enveloped = false; 25 | 26 | for n in 0..blocks { 27 | let parameters = EngineParameters { 28 | trigger: if n == 0 { 29 | TriggerState::RisingEdge 30 | } else { 31 | TriggerState::Low 32 | }, 33 | note: 48.0, 34 | timbre: 0.5, 35 | morph: 0.5, 36 | harmonics: modulation::ramp_up(n, blocks), 37 | accent: 1.0, 38 | }; 39 | 40 | engine.render(¶meters, &mut out, &mut aux, &mut already_enveloped); 41 | wav_data.extend_from_slice(&out); 42 | wav_data_aux.extend_from_slice(&aux); 43 | } 44 | 45 | wav_writer::write( 46 | "engines/virtual_analog_vcf/virtual_analog_vcf_harmonics.wav", 47 | &wav_data, 48 | ) 49 | .ok(); 50 | wav_writer::write( 51 | "engines/virtual_analog_vcf/virtual_analog_vcf_harmonics_aux.wav", 52 | &wav_data_aux, 53 | ) 54 | .ok(); 55 | } 56 | 57 | #[test] 58 | fn virtual_analog_vcf_engine_timbre() { 59 | let mut engine = virtual_analog_vcf_engine::VirtualAnalogVcfEngine::new(); 60 | let mut out = [0.0; BLOCK_SIZE]; 61 | let mut aux = [0.0; BLOCK_SIZE]; 62 | let mut wav_data = Vec::new(); 63 | let mut wav_data_aux = Vec::new(); 64 | 65 | engine.init(); 66 | 67 | let duration = 2.0; 68 | let blocks = (duration * SAMPLE_RATE / (BLOCK_SIZE as f32)) as usize; 69 | let mut already_enveloped = false; 70 | 71 | for n in 0..blocks { 72 | let parameters = EngineParameters { 73 | trigger: if n == 0 { 74 | TriggerState::RisingEdge 75 | } else { 76 | TriggerState::Low 77 | }, 78 | note: 48.0, 79 | timbre: modulation::ramp_up(n, blocks), 80 | morph: 0.5, 81 | harmonics: 0.5, 82 | accent: 1.0, 83 | }; 84 | 85 | engine.render(¶meters, &mut out, &mut aux, &mut already_enveloped); 86 | wav_data.extend_from_slice(&out); 87 | wav_data_aux.extend_from_slice(&aux); 88 | } 89 | 90 | wav_writer::write( 91 | "engines/virtual_analog_vcf/virtual_analog_vcf_timbre.wav", 92 | &wav_data, 93 | ) 94 | .ok(); 95 | wav_writer::write( 96 | "engines/virtual_analog_vcf/virtual_analog_vcf_timbre_aux.wav", 97 | &wav_data_aux, 98 | ) 99 | .ok(); 100 | } 101 | 102 | #[test] 103 | fn virtual_analog_vcf_engine_morph() { 104 | let mut engine = virtual_analog_vcf_engine::VirtualAnalogVcfEngine::new(); 105 | let mut out = [0.0; BLOCK_SIZE]; 106 | let mut aux = [0.0; BLOCK_SIZE]; 107 | let mut wav_data = Vec::new(); 108 | let mut wav_data_aux = Vec::new(); 109 | 110 | engine.init(); 111 | 112 | let duration = 2.0; 113 | let blocks = (duration * SAMPLE_RATE / (BLOCK_SIZE as f32)) as usize; 114 | let mut already_enveloped = false; 115 | 116 | for n in 0..blocks { 117 | let parameters = EngineParameters { 118 | trigger: if n == 0 { 119 | TriggerState::RisingEdge 120 | } else { 121 | TriggerState::Low 122 | }, 123 | note: 48.0, 124 | timbre: 0.5, 125 | morph: modulation::ramp_up(n, blocks), 126 | harmonics: 0.5, 127 | accent: 1.0, 128 | }; 129 | 130 | engine.render(¶meters, &mut out, &mut aux, &mut already_enveloped); 131 | wav_data.extend_from_slice(&out); 132 | wav_data_aux.extend_from_slice(&aux); 133 | } 134 | 135 | wav_writer::write( 136 | "engines/virtual_analog_vcf/virtual_analog_vcf_morph.wav", 137 | &wav_data, 138 | ) 139 | .ok(); 140 | wav_writer::write( 141 | "engines/virtual_analog_vcf/virtual_analog_vcf_morph_aux.wav", 142 | &wav_data_aux, 143 | ) 144 | .ok(); 145 | } 146 | -------------------------------------------------------------------------------- /tests/engines/wave_terrain_engine.rs: -------------------------------------------------------------------------------- 1 | //! Tests for wave terrain engine 2 | 3 | use mi_plaits_dsp::engine::*; 4 | use mi_plaits_dsp::engine2::*; 5 | use mi_plaits_dsp::SAMPLE_RATE; 6 | 7 | use crate::modulation; 8 | use crate::wav_writer; 9 | 10 | const BLOCK_SIZE: usize = 24; 11 | 12 | #[test] 13 | fn wave_terrain_engine_harmonics() { 14 | let mut engine = wave_terrain_engine::WaveTerrainEngine::new(BLOCK_SIZE); 15 | let mut out = [0.0; BLOCK_SIZE]; 16 | let mut aux = [0.0; BLOCK_SIZE]; 17 | let mut wav_data = Vec::new(); 18 | let mut wav_data_aux = Vec::new(); 19 | 20 | engine.init(); 21 | 22 | let duration = 2.0; 23 | let blocks = (duration * SAMPLE_RATE / (BLOCK_SIZE as f32)) as usize; 24 | let mut already_enveloped = false; 25 | 26 | for n in 0..blocks { 27 | let parameters = EngineParameters { 28 | trigger: if n == 0 { 29 | TriggerState::RisingEdge 30 | } else { 31 | TriggerState::Low 32 | }, 33 | note: 48.0, 34 | timbre: 0.5, 35 | morph: 0.5, 36 | harmonics: modulation::ramp_up(n, blocks), 37 | accent: 1.0, 38 | }; 39 | 40 | engine.render(¶meters, &mut out, &mut aux, &mut already_enveloped); 41 | wav_data.extend_from_slice(&out); 42 | wav_data_aux.extend_from_slice(&aux); 43 | } 44 | 45 | wav_writer::write("engines/wave_terrain/wave_terrain_harmonics.wav", &wav_data).ok(); 46 | wav_writer::write( 47 | "engines/wave_terrain/wave_terrain_harmonics_aux.wav", 48 | &wav_data_aux, 49 | ) 50 | .ok(); 51 | } 52 | 53 | #[test] 54 | fn wave_terrain_engine_timbre() { 55 | let mut engine = wave_terrain_engine::WaveTerrainEngine::new(BLOCK_SIZE); 56 | let mut out = [0.0; BLOCK_SIZE]; 57 | let mut aux = [0.0; BLOCK_SIZE]; 58 | let mut wav_data = Vec::new(); 59 | let mut wav_data_aux = Vec::new(); 60 | 61 | engine.init(); 62 | 63 | let duration = 2.0; 64 | let blocks = (duration * SAMPLE_RATE / (BLOCK_SIZE as f32)) as usize; 65 | let mut already_enveloped = false; 66 | 67 | for n in 0..blocks { 68 | let parameters = EngineParameters { 69 | trigger: if n == 0 { 70 | TriggerState::RisingEdge 71 | } else { 72 | TriggerState::Low 73 | }, 74 | note: 48.0, 75 | timbre: modulation::ramp_up(n, blocks), 76 | morph: 0.5, 77 | harmonics: 0.5, 78 | accent: 1.0, 79 | }; 80 | 81 | engine.render(¶meters, &mut out, &mut aux, &mut already_enveloped); 82 | wav_data.extend_from_slice(&out); 83 | wav_data_aux.extend_from_slice(&aux); 84 | } 85 | 86 | wav_writer::write("engines/wave_terrain/wave_terrain_timbre.wav", &wav_data).ok(); 87 | wav_writer::write( 88 | "engines/wave_terrain/wave_terrain_timbre_aux.wav", 89 | &wav_data_aux, 90 | ) 91 | .ok(); 92 | } 93 | 94 | #[test] 95 | fn wave_terrain_engine_morph() { 96 | let mut engine = wave_terrain_engine::WaveTerrainEngine::new(BLOCK_SIZE); 97 | let mut out = [0.0; BLOCK_SIZE]; 98 | let mut aux = [0.0; BLOCK_SIZE]; 99 | let mut wav_data = Vec::new(); 100 | let mut wav_data_aux = Vec::new(); 101 | 102 | engine.init(); 103 | 104 | let duration = 2.0; 105 | let blocks = (duration * SAMPLE_RATE / (BLOCK_SIZE as f32)) as usize; 106 | let mut already_enveloped = false; 107 | 108 | for n in 0..blocks { 109 | let parameters = EngineParameters { 110 | trigger: if n == 0 { 111 | TriggerState::RisingEdge 112 | } else { 113 | TriggerState::Low 114 | }, 115 | note: 48.0, 116 | timbre: 0.5, 117 | morph: modulation::ramp_up(n, blocks), 118 | harmonics: 0.5, 119 | accent: 1.0, 120 | }; 121 | 122 | engine.render(¶meters, &mut out, &mut aux, &mut already_enveloped); 123 | wav_data.extend_from_slice(&out); 124 | wav_data_aux.extend_from_slice(&aux); 125 | } 126 | 127 | wav_writer::write("engines/wave_terrain/wave_terrain_morph.wav", &wav_data).ok(); 128 | wav_writer::write( 129 | "engines/wave_terrain/wave_terrain_morph_aux.wav", 130 | &wav_data_aux, 131 | ) 132 | .ok(); 133 | } 134 | -------------------------------------------------------------------------------- /tests/engines/waveshaping_engine.rs: -------------------------------------------------------------------------------- 1 | //! Tests for waveshaping engine 2 | 3 | use mi_plaits_dsp::engine::*; 4 | use mi_plaits_dsp::SAMPLE_RATE; 5 | 6 | use crate::modulation; 7 | use crate::wav_writer; 8 | 9 | const BLOCK_SIZE: usize = 24; 10 | 11 | #[test] 12 | fn waveshaping_engine_harmonics() { 13 | let mut engine = waveshaping_engine::WaveshapingEngine::new(); 14 | let mut out = [0.0; BLOCK_SIZE]; 15 | let mut aux = [0.0; BLOCK_SIZE]; 16 | let mut wav_data = Vec::new(); 17 | let mut wav_data_aux = Vec::new(); 18 | 19 | engine.init(); 20 | 21 | let duration = 2.0; 22 | let blocks = (duration * SAMPLE_RATE / (BLOCK_SIZE as f32)) as usize; 23 | let mut already_enveloped = false; 24 | 25 | for n in 0..blocks { 26 | let parameters = EngineParameters { 27 | trigger: if n == 0 { 28 | TriggerState::RisingEdge 29 | } else { 30 | TriggerState::Low 31 | }, 32 | note: 48.0, 33 | timbre: 0.5, 34 | morph: 0.5, 35 | harmonics: modulation::ramp_up(n, blocks), 36 | accent: 1.0, 37 | }; 38 | 39 | engine.render(¶meters, &mut out, &mut aux, &mut already_enveloped); 40 | wav_data.extend_from_slice(&out); 41 | wav_data_aux.extend_from_slice(&aux); 42 | } 43 | 44 | wav_writer::write("engines/waveshaping/waveshaping_harmonics.wav", &wav_data).ok(); 45 | wav_writer::write( 46 | "engines/waveshaping/waveshaping_harmonics_aux.wav", 47 | &wav_data_aux, 48 | ) 49 | .ok(); 50 | } 51 | 52 | #[test] 53 | fn waveshaping_engine_timbre() { 54 | let mut engine = waveshaping_engine::WaveshapingEngine::new(); 55 | let mut out = [0.0; BLOCK_SIZE]; 56 | let mut aux = [0.0; BLOCK_SIZE]; 57 | let mut wav_data = Vec::new(); 58 | let mut wav_data_aux = Vec::new(); 59 | 60 | engine.init(); 61 | 62 | let duration = 2.0; 63 | let blocks = (duration * SAMPLE_RATE / (BLOCK_SIZE as f32)) as usize; 64 | let mut already_enveloped = false; 65 | 66 | for n in 0..blocks { 67 | let parameters = EngineParameters { 68 | trigger: if n == 0 { 69 | TriggerState::RisingEdge 70 | } else { 71 | TriggerState::Low 72 | }, 73 | note: 48.0, 74 | timbre: modulation::ramp_up(n, blocks), 75 | morph: 0.5, 76 | harmonics: 0.5, 77 | accent: 1.0, 78 | }; 79 | 80 | engine.render(¶meters, &mut out, &mut aux, &mut already_enveloped); 81 | wav_data.extend_from_slice(&out); 82 | wav_data_aux.extend_from_slice(&aux); 83 | } 84 | 85 | wav_writer::write("engines/waveshaping/waveshaping_timbre.wav", &wav_data).ok(); 86 | wav_writer::write( 87 | "engines/waveshaping/waveshaping_timbre_aux.wav", 88 | &wav_data_aux, 89 | ) 90 | .ok(); 91 | } 92 | 93 | #[test] 94 | fn waveshaping_engine_morph() { 95 | let mut engine = waveshaping_engine::WaveshapingEngine::new(); 96 | let mut out = [0.0; BLOCK_SIZE]; 97 | let mut aux = [0.0; BLOCK_SIZE]; 98 | let mut wav_data = Vec::new(); 99 | let mut wav_data_aux = Vec::new(); 100 | 101 | engine.init(); 102 | 103 | let duration = 2.0; 104 | let blocks = (duration * SAMPLE_RATE / (BLOCK_SIZE as f32)) as usize; 105 | let mut already_enveloped = false; 106 | 107 | for n in 0..blocks { 108 | let parameters = EngineParameters { 109 | trigger: if n == 0 { 110 | TriggerState::RisingEdge 111 | } else { 112 | TriggerState::Low 113 | }, 114 | note: 48.0, 115 | timbre: 0.5, 116 | morph: modulation::ramp_up(n, blocks), 117 | harmonics: 0.5, 118 | accent: 1.0, 119 | }; 120 | 121 | engine.render(¶meters, &mut out, &mut aux, &mut already_enveloped); 122 | wav_data.extend_from_slice(&out); 123 | wav_data_aux.extend_from_slice(&aux); 124 | } 125 | 126 | wav_writer::write("engines/waveshaping/waveshaping_morph.wav", &wav_data).ok(); 127 | wav_writer::write( 128 | "engines/waveshaping/waveshaping_morph_aux.wav", 129 | &wav_data_aux, 130 | ) 131 | .ok(); 132 | } 133 | -------------------------------------------------------------------------------- /tests/engines/wavetable_engine.rs: -------------------------------------------------------------------------------- 1 | //! Tests for wavetable engine 2 | 3 | use mi_plaits_dsp::engine::*; 4 | use mi_plaits_dsp::SAMPLE_RATE; 5 | 6 | use crate::modulation; 7 | use crate::wav_writer; 8 | 9 | const BLOCK_SIZE: usize = 24; 10 | 11 | #[test] 12 | fn wavetable_engine_harmonics() { 13 | let mut engine = wavetable_engine::WavetableEngine::new(); 14 | let mut out = [0.0; BLOCK_SIZE]; 15 | let mut aux = [0.0; BLOCK_SIZE]; 16 | let mut wav_data = Vec::new(); 17 | let mut wav_data_aux = Vec::new(); 18 | 19 | engine.init(); 20 | 21 | let duration = 2.0; 22 | let blocks = (duration * SAMPLE_RATE / (BLOCK_SIZE as f32)) as usize; 23 | let mut already_enveloped = false; 24 | 25 | for n in 0..blocks { 26 | let parameters = EngineParameters { 27 | trigger: if n == 0 { 28 | TriggerState::RisingEdge 29 | } else { 30 | TriggerState::Low 31 | }, 32 | note: 48.0, 33 | timbre: 0.5, 34 | morph: 0.5, 35 | harmonics: modulation::ramp_up(n, blocks), 36 | accent: 1.0, 37 | }; 38 | 39 | engine.render(¶meters, &mut out, &mut aux, &mut already_enveloped); 40 | wav_data.extend_from_slice(&out); 41 | wav_data_aux.extend_from_slice(&aux); 42 | } 43 | 44 | wav_writer::write("engines/wavetable/wavetable_harmonics.wav", &wav_data).ok(); 45 | wav_writer::write( 46 | "engines/wavetable/wavetable_harmonics_aux.wav", 47 | &wav_data_aux, 48 | ) 49 | .ok(); 50 | } 51 | 52 | #[test] 53 | fn wavetable_engine_timbre() { 54 | let mut engine = wavetable_engine::WavetableEngine::new(); 55 | let mut out = [0.0; BLOCK_SIZE]; 56 | let mut aux = [0.0; BLOCK_SIZE]; 57 | let mut wav_data = Vec::new(); 58 | let mut wav_data_aux = Vec::new(); 59 | 60 | engine.init(); 61 | 62 | let duration = 2.0; 63 | let blocks = (duration * SAMPLE_RATE / (BLOCK_SIZE as f32)) as usize; 64 | let mut already_enveloped = false; 65 | 66 | for n in 0..blocks { 67 | let parameters = EngineParameters { 68 | trigger: if n == 0 { 69 | TriggerState::RisingEdge 70 | } else { 71 | TriggerState::Low 72 | }, 73 | note: 48.0, 74 | timbre: modulation::ramp_up(n, blocks), 75 | morph: 0.5, 76 | harmonics: 0.5, 77 | accent: 1.0, 78 | }; 79 | 80 | engine.render(¶meters, &mut out, &mut aux, &mut already_enveloped); 81 | wav_data.extend_from_slice(&out); 82 | wav_data_aux.extend_from_slice(&aux); 83 | } 84 | 85 | wav_writer::write("engines/wavetable/wavetable_timbre.wav", &wav_data).ok(); 86 | wav_writer::write("engines/wavetable/wavetable_timbre_aux.wav", &wav_data_aux).ok(); 87 | } 88 | 89 | #[test] 90 | fn wavetable_engine_morph() { 91 | let mut engine = wavetable_engine::WavetableEngine::new(); 92 | let mut out = [0.0; BLOCK_SIZE]; 93 | let mut aux = [0.0; BLOCK_SIZE]; 94 | let mut wav_data = Vec::new(); 95 | let mut wav_data_aux = Vec::new(); 96 | 97 | engine.init(); 98 | 99 | let duration = 2.0; 100 | let blocks = (duration * SAMPLE_RATE / (BLOCK_SIZE as f32)) as usize; 101 | let mut already_enveloped = false; 102 | 103 | for n in 0..blocks { 104 | let parameters = EngineParameters { 105 | trigger: if n == 0 { 106 | TriggerState::RisingEdge 107 | } else { 108 | TriggerState::Low 109 | }, 110 | note: 48.0, 111 | timbre: 0.5, 112 | morph: modulation::ramp_up(n, blocks), 113 | harmonics: 0.5, 114 | accent: 1.0, 115 | }; 116 | 117 | engine.render(¶meters, &mut out, &mut aux, &mut already_enveloped); 118 | wav_data.extend_from_slice(&out); 119 | wav_data_aux.extend_from_slice(&aux); 120 | } 121 | 122 | wav_writer::write("engines/wavetable/wavetable_morph.wav", &wav_data).ok(); 123 | wav_writer::write("engines/wavetable/wavetable_morph_aux.wav", &wav_data_aux).ok(); 124 | } 125 | -------------------------------------------------------------------------------- /tests/fx.rs: -------------------------------------------------------------------------------- 1 | //! Tests for the effects 2 | 3 | mod modulation; 4 | mod wav_writer; 5 | 6 | use mi_plaits_dsp::oscillator::sine_oscillator::SineOscillator; 7 | 8 | use mi_plaits_dsp::fx::*; 9 | use mi_plaits_dsp::SAMPLE_RATE; 10 | 11 | const BLOCK_SIZE: usize = 24; 12 | 13 | #[test] 14 | fn diffuser() { 15 | let amount = 1.0; 16 | let rt = 0.5; 17 | let duration = 1.0; 18 | 19 | let mut fx = diffuser::Diffuser::new(); 20 | let mut in_out = [0.0; BLOCK_SIZE]; 21 | let mut wav_data = Vec::new(); 22 | fx.init(); 23 | 24 | let blocks = (duration * SAMPLE_RATE / (BLOCK_SIZE as f32)) as usize; 25 | 26 | for n in 0..blocks { 27 | in_out.fill(0.0); 28 | if n == 0 { 29 | in_out[0] = 1.0; 30 | } 31 | fx.process(amount, rt, &mut in_out); 32 | wav_data.extend_from_slice(&in_out); 33 | } 34 | 35 | wav_writer::write("fx/diffuser.wav", &wav_data).ok(); 36 | } 37 | 38 | #[test] 39 | fn ensemble() { 40 | let frequency = 220.0; 41 | let duration = 2.0; 42 | 43 | let mut osc = SineOscillator::new(); 44 | let mut fx = ensemble::Ensemble::new(); 45 | let mut left = [0.0; BLOCK_SIZE]; 46 | let mut wav_data_left = Vec::new(); 47 | let mut wav_data_right = Vec::new(); 48 | osc.init(); 49 | fx.init(); 50 | fx.set_amount(0.5); 51 | fx.set_depth(0.5); 52 | 53 | let blocks = (duration * SAMPLE_RATE / (BLOCK_SIZE as f32)) as usize; 54 | let f = frequency / SAMPLE_RATE; 55 | 56 | for _ in 0..blocks { 57 | osc.render(f, &mut left); 58 | let mut right = left.clone(); 59 | fx.process(&mut left, &mut right); 60 | wav_data_left.extend_from_slice(&left); 61 | wav_data_right.extend_from_slice(&right); 62 | } 63 | 64 | wav_writer::write("fx/ensemble_left.wav", &wav_data_left).ok(); 65 | wav_writer::write("fx/ensemble_right.wav", &wav_data_right).ok(); 66 | } 67 | 68 | #[test] 69 | fn sample_rate_reducer() { 70 | let frequency = 110.0; 71 | let duration = 2.0; 72 | 73 | let mut osc = SineOscillator::new(); 74 | let mut fx = sample_rate_reducer::SampleRateReducer::new(); 75 | let mut in_out = [0.0; BLOCK_SIZE]; 76 | let mut wav_data = Vec::new(); 77 | osc.init(); 78 | fx.init(); 79 | 80 | let blocks = (duration * SAMPLE_RATE / (BLOCK_SIZE as f32)) as usize; 81 | let f = frequency / SAMPLE_RATE; 82 | 83 | for n in 0..blocks { 84 | osc.render(f, &mut in_out); 85 | let fx_f = modulation::ramp_up(n, blocks) * 0.1; 86 | fx.process(fx_f, &mut in_out, true); 87 | wav_data.extend_from_slice(&in_out); 88 | } 89 | 90 | wav_writer::write("fx/sample_rate_reducer.wav", &wav_data).ok(); 91 | } 92 | 93 | #[test] 94 | fn overdrive() { 95 | let frequency = 110.0; 96 | let duration = 2.0; 97 | 98 | let mut osc = SineOscillator::new(); 99 | let mut fx = overdrive::Overdrive::new(); 100 | let mut in_out = [0.0; BLOCK_SIZE]; 101 | let mut wav_data = Vec::new(); 102 | osc.init(); 103 | fx.init(); 104 | 105 | let blocks = (duration * SAMPLE_RATE / (BLOCK_SIZE as f32)) as usize; 106 | let f = frequency / SAMPLE_RATE; 107 | 108 | for n in 0..blocks { 109 | osc.render(f, &mut in_out); 110 | let drive = modulation::ramp_up(n, blocks); 111 | fx.process(drive, &mut in_out); 112 | wav_data.extend_from_slice(&in_out); 113 | } 114 | 115 | wav_writer::write("fx/overdrive.wav", &wav_data).ok(); 116 | } 117 | -------------------------------------------------------------------------------- /tests/modulation.rs: -------------------------------------------------------------------------------- 1 | //! Modulation sources 2 | 3 | /// Returns a triangle wave in range -1.0..1.0 4 | pub fn triangle(block_no: usize, block_count: usize, periods: f32) -> f32 { 5 | let mut phase = block_no as f32 / block_count as f32 * periods; 6 | 7 | while phase > 1.0 { 8 | phase -= 1.0 9 | } 10 | 11 | let waveform = if phase < 0.25 { 12 | phase * 4.0 13 | } else if phase < 0.5 { 14 | (0.5 - phase) * 4.0 15 | } else if phase < 0.75 { 16 | -(phase - 0.5) * 4.0 17 | } else { 18 | -(1.0 - phase) * 4.0 19 | }; 20 | 21 | waveform 22 | } 23 | 24 | /// Returns a ramp in range 0.0..1.0 25 | pub fn ramp_up(block_no: usize, block_count: usize) -> f32 { 26 | let phase = block_no as f32 / block_count as f32; 27 | 28 | phase 29 | } 30 | -------------------------------------------------------------------------------- /tests/noise.rs: -------------------------------------------------------------------------------- 1 | //! Tests for the noise generators 2 | 3 | mod wav_writer; 4 | 5 | use mi_plaits_dsp::noise::*; 6 | use mi_plaits_dsp::SAMPLE_RATE; 7 | 8 | const BLOCK_SIZE: usize = 24; 9 | 10 | #[test] 11 | fn clocked_noise() { 12 | let frequency = 10.0; 13 | let duration = 1.0; 14 | 15 | let mut noise = clocked_noise::ClockedNoise::new(); 16 | let mut out = [0.0; BLOCK_SIZE]; 17 | let mut wav_data = Vec::new(); 18 | noise.init(); 19 | 20 | let blocks = (duration * SAMPLE_RATE / (BLOCK_SIZE as f32)) as usize; 21 | let f = frequency / SAMPLE_RATE; 22 | 23 | for _ in 0..blocks { 24 | noise.render(false, f, &mut out); 25 | wav_data.extend_from_slice(&out); 26 | } 27 | 28 | wav_writer::write("noise/clocked.wav", &wav_data).ok(); 29 | } 30 | 31 | #[test] 32 | fn dust() { 33 | let frequency = 20.0; 34 | let duration = 1.0; 35 | 36 | let mut wav_data = Vec::new(); 37 | 38 | let samples = (duration * SAMPLE_RATE) as usize; 39 | let f = frequency / SAMPLE_RATE; 40 | 41 | for _ in 0..samples { 42 | let out = dust::dust(f); 43 | wav_data.push(out); 44 | } 45 | 46 | wav_writer::write("noise/dust.wav", &wav_data).ok(); 47 | } 48 | 49 | #[test] 50 | fn particle() { 51 | let frequency = 50.0; 52 | let density = 0.1; 53 | let gain = 1.0; 54 | let spread = 0.5; 55 | let q = 0.9; 56 | let duration = 1.0; 57 | 58 | let mut noise = particle::Particle::new(); 59 | let mut out = [0.0; BLOCK_SIZE]; 60 | let mut aux = [0.0; BLOCK_SIZE]; 61 | let mut wav_data = Vec::new(); 62 | let mut wav_data_aux = Vec::new(); 63 | noise.init(); 64 | 65 | let blocks = (duration * SAMPLE_RATE / (BLOCK_SIZE as f32)) as usize; 66 | let f = frequency / SAMPLE_RATE; 67 | 68 | for n in 0..blocks { 69 | out.fill(0.0); 70 | aux.fill(0.0); 71 | let sync = n % (blocks / 5) == 0; 72 | noise.render(sync, density, gain, f, spread, q, &mut out, &mut aux); 73 | wav_data.extend_from_slice(&out); 74 | wav_data_aux.extend_from_slice(&aux); 75 | } 76 | 77 | wav_writer::write("noise/particle.wav", &wav_data).ok(); 78 | wav_writer::write("noise/particle_aux.wav", &wav_data_aux).ok(); 79 | } 80 | 81 | #[test] 82 | fn smooth_random_generator() { 83 | let frequency = 10.0; 84 | let duration = 1.0; 85 | 86 | let mut osc = smooth_random_generator::SmoothRandomGenerator::new(); 87 | let mut wav_data = Vec::new(); 88 | osc.init(); 89 | 90 | let samples = (duration * SAMPLE_RATE) as usize; 91 | let f = frequency / SAMPLE_RATE; 92 | 93 | for _ in 0..samples { 94 | let out = osc.render(f); 95 | wav_data.push(out); 96 | } 97 | 98 | wav_writer::write("noise/smooth_random_generator.wav", &wav_data).ok(); 99 | } 100 | -------------------------------------------------------------------------------- /tests/voice.rs: -------------------------------------------------------------------------------- 1 | //! Tests for the voice module. 2 | 3 | mod wav_writer; 4 | 5 | use mi_plaits_dsp::voice::{Modulations, Patch, Voice, NUM_ENGINES}; 6 | use mi_plaits_dsp::SAMPLE_RATE; 7 | 8 | const BLOCK_SIZE: usize = 24; 9 | 10 | #[test] 11 | fn all_engines() { 12 | let mut voice = Voice::new(BLOCK_SIZE); 13 | let mut out = [0.0; BLOCK_SIZE]; 14 | let mut aux = [0.0; BLOCK_SIZE]; 15 | let mut wav_data = Vec::new(); 16 | let mut wav_data_aux = Vec::new(); 17 | 18 | voice.init(); 19 | 20 | let duration = 1.0; 21 | let blocks = (duration * SAMPLE_RATE / (BLOCK_SIZE as f32)) as usize; 22 | 23 | let mut patch = Patch { 24 | note: 48.0, 25 | harmonics: 0.5, 26 | timbre: 0.5, 27 | morph: 0.5, 28 | frequency_modulation_amount: 0.0, 29 | timbre_modulation_amount: 0.5, 30 | morph_modulation_amount: 0.5, 31 | engine: 0, 32 | decay: 0.5, 33 | lpg_colour: 0.5, 34 | }; 35 | 36 | let modulations = Modulations { 37 | engine: 0.0, 38 | note: 0.0, 39 | frequency: 0.0, 40 | harmonics: 0.0, 41 | timbre: 0.0, 42 | morph: 0.0, 43 | trigger: 0.0, 44 | level: 0.0, 45 | frequency_patched: false, 46 | timbre_patched: false, 47 | morph_patched: false, 48 | trigger_patched: false, 49 | level_patched: false, 50 | }; 51 | 52 | for engine in 0..NUM_ENGINES { 53 | patch.engine = engine; 54 | 55 | for _ in 0..blocks { 56 | voice.render(&patch, &modulations, &mut out, &mut aux); 57 | wav_data.extend_from_slice(&out); 58 | wav_data_aux.extend_from_slice(&aux); 59 | } 60 | } 61 | 62 | wav_writer::write("voice/all_engines.wav", &wav_data).ok(); 63 | wav_writer::write("voice/all_engines_aux.wav", &wav_data_aux).ok(); 64 | } 65 | 66 | #[test] 67 | fn all_engines_trigger() { 68 | let mut voice = Voice::new(BLOCK_SIZE); 69 | let mut out = [0.0; BLOCK_SIZE]; 70 | let mut aux = [0.0; BLOCK_SIZE]; 71 | let mut wav_data = Vec::new(); 72 | let mut wav_data_aux = Vec::new(); 73 | 74 | voice.init(); 75 | 76 | let duration = 1.0; 77 | let blocks = (duration * SAMPLE_RATE / (BLOCK_SIZE as f32)) as usize; 78 | 79 | let mut patch = Patch { 80 | note: 48.0, 81 | harmonics: 0.5, 82 | timbre: 0.5, 83 | morph: 0.5, 84 | frequency_modulation_amount: 0.0, 85 | timbre_modulation_amount: 0.5, 86 | morph_modulation_amount: 0.5, 87 | engine: 0, 88 | decay: 0.5, 89 | lpg_colour: 0.5, 90 | }; 91 | 92 | let mut modulations = Modulations { 93 | engine: 0.0, 94 | note: 0.0, 95 | frequency: 0.0, 96 | harmonics: 0.0, 97 | timbre: 0.0, 98 | morph: 0.0, 99 | trigger: 0.0, 100 | level: 0.0, 101 | frequency_patched: false, 102 | timbre_patched: false, 103 | morph_patched: false, 104 | trigger_patched: true, 105 | level_patched: false, 106 | }; 107 | 108 | for engine in 0..NUM_ENGINES { 109 | patch.engine = engine; 110 | 111 | for n in 0..blocks { 112 | modulations.trigger = if n == 0 { 1.0 } else { 0.0 }; 113 | voice.render(&patch, &modulations, &mut out, &mut aux); 114 | wav_data.extend_from_slice(&out); 115 | wav_data_aux.extend_from_slice(&aux); 116 | } 117 | } 118 | 119 | wav_writer::write("voice/all_engines_trigger.wav", &wav_data).ok(); 120 | wav_writer::write("voice/all_engines_trigger_aux.wav", &wav_data_aux).ok(); 121 | } 122 | -------------------------------------------------------------------------------- /tests/wav_writer.rs: -------------------------------------------------------------------------------- 1 | //! Writer for WAV files 2 | 3 | use std::path::Path; 4 | 5 | use hound::*; 6 | 7 | use mi_plaits_dsp::SAMPLE_RATE; 8 | 9 | /// Writes sample data as WAV file in 32-bit float format. 10 | pub fn write( 11 | filename: impl AsRef + core::fmt::Display, 12 | samples: &[f32], 13 | ) -> std::io::Result<()> { 14 | let path = format!("out/{filename}"); 15 | let path = Path::new(path.as_str()); 16 | 17 | // Create parent directories to the path if they don't exist. 18 | let parent = path.parent().unwrap(); 19 | std::fs::create_dir_all(parent).ok(); 20 | 21 | let spec = WavSpec { 22 | channels: 2, 23 | sample_rate: SAMPLE_RATE as u32, 24 | bits_per_sample: 32, 25 | sample_format: SampleFormat::Float, 26 | }; 27 | let mut writer = WavWriter::create(path, spec).unwrap(); 28 | 29 | for sample in samples { 30 | writer.write_sample(*sample).unwrap(); 31 | writer.write_sample(*sample).unwrap(); 32 | } 33 | 34 | Ok(()) 35 | } 36 | --------------------------------------------------------------------------------