[opiate, pvoc] implement changing phase and not frequency

main
annieversary 2021-09-16 17:42:18 +02:00
parent be46998301
commit 43a66b4769
3 changed files with 99 additions and 52 deletions

View File

@ -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<PhaseBin>,
out_0: Vec<f32>,
out_1: Vec<f32>,
out_2: Vec<f32>,
@ -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<Bin>], output: &mut [Vec<Bin>]| {
|_channels: usize,
bins: usize,
input: &[Vec<PhaseBin>],
output: &mut [Vec<PhaseBin>]| {
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;
}
}
}

View File

@ -15,32 +15,67 @@ use std::sync::Arc;
#[allow(non_camel_case_types)]
type c64 = Complex<f64>;
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<Self>) -> 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<Self>) -> 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<Self>) -> 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<B: Bin = FreqBin> {
channels: usize,
sample_rate: f64,
frame_size: usize,
@ -61,11 +96,11 @@ pub struct PhaseVocoder {
fft_in: Vec<c64>,
fft_out: Vec<c64>,
fft_scratch: Vec<c64>,
analysis_out: Vec<Vec<Bin>>,
synthesis_in: Vec<Vec<Bin>>,
analysis_out: Vec<Vec<B>>,
synthesis_in: Vec<Vec<B>>,
}
impl PhaseVocoder {
impl<B: Bin> PhaseVocoder<B> {
/// 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<Bin>], &mut [Vec<Bin>]),
F: FnMut(usize, usize, &[Vec<B>], &mut [Vec<B>]),
{
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<Bin>], output: &mut [Vec<Bin>]) {
fn identity(channels: usize, bins: usize, input: &[Vec<FreqBin>], output: &mut [Vec<FreqBin>]) {
for i in 0..channels {
for j in 0..bins {
output[i][j] = input[i][j];

View File

@ -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<const LEN: usize>(
pvoc: &mut PhaseVocoder,
input: &[f32],
@ -17,7 +17,7 @@ pub fn pitch_shift<const LEN: usize>(
pvoc.process(
&[&input],
&mut [&mut output],
|channels: usize, bins: usize, input: &[Vec<Bin>], output: &mut [Vec<Bin>]| {
|channels: usize, bins: usize, input: &[Vec<FreqBin>], output: &mut [Vec<FreqBin>]| {
for i in 0..channels {
for j in 0..bins / 2 {
let index = ((j as f64) * shift) as usize;