mirror of
https://github.com/storytold/soundcraft.git
synced 2026-10-30 21:46:48 -06:00
263 lines
9.3 KiB
Rust
263 lines
9.3 KiB
Rust
//! `AuPlugin`: a loaded Audio Unit behind the safe `soundcraft_dsp::Plugin` trait.
|
|
|
|
use crate::ffi::Instance;
|
|
use soundcraft_dsp::{Plugin, PluginInfo, Transport};
|
|
|
|
/// Most MIDI events queued between two blocks (extra events are dropped).
|
|
const EVENT_CAP: usize = 1024;
|
|
/// Most channels a host strip may ask for.
|
|
const MAX_CHANNELS: usize = 64;
|
|
|
|
/// A queued MIDI channel message at a frame offset within the next `process` call.
|
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
|
struct MidiEvent {
|
|
time: u32,
|
|
status: u8,
|
|
data1: u8,
|
|
data2: u8,
|
|
}
|
|
|
|
/// A hosted Audio Unit. Parameter ids are global-scope `AudioUnitParameterID`s as decimal
|
|
/// strings; values are the unit's own values, with names and ranges in `info().params`.
|
|
pub struct AuPlugin {
|
|
inst: Instance,
|
|
info: &'static PluginInfo,
|
|
/// Audio Unit id of each `info.params` entry (same order).
|
|
ids: Vec<u32>,
|
|
values: Vec<f32>,
|
|
/// Pending MIDI, kept sorted by time. Never grows past `EVENT_CAP`.
|
|
events: Vec<MidiEvent>,
|
|
held: [bool; 128],
|
|
/// Takes audio input (effects); instruments replace the buffer contents.
|
|
has_input: bool,
|
|
/// Accepts MIDI (instruments and music effects).
|
|
takes_midi: bool,
|
|
channels: usize,
|
|
latency: usize,
|
|
tail: usize,
|
|
}
|
|
|
|
impl AuPlugin {
|
|
pub(crate) fn new(inst: Instance, info: &'static PluginInfo, has_input: bool, takes_midi: bool) -> AuPlugin {
|
|
let ids: Vec<u32> = info.params.iter().map(|p| p.id.parse::<u32>().unwrap_or(u32::MAX)).collect();
|
|
let values = info.params.iter().zip(&ids).map(|(p, &id)| inst.get_param(id).map_or(p.default, |v| p.clamp(v))).collect();
|
|
AuPlugin {
|
|
inst,
|
|
info,
|
|
ids,
|
|
values,
|
|
events: Vec::with_capacity(EVENT_CAP),
|
|
held: [false; 128],
|
|
has_input,
|
|
takes_midi,
|
|
channels: 2,
|
|
latency: 0,
|
|
tail: 0,
|
|
}
|
|
}
|
|
|
|
/// Re-reads every parameter value from the unit (after a state load).
|
|
fn refresh_values(&mut self) {
|
|
for ((slot, p), &id) in self.values.iter_mut().zip(self.info.params).zip(&self.ids) {
|
|
if let Some(v) = self.inst.get_param(id) {
|
|
*slot = p.clamp(v);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Inserts keeping the queue time-sorted; drops the event when the queue is full (never
|
|
/// allocates on the audio thread).
|
|
fn push_event(&mut self, e: MidiEvent) {
|
|
if !self.takes_midi || self.events.len() >= self.events.capacity() {
|
|
return;
|
|
}
|
|
let pos = self.events.iter().rposition(|q| q.time <= e.time).map_or(0, |i| i + 1);
|
|
self.events.insert(pos, e);
|
|
}
|
|
}
|
|
|
|
impl Plugin for AuPlugin {
|
|
fn info(&self) -> &'static PluginInfo {
|
|
self.info
|
|
}
|
|
|
|
fn prepare(&mut self, sample_rate: f32, max_block: usize, channels: usize) {
|
|
let sr = if sample_rate.is_finite() && sample_rate > 0.0 { f64::from(sample_rate) } else { 48_000.0 };
|
|
self.channels = channels.clamp(1, MAX_CHANNELS);
|
|
match self.inst.configure(sr, max_block, self.channels) {
|
|
Ok(_) => {
|
|
self.latency = self.inst.latency();
|
|
self.tail = self.inst.tail();
|
|
}
|
|
Err(e) => {
|
|
log::warn!("{}: {e}", self.info.id);
|
|
self.latency = 0;
|
|
self.tail = 0;
|
|
}
|
|
}
|
|
// Re-send every value so a re-initialized unit matches the session.
|
|
for (&id, &v) in self.ids.iter().zip(&self.values) {
|
|
self.inst.set_param(id, v);
|
|
}
|
|
}
|
|
|
|
fn reset(&mut self) {
|
|
self.events.clear();
|
|
self.held = [false; 128];
|
|
self.inst.reset();
|
|
}
|
|
|
|
fn set_param(&mut self, id: &str, value: f32) -> bool {
|
|
let Some(i) = self.info.params.iter().position(|p| p.id == id) else { return false };
|
|
let (Some(pi), Some(&aid)) = (self.info.params.get(i), self.ids.get(i)) else { return false };
|
|
let v = pi.clamp(value);
|
|
if let Some(slot) = self.values.get_mut(i) {
|
|
*slot = v;
|
|
}
|
|
self.inst.set_param(aid, v);
|
|
true
|
|
}
|
|
|
|
fn param(&self, id: &str) -> Option<f32> {
|
|
let i = self.info.params.iter().position(|p| p.id == id)?;
|
|
self.values.get(i).copied()
|
|
}
|
|
|
|
fn process(&mut self, io: &mut [Vec<f32>], frames: usize) {
|
|
let ch = self.channels.min(io.len());
|
|
let frames = io.iter().take(ch).map(Vec::len).fold(frames, usize::min);
|
|
if frames == 0 || ch == 0 {
|
|
return;
|
|
}
|
|
if !self.inst.is_ready() {
|
|
// Effects pass through; instruments are silent.
|
|
if !self.has_input {
|
|
for c in io.iter_mut().take(ch) {
|
|
c.iter_mut().take(frames).for_each(|s| *s = 0.0);
|
|
}
|
|
}
|
|
self.events.clear();
|
|
return;
|
|
}
|
|
let max = self.inst.max_frames();
|
|
let au_ch = self.inst.channels();
|
|
let last = u32::try_from(frames - 1).unwrap_or(u32::MAX);
|
|
for e in &mut self.events {
|
|
e.time = e.time.min(last);
|
|
}
|
|
let mut start = 0usize;
|
|
while start < frames {
|
|
let n = (frames - start).min(max);
|
|
if self.has_input {
|
|
for c in 0..au_ch {
|
|
let src = io.get(c.min(ch - 1)).and_then(|s| s.get(start..start + n));
|
|
if let (Some(src), Some(dst)) = (src, self.inst.input_mut(c).and_then(|d| d.get_mut(..n))) {
|
|
dst.copy_from_slice(src);
|
|
}
|
|
}
|
|
}
|
|
let end = start + n;
|
|
let k = self.events.iter().position(|e| e.time as usize >= end).unwrap_or(self.events.len());
|
|
for e in self.events.iter().take(k) {
|
|
let offset = u32::try_from((e.time as usize).saturating_sub(start)).unwrap_or(0);
|
|
self.inst.midi(e.status, e.data1, e.data2, offset);
|
|
}
|
|
self.events.drain(..k);
|
|
match self.inst.render(n) {
|
|
Ok(()) => {
|
|
for (c, dst) in io.iter_mut().take(ch).enumerate() {
|
|
let Some(dst) = dst.get_mut(start..end) else { continue };
|
|
if ch == 1 && au_ch >= 2 {
|
|
if let (Some(l), Some(r)) = (self.inst.output(0, n), self.inst.output(1, n)) {
|
|
for ((d, a), b) in dst.iter_mut().zip(l).zip(r) {
|
|
*d = 0.5 * (a + b);
|
|
}
|
|
}
|
|
} else if let Some(src) = self.inst.output(c.min(au_ch.saturating_sub(1)), n) {
|
|
for (d, &s) in dst.iter_mut().zip(src) {
|
|
*d = s;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Err(e) => {
|
|
log::debug!("{}: render failed ({e})", self.info.id);
|
|
if !self.has_input {
|
|
for c in io.iter_mut().take(ch) {
|
|
if let Some(d) = c.get_mut(start..end) {
|
|
d.iter_mut().for_each(|s| *s = 0.0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
start = end;
|
|
}
|
|
}
|
|
|
|
fn latency(&self) -> usize {
|
|
self.latency
|
|
}
|
|
|
|
fn set_transport(&mut self, transport: &Transport) {
|
|
self.inst.set_transport(transport);
|
|
}
|
|
|
|
fn tail_samples(&self) -> usize {
|
|
self.tail
|
|
}
|
|
|
|
fn note_on(&mut self, offset: usize, note: u8, velocity: u8) {
|
|
if note > 127 {
|
|
return;
|
|
}
|
|
let time = u32::try_from(offset).unwrap_or(u32::MAX);
|
|
// Velocity 0 would be a note-off.
|
|
self.push_event(MidiEvent { time, status: 0x90, data1: note, data2: velocity.clamp(1, 127) });
|
|
if let Some(h) = self.held.get_mut(note as usize) {
|
|
*h = true;
|
|
}
|
|
}
|
|
|
|
fn note_off(&mut self, offset: usize, note: u8) {
|
|
if note > 127 {
|
|
return;
|
|
}
|
|
let time = u32::try_from(offset).unwrap_or(u32::MAX);
|
|
self.push_event(MidiEvent { time, status: 0x80, data1: note, data2: 0 });
|
|
if let Some(h) = self.held.get_mut(note as usize) {
|
|
*h = false;
|
|
}
|
|
}
|
|
|
|
fn midi(&mut self, offset: usize, msg: [u8; 3]) {
|
|
let [status, data1, data2] = msg;
|
|
if status & 0xF0 >= 0xA0 && status & 0xF0 < 0xF0 {
|
|
self.push_event(MidiEvent { time: u32::try_from(offset).unwrap_or(u32::MAX), status, data1: data1 & 0x7F, data2: data2 & 0x7F });
|
|
}
|
|
}
|
|
|
|
fn all_notes_off(&mut self) {
|
|
for k in 0..128u8 {
|
|
if self.held.get(k as usize).copied().unwrap_or(false) {
|
|
self.note_off(0, k);
|
|
}
|
|
}
|
|
// CC 123: All Notes Off (catches anything the unit still sustains).
|
|
self.push_event(MidiEvent { time: 0, status: 0xB0, data1: 123, data2: 0 });
|
|
}
|
|
|
|
fn load_state(&mut self, data: &[u8]) -> bool {
|
|
let ok = self.inst.load_state(data);
|
|
if ok {
|
|
self.refresh_values();
|
|
}
|
|
ok
|
|
}
|
|
|
|
fn state_reader(&mut self) -> Option<Box<dyn soundcraft_dsp::PluginStateReader>> {
|
|
let link = self.inst.state_link();
|
|
Some(Box::new(move || link.save_state()))
|
|
}
|
|
}
|