Skip to content

Commit

Permalink
Implement more performance efficient variable delay line
Browse files Browse the repository at this point in the history
  • Loading branch information
oamaok committed Jan 16, 2024
1 parent 38ccfc3 commit 5d9a542
Show file tree
Hide file tree
Showing 4 changed files with 137 additions and 74 deletions.
184 changes: 123 additions & 61 deletions worklets/src/modulate_core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -254,110 +254,172 @@ pub fn exp_curve(x: f32) -> f32 {
(3.0 + x * (-13.0 + 5.0 * x)) / (3.0 + 2.0 * x)
}

pub struct RingBuffer {
buffers: [Vec<f32>; 2],
current: usize,
length: usize,
pos: usize,
}
pub struct VariableDelayLineInterpolated {
size: usize,
buffer: Vec<f32>,
write_pos: usize,
read_pos: usize,
read_pos_fract: f32,
feedback: f32,
}

impl VariableDelayLineInterpolated {
pub fn new(size: usize, delay: f32) -> VariableDelayLineInterpolated {
VariableDelayLineInterpolated {
size,
buffer: vec![0.0; size],
write_pos: delay as usize,
read_pos: 0,
read_pos_fract: 1.0 - delay.fract(),
feedback: 0.0,
}
}

impl RingBuffer {
pub fn new(length: usize) -> RingBuffer {
RingBuffer {
buffers: [vec![0.0; SAMPLE_RATE * 16], vec![0.0; SAMPLE_RATE * 16]],
current: 0,
length,
pos: 0,
pub fn set_delay(&mut self, delay: f32) {
assert!(delay < self.size as f32);

self.read_pos_fract = 1.0 - delay.fract();
let delay_int = delay as i32;

let mut read_pos = self.write_pos as i32 - delay_int as i32;
if read_pos < 0 {
read_pos += self.size as i32;
}
self.read_pos = read_pos as usize;
}

pub fn set_feedback(&mut self, feedback: f32) {
self.feedback = feedback;
}

pub fn rms(&self) -> f32 {
let mut value = 0.0;
pub fn step(&mut self, input: f32) -> f32 {
let curr = self.buffer[self.read_pos];
let next = {
if self.read_pos == self.size - 1 {
self.buffer[0]
} else {
self.buffer[self.read_pos + 1]
}
};

let delayed = lerp(curr, next, self.read_pos_fract);

self.buffer[self.write_pos] = input + delayed * self.feedback;

for i in 0..self.length {
value += self.buffers[self.current][i] * self.buffers[self.current][i];
self.write_pos += 1;
self.read_pos += 1;

if self.write_pos >= self.size {
self.write_pos = 0;
}

value /= self.length as f32;
value = f32::sqrt(value);
if self.read_pos >= self.size {
self.read_pos = 0;
}

value
return delayed;
}
}

pub fn resize(&mut self, mut len: usize) {
if len == 0 {
len = 1
}
if self.length == len {
return;
pub struct VariableDelayLine {
size: usize,
buffer: Vec<f32>,
write_pos: usize,
read_pos: usize,
feedback: f32,
}

impl VariableDelayLine {
pub fn new(size: usize, delay: usize) -> VariableDelayLine {
assert!(delay < size);
VariableDelayLine {
size,
buffer: vec![0.0; size],
write_pos: delay,
read_pos: 0,
feedback: 0.0,
}
}

let ratio = self.length as f32 / len as f32;
pub fn set_delay(&mut self, delay: usize) {
assert!(delay < self.size);

let dst = if self.current == 0 {
self.buffers[1].as_mut_ptr()
} else {
self.buffers[0].as_mut_ptr()
};
let mut read_pos = self.write_pos as i32 - delay as i32;
if read_pos < 0 {
read_pos += self.size as i32;
}
self.read_pos = read_pos as usize;
}

let mut pos = 0.0;
for sample in 0..len {
let ipos = pos as u32;
let src_index = ipos as usize;
let t = pos - ipos as f32;
pub fn set_feedback(&mut self, feedback: f32) {
self.feedback = feedback;
}

let a = self.at_usize(src_index + self.pos);
let b = self.at_usize(src_index + 1 + self.pos);
pub fn step(&mut self, input: f32) -> f32 {
let delayed = self.buffer[self.read_pos];

unsafe {
*dst.add(sample) = lerp(a, b, t);
}
self.buffer[self.write_pos] = input + delayed * self.feedback;

pos += ratio;
self.write_pos += 1;
self.read_pos += 1;

if self.write_pos >= self.size {
self.write_pos = 0;
}

self.current = if self.current == 0 { 1 } else { 0 };
if self.read_pos >= self.size {
self.read_pos = 0;
}

self.pos = 0;
self.length = len;
return delayed;
}
}

pub fn at_usize(&self, mut index: usize) -> f32 {
index %= self.length;
self.buffers[self.current][index as usize]
}
pub struct RingBuffer {
buffer: Vec<f32>,
position: usize,
}

pub fn head(&self) -> f32 {
self.buffers[self.current][self.pos]
impl RingBuffer {
pub fn new(size: usize) -> RingBuffer {
RingBuffer {
buffer: vec![0.0; size],
position: 0,
}
}

pub fn write(&mut self, value: f32) {
self.buffers[self.current][self.pos] = value;
self.pos = (self.pos + 1) % self.length;
self.buffer[self.position] = value;
self.position += 1;
if self.position >= self.buffer.len() {
self.position = 0;
}
}

pub fn head(&self) -> f32 {
self.buffer[self.position]
}
}

pub struct FeedbackCombFilter {
buffer: RingBuffer,
delay: VariableDelayLine,
pub gain: f32,
}

impl FeedbackCombFilter {
pub fn new(len: usize, gain: f32) -> FeedbackCombFilter {
pub fn new(delay: usize, gain: f32) -> FeedbackCombFilter {
FeedbackCombFilter {
buffer: RingBuffer::new(len),
delay: VariableDelayLine::new(SAMPLE_RATE, delay),
gain,
}
}

pub fn set_delay(&mut self, length: usize) {
self.buffer.resize(length)
self.delay.set_delay(length);
}

pub fn step(&mut self, input: f32) -> f32 {
let value = self.buffer.head() * self.gain + input;
self.buffer.write(value);
value
self.delay.step(input) * self.gain + input
}
}

Expand Down
4 changes: 1 addition & 3 deletions worklets/src/modules/chorus.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
use std::f32::consts::PI;

use crate::{
modulate_core::{
lerp, AudioInput, AudioOutput, AudioParam, QUANTUM_SIZE, SAMPLE_RATE,
},
modulate_core::{lerp, AudioInput, AudioOutput, AudioParam, QUANTUM_SIZE, SAMPLE_RATE},
module::Module,
};

Expand Down
20 changes: 11 additions & 9 deletions worklets/src/modules/delay.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use crate::{
modulate_core::{AudioInput, AudioOutput, AudioParam, RingBuffer, QUANTUM_SIZE, SAMPLE_RATE},
modulate_core::{
AudioInput, AudioOutput, AudioParam, VariableDelayLineInterpolated, QUANTUM_SIZE, SAMPLE_RATE,
},
module::Module,
};

Expand All @@ -12,19 +14,19 @@ pub struct Delay {
wet: AudioParam,
dry: AudioParam,

buffer: RingBuffer,
delay: VariableDelayLineInterpolated,
}

impl Module for Delay {
fn process(&mut self, _quantum: u64) {
self
.buffer
.resize((self.time.at(0) * SAMPLE_RATE as f32) as usize);

for sample in 0..QUANTUM_SIZE {
self
.delay
.set_delay(self.time.at(sample) * SAMPLE_RATE as f32);
self.delay.set_feedback(self.feedback.at(sample));

let input = self.input.at(sample);
let wet = self.buffer.head();
self.buffer.write(input + wet * self.feedback.at(sample));
let wet = self.delay.step(input);
self.output[sample] = wet * self.wet.at(sample) + input * self.dry.at(sample);
}
}
Expand Down Expand Up @@ -58,7 +60,7 @@ impl Delay {
wet: AudioParam::default(),
dry: AudioParam::default(),

buffer: RingBuffer::new(10000),
delay: VariableDelayLineInterpolated::new(SAMPLE_RATE * 10, 10000.0),
})
}
}
3 changes: 2 additions & 1 deletion worklets/src/modules/limiter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ pub struct Limiter {
impl Module for Limiter {
fn process(&mut self, _quantum: u64) {
for sample in 0..QUANTUM_SIZE {
let rms = self.buffer.rms();
// FIXME: Implement new RMS/Peak calculation for limiter
let rms = 0.5;
let threshold = self.threshold.at(sample);
let ratio = if rms > threshold {
threshold / rms
Expand Down

0 comments on commit 5d9a542

Please sign in to comment.