#![allow(incomplete_features)] #![feature(generic_associated_types)] use baseplug::{Plugin, ProcessContext}; use serde::{Deserialize, Serialize}; use utils::buffers::*; use utils::delay::*; use utils::pitch::*; // If you change this remember to change the max on the model const LEN: usize = 48000; const PITCH_LEN: usize = 2 << 9; baseplug::model! { #[derive(Debug, Serialize, Deserialize)] struct SostenModel { #[model(min = 0.0, max = 1.0)] #[parameter(name = "enable")] enable: f32, #[model(min = 10.0, max = 48000.0)] #[parameter(name = "length")] length: f32, #[model(min = 0.0, max = 1.0)] #[parameter(name = "manual/pitch detection")] manual: f32, #[model(min = 0.0, max = 1.0)] #[parameter(name = "dissipation")] dissipation: f32, } } impl Default for SostenModel { fn default() -> Self { Self { enable: 0.0, length: 1000.0, manual: 0.0, dissipation: 1.0, } } } struct Sosten { delay: DelayLines, buffers: Buffers, playing: bool, detector_thread: pitch_detection::PitchDetectorThread, /// Period of the thing we're currently repeating used_period: Option, /// Period of the processed input /// We keep both so we can instantly switch period: Option, } impl Plugin for Sosten { const NAME: &'static str = "sosten"; const PRODUCT: &'static str = "sosten"; const VENDOR: &'static str = "unnieversal"; const INPUT_CHANNELS: usize = 2; const OUTPUT_CHANNELS: usize = 2; type Model = SostenModel; #[inline] fn new(_sample_rate: f32, _model: &SostenModel) -> Self { Self { delay: DelayLines::::new(), buffers: Buffers::new(), playing: false, detector_thread: pitch_detection::PitchDetectorThread::::new(), used_period: None, period: None, } } #[inline] fn process(&mut self, model: &SostenModelProcess, ctx: &mut ProcessContext) { let input = &ctx.inputs[0].buffers; let output = &mut ctx.outputs[0].buffers; for i in 0..ctx.nframes { // update delays self.delay.write_and_advance(input[0][i], input[1][i]); // pass input to pitch detector, in mono self.detector_thread.write( 0.5 * (input[0][i] + input[0][i]), 0.0, ctx.sample_rate as u32, ); // get pitch from detector thread match self.detector_thread.try_get_pitch() { Some((pitch, _)) => { let sr = ctx.sample_rate; self.period = pitch.map(|pitch| (sr / pitch).floor() as usize); } _ => {} } // Toggle playing according to `enable` if model.enable[i] >= 0.5 { // If it wasn't playing before this, reload buffer if !self.playing { self.delay.read_to_buffers(&mut self.buffers); self.buffers.reset(); self.playing = true; self.used_period = self.period; } } else { self.playing = false; } // Play the repeating part if self.playing { // Length of section to play let len = if model.manual[i] < 0.5 { model.length[i].trunc() as usize } else { self.used_period.unwrap_or(model.length[i].trunc() as usize) }; // If len has changed, idx may have not, so we do the min so we don't go out of bounds let idx = self.buffers.idx.min(len - 1); // Play from Buffer let (l, r) = self.buffers.read((LEN - len) + idx); output[0][i] = l; output[1][i] = r; // dissipates the audio in the buffer, idk // it adds a bit of the next sample to this one, which smooths it out let diss = model.dissipation[i]; let a = (LEN - len) + idx; self.buffers.l[a] *= diss; self.buffers.r[a] *= diss; self.buffers.l[a] += (1.0 - diss) * self.buffers.l[(a + 1) % LEN]; self.buffers.r[a] += (1.0 - diss) * self.buffers.r[(a + 1) % LEN]; // Loop index after we finish playing a section self.buffers.idx += 1; if self.buffers.idx >= len { self.buffers.idx = 0; } } else { // If it's not on a repeat section, pass all the audio fully output[0][i] = input[0][i]; output[1][i] = input[1][i]; } } } } baseplug::vst2!(Sosten, b"sost");