use alloc::vec::Vec;
use serde::{Deserialize, Serialize};
use crate::aero::WhooshType;
use crate::dsp::{DcBlocker, validate_sample_rate};
use crate::error::Result;
use crate::rng::Rng;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Whoosh {
whoosh_type: WhooshType,
sample_rate: f32,
rng: Rng,
dc_blocker: DcBlocker,
sample_position: usize,
envelope_samples: usize,
brightness: f32,
low_content: f32,
active: bool,
trigger_position: usize,
speed: f32,
#[cfg(feature = "naad-backend")]
noise_gen: naad::noise::NoiseGenerator,
#[cfg(feature = "naad-backend")]
hi_filter: naad::filter::BiquadFilter,
#[cfg(feature = "naad-backend")]
lo_filter: naad::filter::BiquadFilter,
}
impl Whoosh {
pub fn new(whoosh_type: WhooshType, sample_rate: f32) -> Result<Self> {
validate_sample_rate(sample_rate)?;
let (env_dur, brightness, low_content) = whoosh_type.config();
let envelope_samples = (env_dur * sample_rate) as usize;
#[cfg(feature = "naad-backend")]
let noise_gen = naad::noise::NoiseGenerator::new(naad::noise::NoiseType::Pink, 1111);
#[cfg(feature = "naad-backend")]
let hi_filter = naad::filter::BiquadFilter::new(
naad::filter::FilterType::HighPass,
sample_rate,
(brightness * 4000.0).clamp(200.0, 8000.0),
0.7,
)
.map_err(|e| crate::error::GarjanError::SynthesisFailed(alloc::format!("{e}")))?;
#[cfg(feature = "naad-backend")]
let lo_filter = naad::filter::BiquadFilter::new(
naad::filter::FilterType::LowPass,
sample_rate,
(300.0 + low_content * 500.0).clamp(100.0, 1000.0),
0.5,
)
.map_err(|e| crate::error::GarjanError::SynthesisFailed(alloc::format!("{e}")))?;
Ok(Self {
whoosh_type,
sample_rate,
rng: Rng::new(1111),
dc_blocker: DcBlocker::new(sample_rate),
sample_position: 0,
envelope_samples,
brightness,
low_content,
active: false,
trigger_position: 0,
speed: 0.5,
#[cfg(feature = "naad-backend")]
noise_gen,
#[cfg(feature = "naad-backend")]
hi_filter,
#[cfg(feature = "naad-backend")]
lo_filter,
})
}
pub fn set_speed(&mut self, speed: f32) {
self.speed = speed.clamp(0.0, 1.0);
}
pub fn trigger(&mut self) {
self.active = true;
self.trigger_position = 0;
}
#[inline]
pub fn synthesize(&mut self, duration: f32) -> Result<Vec<f32>> {
crate::dsp::validate_duration(duration)?;
let num_samples = (self.sample_rate * duration) as usize;
let mut output = alloc::vec![0.0f32; num_samples];
self.trigger();
self.process_block(&mut output);
Ok(output)
}
#[inline]
pub fn process_block(&mut self, output: &mut [f32]) {
let speed_scale = 0.5 + self.speed * 0.5;
let effective_len = (self.envelope_samples as f32 / speed_scale) as usize;
for sample in output.iter_mut() {
if !self.active {
*sample = 0.0;
self.dc_blocker.process(0.0);
continue;
}
let t = self.trigger_position as f32 / effective_len.max(1) as f32;
let env = if t < 1.0 {
let s = crate::math::f32::sin(core::f32::consts::PI * t);
s * s
} else {
self.active = false;
0.0
};
let hi = self.generate_noise_hi() * env * self.brightness;
let lo = self.generate_noise_lo() * env * self.low_content;
*sample = (hi + lo) * self.speed;
*sample = self.dc_blocker.process(*sample);
self.trigger_position += 1;
}
self.sample_position += output.len();
}
#[inline]
fn generate_noise_hi(&mut self) -> f32 {
#[cfg(feature = "naad-backend")]
{
let raw = self.noise_gen.next_sample();
self.hi_filter.process_sample(raw)
}
#[cfg(not(feature = "naad-backend"))]
{
self.rng.next_f32()
}
}
#[inline]
fn generate_noise_lo(&mut self) -> f32 {
#[cfg(feature = "naad-backend")]
{
let raw = self.noise_gen.next_sample();
self.lo_filter.process_sample(raw)
}
#[cfg(not(feature = "naad-backend"))]
{
(self.rng.next_f32() + self.rng.next_f32()) * 0.5
}
}
}