SamiPerttu / fundsp

Library for audio processing and synthesis
Apache License 2.0
748 stars 41 forks source link

Phase vocoder pitch shifter #48

Closed Sorseg closed 11 hours ago

Sorseg commented 1 month ago

Hi! I've managed to implement a pitch-shifter that sounds decently to me, using the "phase vocoder" approach, nicely explained in this youtube playlist. The resynth node was a great timesaver!

However I am not fully grokking the fundsp API so I could use some help with the PR.

The whole code, provided by me under CC0

```rust let pitch_shift = 0.8_f32; const FRAME_SIZE: usize = 512 * 2; // FIXME: these should probably be of size `FRAME_SIZE/2 + 1` let mut input_phases = [0.0; FRAME_SIZE]; let mut incoming_frequencies = [0.0; FRAME_SIZE]; let mut outgoing_phases = [0.0; FRAME_SIZE]; let bin_width = rec_sample_rate as f32 / FRAME_SIZE as f32; // copy-pasted from the resynth const WINDOWS: usize = 4; // how much time passes between the resynth calls let dt = FRAME_SIZE as f32 / pb_sample_rate as f32 / WINDOWS as f32; let pitch_shift = resynth::(FRAME_SIZE, move |fft: &mut FftWindow| { for i in 0..(fft.bins() - 1) { let freq = fft.frequency(i); // phase [-pi, pi] let (amplitude, phase) = fft.at(0, i).to_polar(); // [0, tau) let phase_delta = (phase - input_phases[i] + TAU) % TAU; // [0, tau) let expected_phase_d = freq * dt * TAU % TAU; // [-pi, pi) let phase_error = (phase_delta - expected_phase_d) % PI; // phase offset to frequency let freq_deviation = phase_error / TAU / dt; let bin_deviation = freq_deviation / bin_width; incoming_frequencies[i] = i as f32 + bin_deviation; input_phases[i] = phase; // calc output let newbin = (i as f32 * pitch_shift).round() as usize; if newbin > 0 && newbin < fft.bins() { let bin_deviation = incoming_frequencies[i] * pitch_shift - newbin as f32; let freq = bin_deviation * bin_width + fft.frequency(newbin); let phase_diff = freq * dt * TAU; let out_phase = (outgoing_phases[newbin] + phase_diff) % TAU; fft.set(0, newbin, Complex32::from_polar(amplitude, out_phase)); outgoing_phases[newbin] = out_phase; } } }); ```

Used in this voice changer project https://github.com/Sorseg/combiner/tree/master

rhizoome commented 1 week ago

Very interesting. I came up with a novel idea for a synth, basically because I assumed I will never in my life find out how to properly align phases in resynth. I am glad I didn't find your post two weeks ago, because then I would probably just have fixed my wavetables with pitch-shift. That does not mean that pitch-shift will not be handy in the future. Thanks!

SamiPerttu commented 11 hours ago

Hi! Thanks for the snippet. I think you understood the interface just fine, probably better than me. I'm closing this but open another issue if you still have questions.