renamed and improved, so no more atk/dcy but direct controls instead

This commit is contained in:
Dimas Leenman 2022-08-08 13:53:57 +02:00
parent c4a9b56099
commit e1799f6d45
3 changed files with 76 additions and 80 deletions

View file

@ -507,7 +507,7 @@ mod node_delay;
#[allow(non_upper_case_globals)] #[allow(non_upper_case_globals)]
mod node_fbwr_fbrd; mod node_fbwr_fbrd;
#[allow(non_upper_case_globals)] #[allow(non_upper_case_globals)]
mod node_formant; mod node_formfm;
#[allow(non_upper_case_globals)] #[allow(non_upper_case_globals)]
mod node_map; mod node_map;
#[allow(non_upper_case_globals)] #[allow(non_upper_case_globals)]
@ -595,7 +595,7 @@ use node_cqnt::CQnt;
use node_delay::Delay; use node_delay::Delay;
use node_fbwr_fbrd::FbRd; use node_fbwr_fbrd::FbRd;
use node_fbwr_fbrd::FbWr; use node_fbwr_fbrd::FbWr;
use node_formant::Formant; use node_formfm::FormFM;
use node_map::Map; use node_map::Map;
use node_mix3::Mix3; use node_mix3::Mix3;
use node_mux9::Mux9; 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) (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} {2 0 mode setting(0) mode fa_noise_mode 0 1}
[0 sig], [0 sig],
formant => Formant UIType::Generic UICategory::Osc formfm => FormFM UIType::Generic UICategory::Osc
(0 freq n_pit d_pit r_fq f_freq stp_d -1.0, 1.0, 440.0) (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, 1.0, 440.0) (1 form n_pit d_pit r_fq f_freq stp_d -1.0, 0.5647131, 440.0)
(2 atk n_pit d_pit r_fq f_freq stp_d -1.0, 1.0, 44.0) (2 side n_id d_id r_id f_def stp_d 0.0, 1.0, 0.2)
(3 dcy n_pit d_pit r_fq f_freq stp_d -1.0, 1.0, 44.0) (3 peak n_id d_id r_id f_def stp_d 0.0, 1.0, 0.4)
[0 sig], [0 sig],
sfilter => SFilter UIType::Generic UICategory::Signal sfilter => SFilter UIType::Generic UICategory::Signal
(0 inp n_id d_id r_id f_def stp_d -1.0, 1.0, 0.0) (0 inp n_id d_id r_id f_def stp_d -1.0, 1.0, 0.0)

View file

