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_audio::PcmSpec;
6use sim_lib_stream_core::{
7 PcmPacket, StreamEnvelope, StreamInspectorSnapshot, StreamInspectorStatus, StreamPacket,
8 TransportProfile,
9};
10
11use crate::{
12 AudioToControlQueue, ControlToAudioQueue, LiveAudioEvent, LiveControlEvent, LiveStreamLane,
13 validate_realtime_local_audio_profile,
14};
15
16const MAX_LIVE_CHANNELS: usize = 2;
17const MAX_LIVE_EVENTS: usize = 64;
18
19#[derive(Clone, Copy, Debug, PartialEq, Eq)]
21pub struct LiveGraphConfig {
22 spec: PcmSpec,
23 input_channels: usize,
24 max_block_frames: u32,
25 control_queue_capacity: usize,
26 audio_queue_capacity: usize,
27}
28
29#[derive(Clone, Copy, Debug, PartialEq, Eq)]
31pub struct LiveProcessReport {
32 frames: u32,
33 control_events: usize,
34 dropped_control_events: u64,
35}
36
37#[derive(Clone, Debug, PartialEq, Eq)]
39pub struct LiveSteadyStateSnapshot {
40 input_lane_capacity: Vec<usize>,
41 output_lane_capacity: Vec<usize>,
42 scratch_capacity: usize,
43 control_queue_capacity: usize,
44 audio_queue_capacity: usize,
45}
46
47#[derive(Debug)]
49pub struct LiveGraphRunner<P> {
50 processor: P,
51 config: LiveGraphConfig,
52 input_planar: Vec<Vec<f32>>,
53 output_planar: Vec<Vec<f32>>,
54 scratch: BlockArena,
55 event_slots: [BlockEvent<'static>; MAX_LIVE_EVENTS],
56 control_to_audio: ControlToAudioQueue,
57 audio_to_control: AudioToControlQueue,
58}
59
60impl LiveGraphConfig {
61 pub fn new(
64 spec: PcmSpec,
65 input_channels: usize,
66 max_block_frames: u32,
67 control_queue_capacity: usize,
68 audio_queue_capacity: usize,
69 ) -> Result<Self> {
70 if max_block_frames == 0 {
71 return Err(Error::Eval(
72 "live graph max block frames must be greater than zero".to_owned(),
73 ));
74 }
75 if input_channels > MAX_LIVE_CHANNELS || spec.channels() > MAX_LIVE_CHANNELS {
76 return Err(Error::Eval(format!(
77 "live graph runner supports up to {MAX_LIVE_CHANNELS} channels"
78 )));
79 }
80 if control_queue_capacity == 0 || audio_queue_capacity == 0 {
81 return Err(Error::Eval(
82 "live graph queues must be bounded and non-zero".to_owned(),
83 ));
84 }
85 if control_queue_capacity > MAX_LIVE_EVENTS {
86 return Err(Error::Eval(format!(
87 "live graph control queue supports up to {MAX_LIVE_EVENTS} events per block"
88 )));
89 }
90 if audio_queue_capacity > MAX_LIVE_EVENTS {
91 return Err(Error::Eval(format!(
92 "live graph audio diagnostic queue supports up to {MAX_LIVE_EVENTS} events per block"
93 )));
94 }
95 Ok(Self {
96 spec,
97 input_channels,
98 max_block_frames,
99 control_queue_capacity,
100 audio_queue_capacity,
101 })
102 }
103
104 pub fn stereo(sample_rate_hz: u32, max_block_frames: u32) -> Result<Self> {
106 Self::new(
107 PcmSpec::f32(2, sample_rate_hz)?,
108 2,
109 max_block_frames,
110 MAX_LIVE_EVENTS,
111 MAX_LIVE_EVENTS,
112 )
113 }
114
115 pub fn spec(self) -> PcmSpec {
117 self.spec
118 }
119
120 pub fn input_channels(self) -> usize {
122 self.input_channels
123 }
124
125 pub fn output_channels(self) -> usize {
127 self.spec.channels()
128 }
129
130 pub fn max_block_frames(self) -> u32 {
132 self.max_block_frames
133 }
134}
135
136impl<P: Processor> LiveGraphRunner<P> {
137 pub fn new_realtime(
139 processor: P,
140 config: LiveGraphConfig,
141 profile: &TransportProfile,
142 ) -> Result<Self> {
143 validate_realtime_local_audio_profile(profile)?;
144 Self::new(processor, config)
145 }
146
147 pub fn new(mut processor: P, config: LiveGraphConfig) -> Result<Self> {
150 processor.prepare(PrepareConfig::new(
151 config.spec.sample_rate_hz(),
152 config.max_block_frames,
153 checked_channels(config.input_channels, "input")?,
154 checked_channels(config.spec.channels(), "output")?,
155 ));
156 let max_frames = config.max_block_frames as usize;
157 Ok(Self {
158 processor,
159 config,
160 input_planar: vec![vec![0.0; max_frames]; config.input_channels],
161 output_planar: vec![vec![0.0; max_frames]; config.spec.channels()],
162 scratch: BlockArena::with_f32_capacity(
163 max_frames * config.input_channels.max(config.spec.channels()).max(1),
164 ),
165 event_slots: [empty_event(); MAX_LIVE_EVENTS],
166 control_to_audio: ControlToAudioQueue::with_capacity(config.control_queue_capacity)?,
167 audio_to_control: AudioToControlQueue::with_capacity(config.audio_queue_capacity)?,
168 })
169 }
170
171 pub fn enqueue_control_event(&mut self, event: LiveControlEvent) -> crate::LiveQueuePush {
173 self.control_to_audio.push(event)
174 }
175
176 pub fn enqueue_midi_short(
178 &mut self,
179 offset: u32,
180 bytes: &[u8],
181 ) -> Result<crate::LiveQueuePush> {
182 Ok(self.enqueue_control_event(LiveControlEvent::midi_short(offset, bytes)?))
183 }
184
185 pub fn enqueue_param_set(
187 &mut self,
188 offset: u32,
189 param: u32,
190 value: f64,
191 ) -> Result<crate::LiveQueuePush> {
192 Ok(self.enqueue_control_event(LiveControlEvent::param_set(offset, param, value)?))
193 }
194
195 pub fn process_interleaved_f32(
198 &mut self,
199 input: Option<&[f32]>,
200 output: &mut [f32],
201 frames: usize,
202 transport: Transport,
203 ) -> Result<LiveProcessReport> {
204 self.validate_block(input, output, frames)?;
205 let dropped_control_events = self.control_to_audio.take_dropped();
206 if dropped_control_events > 0 {
207 self.record_audio_event(LiveAudioEvent::DroppedControlEvents {
208 count: dropped_control_events,
209 });
210 }
211 let event_count = self.drain_control_events(frames)?;
212 self.copy_input(input, frames);
213 self.clear_output(frames);
214 self.run_processor(frames, event_count, transport)?;
215 self.copy_output(output, frames);
216 Ok(LiveProcessReport {
217 frames: frames as u32,
218 control_events: event_count,
219 dropped_control_events,
220 })
221 }
222
223 pub fn drain_audio_events(&mut self) -> Vec<LiveAudioEvent> {
226 let mut events = Vec::new();
227 while let Some(event) = self.audio_to_control.pop() {
228 events.push(event);
229 }
230 let dropped = self.audio_to_control.take_dropped();
231 if dropped > 0 {
232 events.push(LiveAudioEvent::DroppedAudioEvents { count: dropped });
233 }
234 events
235 }
236
237 pub fn drain_audio_diagnostics(&mut self) -> Vec<sim_lib_stream_core::StreamPacket> {
239 self.drain_audio_events()
240 .into_iter()
241 .map(LiveAudioEvent::to_diagnostic_packet)
242 .collect()
243 }
244
245 pub fn diagnostic_inspector(&self) -> Result<StreamInspectorSnapshot> {
247 let metadata = LiveStreamLane::Diagnostic.metadata(self.audio_to_control.capacity())?;
248 let stats = self.audio_to_control.stats();
249 Ok(StreamInspectorSnapshot::new(
250 &metadata,
251 Symbol::qualified("stream/route", "live-audio-callback"),
252 TransportProfile::realtime_local_audio().name().clone(),
253 StreamInspectorStatus::from_stats(&stats, false),
254 self.audio_to_control.len(),
255 &stats,
256 stats.pushed.checked_sub(1),
257 Vec::new(),
258 ))
259 }
260
261 pub fn steady_state_snapshot(&self) -> LiveSteadyStateSnapshot {
263 LiveSteadyStateSnapshot {
264 input_lane_capacity: self.input_planar.iter().map(Vec::capacity).collect(),
265 output_lane_capacity: self.output_planar.iter().map(Vec::capacity).collect(),
266 scratch_capacity: self.scratch.f32_capacity(),
267 control_queue_capacity: self.control_to_audio.allocated_capacity(),
268 audio_queue_capacity: self.audio_to_control.allocated_capacity(),
269 }
270 }
271
272 pub fn buffered_preview_chunk(
274 &self,
275 output: &[f32],
276 frames: usize,
277 sequence: u64,
278 ) -> Result<StreamEnvelope> {
279 let samples = self.validate_preview_block(output, frames)?;
280 let packet = StreamPacket::Pcm(PcmPacket::f32(
281 self.config.spec.channels(),
282 frames,
283 output[..samples].to_vec(),
284 )?);
285 LiveStreamLane::AudioOutput.lan_buffered_preview_envelope(sequence, Vec::new(), packet)
286 }
287
288 fn validate_block(
289 &mut self,
290 input: Option<&[f32]>,
291 output: &[f32],
292 frames: usize,
293 ) -> Result<()> {
294 if frames > self.config.max_block_frames as usize {
295 self.record_audio_event(LiveAudioEvent::Xrun {
296 frames: frames as u32,
297 max_frames: self.config.max_block_frames,
298 });
299 return Err(Error::Eval(format!(
300 "live graph block has {frames} frames, max block is {}",
301 self.config.max_block_frames
302 )));
303 }
304 let input_samples = frames.saturating_mul(self.config.input_channels);
305 if let Some(samples) = input
306 && samples.len() < input_samples
307 {
308 return Err(Error::Eval(format!(
309 "live graph input has {} samples, expected at least {input_samples}",
310 samples.len()
311 )));
312 }
313 let output_samples = frames.saturating_mul(self.config.spec.channels());
314 if output.len() < output_samples {
315 return Err(Error::Eval(format!(
316 "live graph output has {} samples, expected at least {output_samples}",
317 output.len()
318 )));
319 }
320 Ok(())
321 }
322
323 fn validate_preview_block(&self, output: &[f32], frames: usize) -> Result<usize> {
324 if frames > self.config.max_block_frames as usize {
325 return Err(Error::Eval(format!(
326 "live graph preview has {frames} frames, max block is {}",
327 self.config.max_block_frames
328 )));
329 }
330 let output_samples = frames
331 .checked_mul(self.config.spec.channels())
332 .ok_or_else(|| Error::Eval("live graph preview sample count overflowed".to_owned()))?;
333 if output.len() < output_samples {
334 return Err(Error::Eval(format!(
335 "live graph preview has {} samples, expected at least {output_samples}",
336 output.len()
337 )));
338 }
339 Ok(output_samples)
340 }
341
342 fn drain_control_events(&mut self, frames: usize) -> Result<usize> {
343 let mut count = 0;
344 while let Some(event) = self.control_to_audio.pop() {
345 if event.offset() > frames as u32 {
346 return Err(Error::Eval(format!(
347 "live control event offset {} exceeds block frames {frames}",
348 event.offset()
349 )));
350 }
351 self.event_slots[count] = event.to_block_event();
352 count += 1;
353 }
354 Ok(count)
355 }
356
357 fn copy_input(&mut self, input: Option<&[f32]>, frames: usize) {
358 for lane in &mut self.input_planar {
359 lane[..frames].fill(0.0);
360 }
361 if let Some(samples) = input {
362 for frame in 0..frames {
363 for channel in 0..self.config.input_channels {
364 self.input_planar[channel][frame] =
365 samples[frame * self.config.input_channels + channel];
366 }
367 }
368 }
369 }
370
371 fn clear_output(&mut self, frames: usize) {
372 for lane in &mut self.output_planar {
373 lane[..frames].fill(0.0);
374 }
375 }
376
377 fn copy_output(&self, output: &mut [f32], frames: usize) {
378 let channels = self.config.spec.channels();
379 for frame in 0..frames {
380 for channel in 0..channels {
381 output[frame * channels + channel] = self.output_planar[channel][frame];
382 }
383 }
384 }
385
386 fn run_processor(
387 &mut self,
388 frames: usize,
389 event_count: usize,
390 transport: Transport,
391 ) -> Result<()> {
392 let in_events = &self.event_slots[..event_count];
393 let processor = &mut self.processor;
394 let scratch = &mut self.scratch;
395 let input_planar = &self.input_planar;
396 let output_planar = &mut self.output_planar;
397 let audio_to_control = &mut self.audio_to_control;
398
399 macro_rules! run_block {
400 ($in_audio:expr, $out_audio:expr) => {{
401 let mut event_sink = LiveEventSink {
402 queue: audio_to_control,
403 };
404 scratch.reset();
405 let mut block = ProcessBlock {
406 frames: frames as u32,
407 in_audio: $in_audio,
408 out_audio: $out_audio,
409 in_events,
410 out_events: &mut event_sink,
411 transport,
412 scratch,
413 };
414 block.validate_audio_lanes()?;
415 processor.process(&mut block);
416 block.validate_audio_lanes()
417 }};
418 }
419
420 match (self.config.input_channels, self.config.spec.channels()) {
421 (0, 1) => {
422 let in_audio: [&[f32]; 0] = [];
423 let mut out_audio = [&mut output_planar[0][..frames]];
424 run_block!(&in_audio, &mut out_audio)
425 }
426 (0, 2) => {
427 let in_audio: [&[f32]; 0] = [];
428 let (left, right) = output_planar.split_at_mut(1);
429 let mut out_audio = [&mut left[0][..frames], &mut right[0][..frames]];
430 run_block!(&in_audio, &mut out_audio)
431 }
432 (1, 1) => {
433 let in_audio = [&input_planar[0][..frames]];
434 let mut out_audio = [&mut output_planar[0][..frames]];
435 run_block!(&in_audio, &mut out_audio)
436 }
437 (1, 2) => {
438 let in_audio = [&input_planar[0][..frames]];
439 let (left, right) = output_planar.split_at_mut(1);
440 let mut out_audio = [&mut left[0][..frames], &mut right[0][..frames]];
441 run_block!(&in_audio, &mut out_audio)
442 }
443 (2, 1) => {
444 let in_audio = [&input_planar[0][..frames], &input_planar[1][..frames]];
445 let mut out_audio = [&mut output_planar[0][..frames]];
446 run_block!(&in_audio, &mut out_audio)
447 }
448 (2, 2) => {
449 let in_audio = [&input_planar[0][..frames], &input_planar[1][..frames]];
450 let (left, right) = output_planar.split_at_mut(1);
451 let mut out_audio = [&mut left[0][..frames], &mut right[0][..frames]];
452 run_block!(&in_audio, &mut out_audio)
453 }
454 _ => Err(Error::Eval(
455 "live graph runner supports mono and stereo I/O".to_owned(),
456 )),
457 }
458 }
459
460 fn record_audio_event(&mut self, event: LiveAudioEvent) {
461 let _ = self.audio_to_control.push(event);
462 }
463}
464
465struct LiveEventSink<'a> {
466 queue: &'a mut AudioToControlQueue,
467}
468
469impl EventSink for LiveEventSink<'_> {
470 fn push(&mut self, event: BlockEvent<'_>) -> Result<()> {
471 if let Some(event) = LiveAudioEvent::from_processor_event(event) {
472 let _ = self.queue.push(event);
473 }
474 Ok(())
475 }
476}
477
478fn checked_channels(channels: usize, role: &str) -> Result<u16> {
479 u16::try_from(channels)
480 .map_err(|_| Error::Eval(format!("live graph {role} channel count exceeds u16")))
481}
482
483const fn empty_event() -> BlockEvent<'static> {
484 BlockEvent::ParamSet {
485 offset: 0,
486 param: 0,
487 value: 0.0,
488 }
489}
490
491impl LiveProcessReport {
492 pub fn frames(self) -> u32 {
494 self.frames
495 }
496
497 pub fn control_events(self) -> usize {
499 self.control_events
500 }
501
502 pub fn dropped_control_events(self) -> u64 {
504 self.dropped_control_events
505 }
506}