1use sim_kernel::{Error, Result, Symbol};
2use sim_lib_audio_graph_core::{
3 BlockArena, BlockEvent, EventSink, PrepareConfig, ProcessBlock, Processor, Transport,
4};
5use sim_lib_stream_core::{
6 PcmPacket, StreamEnvelope, StreamInspectorSnapshot, StreamInspectorStatus, StreamPacket,
7 TransportProfile,
8};
9
10pub use crate::runner_types::{LiveGraphConfig, LiveProcessReport, LiveSteadyStateSnapshot};
11use crate::{
12 AudioToControlQueue, ControlToAudioQueue, LiveAudioEvent, LiveControlEvent, LiveStreamLane,
13 runner_types::MAX_LIVE_EVENTS, validate_realtime_local_audio_profile,
14};
15
16#[derive(Debug)]
18pub struct LiveGraphRunner<P> {
19 processor: P,
20 config: LiveGraphConfig,
21 input_planar: Vec<Vec<f32>>,
22 output_planar: Vec<Vec<f32>>,
23 scratch: BlockArena,
24 event_slots: [BlockEvent<'static>; MAX_LIVE_EVENTS],
25 control_to_audio: ControlToAudioQueue,
26 audio_to_control: AudioToControlQueue,
27}
28
29impl<P: Processor> LiveGraphRunner<P> {
30 pub fn new_realtime(
32 processor: P,
33 config: LiveGraphConfig,
34 profile: &TransportProfile,
35 ) -> Result<Self> {
36 validate_realtime_local_audio_profile(profile)?;
37 Self::new(processor, config)
38 }
39
40 pub fn new(mut processor: P, config: LiveGraphConfig) -> Result<Self> {
43 processor.prepare(PrepareConfig::new(
44 config.spec.sample_rate_hz(),
45 config.max_block_frames,
46 checked_channels(config.input_channels, "input")?,
47 checked_channels(config.spec.channels(), "output")?,
48 ));
49 let max_frames = config.max_block_frames as usize;
50 Ok(Self {
51 processor,
52 config,
53 input_planar: vec![vec![0.0; max_frames]; config.input_channels],
54 output_planar: vec![vec![0.0; max_frames]; config.spec.channels()],
55 scratch: BlockArena::with_f32_capacity(
56 max_frames * config.input_channels.max(config.spec.channels()).max(1),
57 ),
58 event_slots: [empty_event(); MAX_LIVE_EVENTS],
59 control_to_audio: ControlToAudioQueue::with_capacity(config.control_queue_capacity)?,
60 audio_to_control: AudioToControlQueue::with_capacity(config.audio_queue_capacity)?,
61 })
62 }
63
64 pub fn enqueue_control_event(&mut self, event: LiveControlEvent) -> crate::LiveQueuePush {
66 self.control_to_audio.push(event)
67 }
68
69 pub fn enqueue_midi_short(
71 &mut self,
72 offset: u32,
73 bytes: &[u8],
74 ) -> Result<crate::LiveQueuePush> {
75 Ok(self.enqueue_control_event(LiveControlEvent::midi_short(offset, bytes)?))
76 }
77
78 pub fn enqueue_param_set(
80 &mut self,
81 offset: u32,
82 param: u32,
83 value: f64,
84 ) -> Result<crate::LiveQueuePush> {
85 Ok(self.enqueue_control_event(LiveControlEvent::param_set(offset, param, value)?))
86 }
87
88 pub fn process_interleaved_f32(
91 &mut self,
92 input: Option<&[f32]>,
93 output: &mut [f32],
94 frames: usize,
95 transport: Transport,
96 ) -> Result<LiveProcessReport> {
97 self.validate_block(input, output, frames)?;
98 let dropped_control_events = self.control_to_audio.take_dropped();
99 if dropped_control_events > 0 {
100 self.record_audio_event(LiveAudioEvent::DroppedControlEvents {
101 count: dropped_control_events,
102 });
103 }
104 let event_count = self.drain_control_events(frames)?;
105 self.copy_input(input, frames);
106 self.clear_output(frames);
107 self.run_processor(frames, event_count, transport)?;
108 self.copy_output(output, frames);
109 Ok(LiveProcessReport {
110 frames: frames as u32,
111 control_events: event_count,
112 dropped_control_events,
113 })
114 }
115
116 pub fn drain_audio_events(&mut self) -> Vec<LiveAudioEvent> {
119 let mut events = Vec::new();
120 while let Some(event) = self.audio_to_control.pop() {
121 events.push(event);
122 }
123 let dropped = self.audio_to_control.take_dropped();
124 if dropped > 0 {
125 events.push(LiveAudioEvent::DroppedAudioEvents { count: dropped });
126 }
127 events
128 }
129
130 pub fn drain_audio_diagnostics(&mut self) -> Vec<sim_lib_stream_core::StreamPacket> {
132 self.drain_audio_events()
133 .into_iter()
134 .map(LiveAudioEvent::to_diagnostic_packet)
135 .collect()
136 }
137
138 pub fn diagnostic_inspector(&self) -> Result<StreamInspectorSnapshot> {
140 let metadata = LiveStreamLane::Diagnostic.metadata(self.audio_to_control.capacity())?;
141 let stats = self.audio_to_control.stats();
142 Ok(StreamInspectorSnapshot::new(
143 &metadata,
144 Symbol::qualified("stream/route", "live-audio-callback"),
145 TransportProfile::realtime_local_audio().name().clone(),
146 StreamInspectorStatus::from_stats(&stats, false),
147 self.audio_to_control.len(),
148 &stats,
149 stats.pushed.checked_sub(1),
150 Vec::new(),
151 ))
152 }
153
154 pub fn steady_state_snapshot(&self) -> LiveSteadyStateSnapshot {
156 LiveSteadyStateSnapshot {
157 input_lane_capacity: self.input_planar.iter().map(Vec::capacity).collect(),
158 output_lane_capacity: self.output_planar.iter().map(Vec::capacity).collect(),
159 scratch_capacity: self.scratch.f32_capacity(),
160 control_queue_capacity: self.control_to_audio.allocated_capacity(),
161 audio_queue_capacity: self.audio_to_control.allocated_capacity(),
162 }
163 }
164
165 pub fn buffered_preview_chunk(
167 &self,
168 output: &[f32],
169 frames: usize,
170 sequence: u64,
171 ) -> Result<StreamEnvelope> {
172 let samples = self.validate_preview_block(output, frames)?;
173 let packet = StreamPacket::Pcm(PcmPacket::f32(
174 self.config.spec.channels(),
175 frames,
176 output[..samples].to_vec(),
177 )?);
178 LiveStreamLane::AudioOutput.lan_buffered_preview_envelope(sequence, Vec::new(), packet)
179 }
180
181 fn validate_block(
182 &mut self,
183 input: Option<&[f32]>,
184 output: &[f32],
185 frames: usize,
186 ) -> Result<()> {
187 if frames > self.config.max_block_frames as usize {
188 self.record_audio_event(LiveAudioEvent::Xrun {
189 frames: frames as u32,
190 max_frames: self.config.max_block_frames,
191 });
192 return Err(Error::Eval(format!(
193 "live graph block has {frames} frames, max block is {}",
194 self.config.max_block_frames
195 )));
196 }
197 let input_samples = frames.saturating_mul(self.config.input_channels);
198 if let Some(samples) = input
199 && samples.len() < input_samples
200 {
201 return Err(Error::Eval(format!(
202 "live graph input has {} samples, expected at least {input_samples}",
203 samples.len()
204 )));
205 }
206 let output_samples = frames.saturating_mul(self.config.spec.channels());
207 if output.len() < output_samples {
208 return Err(Error::Eval(format!(
209 "live graph output has {} samples, expected at least {output_samples}",
210 output.len()
211 )));
212 }
213 Ok(())
214 }
215
216 fn validate_preview_block(&self, output: &[f32], frames: usize) -> Result<usize> {
217 if frames > self.config.max_block_frames as usize {
218 return Err(Error::Eval(format!(
219 "live graph preview has {frames} frames, max block is {}",
220 self.config.max_block_frames
221 )));
222 }
223 let output_samples = frames
224 .checked_mul(self.config.spec.channels())
225 .ok_or_else(|| Error::Eval("live graph preview sample count overflowed".to_owned()))?;
226 if output.len() < output_samples {
227 return Err(Error::Eval(format!(
228 "live graph preview has {} samples, expected at least {output_samples}",
229 output.len()
230 )));
231 }
232 Ok(output_samples)
233 }
234
235 fn drain_control_events(&mut self, frames: usize) -> Result<usize> {
236 let mut count = 0;
237 while let Some(event) = self.control_to_audio.pop() {
238 if event.offset() >= frames as u32 {
239 return Err(Error::Eval(format!(
240 "live control event offset {} is outside block frames 0..{frames}",
241 event.offset()
242 )));
243 }
244 self.event_slots[count] = event.to_block_event();
245 count += 1;
246 }
247 Ok(count)
248 }
249
250 fn copy_input(&mut self, input: Option<&[f32]>, frames: usize) {
251 for lane in &mut self.input_planar {
252 lane[..frames].fill(0.0);
253 }
254 if let Some(samples) = input {
255 for frame in 0..frames {
256 for channel in 0..self.config.input_channels {
257 self.input_planar[channel][frame] =
258 samples[frame * self.config.input_channels + channel];
259 }
260 }
261 }
262 }
263
264 fn clear_output(&mut self, frames: usize) {
265 for lane in &mut self.output_planar {
266 lane[..frames].fill(0.0);
267 }
268 }
269
270 fn copy_output(&self, output: &mut [f32], frames: usize) {
271 let channels = self.config.spec.channels();
272 for frame in 0..frames {
273 for channel in 0..channels {
274 output[frame * channels + channel] = self.output_planar[channel][frame];
275 }
276 }
277 }
278
279 fn run_processor(
280 &mut self,
281 frames: usize,
282 event_count: usize,
283 transport: Transport,
284 ) -> Result<()> {
285 let in_events = &self.event_slots[..event_count];
286 let processor = &mut self.processor;
287 let scratch = &mut self.scratch;
288 let input_planar = &self.input_planar;
289 let output_planar = &mut self.output_planar;
290 let audio_to_control = &mut self.audio_to_control;
291
292 macro_rules! run_block {
293 ($in_audio:expr, $out_audio:expr) => {{
294 let mut event_sink = LiveEventSink {
295 queue: audio_to_control,
296 };
297 scratch.reset();
298 let mut block = ProcessBlock {
299 frames: frames as u32,
300 in_audio: $in_audio,
301 out_audio: $out_audio,
302 in_events,
303 out_events: &mut event_sink,
304 transport,
305 scratch,
306 };
307 block.validate_audio_lanes()?;
308 processor.process(&mut block);
309 block.validate_audio_lanes()
310 }};
311 }
312
313 match (self.config.input_channels, self.config.spec.channels()) {
314 (0, 1) => {
315 let in_audio: [&[f32]; 0] = [];
316 let mut out_audio = [&mut output_planar[0][..frames]];
317 run_block!(&in_audio, &mut out_audio)
318 }
319 (0, 2) => {
320 let in_audio: [&[f32]; 0] = [];
321 let (left, right) = output_planar.split_at_mut(1);
322 let mut out_audio = [&mut left[0][..frames], &mut right[0][..frames]];
323 run_block!(&in_audio, &mut out_audio)
324 }
325 (1, 1) => {
326 let in_audio = [&input_planar[0][..frames]];
327 let mut out_audio = [&mut output_planar[0][..frames]];
328 run_block!(&in_audio, &mut out_audio)
329 }
330 (1, 2) => {
331 let in_audio = [&input_planar[0][..frames]];
332 let (left, right) = output_planar.split_at_mut(1);
333 let mut out_audio = [&mut left[0][..frames], &mut right[0][..frames]];
334 run_block!(&in_audio, &mut out_audio)
335 }
336 (2, 1) => {
337 let in_audio = [&input_planar[0][..frames], &input_planar[1][..frames]];
338 let mut out_audio = [&mut output_planar[0][..frames]];
339 run_block!(&in_audio, &mut out_audio)
340 }
341 (2, 2) => {
342 let in_audio = [&input_planar[0][..frames], &input_planar[1][..frames]];
343 let (left, right) = output_planar.split_at_mut(1);
344 let mut out_audio = [&mut left[0][..frames], &mut right[0][..frames]];
345 run_block!(&in_audio, &mut out_audio)
346 }
347 _ => Err(Error::Eval(
348 "live graph runner supports mono and stereo I/O".to_owned(),
349 )),
350 }
351 }
352
353 fn record_audio_event(&mut self, event: LiveAudioEvent) {
354 let _ = self.audio_to_control.push(event);
355 }
356}
357
358struct LiveEventSink<'a> {
359 queue: &'a mut AudioToControlQueue,
360}
361
362impl EventSink for LiveEventSink<'_> {
363 fn push(&mut self, event: BlockEvent<'_>) -> Result<()> {
364 if let Some(event) = LiveAudioEvent::from_processor_event(event) {
365 let _ = self.queue.push(event);
366 }
367 Ok(())
368 }
369}
370
371fn checked_channels(channels: usize, role: &str) -> Result<u16> {
372 u16::try_from(channels)
373 .map_err(|_| Error::Eval(format!("live graph {role} channel count exceeds u16")))
374}
375
376const fn empty_event() -> BlockEvent<'static> {
377 BlockEvent::ParamSet {
378 offset: 0,
379 param: 0,
380 value: 0.0,
381 }
382}