diff --git a/crates/opiate/src/lib.rs b/crates/opiate/src/lib.rs index 6e01f4e..61489d7 100644 --- a/crates/opiate/src/lib.rs +++ b/crates/opiate/src/lib.rs @@ -4,7 +4,7 @@ // morphing algorithm from https://ccrma.stanford.edu/~jhsu/421b/ use baseplug::{Plugin, ProcessContext}; -use pvoc::{Bin, PhaseVocoder}; +use pvoc::{PhaseBin, PhaseVocoder}; use serde::{Deserialize, Serialize}; use utils::logs::*; @@ -24,7 +24,7 @@ impl Default for OpiateModel { } struct Opiate { - pvoc: PhaseVocoder, + pvoc: PhaseVocoder, out_0: Vec, out_1: Vec, out_2: Vec, @@ -68,10 +68,10 @@ impl Plugin for Opiate { } let out = &mut [ - &mut self.out_0[..], - &mut self.out_1[..], - &mut self.out_2[..], - &mut self.out_3[..], + &mut self.out_0[0..ctx.nframes], + &mut self.out_1[0..ctx.nframes], + &mut self.out_2[0..ctx.nframes], + &mut self.out_3[0..ctx.nframes], ][..]; let morph = model.morph[0] as f64; @@ -79,27 +79,28 @@ impl Plugin for Opiate { self.pvoc.process( input, out, - |_channels: usize, bins: usize, input: &[Vec], output: &mut [Vec]| { + |_channels: usize, + bins: usize, + input: &[Vec], + output: &mut [Vec]| { for j in 0..bins { - // TODO Check if working with the frequencies is the same as working with the phase - // i think we might need to try it to make sure it's the same - // to do that, we'll need to change how pvoc works - + // left let mags = morph * (1.0 - input[0][j].amp) + input[0][j].amp; let mags2 = imorph * (1.0 - input[2][j].amp) + input[2][j].amp; - let phases = input[0][j].freq - (input[0][j].freq * morph); - let phases2 = input[2][j].freq - (input[2][j].freq * imorph); + let phases = input[0][j].phase - (input[0][j].phase * morph); + let phases2 = input[2][j].phase - (input[2][j].phase * imorph); output[0][j].amp = mags * mags2; - output[0][j].freq = phases + phases2; + output[0][j].phase = phases + phases2; + // right let mags = morph * (1.0 - input[1][j].amp) + input[1][j].amp; let mags2 = imorph * (1.0 - input[3][j].amp) + input[3][j].amp; - let phases = input[1][j].freq - (input[1][j].freq * morph); - let phases2 = input[3][j].freq - (input[3][j].freq * imorph); + let phases = input[1][j].phase - (input[1][j].phase * morph); + let phases2 = input[3][j].phase - (input[3][j].phase * imorph); output[1][j].amp = mags * mags2; - output[1][j].freq = phases + phases2; + output[1][j].phase = phases + phases2; } }, ); @@ -107,6 +108,9 @@ impl Plugin for Opiate { for i in 0..ctx.nframes { output[0][i] = self.out_0[i]; output[1][i] = self.out_1[i]; + + self.out_0[i] = 0.0; + self.out_1[i] = 0.0; } } } diff --git a/crates/pvoc-rs/src/lib.rs b/crates/pvoc-rs/src/lib.rs index 0ed5d94..05b75e9 100644 --- a/crates/pvoc-rs/src/lib.rs +++ b/crates/pvoc-rs/src/lib.rs @@ -15,32 +15,67 @@ use std::sync::Arc; #[allow(non_camel_case_types)] type c64 = Complex; +pub trait Bin: Clone + Copy { + fn empty() -> Self; + fn new(freq: f64, phase: f64, amp: f64) -> Self; + fn amp(&self) -> f64; + fn phase(&self, pvoc: &PhaseVocoder) -> f64; +} + +/// Represents a component of the spectrum, composed of a phase and amplitude. +#[derive(Copy, Clone)] +pub struct PhaseBin { + pub phase: f64, + pub amp: f64, +} +impl Bin for PhaseBin { + fn empty() -> Self { + Self { + phase: 0.0, + amp: 0.0, + } + } + fn new(_: f64, phase: f64, amp: f64) -> Self { + Self { phase, amp } + } + + fn amp(&self) -> f64 { + self.amp + } + fn phase(&self, _: &PhaseVocoder) -> f64 { + self.phase + } +} /// Represents a component of the spectrum, composed of a frequency and amplitude. #[derive(Copy, Clone)] -pub struct Bin { +pub struct FreqBin { pub freq: f64, pub amp: f64, } - -impl Bin { - pub fn new(freq: f64, amp: f64) -> Bin { - Bin { - freq: freq, - amp: amp, - } - } - pub fn empty() -> Bin { - Bin { +impl Bin for FreqBin { + fn empty() -> Self { + Self { freq: 0.0, amp: 0.0, } } + fn new(freq: f64, _: f64, amp: f64) -> Self { + Self { freq, amp } + } + + fn amp(&self) -> f64 { + self.amp + } + + fn phase(&self, pvoc: &PhaseVocoder) -> f64 { + pvoc.frequency_to_phase(self.freq) + } } /// A phase vocoder. /// /// Roughly translated from http://blogs.zynaptiq.com/bernsee/pitch-shifting-using-the-ft/ -pub struct PhaseVocoder { +pub struct PhaseVocoder { channels: usize, sample_rate: f64, frame_size: usize, @@ -61,11 +96,11 @@ pub struct PhaseVocoder { fft_in: Vec, fft_out: Vec, fft_scratch: Vec, - analysis_out: Vec>, - synthesis_in: Vec>, + analysis_out: Vec>, + synthesis_in: Vec>, } -impl PhaseVocoder { +impl PhaseVocoder { /// Constructs a new phase vocoder. /// /// `channels` is the number of channels of audio. @@ -80,12 +115,7 @@ impl PhaseVocoder { /// /// # Panics /// Panics if `frame_size` is `<= 1` after rounding. - pub fn new( - channels: usize, - sample_rate: f64, - frame_size: usize, - time_res: usize, - ) -> PhaseVocoder { + pub fn new(channels: usize, sample_rate: f64, frame_size: usize, time_res: usize) -> Self { let mut frame_size = frame_size / time_res * time_res; if frame_size == 0 { frame_size = time_res; @@ -119,8 +149,8 @@ impl PhaseVocoder { fft_in: vec![c64::new(0.0, 0.0); frame_size], fft_out: vec![c64::new(0.0, 0.0); frame_size], fft_scratch: vec![], - analysis_out: vec![vec![Bin::empty(); frame_size]; channels], - synthesis_in: vec![vec![Bin::empty(); frame_size]; channels], + analysis_out: vec![vec![B::empty(); frame_size]; channels], + synthesis_in: vec![vec![B::empty(); frame_size]; channels], }; pv.fft_scratch = vec![ c64::new(0.0, 0.0); @@ -182,10 +212,18 @@ impl PhaseVocoder { ) -> usize where S: Float + ToPrimitive + FromPrimitive, - F: FnMut(usize, usize, &[Vec], &mut [Vec]), + F: FnMut(usize, usize, &[Vec], &mut [Vec]), { - assert_eq!(input.len(), self.channels); - assert_eq!(output.len(), self.channels); + assert_eq!( + input.len(), + self.channels, + "input length does not equal channel count" + ); + assert_eq!( + output.len(), + self.channels, + "output length does not equal channel count" + ); // push samples to input queue for chan in 0..input.len() { @@ -205,7 +243,7 @@ impl PhaseVocoder { // This may be removed in a future release. for synthesis_channel in self.synthesis_in.iter_mut() { for bin in synthesis_channel.iter_mut() { - *bin = Bin::empty(); + *bin = B::empty(); } } @@ -225,10 +263,13 @@ impl PhaseVocoder { for i in 0..self.frame_size { let x = self.fft_out[i]; let (amp, phase) = x.to_polar(); - let freq = self.phase_to_frequency(i, phase - self.last_phase[chan][i]); + let bin_phase = phase - self.last_phase[chan][i]; + let freq = self.phase_to_frequency(i, bin_phase); self.last_phase[chan][i] = phase; - self.analysis_out[chan][i] = Bin::new(freq, amp * 2.0); + // yeah passing both and letting the constructor decide is ugly + // but it's fast to do so + self.analysis_out[chan][i] = B::new(freq, bin_phase, amp * 2.0); } } @@ -243,9 +284,11 @@ impl PhaseVocoder { // SYNTHESIS for chan in 0..self.channels { for i in 0..self.frame_size { - let amp = self.synthesis_in[chan][i].amp; - let freq = self.synthesis_in[chan][i].freq; - let phase = self.frequency_to_phase(freq); + let amp = self.synthesis_in[chan][i].amp(); + // passing self as a param is slightly ugly but hey + // it works + let phase = self.synthesis_in[chan][i].phase(self); + self.sum_phase[chan][i] += phase; let phase = self.sum_phase[chan][i]; @@ -318,7 +361,7 @@ impl PhaseVocoder { } #[cfg(test)] -fn identity(channels: usize, bins: usize, input: &[Vec], output: &mut [Vec]) { +fn identity(channels: usize, bins: usize, input: &[Vec], output: &mut [Vec]) { for i in 0..channels { for j in 0..bins { output[i][j] = input[i][j]; diff --git a/crates/utils/src/pitch/mod.rs b/crates/utils/src/pitch/mod.rs index fe83015..b425b81 100644 --- a/crates/utils/src/pitch/mod.rs +++ b/crates/utils/src/pitch/mod.rs @@ -5,7 +5,7 @@ pub fn generate_vocoder(sample_rate: u32) -> PhaseVocoder { } // From https://github.com/nwoeanhinnogaehr/pvoc-plugins/blob/master/src/plugins/pitchshifter.rs -use pvoc::{Bin, PhaseVocoder}; +use pvoc::{FreqBin, PhaseVocoder}; pub fn pitch_shift( pvoc: &mut PhaseVocoder, input: &[f32], @@ -17,7 +17,7 @@ pub fn pitch_shift( pvoc.process( &[&input], &mut [&mut output], - |channels: usize, bins: usize, input: &[Vec], output: &mut [Vec]| { + |channels: usize, bins: usize, input: &[Vec], output: &mut [Vec]| { for i in 0..channels { for j in 0..bins / 2 { let index = ((j as f64) * shift) as usize;