Skip to main content

sim_lib_stream_coreaudio/
bridge.rs

1use sim_kernel::{Error, Result};
2use sim_lib_audio_graph_core::{
3    BlockArena, NullEventSink, PrepareConfig, ProcessBlock, Processor, Transport,
4};
5
6use crate::CoreAudioTiming;
7
8/// Drives a processor from a CoreAudio render callback.
9#[derive(Debug)]
10pub struct CoreAudioRenderBridge<P> {
11    processor: P,
12    timing: CoreAudioTiming,
13    input_channels: usize,
14    output_channels: usize,
15    max_block_frames: u32,
16    scratch: BlockArena,
17    sample_pos: u64,
18}
19
20impl<P: Processor> CoreAudioRenderBridge<P> {
21    /// Builds a render bridge wrapping `processor` for the given timing and
22    /// channel counts.
23    ///
24    /// The processor is prepared immediately with a [`PrepareConfig`] derived
25    /// from `timing`, and a scratch [`BlockArena`] is sized for the largest
26    /// lane count. Returns an error when both channel counts are zero or when
27    /// the buffer/channel sizes exceed the kernel's frame and channel widths.
28    pub fn new(
29        mut processor: P,
30        timing: CoreAudioTiming,
31        input_channels: usize,
32        output_channels: usize,
33    ) -> Result<Self> {
34        if input_channels == 0 && output_channels == 0 {
35            return Err(Error::Eval(
36                "CoreAudio bridge needs at least one audio lane".to_owned(),
37            ));
38        }
39        let max_block_frames = checked_frames(timing.buffer_frames())?;
40        processor.prepare(PrepareConfig::new(
41            timing.sample_rate_hz(),
42            max_block_frames,
43            checked_channels(input_channels, "input")?,
44            checked_channels(output_channels, "output")?,
45        ));
46        Ok(Self {
47            processor,
48            timing,
49            input_channels,
50            output_channels,
51            max_block_frames,
52            scratch: BlockArena::with_f32_capacity(
53                timing.buffer_frames() * input_channels.max(output_channels).max(1),
54            ),
55            sample_pos: 0,
56        })
57    }
58
59    /// Runs one processing block of `frames` planar frames and returns the
60    /// rendered output lanes.
61    ///
62    /// `input` supplies one slice per input channel; `None` substitutes silence.
63    /// The wrapped processor's audio lanes are validated before and after the
64    /// call, and the bridge sample position advances by `frames`. Returns an
65    /// error when `frames` exceeds the prepared maximum block size or the input
66    /// lane shape does not match the configured channel count.
67    pub fn render_planar_f32(
68        &mut self,
69        input: Option<&[&[f32]]>,
70        frames: usize,
71    ) -> Result<Vec<Vec<f32>>> {
72        if frames > self.max_block_frames as usize {
73            return Err(Error::Eval(format!(
74                "CoreAudio callback received {frames} frames, max block size is {}",
75                self.max_block_frames
76            )));
77        }
78        let input_planar = input_planar(input, self.input_channels, frames)?;
79        let mut output_planar = vec![vec![0.0; frames]; self.output_channels];
80        {
81            let input_refs = input_planar.iter().map(Vec::as_slice).collect::<Vec<_>>();
82            let mut output_refs = output_planar
83                .iter_mut()
84                .map(Vec::as_mut_slice)
85                .collect::<Vec<_>>();
86            self.scratch.reset();
87            let mut event_sink = NullEventSink;
88            let mut block = ProcessBlock {
89                frames: frames as u32,
90                in_audio: &input_refs,
91                out_audio: &mut output_refs,
92                in_events: &[],
93                out_events: &mut event_sink,
94                transport: Transport {
95                    sample_pos: self.sample_pos,
96                    ..Transport::default()
97                },
98                scratch: &mut self.scratch,
99            };
100            block.validate_audio_lanes()?;
101            self.processor.process(&mut block);
102            block.validate_audio_lanes()?;
103        }
104        self.sample_pos += frames as u64;
105        Ok(output_planar)
106    }
107
108    /// Returns the running sample position, in frames rendered so far.
109    pub fn sample_pos(&self) -> u64 {
110        self.sample_pos
111    }
112
113    /// Returns the timing configuration this bridge was built with.
114    pub fn timing(&self) -> CoreAudioTiming {
115        self.timing
116    }
117
118    /// Resets the wrapped processor, the sample position, and the scratch arena.
119    pub fn reset(&mut self) {
120        self.processor.reset();
121        self.sample_pos = 0;
122        self.scratch.reset();
123    }
124}
125
126fn input_planar(input: Option<&[&[f32]]>, channels: usize, frames: usize) -> Result<Vec<Vec<f32>>> {
127    let Some(input) = input else {
128        return Ok(vec![vec![0.0; frames]; channels]);
129    };
130    if input.len() != channels {
131        return Err(Error::Eval(format!(
132            "CoreAudio input has {} channels, expected {channels}",
133            input.len()
134        )));
135    }
136    input
137        .iter()
138        .map(|lane| {
139            if lane.len() < frames {
140                return Err(Error::Eval(format!(
141                    "CoreAudio input lane has {} frames, expected {frames}",
142                    lane.len()
143                )));
144            }
145            Ok(lane[..frames].to_vec())
146        })
147        .collect()
148}
149
150fn checked_frames(frames: usize) -> Result<u32> {
151    u32::try_from(frames).map_err(|_| Error::Eval("CoreAudio buffer size exceeds u32".to_owned()))
152}
153
154fn checked_channels(channels: usize, role: &str) -> Result<u16> {
155    u16::try_from(channels)
156        .map_err(|_| Error::Eval(format!("CoreAudio {role} channel count exceeds u16")))
157}