155 lines
3.5 KiB
Rust
155 lines
3.5 KiB
Rust
#![allow(incomplete_features)]
|
|
#![feature(generic_associated_types)]
|
|
#![feature(min_specialization)]
|
|
|
|
use std::f32::consts::PI;
|
|
|
|
use serde::{Serialize, Deserialize};
|
|
|
|
use baseplug::{
|
|
ProcessContext,
|
|
Plugin,
|
|
MidiReceiver,
|
|
util::db_to_coeff
|
|
};
|
|
|
|
|
|
baseplug::model! {
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
struct MidiSineModel {
|
|
#[model(min = -90.0, max = 3.0)]
|
|
#[parameter(name = "gain", unit = "Decibels",
|
|
gradient = "Power(0.15)")]
|
|
gain: f32,
|
|
|
|
#[model(min = 0.05, max = 0.95)]
|
|
#[parameter(name = "phase distortion")]
|
|
pd: f32,
|
|
|
|
#[model(min = 220.0, max = 880.0)]
|
|
#[parameter(name = "a4 tuning", gradient = "Exponential")]
|
|
a4: f32
|
|
}
|
|
}
|
|
|
|
impl Default for MidiSineModel {
|
|
fn default() -> Self {
|
|
Self {
|
|
gain: db_to_coeff(-3.0),
|
|
pd: 0.5,
|
|
a4: 440.0
|
|
}
|
|
}
|
|
}
|
|
|
|
struct Oscillator {
|
|
phase: f64,
|
|
step: f64
|
|
}
|
|
|
|
impl Oscillator {
|
|
#[inline]
|
|
fn new() -> Self {
|
|
Self {
|
|
// cheeky little hack to keep cosine output from jumping to +1.0 when adding the plugin
|
|
// to the host ;>
|
|
phase: 0.25,
|
|
step: 0.0
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
fn set_frequency(&mut self, frequency: f64, sample_rate: f64) {
|
|
self.step = frequency / sample_rate;
|
|
}
|
|
|
|
#[inline]
|
|
fn tick(&mut self) {
|
|
self.phase += self.step;
|
|
|
|
// usually cheaper than modulo
|
|
while self.phase > 1.0 {
|
|
self.phase -= 1.0;
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
fn pd_phase(&self, d: f32) -> f32 {
|
|
let mut phase = self.phase as f32;
|
|
|
|
if phase < d {
|
|
phase /= d;
|
|
} else {
|
|
phase = 1.0 + ((phase - d) / (1.0 - d));
|
|
}
|
|
|
|
phase * 0.5
|
|
}
|
|
}
|
|
|
|
struct MidiSine {
|
|
osc: Oscillator,
|
|
sample_rate: f32,
|
|
|
|
freq_ratio: f32,
|
|
}
|
|
|
|
impl Plugin for MidiSine {
|
|
const NAME: &'static str = "midi sine plug";
|
|
const PRODUCT: &'static str = "midi sine plug";
|
|
const VENDOR: &'static str = "spicy plugins & co";
|
|
|
|
const INPUT_CHANNELS: usize = 0;
|
|
const OUTPUT_CHANNELS: usize = 2;
|
|
|
|
type Model = MidiSineModel;
|
|
|
|
#[inline]
|
|
fn new(sample_rate: f32, _model: &MidiSineModel) -> Self {
|
|
Self {
|
|
osc: Oscillator::new(),
|
|
sample_rate,
|
|
|
|
freq_ratio: 0.0
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
fn process(&mut self, model: &MidiSineModelProcess, ctx: &mut ProcessContext<Self>) {
|
|
let output = &mut ctx.outputs[0].buffers;
|
|
|
|
for i in 0..ctx.nframes {
|
|
if model.a4.is_smoothing() {
|
|
self.osc.set_frequency((self.freq_ratio * model.a4[i]) as f64, self.sample_rate as f64);
|
|
}
|
|
|
|
let wave = {
|
|
let phase = self.osc.pd_phase(model.pd[i]);
|
|
(phase * 2.0 * PI).cos()
|
|
};
|
|
self.osc.tick();
|
|
|
|
output[0][i] = wave * model.gain[i];
|
|
output[1][i] = wave * model.gain[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
impl MidiReceiver for MidiSine {
|
|
fn midi_input(&mut self, model: &MidiSineModelProcess, data: [u8; 3]) {
|
|
match data[0] {
|
|
// note on
|
|
0x90 => {
|
|
let ratio = ((data[1] as f32 - 69.0) / 12.0).exp2();
|
|
self.freq_ratio = ratio;
|
|
|
|
let freq = ratio * model.a4[0];
|
|
self.osc.set_frequency(freq as f64, self.sample_rate as f64);
|
|
},
|
|
|
|
_ => ()
|
|
}
|
|
}
|
|
}
|
|
|
|
baseplug::vst2!(MidiSine, b"~Ss~");
|