From 20414d4b6ab6d1177d8008ee987931c9e4ee65d3 Mon Sep 17 00:00:00 2001 From: ozpv <39195175+ozpv@users.noreply.github.com> Date: Mon, 18 May 2026 01:58:17 -0500 Subject: a few updates --- src/window_function.rs | 259 ++++++++++++++++++++++++++++++++++++++++++++++--- src/window_size.rs | 8 +- src/window_type.rs | 29 +++++- src/windowed_fft.rs | 11 ++- 4 files changed, 281 insertions(+), 26 deletions(-) diff --git a/src/window_function.rs b/src/window_function.rs index 8e21694..b0ed54d 100644 --- a/src/window_function.rs +++ b/src/window_function.rs @@ -1,3 +1,6 @@ +#![allow(unused)] +#![allow(dead_code)] + use crate::window_size::WindowSize; use std::{collections::VecDeque, f32::consts::PI}; @@ -12,6 +15,7 @@ pub trait WindowFunction: Send { fn window_size(&mut self, window_size: WindowSize); } +/// Effectively a chunking function rather than a sliding window pub struct RectangularWindow { window_size: usize, } @@ -33,9 +37,118 @@ impl WindowFunction for RectangularWindow { self.window_size } - fn window_size(&mut self, _window_size: WindowSize) {} + fn window_size(&mut self, _window_size: WindowSize) { + todo!() + } +} + +/// A window designed specifically to reduce clicking and leave all other artifacts +/// Results vary +pub struct HaemolacriaaWindow { + window_size: usize, + function_start: Vec, + function_end: Vec, + previous: Vec, + overlap_add: Vec, +} + +impl HaemolacriaaWindow { + /// Ensure this value isn't greater than or equal to any default window size + const SAMPLE_COUNT: usize = 16; + + pub fn new(window_size: &WindowSize) -> Self { + let window_size = window_size.inner(); + let sample_count_f32 = Self::SAMPLE_COUNT as f32; + + let overlap_add = vec![0.0; Self::SAMPLE_COUNT]; + + let function_start = (0..Self::SAMPLE_COUNT) + .map(|i| { + let x = i as f32 / (sample_count_f32 - 1.0); + + f32::sin(PI * 0.5 * x) + }) + .collect::>(); + + let function_end = (0..Self::SAMPLE_COUNT) + .map(|i| { + let x = i as f32 / (sample_count_f32 - 1.0); + + f32::cos(PI * 0.5 * x) + }) + .collect::>(); + + let previous = Vec::with_capacity(Self::SAMPLE_COUNT); + + Self { + window_size, + function_start, + function_end, + previous, + overlap_add, + } + } +} + +impl WindowFunction for HaemolacriaaWindow { + fn apply(&mut self, data: &mut VecDeque) { + let last_few = self.window_size - Self::SAMPLE_COUNT; + + if self.previous.is_empty() { + for sample in data.iter().skip(last_few).copied() { + self.previous.push(sample); + } + + for i in 0..Self::SAMPLE_COUNT { + data[i] *= self.function_start[i]; + data[last_few + i] *= self.function_end[i]; + } + + return; + } + + for sample in self.previous.iter().rev().copied() { + data.push_front(sample); + } + + self.previous.clear(); + + for sample in data.iter().skip(last_few).copied() { + self.previous.push(sample); + } + + for i in 0..Self::SAMPLE_COUNT { + data[i] *= self.function_start[i]; + data[last_few + i] *= self.function_end[i]; + } + } + + fn reverse(&mut self, data: &mut [f32]) { + let last_few = self.window_size - Self::SAMPLE_COUNT; + + for (i, sample) in data.iter_mut().take(Self::SAMPLE_COUNT).enumerate() { + *sample += self.overlap_add[i]; + } + + for (i, sample) in data.iter().skip(last_few).copied().enumerate() { + self.overlap_add[i] = sample; + } + } + + fn needed(&self) -> usize { + if self.previous.is_empty() { + self.window_size + } else { + self.window_size - Self::SAMPLE_COUNT + } + } + + fn window_size(&mut self, _window_size: WindowSize) { + todo!() + } } +/// Hann function pub struct HannWindow { window_size: usize, function: Vec, @@ -101,7 +214,6 @@ impl WindowFunction for HannWindow { } // In a real time "sliding window" it's necessary to add in the previous samples - // to get the full resolution for sample in self.previous.iter().rev().copied() { data.push_front(sample); } @@ -122,15 +234,15 @@ impl WindowFunction for HannWindow { fn reverse(&mut self, data: &mut [f32]) { let half_window_size = self.window_size / 2; - for i in 0..self.window_size { - self.overlap_add[i] += data[i] * self.function[i]; + for (i, sample) in data.iter().copied().enumerate() { + self.overlap_add[i] += sample * self.function[i]; } - for i in 0..half_window_size { + for (i, sample) in data.iter_mut().enumerate().take(half_window_size) { if self.normalize[i] > 1e-6 { - data[i] = self.overlap_add[i] / self.normalize[i]; + *sample = self.overlap_add[i] / self.normalize[i]; } else { - data[i] = 0.0; + *sample = 0.0; } } @@ -176,6 +288,7 @@ impl WindowFunction for HannWindow { } } +/// 4-term Blackman-Harris window function pub struct BlackmanHarrisWindow { window_size: usize, function: Vec, @@ -267,15 +380,15 @@ impl WindowFunction for BlackmanHarrisWindow { let quarter_window_size = self.window_size / 4; let three_quarters_window_size = 3 * quarter_window_size; - for i in 0..self.window_size { - self.overlap_add[i] += data[i] * self.function[i]; + for (i, sample) in data.iter().copied().enumerate() { + self.overlap_add[i] += sample * self.function[i]; } - for i in 0..quarter_window_size { + for (i, sample) in data.iter_mut().enumerate().take(quarter_window_size) { if self.normalize[i] > 1e-6 { - data[i] = self.overlap_add[i] / self.normalize[i]; + *sample = self.overlap_add[i] / self.normalize[i]; } else { - data[i] = 0.0; + *sample = 0.0; } } @@ -330,3 +443,125 @@ impl WindowFunction for BlackmanHarrisWindow { .collect::>(); } } + +/// 4th order power of sine window function +pub struct Sine4Window { + window_size: usize, + function: Vec, + normalize: Vec, + previous: Vec, + overlap_add: Vec, +} + +impl Sine4Window { + const A_0: f32 = 0.375; + const A_1: f32 = 0.5; + const A_2: f32 = 0.125; + + pub fn new(window_size: &WindowSize) -> Self { + let window_size = window_size.inner(); + let quarter_window_size = window_size / 4; + let three_quarters_window_size = 3 * quarter_window_size; + let window_size_f32 = window_size as f32; + + let overlap_add = vec![0.0; window_size]; + + let previous = Vec::with_capacity(three_quarters_window_size); + + let function = (0..window_size) + .map(|i| { + let i = i as f32; + + let two = f32::cos((2.0 * PI * i) / (window_size_f32 - 1.0)); + let four = f32::cos((4.0 * PI * i) / (window_size_f32 - 1.0)); + + Self::A_0 - (Self::A_1 * two) + (Self::A_2 * four) + }) + .collect::>(); + + #[rustfmt::skip] + let normalize = (0..quarter_window_size) + .map(|i| { + (function[i] * function[i]) + + (function[i + quarter_window_size] * function[i + quarter_window_size]) + + (function[i + 2 * quarter_window_size] * function[i + 2 * quarter_window_size]) + + (function[i + 3 * quarter_window_size] * function[i + 3 * quarter_window_size]) + }) + .collect::>(); + + Self { + window_size, + function, + normalize, + previous, + overlap_add, + } + } +} + +impl WindowFunction for Sine4Window { + fn apply(&mut self, data: &mut VecDeque) { + let quarter_window_size = self.window_size / 4; + + if self.previous.is_empty() { + for sample in data.iter().skip(quarter_window_size).copied() { + self.previous.push(sample); + } + + for (i, sample) in data.iter_mut().enumerate() { + *sample *= self.function[i]; + } + + return; + } + + for sample in self.previous.iter().rev().copied() { + data.push_front(sample); + } + + self.previous.clear(); + + for sample in data.iter().skip(quarter_window_size).copied() { + self.previous.push(sample); + } + + for (i, sample) in data.iter_mut().enumerate() { + *sample *= self.function[i]; + } + } + + fn reverse(&mut self, data: &mut [f32]) { + let quarter_window_size = self.window_size / 4; + let three_quarters_window_size = 3 * quarter_window_size; + + for (i, sample) in data.iter().copied().enumerate() { + self.overlap_add[i] += sample * self.function[i]; + } + + for (i, sample) in data.iter_mut().enumerate().take(quarter_window_size) { + if self.normalize[i] > 1e-6 { + *sample = self.overlap_add[i] / self.normalize[i]; + } else { + *sample = 0.0; + } + } + + self.overlap_add.rotate_left(quarter_window_size); + + for sample in &mut self.overlap_add[three_quarters_window_size..] { + *sample = 0.0; + } + } + + fn needed(&self) -> usize { + if self.previous.is_empty() { + self.window_size + } else { + self.window_size / 4 + } + } + + fn window_size(&mut self, window_size: WindowSize) { + todo!() + } +} diff --git a/src/window_size.rs b/src/window_size.rs index bfc3957..620a307 100644 --- a/src/window_size.rs +++ b/src/window_size.rs @@ -1,8 +1,8 @@ /// The max value of the custom window size is `u32::MAX`. +/// The min value of the custom window size is `WindowSize::Size256`. /// This is because it's the maximum latency one can report to a CLAP host. #[derive(Debug, PartialEq)] pub enum WindowSize { - Size128, Size256, Size512, Size1024, @@ -16,10 +16,9 @@ pub enum WindowSize { impl From for WindowSize { fn from(item: usize) -> Self { - let item = item.clamp(0, u32::MAX as usize); + let item = item.clamp(256, u32::MAX as usize); match item { - 128 => Self::Size128, 256 => Self::Size256, 512 => Self::Size512, 1024 => Self::Size1024, @@ -36,7 +35,6 @@ impl From for WindowSize { impl WindowSize { pub fn as_str(&self) -> &'static str { match self { - Self::Size128 => "128", Self::Size256 => "256", Self::Size512 => "512", Self::Size1024 => "1024", @@ -51,7 +49,6 @@ impl WindowSize { pub fn inner(&self) -> usize { match self { - Self::Size128 => 128, Self::Size256 => 256, Self::Size512 => 512, Self::Size1024 => 1024, @@ -66,7 +63,6 @@ impl WindowSize { pub fn iter() -> impl Iterator { [ - Self::Size128, Self::Size256, Self::Size512, Self::Size1024, diff --git a/src/window_type.rs b/src/window_type.rs index 371a410..06c87be 100644 --- a/src/window_type.rs +++ b/src/window_type.rs @@ -1,32 +1,51 @@ +#![allow(unused)] +#![allow(dead_code)] + use crate::{ - window_function::{BlackmanHarrisWindow, HannWindow, RectangularWindow, WindowFunction}, + window_function::{ + BlackmanHarrisWindow, HaemolacriaaWindow, HannWindow, RectangularWindow, Sine4Window, + WindowFunction, + }, window_size::WindowSize, }; pub enum WindowType { - Hann, Rectangular, + Haemolacriaa, + Hann, BlackmanHarris, + Sine4, } impl WindowType { fn into_function(self, window_size: &WindowSize) -> Box { match self { - Self::Hann => Box::new(HannWindow::new(window_size)), Self::Rectangular => Box::new(RectangularWindow::new(window_size)), + Self::Haemolacriaa => Box::new(HaemolacriaaWindow::new(window_size)), + Self::Hann => Box::new(HannWindow::new(window_size)), Self::BlackmanHarris => Box::new(BlackmanHarrisWindow::new(window_size)), + Self::Sine4 => Box::new(Sine4Window::new(window_size)), } } pub fn as_str(&self) -> &'static str { match self { - Self::Hann => "Hann", Self::Rectangular => "Rectangular", + Self::Haemolacriaa => "haemolacriaa", + Self::Hann => "Hann", Self::BlackmanHarris => "Blackman-Harris", + Self::Sine4 => "Sin^4", } } pub fn iter() -> impl Iterator { - [Self::Hann, Self::Rectangular].into_iter() + [ + Self::Rectangular, + Self::Haemolacriaa, + Self::Hann, + Self::BlackmanHarris, + Self::Sine4, + ] + .into_iter() } } diff --git a/src/windowed_fft.rs b/src/windowed_fft.rs index c08c2b4..19bf566 100644 --- a/src/windowed_fft.rs +++ b/src/windowed_fft.rs @@ -1,8 +1,13 @@ #![allow(clippy::must_use_candidate)] #![allow(clippy::return_self_not_must_use)] +#![allow(unused)] +#![allow(dead_code)] use crate::{ - window_function::{BlackmanHarrisWindow, HannWindow, RectangularWindow, WindowFunction}, + window_function::{ + BlackmanHarrisWindow, HaemolacriaaWindow, HannWindow, RectangularWindow, Sine4Window, + WindowFunction, + }, window_size::WindowSize, }; use num_complex::Complex; @@ -23,7 +28,7 @@ pub struct WindowedRealFft { impl WindowedRealFft { pub fn new(window_size: WindowSize) -> Self { - let window_function = Box::new(BlackmanHarrisWindow::new(&window_size)); + let window_function = Box::new(HaemolacriaaWindow::new(&window_size)); let window_size = window_size.inner(); let mut planner = RealFftPlanner::new(); @@ -58,7 +63,7 @@ impl WindowedRealFft { return; } - self.window_function = Box::new(HannWindow::new(&window_size)); + self.window_function = Box::new(HaemolacriaaWindow::new(&window_size)); self.window_size = window_size.inner(); self.forward = self.planner.plan_fft_forward(self.window_size); -- cgit v1.2.3