mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 19:56:42 +01:00
relayer arranger view and extract button_2 and button_3 to top level
This commit is contained in:
parent
d1bb33dc41
commit
fe9d5a309e
43 changed files with 7158 additions and 276 deletions
222
plugin/vst/examples/dimension_expander.rs
Normal file
222
plugin/vst/examples/dimension_expander.rs
Normal file
|
|
@ -0,0 +1,222 @@
|
|||
// author: Marko Mijalkovic <marko.mijalkovic97@gmail.com>
|
||||
|
||||
#[macro_use]
|
||||
extern crate vst;
|
||||
|
||||
use std::collections::VecDeque;
|
||||
use std::f64::consts::PI;
|
||||
use std::sync::Arc;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
use vst::prelude::*;
|
||||
|
||||
/// Calculate the length in samples for a delay. Size ranges from 0.0 to 1.0.
|
||||
fn delay(index: usize, mut size: f32) -> isize {
|
||||
const SIZE_OFFSET: f32 = 0.06;
|
||||
const SIZE_MULT: f32 = 1_000.0;
|
||||
|
||||
size += SIZE_OFFSET;
|
||||
|
||||
// Spread ratio between delays
|
||||
const SPREAD: f32 = 0.3;
|
||||
|
||||
let base = size * SIZE_MULT;
|
||||
let mult = (index as f32 * SPREAD) + 1.0;
|
||||
let offset = if index > 2 { base * SPREAD / 2.0 } else { 0.0 };
|
||||
|
||||
(base * mult + offset) as isize
|
||||
}
|
||||
|
||||
/// A left channel and right channel sample.
|
||||
type SamplePair = (f32, f32);
|
||||
|
||||
/// The Dimension Expander.
|
||||
struct DimensionExpander {
|
||||
buffers: Vec<VecDeque<SamplePair>>,
|
||||
params: Arc<DimensionExpanderParameters>,
|
||||
old_size: f32,
|
||||
}
|
||||
|
||||
struct DimensionExpanderParameters {
|
||||
dry_wet: AtomicFloat,
|
||||
size: AtomicFloat,
|
||||
}
|
||||
|
||||
impl DimensionExpander {
|
||||
fn new(size: f32, dry_wet: f32) -> DimensionExpander {
|
||||
const NUM_DELAYS: usize = 4;
|
||||
|
||||
let mut buffers = Vec::new();
|
||||
|
||||
// Generate delay buffers
|
||||
for i in 0..NUM_DELAYS {
|
||||
let samples = delay(i, size);
|
||||
let mut buffer = VecDeque::with_capacity(samples as usize);
|
||||
|
||||
// Fill in the delay buffers with empty samples
|
||||
for _ in 0..samples {
|
||||
buffer.push_back((0.0, 0.0));
|
||||
}
|
||||
|
||||
buffers.push(buffer);
|
||||
}
|
||||
|
||||
DimensionExpander {
|
||||
buffers,
|
||||
params: Arc::new(DimensionExpanderParameters {
|
||||
dry_wet: AtomicFloat::new(dry_wet),
|
||||
size: AtomicFloat::new(size),
|
||||
}),
|
||||
old_size: size,
|
||||
}
|
||||
}
|
||||
|
||||
/// Update the delay buffers with a new size value.
|
||||
fn resize(&mut self, n: f32) {
|
||||
let old_size = self.old_size;
|
||||
|
||||
for (i, buffer) in self.buffers.iter_mut().enumerate() {
|
||||
// Calculate the size difference between delays
|
||||
let old_delay = delay(i, old_size);
|
||||
let new_delay = delay(i, n);
|
||||
|
||||
let diff = new_delay - old_delay;
|
||||
|
||||
// Add empty samples if the delay was increased, remove if decreased
|
||||
if diff > 0 {
|
||||
for _ in 0..diff {
|
||||
buffer.push_back((0.0, 0.0));
|
||||
}
|
||||
} else if diff < 0 {
|
||||
for _ in 0..-diff {
|
||||
let _ = buffer.pop_front();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.old_size = n;
|
||||
}
|
||||
}
|
||||
|
||||
impl Plugin for DimensionExpander {
|
||||
fn new(_host: HostCallback) -> Self {
|
||||
DimensionExpander::new(0.12, 0.66)
|
||||
}
|
||||
|
||||
fn get_info(&self) -> Info {
|
||||
Info {
|
||||
name: "Dimension Expander".to_string(),
|
||||
vendor: "overdrivenpotato".to_string(),
|
||||
unique_id: 243723071,
|
||||
version: 1,
|
||||
inputs: 2,
|
||||
outputs: 2,
|
||||
parameters: 2,
|
||||
category: Category::Effect,
|
||||
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn process(&mut self, buffer: &mut AudioBuffer<f32>) {
|
||||
let (inputs, outputs) = buffer.split();
|
||||
|
||||
// Assume 2 channels
|
||||
if inputs.len() < 2 || outputs.len() < 2 {
|
||||
return;
|
||||
}
|
||||
|
||||
// Resize if size changed
|
||||
let size = self.params.size.get();
|
||||
if size != self.old_size {
|
||||
self.resize(size);
|
||||
}
|
||||
|
||||
// Iterate over inputs as (&f32, &f32)
|
||||
let (l, r) = inputs.split_at(1);
|
||||
let stereo_in = l[0].iter().zip(r[0].iter());
|
||||
|
||||
// Iterate over outputs as (&mut f32, &mut f32)
|
||||
let (mut l, mut r) = outputs.split_at_mut(1);
|
||||
let stereo_out = l[0].iter_mut().zip(r[0].iter_mut());
|
||||
|
||||
// Zip and process
|
||||
for ((left_in, right_in), (left_out, right_out)) in stereo_in.zip(stereo_out) {
|
||||
// Push the new samples into the delay buffers.
|
||||
for buffer in &mut self.buffers {
|
||||
buffer.push_back((*left_in, *right_in));
|
||||
}
|
||||
|
||||
let mut left_processed = 0.0;
|
||||
let mut right_processed = 0.0;
|
||||
|
||||
// Recalculate time per sample
|
||||
let time_s = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs_f64();
|
||||
|
||||
// Use buffer index to offset volume LFO
|
||||
for (n, buffer) in self.buffers.iter_mut().enumerate() {
|
||||
if let Some((left_old, right_old)) = buffer.pop_front() {
|
||||
const LFO_FREQ: f64 = 0.5;
|
||||
const WET_MULT: f32 = 0.66;
|
||||
|
||||
let offset = 0.25 * (n % 4) as f64;
|
||||
|
||||
// Sine wave volume LFO
|
||||
let lfo = ((time_s * LFO_FREQ + offset) * PI * 2.0).sin() as f32;
|
||||
|
||||
let wet = self.params.dry_wet.get() * WET_MULT;
|
||||
let mono = (left_old + right_old) / 2.0;
|
||||
|
||||
// Flip right channel and keep left mono so that the result is
|
||||
// entirely stereo
|
||||
left_processed += mono * wet * lfo;
|
||||
right_processed += -mono * wet * lfo;
|
||||
}
|
||||
}
|
||||
|
||||
// By only adding to the input, the output value always remains the same in mono
|
||||
*left_out = *left_in + left_processed;
|
||||
*right_out = *right_in + right_processed;
|
||||
}
|
||||
}
|
||||
|
||||
fn get_parameter_object(&mut self) -> Arc<dyn PluginParameters> {
|
||||
Arc::clone(&self.params) as Arc<dyn PluginParameters>
|
||||
}
|
||||
}
|
||||
|
||||
impl PluginParameters for DimensionExpanderParameters {
|
||||
fn get_parameter(&self, index: i32) -> f32 {
|
||||
match index {
|
||||
0 => self.size.get(),
|
||||
1 => self.dry_wet.get(),
|
||||
_ => 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_parameter_text(&self, index: i32) -> String {
|
||||
match index {
|
||||
0 => format!("{}", (self.size.get() * 1000.0) as isize),
|
||||
1 => format!("{:.1}%", self.dry_wet.get() * 100.0),
|
||||
_ => "".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_parameter_name(&self, index: i32) -> String {
|
||||
match index {
|
||||
0 => "Size",
|
||||
1 => "Dry/Wet",
|
||||
_ => "",
|
||||
}
|
||||
.to_string()
|
||||
}
|
||||
|
||||
fn set_parameter(&self, index: i32, val: f32) {
|
||||
match index {
|
||||
0 => self.size.set(val),
|
||||
1 => self.dry_wet.set(val),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
plugin_main!(DimensionExpander);
|
||||
71
plugin/vst/examples/fwd_midi.rs
Normal file
71
plugin/vst/examples/fwd_midi.rs
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
#[macro_use]
|
||||
extern crate vst;
|
||||
|
||||
use vst::api;
|
||||
use vst::prelude::*;
|
||||
|
||||
plugin_main!(MyPlugin); // Important!
|
||||
|
||||
#[derive(Default)]
|
||||
struct MyPlugin {
|
||||
host: HostCallback,
|
||||
recv_buffer: SendEventBuffer,
|
||||
send_buffer: SendEventBuffer,
|
||||
}
|
||||
|
||||
impl MyPlugin {
|
||||
fn send_midi(&mut self) {
|
||||
self.send_buffer
|
||||
.send_events(self.recv_buffer.events().events(), &mut self.host);
|
||||
self.recv_buffer.clear();
|
||||
}
|
||||
}
|
||||
|
||||
impl Plugin for MyPlugin {
|
||||
fn new(host: HostCallback) -> Self {
|
||||
MyPlugin {
|
||||
host,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn get_info(&self) -> Info {
|
||||
Info {
|
||||
name: "fwd_midi".to_string(),
|
||||
unique_id: 7357001, // Used by hosts to differentiate between plugins.
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn process_events(&mut self, events: &api::Events) {
|
||||
self.recv_buffer.store_events(events.events());
|
||||
}
|
||||
|
||||
fn process(&mut self, buffer: &mut AudioBuffer<f32>) {
|
||||
for (input, output) in buffer.zip() {
|
||||
for (in_sample, out_sample) in input.iter().zip(output) {
|
||||
*out_sample = *in_sample;
|
||||
}
|
||||
}
|
||||
self.send_midi();
|
||||
}
|
||||
|
||||
fn process_f64(&mut self, buffer: &mut AudioBuffer<f64>) {
|
||||
for (input, output) in buffer.zip() {
|
||||
for (in_sample, out_sample) in input.iter().zip(output) {
|
||||
*out_sample = *in_sample;
|
||||
}
|
||||
}
|
||||
self.send_midi();
|
||||
}
|
||||
|
||||
fn can_do(&self, can_do: CanDo) -> vst::api::Supported {
|
||||
use vst::api::Supported::*;
|
||||
use vst::plugin::CanDo::*;
|
||||
|
||||
match can_do {
|
||||
SendEvents | SendMidiEvent | ReceiveEvents | ReceiveMidiEvent => Yes,
|
||||
_ => No,
|
||||
}
|
||||
}
|
||||
}
|
||||
129
plugin/vst/examples/gain_effect.rs
Normal file
129
plugin/vst/examples/gain_effect.rs
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
// author: doomy <notdoomy@protonmail.com>
|
||||
|
||||
#[macro_use]
|
||||
extern crate vst;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use vst::prelude::*;
|
||||
|
||||
/// Simple Gain Effect.
|
||||
/// Note that this does not use a proper scale for sound and shouldn't be used in
|
||||
/// a production amplification effect! This is purely for demonstration purposes,
|
||||
/// as well as to keep things simple as this is meant to be a starting point for
|
||||
/// any effect.
|
||||
struct GainEffect {
|
||||
// Store a handle to the plugin's parameter object.
|
||||
params: Arc<GainEffectParameters>,
|
||||
}
|
||||
|
||||
/// The plugin's parameter object contains the values of parameters that can be
|
||||
/// adjusted from the host. If we were creating an effect that didn't allow the
|
||||
/// user to modify it at runtime or have any controls, we could omit this part.
|
||||
///
|
||||
/// The parameters object is shared between the processing and GUI threads.
|
||||
/// For this reason, all mutable state in the object has to be represented
|
||||
/// through thread-safe interior mutability. The easiest way to achieve this
|
||||
/// is to store the parameters in atomic containers.
|
||||
struct GainEffectParameters {
|
||||
// The plugin's state consists of a single parameter: amplitude.
|
||||
amplitude: AtomicFloat,
|
||||
}
|
||||
|
||||
impl Default for GainEffectParameters {
|
||||
fn default() -> GainEffectParameters {
|
||||
GainEffectParameters {
|
||||
amplitude: AtomicFloat::new(0.5),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// All plugins using `vst` also need to implement the `Plugin` trait. Here, we
|
||||
// define functions that give necessary info to our host.
|
||||
impl Plugin for GainEffect {
|
||||
fn new(_host: HostCallback) -> Self {
|
||||
// Note that controls will always return a value from 0 - 1.
|
||||
// Setting a default to 0.5 means it's halfway up.
|
||||
GainEffect {
|
||||
params: Arc::new(GainEffectParameters::default()),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_info(&self) -> Info {
|
||||
Info {
|
||||
name: "Gain Effect in Rust".to_string(),
|
||||
vendor: "Rust DSP".to_string(),
|
||||
unique_id: 243723072,
|
||||
version: 1,
|
||||
inputs: 2,
|
||||
outputs: 2,
|
||||
// This `parameters` bit is important; without it, none of our
|
||||
// parameters will be shown!
|
||||
parameters: 1,
|
||||
category: Category::Effect,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
// Here is where the bulk of our audio processing code goes.
|
||||
fn process(&mut self, buffer: &mut AudioBuffer<f32>) {
|
||||
// Read the amplitude from the parameter object
|
||||
let amplitude = self.params.amplitude.get();
|
||||
// First, we destructure our audio buffer into an arbitrary number of
|
||||
// input and output buffers. Usually, we'll be dealing with stereo (2 of each)
|
||||
// but that might change.
|
||||
for (input_buffer, output_buffer) in buffer.zip() {
|
||||
// Next, we'll loop through each individual sample so we can apply the amplitude
|
||||
// value to it.
|
||||
for (input_sample, output_sample) in input_buffer.iter().zip(output_buffer) {
|
||||
*output_sample = *input_sample * amplitude;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return the parameter object. This method can be omitted if the
|
||||
// plugin has no parameters.
|
||||
fn get_parameter_object(&mut self) -> Arc<dyn PluginParameters> {
|
||||
Arc::clone(&self.params) as Arc<dyn PluginParameters>
|
||||
}
|
||||
}
|
||||
|
||||
impl PluginParameters for GainEffectParameters {
|
||||
// the `get_parameter` function reads the value of a parameter.
|
||||
fn get_parameter(&self, index: i32) -> f32 {
|
||||
match index {
|
||||
0 => self.amplitude.get(),
|
||||
_ => 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
// the `set_parameter` function sets the value of a parameter.
|
||||
fn set_parameter(&self, index: i32, val: f32) {
|
||||
#[allow(clippy::single_match)]
|
||||
match index {
|
||||
0 => self.amplitude.set(val),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
// This is what will display underneath our control. We can
|
||||
// format it into a string that makes the most since.
|
||||
fn get_parameter_text(&self, index: i32) -> String {
|
||||
match index {
|
||||
0 => format!("{:.2}", (self.amplitude.get() - 0.5) * 2f32),
|
||||
_ => "".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
// This shows the control's name.
|
||||
fn get_parameter_name(&self, index: i32) -> String {
|
||||
match index {
|
||||
0 => "Amplitude",
|
||||
_ => "",
|
||||
}
|
||||
.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
// This part is important! Without it, our plugin won't work.
|
||||
plugin_main!(GainEffect);
|
||||
248
plugin/vst/examples/ladder_filter.rs
Normal file
248
plugin/vst/examples/ladder_filter.rs
Normal file
|
|
@ -0,0 +1,248 @@
|
|||
//! This zero-delay feedback filter is based on a 4-stage transistor ladder filter.
|
||||
//! It follows the following equations:
|
||||
//! x = input - tanh(self.res * self.vout[3])
|
||||
//! vout[0] = self.params.g.get() * (tanh(x) - tanh(self.vout[0])) + self.s[0]
|
||||
//! vout[1] = self.params.g.get() * (tanh(self.vout[0]) - tanh(self.vout[1])) + self.s[1]
|
||||
//! vout[0] = self.params.g.get() * (tanh(self.vout[1]) - tanh(self.vout[2])) + self.s[2]
|
||||
//! vout[0] = self.params.g.get() * (tanh(self.vout[2]) - tanh(self.vout[3])) + self.s[3]
|
||||
//! since we can't easily solve a nonlinear equation,
|
||||
//! Mystran's fixed-pivot method is used to approximate the tanh() parts.
|
||||
//! Quality can be improved a lot by oversampling a bit.
|
||||
//! Feedback is clipped independently of the input, so it doesn't disappear at high gains.
|
||||
|
||||
#[macro_use]
|
||||
extern crate vst;
|
||||
use std::f32::consts::PI;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::Arc;
|
||||
|
||||
use vst::prelude::*;
|
||||
|
||||
// this is a 4-pole filter with resonance, which is why there's 4 states and vouts
|
||||
#[derive(Clone)]
|
||||
struct LadderFilter {
|
||||
// Store a handle to the plugin's parameter object.
|
||||
params: Arc<LadderParameters>,
|
||||
// the output of the different filter stages
|
||||
vout: [f32; 4],
|
||||
// s is the "state" parameter. In an IIR it would be the last value from the filter
|
||||
// In this we find it by trapezoidal integration to avoid the unit delay
|
||||
s: [f32; 4],
|
||||
}
|
||||
|
||||
struct LadderParameters {
|
||||
// the "cutoff" parameter. Determines how heavy filtering is
|
||||
cutoff: AtomicFloat,
|
||||
g: AtomicFloat,
|
||||
// needed to calculate cutoff.
|
||||
sample_rate: AtomicFloat,
|
||||
// makes a peak at cutoff
|
||||
res: AtomicFloat,
|
||||
// used to choose where we want our output to be
|
||||
poles: AtomicUsize,
|
||||
// pole_value is just to be able to use get_parameter on poles
|
||||
pole_value: AtomicFloat,
|
||||
// a drive parameter. Just used to increase the volume, which results in heavier distortion
|
||||
drive: AtomicFloat,
|
||||
}
|
||||
|
||||
impl Default for LadderParameters {
|
||||
fn default() -> LadderParameters {
|
||||
LadderParameters {
|
||||
cutoff: AtomicFloat::new(1000.),
|
||||
res: AtomicFloat::new(2.),
|
||||
poles: AtomicUsize::new(3),
|
||||
pole_value: AtomicFloat::new(1.),
|
||||
drive: AtomicFloat::new(0.),
|
||||
sample_rate: AtomicFloat::new(44100.),
|
||||
g: AtomicFloat::new(0.07135868),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// member methods for the struct
|
||||
impl LadderFilter {
|
||||
// the state needs to be updated after each process. Found by trapezoidal integration
|
||||
fn update_state(&mut self) {
|
||||
self.s[0] = 2. * self.vout[0] - self.s[0];
|
||||
self.s[1] = 2. * self.vout[1] - self.s[1];
|
||||
self.s[2] = 2. * self.vout[2] - self.s[2];
|
||||
self.s[3] = 2. * self.vout[3] - self.s[3];
|
||||
}
|
||||
|
||||
// performs a complete filter process (mystran's method)
|
||||
fn tick_pivotal(&mut self, input: f32) {
|
||||
if self.params.drive.get() > 0. {
|
||||
self.run_ladder_nonlinear(input * (self.params.drive.get() + 0.7));
|
||||
} else {
|
||||
//
|
||||
self.run_ladder_linear(input);
|
||||
}
|
||||
self.update_state();
|
||||
}
|
||||
|
||||
// nonlinear ladder filter function with distortion.
|
||||
fn run_ladder_nonlinear(&mut self, input: f32) {
|
||||
let mut a = [1f32; 5];
|
||||
let base = [input, self.s[0], self.s[1], self.s[2], self.s[3]];
|
||||
// a[n] is the fixed-pivot approximation for tanh()
|
||||
for n in 0..base.len() {
|
||||
if base[n] != 0. {
|
||||
a[n] = base[n].tanh() / base[n];
|
||||
} else {
|
||||
a[n] = 1.;
|
||||
}
|
||||
}
|
||||
// denominators of solutions of individual stages. Simplifies the math a bit
|
||||
let g0 = 1. / (1. + self.params.g.get() * a[1]);
|
||||
let g1 = 1. / (1. + self.params.g.get() * a[2]);
|
||||
let g2 = 1. / (1. + self.params.g.get() * a[3]);
|
||||
let g3 = 1. / (1. + self.params.g.get() * a[4]);
|
||||
// these are just factored out of the feedback solution. Makes the math way easier to read
|
||||
let f3 = self.params.g.get() * a[3] * g3;
|
||||
let f2 = self.params.g.get() * a[2] * g2 * f3;
|
||||
let f1 = self.params.g.get() * a[1] * g1 * f2;
|
||||
let f0 = self.params.g.get() * g0 * f1;
|
||||
// outputs a 24db filter
|
||||
self.vout[3] =
|
||||
(f0 * input * a[0] + f1 * g0 * self.s[0] + f2 * g1 * self.s[1] + f3 * g2 * self.s[2] + g3 * self.s[3])
|
||||
/ (f0 * self.params.res.get() * a[3] + 1.);
|
||||
// since we know the feedback, we can solve the remaining outputs:
|
||||
self.vout[0] = g0
|
||||
* (self.params.g.get() * a[1] * (input * a[0] - self.params.res.get() * a[3] * self.vout[3]) + self.s[0]);
|
||||
self.vout[1] = g1 * (self.params.g.get() * a[2] * self.vout[0] + self.s[1]);
|
||||
self.vout[2] = g2 * (self.params.g.get() * a[3] * self.vout[1] + self.s[2]);
|
||||
}
|
||||
|
||||
// linear version without distortion
|
||||
pub fn run_ladder_linear(&mut self, input: f32) {
|
||||
// denominators of solutions of individual stages. Simplifies the math a bit
|
||||
let g0 = 1. / (1. + self.params.g.get());
|
||||
let g1 = self.params.g.get() * g0 * g0;
|
||||
let g2 = self.params.g.get() * g1 * g0;
|
||||
let g3 = self.params.g.get() * g2 * g0;
|
||||
// outputs a 24db filter
|
||||
self.vout[3] =
|
||||
(g3 * self.params.g.get() * input + g0 * self.s[3] + g1 * self.s[2] + g2 * self.s[1] + g3 * self.s[0])
|
||||
/ (g3 * self.params.g.get() * self.params.res.get() + 1.);
|
||||
// since we know the feedback, we can solve the remaining outputs:
|
||||
self.vout[0] = g0 * (self.params.g.get() * (input - self.params.res.get() * self.vout[3]) + self.s[0]);
|
||||
self.vout[1] = g0 * (self.params.g.get() * self.vout[0] + self.s[1]);
|
||||
self.vout[2] = g0 * (self.params.g.get() * self.vout[1] + self.s[2]);
|
||||
}
|
||||
}
|
||||
|
||||
impl LadderParameters {
|
||||
pub fn set_cutoff(&self, value: f32) {
|
||||
// cutoff formula gives us a natural feeling cutoff knob that spends more time in the low frequencies
|
||||
self.cutoff.set(20000. * (1.8f32.powf(10. * value - 10.)));
|
||||
// bilinear transformation for g gives us a very accurate cutoff
|
||||
self.g.set((PI * self.cutoff.get() / (self.sample_rate.get())).tan());
|
||||
}
|
||||
|
||||
// returns the value used to set cutoff. for get_parameter function
|
||||
pub fn get_cutoff(&self) -> f32 {
|
||||
1. + 0.17012975 * (0.00005 * self.cutoff.get()).ln()
|
||||
}
|
||||
|
||||
pub fn set_poles(&self, value: f32) {
|
||||
self.pole_value.set(value);
|
||||
self.poles.store(((value * 3.).round()) as usize, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
impl PluginParameters for LadderParameters {
|
||||
// get_parameter has to return the value used in set_parameter
|
||||
fn get_parameter(&self, index: i32) -> f32 {
|
||||
match index {
|
||||
0 => self.get_cutoff(),
|
||||
1 => self.res.get() / 4.,
|
||||
2 => self.pole_value.get(),
|
||||
3 => self.drive.get() / 5.,
|
||||
_ => 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
fn set_parameter(&self, index: i32, value: f32) {
|
||||
match index {
|
||||
0 => self.set_cutoff(value),
|
||||
1 => self.res.set(value * 4.),
|
||||
2 => self.set_poles(value),
|
||||
3 => self.drive.set(value * 5.),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_parameter_name(&self, index: i32) -> String {
|
||||
match index {
|
||||
0 => "cutoff".to_string(),
|
||||
1 => "resonance".to_string(),
|
||||
2 => "filter order".to_string(),
|
||||
3 => "drive".to_string(),
|
||||
_ => "".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_parameter_label(&self, index: i32) -> String {
|
||||
match index {
|
||||
0 => "Hz".to_string(),
|
||||
1 => "%".to_string(),
|
||||
2 => "poles".to_string(),
|
||||
3 => "%".to_string(),
|
||||
_ => "".to_string(),
|
||||
}
|
||||
}
|
||||
// This is what will display underneath our control. We can
|
||||
// format it into a string that makes the most sense.
|
||||
fn get_parameter_text(&self, index: i32) -> String {
|
||||
match index {
|
||||
0 => format!("{:.0}", self.cutoff.get()),
|
||||
1 => format!("{:.3}", self.res.get()),
|
||||
2 => format!("{}", self.poles.load(Ordering::Relaxed) + 1),
|
||||
3 => format!("{:.3}", self.drive.get()),
|
||||
_ => format!(""),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Plugin for LadderFilter {
|
||||
fn new(_host: HostCallback) -> Self {
|
||||
LadderFilter {
|
||||
vout: [0f32; 4],
|
||||
s: [0f32; 4],
|
||||
params: Arc::new(LadderParameters::default()),
|
||||
}
|
||||
}
|
||||
|
||||
fn set_sample_rate(&mut self, rate: f32) {
|
||||
self.params.sample_rate.set(rate);
|
||||
}
|
||||
|
||||
fn get_info(&self) -> Info {
|
||||
Info {
|
||||
name: "LadderFilter".to_string(),
|
||||
unique_id: 9263,
|
||||
inputs: 1,
|
||||
outputs: 1,
|
||||
category: Category::Effect,
|
||||
parameters: 4,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn process(&mut self, buffer: &mut AudioBuffer<f32>) {
|
||||
for (input_buffer, output_buffer) in buffer.zip() {
|
||||
for (input_sample, output_sample) in input_buffer.iter().zip(output_buffer) {
|
||||
self.tick_pivotal(*input_sample);
|
||||
// the poles parameter chooses which filter stage we take our output from.
|
||||
*output_sample = self.vout[self.params.poles.load(Ordering::Relaxed)];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_parameter_object(&mut self) -> Arc<dyn PluginParameters> {
|
||||
Arc::clone(&self.params) as Arc<dyn PluginParameters>
|
||||
}
|
||||
}
|
||||
|
||||
plugin_main!(LadderFilter);
|
||||
63
plugin/vst/examples/simple_host.rs
Normal file
63
plugin/vst/examples/simple_host.rs
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
extern crate vst;
|
||||
|
||||
use std::env;
|
||||
use std::path::Path;
|
||||
use std::process;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use vst::host::{Host, PluginLoader};
|
||||
use vst::plugin::Plugin;
|
||||
|
||||
#[allow(dead_code)]
|
||||
struct SampleHost;
|
||||
|
||||
impl Host for SampleHost {
|
||||
fn automate(&self, index: i32, value: f32) {
|
||||
println!("Parameter {} had its value changed to {}", index, value);
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let args: Vec<String> = env::args().collect();
|
||||
if args.len() < 2 {
|
||||
println!("usage: simple_host path/to/vst");
|
||||
process::exit(1);
|
||||
}
|
||||
|
||||
let path = Path::new(&args[1]);
|
||||
|
||||
// Create the host
|
||||
let host = Arc::new(Mutex::new(SampleHost));
|
||||
|
||||
println!("Loading {}...", path.to_str().unwrap());
|
||||
|
||||
// Load the plugin
|
||||
let mut loader =
|
||||
PluginLoader::load(path, Arc::clone(&host)).unwrap_or_else(|e| panic!("Failed to load plugin: {}", e));
|
||||
|
||||
// Create an instance of the plugin
|
||||
let mut instance = loader.instance().unwrap();
|
||||
|
||||
// Get the plugin information
|
||||
let info = instance.get_info();
|
||||
|
||||
println!(
|
||||
"Loaded '{}':\n\t\
|
||||
Vendor: {}\n\t\
|
||||
Presets: {}\n\t\
|
||||
Parameters: {}\n\t\
|
||||
VST ID: {}\n\t\
|
||||
Version: {}\n\t\
|
||||
Initial Delay: {} samples",
|
||||
info.name, info.vendor, info.presets, info.parameters, info.unique_id, info.version, info.initial_delay
|
||||
);
|
||||
|
||||
// Initialize the instance
|
||||
instance.init();
|
||||
println!("Initialized instance!");
|
||||
|
||||
println!("Closing instance...");
|
||||
// Close the instance. This is not necessary as the instance is shut down when
|
||||
// it is dropped as it goes out of scope.
|
||||
// drop(instance);
|
||||
}
|
||||
160
plugin/vst/examples/sine_synth.rs
Normal file
160
plugin/vst/examples/sine_synth.rs
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
// author: Rob Saunders <hello@robsaunders.io>
|
||||
|
||||
#[macro_use]
|
||||
extern crate vst;
|
||||
|
||||
use vst::prelude::*;
|
||||
|
||||
use std::f64::consts::PI;
|
||||
|
||||
/// Convert the midi note's pitch into the equivalent frequency.
|
||||
///
|
||||
/// This function assumes A4 is 440hz.
|
||||
fn midi_pitch_to_freq(pitch: u8) -> f64 {
|
||||
const A4_PITCH: i8 = 69;
|
||||
const A4_FREQ: f64 = 440.0;
|
||||
|
||||
// Midi notes can be 0-127
|
||||
((f64::from(pitch as i8 - A4_PITCH)) / 12.).exp2() * A4_FREQ
|
||||
}
|
||||
|
||||
struct SineSynth {
|
||||
sample_rate: f64,
|
||||
time: f64,
|
||||
note_duration: f64,
|
||||
note: Option<u8>,
|
||||
}
|
||||
|
||||
impl SineSynth {
|
||||
fn time_per_sample(&self) -> f64 {
|
||||
1.0 / self.sample_rate
|
||||
}
|
||||
|
||||
/// Process an incoming midi event.
|
||||
///
|
||||
/// The midi data is split up like so:
|
||||
///
|
||||
/// `data[0]`: Contains the status and the channel. Source: [source]
|
||||
/// `data[1]`: Contains the supplemental data for the message - so, if this was a NoteOn then
|
||||
/// this would contain the note.
|
||||
/// `data[2]`: Further supplemental data. Would be velocity in the case of a NoteOn message.
|
||||
///
|
||||
/// [source]: http://www.midimountain.com/midi/midi_status.htm
|
||||
fn process_midi_event(&mut self, data: [u8; 3]) {
|
||||
match data[0] {
|
||||
128 => self.note_off(data[1]),
|
||||
144 => self.note_on(data[1]),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
fn note_on(&mut self, note: u8) {
|
||||
self.note_duration = 0.0;
|
||||
self.note = Some(note)
|
||||
}
|
||||
|
||||
fn note_off(&mut self, note: u8) {
|
||||
if self.note == Some(note) {
|
||||
self.note = None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub const TAU: f64 = PI * 2.0;
|
||||
|
||||
impl Plugin for SineSynth {
|
||||
fn new(_host: HostCallback) -> Self {
|
||||
SineSynth {
|
||||
sample_rate: 44100.0,
|
||||
note_duration: 0.0,
|
||||
time: 0.0,
|
||||
note: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_info(&self) -> Info {
|
||||
Info {
|
||||
name: "SineSynth".to_string(),
|
||||
vendor: "DeathDisco".to_string(),
|
||||
unique_id: 6667,
|
||||
category: Category::Synth,
|
||||
inputs: 2,
|
||||
outputs: 2,
|
||||
parameters: 0,
|
||||
initial_delay: 0,
|
||||
..Info::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
#[allow(clippy::single_match)]
|
||||
fn process_events(&mut self, events: &Events) {
|
||||
for event in events.events() {
|
||||
match event {
|
||||
Event::Midi(ev) => self.process_midi_event(ev.data),
|
||||
// More events can be handled here.
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn set_sample_rate(&mut self, rate: f32) {
|
||||
self.sample_rate = f64::from(rate);
|
||||
}
|
||||
|
||||
fn process(&mut self, buffer: &mut AudioBuffer<f32>) {
|
||||
let samples = buffer.samples();
|
||||
let (_, mut outputs) = buffer.split();
|
||||
let output_count = outputs.len();
|
||||
let per_sample = self.time_per_sample();
|
||||
let mut output_sample;
|
||||
for sample_idx in 0..samples {
|
||||
let time = self.time;
|
||||
let note_duration = self.note_duration;
|
||||
if let Some(current_note) = self.note {
|
||||
let signal = (time * midi_pitch_to_freq(current_note) * TAU).sin();
|
||||
|
||||
// Apply a quick envelope to the attack of the signal to avoid popping.
|
||||
let attack = 0.5;
|
||||
let alpha = if note_duration < attack {
|
||||
note_duration / attack
|
||||
} else {
|
||||
1.0
|
||||
};
|
||||
|
||||
output_sample = (signal * alpha) as f32;
|
||||
|
||||
self.time += per_sample;
|
||||
self.note_duration += per_sample;
|
||||
} else {
|
||||
output_sample = 0.0;
|
||||
}
|
||||
for buf_idx in 0..output_count {
|
||||
let buff = outputs.get_mut(buf_idx);
|
||||
buff[sample_idx] = output_sample;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn can_do(&self, can_do: CanDo) -> Supported {
|
||||
match can_do {
|
||||
CanDo::ReceiveMidiEvent => Supported::Yes,
|
||||
_ => Supported::Maybe,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
plugin_main!(SineSynth);
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use midi_pitch_to_freq;
|
||||
|
||||
#[test]
|
||||
fn test_midi_pitch_to_freq() {
|
||||
for i in 0..127 {
|
||||
// expect no panics
|
||||
midi_pitch_to_freq(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
136
plugin/vst/examples/transfer_and_smooth.rs
Normal file
136
plugin/vst/examples/transfer_and_smooth.rs
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
// This example illustrates how an existing plugin can be ported to the new,
|
||||
// thread-safe API with the help of the ParameterTransfer struct.
|
||||
// It shows how the parameter iteration feature of ParameterTransfer can be
|
||||
// used to react explicitly to parameter changes in an efficient way (here,
|
||||
// to implement smoothing of parameters).
|
||||
|
||||
#[macro_use]
|
||||
extern crate vst;
|
||||
|
||||
use std::f32;
|
||||
use std::sync::Arc;
|
||||
|
||||
use vst::prelude::*;
|
||||
|
||||
const PARAMETER_COUNT: usize = 100;
|
||||
const BASE_FREQUENCY: f32 = 5.0;
|
||||
const FILTER_FACTOR: f32 = 0.01; // Set this to 1.0 to disable smoothing.
|
||||
const TWO_PI: f32 = 2.0 * f32::consts::PI;
|
||||
|
||||
// 1. Define a struct to hold parameters. Put a ParameterTransfer inside it,
|
||||
// plus optionally a HostCallback.
|
||||
struct MyPluginParameters {
|
||||
#[allow(dead_code)]
|
||||
host: HostCallback,
|
||||
transfer: ParameterTransfer,
|
||||
}
|
||||
|
||||
// 2. Put an Arc reference to your parameter struct in your main Plugin struct.
|
||||
struct MyPlugin {
|
||||
params: Arc<MyPluginParameters>,
|
||||
states: Vec<Smoothed>,
|
||||
sample_rate: f32,
|
||||
phase: f32,
|
||||
}
|
||||
|
||||
// 3. Implement PluginParameters for your parameter struct.
|
||||
// The set_parameter and get_parameter just access the ParameterTransfer.
|
||||
// The other methods can be implemented on top of this as well.
|
||||
impl PluginParameters for MyPluginParameters {
|
||||
fn set_parameter(&self, index: i32, value: f32) {
|
||||
self.transfer.set_parameter(index as usize, value);
|
||||
}
|
||||
|
||||
fn get_parameter(&self, index: i32) -> f32 {
|
||||
self.transfer.get_parameter(index as usize)
|
||||
}
|
||||
}
|
||||
|
||||
impl Plugin for MyPlugin {
|
||||
fn new(host: HostCallback) -> Self {
|
||||
MyPlugin {
|
||||
// 4. Initialize your main Plugin struct with a parameter struct
|
||||
// wrapped in an Arc, and put the HostCallback inside it.
|
||||
params: Arc::new(MyPluginParameters {
|
||||
host,
|
||||
transfer: ParameterTransfer::new(PARAMETER_COUNT),
|
||||
}),
|
||||
states: vec![Smoothed::default(); PARAMETER_COUNT],
|
||||
sample_rate: 44100.0,
|
||||
phase: 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_info(&self) -> Info {
|
||||
Info {
|
||||
parameters: PARAMETER_COUNT as i32,
|
||||
inputs: 0,
|
||||
outputs: 2,
|
||||
category: Category::Synth,
|
||||
f64_precision: false,
|
||||
|
||||
name: "transfer_and_smooth".to_string(),
|
||||
vendor: "Loonies".to_string(),
|
||||
unique_id: 0x500007,
|
||||
version: 100,
|
||||
|
||||
..Info::default()
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Return a reference to the parameter struct from get_parameter_object.
|
||||
fn get_parameter_object(&mut self) -> Arc<dyn PluginParameters> {
|
||||
Arc::clone(&self.params) as Arc<dyn PluginParameters>
|
||||
}
|
||||
|
||||
fn set_sample_rate(&mut self, sample_rate: f32) {
|
||||
self.sample_rate = sample_rate;
|
||||
}
|
||||
|
||||
fn process(&mut self, buffer: &mut AudioBuffer<f32>) {
|
||||
// 6. In the process method, iterate over changed parameters and do
|
||||
// for each what you would previously do in set_parameter. Since this
|
||||
// runs in the processing thread, it has mutable access to the Plugin.
|
||||
for (p, value) in self.params.transfer.iterate(true) {
|
||||
// Example: Update filter state of changed parameter.
|
||||
self.states[p].set(value);
|
||||
}
|
||||
|
||||
// Example: Dummy synth adding together a bunch of sines.
|
||||
let samples = buffer.samples();
|
||||
let mut outputs = buffer.split().1;
|
||||
for i in 0..samples {
|
||||
let mut sum = 0.0;
|
||||
for p in 0..PARAMETER_COUNT {
|
||||
let amp = self.states[p].get();
|
||||
if amp != 0.0 {
|
||||
sum += (self.phase * p as f32 * TWO_PI).sin() * amp;
|
||||
}
|
||||
}
|
||||
outputs[0][i] = sum;
|
||||
outputs[1][i] = sum;
|
||||
self.phase = (self.phase + BASE_FREQUENCY / self.sample_rate).fract();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Example: Parameter smoothing as an example of non-trivial parameter handling
|
||||
// that has to happen when a parameter changes.
|
||||
#[derive(Clone, Default)]
|
||||
struct Smoothed {
|
||||
state: f32,
|
||||
target: f32,
|
||||
}
|
||||
|
||||
impl Smoothed {
|
||||
fn set(&mut self, value: f32) {
|
||||
self.target = value;
|
||||
}
|
||||
|
||||
fn get(&mut self) -> f32 {
|
||||
self.state += (self.target - self.state) * FILTER_FACTOR;
|
||||
self.state
|
||||
}
|
||||
}
|
||||
|
||||
plugin_main!(MyPlugin);
|
||||
Loading…
Add table
Add a link
Reference in a new issue