@@ 1,99 0,0 @@
-# This file is automatically @generated by Cargo.
-# It is not intended for manual editing.
-version = 3
-
-[[package]]
-name = "autocfg"
-version = "1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
-
-[[package]]
-name = "fft"
-version = "0.1.0"
-dependencies = [
- "realfft",
-]
-
-[[package]]
-name = "num-complex"
-version = "0.4.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "02e0d21255c828d6f128a1e41534206671e8c3ea0c62f32291e808dc82cff17d"
-dependencies = [
- "num-traits",
-]
-
-[[package]]
-name = "num-integer"
-version = "0.1.45"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
-dependencies = [
- "autocfg",
- "num-traits",
-]
-
-[[package]]
-name = "num-traits"
-version = "0.2.15"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
-dependencies = [
- "autocfg",
-]
-
-[[package]]
-name = "primal-check"
-version = "0.3.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9df7f93fd637f083201473dab4fee2db4c429d32e55e3299980ab3957ab916a0"
-dependencies = [
- "num-integer",
-]
-
-[[package]]
-name = "realfft"
-version = "3.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "953d9f7e5cdd80963547b456251296efc2626ed4e3cbf36c869d9564e0220571"
-dependencies = [
- "rustfft",
-]
-
-[[package]]
-name = "rustfft"
-version = "6.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e17d4f6cbdb180c9f4b2a26bbf01c4e647f1e1dea22fe8eb9db54198b32f9434"
-dependencies = [
- "num-complex",
- "num-integer",
- "num-traits",
- "primal-check",
- "strength_reduce",
- "transpose",
- "version_check",
-]
-
-[[package]]
-name = "strength_reduce"
-version = "0.2.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fe895eb47f22e2ddd4dabc02bce419d2e643c8e3b585c78158b349195bc24d82"
-
-[[package]]
-name = "transpose"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e6522d49d03727ffb138ae4cbc1283d3774f0d10aa7f9bf52e6784c45daf9b23"
-dependencies = [
- "num-integer",
- "strength_reduce",
-]
-
-[[package]]
-name = "version_check"
-version = "0.9.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
@@ 1,3 1,11 @@
+use std::sync::{RwLock, Arc};
+
+use audio_backend::node_interface::{Metadata, Processor};
+use blockfree_chan::{BlockFreeTx, blockfree_channel};
+use num::{Float, Signed, FromPrimitive};
+
+pub use audio_backend::Backend;
+pub use blockfree_chan::BlockFreeRx;
///Helper functions and their data for running and configuring several overlapping ffts
use realfft::num_complex::Complex;
@@ 12,15 20,15 @@ pub enum Windows {
}
-pub fn make_window(len: usize, window: Windows) -> Vec<f32> {
+pub fn make_window<T: Float>(len: usize, window: Windows) -> Vec<T> {
(0..len).map(|i| {
match window {
- Windows::Rect => 1.0,
- Windows::Hann => 0.5*(1.0-(2.0*std::f32::consts::PI*i as f32/len as f32).cos()),
+ Windows::Rect => T::one(),
+ Windows::Hann => T::from(0.5*(1.0-(2.0*std::f32::consts::PI*i as f32/len as f32).cos())).unwrap(),
Windows::Hamming => {
let a0 = 25.0/46.0;
let a1 = 1.0-a0;
- a0-(a1*2.0*std::f32::consts::PI*i as f32/len as f32).cos()
+ T::from(a0-(a1*2.0*std::f32::consts::PI*i as f32/len as f32).cos()).unwrap()
},
Windows::BlackmanNutall => {
let x = 2.0*std::f32::consts::PI*i as f32/len as f32;
@@ 28,7 36,7 @@ pub fn make_window(len: usize, window: Windows) -> Vec<f32> {
let a1 = 0.4891775;
let a2 = 0.1365995;
let a3 = 0.0106411;
- a0-a1*(x).cos()+a2*(2.0*x.cos())-a3*(3.0*x.cos())
+ T::from(a0-a1*(x).cos()+a2*(2.0*x.cos())-a3*(3.0*x.cos())).unwrap()
},
}
}).collect()
@@ 42,69 50,117 @@ pub struct FftSpec {
}
///Data needed for maintaining an fft stream over some audio data.
-pub struct FftState {
- fft: std::sync::Arc<dyn realfft::RealToComplex<f32>>,
- in_buf: Vec<f32>,
+pub struct FftState<T: Float> {
+ fft: std::sync::Arc<dyn realfft::RealToComplex<T>>,
+ in_buf: Vec<T>,
//separate from the in_buf so we can have overlaps. Since the fft is stateless we can just run
//the same fft for every window, using different slices from the running_buf
- running_buf: Vec<f32>,
+ running_buf: Vec<T>,
//Window function, as a table.
- window: Vec<f32>,
- pub spectrum: Vec<Complex<f32>>,
- scratch_buf: Vec<Complex<f32>>,
+ window: Vec<T>,
+ pub spectrum: Vec<Complex<T>>,
+ scratch_buf: Vec<Complex<T>>,
idx: u32,
pub spec: FftSpec,
//determines if the fft data has been read before.
pub stale: bool,
+ bins_tx: BlockFreeTx<Vec<T>>,
+ vec: Vec<T>,
}
-impl FftState {
+impl<T: Float+std::fmt::Debug+Send+Sync+Signed+FromPrimitive+'static> FftState<T> {
//Initialize the state for an fft given a spec and a window type.
- pub fn new(spec: FftSpec, window: Windows) -> Self {
- let fft = realfft::RealFftPlanner::<f32>::new()
- .plan_fft_forward(spec.num_bins as usize);
+ pub fn new(spec: FftSpec, window: Windows, bins_tx: blockfree_chan::BlockFreeTx<Vec<T>>) -> Self {
+ let fft = realfft::RealFftPlanner::<T>::new()
+ .plan_fft_forward(spec.num_bins as usize);
let spectrum = fft.make_output_vec();
let in_buf = fft.make_input_vec();
let scratch_buf = fft.make_scratch_vec();
let running_buf = in_buf.clone();
+ let vec = vec![T::zero(); spec.num_bins as usize/2];
Self {
fft,
idx: 0,
spectrum,
in_buf,
+ bins_tx,
running_buf,
scratch_buf,
window: make_window(spec.num_bins as usize, window),
spec,
+ vec,
stale: true,
}
}
}
-impl FftState {
- pub fn reset(&mut self) {
+impl<T: Float+Send+Sync> Processor for FftState<T> {
+ type SampleType = T;
+
+ fn process(&mut self, audio: &mut audio_backend::node_interface::Audio<Self::SampleType>) {
+ for frame in audio.input_frames() {
+ for samp in frame.take(1) {
+ let buf_len = self.running_buf.len() as u32;
+ self.running_buf[self.idx as usize] = *samp;
+
+ let new_idx = (self.idx+1)%buf_len;
+ if new_idx%(buf_len/self.spec.overlap) == 0 {
+ self.in_buf.iter_mut()
+ .zip(self.running_buf.iter().cycle().skip(self.idx as usize))
+ .zip(self.window.iter())
+ .for_each(|((old, new), window_cf)| {
+ let windowed = *new**window_cf;
+ *old = windowed;
+ });
+ let _ = self.fft.process_with_scratch(&mut self.in_buf, &mut self.spectrum, &mut self.scratch_buf);
+ // self.stale = false;
+ //normalize the levels of each bin.
+ let l = self.vec.len();
+ self.spectrum.iter().zip(self.vec.iter_mut()).for_each(|(x, y)| {
+ let x1 = x.norm_sqr();
+ *y = x1/T::from(l).unwrap();
+ });
+ //send the bins to the ui thread. We use this method so that we can avoid allocating a
+ //new vec, and thus breaking our realtime requirements.
+ self.bins_tx.write_by(&self.vec, &mut |old: &mut Vec<T>, new: &Vec<T>| {
+ old.copy_from_slice(new);
+ });
+ }
+ self.idx = new_idx;
+ }
+ }
+ }
+
+ fn reset(&mut self) {
self.idx = 0;
- self.running_buf.fill(0.0);
+ self.running_buf.fill(T::zero());
}
- ///Intake a sample. If enough samples have been received, calculate a spectrum, accumulate it
- ///to the existing spectrum, and send the existing spectrum via the blockfree channel.
- pub fn process_sample(&mut self, samp: f32) {
- let buf_len = self.running_buf.len() as u32;
- self.running_buf[self.idx as usize] = samp;
-
- let new_idx = (self.idx+1)%buf_len;
- if new_idx%(buf_len/self.spec.overlap) == 0 {
- self.in_buf.iter_mut()
- .zip(self.running_buf.iter().cycle().skip(self.idx as usize))
- .zip(self.window.iter())
- .for_each(|((old, new), window_cf)| {
- let windowed = *new*window_cf;
- *old = windowed;
- });
- let _ = self.fft.process_with_scratch(&mut self.in_buf, &mut self.spectrum, &mut self.scratch_buf);
- self.stale = false;
- }
- self.idx = new_idx;
+ fn update(&mut self) {
+
+ }
+
+ fn params(&mut self) -> &mut dyn audio_backend::node_interface::Param {
+ todo!()
+ }
+
+ fn prepare(&mut self, spec: &audio_backend::node_interface::ProcessSpec) {
+ self.reset();
+ }
+
+ fn metadata(&self) -> Metadata {
+ Metadata{inputs: vec!["Input".into()], outputs: vec![], name: "FFT".into()}
}
}
+
+pub fn run_fft(name: &str, backend: audio_backend::Backend, window: Windows, spec: FftSpec) -> BlockFreeRx<Vec<f32>> {
+ let (bins_tx, bins_rx) = blockfree_channel(&vec![0.0; spec.num_bins as usize/2]);
+
+ let fft_node: FftState<f32> = FftState::new(spec, window, bins_tx);
+ let mut meta = fft_node.metadata();
+ meta.name = name.into();
+
+ audio_backend::run(backend, &meta, Arc::new(RwLock::new(fft_node)));
+ bins_rx
+}
+