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)]
|
||||
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)
|
||||
|
|
|
@ -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
|
|
@ -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)]);
|
||||
}
|
Loading…
Reference in a new issue