@ -3,41 +3,50 @@ use crate::nodes::{NodeAudioContext, NodeExecContext};
/// A simple amplifier /// A simple amplifier
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Formant { pub struct FormFM {
inv_sample_rate: f32, inv_sample_rate: f32,
phase: f32, phase: f32,
} }
impl Formant { impl FormFM {
pub fn new(_nid: &NodeId) -> Self { pub fn new(_nid: &NodeId) -> Self {
Self { inv_sample_rate: 1.0 / 44100.0, phase: 0.0 } 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 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 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 = pub const side: &'static str =
"Formant atk\nFormant attack bandwidth, controls the general bandwidth"; "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 dcy: &'static str = pub const peak: &'static str =
"Formant dcy\nFormant decay bandwidth, controls the peak bandwidth"; "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 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. Simple formant oscillator that generates a formant like sound.
The attack and decay frequencies both control the bandwidth of the formant, decay the peak of the bandwidth, attack peak. Loosely based on the ModFM synthesis method.
"#; "#;
pub const HELP: &'static str = r#"Formant - Direct formant synthesized pub const HELP: &'static str = r#"formfm - Direct formant synthesizer
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` 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. `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. `form` controls the formant frequency. Lower values give more bass to the sound,
If `form` is lower than `freq`, the overal loudness will go down, however it's guaranteed to never exceed the [-1,1] range. and higher values give the high frequencies more sound.
`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. `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 { fn outputs() -> usize {
1 1
} }
@ -63,39 +72,26 @@ impl DspNode for Formant {
) { ) {
use crate::dsp::{denorm, inp, out}; use crate::dsp::{denorm, inp, out};
let base_freq = inp::Formant::freq(inputs); let base_freq = inp::FormFM::freq(inputs);
let formant_freq = inp::Formant::form(inputs); let formant_freq = inp::FormFM::form(inputs);
let attack_freq = inp::Formant::atk(inputs); let side_val = inp::FormFM::side(inputs);
let decay_freq = inp::Formant::dcy(inputs); let peak_val = inp::FormFM::peak(inputs);
let out = out::Formant::sig(outputs); let out = out::FormFM::sig(outputs);
for frame in 0..ctx.nframes() { for frame in 0..ctx.nframes() {
// get the inputs // get the inputs
let base_freq = denorm::Formant::freq(base_freq, frame); let base_freq = denorm::FormFM::freq(base_freq, frame);
let formant_freq = denorm::Formant::form(formant_freq, frame); let formant_freq = denorm::FormFM::form(formant_freq, frame);
let attack_freq = denorm::Formant::atk(attack_freq, frame); let side_val = denorm::FormFM::side(side_val, frame).min(1.0 - 1e-6).max(1e-6);
let decay_freq = denorm::Formant::dcy(decay_freq, frame); let peak_val = denorm::FormFM::peak(peak_val, 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()
};
// make a triangle wave, with the peak at carrier center // make a triangle wave, with the peak at carrier center
let carrier_base = 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 // smoothstep
let carrier = 1.0 let carrier = 1.0
- ((1.0 - carrier_lowest_amplitude) - ((1.0 - peak_val)
* (carrier_base * carrier_base * (3.0 - 2.0 * carrier_base))); * (carrier_base * carrier_base * (3.0 - 2.0 * carrier_base)));
// multiple of the frequency the modulators are at // multiple of the frequency the modulators are at

View file

@ -8,23 +8,23 @@ fn check_normalized_if_freq_lower_than_formant_freq() {
let mut matrix = Matrix::new(node_conf, 3, 3); let mut matrix = Matrix::new(node_conf, 3, 3);
let mut chain = MatrixCellChain::new(CellDir::B); 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(); matrix.sync().unwrap();
let formant = NodeId::Formant(0); let formant = NodeId::FormFM(0);
// params // params
let freq_p = formant.inp_param("freq").unwrap(); let freq_p = formant.inp_param("freq").unwrap();
let form_p = formant.inp_param("form").unwrap(); let form_p = formant.inp_param("form").unwrap();
let atk_p = formant.inp_param("atk").unwrap(); let side_p = formant.inp_param("side").unwrap();
let dcy_p = formant.inp_param("dcy").unwrap(); let peak_p = formant.inp_param("peak").unwrap();
// set params to reasonable values // set params to reasonable values
matrix.set_param(freq_p, SAtom::param(-0.2)); matrix.set_param(freq_p, SAtom::param(-0.2));
matrix.set_param(form_p, SAtom::param(0.0)); matrix.set_param(form_p, SAtom::param(0.0));
matrix.set_param(atk_p, SAtom::param(0.2)); matrix.set_param(side_p, SAtom::param(0.2));
matrix.set_param(dcy_p, SAtom::param(-0.2)); matrix.set_param(peak_p, SAtom::param(0.4));
// run // run
let res = run_for_ms(&mut node_exec, 100.0); 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 matrix = Matrix::new(node_conf, 3, 3);
let mut chain = MatrixCellChain::new(CellDir::B); 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(); matrix.sync().unwrap();
let formant = NodeId::Formant(0); let formant = NodeId::FormFM(0);
// params // params
let freq_p = formant.inp_param("freq").unwrap(); let freq_p = formant.inp_param("freq").unwrap();
let form_p = formant.inp_param("form").unwrap(); let form_p = formant.inp_param("form").unwrap();
let atk_p = formant.inp_param("atk").unwrap(); let side_p = formant.inp_param("side").unwrap();
let dcy_p = formant.inp_param("dcy").unwrap(); let peak_p = formant.inp_param("peak").unwrap();
// set params to reasonable values // set params to reasonable values
matrix.set_param(freq_p, SAtom::param(0.0)); matrix.set_param(freq_p, SAtom::param(0.0));
matrix.set_param(form_p, SAtom::param(-0.2)); matrix.set_param(form_p, SAtom::param(-0.2));
matrix.set_param(atk_p, SAtom::param(0.2)); matrix.set_param(side_p, SAtom::param(0.2));
matrix.set_param(dcy_p, SAtom::param(-0.2)); matrix.set_param(peak_p, SAtom::param(0.4));
// run // run
let res = run_for_ms(&mut node_exec, 100.0); 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 matrix = Matrix::new(node_conf, 3, 3);
let mut chain = MatrixCellChain::new(CellDir::B); 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(); matrix.sync().unwrap();
let formant = NodeId::Formant(0); let formant = NodeId::FormFM(0);
// params // params
let freq_p = formant.inp_param("freq").unwrap(); let freq_p = formant.inp_param("freq").unwrap();
let form_p = formant.inp_param("form").unwrap(); let form_p = formant.inp_param("form").unwrap();
let atk_p = formant.inp_param("atk").unwrap(); let side_p = formant.inp_param("side").unwrap();
let dcy_p = formant.inp_param("dcy").unwrap(); let peak_p = formant.inp_param("peak").unwrap();
// set params to non-reasonable values here // set params to non-reasonable values here
// base freq 0 // base freq 0
matrix.set_param(freq_p, SAtom::param(-1.0)); matrix.set_param(freq_p, SAtom::param(-1.0));
matrix.set_param(form_p, SAtom::param(0.0)); matrix.set_param(form_p, SAtom::param(0.0));
matrix.set_param(atk_p, SAtom::param(0.2)); matrix.set_param(side_p, SAtom::param(0.2));
matrix.set_param(dcy_p, SAtom::param(-0.2)); matrix.set_param(peak_p, SAtom::param(0.4));
// run // run
let res = run_for_ms(&mut node_exec, 100.0); 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())); assert!(res.0.iter().all(|x| !x.is_nan()));
// set params to non-reasonable values here // 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(freq_p, SAtom::param(-0.2));
matrix.set_param(form_p, SAtom::param(0.0)); matrix.set_param(form_p, SAtom::param(0.0));
matrix.set_param(atk_p, SAtom::param(-1.0)); matrix.set_param(side_p, SAtom::param(-1.0));
matrix.set_param(dcy_p, SAtom::param(-0.2)); matrix.set_param(peak_p, SAtom::param(0.4));
// run // run
let res = run_for_ms(&mut node_exec, 100.0); 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())); assert!(res.0.iter().all(|x| !x.is_nan()));
// set params to non-reasonable values here // 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(freq_p, SAtom::param(-0.2));
matrix.set_param(form_p, SAtom::param(0.0)); matrix.set_param(form_p, SAtom::param(0.0));
matrix.set_param(atk_p, SAtom::param(0.2)); matrix.set_param(peak_p, SAtom::param(1.0));
matrix.set_param(dcy_p, SAtom::param(-1.0)); matrix.set_param(side_p, SAtom::param(0.4));
// run // run
let res = run_for_ms(&mut node_exec, 100.0); 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 matrix = Matrix::new(node_conf, 3, 3);
let mut chain = MatrixCellChain::new(CellDir::B); 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(); matrix.sync().unwrap();
let formant = NodeId::Formant(0); let formant = NodeId::FormFM(0);
// params // params
let freq_p = formant.inp_param("freq").unwrap(); let freq_p = formant.inp_param("freq").unwrap();
let form_p = formant.inp_param("form").unwrap(); let form_p = formant.inp_param("form").unwrap();
let atk_p = formant.inp_param("atk").unwrap(); let side_p = formant.inp_param("side").unwrap();
let dcy_p = formant.inp_param("dcy").unwrap(); let peak_p = formant.inp_param("peak").unwrap();
// set params to reasonable values // set params to reasonable values
matrix.set_param(freq_p, SAtom::param(-0.2)); matrix.set_param(freq_p, SAtom::param(-0.2));
matrix.set_param(form_p, SAtom::param(0.0)); matrix.set_param(form_p, SAtom::param(0.0));
matrix.set_param(atk_p, SAtom::param(0.2)); matrix.set_param(side_p, SAtom::param(0.2));
matrix.set_param(dcy_p, SAtom::param(-0.2)); matrix.set_param(peak_p, SAtom::param(0.4));
// run // run
let fft = run_and_get_avg_fft4096_now(&mut node_exec, 180); let fft = run_and_get_avg_fft4096_now(&mut node_exec, 100);
assert_eq!(fft, vec![(334, 191), (431, 331), (441, 546), (452, 222), (549, 209)]); assert_eq!(fft, vec![(323, 106), (334, 131), (431, 430), (441, 708), (452, 288), (549, 140)]);
} }