diff --git a/src/dsp/mod.rs b/src/dsp/mod.rs index 5d59676..04e3e4a 100644 --- a/src/dsp/mod.rs +++ b/src/dsp/mod.rs @@ -507,6 +507,8 @@ mod node_delay; #[allow(non_upper_case_globals)] mod node_fbwr_fbrd; #[allow(non_upper_case_globals)] +mod node_formfm; +#[allow(non_upper_case_globals)] mod node_map; #[allow(non_upper_case_globals)] mod node_mix3; @@ -593,6 +595,7 @@ use node_cqnt::CQnt; use node_delay::Delay; use node_fbwr_fbrd::FbRd; use node_fbwr_fbrd::FbWr; +use node_formfm::FormFM; use node_map::Map; use node_mix3::Mix3; use node_mux9::Mux9; @@ -1496,6 +1499,12 @@ 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], + 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) (1 freq n_pit d_pit r_fq f_freq stp_d -1.0, 0.5647131, 1000.0) @@ -1528,7 +1537,7 @@ 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], - test => Test UIType::Generic UICategory::IOUtil + 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} {2 1 trig param(0.0) knob fa_test_s 0 0} diff --git a/src/dsp/node_ad.rs b/src/dsp/node_ad.rs index ef69e5a..89b58f4 100644 --- a/src/dsp/node_ad.rs +++ b/src/dsp/node_ad.rs @@ -2,11 +2,11 @@ // This file is a part of HexoDSP. Released under GPL-3.0-or-later. // See README.md and COPYING for details. -use synfx_dsp::{sqrt4_to_pow4, TrigSignal, Trigger}; use crate::dsp::{ DspNode, GraphAtomData, GraphFun, LedPhaseVals, NodeContext, NodeId, ProcBuf, SAtom, }; use crate::nodes::{NodeAudioContext, NodeExecContext}; +use synfx_dsp::{sqrt4_to_pow4, TrigSignal, Trigger}; #[macro_export] macro_rules! fa_ad_mult { diff --git a/src/dsp/node_allp.rs b/src/dsp/node_allp.rs index adf3cbd..d76ab7c 100644 --- a/src/dsp/node_allp.rs +++ b/src/dsp/node_allp.rs @@ -2,9 +2,9 @@ // This file is a part of HexoDSP. Released under GPL-3.0-or-later. // See README.md and COPYING for details. -use synfx_dsp::AllPass; use crate::dsp::{DspNode, LedPhaseVals, NodeContext, NodeId, ProcBuf, SAtom}; use crate::nodes::{NodeAudioContext, NodeExecContext}; +use synfx_dsp::AllPass; /// A simple amplifier #[derive(Debug, Clone)] diff --git a/src/dsp/node_biqfilt.rs b/src/dsp/node_biqfilt.rs index 8d3c373..ac27b07 100644 --- a/src/dsp/node_biqfilt.rs +++ b/src/dsp/node_biqfilt.rs @@ -2,9 +2,9 @@ // This file is a part of HexoDSP. Released under GPL-3.0-or-later. // See README.md and COPYING for details. -use synfx_dsp::*; use crate::dsp::{DspNode, LedPhaseVals, NodeContext, NodeId, ProcBuf, SAtom}; use crate::nodes::{NodeAudioContext, NodeExecContext}; +use synfx_dsp::*; #[macro_export] macro_rules! fa_biqfilt_type { diff --git a/src/dsp/node_bosc.rs b/src/dsp/node_bosc.rs index e4eb603..ddf8ade 100644 --- a/src/dsp/node_bosc.rs +++ b/src/dsp/node_bosc.rs @@ -2,11 +2,11 @@ // This file is a part of HexoDSP. Released under GPL-3.0-or-later. // See README.md and COPYING for details. -use synfx_dsp::PolyBlepOscillator; use crate::dsp::{ DspNode, GraphAtomData, GraphFun, LedPhaseVals, NodeContext, NodeId, ProcBuf, SAtom, }; use crate::nodes::{NodeAudioContext, NodeExecContext}; +use synfx_dsp::PolyBlepOscillator; #[macro_export] macro_rules! fa_bosc_wtype { diff --git a/src/dsp/node_bowstri.rs b/src/dsp/node_bowstri.rs index f16ea01..7e68b8a 100644 --- a/src/dsp/node_bowstri.rs +++ b/src/dsp/node_bowstri.rs @@ -2,11 +2,11 @@ // This file is a part of HexoDSP. Released under GPL-3.0-or-later. // See README.md and COPYING for details. -use synfx_dsp::{DelayBuffer, FixedOnePole, Biquad}; use crate::dsp::{ denorm, denorm_offs, inp, out, DspNode, LedPhaseVals, NodeContext, NodeId, ProcBuf, SAtom, }; use crate::nodes::{NodeAudioContext, NodeExecContext}; +use synfx_dsp::{Biquad, DelayBuffer, FixedOnePole}; // Bowed String instrument oscillator // Bowed string model, a la Smith (1986), diff --git a/src/dsp/node_comb.rs b/src/dsp/node_comb.rs index 285368b..e96cd78 100644 --- a/src/dsp/node_comb.rs +++ b/src/dsp/node_comb.rs @@ -2,9 +2,9 @@ // This file is a part of HexoDSP. Released under GPL-3.0-or-later. // See README.md and COPYING for details. -use synfx_dsp; use crate::dsp::{DspNode, LedPhaseVals, NodeContext, NodeId, ProcBuf, SAtom}; use crate::nodes::{NodeAudioContext, NodeExecContext}; +use synfx_dsp; #[macro_export] macro_rules! fa_comb_mode { diff --git a/src/dsp/node_cqnt.rs b/src/dsp/node_cqnt.rs index 3f36f3a..ddb92e7 100644 --- a/src/dsp/node_cqnt.rs +++ b/src/dsp/node_cqnt.rs @@ -2,9 +2,9 @@ // This file is a part of HexoDSP. Released under GPL-3.0-or-later. // See README.md and COPYING for details. -use synfx_dsp::{ChangeTrig, CtrlPitchQuantizer}; use crate::dsp::{DspNode, LedPhaseVals, NodeContext, NodeId, ProcBuf, SAtom}; use crate::nodes::{NodeAudioContext, NodeExecContext}; +use synfx_dsp::{ChangeTrig, CtrlPitchQuantizer}; #[macro_export] macro_rules! fa_cqnt { diff --git a/src/dsp/node_delay.rs b/src/dsp/node_delay.rs index 2e2e826..ae9ebbe 100644 --- a/src/dsp/node_delay.rs +++ b/src/dsp/node_delay.rs @@ -2,9 +2,9 @@ // This file is a part of HexoDSP. Released under GPL-3.0-or-later. // See README.md and COPYING for details. -use synfx_dsp::{crossfade, DelayBuffer, TriggerSampleClock}; use crate::dsp::{DspNode, LedPhaseVals, NodeContext, NodeId, ProcBuf, SAtom}; use crate::nodes::{NodeAudioContext, NodeExecContext}; +use synfx_dsp::{crossfade, DelayBuffer, TriggerSampleClock}; #[macro_export] macro_rules! fa_delay_mode { diff --git a/src/dsp/node_formfm.rs b/src/dsp/node_formfm.rs new file mode 100644 index 0000000..265f736 --- /dev/null +++ b/src/dsp/node_formfm.rs @@ -0,0 +1,132 @@ +// Copyright (c) 2022 Dimas Leenman +// This file is a part of HexoDSP. Released under GPL-3.0-or-later. +// See README.md and COPYING for details. + +use crate::dsp::{DspNode, LedPhaseVals, NodeContext, NodeId, ProcBuf, SAtom}; +use crate::nodes::{NodeAudioContext, NodeExecContext}; + +/// A simple amplifier +#[derive(Debug, Clone)] +pub struct FormFM { + inv_sample_rate: f32, + phase: f32, +} + +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 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#"Formant oscillator + +Simple formant oscillator that generates a formant like sound. +Loosely based on the ModFM synthesis method. +"#; + 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` + +`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. + +`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 FormFM { + 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::{denorm, inp, out}; + + 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::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 / side_val).min((1.0 - self.phase) / (1.0 - side_val)); + + // smoothstep + let carrier = 1.0 + - ((1.0 - peak_val) + * (carrier_base * carrier_base * (3.0 - 2.0 * carrier_base))); + + // multiple of the frequency the modulators are at + let multiple = formant_freq / base_freq.max(1e-6); + + // 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) + * if multiple < 1.0 { + 0.0 + } else { + (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; + + // increment phase (very imporant) + self.phase += base_freq * self.inv_sample_rate; + + // wrap around + self.phase = self.phase.fract(); + + out.write(frame, wave); + } + } +} diff --git a/src/dsp/node_mux9.rs b/src/dsp/node_mux9.rs index ff8aa58..a1da24a 100644 --- a/src/dsp/node_mux9.rs +++ b/src/dsp/node_mux9.rs @@ -2,9 +2,9 @@ // This file is a part of HexoDSP. Released under GPL-3.0-or-later. // See README.md and COPYING for details. -use synfx_dsp::Trigger; use crate::dsp::{DspNode, LedPhaseVals, NodeContext, NodeId, ProcBuf, SAtom}; use crate::nodes::{NodeAudioContext, NodeExecContext}; +use synfx_dsp::Trigger; #[macro_export] macro_rules! fa_mux9_in_cnt { diff --git a/src/dsp/node_noise.rs b/src/dsp/node_noise.rs index ce3d5ca..0009cd4 100644 --- a/src/dsp/node_noise.rs +++ b/src/dsp/node_noise.rs @@ -2,9 +2,9 @@ // This file is a part of HexoDSP. Released under GPL-3.0-or-later. // See README.md and COPYING for details. -use synfx_dsp::Rng; use crate::dsp::{DspNode, LedPhaseVals, NodeContext, NodeId, ProcBuf, SAtom}; use crate::nodes::{NodeAudioContext, NodeExecContext}; +use synfx_dsp::Rng; #[macro_export] macro_rules! fa_noise_mode { diff --git a/src/dsp/node_pverb.rs b/src/dsp/node_pverb.rs index 20853c1..75f0034 100644 --- a/src/dsp/node_pverb.rs +++ b/src/dsp/node_pverb.rs @@ -2,9 +2,9 @@ // This file is a part of HexoDSP. Released under GPL-3.0-or-later. // See README.md and COPYING for details. -use synfx_dsp::{DattorroReverb, DattorroReverbParams, crossfade}; use crate::dsp::{denorm, DspNode, LedPhaseVals, NodeContext, NodeId, ProcBuf, SAtom}; use crate::nodes::{NodeAudioContext, NodeExecContext}; +use synfx_dsp::{crossfade, DattorroReverb, DattorroReverbParams}; pub struct DatParams { frame: usize, diff --git a/src/dsp/node_quant.rs b/src/dsp/node_quant.rs index 6955fd6..0d40549 100644 --- a/src/dsp/node_quant.rs +++ b/src/dsp/node_quant.rs @@ -2,9 +2,9 @@ // This file is a part of HexoDSP. Released under GPL-3.0-or-later. // See README.md and COPYING for details. -use synfx_dsp::{ChangeTrig, Quantizer}; use crate::dsp::{DspNode, LedPhaseVals, NodeContext, NodeId, ProcBuf, SAtom}; use crate::nodes::{NodeAudioContext, NodeExecContext}; +use synfx_dsp::{ChangeTrig, Quantizer}; #[macro_export] macro_rules! fa_quant { diff --git a/src/dsp/node_rndwk.rs b/src/dsp/node_rndwk.rs index 3996944..a60d152 100644 --- a/src/dsp/node_rndwk.rs +++ b/src/dsp/node_rndwk.rs @@ -2,9 +2,9 @@ // This file is a part of HexoDSP. Released under GPL-3.0-or-later. // See README.md and COPYING for details. -use synfx_dsp::{Rng, SlewValue, Trigger}; use crate::dsp::{DspNode, LedPhaseVals, NodeContext, NodeId, ProcBuf, SAtom}; use crate::nodes::{NodeAudioContext, NodeExecContext}; +use synfx_dsp::{Rng, SlewValue, Trigger}; /// A triggered random walker #[derive(Debug, Clone)] diff --git a/src/dsp/node_sampl.rs b/src/dsp/node_sampl.rs index 0e7e6ae..45556bf 100644 --- a/src/dsp/node_sampl.rs +++ b/src/dsp/node_sampl.rs @@ -2,10 +2,10 @@ // This file is a part of HexoDSP. Released under GPL-3.0-or-later. // See README.md and COPYING for details. -use synfx_dsp::{cubic_interpolate, Trigger}; use crate::dsp::{at, denorm, denorm_offs, inp, out}; //, inp, denorm, denorm_v, inp_dir, at}; use crate::dsp::{DspNode, LedPhaseVals, NodeContext, NodeId, ProcBuf, SAtom}; use crate::nodes::{NodeAudioContext, NodeExecContext}; +use synfx_dsp::{cubic_interpolate, Trigger}; #[macro_export] macro_rules! fa_sampl_dir { diff --git a/src/dsp/node_scope.rs b/src/dsp/node_scope.rs index 76a6f46..ab2b7f7 100644 --- a/src/dsp/node_scope.rs +++ b/src/dsp/node_scope.rs @@ -8,12 +8,12 @@ // Copyright by Andrew Belt, 2021 //use super::helpers::{sqrt4_to_pow4, TrigSignal, Trigger}; -use synfx_dsp::CustomTrigger; use crate::dsp::{DspNode, LedPhaseVals, NodeContext, NodeId, ProcBuf, SAtom}; use crate::nodes::SCOPE_SAMPLES; use crate::nodes::{NodeAudioContext, NodeExecContext}; use crate::ScopeHandle; use std::sync::Arc; +use synfx_dsp::CustomTrigger; #[macro_export] macro_rules! fa_scope_tsrc { diff --git a/src/dsp/node_sfilter.rs b/src/dsp/node_sfilter.rs index ad6f519..d887d5c 100644 --- a/src/dsp/node_sfilter.rs +++ b/src/dsp/node_sfilter.rs @@ -2,13 +2,13 @@ // This file is a part of HexoDSP. Released under GPL-3.0-or-later. // See README.md and COPYING for details. +use crate::dsp::{DspNode, LedPhaseVals, NodeContext, NodeId, ProcBuf, SAtom}; +use crate::nodes::{NodeAudioContext, NodeExecContext}; use synfx_dsp::{ process_1pole_highpass, process_1pole_lowpass, process_1pole_tpt_highpass, process_1pole_tpt_lowpass, process_hal_chamberlin_svf, process_simper_svf, process_stilson_moog, }; -use crate::dsp::{DspNode, LedPhaseVals, NodeContext, NodeId, ProcBuf, SAtom}; -use crate::nodes::{NodeAudioContext, NodeExecContext}; #[macro_export] macro_rules! fa_sfilter_type { diff --git a/src/dsp/node_sin.rs b/src/dsp/node_sin.rs index 05afe05..3988b40 100644 --- a/src/dsp/node_sin.rs +++ b/src/dsp/node_sin.rs @@ -2,11 +2,11 @@ // This file is a part of HexoDSP. Released under GPL-3.0-or-later. // See README.md and COPYING for details. -use synfx_dsp::fast_sin; use crate::dsp::{ denorm_offs, inp, out, DspNode, LedPhaseVals, NodeContext, NodeId, ProcBuf, SAtom, }; use crate::nodes::{NodeAudioContext, NodeExecContext}; +use synfx_dsp::fast_sin; /// A sine oscillator #[derive(Debug, Clone)] diff --git a/src/dsp/node_test.rs b/src/dsp/node_test.rs index 3eb0b32..02bcb87 100644 --- a/src/dsp/node_test.rs +++ b/src/dsp/node_test.rs @@ -2,11 +2,11 @@ // This file is a part of HexoDSP. Released under GPL-3.0-or-later. // See README.md and COPYING for details. -use synfx_dsp::TrigSignal; use crate::dsp::{ DspNode, GraphAtomData, GraphFun, LedPhaseVals, NodeContext, NodeId, ProcBuf, SAtom, }; use crate::nodes::{NodeAudioContext, NodeExecContext}; +use synfx_dsp::TrigSignal; #[macro_export] macro_rules! fa_test_s { diff --git a/src/dsp/node_tseq.rs b/src/dsp/node_tseq.rs index 3397568..43f75c4 100644 --- a/src/dsp/node_tseq.rs +++ b/src/dsp/node_tseq.rs @@ -2,10 +2,10 @@ // This file is a part of HexoDSP. Released under GPL-3.0-or-later. // See README.md and COPYING for details. -use synfx_dsp::{Trigger, TriggerPhaseClock}; use crate::dsp::tracker::TrackerBackend; use crate::dsp::{DspNode, LedPhaseVals, NodeContext, NodeId, ProcBuf, SAtom}; use crate::nodes::{NodeAudioContext, NodeExecContext}; +use synfx_dsp::{Trigger, TriggerPhaseClock}; use crate::dsp::MAX_BLOCK_SIZE; diff --git a/src/dsp/node_tslfo.rs b/src/dsp/node_tslfo.rs index 00355d6..bcefe93 100644 --- a/src/dsp/node_tslfo.rs +++ b/src/dsp/node_tslfo.rs @@ -2,11 +2,11 @@ // This file is a part of HexoDSP. Released under GPL-3.0-or-later. // See README.md and COPYING for details. -use synfx_dsp::{TriSawLFO, Trigger}; use crate::dsp::{ DspNode, GraphAtomData, GraphFun, LedPhaseVals, NodeContext, NodeId, ProcBuf, SAtom, }; use crate::nodes::{NodeAudioContext, NodeExecContext}; +use synfx_dsp::{TriSawLFO, Trigger}; #[derive(Debug, Clone)] pub struct TsLFO { diff --git a/src/dsp/node_vosc.rs b/src/dsp/node_vosc.rs index a35a371..aa92e55 100644 --- a/src/dsp/node_vosc.rs +++ b/src/dsp/node_vosc.rs @@ -2,11 +2,11 @@ // This file is a part of HexoDSP. Released under GPL-3.0-or-later. // See README.md and COPYING for details. -use synfx_dsp::{Oversampling, apply_distortion, VPSOscillator}; use crate::dsp::{ DspNode, GraphAtomData, GraphFun, LedPhaseVals, NodeContext, NodeId, ProcBuf, SAtom, }; use crate::nodes::{NodeAudioContext, NodeExecContext}; +use synfx_dsp::{apply_distortion, Oversampling, VPSOscillator}; #[macro_export] macro_rules! fa_vosc_ovrsmpl { diff --git a/src/lib.rs b/src/lib.rs index f944009..7a4ad7f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -321,8 +321,8 @@ pub mod monitor; pub mod nodes; pub mod sample_lib; pub mod scope_handle; -pub mod wblockdsp; mod util; +pub mod wblockdsp; pub use cell_dir::CellDir; pub use chain_builder::MatrixCellChain; diff --git a/src/matrix.rs b/src/matrix.rs index ee2fb50..2404341 100644 --- a/src/matrix.rs +++ b/src/matrix.rs @@ -8,9 +8,9 @@ use crate::matrix_repr::*; pub use crate::monitor::MON_SIG_CNT; pub use crate::nodes::MinMaxMonitorSamples; use crate::nodes::{NodeConfigurator, NodeGraphOrdering, NodeProg, MAX_ALLOCATED_NODES}; +use crate::wblockdsp::{BlkJITCompileError, BlockFun, BlockFunSnapshot}; pub use crate::CellDir; use crate::ScopeHandle; -use crate::wblockdsp::{BlockFun, BlockFunSnapshot, BlkJITCompileError}; use std::collections::{HashMap, HashSet}; diff --git a/src/matrix_repr.rs b/src/matrix_repr.rs index 3d34aa3..f054aa5 100644 --- a/src/matrix_repr.rs +++ b/src/matrix_repr.rs @@ -3,8 +3,8 @@ // See README.md and COPYING for details. use crate::dsp::{NodeId, ParamId, SAtom}; -use serde_json::{json, Value}; use crate::wblockdsp::BlockFunSnapshot; +use serde_json::{json, Value}; #[derive(Debug, Clone, Copy)] pub struct CellRepr { diff --git a/src/nodes/node_conf.rs b/src/nodes/node_conf.rs index 422e8be..5f1b5f4 100644 --- a/src/nodes/node_conf.rs +++ b/src/nodes/node_conf.rs @@ -6,15 +6,15 @@ use super::{ FeedbackFilter, GraphMessage, NodeOp, NodeProg, MAX_ALLOCATED_NODES, MAX_AVAIL_CODE_ENGINES, MAX_AVAIL_TRACKERS, MAX_INPUTS, MAX_SCOPES, UNUSED_MONITOR_IDX, }; -use crate::wblockdsp::*; use crate::dsp::tracker::{PatternData, Tracker}; use crate::dsp::{node_factory, Node, NodeId, NodeInfo, ParamId, SAtom}; use crate::monitor::{new_monitor_processor, MinMaxMonitorSamples, Monitor, MON_SIG_CNT}; use crate::nodes::drop_thread::DropThread; -#[cfg(feature = "synfx-dsp-jit")] -use synfx_dsp_jit::engine::CodeEngine; +use crate::wblockdsp::*; use crate::SampleLibrary; use crate::ScopeHandle; +#[cfg(feature = "synfx-dsp-jit")] +use synfx_dsp_jit::engine::CodeEngine; use ringbuf::{Producer, RingBuffer}; use std::collections::HashMap; diff --git a/src/wblockdsp/compiler.rs b/src/wblockdsp/compiler.rs index 2492d60..99e7689 100644 --- a/src/wblockdsp/compiler.rs +++ b/src/wblockdsp/compiler.rs @@ -210,7 +210,7 @@ pub struct Block2JITCompiler { #[cfg(not(feature = "synfx-dsp-jit"))] pub enum ASTNode { - NoSynfxDSPJit + NoSynfxDSPJit, } impl Block2JITCompiler { diff --git a/src/wblockdsp/definition.rs b/src/wblockdsp/definition.rs index f73a00a..c92cc36 100644 --- a/src/wblockdsp/definition.rs +++ b/src/wblockdsp/definition.rs @@ -208,39 +208,42 @@ pub fn setup_hxdsp_block_language( }); } - dsp_lib.borrow().for_each(|node_type| -> Result<(), ()> { - let max_ports = node_type.input_count().max(node_type.output_count()); - let is_stateful = node_type.is_stateful(); + dsp_lib + .borrow() + .for_each(|node_type| -> Result<(), ()> { + let max_ports = node_type.input_count().max(node_type.output_count()); + let is_stateful = node_type.is_stateful(); - let mut inputs = vec![]; - let mut outputs = vec![]; + let mut inputs = vec![]; + let mut outputs = vec![]; - let mut i = 0; - while let Some(name) = node_type.input_names(i) { - inputs.push(Some(name[0..(name.len().min(2))].to_string())); - i += 1; - } + let mut i = 0; + while let Some(name) = node_type.input_names(i) { + inputs.push(Some(name[0..(name.len().min(2))].to_string())); + i += 1; + } - let mut i = 0; - while let Some(name) = node_type.output_names(i) { - outputs.push(Some(name[0..(name.len().min(2))].to_string())); - i += 1; - } + let mut i = 0; + while let Some(name) = node_type.output_names(i) { + outputs.push(Some(name[0..(name.len().min(2))].to_string())); + i += 1; + } - lang.define(BlockType { - category: if is_stateful { "nodes".to_string() } else { "functions".to_string() }, - name: node_type.name().to_string(), - rows: max_ports, - area_count: 0, - user_input: BlockUserInput::None, - description: node_type.documentation().to_string(), - color: if is_stateful { 8 } else { 16 }, - inputs, - outputs, - }); + lang.define(BlockType { + category: if is_stateful { "nodes".to_string() } else { "functions".to_string() }, + name: node_type.name().to_string(), + rows: max_ports, + area_count: 0, + user_input: BlockUserInput::None, + description: node_type.documentation().to_string(), + color: if is_stateful { 8 } else { 16 }, + inputs, + outputs, + }); - Ok(()) - }).expect("seriously no error here"); + Ok(()) + }) + .expect("seriously no error here"); lang.define_identifier("in1"); lang.define_identifier("in2"); diff --git a/src/wblockdsp/mod.rs b/src/wblockdsp/mod.rs index fc210df..6743b70 100644 --- a/src/wblockdsp/mod.rs +++ b/src/wblockdsp/mod.rs @@ -6,10 +6,10 @@ */ +mod compiler; mod definition; mod language; -mod compiler; +pub use compiler::*; pub use definition::*; pub use language::*; -pub use compiler::*; diff --git a/tests/node_formfm.rs b/tests/node_formfm.rs new file mode 100644 index 0000000..dc728e6 --- /dev/null +++ b/tests/node_formfm.rs @@ -0,0 +1,164 @@ +// Copyright (c) 2022 Dimas Leenman +// This file is a part of HexoDSP. Released under GPL-3.0-or-later. +// See README.md and COPYING for details. + +mod common; +use common::*; + +#[test] +fn check_normalized_if_freq_lower_than_formant_freq() { + let (node_conf, mut node_exec) = new_node_engine(); + + let mut matrix = Matrix::new(node_conf, 3, 3); + + let mut chain = MatrixCellChain::new(CellDir::B); + chain.node_out("formfm", "sig").node_inp("out", "ch1").place(&mut matrix, 0, 0).unwrap(); + + matrix.sync().unwrap(); + + let formant = NodeId::FormFM(0); + + // params + let freq_p = formant.inp_param("freq").unwrap(); + let form_p = formant.inp_param("form").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(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); + + // and check it's normalized + let max = res.0.iter().fold(0.0 as f32, |acc, x| acc.max(*x)); + let min = res.0.iter().fold(0.0 as f32, |acc, x| acc.min(*x)); + assert!(max > 0.8 && max < 1.01 && min < -0.8 && min > -1.01); +} + +#[test] +fn check_no_dc_bias_at_formant_freq_lower_than_freq() { + let (node_conf, mut node_exec) = new_node_engine(); + + let mut matrix = Matrix::new(node_conf, 3, 3); + + let mut chain = MatrixCellChain::new(CellDir::B); + chain.node_out("formfm", "sig").node_inp("out", "ch1").place(&mut matrix, 0, 0).unwrap(); + + matrix.sync().unwrap(); + + let formant = NodeId::FormFM(0); + + // params + let freq_p = formant.inp_param("freq").unwrap(); + let form_p = formant.inp_param("form").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(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); + + // average should remain at ~0 + let sum = res.0.iter().sum::(); + let avg = sum / res.0.len() as f32; + + assert!(avg > -0.05 && avg < 0.05); +} + +#[test] +fn check_no_nan() { + let (node_conf, mut node_exec) = new_node_engine(); + + let mut matrix = Matrix::new(node_conf, 3, 3); + + let mut chain = MatrixCellChain::new(CellDir::B); + chain.node_out("formfm", "sig").node_inp("out", "ch1").place(&mut matrix, 0, 0).unwrap(); + + matrix.sync().unwrap(); + + let formant = NodeId::FormFM(0); + + // params + let freq_p = formant.inp_param("freq").unwrap(); + let form_p = formant.inp_param("form").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(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); + + // and check there's no NaN + assert!(res.0.iter().all(|x| !x.is_nan())); + + // set params to non-reasonable values here + // side to 0 + matrix.set_param(freq_p, SAtom::param(-0.2)); + matrix.set_param(form_p, SAtom::param(0.0)); + 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); + + // and check there's no NaN + assert!(res.0.iter().all(|x| !x.is_nan())); + + // set params to non-reasonable values here + // side to 1 + matrix.set_param(freq_p, SAtom::param(-0.2)); + matrix.set_param(form_p, SAtom::param(0.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); + + // and check there's no NaN + assert!(res.0.iter().all(|x| !x.is_nan())); +} + +#[test] +fn check_formant_freq() { + let (node_conf, mut node_exec) = new_node_engine(); + + let mut matrix = Matrix::new(node_conf, 3, 3); + + let mut chain = MatrixCellChain::new(CellDir::B); + chain.node_out("formfm", "sig").node_inp("out", "ch1").place(&mut matrix, 0, 0).unwrap(); + + matrix.sync().unwrap(); + + let formant = NodeId::FormFM(0); + + // params + let freq_p = formant.inp_param("freq").unwrap(); + let form_p = formant.inp_param("form").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(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, 100); + assert_eq!(fft, vec![(323, 106), (334, 131), (431, 430), (441, 708), (452, 288), (549, 140)]); +}