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)]
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)

View file

@ -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

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 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)]);
}