|
| 1 | +use std::sync::{Arc, mpsc::Receiver}; |
| 2 | + |
| 3 | +use cpal::{FromSample, Sample}; |
| 4 | +use plinth_core::{ buffers::buffer::Buffer, signals::{ signal::{Signal, SignalMut}, signal_base::SignalBase } }; |
| 5 | + |
| 6 | +use super::parameters::StandaloneParameterEventMap; |
| 7 | +use super::plugin::StandalonePlugin; |
| 8 | +use crate::{Event, Processor}; |
| 9 | + |
| 10 | +/// Push events to a event list vec, printing a warning when preallocated memory exceeded. |
| 11 | +trait EventListPush { |
| 12 | + type EventType; |
| 13 | + fn push_event(&mut self, event: Self::EventType); |
| 14 | +} |
| 15 | + |
| 16 | +impl EventListPush for Vec<Event> { |
| 17 | + type EventType = Event; |
| 18 | + fn push_event(&mut self, event: Event) { |
| 19 | + if self.len() == self.capacity() { |
| 20 | + log::warn!( |
| 21 | + "Event queue exceeded preallocated capacity of {} - allocating more. \ |
| 22 | + Increase EVENT_QUEUE_LEN to avoid allocation on the audio thread.", |
| 23 | + self.capacity() |
| 24 | + ); |
| 25 | + self.reserve(128); |
| 26 | + } |
| 27 | + self.push(event); |
| 28 | + } |
| 29 | +} |
| 30 | + |
| 31 | +/// Runs a plinth processor on a CPAL audio stream |
| 32 | +pub struct AudioState<P: StandalonePlugin> { |
| 33 | + pub processor: P::Processor, |
| 34 | + pub buffer: Buffer, |
| 35 | + pub channels: usize, |
| 36 | + pub midi_receiver: Receiver<Event>, |
| 37 | + pub parameter_event_map: Arc<StandaloneParameterEventMap>, |
| 38 | + pending_events: Vec<Event>, |
| 39 | +} |
| 40 | + |
| 41 | +impl<P: StandalonePlugin> AudioState<P> { |
| 42 | + pub fn new( |
| 43 | + processor: P::Processor, |
| 44 | + channels: usize, |
| 45 | + midi_receiver: Receiver<Event>, |
| 46 | + parameter_event_map: Arc<StandaloneParameterEventMap>, |
| 47 | + ) -> Self { |
| 48 | + Self { |
| 49 | + processor, |
| 50 | + buffer: Buffer::new(channels, P::MAX_BLOCK_SIZE), |
| 51 | + channels, |
| 52 | + midi_receiver, |
| 53 | + parameter_event_map, |
| 54 | + pending_events: Vec::with_capacity(P::EVENT_QUEUE_LEN), |
| 55 | + } |
| 56 | + } |
| 57 | + |
| 58 | + pub fn process<T>(&mut self, data: &mut [T], channels: usize) |
| 59 | + where |
| 60 | + T: Sample + FromSample<f32>, |
| 61 | + f32: FromSample<T>, |
| 62 | + { |
| 63 | + let frame_count = data.len() / channels; |
| 64 | + |
| 65 | + // Drain MIDI events |
| 66 | + self.pending_events.clear(); |
| 67 | + while let Ok(event) = self.midi_receiver.try_recv() { |
| 68 | + self.pending_events.push_event(event); |
| 69 | + } |
| 70 | + |
| 71 | + // Collect pending parameter change events |
| 72 | + for event in self.parameter_event_map.iter_events() { |
| 73 | + self.pending_events.push_event(event); |
| 74 | + } |
| 75 | + |
| 76 | + // Process audio, ensuring we don't call process with more than P::MAX_BLOCK_SIZE frames |
| 77 | + debug_assert!( |
| 78 | + self.buffer.capacity() == P::MAX_BLOCK_SIZE, |
| 79 | + "Buffer must be preallocated to avoid allocation on the audio thread" |
| 80 | + ); |
| 81 | + |
| 82 | + let mut frame_offset = 0; |
| 83 | + while frame_offset < frame_count { |
| 84 | + let chunk_size = (frame_count - frame_offset).min(P::MAX_BLOCK_SIZE); |
| 85 | + |
| 86 | + // Truncate or extend buffer to fit the chunk |
| 87 | + if self.buffer.len() != chunk_size { |
| 88 | + self.buffer.resize(chunk_size); |
| 89 | + } |
| 90 | + |
| 91 | + // Deinterleave chunk from CPAL buffer |
| 92 | + for frame in 0..chunk_size { |
| 93 | + for ch in 0..self.channels { |
| 94 | + self.buffer.channel_mut(ch)[frame] = |
| 95 | + f32::from_sample(data[(frame_offset + frame) * self.channels + ch]); |
| 96 | + } |
| 97 | + } |
| 98 | + |
| 99 | + // Process and drain all events on first run, assuming they have no time tags |
| 100 | + let aux: Option<&Buffer> = None; |
| 101 | + self.processor |
| 102 | + .process(&mut self.buffer, aux, None, self.pending_events.drain(..)); |
| 103 | + |
| 104 | + // Reinterleave chunk back into CPAL buffer |
| 105 | + for frame in 0..chunk_size { |
| 106 | + for ch in 0..self.channels { |
| 107 | + data[(frame_offset + frame) * self.channels + ch] = |
| 108 | + T::from_sample(self.buffer.channel(ch)[frame]); |
| 109 | + } |
| 110 | + } |
| 111 | + |
| 112 | + frame_offset += chunk_size; |
| 113 | + } |
| 114 | + } |
| 115 | +} |
0 commit comments