diff --git a/crates/transmute_pitch/Cargo.toml b/crates/transmute_pitch/Cargo.toml new file mode 100644 index 0000000..1979cd7 --- /dev/null +++ b/crates/transmute_pitch/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "transmute_pitch" +version = "0.1.0" +edition = "2018" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +baseplug = { git = "https://github.com/wrl/baseplug.git", rev = "9cec68f31cca9c0c7a1448379f75d92bbbc782a8" } +serde = "1.0.126" +log = "0.4.14" +ringbuf = "0.2.5" + +utils = { path = "../utils" } diff --git a/crates/transmute_pitch/src/lib.rs b/crates/transmute_pitch/src/lib.rs new file mode 100644 index 0000000..19644df --- /dev/null +++ b/crates/transmute_pitch/src/lib.rs @@ -0,0 +1,108 @@ +#![allow(incomplete_features)] +#![feature(generic_associated_types)] + +use baseplug::{event::Data, Event, Plugin, ProcessContext}; +use serde::{Deserialize, Serialize}; + +use utils::pitch::*; + +const BUFFER_LEN: usize = 2 << 9; + +baseplug::model! { + #[derive(Debug, Serialize, Deserialize)] + struct TransmutePitchModel { + #[model(min = 0.0, max = 1.0)] + #[parameter(name = "passthrough")] + passthrough: f32, + } +} + +impl Default for TransmutePitchModel { + fn default() -> Self { + Self { passthrough: 1.0 } + } +} + +struct TransmutePitch { + detector_thread: pitch_detection::PitchDetectorThread, + last_note: Option, +} + +impl Plugin for TransmutePitch { + const NAME: &'static str = "transmute pitch"; + const PRODUCT: &'static str = "transmute pitch"; + const VENDOR: &'static str = "unnieversal"; + + const INPUT_CHANNELS: usize = 1; + const OUTPUT_CHANNELS: usize = 1; + + type Model = TransmutePitchModel; + + #[inline] + fn new(_sample_rate: f32, _model: &TransmutePitchModel) -> Self { + let detector_thread = pitch_detection::PitchDetectorThread::::new(); + Self { + detector_thread, + last_note: None, + } + } + + #[inline] + fn process(&mut self, model: &TransmutePitchModelProcess, ctx: &mut ProcessContext) { + let input = &ctx.inputs[0].buffers; + let output = &mut ctx.outputs[0].buffers; + let enqueue_midi = &mut ctx.enqueue_event; + + for i in 0..ctx.nframes { + output[0][i] = model.passthrough[i] * input[0][i]; + output[1][i] = model.passthrough[i] * input[1][i]; + + // pass input to pitch detector + self.detector_thread + .write(input[0][i], 0.0, ctx.sample_rate as u32); + + // Try to get a processed buffer from the processor thread + match self.detector_thread.try_get_pitch() { + Some((Some(pitch), _)) => { + let note = pitch_to_midi_note(pitch); + + // If note changed + if self.last_note != Some(note) { + // Send note off for last note + if let Some(last_note) = self.last_note { + let note_off = Event:: { + frame: i, + data: Data::Midi([0x80, last_note, 0]), + }; + enqueue_midi(note_off); + } + + // Send note on for the new one + let note_on = Event:: { + frame: i, + data: Data::Midi([0x90, note, 64]), + }; + enqueue_midi(note_on); + + // Update note + self.last_note = Some(note); + } + } + Some((None, _)) => { + if let Some(last_note) = self.last_note { + let note_off = Event:: { + frame: i, + data: Data::Midi([0x80, last_note, 0]), + }; + enqueue_midi(note_off); + } + + self.last_note = None; + } + _ => {} + } + } + } +} + +baseplug::vst2!(TransmutePitch, b"trpi");