pub const PITCH_BUFFER_SIZE: usize = 16384; pub const GRAIN_SIZE: usize = 2048;
pub struct PitchShifter {
buffer: [f32; PITCH_BUFFER_SIZE],
write_idx: usize,
phase1: f32,
semitones: f32,
ratio: f32,
hann_window: [f32; GRAIN_SIZE],
enabled: bool,
}
impl PitchShifter {
pub fn new() -> Self {
let mut hann = [0.0; GRAIN_SIZE];
for i in 0..GRAIN_SIZE {
hann[i] =
0.5 * (1.0 - (2.0 * std::f32::consts::PI * i as f32 / GRAIN_SIZE as f32).cos());
}
Self {
buffer: [0.0; PITCH_BUFFER_SIZE],
write_idx: 0,
phase1: 0.0,
semitones: 0.0,
ratio: 1.0,
hann_window: hann,
enabled: false,
}
}
pub fn set_semitones(&mut self, semitones: f32) {
self.semitones = semitones.clamp(-24.0, 24.0);
self.ratio = 2.0f32.powf(self.semitones / 12.0);
self.enabled = self.semitones.abs() > 0.05;
}
pub fn semitones(&self) -> f32 {
self.semitones
}
pub fn is_enabled(&self) -> bool {
self.enabled
}
#[inline(always)]
fn hermite_interpolate(y0: f32, y1: f32, y2: f32, y3: f32, x: f32) -> f32 {
let c0 = y1;
let c1 = 0.5 * (y2 - y0);
let c2 = y0 - 2.5 * y1 + 2.0 * y2 - 0.5 * y3;
let c3 = 0.5 * (y3 - y0) + 1.5 * (y1 - y2);
((c3 * x + c2) * x + c1) * x + c0
}
#[inline(always)]
fn read_interpolated(&self, read_pos: f32) -> f32 {
let buf_len = PITCH_BUFFER_SIZE as f32;
let pos_norm = ((read_pos % buf_len) + buf_len) % buf_len;
let i1 = pos_norm as usize;
let frac = pos_norm - (i1 as f32);
let i0 = (i1 + PITCH_BUFFER_SIZE - 1) % PITCH_BUFFER_SIZE;
let i2 = (i1 + 1) % PITCH_BUFFER_SIZE;
let i3 = (i1 + 2) % PITCH_BUFFER_SIZE;
Self::hermite_interpolate(
self.buffer[i0],
self.buffer[i1],
self.buffer[i2],
self.buffer[i3],
frac,
)
}
#[inline(always)]
pub fn process(&mut self, input: f32) -> f32 {
if !self.enabled {
return input;
}
let w = self.write_idx;
self.buffer[w] = input;
self.write_idx = (w + 1) % PITCH_BUFFER_SIZE;
let g = GRAIN_SIZE as f32;
let half_g = g * 0.5;
let p1 = self.phase1;
let p2 = if p1 + half_g >= g {
p1 + half_g - g
} else {
p1 + half_g
};
let idx1 = (p1 as usize).min(GRAIN_SIZE - 1);
let idx2 = (p2 as usize).min(GRAIN_SIZE - 1);
let w1 = self.hann_window[idx1];
let w2 = self.hann_window[idx2];
let base_delay = 64.0f32;
let delay1 = base_delay + p1;
let delay2 = base_delay + p2;
let read_pos1 = (w as f32) - delay1;
let read_pos2 = (w as f32) - delay2;
let s1 = self.read_interpolated(read_pos1) * w1;
let s2 = self.read_interpolated(read_pos2) * w2;
if input.abs() > 1e-4 || (s1.abs() + s2.abs()) > 1e-4 {
let slope = 1.0 - self.ratio;
self.phase1 += slope;
while self.phase1 >= g {
self.phase1 -= g;
}
while self.phase1 < 0.0 {
self.phase1 += g;
}
}
s1 + s2
}
pub fn reset(&mut self) {
self.buffer.fill(0.0);
self.write_idx = 0;
self.phase1 = 0.0;
}
}