1use crate::config::{Action, EncoderAction, Preset};
4
5#[derive(Debug, Clone, PartialEq, Eq)]
7pub struct MidiMessage {
8 pub data: [u8; 3],
9 pub len: usize,
10}
11
12pub fn execute_button_press(preset: &Preset, btn_idx: usize) -> heapless::Vec<MidiMessage, 8> {
14 let mut messages = heapless::Vec::new();
15 let Some(btn) = preset.buttons.get(btn_idx) else {
16 return messages;
17 };
18 for action in &btn.on_press {
19 if let Some(msg) = action_to_midi(action) {
20 messages.push(msg).ok();
21 }
22 }
23 messages
24}
25
26pub fn action_to_midi(action: &Action) -> Option<MidiMessage> {
28 match action {
29 Action::Midi { data, len } => {
30 let len = (*len as usize).min(3);
31 if len == 0 {
32 return None;
33 }
34 Some(MidiMessage { data: *data, len })
35 }
36 _ => None,
37 }
38}
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq)]
42pub enum EncoderDirection {
43 Clockwise,
44 CounterClockwise,
45}
46
47pub fn encoder_cc(
50 preset: &Preset,
51 encoder_idx: usize,
52 direction: EncoderDirection,
53 current_value: &mut u8,
54) -> Option<MidiMessage> {
55 let enc = preset.encoders.get(encoder_idx)?;
56 match &enc.action {
57 EncoderAction::Cc {
58 cc,
59 channel,
60 min,
61 max,
62 } => {
63 let val = match direction {
64 EncoderDirection::Clockwise => (*current_value).saturating_add(1).min(*max),
65 EncoderDirection::CounterClockwise => (*current_value).saturating_sub(1).max(*min),
66 };
67 *current_value = val;
68 Some(MidiMessage {
69 data: [0xB0 | (channel - 1), *cc as u8, val],
70 len: 3,
71 })
72 }
73 EncoderAction::CcRelative {
74 cc,
75 channel,
76 increment,
77 decrement,
78 } => {
79 let val = match direction {
80 EncoderDirection::Clockwise => *increment,
81 EncoderDirection::CounterClockwise => *decrement,
82 };
83 Some(MidiMessage {
84 data: [0xB0 | (channel - 1), *cc, val],
85 len: 3,
86 })
87 }
88 EncoderAction::PresetScroll => None,
89 }
90}
91
92pub fn analog_cc(
94 preset: &Preset,
95 analog_idx: usize,
96 raw: u16,
97 adc_min: u16,
98 adc_max: u16,
99) -> Option<MidiMessage> {
100 let cfg = preset.analog.get(analog_idx)?;
101 let range = cfg.max - cfg.min;
102 let clamped = raw.clamp(adc_min, adc_max);
104 let span = adc_max.saturating_sub(adc_min).max(1) as u32;
105 let value =
106 cfg.min + (((clamped - adc_min) as u32 * range as u32) / span).min(range as u32) as u8;
107 Some(MidiMessage {
108 data: [0xB0 | (cfg.channel - 1), cfg.cc, value],
109 len: 3,
110 })
111}
112
113#[cfg(test)]
114mod tests {
115 use super::*;
116 use crate::config::*;
117 use heapless::Vec;
118
119 fn make_preset(buttons: Vec<ButtonConfig, MAX_BUTTONS>) -> Preset {
120 Preset {
121 name: Label::new(),
122 buttons,
123 encoders: Vec::new(),
124 analog: Vec::new(),
125 defaults: Default::default(),
126 on_enter: heapless::Vec::new(),
127 on_exit: heapless::Vec::new(),
128 triggers: heapless::Vec::new(),
129 }
130 }
131
132 #[test]
133 fn note_on_action() {
134 let msg = action_to_midi(&Action::note_on(60, 1).unwrap()).unwrap();
135 assert_eq!(msg.data, [0x90, 60, 127]);
136 assert_eq!(msg.len, 3);
137 }
138
139 #[test]
140 fn note_on_channel_2() {
141 let msg = action_to_midi(&Action::note_on(64, 2).unwrap()).unwrap();
142 assert_eq!(msg.data, [0x91, 64, 127]);
143 }
144
145 #[test]
146 fn cc_action() {
147 let msg = action_to_midi(&Action::cc(10, 127, 1).unwrap()).unwrap();
148 assert_eq!(msg.data, [0xB0, 10, 127]);
149 assert_eq!(msg.len, 3);
150 }
151
152 #[test]
153 fn program_change_action() {
154 let msg = action_to_midi(&Action::program_change(5, 3).unwrap()).unwrap();
155 assert_eq!(msg.data, [0xC2, 5, 0]);
156 assert_eq!(msg.len, 2);
157 }
158
159 #[test]
160 fn unsupported_action_returns_none() {
161 assert!(action_to_midi(&Action::Delay(100)).is_none());
162 assert!(action_to_midi(&Action::PresetNext).is_none());
163 }
164
165 #[test]
166 fn execute_button_press_multi_action() {
167 let mut buttons: Vec<ButtonConfig, MAX_BUTTONS> = Vec::new();
168 let mut on_press: Vec<Action, MAX_ACTIONS> = Vec::new();
169 on_press.push(Action::program_change(0, 1).unwrap()).ok();
170 on_press.push(Action::cc(69, 127, 1).unwrap()).ok();
171 buttons
172 .push(ButtonConfig {
173 label: Label::new(),
174 color: LedConfig::default(),
175 mode: ButtonMode::default(),
176 on_press,
177 cycle_values: Vec::new(),
178 listen_cc: None,
179 on_release: Vec::new(),
180 on_long_press: Vec::new(),
181 })
182 .ok();
183
184 let preset = make_preset(buttons);
185 let msgs = execute_button_press(&preset, 0);
186 assert_eq!(msgs.len(), 2);
187 assert_eq!(msgs[0].data, [0xC0, 0, 0]);
188 assert_eq!(msgs[1].data, [0xB0, 69, 127]);
189 }
190
191 #[test]
192 fn execute_button_press_invalid_index() {
193 let preset = make_preset(Vec::new());
194 let msgs = execute_button_press(&preset, 5);
195 assert!(msgs.is_empty());
196 }
197
198 fn make_encoder_preset() -> Preset {
199 let mut encoders: Vec<EncoderConfig, MAX_ENCODERS> = Vec::new();
200 encoders
201 .push(EncoderConfig {
202 label: Label::try_from("Vol").unwrap(),
203 action: EncoderAction::Cc {
204 cc: 7,
205 channel: 1,
206 min: 0,
207 max: 127,
208 },
209 })
210 .ok();
211 encoders
212 .push(EncoderConfig {
213 label: Label::try_from("Pan").unwrap(),
214 action: EncoderAction::CcRelative {
215 cc: 16,
216 channel: 2,
217 increment: 65,
218 decrement: 63,
219 },
220 })
221 .ok();
222 Preset {
223 name: Label::new(),
224 buttons: Vec::new(),
225 encoders,
226 analog: Vec::new(),
227 defaults: Default::default(),
228 on_enter: heapless::Vec::new(),
229 on_exit: heapless::Vec::new(),
230 triggers: heapless::Vec::new(),
231 }
232 }
233
234 #[test]
235 fn encoder_cc_clockwise() {
236 let preset = make_encoder_preset();
237 let mut val = 64u8;
238 let msg = encoder_cc(&preset, 0, EncoderDirection::Clockwise, &mut val).unwrap();
239 assert_eq!(msg.data, [0xB0, 7, 65]);
240 assert_eq!(val, 65);
241 }
242
243 #[test]
244 fn encoder_cc_counter_clockwise() {
245 let preset = make_encoder_preset();
246 let mut val = 64u8;
247 let msg = encoder_cc(&preset, 0, EncoderDirection::CounterClockwise, &mut val).unwrap();
248 assert_eq!(msg.data, [0xB0, 7, 63]);
249 assert_eq!(val, 63);
250 }
251
252 #[test]
253 fn encoder_cc_clamps_at_max() {
254 let preset = make_encoder_preset();
255 let mut val = 127u8;
256 let msg = encoder_cc(&preset, 0, EncoderDirection::Clockwise, &mut val).unwrap();
257 assert_eq!(msg.data, [0xB0, 7, 127]);
258 assert_eq!(val, 127);
259 }
260
261 #[test]
262 fn encoder_cc_clamps_at_min() {
263 let preset = make_encoder_preset();
264 let mut val = 0u8;
265 let msg = encoder_cc(&preset, 0, EncoderDirection::CounterClockwise, &mut val).unwrap();
266 assert_eq!(msg.data, [0xB0, 7, 0]);
267 assert_eq!(val, 0);
268 }
269
270 #[test]
271 fn encoder_cc_relative() {
272 let preset = make_encoder_preset();
273 let mut val = 0u8;
274 let msg = encoder_cc(&preset, 1, EncoderDirection::Clockwise, &mut val).unwrap();
275 assert_eq!(msg.data, [0xB1, 16, 65]);
276 let msg = encoder_cc(&preset, 1, EncoderDirection::CounterClockwise, &mut val).unwrap();
277 assert_eq!(msg.data, [0xB1, 16, 63]);
278 }
279
280 #[test]
281 fn encoder_invalid_index_returns_none() {
282 let preset = make_encoder_preset();
283 let mut val = 0u8;
284 assert!(encoder_cc(&preset, 5, EncoderDirection::Clockwise, &mut val).is_none());
285 }
286
287 #[test]
288 fn analog_cc_mid_range() {
289 let mut analog: Vec<AnalogConfig, MAX_ANALOG> = Vec::new();
290 analog
291 .push(AnalogConfig {
292 label: Label::try_from("Exp").unwrap(),
293 cc: 11,
294 channel: 1,
295 min: 0,
296 max: 127,
297 })
298 .ok();
299 let preset = Preset {
300 name: Label::new(),
301 buttons: Vec::new(),
302 encoders: Vec::new(),
303 analog,
304 defaults: Default::default(),
305 on_enter: heapless::Vec::new(),
306 on_exit: heapless::Vec::new(),
307 triggers: heapless::Vec::new(),
308 };
309 let msg = analog_cc(&preset, 0, 2048, 0, 4095).unwrap();
310 assert_eq!(msg.data[0], 0xB0);
311 assert_eq!(msg.data[1], 11);
312 assert!(msg.data[2] >= 63 && msg.data[2] <= 64);
314 }
315
316 #[test]
317 fn analog_cc_with_range() {
318 let mut analog: Vec<AnalogConfig, MAX_ANALOG> = Vec::new();
319 analog
320 .push(AnalogConfig {
321 label: Label::new(),
322 cc: 4,
323 channel: 2,
324 min: 20,
325 max: 100,
326 })
327 .ok();
328 let preset = Preset {
329 name: Label::new(),
330 buttons: Vec::new(),
331 encoders: Vec::new(),
332 analog,
333 defaults: Default::default(),
334 on_enter: heapless::Vec::new(),
335 on_exit: heapless::Vec::new(),
336 triggers: heapless::Vec::new(),
337 };
338 let msg = analog_cc(&preset, 0, 4095, 0, 4095).unwrap();
340 assert_eq!(msg.data, [0xB1, 4, 100]);
341 let msg = analog_cc(&preset, 0, 0, 0, 4095).unwrap();
343 assert_eq!(msg.data, [0xB1, 4, 20]);
344 }
345
346 #[test]
347 fn analog_invalid_index_returns_none() {
348 let preset = Preset {
349 name: Label::new(),
350 buttons: Vec::new(),
351 encoders: Vec::new(),
352 analog: Vec::new(),
353 defaults: Default::default(),
354 on_enter: heapless::Vec::new(),
355 on_exit: heapless::Vec::new(),
356 triggers: heapless::Vec::new(),
357 };
358 assert!(analog_cc(&preset, 0, 2048, 0, 4095).is_none());
359 }
360
361 #[test]
362 fn analog_cc_with_calibration() {
363 let mut analog: Vec<AnalogConfig, MAX_ANALOG> = Vec::new();
364 analog
365 .push(AnalogConfig {
366 label: Label::new(),
367 cc: 7,
368 channel: 1,
369 min: 0,
370 max: 127,
371 })
372 .ok();
373 let preset = Preset {
374 name: Label::new(),
375 buttons: Vec::new(),
376 encoders: Vec::new(),
377 analog,
378 defaults: Default::default(),
379 on_enter: heapless::Vec::new(),
380 on_exit: heapless::Vec::new(),
381 triggers: heapless::Vec::new(),
382 };
383 let msg = analog_cc(&preset, 0, 200, 200, 3700).unwrap();
386 assert_eq!(msg.data[2], 0);
387 let msg = analog_cc(&preset, 0, 3700, 200, 3700).unwrap();
389 assert_eq!(msg.data[2], 127);
390 let msg = analog_cc(&preset, 0, 1950, 200, 3700).unwrap();
392 assert!(msg.data[2] >= 63 && msg.data[2] <= 64);
393 let msg = analog_cc(&preset, 0, 50, 200, 3700).unwrap();
395 assert_eq!(msg.data[2], 0);
396 let msg = analog_cc(&preset, 0, 4000, 200, 3700).unwrap();
398 assert_eq!(msg.data[2], 127);
399 }
400}