Skip to main content

sim_lib_audio_graph_live/
event.rs

1use sim_kernel::{Error, Result, Symbol};
2use sim_lib_audio_graph_core::BlockEvent;
3use sim_lib_stream_core::{BackpressureOutcome, StreamDiagnostic, StreamPacket};
4
5/// Result of pushing into a bounded live queue.
6pub type LiveQueuePush = BackpressureOutcome;
7
8/// Owned control event moved from the control thread to the audio callback.
9#[derive(Clone, Copy, Debug, PartialEq)]
10pub enum LiveControlEvent {
11    /// A short MIDI message of up to three bytes.
12    Midi {
13        /// Sample offset into the block.
14        offset: u32,
15        /// Message bytes (only the first `len` are valid).
16        bytes: [u8; 3],
17        /// Number of valid bytes in `bytes`.
18        len: u8,
19    },
20    /// A parameter-set event.
21    ParamSet {
22        /// Sample offset into the block.
23        offset: u32,
24        /// Parameter index.
25        param: u32,
26        /// New parameter value.
27        value: f64,
28    },
29}
30
31/// Owned audio event moved from the audio callback to the control thread.
32#[derive(Clone, Copy, Debug, PartialEq)]
33pub enum LiveAudioEvent {
34    /// An audio callback received more frames than the prepared maximum.
35    Xrun {
36        /// Frames delivered to the callback.
37        frames: u32,
38        /// Maximum prepared block size.
39        max_frames: u32,
40    },
41    /// Control-to-audio queue dropped events under backpressure.
42    DroppedControlEvents {
43        /// Number of dropped events.
44        count: u64,
45    },
46    /// Audio-to-control queue dropped events under backpressure.
47    DroppedAudioEvents {
48        /// Number of dropped events.
49        count: u64,
50    },
51    /// A processor emitted a parameter-set event.
52    ProcessorParamSet {
53        /// Sample offset into the block.
54        offset: u32,
55        /// Parameter index.
56        param: u32,
57        /// New parameter value.
58        value: f64,
59    },
60    /// A processor emitted a short MIDI message.
61    ProcessorMidi {
62        /// Sample offset into the block.
63        offset: u32,
64        /// Message bytes (only the first `len` are valid).
65        bytes: [u8; 3],
66        /// Number of valid bytes in `bytes`.
67        len: u8,
68    },
69}
70
71impl LiveControlEvent {
72    /// Builds a [`LiveControlEvent::Midi`] from one to three MIDI bytes.
73    pub fn midi_short(offset: u32, bytes: &[u8]) -> Result<Self> {
74        if bytes.is_empty() || bytes.len() > 3 {
75            return Err(Error::Eval(
76                "live MIDI event must contain one to three bytes".to_owned(),
77            ));
78        }
79        let mut padded = [0; 3];
80        padded[..bytes.len()].copy_from_slice(bytes);
81        Ok(Self::Midi {
82            offset,
83            bytes: padded,
84            len: bytes.len() as u8,
85        })
86    }
87
88    /// Builds a [`LiveControlEvent::ParamSet`], requiring a finite value.
89    pub fn param_set(offset: u32, param: u32, value: f64) -> Result<Self> {
90        if !value.is_finite() {
91            return Err(Error::Eval(
92                "live parameter value must be finite".to_owned(),
93            ));
94        }
95        Ok(Self::ParamSet {
96            offset,
97            param,
98            value,
99        })
100    }
101
102    /// Returns the event's sample offset within its block.
103    pub fn offset(self) -> u32 {
104        match self {
105            Self::Midi { offset, .. } | Self::ParamSet { offset, .. } => offset,
106        }
107    }
108
109    /// Converts the event into a graph-core [`BlockEvent`].
110    pub fn to_block_event(self) -> BlockEvent<'static> {
111        match self {
112            Self::Midi { offset, bytes, len } => BlockEvent::Midi { offset, bytes, len },
113            Self::ParamSet {
114                offset,
115                param,
116                value,
117            } => BlockEvent::ParamSet {
118                offset,
119                param,
120                value,
121            },
122        }
123    }
124}
125
126impl LiveAudioEvent {
127    /// Captures a processor-emitted [`BlockEvent`] as a live audio event.
128    ///
129    /// Returns `None` for events that are not carried back to the control
130    /// thread (long MIDI, note-on, and note-off).
131    pub fn from_processor_event(event: BlockEvent<'_>) -> Option<Self> {
132        match event {
133            BlockEvent::Midi { offset, bytes, len } => {
134                Some(Self::ProcessorMidi { offset, bytes, len })
135            }
136            BlockEvent::ParamSet {
137                offset,
138                param,
139                value,
140            } => Some(Self::ProcessorParamSet {
141                offset,
142                param,
143                value,
144            }),
145            BlockEvent::MidiLong { .. }
146            | BlockEvent::NoteOn { .. }
147            | BlockEvent::NoteOff { .. } => None,
148        }
149    }
150
151    /// Renders the event as a stream diagnostic packet for the control thread.
152    pub fn to_diagnostic_packet(self) -> StreamPacket {
153        let (kind, message) = match self {
154            Self::Xrun { frames, max_frames } => (
155                Symbol::qualified("stream/diagnostic", "xrun"),
156                format!("live callback received {frames} frames, max block is {max_frames}"),
157            ),
158            Self::DroppedControlEvents { count } => (
159                Symbol::qualified("stream/diagnostic", "control-drop"),
160                format!("live control-to-audio queue dropped {count} events"),
161            ),
162            Self::DroppedAudioEvents { count } => (
163                Symbol::qualified("stream/diagnostic", "audio-drop"),
164                format!("live audio-to-control queue dropped {count} events"),
165            ),
166            Self::ProcessorParamSet {
167                offset,
168                param,
169                value,
170            } => (
171                Symbol::qualified("stream/diagnostic", "processor-param"),
172                format!("processor emitted param {param}={value} at frame {offset}"),
173            ),
174            Self::ProcessorMidi { offset, len, .. } => (
175                Symbol::qualified("stream/diagnostic", "processor-midi"),
176                format!("processor emitted {len}-byte MIDI event at frame {offset}"),
177            ),
178        };
179        StreamPacket::Diagnostic(StreamDiagnostic::new(kind, message))
180    }
181}