use super::{AudioNode, ChannelConfig, ChannelConfigOptions, ChannelInterpretation};
use crate::buffer::AudioBuffer;
use crate::context::{AudioContextRegistration, BaseAudioContext};
use crate::render::{AudioParamValues, AudioProcessor, AudioRenderQuantum, RenderScope};
use crate::RENDER_QUANTUM_SIZE;
use crossbeam_channel::{Receiver, Sender};
use realfft::{num_complex::Complex, ComplexToReal, RealFftPlanner, RealToComplex};
use std::sync::{
atomic::{AtomicBool, Ordering},
Arc, Mutex,
};
fn normalization(buffer: &AudioBuffer) -> f32 {
let gain_calibration = 0.00125;
let gain_calibration_sample_rate = 44100.;
let min_power = 0.000125;
let number_of_channels = buffer.number_of_channels();
let length = buffer.length();
let sample_rate = buffer.sample_rate();
let mut power: f32 = buffer
.channels()
.iter()
.map(|c| c.as_slice().iter().map(|&s| s * s).sum::<f32>())
.sum();
power = (power / (number_of_channels * length) as f32).sqrt();
if !power.is_finite() || power.is_nan() || power < min_power {
power = min_power;
}
let mut scale = 1. / power;
scale *= gain_calibration;
scale *= gain_calibration_sample_rate / sample_rate;
if number_of_channels == 4 {
scale *= 0.5;
}
scale
}
#[derive(Clone, Debug, Default)]
pub struct ConvolverOptions {
pub buffer: Option<AudioBuffer>,
pub disable_normalization: bool,
pub channel_config: ChannelConfigOptions,
}
pub struct ConvolverNode {
registration: AudioContextRegistration,
channel_config: ChannelConfig,
normalize: AtomicBool,
buffer: Mutex<Option<AudioBuffer>>,
sender: Sender<ConvolverRendererInner>,
}
impl AudioNode for ConvolverNode {
fn registration(&self) -> &AudioContextRegistration {
&self.registration
}
fn channel_config(&self) -> &ChannelConfig {
&self.channel_config
}
fn number_of_inputs(&self) -> usize {
1
}
fn number_of_outputs(&self) -> usize {
1
}
}
impl ConvolverNode {
pub fn new<C: BaseAudioContext>(context: &C, options: ConvolverOptions) -> Self {
context.base().register(move |registration| {
let ConvolverOptions {
buffer,
disable_normalization,
channel_config,
} = options;
let (sender, receiver) = crossbeam_channel::bounded(1);
let renderer = ConvolverRenderer::new(receiver);
let node = Self {
registration,
channel_config: channel_config.into(),
normalize: AtomicBool::new(!disable_normalization),
sender,
buffer: Mutex::new(None),
};
if let Some(buffer) = buffer {
node.set_buffer(buffer);
}
(node, Box::new(renderer))
})
}
#[allow(clippy::missing_panics_doc)]
pub fn buffer(&self) -> Option<AudioBuffer> {
self.buffer.lock().unwrap().clone()
}
pub fn set_buffer(&self, mut buffer: AudioBuffer) {
buffer.resample(self.context().sample_rate());
let sample_rate = buffer.sample_rate();
let scale = if self.normalize() {
normalization(&buffer)
} else {
1.
};
let length = buffer.length();
let padded_length = length.next_power_of_two().max(2 * RENDER_QUANTUM_SIZE);
let samples: Vec<_> = (0..buffer.number_of_channels())
.map(|_| {
let mut samples = vec![0.; padded_length];
samples[..length]
.iter_mut()
.zip(buffer.get_channel_data(0))
.for_each(|(o, i)| *o = *i * scale);
samples
})
.collect();
let padded_buffer = AudioBuffer::from(samples, sample_rate);
let convolve = ConvolverRendererInner::new(padded_buffer);
let _ = self.sender.send(convolve);
*self.buffer.lock().unwrap() = Some(buffer);
}
pub fn normalize(&self) -> bool {
self.normalize.load(Ordering::SeqCst)
}
pub fn set_normalize(&self, value: bool) {
self.normalize.store(value, Ordering::SeqCst);
}
}
fn roll_zero<T: Default + Copy>(signal: &mut [T], n: usize) {
let len = signal.len();
signal.copy_within(n.., 0);
signal[len - n..].fill(T::default());
}
struct Fft {
fft_forward: Arc<dyn RealToComplex<f32>>,
fft_inverse: Arc<dyn ComplexToReal<f32>>,
fft_input: Vec<f32>,
fft_scratch: Vec<Complex<f32>>,
fft_output: Vec<Complex<f32>>,
}
impl Fft {
fn new(length: usize) -> Self {
let mut fft_planner = RealFftPlanner::<f32>::new();
let fft_forward = fft_planner.plan_fft_forward(length);
let fft_inverse = fft_planner.plan_fft_inverse(length);
let fft_input = fft_forward.make_input_vec();
let fft_scratch = fft_forward.make_scratch_vec();
let fft_output = fft_forward.make_output_vec();
Self {
fft_forward,
fft_inverse,
fft_input,
fft_scratch,
fft_output,
}
}
fn real(&mut self) -> &mut [f32] {
&mut self.fft_input[..]
}
fn complex(&mut self) -> &mut [Complex<f32>] {
&mut self.fft_output[..]
}
fn process(&mut self) -> &[Complex<f32>] {
self.fft_forward
.process_with_scratch(
&mut self.fft_input,
&mut self.fft_output,
&mut self.fft_scratch,
)
.unwrap();
&self.fft_output[..]
}
fn inverse(&mut self) -> &[f32] {
self.fft_inverse
.process_with_scratch(
&mut self.fft_output,
&mut self.fft_input,
&mut self.fft_scratch,
)
.unwrap();
&self.fft_input[..]
}
}
struct ConvolverRenderer {
receiver: Receiver<ConvolverRendererInner>,
inner: Option<ConvolverRendererInner>,
}
impl ConvolverRenderer {
fn new(receiver: Receiver<ConvolverRendererInner>) -> Self {
Self {
receiver,
inner: None,
}
}
}
struct ConvolverRendererInner {
num_ir_blocks: usize,
h: Vec<Complex<f32>>,
fdl: Vec<Complex<f32>>,
out: Vec<f32>,
fft2: Fft,
}
impl ConvolverRendererInner {
fn new(response: AudioBuffer) -> Self {
let response = response.channel_data(0).as_slice();
let mut fft2 = Fft::new(2 * RENDER_QUANTUM_SIZE);
let p = response.len();
let num_ir_blocks = p / RENDER_QUANTUM_SIZE;
let mut h = vec![Complex::default(); num_ir_blocks * 2 * RENDER_QUANTUM_SIZE];
for (resp_fft, resp) in h
.chunks_mut(2 * RENDER_QUANTUM_SIZE)
.zip(response.chunks(RENDER_QUANTUM_SIZE))
{
fft2.real()[..RENDER_QUANTUM_SIZE].copy_from_slice(resp);
fft2.real()[RENDER_QUANTUM_SIZE..].fill(0.);
resp_fft[..fft2.complex().len()].copy_from_slice(fft2.process());
}
let fdl = vec![Complex::default(); 2 * RENDER_QUANTUM_SIZE * num_ir_blocks];
let out = vec![0.; 2 * RENDER_QUANTUM_SIZE - 1];
Self {
num_ir_blocks,
h,
fdl,
out,
fft2,
}
}
fn process(&mut self, input: &[f32], output: &mut [f32]) {
self.fft2.real()[..RENDER_QUANTUM_SIZE].copy_from_slice(input);
self.fft2.real()[RENDER_QUANTUM_SIZE..].fill(0.);
let spectrum = self.fft2.process();
self.fdl
.chunks_mut(2 * RENDER_QUANTUM_SIZE)
.zip(self.h.chunks(2 * RENDER_QUANTUM_SIZE))
.for_each(|(fdl_c, h_c)| {
fdl_c
.iter_mut()
.zip(h_c)
.zip(spectrum)
.for_each(|((f, h), s)| *f += h * s)
});
let c_len = self.fft2.complex().len();
self.fft2.complex().copy_from_slice(&self.fdl[..c_len]);
let inverse = self.fft2.inverse();
self.out.iter_mut().zip(inverse).for_each(|(o, i)| {
*o += i / (2 * RENDER_QUANTUM_SIZE) as f32;
});
output.copy_from_slice(&self.out[..RENDER_QUANTUM_SIZE]);
roll_zero(&mut self.fdl[..], 2 * RENDER_QUANTUM_SIZE);
roll_zero(&mut self.out[..], RENDER_QUANTUM_SIZE);
}
fn tail(&mut self, output: &mut AudioRenderQuantum) -> bool {
if self.num_ir_blocks == 0 {
output.make_silent();
return false;
}
self.num_ir_blocks -= 1;
let c_len = self.fft2.complex().len();
self.fft2.complex().copy_from_slice(&self.fdl[..c_len]);
let inverse = self.fft2.inverse();
self.out.iter_mut().zip(inverse).for_each(|(o, i)| {
*o += i / (2 * RENDER_QUANTUM_SIZE) as f32;
});
output
.channel_data_mut(0)
.copy_from_slice(&self.out[..RENDER_QUANTUM_SIZE]);
roll_zero(&mut self.fdl[..], 2 * RENDER_QUANTUM_SIZE);
roll_zero(&mut self.out[..], RENDER_QUANTUM_SIZE);
self.num_ir_blocks > 0
}
}
impl AudioProcessor for ConvolverRenderer {
fn process(
&mut self,
inputs: &[AudioRenderQuantum],
outputs: &mut [AudioRenderQuantum],
_params: AudioParamValues,
_scope: &RenderScope,
) -> bool {
let input = &inputs[0];
let output = &mut outputs[0];
output.force_mono();
if let Ok(msg) = self.receiver.try_recv() {
self.inner = Some(msg);
}
let convolver = match &mut self.inner {
None => {
*output = input.clone();
return !input.is_silent();
}
Some(convolver) => convolver,
};
if input.is_silent() {
return convolver.tail(output);
}
let mut mono = input.clone();
mono.mix(1, ChannelInterpretation::Speakers);
let input = &mono.channel_data(0)[..];
let output = &mut output.channel_data_mut(0)[..];
convolver.process(input, output);
true
}
}
#[cfg(test)]
mod tests {
use float_eq::assert_float_eq;
use crate::context::{BaseAudioContext, OfflineAudioContext};
use crate::node::{AudioBufferSourceNode, AudioBufferSourceOptions, AudioScheduledSourceNode};
use super::*;
#[test]
fn test_roll_zero() {
let mut input = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
roll_zero(&mut input, 3);
assert_eq!(&input, &[4, 5, 6, 7, 8, 9, 10, 0, 0, 0]);
}
fn test_convolve(signal: &[f32], impulse_resp: Option<Vec<f32>>, length: usize) -> AudioBuffer {
let sample_rate = 44100.;
let context = OfflineAudioContext::new(1, length, sample_rate);
let input = AudioBuffer::from(vec![signal.to_vec()], sample_rate);
let src = AudioBufferSourceNode::new(&context, AudioBufferSourceOptions::default());
src.set_buffer(input);
src.start();
let conv = ConvolverNode::new(&context, ConvolverOptions::default());
if let Some(ir) = impulse_resp {
conv.set_buffer(AudioBuffer::from(vec![ir.to_vec()], sample_rate));
}
src.connect(&conv);
conv.connect(&context.destination());
context.start_rendering_sync()
}
#[test]
fn test_passthrough() {
let output = test_convolve(&[0., 1., 0., -1., 0.], None, 10);
let expected = vec![0., 1., 0., -1., 0., 0., 0., 0., 0., 0.];
assert_float_eq!(output.get_channel_data(0), &expected[..], abs_all <= 1E-6);
}
#[test]
fn test_empty() {
let ir = vec![];
let output = test_convolve(&[0., 1., 0., -1., 0.], Some(ir), 10);
let expected = vec![0.; 10];
assert_float_eq!(output.get_channel_data(0), &expected[..], abs_all <= 1E-6);
}
#[test]
fn test_zeroed() {
let ir = vec![0., 0., 0., 0., 0., 0.];
let output = test_convolve(&[0., 1., 0., -1., 0.], Some(ir), 10);
let expected = vec![0.; 10];
assert_float_eq!(output.get_channel_data(0), &expected[..], abs_all <= 1E-6);
}
#[test]
fn test_identity() {
let ir = vec![1.];
let calibration = 0.00125;
let output = test_convolve(&[0., 1., 0., -1., 0.], Some(ir), 10);
let expected = vec![0., calibration, 0., -calibration, 0., 0., 0., 0., 0., 0.];
assert_float_eq!(output.get_channel_data(0), &expected[..], abs_all <= 1E-6);
}
#[test]
fn test_two_id() {
let ir = vec![1., 1.];
let calibration = 0.00125;
let output = test_convolve(&[0., 1., 0., -1., 0.], Some(ir), 10);
let expected = vec![
0.,
calibration,
calibration,
-calibration,
-calibration,
0.,
0.,
0.,
0.,
0.,
];
assert_float_eq!(output.get_channel_data(0), &expected[..], abs_all <= 1E-6);
}
#[test]
fn test_should_have_tail_time() {
const IR_LEN: usize = 256;
let ir = vec![1.; IR_LEN];
let input = &[1.];
let output = test_convolve(input, Some(ir), 512);
let output = output.channel_data(0).as_slice();
assert!(!output[..IR_LEN].iter().any(|v| *v <= 1E-6));
assert_float_eq!(&output[IR_LEN..], &[0.; 512 - IR_LEN][..], abs_all <= 1E-6);
}
#[test]
fn test_resample() {
let ctx_sample_rate = 44100.;
let ir_sample_rate = 48000.;
let context = OfflineAudioContext::new(1, 128, ctx_sample_rate);
let conv = ConvolverNode::new(&context, ConvolverOptions::default());
let ir = vec![1.; 128];
let ir_buffer = AudioBuffer::from(vec![ir], ir_sample_rate);
conv.set_buffer(ir_buffer);
assert_eq!(conv.buffer().unwrap().sample_rate(), ctx_sample_rate);
}
}