2021-07-24 16:40:59 +00:00
|
|
|
#![allow(incomplete_features)]
|
|
|
|
#![feature(generic_associated_types)]
|
|
|
|
|
|
|
|
use baseplug::{Plugin, ProcessContext};
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
|
2021-08-05 13:53:23 +00:00
|
|
|
use utils::buffers::*;
|
2021-07-31 09:23:11 +00:00
|
|
|
use utils::delay::*;
|
2021-08-05 13:53:23 +00:00
|
|
|
use utils::pitch::*;
|
2021-07-24 16:40:59 +00:00
|
|
|
|
|
|
|
// If you change this remember to change the max on the model
|
|
|
|
const LEN: usize = 48000;
|
2021-08-05 13:53:23 +00:00
|
|
|
const PITCH_LEN: usize = 2 << 9;
|
2021-07-24 16:40:59 +00:00
|
|
|
|
|
|
|
baseplug::model! {
|
|
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
|
|
struct SostenModel {
|
2021-08-05 13:53:23 +00:00
|
|
|
#[model(min = 0.0, max = 1.0)]
|
|
|
|
#[parameter(name = "enable")]
|
|
|
|
enable: f32,
|
2021-07-24 16:40:59 +00:00
|
|
|
#[model(min = 10.0, max = 48000.0)]
|
|
|
|
#[parameter(name = "length")]
|
|
|
|
length: f32,
|
|
|
|
#[model(min = 0.0, max = 1.0)]
|
2021-08-05 13:53:23 +00:00
|
|
|
#[parameter(name = "manual/pitch detection")]
|
|
|
|
manual: f32,
|
2021-08-05 10:00:29 +00:00
|
|
|
#[model(min = 0.0, max = 1.0)]
|
|
|
|
#[parameter(name = "dissipation")]
|
|
|
|
dissipation: f32,
|
2021-08-05 14:35:35 +00:00
|
|
|
#[model(min = 0.0, max = 1.0)]
|
|
|
|
#[parameter(name = "direction")]
|
|
|
|
direction: f32,
|
2021-07-24 16:40:59 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for SostenModel {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self {
|
|
|
|
enable: 0.0,
|
2021-08-05 13:53:23 +00:00
|
|
|
length: 1000.0,
|
|
|
|
manual: 0.0,
|
2021-08-05 10:00:29 +00:00
|
|
|
dissipation: 1.0,
|
2021-08-05 14:35:35 +00:00
|
|
|
direction: 1.0,
|
2021-07-24 16:40:59 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
struct Sosten {
|
2021-08-05 13:53:23 +00:00
|
|
|
delay: DelayLines<LEN>,
|
|
|
|
buffers: Buffers<LEN>,
|
2021-07-24 16:40:59 +00:00
|
|
|
|
|
|
|
playing: bool,
|
2021-08-05 13:53:23 +00:00
|
|
|
|
|
|
|
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>,
|
2021-08-05 14:35:35 +00:00
|
|
|
|
|
|
|
/// Keeps track of whether we are going forward while in cycle mode
|
|
|
|
forwards_in_cycle_mode: bool,
|
2021-07-24 16:40:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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 {
|
2021-08-05 13:53:23 +00:00
|
|
|
delay: DelayLines::<LEN>::new(),
|
|
|
|
buffers: Buffers::new(),
|
2021-07-24 16:40:59 +00:00
|
|
|
|
|
|
|
playing: false,
|
2021-08-05 13:53:23 +00:00
|
|
|
|
|
|
|
detector_thread: pitch_detection::PitchDetectorThread::<PITCH_LEN>::new(),
|
|
|
|
used_period: None,
|
|
|
|
period: None,
|
2021-08-05 14:35:35 +00:00
|
|
|
|
|
|
|
forwards_in_cycle_mode: true,
|
2021-07-24 16:40:59 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[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 {
|
2021-08-05 13:53:23 +00:00
|
|
|
// 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);
|
|
|
|
}
|
|
|
|
_ => {}
|
|
|
|
}
|
2021-07-24 16:40:59 +00:00
|
|
|
|
|
|
|
// Toggle playing according to `enable`
|
|
|
|
if model.enable[i] >= 0.5 {
|
|
|
|
// If it wasn't playing before this, reload buffer
|
|
|
|
if !self.playing {
|
2021-08-05 13:53:23 +00:00
|
|
|
self.delay.read_to_buffers(&mut self.buffers);
|
|
|
|
self.buffers.reset();
|
2021-07-24 16:40:59 +00:00
|
|
|
|
|
|
|
self.playing = true;
|
2021-08-05 13:53:23 +00:00
|
|
|
self.used_period = self.period;
|
2021-07-24 16:40:59 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
self.playing = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Play the repeating part
|
|
|
|
if self.playing {
|
|
|
|
// Length of section to play
|
2021-08-05 13:53:23 +00:00
|
|
|
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)
|
|
|
|
};
|
2021-07-24 16:40:59 +00:00
|
|
|
|
|
|
|
// If len has changed, idx may have not, so we do the min so we don't go out of bounds
|
2021-08-05 13:53:23 +00:00
|
|
|
let idx = self.buffers.idx.min(len - 1);
|
2021-07-24 16:40:59 +00:00
|
|
|
|
|
|
|
// Play from Buffer
|
2021-08-05 16:04:17 +00:00
|
|
|
let (l, r) = self.buffers.read_at((LEN - len) + idx);
|
2021-08-05 13:53:23 +00:00
|
|
|
|
|
|
|
output[0][i] = l;
|
|
|
|
output[1][i] = r;
|
2021-08-05 10:00:29 +00:00
|
|
|
|
|
|
|
// 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;
|
2021-08-05 13:53:23 +00:00
|
|
|
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];
|
2021-07-24 16:40:59 +00:00
|
|
|
|
|
|
|
// Loop index after we finish playing a section
|
2021-08-05 14:35:35 +00:00
|
|
|
if model.direction[i] > 0.6 {
|
|
|
|
// we are going forwards, so increase the idx
|
|
|
|
self.buffers.idx += 1;
|
|
|
|
// and loop at the end
|
|
|
|
if self.buffers.idx >= len {
|
|
|
|
self.buffers.idx = 0;
|
|
|
|
}
|
|
|
|
} else if model.direction[i] > 0.4 {
|
|
|
|
// we're in cycle mode
|
|
|
|
|
|
|
|
// alternate directions at the end
|
|
|
|
if self.buffers.idx == 0 {
|
|
|
|
self.forwards_in_cycle_mode = true;
|
|
|
|
}
|
|
|
|
if self.buffers.idx >= len {
|
|
|
|
self.forwards_in_cycle_mode = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// increase/decrease index
|
|
|
|
if self.forwards_in_cycle_mode {
|
|
|
|
self.buffers.idx += 1;
|
|
|
|
} else {
|
|
|
|
self.buffers.idx -= 1;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// we are going backwards, so loop first
|
|
|
|
// this ensures we don't do 0 - 1
|
|
|
|
if self.buffers.idx == 0 {
|
|
|
|
self.buffers.idx = len + 1;
|
|
|
|
}
|
|
|
|
// then decrease index
|
|
|
|
self.buffers.idx -= 1;
|
2021-07-24 16:40:59 +00:00
|
|
|
}
|
|
|
|
} 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");
|