relayer arranger view and extract button_2 and button_3 to top level

This commit is contained in:
🪞👃🪞 2025-02-09 13:59:51 +01:00
parent d1bb33dc41
commit fe9d5a309e
43 changed files with 7158 additions and 276 deletions

View 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);

View 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,
}
}
}

View 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);

View 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);

View 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);
}

View 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);
}
}
}

View 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);