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