initial formant synth
This commit is contained in:
parent
5216a88cc8
commit
cbf00e8e39
6 changed files with 145 additions and 18 deletions
|
@ -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}
|
||||
|
|
127
src/dsp/node_formant.rs
Normal file
127
src/dsp/node_formant.rs
Normal file
|
@ -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<T: NodeAudioContext>(
|
||||
&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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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};
|
||||
|
|
Loading…
Reference in a new issue