use sim_kernel::{Error, Result, Symbol};
use sim_lib_audio_graph_core::{
BlockArena, BlockEvent, EventSink, PrepareConfig, ProcessBlock, Processor, Transport,
};
use sim_lib_stream_core::{
PcmPacket, StreamEnvelope, StreamInspectorSnapshot, StreamInspectorStatus, StreamPacket,
TransportProfile,
};
pub use crate::runner_types::{LiveGraphConfig, LiveProcessReport, LiveSteadyStateSnapshot};
use crate::{
AudioToControlQueue, ControlToAudioQueue, LiveAudioEvent, LiveControlEvent, LiveStreamLane,
runner_types::MAX_LIVE_EVENTS, validate_realtime_local_audio_profile,
};
#[derive(Debug)]
pub struct LiveGraphRunner<P> {
processor: P,
config: LiveGraphConfig,
input_planar: Vec<Vec<f32>>,
output_planar: Vec<Vec<f32>>,
scratch: BlockArena,
event_slots: [BlockEvent<'static>; MAX_LIVE_EVENTS],
control_to_audio: ControlToAudioQueue,
audio_to_control: AudioToControlQueue,
}
impl<P: Processor> LiveGraphRunner<P> {
pub fn new_realtime(
processor: P,
config: LiveGraphConfig,
profile: &TransportProfile,
) -> Result<Self> {
validate_realtime_local_audio_profile(profile)?;
Self::new(processor, config)
}
pub fn new(mut processor: P, config: LiveGraphConfig) -> Result<Self> {
processor.prepare(PrepareConfig::new(
config.spec.sample_rate_hz(),
config.max_block_frames,
checked_channels(config.input_channels, "input")?,
checked_channels(config.spec.channels(), "output")?,
));
let max_frames = config.max_block_frames as usize;
Ok(Self {
processor,
config,
input_planar: vec![vec![0.0; max_frames]; config.input_channels],
output_planar: vec![vec![0.0; max_frames]; config.spec.channels()],
scratch: BlockArena::with_f32_capacity(
max_frames * config.input_channels.max(config.spec.channels()).max(1),
),
event_slots: [empty_event(); MAX_LIVE_EVENTS],
control_to_audio: ControlToAudioQueue::with_capacity(config.control_queue_capacity)?,
audio_to_control: AudioToControlQueue::with_capacity(config.audio_queue_capacity)?,
})
}
pub fn enqueue_control_event(&mut self, event: LiveControlEvent) -> crate::LiveQueuePush {
self.control_to_audio.push(event)
}
pub fn enqueue_midi_short(
&mut self,
offset: u32,
bytes: &[u8],
) -> Result<crate::LiveQueuePush> {
Ok(self.enqueue_control_event(LiveControlEvent::midi_short(offset, bytes)?))
}
pub fn enqueue_param_set(
&mut self,
offset: u32,
param: u32,
value: f64,
) -> Result<crate::LiveQueuePush> {
Ok(self.enqueue_control_event(LiveControlEvent::param_set(offset, param, value)?))
}
pub fn process_interleaved_f32(
&mut self,
input: Option<&[f32]>,
output: &mut [f32],
frames: usize,
transport: Transport,
) -> Result<LiveProcessReport> {
self.validate_block(input, output, frames)?;
let dropped_control_events = self.control_to_audio.take_dropped();
if dropped_control_events > 0 {
self.record_audio_event(LiveAudioEvent::DroppedControlEvents {
count: dropped_control_events,
});
}
let event_count = self.drain_control_events(frames)?;
self.copy_input(input, frames);
self.clear_output(frames);
self.run_processor(frames, event_count, transport)?;
self.copy_output(output, frames);
Ok(LiveProcessReport {
frames: frames as u32,
control_events: event_count,
dropped_control_events,
})
}
pub fn drain_audio_events(&mut self) -> Vec<LiveAudioEvent> {
let mut events = Vec::new();
while let Some(event) = self.audio_to_control.pop() {
events.push(event);
}
let dropped = self.audio_to_control.take_dropped();
if dropped > 0 {
events.push(LiveAudioEvent::DroppedAudioEvents { count: dropped });
}
events
}
pub fn drain_audio_diagnostics(&mut self) -> Vec<sim_lib_stream_core::StreamPacket> {
self.drain_audio_events()
.into_iter()
.map(LiveAudioEvent::to_diagnostic_packet)
.collect()
}
pub fn diagnostic_inspector(&self) -> Result<StreamInspectorSnapshot> {
let metadata = LiveStreamLane::Diagnostic.metadata(self.audio_to_control.capacity())?;
let stats = self.audio_to_control.stats();
Ok(StreamInspectorSnapshot::new(
&metadata,
Symbol::qualified("stream/route", "live-audio-callback"),
TransportProfile::realtime_local_audio().name().clone(),
StreamInspectorStatus::from_stats(&stats, false),
self.audio_to_control.len(),
&stats,
stats.pushed.checked_sub(1),
Vec::new(),
))
}
pub fn steady_state_snapshot(&self) -> LiveSteadyStateSnapshot {
LiveSteadyStateSnapshot {
input_lane_capacity: self.input_planar.iter().map(Vec::capacity).collect(),
output_lane_capacity: self.output_planar.iter().map(Vec::capacity).collect(),
scratch_capacity: self.scratch.f32_capacity(),
control_queue_capacity: self.control_to_audio.allocated_capacity(),
audio_queue_capacity: self.audio_to_control.allocated_capacity(),
}
}
pub fn buffered_preview_chunk(
&self,
output: &[f32],
frames: usize,
sequence: u64,
) -> Result<StreamEnvelope> {
let samples = self.validate_preview_block(output, frames)?;
let packet = StreamPacket::Pcm(PcmPacket::f32(
self.config.spec.channels(),
frames,
output[..samples].to_vec(),
)?);
LiveStreamLane::AudioOutput.lan_buffered_preview_envelope(sequence, Vec::new(), packet)
}
fn validate_block(
&mut self,
input: Option<&[f32]>,
output: &[f32],
frames: usize,
) -> Result<()> {
if frames > self.config.max_block_frames as usize {
self.record_audio_event(LiveAudioEvent::Xrun {
frames: frames as u32,
max_frames: self.config.max_block_frames,
});
return Err(Error::Eval(format!(
"live graph block has {frames} frames, max block is {}",
self.config.max_block_frames
)));
}
let input_samples = frames.saturating_mul(self.config.input_channels);
if let Some(samples) = input
&& samples.len() < input_samples
{
return Err(Error::Eval(format!(
"live graph input has {} samples, expected at least {input_samples}",
samples.len()
)));
}
let output_samples = frames.saturating_mul(self.config.spec.channels());
if output.len() < output_samples {
return Err(Error::Eval(format!(
"live graph output has {} samples, expected at least {output_samples}",
output.len()
)));
}
Ok(())
}
fn validate_preview_block(&self, output: &[f32], frames: usize) -> Result<usize> {
if frames > self.config.max_block_frames as usize {
return Err(Error::Eval(format!(
"live graph preview has {frames} frames, max block is {}",
self.config.max_block_frames
)));
}
let output_samples = frames
.checked_mul(self.config.spec.channels())
.ok_or_else(|| Error::Eval("live graph preview sample count overflowed".to_owned()))?;
if output.len() < output_samples {
return Err(Error::Eval(format!(
"live graph preview has {} samples, expected at least {output_samples}",
output.len()
)));
}
Ok(output_samples)
}
fn drain_control_events(&mut self, frames: usize) -> Result<usize> {
let mut count = 0;
while let Some(event) = self.control_to_audio.pop() {
if event.offset() >= frames as u32 {
return Err(Error::Eval(format!(
"live control event offset {} is outside block frames 0..{frames}",
event.offset()
)));
}
self.event_slots[count] = event.to_block_event();
count += 1;
}
Ok(count)
}
fn copy_input(&mut self, input: Option<&[f32]>, frames: usize) {
for lane in &mut self.input_planar {
lane[..frames].fill(0.0);
}
if let Some(samples) = input {
for frame in 0..frames {
for channel in 0..self.config.input_channels {
self.input_planar[channel][frame] =
samples[frame * self.config.input_channels + channel];
}
}
}
}
fn clear_output(&mut self, frames: usize) {
for lane in &mut self.output_planar {
lane[..frames].fill(0.0);
}
}
fn copy_output(&self, output: &mut [f32], frames: usize) {
let channels = self.config.spec.channels();
for frame in 0..frames {
for channel in 0..channels {
output[frame * channels + channel] = self.output_planar[channel][frame];
}
}
}
fn run_processor(
&mut self,
frames: usize,
event_count: usize,
transport: Transport,
) -> Result<()> {
let in_events = &self.event_slots[..event_count];
let processor = &mut self.processor;
let scratch = &mut self.scratch;
let input_planar = &self.input_planar;
let output_planar = &mut self.output_planar;
let audio_to_control = &mut self.audio_to_control;
macro_rules! run_block {
($in_audio:expr, $out_audio:expr) => {{
let mut event_sink = LiveEventSink {
queue: audio_to_control,
};
scratch.reset();
let mut block = ProcessBlock {
frames: frames as u32,
in_audio: $in_audio,
out_audio: $out_audio,
in_events,
out_events: &mut event_sink,
transport,
scratch,
};
block.validate_audio_lanes()?;
processor.process(&mut block);
block.validate_audio_lanes()
}};
}
match (self.config.input_channels, self.config.spec.channels()) {
(0, 1) => {
let in_audio: [&[f32]; 0] = [];
let mut out_audio = [&mut output_planar[0][..frames]];
run_block!(&in_audio, &mut out_audio)
}
(0, 2) => {
let in_audio: [&[f32]; 0] = [];
let (left, right) = output_planar.split_at_mut(1);
let mut out_audio = [&mut left[0][..frames], &mut right[0][..frames]];
run_block!(&in_audio, &mut out_audio)
}
(1, 1) => {
let in_audio = [&input_planar[0][..frames]];
let mut out_audio = [&mut output_planar[0][..frames]];
run_block!(&in_audio, &mut out_audio)
}
(1, 2) => {
let in_audio = [&input_planar[0][..frames]];
let (left, right) = output_planar.split_at_mut(1);
let mut out_audio = [&mut left[0][..frames], &mut right[0][..frames]];
run_block!(&in_audio, &mut out_audio)
}
(2, 1) => {
let in_audio = [&input_planar[0][..frames], &input_planar[1][..frames]];
let mut out_audio = [&mut output_planar[0][..frames]];
run_block!(&in_audio, &mut out_audio)
}
(2, 2) => {
let in_audio = [&input_planar[0][..frames], &input_planar[1][..frames]];
let (left, right) = output_planar.split_at_mut(1);
let mut out_audio = [&mut left[0][..frames], &mut right[0][..frames]];
run_block!(&in_audio, &mut out_audio)
}
_ => Err(Error::Eval(
"live graph runner supports mono and stereo I/O".to_owned(),
)),
}
}
fn record_audio_event(&mut self, event: LiveAudioEvent) {
let _ = self.audio_to_control.push(event);
}
}
struct LiveEventSink<'a> {
queue: &'a mut AudioToControlQueue,
}
impl EventSink for LiveEventSink<'_> {
fn push(&mut self, event: BlockEvent<'_>) -> Result<()> {
if let Some(event) = LiveAudioEvent::from_processor_event(event) {
let _ = self.queue.push(event);
}
Ok(())
}
}
fn checked_channels(channels: usize, role: &str) -> Result<u16> {
u16::try_from(channels)
.map_err(|_| Error::Eval(format!("live graph {role} channel count exceeds u16")))
}
const fn empty_event() -> BlockEvent<'static> {
BlockEvent::ParamSet {
offset: 0,
param: 0,
value: 0.0,
}
}