diff --git a/src/dsp/mod.rs b/src/dsp/mod.rs index 936e189..04e3e4a 100644 --- a/src/dsp/mod.rs +++ b/src/dsp/mod.rs @@ -507,7 +507,7 @@ mod node_delay; #[allow(non_upper_case_globals)] mod node_fbwr_fbrd; #[allow(non_upper_case_globals)] -mod node_formant; +mod node_formfm; #[allow(non_upper_case_globals)] mod node_map; #[allow(non_upper_case_globals)] @@ -595,7 +595,7 @@ use node_cqnt::CQnt; use node_delay::Delay; use node_fbwr_fbrd::FbRd; use node_fbwr_fbrd::FbWr; -use node_formant::Formant; +use node_formfm::FormFM; use node_map::Map; use node_mix3::Mix3; use node_mux9::Mux9; @@ -1499,11 +1499,11 @@ macro_rules! node_list { (1 offs n_id d_id r_s f_def stp_d -1.0, 1.0, 0.0) {2 0 mode setting(0) mode fa_noise_mode 0 1} [0 sig], - formant => Formant UIType::Generic UICategory::Osc - (0 freq n_pit d_pit r_fq f_freq stp_d -1.0, 1.0, 440.0) - (1 form n_pit d_pit r_fq f_freq stp_d -1.0, 1.0, 440.0) - (2 atk n_pit d_pit r_fq f_freq stp_d -1.0, 1.0, 44.0) - (3 dcy n_pit d_pit r_fq f_freq stp_d -1.0, 1.0, 44.0) + formfm => FormFM UIType::Generic UICategory::Osc + (0 freq n_pit d_pit r_fq f_freq stp_d -1.0, 0.5647131, 440.0) + (1 form n_pit d_pit r_fq f_freq stp_d -1.0, 0.5647131, 440.0) + (2 side n_id d_id r_id f_def stp_d 0.0, 1.0, 0.2) + (3 peak n_id d_id r_id f_def stp_d 0.0, 1.0, 0.4) [0 sig], sfilter => SFilter UIType::Generic UICategory::Signal (0 inp n_id d_id r_id f_def stp_d -1.0, 1.0, 0.0) diff --git a/src/dsp/node_formant.rs b/src/dsp/node_formfm.rs similarity index 50% rename from src/dsp/node_formant.rs rename to src/dsp/node_formfm.rs index c9a623c..702a213 100644 --- a/src/dsp/node_formant.rs +++ b/src/dsp/node_formfm.rs @@ -3,41 +3,50 @@ use crate::nodes::{NodeAudioContext, NodeExecContext}; /// A simple amplifier #[derive(Debug, Clone)] -pub struct Formant { +pub struct FormFM { inv_sample_rate: f32, phase: f32, } -impl Formant { +impl FormFM { pub fn new(_nid: &NodeId) -> Self { Self { inv_sample_rate: 1.0 / 44100.0, phase: 0.0 } } pub const freq: &'static str = "Formant freq\nBase frequency to oscilate at\n"; pub const form: &'static str = "Formant form\nFrequency of the formant\nThis affects how much lower or higher tones the sound has."; - pub const atk: &'static str = - "Formant atk\nFormant attack bandwidth, controls the general bandwidth"; - pub const dcy: &'static str = - "Formant dcy\nFormant decay bandwidth, controls the peak bandwidth"; + pub const side: &'static str = + "Formant side\nWhich side the peak of the wave is. Values more towards 0.0 or 1.0 make the base frequency more pronounced"; + pub const peak: &'static str = + "Formant peak\nHow high the peak amplitude is. Lower values make the effect more pronounced"; pub const sig: &'static str = "Formant sig\nGenerated formant signal"; - pub const DESC: &'static str = r#"Direct formant synthesizer + pub const DESC: &'static str = r#"Formant oscillator -This generates a single formant from a given frequency, formant frequency, as well as attack and decay frequencies. -The attack and decay frequencies both control the bandwidth of the formant, decay the peak of the bandwidth, attack peak. +Simple formant oscillator that generates a formant like sound. +Loosely based on the ModFM synthesis method. "#; - pub const HELP: &'static str = r#"Formant - Direct formant synthesized -This is a formant synthesizer that directly generates the audio of a single formant. + pub const HELP: &'static str = r#"formfm - Direct formant synthesizer -This can be seen as passing a saw wave with frequency `freq` into a bandpass filter with the cutoff at `form` +This is a formant synthesizer that directly generates +the audio of a single formant. + +This can be seen as passing a saw wave with frequency `freq` +into a bandpass filter with the cutoff at `form` `freq` controls the base frequency of the formant. -`form` controls the formant frequency. Lower values give more bass to the sound, and higher values give the high frequencies more sound. -If `form` is lower than `freq`, the overal loudness will go down, however it's guaranteed to never exceed the [-1,1] range. -`atk` and `dcy` both control the bandwidth/resonance of the formant. The further apart they are in value, the higher the bandwidth/lower the resonance. -If these are set to a low value, the sine wave used for the formant effect becomes very audible. +`form` controls the formant frequency. Lower values give more bass to the sound, +and higher values give the high frequencies more sound. + +`side` controls where the peak of the carrier wave is, +and in turn controls the bandwidth of the effect. The more towards 0.0 or 1.0, +the more the formant is audible. + +`peak` controls how high the peak of the carrier wave is. +This also controls the bandwidth of the effect, where lower means a higher +bandwidth, and thus more audible formant. "#; } -impl DspNode for Formant { +impl DspNode for FormFM { fn outputs() -> usize { 1 } @@ -63,39 +72,26 @@ impl DspNode for Formant { ) { use crate::dsp::{denorm, inp, out}; - let base_freq = inp::Formant::freq(inputs); - let formant_freq = inp::Formant::form(inputs); - let attack_freq = inp::Formant::atk(inputs); - let decay_freq = inp::Formant::dcy(inputs); - let out = out::Formant::sig(outputs); + let base_freq = inp::FormFM::freq(inputs); + let formant_freq = inp::FormFM::form(inputs); + let side_val = inp::FormFM::side(inputs); + let peak_val = inp::FormFM::peak(inputs); + let out = out::FormFM::sig(outputs); for frame in 0..ctx.nframes() { // get the inputs - let base_freq = denorm::Formant::freq(base_freq, frame); - let formant_freq = denorm::Formant::form(formant_freq, frame); - let attack_freq = denorm::Formant::atk(attack_freq, frame); - let decay_freq = denorm::Formant::dcy(decay_freq, frame); - - // where the two decays meet - // clamp to avoid division by 0 - let carrier_center = - (attack_freq / (attack_freq + decay_freq)).max(1e-6).min(1.0 - 1e-6); - - // where they meet in amplitude - let carrier_lowest_amplitude = - if carrier_center * decay_freq > base_freq * 2.0 || base_freq == 0.0 { - 0.0 - } else { - (-(std::f32::consts::PI * carrier_center * decay_freq) / base_freq).exp() - }; + let base_freq = denorm::FormFM::freq(base_freq, frame); + let formant_freq = denorm::FormFM::form(formant_freq, frame); + let side_val = denorm::FormFM::side(side_val, frame).min(1.0 - 1e-6).max(1e-6); + let peak_val = denorm::FormFM::peak(peak_val, frame); // make a triangle wave, with the peak at carrier center let carrier_base = - (self.phase / carrier_center).min((1.0 - self.phase) / (1.0 - carrier_center)); + (self.phase / side_val).min((1.0 - self.phase) / (1.0 - side_val)); // smoothstep let carrier = 1.0 - - ((1.0 - carrier_lowest_amplitude) + - ((1.0 - peak_val) * (carrier_base * carrier_base * (3.0 - 2.0 * carrier_base))); // multiple of the frequency the modulators are at diff --git a/tests/node_formant.rs b/tests/node_formfm.rs similarity index 65% rename from tests/node_formant.rs rename to tests/node_formfm.rs index 9bbcdb8..f54c6f7 100644 --- a/tests/node_formant.rs +++ b/tests/node_formfm.rs @@ -8,23 +8,23 @@ fn check_normalized_if_freq_lower_than_formant_freq() { let mut matrix = Matrix::new(node_conf, 3, 3); let mut chain = MatrixCellChain::new(CellDir::B); - chain.node_out("formant", "sig").node_inp("out", "ch1").place(&mut matrix, 0, 0).unwrap(); + chain.node_out("formfm", "sig").node_inp("out", "ch1").place(&mut matrix, 0, 0).unwrap(); matrix.sync().unwrap(); - let formant = NodeId::Formant(0); + let formant = NodeId::FormFM(0); // params let freq_p = formant.inp_param("freq").unwrap(); let form_p = formant.inp_param("form").unwrap(); - let atk_p = formant.inp_param("atk").unwrap(); - let dcy_p = formant.inp_param("dcy").unwrap(); + let side_p = formant.inp_param("side").unwrap(); + let peak_p = formant.inp_param("peak").unwrap(); // set params to reasonable values matrix.set_param(freq_p, SAtom::param(-0.2)); matrix.set_param(form_p, SAtom::param(0.0)); - matrix.set_param(atk_p, SAtom::param(0.2)); - matrix.set_param(dcy_p, SAtom::param(-0.2)); + matrix.set_param(side_p, SAtom::param(0.2)); + matrix.set_param(peak_p, SAtom::param(0.4)); // run let res = run_for_ms(&mut node_exec, 100.0); @@ -42,23 +42,23 @@ fn check_no_dc_bias_at_formant_freq_lower_than_freq() { let mut matrix = Matrix::new(node_conf, 3, 3); let mut chain = MatrixCellChain::new(CellDir::B); - chain.node_out("formant", "sig").node_inp("out", "ch1").place(&mut matrix, 0, 0).unwrap(); + chain.node_out("formfm", "sig").node_inp("out", "ch1").place(&mut matrix, 0, 0).unwrap(); matrix.sync().unwrap(); - let formant = NodeId::Formant(0); + let formant = NodeId::FormFM(0); // params let freq_p = formant.inp_param("freq").unwrap(); let form_p = formant.inp_param("form").unwrap(); - let atk_p = formant.inp_param("atk").unwrap(); - let dcy_p = formant.inp_param("dcy").unwrap(); + let side_p = formant.inp_param("side").unwrap(); + let peak_p = formant.inp_param("peak").unwrap(); // set params to reasonable values matrix.set_param(freq_p, SAtom::param(0.0)); matrix.set_param(form_p, SAtom::param(-0.2)); - matrix.set_param(atk_p, SAtom::param(0.2)); - matrix.set_param(dcy_p, SAtom::param(-0.2)); + matrix.set_param(side_p, SAtom::param(0.2)); + matrix.set_param(peak_p, SAtom::param(0.4)); // run let res = run_for_ms(&mut node_exec, 100.0); @@ -77,24 +77,24 @@ fn check_no_nan() { let mut matrix = Matrix::new(node_conf, 3, 3); let mut chain = MatrixCellChain::new(CellDir::B); - chain.node_out("formant", "sig").node_inp("out", "ch1").place(&mut matrix, 0, 0).unwrap(); + chain.node_out("formfm", "sig").node_inp("out", "ch1").place(&mut matrix, 0, 0).unwrap(); matrix.sync().unwrap(); - let formant = NodeId::Formant(0); + let formant = NodeId::FormFM(0); // params let freq_p = formant.inp_param("freq").unwrap(); let form_p = formant.inp_param("form").unwrap(); - let atk_p = formant.inp_param("atk").unwrap(); - let dcy_p = formant.inp_param("dcy").unwrap(); + let side_p = formant.inp_param("side").unwrap(); + let peak_p = formant.inp_param("peak").unwrap(); // set params to non-reasonable values here // base freq 0 matrix.set_param(freq_p, SAtom::param(-1.0)); matrix.set_param(form_p, SAtom::param(0.0)); - matrix.set_param(atk_p, SAtom::param(0.2)); - matrix.set_param(dcy_p, SAtom::param(-0.2)); + matrix.set_param(side_p, SAtom::param(0.2)); + matrix.set_param(peak_p, SAtom::param(0.4)); // run let res = run_for_ms(&mut node_exec, 100.0); @@ -103,11 +103,11 @@ fn check_no_nan() { assert!(res.0.iter().all(|x| !x.is_nan())); // set params to non-reasonable values here - // base freq attack freq 0 + // side to 0 matrix.set_param(freq_p, SAtom::param(-0.2)); matrix.set_param(form_p, SAtom::param(0.0)); - matrix.set_param(atk_p, SAtom::param(-1.0)); - matrix.set_param(dcy_p, SAtom::param(-0.2)); + matrix.set_param(side_p, SAtom::param(-1.0)); + matrix.set_param(peak_p, SAtom::param(0.4)); // run let res = run_for_ms(&mut node_exec, 100.0); @@ -116,11 +116,11 @@ fn check_no_nan() { assert!(res.0.iter().all(|x| !x.is_nan())); // set params to non-reasonable values here - // decay freq freq 0 + // side to 1 matrix.set_param(freq_p, SAtom::param(-0.2)); matrix.set_param(form_p, SAtom::param(0.0)); - matrix.set_param(atk_p, SAtom::param(0.2)); - matrix.set_param(dcy_p, SAtom::param(-1.0)); + matrix.set_param(peak_p, SAtom::param(1.0)); + matrix.set_param(side_p, SAtom::param(0.4)); // run let res = run_for_ms(&mut node_exec, 100.0); @@ -136,25 +136,25 @@ fn check_formant_freq() { let mut matrix = Matrix::new(node_conf, 3, 3); let mut chain = MatrixCellChain::new(CellDir::B); - chain.node_out("formant", "sig").node_inp("out", "ch1").place(&mut matrix, 0, 0).unwrap(); + chain.node_out("formfm", "sig").node_inp("out", "ch1").place(&mut matrix, 0, 0).unwrap(); matrix.sync().unwrap(); - let formant = NodeId::Formant(0); + let formant = NodeId::FormFM(0); // params let freq_p = formant.inp_param("freq").unwrap(); let form_p = formant.inp_param("form").unwrap(); - let atk_p = formant.inp_param("atk").unwrap(); - let dcy_p = formant.inp_param("dcy").unwrap(); + let side_p = formant.inp_param("side").unwrap(); + let peak_p = formant.inp_param("peak").unwrap(); // set params to reasonable values matrix.set_param(freq_p, SAtom::param(-0.2)); matrix.set_param(form_p, SAtom::param(0.0)); - matrix.set_param(atk_p, SAtom::param(0.2)); - matrix.set_param(dcy_p, SAtom::param(-0.2)); + matrix.set_param(side_p, SAtom::param(0.2)); + matrix.set_param(peak_p, SAtom::param(0.4)); // run - let fft = run_and_get_avg_fft4096_now(&mut node_exec, 180); - assert_eq!(fft, vec![(334, 191), (431, 331), (441, 546), (452, 222), (549, 209)]); + let fft = run_and_get_avg_fft4096_now(&mut node_exec, 100); + assert_eq!(fft, vec![(323, 106), (334, 131), (431, 430), (441, 708), (452, 288), (549, 140)]); }