|
|
@ -0,0 +1,172 @@ |
|
|
|
#[macro_use]
|
|
|
|
extern crate vst;
|
|
|
|
|
|
|
|
use vst::buffer::AudioBuffer;
|
|
|
|
use vst::plugin::{Category, HostCallback, Info, Plugin};
|
|
|
|
|
|
|
|
type Frame = (f32, f32);
|
|
|
|
|
|
|
|
#[derive(Default)]
|
|
|
|
struct Rutter {
|
|
|
|
buf: Vec<Frame>,
|
|
|
|
loop_length: u32,
|
|
|
|
active: bool,
|
|
|
|
// position inside the buffer
|
|
|
|
write_index: usize,
|
|
|
|
read_index: usize,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Rutter {
|
|
|
|
fn is_stereo(&self, buf: &AudioBuffer<f32>) -> bool {
|
|
|
|
buf.input_count() == 2 && buf.output_count() == 2
|
|
|
|
}
|
|
|
|
|
|
|
|
fn is_not_stereo(&self, buf: &AudioBuffer<f32>) -> bool {
|
|
|
|
!self.is_stereo(buf)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Plugin for Rutter {
|
|
|
|
fn new(host: HostCallback) -> Self {
|
|
|
|
Rutter {
|
|
|
|
buf: vec![(0.0, 0.0); 48_000],
|
|
|
|
loop_length: 1_000,
|
|
|
|
active: false,
|
|
|
|
write_index: 0,
|
|
|
|
read_index: 0,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn get_info(&self) -> Info {
|
|
|
|
Info {
|
|
|
|
name: "rutter".into(),
|
|
|
|
vendor: "klingt.net".into(),
|
|
|
|
unique_id: 788968228,
|
|
|
|
category: Category::Effect,
|
|
|
|
inputs: 2,
|
|
|
|
outputs: 2,
|
|
|
|
parameters: 2,
|
|
|
|
|
|
|
|
..Default::default()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn get_parameter(&self, index: i32) -> f32 {
|
|
|
|
match index {
|
|
|
|
0 => (self.active as u32) as f32,
|
|
|
|
1 => self.loop_length as f32,
|
|
|
|
_ => 0.0,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn set_parameter(&mut self, index: i32, val: f32) {
|
|
|
|
match index {
|
|
|
|
0 => self.active = val >= 1.0,
|
|
|
|
1 => self.loop_length = (val * self.buf.len() as f32) as u32, // set min length
|
|
|
|
_ => (),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn get_parameter_text(&self, index: i32) -> String {
|
|
|
|
match index {
|
|
|
|
0 => if self.active { "on" } else { "off" }.into(),
|
|
|
|
1 => format!("{:.2}smpls", (self.loop_length)),
|
|
|
|
_ => "".into(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// This shows the control's name.
|
|
|
|
fn get_parameter_name(&self, index: i32) -> String {
|
|
|
|
match index {
|
|
|
|
0 => "Active",
|
|
|
|
1 => "Loop Length",
|
|
|
|
_ => "",
|
|
|
|
}.to_string()
|
|
|
|
}
|
|
|
|
|
|
|
|
fn process(&mut self, buffer: &mut AudioBuffer<f32>) {
|
|
|
|
if self.is_not_stereo(buffer) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// destructure
|
|
|
|
let buffer_samples = buffer.samples();
|
|
|
|
let (inb, outb) = buffer.split();
|
|
|
|
let in_l = inb.get(0);
|
|
|
|
let in_r = inb.get(1);
|
|
|
|
let out_l = outb.get_mut(0);
|
|
|
|
let out_r = outb.get_mut(1);
|
|
|
|
|
|
|
|
let space_left = self.buf.len() as isize - (self.write_index + buffer_samples) as isize;
|
|
|
|
|
|
|
|
// do not write into buffer when active
|
|
|
|
// TODO: maybe copy looped slice out of internal buffer when active
|
|
|
|
if !self.active {
|
|
|
|
if space_left > 0 {
|
|
|
|
for (i, (l, r)) in
|
|
|
|
(self.write_index..self.write_index + buffer_samples).zip(in_l.iter().zip(in_r))
|
|
|
|
{
|
|
|
|
// TODO: use vec.splice to replace parts
|
|
|
|
self.buf[i] = (*l, *r);
|
|
|
|
}
|
|
|
|
self.write_index += buffer_samples;
|
|
|
|
} else {
|
|
|
|
// wrap around
|
|
|
|
for (i, (l, r)) in (self.write_index..self.buf.len())
|
|
|
|
.chain(0..space_left.abs() as usize)
|
|
|
|
.zip(in_l.iter().zip(in_r))
|
|
|
|
{
|
|
|
|
self.buf[i] = (*l, *r);
|
|
|
|
}
|
|
|
|
self.write_index = space_left.abs() as usize;
|
|
|
|
}
|
|
|
|
self.read_index = self.write_index;
|
|
|
|
}
|
|
|
|
|
|
|
|
// if active read from internal buffer otherwise copy through
|
|
|
|
if self.active {
|
|
|
|
// here should be some loop logic:
|
|
|
|
// - loop length
|
|
|
|
// - playback speed?
|
|
|
|
// - reverse
|
|
|
|
// - modify a running index (`buffer` is usually smaller than the internal buffer)
|
|
|
|
// - start from last index (and wrap-around)
|
|
|
|
|
|
|
|
let wrap_around = self.read_index + buffer_samples > self.buf.len();
|
|
|
|
if wrap_around {
|
|
|
|
let wrap_end = (self.read_index + buffer_samples) - self.buf.len();
|
|
|
|
for (i, (ol, or)) in (self.read_index..self.buf.len())
|
|
|
|
.chain(0..wrap_end)
|
|
|
|
.zip(out_l.iter_mut().zip(out_r))
|
|
|
|
{
|
|
|
|
*ol = self.buf[i].0;
|
|
|
|
*or = self.buf[i].1;
|
|
|
|
}
|
|
|
|
if self.loop_length as usize - (self.buf.len() - self.write_index) < wrap_end {
|
|
|
|
self.read_index = self.write_index;
|
|
|
|
} else {
|
|
|
|
self.read_index = wrap_end;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
for (i, (ol, or)) in (self.read_index..self.read_index + buffer_samples)
|
|
|
|
.zip(out_l.iter_mut().zip(out_r))
|
|
|
|
{
|
|
|
|
*ol = self.buf[i].0;
|
|
|
|
*or = self.buf[i].1;
|
|
|
|
}
|
|
|
|
if self.read_index - self.write_index > self.loop_length as usize {
|
|
|
|
self.read_index = self.write_index;
|
|
|
|
} else {
|
|
|
|
self.read_index += buffer_samples;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// write through
|
|
|
|
for ((li, ri), (lo, ro)) in in_l.iter().zip(in_r).zip(out_l.iter_mut().zip(out_r)) {
|
|
|
|
*lo = *li;
|
|
|
|
*ro = *ri;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
plugin_main!(Rutter);
|