sim_lib_plugin_clap/event.rs
1use std::collections::BTreeMap;
2
3use sim_kernel::{Error, Result};
4use sim_lib_audio_graph_core::BlockEvent;
5
6/// A CLAP input event, mirroring the CLAP event union SIM accepts from a host.
7///
8/// Each variant carries a sample-relative `time` offset and maps to a matching
9/// `BlockEvent` via [`ClapEvent::to_block_event`].
10#[derive(Clone, Debug, PartialEq)]
11pub enum ClapEvent {
12 /// A short (up to 3-byte) raw MIDI message.
13 MidiShort {
14 /// Sample offset of the event within the block.
15 time: u32,
16 /// Raw MIDI bytes; only the first `len` are significant.
17 bytes: [u8; 3],
18 /// Number of valid bytes in `bytes`.
19 len: u8,
20 },
21 /// A note-on event.
22 NoteOn {
23 /// Sample offset of the event within the block.
24 time: u32,
25 /// MIDI channel (0-based).
26 channel: u8,
27 /// Note key number.
28 key: u8,
29 /// Note velocity, normalized to 0.0 to 1.0.
30 velocity: f32,
31 },
32 /// A note-off event.
33 NoteOff {
34 /// Sample offset of the event within the block.
35 time: u32,
36 /// MIDI channel (0-based).
37 channel: u8,
38 /// Note key number.
39 key: u8,
40 /// Release velocity, normalized to 0.0 to 1.0.
41 velocity: f32,
42 },
43 /// A parameter value change addressed by CLAP parameter id.
44 ParamValue {
45 /// Sample offset of the event within the block.
46 time: u32,
47 /// CLAP parameter id, translated through a [`ClapParamMap`].
48 clap_param_id: u32,
49 /// New parameter value.
50 value: f64,
51 },
52}
53
54impl ClapEvent {
55 /// Translates this CLAP event into the audio-graph `BlockEvent`.
56 ///
57 /// Note and MIDI events pass through unchanged; a [`ClapEvent::ParamValue`]
58 /// has its CLAP parameter id resolved to a SIM parameter id through
59 /// `params`.
60 pub fn to_block_event(&self, params: &ClapParamMap) -> BlockEvent<'_> {
61 match self {
62 Self::MidiShort { time, bytes, len } => BlockEvent::Midi {
63 offset: *time,
64 bytes: *bytes,
65 len: *len,
66 },
67 Self::NoteOn {
68 time,
69 channel,
70 key,
71 velocity,
72 } => BlockEvent::NoteOn {
73 offset: *time,
74 channel: *channel,
75 key: *key,
76 velocity: *velocity,
77 },
78 Self::NoteOff {
79 time,
80 channel,
81 key,
82 velocity,
83 } => BlockEvent::NoteOff {
84 offset: *time,
85 channel: *channel,
86 key: *key,
87 velocity: *velocity,
88 },
89 Self::ParamValue {
90 time,
91 clap_param_id,
92 value,
93 } => BlockEvent::ParamSet {
94 offset: *time,
95 param: params.sim_param_for(*clap_param_id),
96 value: *value,
97 },
98 }
99 }
100
101 /// Translates this CLAP event after checking that its offset is inside the
102 /// current processing block.
103 pub fn try_to_block_event(&self, params: &ClapParamMap, frames: u32) -> Result<BlockEvent<'_>> {
104 let event = self.to_block_event(params);
105 let offset = block_event_offset(&event);
106 if offset >= frames {
107 return Err(Error::Eval(format!(
108 "CLAP event offset {offset} is outside block frames 0..{frames}"
109 )));
110 }
111 Ok(event)
112 }
113}
114
115fn block_event_offset(event: &BlockEvent<'_>) -> u32 {
116 match *event {
117 BlockEvent::Midi { offset, .. }
118 | BlockEvent::MidiLong { offset, .. }
119 | BlockEvent::ParamSet { offset, .. }
120 | BlockEvent::NoteOn { offset, .. }
121 | BlockEvent::NoteOff { offset, .. } => offset,
122 }
123}
124
125/// A translation table from CLAP parameter ids to SIM parameter ids.
126///
127/// Unmapped CLAP ids resolve to themselves, so an empty map behaves as the
128/// identity mapping.
129#[derive(Clone, Debug, Default, PartialEq, Eq)]
130pub struct ClapParamMap {
131 clap_to_sim: BTreeMap<u32, u32>,
132}
133
134impl ClapParamMap {
135 /// Creates an empty map (identity translation for every id).
136 pub fn new() -> Self {
137 Self::default()
138 }
139
140 /// Builds a map sending each id in `ids` to itself.
141 pub fn identity(ids: impl IntoIterator<Item = u32>) -> Self {
142 let mut map = Self::new();
143 for id in ids {
144 map.insert(id, id);
145 }
146 map
147 }
148
149 /// Records that `clap_param_id` translates to `sim_param_id`.
150 pub fn insert(&mut self, clap_param_id: u32, sim_param_id: u32) {
151 self.clap_to_sim.insert(clap_param_id, sim_param_id);
152 }
153
154 /// Resolves a CLAP parameter id to its SIM parameter id.
155 ///
156 /// Returns `clap_param_id` unchanged when no mapping is recorded.
157 pub fn sim_param_for(&self, clap_param_id: u32) -> u32 {
158 self.clap_to_sim
159 .get(&clap_param_id)
160 .copied()
161 .unwrap_or(clap_param_id)
162 }
163}
164
165/// An ordered buffer of [`ClapEvent`]s for a single processing block.
166#[derive(Clone, Debug, Default, PartialEq)]
167pub struct ClapEventBuffer {
168 events: Vec<ClapEvent>,
169}
170
171impl ClapEventBuffer {
172 /// Builds a buffer from a pre-collected list of events.
173 pub fn new(events: Vec<ClapEvent>) -> Self {
174 Self { events }
175 }
176
177 /// Returns the buffered events in order.
178 pub fn events(&self) -> &[ClapEvent] {
179 &self.events
180 }
181
182 /// Appends one event to the end of the buffer.
183 pub fn push(&mut self, event: ClapEvent) {
184 self.events.push(event);
185 }
186
187 /// Translates every buffered event into an audio-graph `BlockEvent`,
188 /// resolving parameter ids through `params`.
189 pub fn to_block_events(&self, params: &ClapParamMap) -> Vec<BlockEvent<'_>> {
190 self.events
191 .iter()
192 .map(|event| event.to_block_event(params))
193 .collect()
194 }
195
196 /// Translates buffered events after checking every event offset against the
197 /// current processing block.
198 pub fn try_to_block_events(
199 &self,
200 params: &ClapParamMap,
201 frames: u32,
202 ) -> Result<Vec<BlockEvent<'_>>> {
203 self.events
204 .iter()
205 .map(|event| event.try_to_block_event(params, frames))
206 .collect()
207 }
208}