Skip to main content

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}