// Copyright (c) 2021 Weird Constructor // This file is a part of HexoDSP. Released under GPL-3.0-or-later. // See README.md and COPYING for details. pub use hexodsp::dsp::*; pub use hexodsp::matrix::*; pub use hexodsp::nodes::new_node_engine; pub use hexodsp::MatrixCellChain; pub use hexodsp::NodeExecutor; pub use hexodsp::nodes::{HxTimedEvent, HxMidiEvent}; use hound; pub const SAMPLE_RATE: f32 = 44100.0; #[allow(dead_code)] pub const SAMPLE_RATE_US: usize = 44100; #[macro_export] macro_rules! init_test { ($matrix: ident, $node_exec: ident, $size: expr) => { let (node_conf, mut node_exec) = new_node_engine(); let mut matrix = Matrix::new(node_conf, $size, $size); let $matrix = &mut matrix; let $node_exec = &mut node_exec; }; } #[macro_export] macro_rules! assert_float_eq { ($a:expr, $b:expr) => { if ($a - $b).abs() > 0.0001 { panic!( r#"assertion failed: `(left == right)` left: `{:?}`, right: `{:?}`"#, $a, $b ) } }; } #[macro_export] macro_rules! assert_fpair_eq { ($a:expr, $b:expr) => { if ($a.0 - $b.0).abs() > 0.0001 { panic!( r#"assertion failed: `(left.0 == right.0)` left: `{:?}`, right: `{:?}`"#, $a.0, $b.0 ) } if ($a.1 - $b.1).abs() > 0.0001 { panic!( r#"assertion failed: `(left.1 == right.1)` left: `{:?}`, right: `{:?}`"#, $a.1, $b.1 ) } }; } #[macro_export] macro_rules! assert_f3tupl_eq { ($a:expr, $b:expr) => { if ($a.0 - $b.0).abs() > 0.0001 { panic!( r#"assertion failed: `(left.0 == right.0)` left.0: `{:?}`, right.0: `{:?}`"#, $a.0, $b.0 ) } if ($a.1 - $b.1).abs() > 0.0001 { panic!( r#"assertion failed: `(left.1 == right.1)` left.1: `{:?}`, right.1: `{:?}`"#, $a.1, $b.1 ) } if ($a.2 - $b.2).abs() > 0.0001 { panic!( r#"assertion failed: `(left.2 == right.2)` left.2: `{:?}`, right.2: `{:?}`"#, $a.2, $b.2 ) } }; } #[macro_export] macro_rules! assert_vec_feq { ($vec:expr, $cmp_vec:expr) => { let cmp_vec = $cmp_vec; let res: Vec = $vec.iter().copied().collect(); for (i, (s, scmp)) in res.iter().zip(cmp_vec.iter()).enumerate() { if (s - scmp).abs() > 0.0001 { panic!( r#" table_left: {:?} table_right: {:?} assertion failed: `(left[{}] == right[{}])` left: `{:?}`, right: `{:?}`"#, &res[i..], &(cmp_vec[i..]), i, i, s, scmp ) } } }; } #[macro_export] macro_rules! assert_decimated_feq { ($vec:expr, $decimate:expr, $cmp_vec:expr) => { let cmp_vec = $cmp_vec; let res: Vec = $vec.iter().step_by($decimate).copied().collect(); for (i, (s, scmp)) in res.iter().zip(cmp_vec.iter()).enumerate() { if (s - scmp).abs() > 0.0001 { panic!( r#" table_left: {:?} table_right: {:?} assertion failed: `(left[{}] == right[{}])` left: `{:?}`, right: `{:?}`"#, &res[i..], &(cmp_vec[i..]), i, i, s, scmp ) } } }; } #[macro_export] macro_rules! assert_slope_feq { ($vec:expr, $cmp_vec:expr) => { let cmp_vec = $cmp_vec; let mut res: Vec = vec![]; let mut prev = 0.0; for (i, s) in $vec.iter().enumerate() { let delta = *s - prev; if i > 0 { res.push(delta); } prev = *s; } let res: Vec = res.iter().copied().collect(); for (i, (s, scmp)) in res.iter().zip(cmp_vec.iter()).enumerate() { if (s - scmp).abs() > 0.0001 { panic!( r#" table_left: {:?} table_right: {:?} assertion failed: `(left[{}] == right[{}])` left: `{:?}`, right: `{:?}`"#, &res[i..], &(cmp_vec[i..]), i, i, s, scmp ) } } }; } #[macro_export] macro_rules! assert_decimated_slope_feq { ($vec:expr, $decimate:expr, $cmp_vec:expr) => { let cmp_vec = $cmp_vec; let mut res: Vec = vec![]; let mut prev = 0.0; for (i, s) in $vec.iter().enumerate() { let delta = *s - prev; if i > 0 { res.push(delta); } prev = *s; } let res: Vec = res.iter().step_by($decimate).copied().collect(); for (i, (s, scmp)) in res.iter().zip(cmp_vec.iter()).enumerate() { if (s - scmp).abs() > 0.0001 { panic!( r#" table_left: {:?} table_right: {:?} assertion failed: `(left[{}] == right[{}])` left: `{:?}`, right: `{:?}`"#, &res[i..], &(cmp_vec[i..]), i, i, s, scmp ) } } }; } #[macro_export] macro_rules! assert_decimated_slope_feq_fine { ($vec:expr, $decimate:expr, $cmp_vec:expr) => { let cmp_vec = $cmp_vec; let mut res: Vec = vec![]; let mut prev = 0.0; for (i, s) in $vec.iter().enumerate() { let delta = *s - prev; if i > 0 { res.push(delta); } prev = *s; } let res: Vec = res.iter().step_by($decimate).copied().collect(); for (i, (s, scmp)) in res.iter().zip(cmp_vec.iter()).enumerate() { if (s - scmp).abs() > 0.0000001 { panic!( r#" table_left: {:?} table_right: {:?} assertion failed: `(left[{}] == right[{}])` left: `{:?}`, right: `{:?}`"#, &res[i..], &(cmp_vec[i..]), i, i, s, scmp ) } } }; } #[macro_export] macro_rules! assert_decimated_slope_feq_sfine { ($vec:expr, $decimate:expr, $cmp_vec:expr) => { let cmp_vec = $cmp_vec; let mut res: Vec = vec![]; let mut prev = 0.0; for (i, s) in $vec.iter().enumerate() { let delta = *s - prev; if i > 0 { res.push(delta); } prev = *s; } let res: Vec = res.iter().step_by($decimate).copied().collect(); for (i, (s, scmp)) in res.iter().zip(cmp_vec.iter()).enumerate() { if (s - scmp).abs() > 0.000000001 { panic!( r#" table_left: {:?} table_right: {:?} assertion failed: `(left[{}] == right[{}])` left: `{:?}`, right: `{:?}`"#, &res[i..], &(cmp_vec[i..]), i, i, s, scmp ) } } }; } #[macro_export] macro_rules! dump_table { ($vec:expr) => { for (i, s) in $vec.iter().enumerate() { println!("{:2} {:?}", i, s); } }; } #[allow(dead_code)] pub fn collect_signal_changes(inp: &[f32], thres: i64) -> Vec<(usize, i64)> { let mut idxs = vec![]; let mut last_sig = 0.0; for i in 0..inp.len() { if (inp[i] - last_sig).abs() > 0.1 { idxs.push((i, (inp[i] * 100.0).floor() as i64)); last_sig = inp[i]; } } let mut idxs_big = vec![]; for v in idxs.iter() { if v.1.abs() > thres { idxs_big.push(*v); } } return idxs_big; } #[allow(dead_code)] pub fn collect_non_zero(inp: &[f32]) -> Vec<(usize, usize)> { let mut idxs = vec![]; let mut start_idx = 0; let mut length = 0; for i in 0..inp.len() { if inp[i].abs() > 0.00001 { if length == 0 { start_idx = i; } length += 1; } else { if length > 0 { idxs.push((start_idx, length)); length = 0; } } } return idxs; } #[allow(dead_code)] pub fn collect_gates(inp: &[f32]) -> Vec<(usize, usize)> { let mut idxs = vec![]; let mut start_idx = 0; let mut length = 0; for i in 0..inp.len() { if inp[i].abs() > 0.1 { if length == 0 { start_idx = i; } length += 1; } else { if length > 0 { idxs.push((start_idx, length)); length = 0; } } } return idxs; } #[macro_export] macro_rules! assert_rmsmima { ($rms:expr, $b:expr) => { assert_f3tupl_eq!($rms, $b); }; } #[macro_export] macro_rules! assert_minmax_of_rms { ($rms:expr, $b:expr) => { let (_, min, max) = $rms; assert_fpair_eq!((min, max), $b); }; } #[allow(unused)] pub fn wait_params_smooth(ne: &mut NodeExecutor) { run_for_ms(ne, 15.0); } #[allow(unused)] pub fn node_pset_s(matrix: &mut Matrix, node: &str, instance: usize, parm: &str, set: i64) { let nid = NodeId::from_str(node).to_instance(instance); assert!(nid != NodeId::Nop); let p = nid.inp_param(parm).expect("param exists"); matrix.set_param(p, SAtom::setting(set)); } #[allow(unused)] pub fn pset_s(matrix: &mut Matrix, nid: NodeId, parm: &str, set: i64) { let p = nid.inp_param(parm).expect("param exists"); matrix.set_param(p, SAtom::setting(set)); } #[allow(unused)] pub fn pset_n(matrix: &mut Matrix, nid: NodeId, parm: &str, v_norm: f32) { let p = nid.inp_param(parm).expect("param exists"); matrix.set_param(p, SAtom::param(v_norm)); } #[allow(unused)] pub fn node_pset_n(matrix: &mut Matrix, node: &str, instance: usize, parm: &str, v_norm: f32) { let nid = NodeId::from_str(node).to_instance(instance); assert!(nid != NodeId::Nop); let p = nid.inp_param(parm).expect("param exists"); matrix.set_param(p, SAtom::param(v_norm)); } #[allow(unused)] pub fn pset_d(matrix: &mut Matrix, nid: NodeId, parm: &str, v_denorm: f32) { let p = nid.inp_param(parm).expect("param exists"); matrix.set_param(p, SAtom::param(p.norm(v_denorm))); } #[allow(unused)] pub fn node_pset_d(matrix: &mut Matrix, node: &str, instance: usize, parm: &str, v_denorm: f32) { let nid = NodeId::from_str(node).to_instance(instance); assert!(nid != NodeId::Nop); let p = nid.inp_param(parm).expect("param exists"); matrix.set_param(p, SAtom::param(p.norm(v_denorm))); } #[allow(unused)] pub fn pset_n_wait( matrix: &mut Matrix, ne: &mut NodeExecutor, nid: NodeId, parm: &str, v_norm: f32, ) { let p = nid.inp_param(parm).expect("param exists"); matrix.set_param(p, SAtom::param(v_norm)); wait_params_smooth(ne); } #[allow(unused)] pub fn pset_d_wait( matrix: &mut Matrix, ne: &mut NodeExecutor, nid: NodeId, parm: &str, v_denorm: f32, ) { let p = nid.inp_param(parm).expect("param exists"); matrix.set_param(p, SAtom::param(p.norm(v_denorm))); wait_params_smooth(ne); } #[allow(unused)] pub fn pset_mod(matrix: &mut Matrix, nid: NodeId, parm: &str, modamt: f32) { let p = nid.inp_param(parm).expect("param exists"); matrix.set_param_modamt(p, Some(modamt)); } #[allow(unused)] pub fn pset_mod_wait( matrix: &mut Matrix, ne: &mut NodeExecutor, nid: NodeId, parm: &str, modamt: f32, ) { let p = nid.inp_param(parm).unwrap(); matrix.set_param_modamt(p, Some(modamt)); wait_params_smooth(ne); } #[allow(dead_code)] pub fn save_wav(name: &str, buf: &[f32]) { let spec = hound::WavSpec { channels: 1, sample_rate: SAMPLE_RATE as u32, bits_per_sample: 16, sample_format: hound::SampleFormat::Int, }; let mut writer = hound::WavWriter::create(name, spec).unwrap(); for s in buf.iter() { let amp = i16::MAX as f32; writer.write_sample((amp * s) as i16).unwrap(); } } pub fn run_no_input( node_exec: &mut hexodsp::nodes::NodeExecutor, seconds: f32, ) -> (Vec, Vec) { run_realtime_no_input(node_exec, seconds, false) } #[allow(dead_code)] pub fn run_for_ms(node_exec: &mut hexodsp::nodes::NodeExecutor, ms: f32) -> (Vec, Vec) { run_realtime_no_input(node_exec, ms / 1000.0, false) } pub fn run_realtime_no_input( node_exec: &mut hexodsp::nodes::NodeExecutor, seconds: f32, sleep_a_bit: bool, ) -> (Vec, Vec) { node_exec.test_run(seconds, sleep_a_bit, vec![]) } pub fn calc_rms_mimax_each_ms(buf: &[f32], ms: f32) -> Vec<(f32, f32, f32)> { let ms_samples = ms * SAMPLE_RATE / 1000.0; let len_ms = ms_samples as usize; let mut idx = 0; let mut res = vec![]; loop { if (idx + len_ms) > buf.len() { break; } let mut max = -1000.0; let mut min = 1000.0; for s in buf[idx..(idx + len_ms)].iter() { max = s.max(max); min = s.min(min); } let rms: f32 = buf[idx..(idx + len_ms)].iter().map(|s: &f32| s * s).sum::() / ms_samples; res.push((rms, min, max)); idx += len_ms; } res } #[allow(dead_code)] pub fn run_and_undersample( node_exec: &mut hexodsp::nodes::NodeExecutor, run_len_ms: f32, samples: usize, ) -> Vec { let (out_l, _out_r) = run_no_input(node_exec, run_len_ms / 1000.0); let sample_interval = out_l.len() / samples; let mut out_samples = vec![]; for i in 0..samples { let idx = i * sample_interval; out_samples.push(out_l[idx]); } out_samples } #[allow(dead_code)] pub fn run_and_get_each_rms_mimax( node_exec: &mut hexodsp::nodes::NodeExecutor, len_ms: f32, ) -> Vec<(f32, f32, f32)> { let (out_l, _out_r) = run_no_input(node_exec, (len_ms * 3.0) / 1000.0); calc_rms_mimax_each_ms(&out_l[..], len_ms) } /// Get the rms of a specified amount of time 'len_ms' and take samples /// of RMS, Min and Max each 'sample_ms' (of the same length). /// /// * 'len_ms' - complete runtime /// * 'sample_ms' - length of each rms sample #[allow(dead_code)] pub fn run_and_get_rms_mimax( node_exec: &mut hexodsp::nodes::NodeExecutor, len_ms: f32, sample_ms: f32, ) -> Vec<(f32, f32, f32)> { let (out_l, _out_r) = run_no_input(node_exec, len_ms / 1000.0); calc_rms_mimax_each_ms(&out_l[..], sample_ms) } #[allow(dead_code)] pub fn run_and_get_first_rms_mimax( node_exec: &mut hexodsp::nodes::NodeExecutor, len_ms: f32, ) -> (f32, f32, f32) { let (out_l, _out_r) = run_no_input(node_exec, (len_ms * 3.0) / 1000.0); let rms_mimax = calc_rms_mimax_each_ms(&out_l[..], len_ms); rms_mimax[0] } #[allow(unused)] pub fn run_and_get_l_rms_mimax( node_exec: &mut hexodsp::nodes::NodeExecutor, len_ms: f32, ) -> (f32, f32, f32) { let (out_l, _out_r) = run_no_input(node_exec, (len_ms * 3.0) / 1000.0); let rms_mimax = calc_rms_mimax_each_ms(&out_l[..], len_ms); rms_mimax[1] } #[allow(unused)] 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; } println!("SAMPLES: {}", out_l.len()); 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 } #[allow(unused)] pub fn run_and_get_fft4096( node_exec: &mut hexodsp::nodes::NodeExecutor, thres: u32, offs_ms: f32, ) -> Vec<(u16, u32)> { let min_samples_for_fft = 4096.0; let offs_samples = (offs_ms * (SAMPLE_RATE / 1000.0)).ceil(); let min_len_samples = offs_samples // 2.0 * for safety margin + 2.0 * min_samples_for_fft; let run_len_s = min_len_samples / SAMPLE_RATE; let (mut out_l, _out_r) = run_no_input(node_exec, run_len_s); fft_thres_at_ms(&mut out_l[..], FFT::F4096, thres, offs_ms) } #[allow(unused)] pub fn run_and_get_fft4096_2( node_exec: &mut hexodsp::nodes::NodeExecutor, thres: u32, ) -> Vec<(u16, u32)> { let min_samples_for_fft = 4096.0; let min_len_samples = 2.0 * min_samples_for_fft; let run_len_s = min_len_samples / SAMPLE_RATE; let (mut out_l, _out_r) = run_no_input(node_exec, run_len_s); fft(&mut out_l[..], FFT::F4096, thres) } /// Minimal 'each_ms' is 47ms, because each spectrum takes that time. #[allow(unused)] pub fn run_fft_spectrum_each_47ms( node_exec: &mut hexodsp::nodes::NodeExecutor, thres: u32, count: usize, ) -> Vec> { let mut out = vec![]; for _ in 0..count { let min_samples_for_fft = 1024.0; let min_len_samples = 2.0 * min_samples_for_fft; let run_len_s = min_len_samples / SAMPLE_RATE; let offs_ms = (run_len_s * 1000.0); let (mut out_l, _out_r) = run_no_input(node_exec, run_len_s); out.push(fft(&mut out_l[..], FFT::F1024, thres)); } out } #[allow(unused)] pub fn calc_exp_avg_buckets4096(fft: &[(u16, u32)]) -> Vec<(u16, u32)> { let mut avg = vec![]; let mut last_n = [0; 256]; let mut p = 0; let mut cur_len = 2; for (i, (fq, lvl)) in fft.iter().enumerate() { last_n[p] = *lvl; p += 1; if p >= cur_len { avg.push(( *fq, last_n.iter().take(cur_len).map(|x| *x).sum::() / (cur_len as u32), )); p = 0; } if i % 16 == 0 { cur_len += 2; if cur_len > last_n.len() { cur_len = last_n.len(); } //d// println!("len={}", cur_len); } } avg } #[allow(unused)] pub fn avg_fft_freqs(round_by: f32, ranges: &[u16], fft: &[(u16, u32)]) -> Vec<(u16, u32)> { let mut from = 0; let mut out = vec![]; for rng in ranges.iter() { out.push((from, ((avg_fft_range(from, *rng, fft) / round_by).floor() * round_by) as u32)); from = *rng; } out } #[allow(unused)] pub fn avg_fft_range(from_freq: u16, to_freq: u16, fft: &[(u16, u32)]) -> f32 { let mut count = 0; let mut sum = 0; for (fq, lvl) in fft.iter() { if from_freq <= *fq && *fq < to_freq { sum += *lvl; count += 1; } } sum as f32 / count as f32 } #[allow(unused)] pub fn run_and_get_fft512( node_exec: &mut hexodsp::nodes::NodeExecutor, thres: u32, offs_ms: f32, ) -> Vec<(u16, u32)> { let min_samples_for_fft = 512.0; let offs_samples = (offs_ms * (SAMPLE_RATE / 1000.0)).ceil(); let min_len_samples = offs_samples // 2.0 * for safety margin + 2.0 * min_samples_for_fft; let run_len_s = min_len_samples / SAMPLE_RATE; let (mut out_l, _out_r) = run_no_input(node_exec, run_len_s); fft_thres_at_ms(&mut out_l[..], FFT::F512, thres, offs_ms) } #[allow(unused)] pub fn run_and_get_fft4096_now( node_exec: &mut hexodsp::nodes::NodeExecutor, thres: u32, ) -> Vec<(u16, u32)> { let min_samples_for_fft = 4096.0 * 1.5; // 1.5 for some extra margin let run_len_s = min_samples_for_fft / SAMPLE_RATE; let (mut out_l, _out_r) = run_no_input(node_exec, run_len_s); fft_thres_at_ms(&mut out_l[..], FFT::F4096, thres, 0.0) } /// Takes about 1 second of audio to average 10 ffts #[allow(unused)] pub fn run_and_get_avg_fft4096_now( node_exec: &mut hexodsp::nodes::NodeExecutor, thres: u32, ) -> Vec<(u16, u32)> { let min_samples_for_fft = 4096.0 * 1.5; // 1.5 for some extra margin let run_len_s = min_samples_for_fft / SAMPLE_RATE; let (mut out_l, _out_r) = run_no_input(node_exec, run_len_s); let mut data = fft_thres_at_ms(&mut out_l[..], FFT::F4096, 0, 0.0); for _ in 0..9 { let (mut out_l, _out_r) = run_no_input(node_exec, run_len_s); let out = fft_thres_at_ms(&mut out_l[..], FFT::F4096, 0, 0.0); for (x, d) in out.iter().zip(data.iter_mut()) { d.1 += x.1; } } for d in data.iter_mut() { d.1 /= 10; } data.iter().filter(|d| d.1 >= thres).copied().collect() } #[allow(unused)] pub enum FFT { F16, F32, F64, F128, F512, F1024, F2048, F4096, F8192, F16384, F65535, } impl FFT { pub fn size(&self) -> usize { match self { FFT::F16 => 16, FFT::F32 => 32, FFT::F64 => 64, FFT::F128 => 128, FFT::F512 => 512, FFT::F1024 => 1024, FFT::F2048 => 2048, FFT::F4096 => 4096, FFT::F8192 => 8192, FFT::F16384 => 16384, FFT::F65535 => 65535, } } } pub fn fft_thres_at_ms(buf: &mut [f32], size: FFT, amp_thres: u32, ms_idx: f32) -> Vec<(u16, u32)> { let ms_sample_offs = ms_idx * (SAMPLE_RATE / 1000.0); let fft_nbins = size.size(); let len = fft_nbins; let idx = ms_sample_offs as usize; if (idx + len) > buf.len() { return vec![]; } fft(&mut buf[idx..(idx + len)], size, amp_thres) } pub fn fft(buf: &mut [f32], size: FFT, amp_thres: u32) -> Vec<(u16, u32)> { let len = size.size(); let mut res = vec![]; if len > buf.len() { return res; } // Hann window: for (i, s) in buf[0..len].iter_mut().enumerate() { let w = 0.5 * (1.0 - ((2.0 * std::f32::consts::PI * i as f32) / (len as f32 - 1.0)).cos()); *s *= w; } use rustfft::{num_complex::Complex, FftPlanner}; let mut complex_buf = buf.iter().map(|s| Complex { re: *s, im: 0.0 }).collect::>>(); let mut p = FftPlanner::::new(); let fft = p.plan_fft_forward(len); fft.process(&mut complex_buf[0..len]); let amplitudes: Vec<_> = complex_buf[0..len].iter().map(|c| c.norm() as u32).collect(); // println!("fft: {:?}", &complex_buf[0..len]); for (i, amp) in amplitudes.iter().enumerate() { if *amp >= amp_thres { let freq = (i as f32 * SAMPLE_RATE) / len as f32; if freq > 22050.0 { // no freqency images above nyquist... continue; } // println!("{:6.0} {}", freq, *amp); res.push((freq.round() as u16, *amp)); } } res }