132 lines
4.5 KiB
Rust
132 lines
4.5 KiB
Rust
use ringbuf::{Consumer, Producer};
|
|
|
|
use utils::buffers::*;
|
|
use utils::pitch::*;
|
|
|
|
use crate::BUFFER_LEN;
|
|
|
|
type SampleRate = u32;
|
|
|
|
pub struct ProcessChunk {
|
|
pub(crate) buffers: Buffers<BUFFER_LEN>,
|
|
pub(crate) sample_rate: SampleRate,
|
|
/// Midi note number to shift frequency to
|
|
pub(crate) note: Option<u8>,
|
|
/// If true, will listen to note
|
|
/// If false, will snap to closest note
|
|
pub(crate) manual: bool,
|
|
/// Extra frequency shifting to do
|
|
pub(crate) freq_gain: f32,
|
|
}
|
|
|
|
pub fn tuna(mut inputs: Consumer<ProcessChunk>, mut outputs: Producer<Buffers<BUFFER_LEN>>) {
|
|
// Keep track of last detected note, and use it in case of not detecting a new one
|
|
let mut prev_l_freq: Option<f32> = None;
|
|
let mut prev_r_freq: Option<f32> = None;
|
|
|
|
let mut detector_l = generate_pitch_detector(BUFFER_LEN);
|
|
let mut detector_r = generate_pitch_detector(BUFFER_LEN);
|
|
|
|
// Sample rates get overriden on first iteration, so we just set 48k
|
|
let mut shifter_l = generate_vocoder(48000);
|
|
let mut shifter_r = generate_vocoder(48000);
|
|
|
|
loop {
|
|
if let Some(ProcessChunk {
|
|
buffers: recording,
|
|
sample_rate,
|
|
note,
|
|
manual,
|
|
freq_gain,
|
|
}) = inputs.pop()
|
|
{
|
|
log::info!("got a buffer to process");
|
|
|
|
// If we're on manual mode, and we don't have a note, just pass through
|
|
if manual && note.is_none() {
|
|
let _ = outputs.push(recording);
|
|
continue;
|
|
}
|
|
|
|
// TODO It does weird stereo things
|
|
|
|
// Update sample rate
|
|
shifter_l.set_sample_rate(sample_rate as f64);
|
|
shifter_r.set_sample_rate(sample_rate as f64);
|
|
|
|
// Left
|
|
let l = recording.l;
|
|
// Try detecting note
|
|
let l = if let Some((actual, _clarity)) = pitch_detect(&mut detector_l, &l, sample_rate)
|
|
{
|
|
log::info!("L: detected actual pitch: {}", actual);
|
|
|
|
// If note is found, set it as previous, and pitch shift
|
|
prev_l_freq = Some(actual);
|
|
|
|
// If it's on manual mode, convert midi note to pitch
|
|
// If not, snap to closest frequency
|
|
let expected = if manual {
|
|
midi_note_to_pitch(note.expect("We wouldn't be here if note is None"))
|
|
} else {
|
|
closest_note_freq(actual)
|
|
};
|
|
|
|
// Perform pitch shift
|
|
// `expected / actual` is how much to shift the pitch
|
|
// If the actual pitch is 400, and expected is 800, we want to shift by 2
|
|
pitch_shift(&mut shifter_l, &l, freq_gain * expected / actual)
|
|
} else if let Some(actual) = prev_l_freq {
|
|
log::info!("L: reusing actual pitch: {}", actual);
|
|
|
|
let expected = if manual {
|
|
midi_note_to_pitch(note.expect("We wouldn't be here if note is None"))
|
|
} else {
|
|
closest_note_freq(actual)
|
|
};
|
|
|
|
pitch_shift(&mut shifter_l, &l, freq_gain * expected / actual)
|
|
} else {
|
|
log::info!("L: no actual pitch");
|
|
|
|
// If there's nothing, leave it as is
|
|
l
|
|
};
|
|
|
|
// Same thing for the right side
|
|
let r = recording.r;
|
|
let r = if let Some((actual, _clarity)) = pitch_detect(&mut detector_r, &r, sample_rate)
|
|
{
|
|
log::info!("R: detected actual pitch: {}", actual);
|
|
|
|
prev_r_freq = Some(actual);
|
|
|
|
let expected = if manual {
|
|
midi_note_to_pitch(note.expect("We wouldn't be here if note is None"))
|
|
} else {
|
|
closest_note_freq(actual)
|
|
};
|
|
|
|
pitch_shift(&mut shifter_r, &l, freq_gain * expected / actual)
|
|
} else if let Some(actual) = prev_r_freq {
|
|
log::info!("R: reusing actual pitch: {}", actual);
|
|
|
|
let expected = if manual {
|
|
midi_note_to_pitch(note.expect("We wouldn't be here if note is None"))
|
|
} else {
|
|
closest_note_freq(actual)
|
|
};
|
|
|
|
pitch_shift(&mut shifter_r, &l, freq_gain * expected / actual)
|
|
} else {
|
|
log::info!("R: no actual pitch");
|
|
|
|
r
|
|
};
|
|
|
|
let _ = outputs.push(Buffers::from(l, r));
|
|
log::info!("finished processing a buffer");
|
|
}
|
|
}
|
|
}
|