168 lines
4.7 KiB
Rust
168 lines
4.7 KiB
Rust
#![allow(incomplete_features)]
|
|
#![feature(generic_associated_types)]
|
|
|
|
use baseplug::{Plugin, ProcessContext};
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use utils::delay::*;
|
|
use utils::envelope::*;
|
|
|
|
const DELAY_LEN: usize = 200;
|
|
|
|
baseplug::model! {
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
struct PaneraModel {
|
|
// peak detection options
|
|
#[model(min = 0.0, max = 3.0)]
|
|
#[parameter(name = "gain")]
|
|
gain: f32,
|
|
#[model(min = 0.0, max = 1.0)]
|
|
#[parameter(name = "attack")]
|
|
attack: f32,
|
|
#[model(min = 0.0, max = 1.0)]
|
|
#[parameter(name = "release")]
|
|
release: f32,
|
|
#[model(min = 0.0, max = 1.0)]
|
|
#[parameter(name = "gate")]
|
|
gate: f32,
|
|
|
|
// options to control the pan oscillation
|
|
#[model(min = 0.0, max = 1.0)]
|
|
#[parameter(name = "panning mode")]
|
|
panning_mode: f32,
|
|
#[model(min = 0.0, max = 10.0)]
|
|
#[parameter(name = "lfo freq")]
|
|
lfo_freq: f32,
|
|
}
|
|
}
|
|
|
|
impl Default for PaneraModel {
|
|
fn default() -> Self {
|
|
Self {
|
|
gain: 1.8,
|
|
attack: 0.0,
|
|
release: 0.27,
|
|
gate: 0.22,
|
|
|
|
panning_mode: 0.0,
|
|
lfo_freq: 1.0,
|
|
}
|
|
}
|
|
}
|
|
|
|
struct Panera {
|
|
/// envelope follower so we can detect peaks
|
|
envelope_follower: EnvelopeFollower,
|
|
/// whether we are currently on a peak
|
|
on: bool,
|
|
/// the current pan position
|
|
/// goes from 0 (left) to 1 (right)
|
|
pan: f32,
|
|
/// delay line is used so we don't change the pan after the peak has started
|
|
/// since to detect a peak we have to be halfway through it, if we change the pan
|
|
/// when we detect a peak, this means that it gets changed halfway, which results in a click
|
|
/// the delay makes it so we can detect a peak before actually playing it, therefore
|
|
/// allowing us to change the pan before the peak actually starts, and thus no clicks :)
|
|
delay: DelayLine<DELAY_LEN>,
|
|
|
|
lfo_idx: usize,
|
|
}
|
|
|
|
impl Plugin for Panera {
|
|
const NAME: &'static str = "panera";
|
|
const PRODUCT: &'static str = "panera";
|
|
const VENDOR: &'static str = "unnieversal";
|
|
|
|
const INPUT_CHANNELS: usize = 1;
|
|
const OUTPUT_CHANNELS: usize = 2;
|
|
|
|
type Model = PaneraModel;
|
|
|
|
#[inline]
|
|
fn new(_sample_rate: f32, _model: &PaneraModel) -> Self {
|
|
Self {
|
|
envelope_follower: EnvelopeFollower::new(),
|
|
on: true,
|
|
pan: 0.0,
|
|
delay: DelayLine::<DELAY_LEN>::new(),
|
|
lfo_idx: 0,
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
fn process(&mut self, model: &PaneraModelProcess, ctx: &mut ProcessContext<Self>) {
|
|
let input = &ctx.inputs[0].buffers;
|
|
let output = &mut ctx.outputs[0].buffers;
|
|
|
|
for i in 0..ctx.nframes {
|
|
// set values for env detector
|
|
self.envelope_follower.set_attack_release(
|
|
ctx.sample_rate,
|
|
model.attack[i],
|
|
model.release[i],
|
|
);
|
|
|
|
// process envelope detector with the non-delayed sample
|
|
let env = self.envelope_follower.process(input[0][i] * model.gain[i]);
|
|
|
|
// detect peak
|
|
if env > model.gate[i] && !self.on {
|
|
self.update_pan(
|
|
model.panning_mode[i].into(),
|
|
model.lfo_freq[i],
|
|
ctx.sample_rate,
|
|
);
|
|
|
|
self.on = true;
|
|
}
|
|
if env < model.gate[i] && self.on {
|
|
self.on = false;
|
|
}
|
|
|
|
// increment lfo counter
|
|
self.lfo_idx += 1;
|
|
|
|
// update delay and get value
|
|
let s = self.delay.write_and_advance_get_last(input[0][i]);
|
|
|
|
// square pan law
|
|
// we play the delayed value
|
|
output[0][i] = self.pan.sqrt() * s;
|
|
output[1][i] = (1.0 - self.pan).sqrt() * s;
|
|
}
|
|
}
|
|
}
|
|
|
|
enum PanningMode {
|
|
Alternating,
|
|
Sine,
|
|
Random,
|
|
}
|
|
impl From<f32> for PanningMode {
|
|
fn from(a: f32) -> Self {
|
|
if a < 0.33 {
|
|
Self::Alternating
|
|
} else if a < 0.66 {
|
|
Self::Sine
|
|
} else {
|
|
Self::Random
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Panera {
|
|
fn update_pan(&mut self, lfo_type: PanningMode, lfo_freq: f32, sample_rate: f32) {
|
|
self.pan = match lfo_type {
|
|
PanningMode::Alternating => 1.0 - self.pan,
|
|
PanningMode::Sine => ((self.lfo_idx as f32 * lfo_freq / sample_rate).sin() + 1.0) / 2.0,
|
|
PanningMode::Random => {
|
|
// idk tbh, just something kinda random ig
|
|
// i just want it to not be predictable
|
|
(((self.lfo_idx * (self.lfo_idx ^ 1234)) as f32 * lfo_freq).sin() + 1.0) / 2.0
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
baseplug::vst2!(Panera, b"pann");
|