diff --git a/src/dsp/mod.rs b/src/dsp/mod.rs index 9ac4b7d..a054107 100644 --- a/src/dsp/mod.rs +++ b/src/dsp/mod.rs @@ -254,8 +254,8 @@ macro_rules! d_pit { ($x: expr) => { // 5.0 * 0.1 => 12.0 // 5.0 * 0.008333333 => 1.0 // 5.0 * 0.000083333 => 0.001 -macro_rules! n_det { ($x: expr) => { ($x * 5.0 * 0.1) / 12.0 } } -macro_rules! d_det { ($x: expr) => { $x * 0.2 * 120.0 } } +macro_rules! n_det { ($x: expr) => { $x / 120.0 } } +macro_rules! d_det { ($x: expr) => { $x * 120.0 } } // Rounding function that does nothing macro_rules! r_id { ($x: expr) => { $x } } @@ -344,9 +344,10 @@ macro_rules! node_list { (2 offs n_id n_id r_id f_def 0.0, 1.0, 0.0) (3 len n_id n_id r_id f_def 0.0, 1.0, 1.0) (4 dcms n_declick d_declick r_id f_def 0.0, 1.0, 3.14) - {5 0 sample audio_unloaded("") f_def 0 0} - {6 1 pmode setting(0) fa_sampl_pmode 0 1} - {7 2 dclick setting(0) fa_sampl_dclick 0 1} + (5 det n_det d_det r_id f_det -1.0, 1.0, 0.0) + {6 0 sample audio_unloaded("") f_def 0 0} + {7 1 pmode setting(0) fa_sampl_pmode 0 1} + {8 2 dclick setting(0) fa_sampl_dclick 0 1} [0 sig], sin => Sin UIType::Generic UICategory::Osc (0 freq n_pit d_pit r_id f_freq -1.0, 1.0, 440.0) @@ -857,6 +858,15 @@ macro_rules! make_node_info_enum { })+ } + #[allow(non_snake_case)] + pub mod denorm_offs { + $(pub mod $variant { + $(#[inline] pub fn $para(buf: &crate::dsp::ProcBuf, offs_val: f32, frame: usize) -> f32 { + $d_fun!(buf.read(frame) + offs_val) + })* + })+ + } + #[allow(non_snake_case)] pub mod inp_dir { $(pub mod $variant { diff --git a/src/dsp/node_sampl.rs b/src/dsp/node_sampl.rs index 2a0bc63..15944fa 100644 --- a/src/dsp/node_sampl.rs +++ b/src/dsp/node_sampl.rs @@ -4,7 +4,7 @@ use crate::nodes::{NodeAudioContext, NodeExecContext}; use crate::dsp::{NodeId, SAtom, ProcBuf, DspNode, LedPhaseVals}; -use crate::dsp::{out, at, inp, denorm}; //, inp, denorm, denorm_v, inp_dir, at}; +use crate::dsp::{out, at, inp, denorm, denorm_offs}; //, inp, denorm, denorm_v, inp_dir, at}; use super::helpers::Trigger; #[macro_export] @@ -64,6 +64,14 @@ impl Sampl { "Sampl len\nLength of the sample, after the offset has been applied.\nRange: (0..1)\n"; pub const dcms : &'static str = "Sampl dcms\nDeclick fade time in milliseconds.\nNot audio rate!\nRange: (0..1)\n"; + pub const det : &'static str = + "Sin det\nDetune the oscillator in semitones and cents. \ + the input of this value is rounded to semitones on coarse input. \ + Fine input lets you detune in cents (rounded). \ + A signal sent to this port is not rounded.\n\ + Note: The signal input allows detuning over +- 10 octaves.\ + \n\nKnob Range: (-0.2 .. 0.2)\n\ + Signal Range: (-1.0 .. 1.0)\n"; pub const sample : &'static str = "Sampl sample\nThe audio sample that is played back.\nRange: (-1..1)\n"; @@ -128,6 +136,7 @@ impl Sampl { let offs = inp::Sampl::offs(inputs); let len = inp::Sampl::len(inputs); let dcms = inp::Sampl::dcms(inputs); + let det = inp::Sampl::det(inputs); let sample_srate = sample_data[0] as f64; let sample_data = &sample_data[1..]; @@ -161,8 +170,10 @@ impl Sampl { let s = if is_playing { - let playback_speed = - denorm::Sampl::freq(freq, frame) / 440.0; + let freq = + denorm_offs::Sampl::freq( + freq, det.read(frame), frame); + let playback_speed = freq / 440.0; let prev_phase = self.phase; diff --git a/src/dsp/node_sin.rs b/src/dsp/node_sin.rs index 6f947f0..e919647 100644 --- a/src/dsp/node_sin.rs +++ b/src/dsp/node_sin.rs @@ -3,7 +3,10 @@ // See README.md and COPYING for details. use crate::nodes::{NodeAudioContext, NodeExecContext}; -use crate::dsp::{NodeId, SAtom, ProcBuf, denorm, out, inp, DspNode, LedPhaseVals}; +use crate::dsp::{ + NodeId, SAtom, ProcBuf, denorm, denorm_offs, + out, inp, DspNode, LedPhaseVals +}; use crate::dsp::helpers::fast_sin; @@ -31,8 +34,9 @@ impl Sin { "Sin det\nDetune the oscillator in semitones and cents. \ the input of this value is rounded to semitones on coarse input. \ Fine input lets you detune in cents (rounded). \ - A signal sent to this port is not rounded.\ - \n\nRange: (-1..1)\n"; + A signal sent to this port is not rounded.\n\ + Note: The signal input allows detune +-10 octaves.\ + \nRange: (Knob -0.2 .. 0.2) / (Signal -1.0 .. 1.0)\n"; pub const sig : &'static str = "Sin sig\nOscillator signal output.\n\nRange: (-1..1)\n"; } @@ -56,11 +60,12 @@ impl DspNode for Sin { { let o = out::Sin::sig(outputs); let freq = inp::Sin::freq(inputs); + let det = inp::Sin::det(inputs); let isr = 1.0 / self.srate; let mut last_val = 0.0; for frame in 0..ctx.nframes() { - let freq = denorm::Sin::freq(freq, frame); + let freq = denorm_offs::Sampl::freq(freq, det.read(frame), frame); last_val = fast_sin(self.phase * TWOPI); o.write(frame, last_val); diff --git a/tests/basics.rs b/tests/basics.rs index 55f8b83..2920547 100644 --- a/tests/basics.rs +++ b/tests/basics.rs @@ -137,6 +137,59 @@ fn check_sine_pitch_change() { assert_eq!(fft_res[0], (4393, 251)); } +#[test] +fn check_detune_parameter() { + let sin = NodeId::Sin(0); + let det_param = sin.inp_param("det").unwrap(); + assert_float_eq!(det_param.norm(12.0), 0.1); + assert_float_eq!(det_param.norm(-12.0), -0.1); + assert_float_eq!(det_param.norm(24.0), 0.2); + assert_float_eq!(det_param.norm(-24.0), -0.2); +} + +#[test] +fn check_sine_freq_detune() { + let (node_conf, mut node_exec) = new_node_engine(); + let mut matrix = Matrix::new(node_conf, 3, 3); + + let sin = NodeId::Sin(0); + let out = NodeId::Out(0); + matrix.place(0, 0, Cell::empty(sin) + .out(None, sin.out("sig"), None)); + matrix.place(1, 0, Cell::empty(out) + .input(None, out.inp("ch1"), None)); + matrix.sync().unwrap(); + + let freq_param = sin.inp_param("freq").unwrap(); + let det_param = sin.inp_param("det").unwrap(); + + run_no_input(&mut node_exec, 50.0); + + let cfreq = run_and_get_counted_freq(&mut node_exec, 100.0); + assert_float_eq!(cfreq.floor(), 440.0); + + matrix.set_param(freq_param, SAtom::param(freq_param.norm(4400.0))); + run_no_input(&mut node_exec, 50.0); + let cfreq = run_and_get_counted_freq(&mut node_exec, 1000.0); + assert_float_eq!(cfreq.floor(), 4400.0); + + matrix.set_param(freq_param, SAtom::param(freq_param.norm(50.0))); + run_no_input(&mut node_exec, 50.0); + let cfreq = run_and_get_counted_freq(&mut node_exec, 100.0); + assert_float_eq!(cfreq.floor(), 50.0); + + matrix.set_param(freq_param, SAtom::param(freq_param.norm(440.0))); + matrix.set_param(det_param, SAtom::param(det_param.norm(12.0))); + run_no_input(&mut node_exec, 50.0); + let cfreq = run_and_get_counted_freq(&mut node_exec, 1000.0); + assert_float_eq!(cfreq.floor(), 880.0); + + matrix.set_param(det_param, SAtom::param(det_param.norm(1.0))); + run_no_input(&mut node_exec, 50.0); + let cfreq = run_and_get_counted_freq(&mut node_exec, 1000.0); + assert_float_eq!(cfreq.floor(), 493.0); +} + #[test] fn check_matrix_monitor() { let (node_conf, mut node_exec) = new_node_engine(); diff --git a/tests/common/mod.rs b/tests/common/mod.rs index eaeba08..939f281 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -201,6 +201,40 @@ pub fn run_and_get_l_rms_mimax( rms_mimax[1] } +pub fn run_and_get_counted_freq( + node_exec: &mut hexodsp::nodes::NodeExecutor, ms: f32) + -> f64 +{ + let (out_l, _out_r) = + // +0.1 here for some extra samples + // this is just for tuning the frequency counter, so that it detects + // the last swing correctly. It's probably wrong, but the results + // match up better this way. + run_no_input(node_exec, (ms + 0.1) / 1000.0); + + let mut zero_trans = 0; + let mut last_val = 0.0; + + for s in out_l.iter() { + if last_val > 0.0 && *s < 0.0 { + zero_trans += 1; + } else if last_val < 0.0 && *s > 0.0 { + zero_trans += 1; + } + + last_val = *s; + } + + //d// println!("SAMPLES: {}", out_l.len()); + //d// println!("ZERO TRANS: {}", zero_trans); + + let trans_per_sample = + // substract the extra samples applied earlier. + (zero_trans as f64) / ((out_l.len() - 4) as f64); + trans_per_sample * 44100.0 * 0.5 +} + + pub fn run_and_get_fft4096( node_exec: &mut hexodsp::nodes::NodeExecutor, thres: u32, diff --git a/tests/node_sampl.rs b/tests/node_sampl.rs index 00dc214..f0932e2 100644 --- a/tests/node_sampl.rs +++ b/tests/node_sampl.rs @@ -53,6 +53,28 @@ fn check_node_sampl_1() { assert_eq!(fft[0], (7127, 1029)); } +#[test] +fn check_node_sampl_detune() { + let (node_conf, mut node_exec) = new_node_engine(); + let mut matrix = Matrix::new(node_conf, 3, 3); + + let smpl = NodeId::Sampl(0); + let out = NodeId::Out(0); + matrix.place(0, 0, Cell::empty(smpl) + .out(None, None, smpl.out("sig"))); + matrix.place(0, 1, Cell::empty(out) + .input(out.inp("ch1"), None, None)); + matrix.sync().unwrap(); + + let sample_p = smpl.inp_param("sample").unwrap(); + let freq_p = smpl.inp_param("freq").unwrap(); + let det_p = smpl.inp_param("det").unwrap(); + matrix.set_param(sample_p, SAtom::audio_unloaded("tests/sample_sin.wav")); + + let cfreq = run_and_get_counted_freq(&mut node_exec, 1000.0); + assert_float_eq!(cfreq, 1.0); +} + #[test] fn check_node_sampl_reload() { {