unnieversal/crates/sosten/src/lib.rs

163 lines
4.9 KiB
Rust

#![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<LEN>,
buffers: Buffers<LEN>,
playing: bool,
detector_thread: pitch_detection::PitchDetectorThread<PITCH_LEN>,
/// Period of the thing we're currently repeating
used_period: Option<usize>,
/// Period of the processed input
/// We keep both so we can instantly switch
period: Option<usize>,
}
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::<LEN>::new(),
buffers: Buffers::new(),
playing: false,
detector_thread: pitch_detection::PitchDetectorThread::<PITCH_LEN>::new(),
used_period: None,
period: None,
}
}
#[inline]
fn process(&mut self, model: &SostenModelProcess, ctx: &mut ProcessContext<Self>) {
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");