use crate::{audio::AudioBuffers, error::Result, midi::MidiEvent, plugin::Plugin};
use rtrb::{Consumer, Producer, RingBuffer};
enum RtCommand {
Midi(MidiEvent),
Param { id: u32, value: f64 },
}
pub struct RealtimePluginRunner {
plugin: Plugin,
rx: Consumer<RtCommand>,
}
pub struct RtControl {
tx: Producer<RtCommand>,
dropped: u64,
}
impl RealtimePluginRunner {
pub fn new(plugin: Plugin, command_capacity: usize) -> (Self, RtControl) {
let (tx, rx) = RingBuffer::new(command_capacity.max(1));
(Self { plugin, rx }, RtControl { tx, dropped: 0 })
}
pub fn start(&mut self) -> Result<()> {
self.plugin.start_processing()
}
pub fn stop(&mut self) -> Result<()> {
self.plugin.stop_processing()
}
pub fn process(&mut self, buffers: &mut AudioBuffers) -> Result<()> {
while let Ok(cmd) = self.rx.pop() {
match cmd {
RtCommand::Midi(event) => {
let _ = self.plugin.send_midi_event(event);
}
RtCommand::Param { id, value } => {
let _ = self.plugin.set_parameter(id, value);
}
}
}
self.plugin.process_audio(buffers)
}
pub fn plugin(&self) -> &Plugin {
&self.plugin
}
pub fn into_plugin(self) -> Plugin {
self.plugin
}
}
impl RtControl {
pub fn send_midi(&mut self, event: MidiEvent) -> bool {
let ok = self.tx.push(RtCommand::Midi(event)).is_ok();
self.track(ok)
}
pub fn set_parameter(&mut self, id: u32, value: f64) -> bool {
let ok = self.tx.push(RtCommand::Param { id, value }).is_ok();
self.track(ok)
}
pub fn dropped_command_count(&self) -> u64 {
self.dropped
}
fn track(&mut self, ok: bool) -> bool {
if !ok {
self.dropped += 1;
}
ok
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn control_queue_reports_full_without_blocking() {
let (tx, _rx) = RingBuffer::<RtCommand>::new(2);
let mut control = RtControl { tx, dropped: 0 };
assert!(control.set_parameter(1, 0.5));
assert!(control.set_parameter(1, 0.6));
assert!(!control.set_parameter(1, 0.7));
assert!(!control.send_midi(crate::midi::MidiEvent::NoteOn {
channel: crate::midi::MidiChannel::Ch1,
note: 60,
velocity: 100
}));
assert_eq!(control.dropped_command_count(), 2);
}
}