#![allow(missing_docs)]
#![allow(clippy::module_inception)]
use firewheel_core::dsp::coeff_update::{CoeffUpdateFactor, CoeffUpdateMask};
use firewheel_core::dsp::filter::smoothing_filter::DEFAULT_SMOOTH_SECONDS;
use firewheel_core::node::NodeError;
use firewheel_core::{
channel_config::{ChannelConfig, ChannelCount},
diff::{Diff, Notify, Patch},
dsp::{
declick::{DeclickFadeCurve, DeclickValues, Declicker},
volume::DEFAULT_MIN_AMP,
},
event::ProcEvents,
node::{
AudioNode, AudioNodeInfo, AudioNodeProcessor, ConstructProcessorContext, EmptyConfig,
ProcBuffers, ProcExtra, ProcInfo, ProcStreamCtx, ProcessStatus,
},
param::smoother::{SmoothedParam, SmootherConfig},
};
use crate::freeverb::freeverb::Freeverb;
mod all_pass;
mod comb;
mod delay_line;
mod freeverb;
#[derive(Diff, Patch, Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "bevy", derive(bevy_ecs::component::Component))]
#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct FreeverbNode {
pub room_size: f32,
pub damping: f32,
pub width: f32,
pub pause: bool,
#[cfg_attr(feature = "serde", serde(skip))]
pub reset: Notify<()>,
pub smooth_seconds: f32,
pub coeff_update_factor: CoeffUpdateFactor,
}
impl Default for FreeverbNode {
fn default() -> Self {
FreeverbNode {
room_size: 0.5,
damping: 0.5,
width: 0.5,
pause: false,
reset: Notify::new(()),
smooth_seconds: DEFAULT_SMOOTH_SECONDS,
coeff_update_factor: CoeffUpdateFactor::default(),
}
}
}
impl AudioNode for FreeverbNode {
type Configuration = EmptyConfig;
fn info(&self, _: &Self::Configuration) -> Result<AudioNodeInfo, NodeError> {
Ok(AudioNodeInfo::new()
.debug_name("freeverb")
.channel_config(ChannelConfig {
num_inputs: ChannelCount::STEREO,
num_outputs: ChannelCount::STEREO,
}))
}
fn construct_processor(
&self,
_: &Self::Configuration,
cx: ConstructProcessorContext,
) -> Result<impl AudioNodeProcessor, NodeError> {
let freeverb = freeverb::Freeverb::new(cx.stream_info.sample_rate.get() as usize);
let smoother_config = SmootherConfig {
smooth_seconds: self.smooth_seconds,
..Default::default()
};
let mut processor = FreeverbProcessor {
freeverb,
damping: SmoothedParam::new(
self.damping.clamp(0.0, 1.0),
1.0,
smoother_config,
cx.stream_info.sample_rate,
),
width: SmoothedParam::new(
self.width.clamp(0.0, 1.0),
1.0,
smoother_config,
cx.stream_info.sample_rate,
),
room_size: SmoothedParam::new(
self.room_size.clamp(0.0, 1.0),
1.0,
smoother_config,
cx.stream_info.sample_rate,
),
paused: self.pause,
pause_declicker: if self.pause {
Declicker::SettledAt0
} else {
Declicker::SettledAt1
},
values: DeclickValues::new(cx.stream_info.declick_frames),
coeff_update_mask: self.coeff_update_factor.mask(),
prev_output_was_silent: true,
};
processor.apply_parameters();
Ok(processor)
}
}
struct FreeverbProcessor {
freeverb: freeverb::Freeverb,
damping: SmoothedParam,
width: SmoothedParam,
room_size: SmoothedParam,
paused: bool,
pause_declicker: Declicker,
values: DeclickValues,
coeff_update_mask: CoeffUpdateMask,
prev_output_was_silent: bool,
}
impl FreeverbProcessor {
fn reset(&mut self, reset_reverb: bool) {
self.pause_declicker.reset_to_target();
self.damping.reset_to_target();
self.room_size.reset_to_target();
self.width.reset_to_target();
if reset_reverb {
self.freeverb.reset();
}
}
}
impl AudioNodeProcessor for FreeverbProcessor {
fn events(&mut self, info: &ProcInfo, events: &mut ProcEvents, _extra: &mut ProcExtra) {
for patch in events.drain_patches::<FreeverbNode>() {
match patch {
FreeverbNodePatch::Damping(value) => {
self.damping.set_value(value.clamp(0.0, 1.0));
}
FreeverbNodePatch::RoomSize(value) => {
self.room_size.set_value(value.clamp(0.0, 1.0));
}
FreeverbNodePatch::Width(value) => {
self.width.set_value(value.clamp(0.0, 1.0));
}
FreeverbNodePatch::Reset(_) => {
self.freeverb.reset();
}
FreeverbNodePatch::Pause(value) => {
self.paused = value;
if value {
self.pause_declicker.fade_to_0(&self.values);
} else {
self.apply_parameters();
self.pause_declicker.fade_to_1(&self.values);
}
}
FreeverbNodePatch::SmoothSeconds(value) => {
self.room_size.set_smooth_seconds(value, info.sample_rate);
self.width.set_smooth_seconds(value, info.sample_rate);
self.damping.set_smooth_seconds(value, info.sample_rate);
}
FreeverbNodePatch::CoeffUpdateFactor(value) => {
self.coeff_update_mask = value.mask();
}
}
}
}
fn bypassed(&mut self, bypassed: bool) {
if !bypassed {
self.reset(true);
}
}
fn process(
&mut self,
info: &ProcInfo,
buffers: ProcBuffers,
_: &mut ProcExtra,
) -> ProcessStatus {
let all_silent = info.in_silence_mask.all_channels_silent(2);
if (self.paused && self.pause_declicker.has_settled())
|| (all_silent && self.prev_output_was_silent)
{
self.reset(false);
self.prev_output_was_silent = true;
return ProcessStatus::ClearAllOutputs;
}
if !all_silent && self.prev_output_was_silent {
self.apply_parameters();
}
assert!(buffers.inputs[0].len() >= info.frames);
assert!(buffers.inputs[1].len() >= info.frames);
assert!(buffers.outputs[0].len() >= info.frames);
assert!(buffers.outputs[1].len() >= info.frames);
if self.damping.is_smoothing() || self.room_size.is_smoothing() || self.width.is_smoothing()
{
for frame in 0..info.frames {
let damping = self.damping.next_smoothed();
let room_size = self.room_size.next_smoothed();
let width = self.width.next_smoothed();
if self.coeff_update_mask.do_update(frame) {
calc_coeffs(&mut self.freeverb, damping, room_size, width);
}
let (left, right) = self.freeverb.tick((
buffers.inputs[0][frame] as f64,
buffers.inputs[1][frame] as f64,
));
buffers.outputs[0][frame] = left as f32;
buffers.outputs[1][frame] = right as f32;
}
self.damping.settle();
self.room_size.settle();
self.width.settle();
} else {
for frame in 0..info.frames {
let (left, right) = self.freeverb.tick((
buffers.inputs[0][frame] as f64,
buffers.inputs[1][frame] as f64,
));
buffers.outputs[0][frame] = left as f32;
buffers.outputs[1][frame] = right as f32;
}
}
if all_silent && !self.prev_output_was_silent {
if matches!(
buffers.check_for_silence_on_outputs(DEFAULT_MIN_AMP),
ProcessStatus::ClearAllOutputs
) {
self.prev_output_was_silent = true;
return ProcessStatus::ClearAllOutputs;
}
}
if !self.pause_declicker.has_settled() {
self.pause_declicker.process(
&mut buffers.outputs[..2],
0..info.frames,
&self.values,
1.0,
DeclickFadeCurve::EqualPower3dB,
);
}
self.prev_output_was_silent = false;
ProcessStatus::OutputsModified
}
fn new_stream(&mut self, stream_info: &firewheel_core::StreamInfo, _proc: &mut ProcStreamCtx) {
self.freeverb.resize(stream_info.sample_rate.get() as usize);
self.damping.update_sample_rate(stream_info.sample_rate);
self.width.update_sample_rate(stream_info.sample_rate);
self.room_size.update_sample_rate(stream_info.sample_rate);
self.reset(true);
self.prev_output_was_silent = true;
}
}
impl FreeverbProcessor {
fn apply_parameters(&mut self) {
self.freeverb
.set_dampening(self.damping.target_value() as f64);
self.freeverb
.set_room_size(self.room_size.target_value() as f64);
self.freeverb.set_width(self.width.target_value() as f64);
self.freeverb.update_combs();
}
}
#[cold]
#[inline(never)]
fn calc_coeffs(freeverb: &mut Freeverb, damping: f32, room_size: f32, width: f32) {
freeverb.set_dampening(damping as f64);
freeverb.set_room_size(room_size as f64);
freeverb.set_width(width as f64);
freeverb.update_combs();
}