From cbf00e8e39d5c693f973e3a35add671081f54236 Mon Sep 17 00:00:00 2001 From: Dimas Leenman Date: Tue, 19 Jul 2022 11:44:54 +0200 Subject: [PATCH] initial formant synth --- src/dsp/helpers.rs | 14 ++--- src/dsp/mod.rs | 5 ++ src/dsp/node_formant.rs | 127 ++++++++++++++++++++++++++++++++++++++++ src/nodes/node_conf.rs | 11 +--- src/nodes/node_exec.rs | 4 +- tests/node_delay.rs | 2 +- 6 files changed, 145 insertions(+), 18 deletions(-) create mode 100644 src/dsp/node_formant.rs diff --git a/src/dsp/helpers.rs b/src/dsp/helpers.rs index 5020fc7..91bd482 100644 --- a/src/dsp/helpers.rs +++ b/src/dsp/helpers.rs @@ -2182,13 +2182,13 @@ impl TriSawLFO { }; if s.abs() > f(1.0) { - println!( - "RECALC TRISAW: rev={}, rise={}, fall={}, phase={}", - self.rev.to_f64().unwrap_or(0.0), - self.rise_r.to_f64().unwrap_or(0.0) as f32, - self.fall_r.to_f64().unwrap_or(0.0) as f32, - self.phase.to_f64().unwrap_or(0.0) as f32 - ); + println!( + "RECALC TRISAW: rev={}, rise={}, fall={}, phase={}", + self.rev.to_f64().unwrap_or(0.0), + self.rise_r.to_f64().unwrap_or(0.0) as f32, + self.fall_r.to_f64().unwrap_or(0.0) as f32, + self.phase.to_f64().unwrap_or(0.0) as f32 + ); } self.phase = self.phase + self.freq * self.israte; diff --git a/src/dsp/mod.rs b/src/dsp/mod.rs index e726073..9ae6cd3 100644 --- a/src/dsp/mod.rs +++ b/src/dsp/mod.rs @@ -23,6 +23,8 @@ mod node_delay; #[allow(non_upper_case_globals)] mod node_fbwr_fbrd; #[allow(non_upper_case_globals)] +mod node_formant; +#[allow(non_upper_case_globals)] mod node_map; #[allow(non_upper_case_globals)] mod node_mix3; @@ -108,6 +110,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_map::Map; use node_mix3::Mix3; use node_mux9::Mux9; @@ -1001,6 +1004,8 @@ macro_rules! node_list { (14 mix n_id d_id r_id f_def stp_d 0.0, 1.0, 0.5) [0 sig_l] [1 sig_r], + formant => Formant UIType::Generic UICategory::Signal + (0), test => Test UIType::Generic UICategory::IOUtil (0 f n_id d_id r_id f_def stp_d 0.0, 1.0, 0.5) {1 0 p param(0.0) knob fa_test_s 0 10} diff --git a/src/dsp/node_formant.rs b/src/dsp/node_formant.rs new file mode 100644 index 0000000..0f73a60 --- /dev/null +++ b/src/dsp/node_formant.rs @@ -0,0 +1,127 @@ +use super::helpers::{sqrt4_to_pow4, TrigSignal, Trigger}; +use crate::dsp::{ + DspNode, GraphAtomData, GraphFun, LedPhaseVals, NodeContext, NodeId, ProcBuf, SAtom, +}; +use crate::nodes::{NodeAudioContext, NodeExecContext}; + +/// A simple amplifier +#[derive(Debug, Clone)] +pub struct Formant { + inv_sample_rate: f32, + phase: f32, +} + +impl Formant { + pub fn new(_nid: &NodeId) -> Self { + Self { inv_sample_rate: 1.0 / 44100.0, phase: 0.0 } + } + pub const inp: &'static str = + "Ad inp\nSignal input. If you don't connect this, and set this to 1.0 \ + this will act as envelope signal generator. But you can also just \ + route a signal directly through this of course.\nRange: (-1..1)\n"; + pub const trig: &'static str = + "Ad trig\nTrigger input that starts the attack phase.\nRange: (0..1)\n"; + pub const atk: &'static str = + "Ad atk\nAttack time of the envelope. You can extend the maximum \ + range of this with the 'mult' setting.\nRange: (0..1)\n"; + pub const dcy: &'static str = "Ad atk\nDecay time of the envelope. You can extend the maximum \ + range of this with the 'mult' setting.\nRange: (0..1)\n"; + pub const ashp: &'static str = "Ad ashp\nAttack shape. This allows you to change the shape \ + of the attack stage from a logarithmic, to a linear and to an \ + exponential shape.\nRange: (0..1)\n"; + pub const dshp: &'static str = "Ad dshp\nDecay shape. This allows you to change the shape \ + of the decay stage from a logarithmic, to a linear and to an \ + exponential shape.\nRange: (0..1)\n"; + pub const mult: &'static str = "Ad mult\nAttack and Decay time range multiplier. \ + This will extend the maximum range of the 'atk' and 'dcy' parameters."; + pub const sig: &'static str = + "Ad sig\nEnvelope signal output. If a signal is sent to the 'inp' port, \ + you will receive an attenuated signal here. If you set 'inp' to a \ + fixed value (for instance 1.0), this will output an envelope signal \ + in the range 0.0 to 'inp' (1.0).\nRange: (-1..1)\n"; + pub const eoet: &'static str = + "Ad eoet\nEnd of envelope trigger. This output sends a trigger once \ + the end of the decay stage has been reached.\nRange: (0..1)"; + pub const DESC: &'static str = r#"A direct formant synthesizer + +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. +"#; + pub const HELP: &'static str = r#"Formant - Single formant synthesizer +This is a formant synthesizer that directly generates the audio, no filters needed. +"#; +} + +impl DspNode for Formant { + fn outputs() -> usize { + 1 + } + + fn set_sample_rate(&mut self, srate: f32) { + self.inv_sample_rate = 1.0 / srate; + } + + fn reset(&mut self) { + self.phase = 0.0; + } + + #[inline] + fn process( + &mut self, + ctx: &mut T, + _ectx: &mut NodeExecContext, + _nctx: &NodeContext, + atoms: &[SAtom], + inputs: &[ProcBuf], + outputs: &mut [ProcBuf], + ctx_vals: LedPhaseVals, + ) { + use crate::dsp::{at, denorm, inp, out}; + + let base_freq = inp::Formant::freq(inputs); + let formant_freq = inp::Formant::fmt(inputs); + let attack_freq = inp::Formant::atk(inputs); + let decay_freq = inp::Formant::dcy(inputs); + let out = out::Formant::sig(outputs); + + for frame in 0..ctx.nframes() { + // where the two decays meet + let carrier_center = decay_freq / (attack_freq + decay_freq); + + // where they meet in amplitude + let carrier_lowest_amplitude = + (-std::f32::consts::TAU * base_freq * carrier_center * decay_freq).exp(); + + // turn it into a triangle wave + let carrier_attack = (1.0 - self.phase) / carrier_center; + let carrier_decay = self.phase / (1.0 - carrier_center); + + // actual triangle wave + let carrier_base = 1.0 - carrier_attack.min(carrier_decay); + + // smoothstep + let carrier = + carrier_base * carrier_base * (3.0 - 2.0 * carrier_base) * carrier_lowest_amplitude + + (1.0 - carrier_lowest_amplitude); + + // multiple of the frequency the modulators are at + let multiple = formant_freq / base_freq; + + // round them to the closest integer of the formant freq + let freq_a = multiple.floor(); + let freq_b = freq_a + 1.0; + + // and how much to lerp between them + let blend = multiple.fract(); + + // get the true modulator + let modulator = (1.0 - blend) * (std::f32::consts::TAU * self.phase * freq_a).cos() + + blend * (std::f32::consts::TAU * self.phase * freq_b).cos(); + + // entire wave + let wave = carrier * modulator; + + out.write(frame, wave); + } + } +} diff --git a/src/nodes/node_conf.rs b/src/nodes/node_conf.rs index f067199..ba9f313 100644 --- a/src/nodes/node_conf.rs +++ b/src/nodes/node_conf.rs @@ -3,8 +3,8 @@ // See README.md and COPYING for details. use super::{ - FeedbackFilter, GraphMessage, NodeOp, NodeProg, MAX_ALLOCATED_NODES, - MAX_AVAIL_TRACKERS, MAX_INPUTS, UNUSED_MONITOR_IDX, + FeedbackFilter, GraphMessage, NodeOp, NodeProg, MAX_ALLOCATED_NODES, MAX_AVAIL_TRACKERS, + MAX_INPUTS, UNUSED_MONITOR_IDX, }; use crate::dsp::tracker::{PatternData, Tracker}; use crate::dsp::{node_factory, Node, NodeId, NodeInfo, ParamId, SAtom}; @@ -245,12 +245,7 @@ impl SharedNodeConf { } ( - Self { - node_ctx_values, - graph_update_prod: rb_graph_prod, - monitor, - drop_thread, - }, + Self { node_ctx_values, graph_update_prod: rb_graph_prod, monitor, drop_thread }, SharedNodeExec { node_ctx_values: exec_node_ctx_vals, graph_update_con: rb_graph_con, diff --git a/src/nodes/node_exec.rs b/src/nodes/node_exec.rs index 069ffb2..699d026 100644 --- a/src/nodes/node_exec.rs +++ b/src/nodes/node_exec.rs @@ -3,8 +3,8 @@ // See README.md and COPYING for details. use super::{ - DropMsg, GraphMessage, NodeProg, FB_DELAY_TIME_US, MAX_ALLOCATED_NODES, - MAX_FB_DELAY_SIZE, MAX_SMOOTHERS, UNUSED_MONITOR_IDX, + DropMsg, GraphMessage, NodeProg, FB_DELAY_TIME_US, MAX_ALLOCATED_NODES, MAX_FB_DELAY_SIZE, + MAX_SMOOTHERS, UNUSED_MONITOR_IDX, }; use crate::dsp::{Node, NodeContext, NodeId, MAX_BLOCK_SIZE}; use crate::monitor::{MonitorBackend, MON_SIG_CNT}; diff --git a/tests/node_delay.rs b/tests/node_delay.rs index 9bbe833..d137a60 100644 --- a/tests/node_delay.rs +++ b/tests/node_delay.rs @@ -119,7 +119,7 @@ fn check_node_delay_2() { vec![ // 10ms smoothing time for "inp" 0.001133, // 30ms delaytime just mixing the 0.5: - 0.5, 0.5, 0.5, // the delayed smoothing ramp (10ms): + 0.5, 0.5, 0.5, // the delayed smoothing ramp (10ms): 0.951113, // the delay + input signal: 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0 ]