renamed and improved, so no more atk/dcy but direct controls instead
This commit is contained in:
parent
c4a9b56099
commit
e1799f6d45
3 changed files with 76 additions and 80 deletions
|
@ -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)
|
||||||
|
|
|
@ -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
|
|
@ -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)]);
|
||||||
}
|
}
|
Loading…
Reference in a new issue