diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..d3e136d --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,538 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in library 'hexodsp'", + "cargo": { + "args": [ + "test", + "--no-run", + "--lib", + "--package=hexodsp" + ], + "filter": { + "name": "hexodsp", + "kind": "lib" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'cpal_demo_node_api'", + "cargo": { + "args": [ + "build", + "--example=cpal_demo_node_api", + "--package=hexodsp" + ], + "filter": { + "name": "cpal_demo_node_api", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'cpal_demo_node_api'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=cpal_demo_node_api", + "--package=hexodsp" + ], + "filter": { + "name": "cpal_demo_node_api", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example 'jack_demo_node_api'", + "cargo": { + "args": [ + "build", + "--example=jack_demo_node_api", + "--package=hexodsp" + ], + "filter": { + "name": "jack_demo_node_api", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in example 'jack_demo_node_api'", + "cargo": { + "args": [ + "test", + "--no-run", + "--example=jack_demo_node_api", + "--package=hexodsp" + ], + "filter": { + "name": "jack_demo_node_api", + "kind": "example" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug integration test 'basics'", + "cargo": { + "args": [ + "test", + "--no-run", + "--test=basics", + "--package=hexodsp" + ], + "filter": { + "name": "basics", + "kind": "test" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug integration test 'modamt'", + "cargo": { + "args": [ + "test", + "--no-run", + "--test=modamt", + "--package=hexodsp" + ], + "filter": { + "name": "modamt", + "kind": "test" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug integration test 'node_ad'", + "cargo": { + "args": [ + "test", + "--no-run", + "--test=node_ad", + "--package=hexodsp" + ], + "filter": { + "name": "node_ad", + "kind": "test" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug integration test 'node_allp'", + "cargo": { + "args": [ + "test", + "--no-run", + "--test=node_allp", + "--package=hexodsp" + ], + "filter": { + "name": "node_allp", + "kind": "test" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug integration test 'node_bosc'", + "cargo": { + "args": [ + "test", + "--no-run", + "--test=node_bosc", + "--package=hexodsp" + ], + "filter": { + "name": "node_bosc", + "kind": "test" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug integration test 'node_comb'", + "cargo": { + "args": [ + "test", + "--no-run", + "--test=node_comb", + "--package=hexodsp" + ], + "filter": { + "name": "node_comb", + "kind": "test" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug integration test 'node_cqnt'", + "cargo": { + "args": [ + "test", + "--no-run", + "--test=node_cqnt", + "--package=hexodsp" + ], + "filter": { + "name": "node_cqnt", + "kind": "test" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug integration test 'node_delay'", + "cargo": { + "args": [ + "test", + "--no-run", + "--test=node_delay", + "--package=hexodsp" + ], + "filter": { + "name": "node_delay", + "kind": "test" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug integration test 'node_goertzel'", + "cargo": { + "args": [ + "test", + "--no-run", + "--test=node_goertzel", + "--package=hexodsp" + ], + "filter": { + "name": "node_goertzel", + "kind": "test" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug integration test 'node_map'", + "cargo": { + "args": [ + "test", + "--no-run", + "--test=node_map", + "--package=hexodsp" + ], + "filter": { + "name": "node_map", + "kind": "test" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug integration test 'node_mix3'", + "cargo": { + "args": [ + "test", + "--no-run", + "--test=node_mix3", + "--package=hexodsp" + ], + "filter": { + "name": "node_mix3", + "kind": "test" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug integration test 'node_mux9'", + "cargo": { + "args": [ + "test", + "--no-run", + "--test=node_mux9", + "--package=hexodsp" + ], + "filter": { + "name": "node_mux9", + "kind": "test" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug integration test 'node_noise'", + "cargo": { + "args": [ + "test", + "--no-run", + "--test=node_noise", + "--package=hexodsp" + ], + "filter": { + "name": "node_noise", + "kind": "test" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug integration test 'node_pverb'", + "cargo": { + "args": [ + "test", + "--no-run", + "--test=node_pverb", + "--package=hexodsp" + ], + "filter": { + "name": "node_pverb", + "kind": "test" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug integration test 'node_quant'", + "cargo": { + "args": [ + "test", + "--no-run", + "--test=node_quant", + "--package=hexodsp" + ], + "filter": { + "name": "node_quant", + "kind": "test" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug integration test 'node_rndwk'", + "cargo": { + "args": [ + "test", + "--no-run", + "--test=node_rndwk", + "--package=hexodsp" + ], + "filter": { + "name": "node_rndwk", + "kind": "test" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug integration test 'node_sampl'", + "cargo": { + "args": [ + "test", + "--no-run", + "--test=node_sampl", + "--package=hexodsp" + ], + "filter": { + "name": "node_sampl", + "kind": "test" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug integration test 'node_sfilter'", + "cargo": { + "args": [ + "test", + "--no-run", + "--test=node_sfilter", + "--package=hexodsp" + ], + "filter": { + "name": "node_sfilter", + "kind": "test" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug integration test 'node_smap'", + "cargo": { + "args": [ + "test", + "--no-run", + "--test=node_smap", + "--package=hexodsp" + ], + "filter": { + "name": "node_smap", + "kind": "test" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug integration test 'node_test'", + "cargo": { + "args": [ + "test", + "--no-run", + "--test=node_test", + "--package=hexodsp" + ], + "filter": { + "name": "node_test", + "kind": "test" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug integration test 'node_tslfo'", + "cargo": { + "args": [ + "test", + "--no-run", + "--test=node_tslfo", + "--package=hexodsp" + ], + "filter": { + "name": "node_tslfo", + "kind": "test" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug integration test 'node_vosc'", + "cargo": { + "args": [ + "test", + "--no-run", + "--test=node_vosc", + "--package=hexodsp" + ], + "filter": { + "name": "node_vosc", + "kind": "test" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug integration test 'quant'", + "cargo": { + "args": [ + "test", + "--no-run", + "--test=quant", + "--package=hexodsp" + ], + "filter": { + "name": "quant", + "kind": "test" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + } + ] +} \ No newline at end of file diff --git a/examples/cpal_demo_node_api.rs b/examples/cpal_demo_node_api.rs index daac2e8..2bed2da 100644 --- a/examples/cpal_demo_node_api.rs +++ b/examples/cpal_demo_node_api.rs @@ -142,7 +142,7 @@ where let channels = config.channels as usize; node_exec.set_sample_rate(sample_rate); - + let input_bufs = [[0.0; hexodsp::dsp::MAX_BLOCK_SIZE]; 2]; let mut outputbufs = [[0.0; hexodsp::dsp::MAX_BLOCK_SIZE]; 2]; diff --git a/src/dsp/goertzel.rs b/src/dsp/goertzel.rs new file mode 100644 index 0000000..714a48b --- /dev/null +++ b/src/dsp/goertzel.rs @@ -0,0 +1,62 @@ +// Copyright (c) 2022 theloni-monk +// This file is a part of HexoDSP. Released under GPL-3.0-or-later. +// See README.md and COPYING for details. +const PI: f32 = std::f32::consts::PI; + +#[derive(Debug, Clone, Default)] +pub struct GoertzelParams {} +#[derive(Debug, Clone, Default)] +pub struct Goertzel { + pub target_freq: f32, + pub coeff: f32, + + q0: f32, + q1: f32, + q2: f32, +} +const DEFAULT_BUFFSIZE: usize = 100; +// Calculates an individual term of the Discrete Fourier Series +// implementation with notes taken from https://www.embedded.com/the-goertzel-algorithm/ +impl Goertzel { + pub fn new() -> Self { + let mut s: Goertzel = Default::default(); + s.setCoeff(880.0, DEFAULT_BUFFSIZE, 44100.0); + (s.q0, s.q1, s.q2) = (0.0, 0.0, 0.0); + s + } + + #[inline] + pub fn new_with(tfreq: f32, buffsize: usize, srate: f32) -> Self { + let mut s = Self::new(); + s.setCoeff(tfreq, buffsize, srate); + s + } + + pub fn reset(&mut self) { + self.q0 = 0.0; + self.q1 = 0.0; + self.q2 = 0.0; + } + + pub fn setCoeff(&mut self, tfreq: f32, buffsize: usize, srate: f32) { + self.target_freq = tfreq; + let k = (0.5 * ((buffsize as f32 * self.target_freq) / srate as f32)).floor() as f32; + let w = (2.0 * PI * k / buffsize as f32) as f32; + let c = f32::cos(w); + self.coeff = 2.0 * c; + } + + #[inline] + pub fn tick(&mut self, input: f32) -> f32 { + let x0 = input; + + self.q0 = self.coeff * self.q1 - self.q2 + x0; + self.q2 = self.q1; + self.q1 = self.q0; + + let mag_squared = + (self.q1.powi(2) + (self.q2.powi(2)) - (self.q1 * self.q2 * self.coeff)) as f32; + + f32::sqrt(mag_squared) / 100.0 + } +} diff --git a/src/dsp/mod.rs b/src/dsp/mod.rs index c5b0975..f65bb5c 100644 --- a/src/dsp/mod.rs +++ b/src/dsp/mod.rs @@ -532,6 +532,8 @@ mod node_test; #[allow(non_upper_case_globals)] mod node_tseq; #[allow(non_upper_case_globals)] +mod node_goertzel; +pub mod goertzel; mod node_tslfo; #[allow(non_upper_case_globals)] mod node_vosc; @@ -597,6 +599,7 @@ use node_out::Out; use node_pverb::PVerb; use node_quant::Quant; use node_rndwk::RndWk; +use node_goertzel::Gz3Filt; use node_sampl::Sampl; use node_sfilter::SFilter; use node_sin::Sin; @@ -1482,6 +1485,16 @@ 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], + + goertzel => Gz3Filt UIType::Generic UICategory::Signal + (0 inp n_id d_id r_id f_def stp_d -1.0, 1.0, 0.0) + (1 freq1 n_pit d_pit r_fq f_freq stp_d 0.0, 20000.0, 220.0) + (2 freq2 n_pit d_pit r_fq f_freq stp_d 0.0, 20000.0, 330.0) + (3 freq3 n_pit d_pit r_fq f_freq stp_d 0.0, 20000.0, 440.0) + (4 latency n_pit d_pit r_fq f_ms stp_d 256.0, 65536.0, 2048.0) + (5 gain n_ogin d_ogin r_id f_def stp_d 0.0, 1.0, 1.0) + [0 sigf1][1 sigf2][2 sigf3], + 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} diff --git a/src/dsp/node_goertzel.rs b/src/dsp/node_goertzel.rs new file mode 100644 index 0000000..4e9f993 --- /dev/null +++ b/src/dsp/node_goertzel.rs @@ -0,0 +1,203 @@ +// Copyright (c) 2022 theloni-monk +// This file is a part of HexoDSP. Released under GPL-3.0-or-later. +// See README.md and COPYING for details. + +use crate::dsp::goertzel::*; +use crate::dsp::{DspNode, LedPhaseVals, NodeContext, NodeId, ProcBuf, SAtom}; +use crate::nodes::{NodeAudioContext, NodeExecContext}; + +#[derive(Debug, Clone)] +pub struct Gz3Filt { + g1: Goertzel, + g2: Goertzel, + g3: Goertzel, + + ofreq1: f32, + ofreq2: f32, + ofreq3: f32, + + srate: f32, + olatency: f32, // how many samples before recomputing goertzel on new window + frames_processed: usize, + + tempf1: f32, + tempf2: f32, + tempf3: f32, + + ogain: f32, +} + +impl Gz3Filt { + pub fn new(_nid: &NodeId) -> Self { + Self { + g1: Goertzel::new(), + g2: Goertzel::new(), + g3: Goertzel::new(), + + ofreq1: 220.0, + ofreq2: 330.0, + ofreq3: 440.0, + + olatency: 2048.0, + frames_processed: 0, + + tempf1: 0.0, + tempf2: 0.0, + tempf3: 0.0, + + srate: 44100.0, + ogain: -2.0, // value that can't be set by the user + } + } + pub const inp: &'static str = "GzFilt inp\nSignal input\nRange: (-1..1)\n"; + pub const freq1: &'static str = "GzFilt freq\nFrequency to extract.\nRange: (20..20000)\n"; + pub const freq2: &'static str = "GzFilt freq\nFrequency to extract.\nRange: (20..20000)\n"; + pub const freq3: &'static str = "GzFilt freq\nFrequency to extract.\nRange: (20..20000)\n"; + pub const latency : &'static str = + "GzFilt latency\n How many samples to average the frequency strength over. Higher is more accurate but less time-specific\nRange: (256..65536)\n"; + pub const gain: &'static str = "GzFilt gain\nFilter gain.\nRange: (0..1)\n"; + pub const sigf1: &'static str = + "GzFilt sig\nFiltered signal output with respect to the first frequency.\nRange: (-1..1)\n"; + pub const sigf2: &'static str = "GzFilt sig\nFiltered signal output with respect to the second frequency.\nRange: (-1..1)\n"; + pub const sigf3: &'static str = + "GzFilt sig\nFiltered signal output with respect to the third frequency.\nRange: (-1..1)\n"; + + pub const DESC: &'static str = r#"Goertzel Algorithm + +This is the implementation of a goertzel algorithm for extraction of a particular frequency. It is basically a fine bandpass around a specific frequency. +"#; + pub const HELP: &'static str = r#"Gz3Filt - Goertzel Filter (Fine Bandpass) +This is the implementation of a goertzel algorithm for extraction of a particular frequency. It is basically a fine bandpass around a specific frequency. + +It can be used as a frequency follower to extract the amplitudes of 3 different frequencies from a signal. +"#; +} +const DEFAULT_BUFFSIZE: usize = 1000; //will get overwritten on first frame anyways +impl DspNode for Gz3Filt { + fn outputs() -> usize { + 1 + } + + fn set_sample_rate(&mut self, srate: f32) { + self.srate = srate; + self.g1.setCoeff(self.ofreq1, DEFAULT_BUFFSIZE, srate); + self.g2.setCoeff(self.ofreq2, DEFAULT_BUFFSIZE, srate); + self.g3.setCoeff(self.ofreq3, DEFAULT_BUFFSIZE, srate); + self.reset(); + } + + fn reset(&mut self) { + self.tempf1 = 0.0; + self.g1.reset(); + self.g2.reset(); + self.g3.reset(); + } + + #[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_idx}; + + // aquiring params from context + let inp = inp::Gz3Filt::inp(inputs); + + let latency = inp::Gz3Filt::latency(inputs); + + let gain = inp::Gz3Filt::gain(inputs); + //let out_i = out_idx::Gz3Filt::sigf1(); //TODO: put this into out_idx crate + let outf1; + let mut outf2; + let outf3; + (outf1, outf2) = outputs.split_at_mut(1); + (outf2, outf3) = outf2.split_at_mut(1); //assumes channels are same size + + let outf1 = &mut outf1[0]; + let outf2 = &mut outf2[0]; + let outf3 = &mut outf3[0]; + + + + let freq1 = inp::Gz3Filt::freq1(inputs); + let freq2 = inp::Gz3Filt::freq2(inputs); + let freq3 = inp::Gz3Filt::freq3(inputs); + + // clamping parameters + let cfreq1 = denorm::Gz3Filt::freq1(freq1, 0); + let cfreq1 = cfreq1.clamp(0.0, 22000.0); + let cfreq2 = denorm::Gz3Filt::freq2(freq2, 0); + let cfreq2 = cfreq2.clamp(0.0, 22000.0); + let cfreq3 = denorm::Gz3Filt::freq3(freq3, 0); + let cfreq3 = cfreq3.clamp(0.0, 22000.0); + + let clatency = denorm::Gz3Filt::latency(latency, 0); + let clatency = clatency.clamp(256.0, 65536.0); + + let cgain = denorm::Gz3Filt::gain(gain, 0); + + let paramschanged = (cfreq1 - self.ofreq1).abs() > 0.0001 + || (cfreq2 - self.ofreq2).abs() > 0.0001 + || (cfreq3 - self.ofreq3).abs() > 0.0001 + || (cgain - self.ogain).abs() > 0.0001 + || (clatency - self.olatency).abs() > 1.0; + if paramschanged { + // recalculate coeffs of all in the cascade + self.g1.target_freq = cfreq1; + self.ofreq1 = cfreq1; + self.g2.target_freq = cfreq2; + self.ofreq2 = cfreq2; + self.g3.target_freq = cfreq3; + self.ofreq3 = cfreq3; + + self.g1.reset(); + self.g2.reset(); + self.g3.reset(); + + self.olatency = clatency; + + self.ogain = cgain; + } + + // recompute param based on buffer size + self.g1.setCoeff(cfreq1, ctx.nframes(), self.srate); + self.g2.setCoeff(cfreq2, ctx.nframes(), self.srate); + self.g3.setCoeff(cfreq3, ctx.nframes(), self.srate); + + // latency winds up rounding to int multiple of buffer size because thats simpler + + let mut s1 = 0.0; + let mut s2: f32; + let mut s3: f32; + for frame in 0..ctx.nframes() { + let s = inp.read(frame); + //WRITEME: split into 3 signals instead of summing + s1 = self.g1.tick(s); + s2 = self.g2.tick(s); + s3 = self.g3.tick(s); + self.frames_processed += 1; + if self.frames_processed as f32 > self.olatency { + self.tempf1 = s1; //only updates after a calculation on a new window + self.tempf2 = s2; //only updates after a calculation on a new window + self.tempf3 = s3; //only updates after a calculation on a new window + + self.frames_processed = 0; + self.g1.reset(); + self.g2.reset(); + self.g3.reset(); + } + let gain = denorm::Gz3Filt::gain(gain, frame); + outf1.write(frame, self.tempf1 * gain); + outf2.write(frame, self.tempf2 * gain); + outf3.write(frame, self.tempf3 * gain); + } + + ctx_vals[0].set(s1); //not sure what this does + } +} diff --git a/tests/node_goertzel.rs b/tests/node_goertzel.rs new file mode 100644 index 0000000..f82e666 --- /dev/null +++ b/tests/node_goertzel.rs @@ -0,0 +1,60 @@ +// Copyright (c) 2022 theloni-monk +// 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::*; + +//WRITEME: more tests +fn setup_gnode_matrix() -> (Matrix, NodeExecutor) { + let (node_conf, node_exec) = new_node_engine(); + let mut matrix = Matrix::new(node_conf, 3, 3); + + let goertzel = NodeId::Gz3Filt(0); + let sin = NodeId::Sin(0); + let out = NodeId::Out(0); + + matrix.place( + 0, + 0, + Cell::empty(sin) + // Top TopLeft BottomLeft + .input(None, None, None) + // TopRight BottomRight Bottom + .out(None, sin.out("sig"), None), + ); + + matrix.place( + 1, + 0, + Cell::empty(goertzel) + .input(None, goertzel.inp("inp"), None) + .out(None, goertzel.out("sig"), None), + ); + + matrix.place(2, 1, Cell::empty(out).input(None, out.inp("ch1"), None)); + + matrix.sync().unwrap(); + + (matrix, node_exec) +} + +//WRITEME: expect signal to be > 0.2 for 880, change goertzel param to 600 target -> expect it to be < 0.2 +#[test] +fn check_node_goertzel() { + let (matrix, mut node_exec) = setup_gnode_matrix(); + + let fft = run_and_get_fft4096_now(&mut node_exec, 500); + eprintln!("FFT: {:#?}", fft); + + let (out_l, _) = run_for_ms(&mut node_exec, 25.0); + //FIXME: out_l empty + let rms_minmax = calc_rms_mimax_each_ms(&out_l[..], 10.0); + eprintln!("RMS: {:?}", rms_minmax); + assert!(rms_minmax[1].2 - rms_minmax[1].1 < 0.01); // the output should be const for const freq input + + // nice formatted debug printing, execute with: + // cargo test goert -- --nocapture + assert!(fft[0].0 == 0); // it should be a const value so a dom frequency should be 0 + +}