unnieversal/crates/robotuna/src/tuna.rs

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");
}
}
}