diff --git a/Cargo.lock b/Cargo.lock index 93d2bc56..30d22849 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18,6 +18,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a13739d7177fbd22bb0ed28badfff9f372f8bef46c863db4e1c6248f6b223b6e" +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + [[package]] name = "ahash" version = "0.7.6" @@ -29,6 +35,15 @@ dependencies = [ "version_check", ] +[[package]] +name = "aho-corasick" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca972c2ea5f742bfce5687b9aef75506a764f61d37f8f649047846a9686ddb66" +dependencies = [ + "memchr 0.1.11", +] + [[package]] name = "anyhow" version = "1.0.55" @@ -71,7 +86,7 @@ dependencies = [ "objc", "raw-window-handle", "uuid", - "winapi", + "winapi 0.3.9", "x11", "xcb 0.9.0", "xcb-util", @@ -89,6 +104,33 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bzip2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6afcd980b5f3a45017c57e57a2fcccbb351cc43a356ce117ef760ef8052b89b0" +dependencies = [ + "bzip2-sys", + "libc", +] + +[[package]] +name = "bzip2-sys" +version = "0.1.11+1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + [[package]] name = "cargo-nih-plug" version = "0.1.0" @@ -108,6 +150,16 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9213f7cd7c27e95c2b57c49f0e69b1ea65b27138da84a170133fd21b07659c00" +dependencies = [ + "num", + "time", +] + [[package]] name = "clap-sys" version = "0.1.0" @@ -120,7 +172,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fdf5e01086b6be750428ba4a40619f847eb2e95756eee84b18e06e5f0b50342" dependencies = [ "lazy-bytes-cast", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -209,6 +261,15 @@ dependencies = [ "libc", ] +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + [[package]] name = "crossbeam" version = "0.8.1" @@ -252,7 +313,7 @@ checksum = "c00d6d2ea26e8b151d99093005cb442fb9a37aeaca582a03ec70946f49ab5ed9" dependencies = [ "cfg-if", "crossbeam-utils", - "lazy_static", + "lazy_static 1.4.0", "memoffset", "scopeguard", ] @@ -274,7 +335,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e5bed1f1c269533fa816a0a5492b3545209a205ca1a54842be180eb63a16a6" dependencies = [ "cfg-if", - "lazy_static", + "lazy_static 1.4.0", ] [[package]] @@ -349,6 +410,57 @@ dependencies = [ "nohash-hasher", ] +[[package]] +name = "fftw" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dc6a84bbb6bac64521bfb5a544701ce118f329d550cf9b9054d1954fe61adef" +dependencies = [ + "bitflags", + "fftw-sys", + "lazy_static 1.4.0", + "ndarray", + "num-complex", + "num-traits", + "thiserror", +] + +[[package]] +name = "fftw-src" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08962470ab0e91e74ec7d338c8731476c28ed4e503a3080b0f001692e395a7c" +dependencies = [ + "anyhow", + "cc", + "fs_extra", + "ftp", + "zip", +] + +[[package]] +name = "fftw-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8e3951d695cc2f17610cd041e87ebc15078d1af5eb8c6be77921381fc98b3fd" +dependencies = [ + "fftw-src", + "libc", + "num-complex", +] + +[[package]] +name = "flate2" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6988e897c1c9c485f43b47a529cef42fde0547f9d8d41a7062518f1d8fc53f" +dependencies = [ + "cfg-if", + "crc32fast", + "libc", + "miniz_oxide", +] + [[package]] name = "foreign-types" version = "0.3.2" @@ -364,6 +476,23 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" +[[package]] +name = "fs_extra" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2022715d62ab30faffd124d40b76f4134a550a87792276512b18d63272333394" + +[[package]] +name = "ftp" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "542951aad0071952c27409e3bd7cb62d1a3ad419c4e7314106bf994e0083ad5d" +dependencies = [ + "chrono", + "lazy_static 0.1.16", + "regex", +] + [[package]] name = "gain" version = "0.1.0" @@ -429,6 +558,16 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" +[[package]] +name = "kernel32-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" +dependencies = [ + "winapi 0.2.8", + "winapi-build", +] + [[package]] name = "keyboard-types" version = "0.6.2" @@ -450,6 +589,12 @@ version = "5.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10257499f089cd156ad82d0a9cd57d9501fa2c989068992a97eb3c27836f206b" +[[package]] +name = "lazy_static" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf186d1a8aa5f5bee5fd662bc9c1b949e0259e1bcc379d1f006847b0080c7417" + [[package]] name = "lazy_static" version = "1.4.0" @@ -469,7 +614,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd" dependencies = [ "cfg-if", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -499,6 +644,24 @@ dependencies = [ "libc", ] +[[package]] +name = "matrixmultiply" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "916806ba0031cd542105d916a97c8572e1fa6dd79c9c51e7eb43a09ec2dd84c1" +dependencies = [ + "rawpointer", +] + +[[package]] +name = "memchr" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b629fb514376c675b98c1421e80b151d3817ac42d7c667717d282761418d20" +dependencies = [ + "libc", +] + [[package]] name = "memchr" version = "2.4.1" @@ -529,6 +692,29 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" +[[package]] +name = "miniz_oxide" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" +dependencies = [ + "adler", + "autocfg", +] + +[[package]] +name = "ndarray" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c0d5c9540a691d153064dc47a4db2504587a75eae07bf1d73f7a596ebc73c04" +dependencies = [ + "matrixmultiply", + "num-complex", + "num-integer", + "num-traits", + "rawpointer", +] + [[package]] name = "nih_plug" version = "0.0.0" @@ -539,7 +725,7 @@ dependencies = [ "cfg-if", "clap-sys", "crossbeam", - "lazy_static", + "lazy_static 1.4.0", "nih_plug_derive", "parking_lot", "raw-window-handle", @@ -566,7 +752,7 @@ dependencies = [ "crossbeam", "egui", "egui-baseview", - "lazy_static", + "lazy_static 1.4.0", "nih_plug", "parking_lot", ] @@ -607,11 +793,61 @@ version = "7.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b1d11e1ef389c76fe5b81bcaf2ea32cf88b62bc494e19f493d0b30e7a930109" dependencies = [ - "memchr", + "memchr 2.4.1", "minimal-lexical", "version_check", ] +[[package]] +name = "num" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4703ad64153382334aa8db57c637364c322d3372e097840c72000dabdcf6156e" +dependencies = [ + "num-integer", + "num-iter", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "747d632c0c558b87dbabbe6a82f3b4ae03720d0646ac5b7b4dae89394be5f2c5" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +dependencies = [ + "autocfg", +] + [[package]] name = "objc" version = "0.2.7" @@ -706,7 +942,7 @@ version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8533f14c8382aaad0d592c812ac3b826162128b65662331e1127b45c3d18536b" dependencies = [ - "memchr", + "memchr 2.4.1", ] [[package]] @@ -727,6 +963,12 @@ dependencies = [ "cty", ] +[[package]] +name = "rawpointer" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" + [[package]] name = "redox_syscall" version = "0.2.11" @@ -742,9 +984,28 @@ version = "0.1.3" source = "git+https://github.com/nicokoch/reflink?rev=e8d93b465f5d9ad340cd052b64bbc77b8ee107e2#e8d93b465f5d9ad340cd052b64bbc77b8ee107e2" dependencies = [ "libc", - "winapi", + "winapi 0.3.9", ] +[[package]] +name = "regex" +version = "0.1.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fd4ace6a8cf7860714a2c2280d6c1f7e6a413486c13298bbc86fd3da019402f" +dependencies = [ + "aho-corasick", + "memchr 0.1.11", + "regex-syntax", + "thread_local", + "utf8-ranges", +] + +[[package]] +name = "regex-syntax" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9ec002c35e86791825ed294b50008eea9ddfc8def4420124fbc6b08db834957" + [[package]] name = "ryu" version = "1.0.9" @@ -835,7 +1096,7 @@ checksum = "1325f292209cee78d5035530932422a30aa4c8fda1a16593ac083c1de211e68a" dependencies = [ "bitflags", "dlib", - "lazy_static", + "lazy_static 1.4.0", "log", "memmap2", "nix", @@ -859,6 +1120,7 @@ dependencies = [ name = "stft" version = "0.1.0" dependencies = [ + "fftw", "nih_plug", ] @@ -873,6 +1135,55 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "thiserror" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread-id" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9539db560102d1cef46b8b78ce737ff0bb64e7e18d35b2a5688f7d097d0ff03" +dependencies = [ + "kernel32-sys", + "libc", +] + +[[package]] +name = "thread_local" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8576dbbfcaef9641452d5cf0df9b0e7eeab7694956dd33bb61515fb8f18cfdd5" +dependencies = [ + "thread-id", +] + +[[package]] +name = "time" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" +dependencies = [ + "libc", + "winapi 0.3.9", +] + [[package]] name = "toml" version = "0.5.8" @@ -894,6 +1205,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" +[[package]] +name = "utf8-ranges" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1ca13c08c41c9c3e04224ed9ff80461d97e121589ff27c753a16cb10830ae0f" + [[package]] name = "uuid" version = "0.8.2" @@ -1021,7 +1338,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9341df79a8975679188e37dab3889bfa57c44ac2cb6da166f519a81cbe452d4" dependencies = [ "dlib", - "lazy_static", + "lazy_static 1.4.0", "pkg-config", ] @@ -1031,6 +1348,12 @@ version = "1.0.0-beta.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6f1efe828a707edf85994a4501734ac1c1b9d244cfcf4de235f11c4125ace8f" +[[package]] +name = "winapi" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" + [[package]] name = "winapi" version = "0.3.9" @@ -1041,6 +1364,12 @@ dependencies = [ "winapi-x86_64-pc-windows-gnu", ] +[[package]] +name = "winapi-build" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" + [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" @@ -1181,3 +1510,17 @@ version = "0.1.0" dependencies = [ "nih_plug_xtask", ] + +[[package]] +name = "zip" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93ab48844d61251bb3835145c521d88aa4031d7139e8485990f60ca911fa0815" +dependencies = [ + "byteorder", + "bzip2", + "crc32fast", + "flate2", + "thiserror", + "time", +] diff --git a/plugins/examples/stft/Cargo.toml b/plugins/examples/stft/Cargo.toml index 71cf7856..15cbfb72 100644 --- a/plugins/examples/stft/Cargo.toml +++ b/plugins/examples/stft/Cargo.toml @@ -10,3 +10,5 @@ crate-type = ["cdylib"] [dependencies] nih_plug = { path = "../../../", features = ["assert_process_allocs"] } + +fftw = "0.7.0" diff --git a/plugins/examples/stft/src/lib.rs b/plugins/examples/stft/src/lib.rs index 4fcdd00e..5bd35712 100644 --- a/plugins/examples/stft/src/lib.rs +++ b/plugins/examples/stft/src/lib.rs @@ -1,3 +1,6 @@ +use fftw::array::AlignedVec; +use fftw::plan::{C2RPlan, C2RPlan32, R2CPlan, R2CPlan32}; +use fftw::types::{c32, Flag}; use nih_plug::prelude::*; use std::pin::Pin; @@ -7,25 +10,82 @@ const OVERLAP_TIMES: usize = 4; struct Stft { params: Pin>, + /// An adapter that performs most of the overlap-add algorithm for us. stft: util::StftHelper, + /// A Hann window window, passed to the overlap-add helper. window_function: Vec, + + /// The FFT of a simple low pass FIR filter. + lp_filter_kernel: Vec, + + /// The algorithms for the FFT and IFFT operations. + plan: Plan, + /// Scratch buffers for computing our FFT. The [`StftHelper`] already contains a buffer for the + /// real values. + complex_fft_scratch_buffer: AlignedVec, } +/// FFTW uses raw pointers which aren't Send+Sync, so we'll wrap this in a separate struct. +struct Plan { + r2c_plan: R2CPlan32, + c2r_plan: C2RPlan32, +} + +unsafe impl Send for Plan {} +unsafe impl Sync for Plan {} + #[derive(Params)] -#[allow(clippy::derivable_impls)] struct StftParams {} impl Default for Stft { fn default() -> Self { + let mut r2c_plan: R2CPlan32 = R2CPlan32::aligned(&[WINDOW_SIZE], Flag::MEASURE).unwrap(); + let c2r_plan: C2RPlan32 = C2RPlan32::aligned(&[WINDOW_SIZE], Flag::MEASURE).unwrap(); + let mut real_fft_scratch_buffer: AlignedVec = AlignedVec::new(WINDOW_SIZE); + let mut complex_fft_scratch_buffer: AlignedVec = AlignedVec::new(WINDOW_SIZE / 2 + 1); + + // Build a super simple low pass filter from one of the built in window function + const FILTER_WINDOW_SIZE: usize = 33; + let filter_window = util::window::hann(FILTER_WINDOW_SIZE); + real_fft_scratch_buffer[0..FILTER_WINDOW_SIZE].copy_from_slice(&filter_window); + + // Our STFT functions will have a window function applied, so we need to do the same thing + // with this filter + let window_function = util::window::hann(WINDOW_SIZE); + util::window::multiply_with_window(&mut real_fft_scratch_buffer, &window_function); + + // And make sure to normalize this so convolution sums to 1 + let filter_sum_recip = real_fft_scratch_buffer.iter().sum::().recip(); + for sample in real_fft_scratch_buffer.as_slice_mut() { + *sample *= filter_sum_recip; + } + + r2c_plan + .r2c( + &mut real_fft_scratch_buffer, + &mut complex_fft_scratch_buffer, + ) + .unwrap(); + Self { params: Box::pin(StftParams::default()), stft: util::StftHelper::new(2, WINDOW_SIZE), - window_function: util::window::hann(WINDOW_SIZE), + window_function, + + lp_filter_kernel: complex_fft_scratch_buffer + .iter() + .take(WINDOW_SIZE) + .copied() + .collect(), + + plan: Plan { r2c_plan, c2r_plan }, + complex_fft_scratch_buffer, } } } +#[allow(clippy::derivable_impls)] impl Default for StftParams { fn default() -> Self { Self {} @@ -73,18 +133,43 @@ impl Plugin for Stft { buffer: &mut Buffer, _context: &mut impl ProcessContext, ) -> ProcessStatus { - const GAIN_COMPENSATION: f32 = 2.0 / OVERLAP_TIMES as f32; + const GAIN_COMPENSATION: f32 = 1.0 / OVERLAP_TIMES as f32 / WINDOW_SIZE as f32; + self.stft.process_overlap_add( buffer, [], &self.window_function, OVERLAP_TIMES, - |_channel_idx, _, block| { - for sample in block { - // TODO: Use the FFTW bindings and do some STFT operation here instead of - // reducing the gain at a 2048 sample latency... - *sample *= GAIN_COMPENSATION; + |_channel_idx, _, real_fft_scratch_buffer| { + // Forward FFT, the helper has already applied window function + self.plan + .r2c_plan + .r2c( + real_fft_scratch_buffer, + &mut self.complex_fft_scratch_buffer, + ) + .unwrap(); + + // As per the convolution theorem we can simply multiply these two buffers. We'll + // also apply the gain compensation at this point. + for (fft_bin, kernel_bin) in self + .complex_fft_scratch_buffer + .as_slice_mut() + .iter_mut() + .zip(&self.lp_filter_kernel) + { + *fft_bin *= *kernel_bin * GAIN_COMPENSATION; } + + // Inverse FFT back into the scratch buffer. This will be added to a ring buffer + // which gets written back to the host at a one block delay. + self.plan + .c2r_plan + .c2r( + &mut self.complex_fft_scratch_buffer, + real_fft_scratch_buffer, + ) + .unwrap(); }, );