use bevy_ecs::component::Component;
use firewheel::{
channel_config::{ChannelConfig, NonZeroChannelCount},
diff::{Diff, Patch},
event::ProcEvents,
node::{
AudioNode, AudioNodeInfo, AudioNodeProcessor, ConstructProcessorContext, ProcBuffers,
ProcExtra, ProcInfo, ProcStreamCtx, ProcessStatus,
},
param::smoother::{SmoothedParam, SmootherConfig},
};
#[derive(Diff, Patch, Debug, Clone, Component)]
#[cfg_attr(feature = "reflect", derive(bevy_reflect::Reflect))]
pub struct LowPassNode {
pub frequency: f32,
}
impl Default for LowPassNode {
fn default() -> Self {
Self { frequency: 1000.0 }
}
}
#[derive(Debug, Component, Clone, PartialEq)]
#[cfg_attr(feature = "reflect", derive(bevy_reflect::Reflect))]
pub struct LowPassConfig {
pub smoother_config: SmootherConfig,
pub channels: NonZeroChannelCount,
}
impl Default for LowPassConfig {
fn default() -> Self {
Self {
smoother_config: Default::default(),
channels: NonZeroChannelCount::STEREO,
}
}
}
impl AudioNode for LowPassNode {
type Configuration = LowPassConfig;
fn info(&self, config: &Self::Configuration) -> AudioNodeInfo {
AudioNodeInfo::new()
.debug_name("low-pass filter")
.channel_config(ChannelConfig {
num_inputs: config.channels.get(),
num_outputs: config.channels.get(),
})
}
fn construct_processor(
&self,
config: &Self::Configuration,
cx: ConstructProcessorContext,
) -> impl AudioNodeProcessor {
LowPassProcessor {
frequency: SmoothedParam::new(
self.frequency,
config.smoother_config,
cx.stream_info.sample_rate,
),
channels: vec![
Lpf::new(cx.stream_info.sample_rate.get() as f32, self.frequency);
config.channels.get().get() as usize
],
}
}
}
#[derive(Clone)]
struct Lpf {
freq: f32,
prev_out: f32,
fixed_coeff: f32,
coeff: f32,
}
impl Lpf {
fn new(sample_rate: f32, frequency: f32) -> Self {
let fixed_coeff = core::f32::consts::TAU / sample_rate;
let mut filter = Self {
freq: 0.,
prev_out: 0.,
fixed_coeff,
coeff: 0.,
};
filter.set_frequency(frequency);
filter
}
pub fn set_frequency(&mut self, freq: f32) {
if freq != self.freq {
self.coeff = (freq * self.fixed_coeff).clamp(0.0, 1.0);
self.freq = freq;
}
}
pub fn process(&mut self, input: f32) -> f32 {
let fb = 1.0 - self.coeff;
let output = self.coeff * input + fb * self.prev_out;
self.prev_out = output;
output
}
}
struct LowPassProcessor {
frequency: SmoothedParam,
channels: Vec<Lpf>,
}
impl AudioNodeProcessor for LowPassProcessor {
fn process(
&mut self,
proc_info: &ProcInfo,
ProcBuffers { inputs, outputs }: ProcBuffers,
events: &mut ProcEvents,
_: &mut ProcExtra,
) -> ProcessStatus {
for patch in events.drain_patches::<LowPassNode>() {
match patch {
LowPassNodePatch::Frequency(f) => self.frequency.set_value(f.clamp(0.0, 20_000.0)),
}
}
if proc_info.in_silence_mask.all_channels_silent(inputs.len()) {
self.frequency.reset_to_target();
return ProcessStatus::ClearAllOutputs;
}
if self.frequency.is_smoothing() {
for sample in 0..inputs[0].len() {
let freq = self.frequency.next_smoothed();
for channel in self.channels.iter_mut() {
channel.set_frequency(freq);
}
for (i, channel) in self.channels.iter_mut().enumerate() {
outputs[i][sample] = channel.process(inputs[i][sample]);
}
}
self.frequency.settle();
} else {
let freq = self.frequency.target_value();
for channel in self.channels.iter_mut() {
channel.set_frequency(freq);
}
for sample in 0..inputs[0].len() {
for (i, channel) in self.channels.iter_mut().enumerate() {
outputs[i][sample] = channel.process(inputs[i][sample]);
}
}
}
ProcessStatus::OutputsModified
}
fn new_stream(&mut self, stream_info: &firewheel::StreamInfo, _: &mut ProcStreamCtx) {
self.frequency.update_sample_rate(stream_info.sample_rate);
}
}