Skip to main content

sim_lib_stream_asio/
bridge.rs

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