diff --git a/src/dsp/helpers.rs b/src/dsp/helpers.rs index e57740f..2f0a1ad 100644 --- a/src/dsp/helpers.rs +++ b/src/dsp/helpers.rs @@ -577,6 +577,44 @@ impl Default for TrigSignal { fn default() -> Self { Self::new() } } +#[derive(Debug, Clone, Copy)] +pub struct ChangeTrig { + ts: TrigSignal, + last: f32, +} + +impl ChangeTrig { + pub fn new() -> Self { + Self { + ts: TrigSignal::new(), + last: -100.0, // some random value :-) + } + } + + pub fn reset(&mut self) { + self.ts.reset(); + self.last = -100.0; + } + + pub fn set_sample_rate(&mut self, srate: f32) { + self.ts.set_sample_rate(srate); + } + + #[inline] + pub fn next(&mut self, inp: f32) -> f32 { + if (inp - self.last).abs() > std::f32::EPSILON { + self.ts.trigger(); + self.last = inp; + } + + self.ts.next() + } +} + +impl Default for ChangeTrig { + fn default() -> Self { Self::new() } +} + #[derive(Debug, Clone, Copy)] pub struct Trigger { triggered: bool, diff --git a/src/dsp/mod.rs b/src/dsp/mod.rs index e613aba..b52e45b 100644 --- a/src/dsp/mod.rs +++ b/src/dsp/mod.rs @@ -747,14 +747,16 @@ macro_rules! node_list { (0 freq n_pit d_pit r_id f_freq stp_d -1.0, 0.5647131, 440.0) (1 oct n_id d_id r_s f_def stp_d -1.0, 1.0, 0.0) {2 0 keys setting(0) fa_quant 0 0} - [0 sig], + [0 sig] + [1 t], cqnt => CQnt UIType::Generic UICategory::CV (0 inp n_id d_id r_id f_def stp_d 0.0, 1.0, 0.0) (1 oct n_id d_id r_s f_def stp_d -1.0, 1.0, 0.0) {2 0 keys setting(0) fa_cqnt 0 0} {3 1 omin setting(0) fa_cqnt_omin 0 4} {4 2 omax setting(0) fa_cqnt_omax 0 4} - [0 sig], + [0 sig] + [1 t], tseq => TSeq UIType::Generic UICategory::Mod (0 clock n_id d_id r_id f_def stp_d 0.0, 1.0, 0.0) (1 trig n_id n_id r_id f_def stp_d -1.0, 1.0, 0.0) diff --git a/src/dsp/node_cqnt.rs b/src/dsp/node_cqnt.rs index 45809ca..79fbec7 100644 --- a/src/dsp/node_cqnt.rs +++ b/src/dsp/node_cqnt.rs @@ -4,7 +4,7 @@ use crate::nodes::{NodeAudioContext, NodeExecContext}; use crate::dsp::{NodeId, SAtom, ProcBuf, DspNode, LedPhaseVals, NodeContext}; -use crate::dsp::helpers::CtrlPitchQuantizer; +use crate::dsp::helpers::{CtrlPitchQuantizer, ChangeTrig}; #[macro_export] macro_rules! fa_cqnt { ($formatter: expr, $v: expr, $denorm_v: expr) => { { @@ -42,13 +42,15 @@ macro_rules! fa_cqnt_omax { ($formatter: expr, $v: expr, $denorm_v: expr) => { { /// A control signal to pitch quantizer/converter #[derive(Debug, Clone)] pub struct CQnt { - quant: Box, + quant: Box, + change_trig: ChangeTrig, } impl CQnt { pub fn new(_nid: &NodeId) -> Self { Self { - quant: Box::new(CtrlPitchQuantizer::new()), + quant: Box::new(CtrlPitchQuantizer::new()), + change_trig: ChangeTrig::new(), } } pub const inp : &'static str = @@ -61,6 +63,10 @@ impl CQnt { "CQnt omax\n\nRange: (-1..1)"; pub const sig : &'static str = "CQnt sig\n\nRange: (-1..1)"; + pub const t : &'static str = + "CQnt t\nEverytime the quantizer snaps to a new pitch, it will \ + emit a short trigger on this signal output. This is useful \ + to trigger for example an envelope."; pub const keys : &'static str = "CQnt keys\n"; pub const DESC : &'static str = @@ -80,8 +86,14 @@ like the 'Quant' node. impl DspNode for CQnt { fn outputs() -> usize { 1 } - fn set_sample_rate(&mut self, _srate: f32) { } - fn reset(&mut self) { } + fn set_sample_rate(&mut self, srate: f32) { + self.change_trig.set_sample_rate(srate); + } + + fn reset(&mut self) { + self.change_trig.reset(); + } + #[inline] fn process( @@ -90,11 +102,12 @@ impl DspNode for CQnt { atoms: &[SAtom], inputs: &[ProcBuf], outputs: &mut [ProcBuf], ctx_vals: LedPhaseVals) { - use crate::dsp::{at, out, inp, denorm}; + use crate::dsp::{at, out_buf, inp, denorm}; let inp = inp::CQnt::inp(inputs); let oct = inp::CQnt::oct(inputs); - let out = out::CQnt::sig(outputs); + let mut out = out_buf::CQnt::sig(outputs); + let mut t = out_buf::CQnt::t(outputs); let keys = at::CQnt::keys(atoms); let omin = at::CQnt::omin(atoms); let omax = at::CQnt::omax(atoms); @@ -107,6 +120,8 @@ impl DspNode for CQnt { let pitch = self.quant.signal_to_pitch( denorm::CQnt::inp(inp, frame)); + + t.write(frame, self.change_trig.next(pitch)); out.write(frame, pitch + denorm::CQnt::oct(oct, frame)); } diff --git a/src/dsp/node_quant.rs b/src/dsp/node_quant.rs index a37abd8..66f2f2c 100644 --- a/src/dsp/node_quant.rs +++ b/src/dsp/node_quant.rs @@ -4,7 +4,7 @@ use crate::nodes::{NodeAudioContext, NodeExecContext}; use crate::dsp::{NodeId, SAtom, ProcBuf, DspNode, LedPhaseVals, NodeContext}; -use crate::dsp::helpers::Quantizer; +use crate::dsp::helpers::{Quantizer, ChangeTrig}; #[macro_export] macro_rules! fa_quant { ($formatter: expr, $v: expr, $denorm_v: expr) => { { @@ -14,23 +14,35 @@ macro_rules! fa_quant { ($formatter: expr, $v: expr, $denorm_v: expr) => { { /// A pitch quantizer #[derive(Debug, Clone)] pub struct Quant { - quant: Box, + quant: Box, + change_trig: ChangeTrig, } impl Quant { pub fn new(_nid: &NodeId) -> Self { Self { - quant: Box::new(Quantizer::new()), + quant: Box::new(Quantizer::new()), + change_trig: ChangeTrig::new(), } } pub const freq : &'static str = - "Quant freq\n\nRange: (0..1)"; + "Quant freq\nAny signal that is to be pitch quantized.\nRange: (-1..1)"; pub const oct : &'static str = - "Quant oct\n\nRange: (-1..1)"; + "Quant oct\nPitch offset, the knob is snapping to octave offsets. \ + Feed signal values snapped to 0.1 multiples for exact octave offsets.\ + \nRange: (-1..1)"; pub const sig : &'static str = - "Quant sig\n\nRange: (-1..1)"; + "Quant sig\nThe quantized output signal that is rounded to \ + the next selected note pitch within the octave of the \ + original input to 'freq'.\nRange: (-1..1)"; pub const keys : &'static str = - "Quant keys\n"; + "Quant keys\nSelect the notes you want to snap to here. \ + If no notes are selected, the quantizer will snap the \ + incoming signal to any closest note."; + pub const t : &'static str = + "Quant t\nEverytime the quantizer snaps to a new pitch, it will \ + emit a short trigger on this signal output. This is useful \ + to trigger for example an envelope."; pub const DESC : &'static str = r#"Pitch Quantizer @@ -52,8 +64,13 @@ please see also the 'CQnt' node. impl DspNode for Quant { fn outputs() -> usize { 1 } - fn set_sample_rate(&mut self, _srate: f32) { } - fn reset(&mut self) { } + fn set_sample_rate(&mut self, srate: f32) { + self.change_trig.set_sample_rate(srate); + } + + fn reset(&mut self) { + self.change_trig.reset(); + } #[inline] fn process( @@ -62,17 +79,20 @@ impl DspNode for Quant { atoms: &[SAtom], inputs: &[ProcBuf], outputs: &mut [ProcBuf], ctx_vals: LedPhaseVals) { - use crate::dsp::{at, out, inp, denorm}; + use crate::dsp::{at, out_buf, inp, denorm}; let freq = inp::Quant::freq(inputs); let oct = inp::Quant::oct(inputs); let keys = at::Quant::keys(atoms); - let out = out::Quant::sig(outputs); + let mut out = out_buf::CQnt::sig(outputs); + let mut t = out_buf::CQnt::t(outputs); self.quant.set_keys(keys.i()); for frame in 0..ctx.nframes() { let pitch = self.quant.process(freq.read(frame)); + + t.write(frame, self.change_trig.next(pitch)); out.write(frame, pitch + denorm::Quant::oct(oct, frame)); }