Skip to main content

sim_lib_stream_bridge/
model.rs

1use sim_citizen_derive::Citizen;
2use sim_kernel::{Diagnostic, Error, Result, Symbol};
3use sim_lib_midi_core::DEFAULT_US_PER_QUARTER;
4use sim_lib_stream_core::StreamValue;
5
6/// Result of a bridge conversion: the produced stream plus any diagnostics.
7pub struct BridgeOutput {
8    /// Converted stream carrying the resulting MIDI or PCM packets.
9    pub stream: StreamValue,
10    /// Diagnostics emitted while converting (for example, unrepresentable pitches).
11    pub diagnostics: Vec<Diagnostic>,
12}
13
14/// Options controlling MIDI-to-PCM rendering through the sound libraries.
15///
16/// # Examples
17///
18/// ```
19/// use sim_lib_stream_bridge::StreamBridgeRenderOptions;
20///
21/// let opts = StreamBridgeRenderOptions::default();
22/// assert_eq!(opts.channels, 2);
23/// assert_eq!(opts.sample_rate, 48_000);
24/// ```
25#[derive(Clone, Debug, PartialEq, Citizen)]
26#[citizen(symbol = "stream-bridge/RenderOptions", version = 1)]
27pub struct StreamBridgeRenderOptions {
28    /// Output sample rate in hertz.
29    pub sample_rate: u32,
30    /// Number of interleaved output channels.
31    pub channels: u8,
32    /// Number of frames carried by each produced PCM packet.
33    pub chunk_frames: usize,
34}
35
36impl StreamBridgeRenderOptions {
37    /// Builds render options and rejects values the renderer cannot honor.
38    pub fn new(sample_rate: u32, channels: u8, chunk_frames: usize) -> Result<Self> {
39        let options = Self {
40            sample_rate,
41            channels,
42            chunk_frames,
43        };
44        options.validate()?;
45        Ok(options)
46    }
47
48    /// Validates the public option fields before rendering.
49    pub fn validate(&self) -> Result<()> {
50        if self.sample_rate == 0 {
51            return Err(Error::Eval(
52                "stream/bridge render sample_rate must be greater than zero".to_owned(),
53            ));
54        }
55        if !(1..=2).contains(&self.channels) {
56            return Err(Error::Eval(
57                "stream/bridge render channels must be 1 or 2".to_owned(),
58            ));
59        }
60        if self.chunk_frames == 0 {
61            return Err(Error::Eval(
62                "stream/bridge render chunk_frames must be greater than zero".to_owned(),
63            ));
64        }
65        Ok(())
66    }
67}
68
69impl Default for StreamBridgeRenderOptions {
70    fn default() -> Self {
71        Self {
72            sample_rate: 48_000,
73            channels: 2,
74            chunk_frames: 512,
75        }
76    }
77}
78
79/// Options controlling PCM-to-MIDI lifting through the audio-lift libraries.
80#[derive(Clone, Debug, PartialEq, Citizen)]
81#[citizen(symbol = "stream-bridge/LiftMidiOptions", version = 1)]
82pub struct StreamBridgeLiftMidiOptions {
83    /// Input sample rate in hertz used to convert sample offsets to time.
84    pub sample_rate: u32,
85    /// Ticks per quarter note for the lifted MIDI timeline.
86    pub tpq: u16,
87    /// Microseconds per quarter note used to map seconds onto ticks.
88    pub us_per_quarter: u32,
89    /// Minimum confidence a lifted note candidate must reach to be emitted.
90    pub min_confidence: f64,
91    /// Analysis window size, in samples, for the lifter.
92    pub window_size: usize,
93    /// Hop size, in samples, between successive analysis windows.
94    pub hop_size: usize,
95    /// Maximum number of MIDI events packed into a single stream packet.
96    pub max_events_per_packet: usize,
97}
98
99impl StreamBridgeLiftMidiOptions {
100    /// Validates the public option fields before PCM-to-MIDI lifting.
101    pub fn validate(&self) -> Result<()> {
102        if self.sample_rate == 0 {
103            return Err(Error::Eval(
104                "stream/bridge lift-midi sample_rate must be greater than zero".to_owned(),
105            ));
106        }
107        if self.tpq == 0 {
108            return Err(Error::Eval(
109                "stream/bridge lift-midi tpq must be greater than zero".to_owned(),
110            ));
111        }
112        if self.us_per_quarter == 0 {
113            return Err(Error::Eval(
114                "stream/bridge lift-midi us_per_quarter must be greater than zero".to_owned(),
115            ));
116        }
117        if !self.min_confidence.is_finite() || !(0.0..=1.0).contains(&self.min_confidence) {
118            return Err(Error::Eval(
119                "stream/bridge lift-midi min_confidence must be between 0 and 1".to_owned(),
120            ));
121        }
122        if self.window_size == 0 {
123            return Err(Error::Eval(
124                "stream/bridge lift-midi window_size must be greater than zero".to_owned(),
125            ));
126        }
127        if self.hop_size == 0 {
128            return Err(Error::Eval(
129                "stream/bridge lift-midi hop_size must be greater than zero".to_owned(),
130            ));
131        }
132        if self.max_events_per_packet == 0 {
133            return Err(Error::Eval(
134                "stream/bridge lift-midi max_events_per_packet must be greater than zero"
135                    .to_owned(),
136            ));
137        }
138        Ok(())
139    }
140}
141
142impl Default for StreamBridgeLiftMidiOptions {
143    fn default() -> Self {
144        Self {
145            sample_rate: 48_000,
146            tpq: 480,
147            us_per_quarter: DEFAULT_US_PER_QUARTER,
148            min_confidence: 0.75,
149            window_size: 2048,
150            hop_size: 512,
151            max_events_per_packet: 64,
152        }
153    }
154}
155
156/// Returns the `stream/bridge` symbol naming the bridge function export.
157///
158/// # Examples
159///
160/// ```
161/// let symbol = sim_lib_stream_bridge::stream_bridge_symbol();
162/// assert_eq!(&*symbol.name, "bridge");
163/// ```
164pub fn stream_bridge_symbol() -> Symbol {
165    Symbol::qualified("stream", "bridge")
166}
167
168/// Returns the class symbol for [`StreamBridgeRenderOptions`].
169pub fn stream_bridge_render_options_class_symbol() -> Symbol {
170    Symbol::qualified("stream-bridge", "RenderOptions")
171}
172
173/// Returns the class symbol for [`StreamBridgeLiftMidiOptions`].
174pub fn stream_bridge_lift_midi_options_class_symbol() -> Symbol {
175    Symbol::qualified("stream-bridge", "LiftMidiOptions")
176}