1use crate::action::{action_to_midi, EncoderDirection};
14use crate::config::{Action, ButtonMode, Config, Preset, MAX_BUTTONS, MAX_ENCODERS};
15use crate::encoder_accel::EncoderAccel;
16use crate::engine::{
17 self, process_triggers, ActionStep, ButtonEvent, DisplayEvent, EngineResult, SystemAction,
18};
19use crate::long_press::{Edge, Gesture, LongPressDetector};
20use crate::state::{PresetState, PresetStateStore};
21use crate::tap_tempo::TapTempo;
22
23#[derive(Debug, Clone, PartialEq, Eq)]
26pub enum Event {
27 ButtonEdge { index: u8, edge: Edge },
29 EncoderTurn { index: u8, clockwise: bool },
31 Analog { index: u8, raw: u16 },
34 IncomingMidi { data: [u8; 3], len: u8 },
36 Tick,
39}
40
41pub struct Output {
44 pub midi: heapless::Vec<ActionStep, 32>,
46 pub display: heapless::Vec<DisplayEvent, 2>,
48 pub leds_changed: bool,
50 pub preset_changed: bool,
52 pub bpm: Option<u16>,
54 pending_system: heapless::Vec<SystemAction, 2>,
56}
57
58impl Output {
59 fn new() -> Self {
60 Self {
61 midi: heapless::Vec::new(),
62 display: heapless::Vec::new(),
63 leds_changed: false,
64 preset_changed: false,
65 bpm: None,
66 pending_system: heapless::Vec::new(),
67 }
68 }
69}
70
71pub struct Controller<const B: usize = MAX_BUTTONS, const E: usize = MAX_ENCODERS> {
84 state_store: PresetStateStore<B, E>,
85 long_press: [LongPressDetector; B],
86 encoder_accel: [EncoderAccel; E],
87 tap_tempo: TapTempo,
88 button_active: [bool; B],
89 active_preset: u8,
90}
91
92pub type DefaultController = Controller<MAX_BUTTONS, MAX_ENCODERS>;
94
95impl<const B: usize, const E: usize> Default for Controller<B, E> {
96 fn default() -> Self {
97 Self::new()
98 }
99}
100
101impl<const B: usize, const E: usize> Controller<B, E> {
102 pub fn new() -> Self {
103 Self {
104 state_store: PresetStateStore::new(),
105 long_press: core::array::from_fn(|_| LongPressDetector::new_fired()),
106 encoder_accel: core::array::from_fn(|_| EncoderAccel::new()),
107 tap_tempo: TapTempo::new(),
108 button_active: [false; B],
109 active_preset: 0,
110 }
111 }
112
113 pub fn with_state(store: PresetStateStore<B, E>) -> Self {
115 let state = store.current().clone();
116 let active = store.active_index();
117 Self {
118 state_store: store,
119 long_press: core::array::from_fn(|_| LongPressDetector::new_fired()),
120 encoder_accel: core::array::from_fn(|_| EncoderAccel::new()),
121 tap_tempo: TapTempo::new(),
122 button_active: state.button_active,
123 active_preset: active,
124 }
125 }
126
127 pub fn process<const A: usize>(
130 &mut self,
131 event: Event,
132 now_ms: u32,
133 config: &Config<B, E, A>,
134 ) -> Output {
135 let mut result = match event {
136 Event::ButtonEdge { index, edge } => {
137 self.process_button(index as usize, edge, now_ms, config)
138 }
139 Event::EncoderTurn { index, clockwise } => {
140 self.process_encoder(index as usize, clockwise, now_ms, config)
141 }
142 Event::Analog { index, raw } => self.process_analog(index as usize, raw, config),
143 Event::IncomingMidi { data, len } => {
144 self.do_process_incoming_midi(&data[..len as usize], config)
145 }
146 Event::Tick => self.do_tick(now_ms, config),
147 };
148
149 self.handle_system_actions(&mut result, now_ms, config);
151
152 result
153 }
154
155 fn do_tick<const A: usize>(&mut self, now_ms: u32, config: &Config<B, E, A>) -> Output {
156 let mut result = Output::new();
157
158 if !self.button_held() {
159 return result;
160 }
161
162 let preset = match config.presets.get(self.active_preset as usize) {
163 Some(p) => p,
164 None => return result,
165 };
166
167 for i in 0..B {
168 if self.long_press[i].is_active() && !self.long_press[i].has_fired() {
169 let has_long_press = preset
170 .buttons
171 .get(i)
172 .map(|b| !b.on_long_press.is_empty())
173 .unwrap_or(false);
174 if has_long_press {
175 if let Some(gesture) = self.long_press[i].update(None, now_ms) {
176 self.handle_gesture(i, gesture, preset, &mut result);
177 }
178 }
179 }
180 }
181
182 result
183 }
184
185 fn do_process_incoming_midi<const A: usize>(
186 &mut self,
187 raw: &[u8],
188 config: &Config<B, E, A>,
189 ) -> Output {
190 let mut result = Output::new();
191
192 let preset = match config.presets.get(self.active_preset as usize) {
193 Some(p) => p,
194 None => return result,
195 };
196
197 if raw.len() >= 2 && !preset.triggers.is_empty() {
198 let mut state = self.working_state();
199 let data2 = if raw.len() >= 3 { raw[2] } else { 0 };
200 let trigger_result = process_triggers(&mut state, preset, raw[0], raw[1], data2);
201 self.apply_state(&state);
202
203 for step in &trigger_result.midi {
204 result.midi.push(step.clone()).ok();
205 }
206 if trigger_result.led_dirty {
207 result.leds_changed = true;
208 }
209
210 for s in &trigger_result.system {
212 self.execute_system_action(*s, &mut result, 0, config);
213 }
214 }
215
216 result
217 }
218
219 pub fn button_held(&self) -> bool {
221 self.long_press.iter().any(|lp| lp.is_active())
222 }
223
224 pub fn button_states(&self) -> &[bool; B] {
226 &self.button_active
227 }
228
229 pub fn active_preset(&self) -> u8 {
231 self.active_preset
232 }
233
234 pub fn encoder_values(&self) -> [u8; E] {
236 self.state_store.current().encoder_values
237 }
238
239 pub fn snapshot_store(&self) -> PresetStateStore<B, E> {
242 let mut store = self.state_store.clone();
243 let working = self.working_state();
244 store.save_working(&working);
245 store
246 }
247
248 pub fn select_preset<const A: usize>(
251 &mut self,
252 preset_idx: u8,
253 config: &Config<B, E, A>,
254 ) -> Output {
255 let mut result = Output::new();
256 self.do_switch_preset(preset_idx, &mut result, config);
257 result
258 }
259
260 pub fn set_encoder_value(&mut self, index: usize, value: u8) {
262 let mut state = self.state_store.current().clone();
263 if index < E {
264 state.encoder_values[index] = value;
265 }
266 self.state_store.save_working(&state);
267 }
268
269 fn current_preset<'a, const A: usize>(
272 &self,
273 config: &'a Config<B, E, A>,
274 ) -> Option<&'a Preset<B, E, A>> {
275 config.presets.get(self.active_preset as usize)
276 }
277
278 fn handle_system_actions<const A: usize>(
279 &mut self,
280 result: &mut Output,
281 now_ms: u32,
282 config: &Config<B, E, A>,
283 ) {
284 let actions: heapless::Vec<SystemAction, 2> = result.pending_system.clone();
285 result.pending_system.clear();
286 for action in &actions {
287 self.execute_system_action(*action, result, now_ms, config);
288 }
289 }
290
291 fn process_button<const A: usize>(
292 &mut self,
293 index: usize,
294 edge: Edge,
295 now_ms: u32,
296 config: &Config<B, E, A>,
297 ) -> Output {
298 let mut result = Output::new();
299
300 let preset = match self.current_preset(config) {
301 Some(p) => p,
302 None => return result,
303 };
304
305 if index >= B {
306 return result;
307 }
308
309 let has_long_press = preset
310 .buttons
311 .get(index)
312 .map(|b| !b.on_long_press.is_empty())
313 .unwrap_or(false);
314
315 let mode = preset
316 .buttons
317 .get(index)
318 .map(|b| &b.mode)
319 .unwrap_or(&ButtonMode::Momentary);
320
321 if has_long_press {
322 if matches!(mode, ButtonMode::Momentary) {
323 match edge {
324 Edge::Activate => {
325 self.button_active[index] = true;
326 result.leds_changed = true;
327 }
328 Edge::Deactivate => {
329 self.button_active[index] = false;
330 result.leds_changed = true;
331 }
332 }
333 }
334
335 if edge == Edge::Activate {
336 if let Some(btn) = preset.buttons.get(index) {
337 let hint_action = btn.on_long_press.iter().find_map(|a| match a {
338 Action::PresetNext => Some(SystemAction::PresetNext),
339 Action::PresetPrev => Some(SystemAction::PresetPrev),
340 _ => None,
341 });
342 if let Some(action) = hint_action {
343 result
344 .display
345 .push(DisplayEvent::LongPressHint { action })
346 .ok();
347 }
348 }
349 }
350
351 if let Some(gesture) = self.long_press[index].update(Some(edge), now_ms) {
352 self.handle_gesture(index, gesture, preset, &mut result);
353 }
354 } else {
355 match edge {
356 Edge::Activate => {
357 let mut state = self.working_state();
358 let r = engine::process_button(&mut state, preset, index, ButtonEvent::Press);
359 self.apply_state(&state);
360 self.merge_engine_result(&r, &mut result);
361 }
362 Edge::Deactivate => {
363 let mut state = self.working_state();
364 let r = engine::process_button(&mut state, preset, index, ButtonEvent::Release);
365 self.apply_state(&state);
366 self.merge_engine_result(&r, &mut result);
367 }
368 }
369 }
370
371 result
372 }
373
374 fn handle_gesture<const A: usize>(
375 &mut self,
376 index: usize,
377 gesture: Gesture,
378 preset: &Preset<B, E, A>,
379 result: &mut Output,
380 ) {
381 let mode = preset
382 .buttons
383 .get(index)
384 .map(|b| &b.mode)
385 .unwrap_or(&ButtonMode::Momentary);
386
387 match gesture {
388 Gesture::ShortPress => {
389 result.display.push(DisplayEvent::LongPressCancel).ok();
390 let mut state = self.working_state();
391 let r = engine::process_button(&mut state, preset, index, ButtonEvent::Press);
392 self.apply_state(&state);
393 self.merge_engine_result(&r, result);
394
395 if let Some(btn) = preset.buttons.get(index) {
396 if mode == &ButtonMode::Momentary || !btn.on_release.is_empty() {
397 let mut state2 = self.working_state();
398 let r2 = engine::process_button(
399 &mut state2,
400 preset,
401 index,
402 ButtonEvent::Release,
403 );
404 self.apply_state(&state2);
405 self.merge_engine_result(&r2, result);
406 }
407 }
408 }
409 Gesture::LongPress => {
410 let mut state = self.working_state();
411 let r = engine::process_button(&mut state, preset, index, ButtonEvent::LongPress);
412 self.apply_state(&state);
413 self.merge_engine_result(&r, result);
414 }
415 }
416 }
417
418 fn process_encoder<const A: usize>(
419 &mut self,
420 index: usize,
421 clockwise: bool,
422 now_ms: u32,
423 config: &Config<B, E, A>,
424 ) -> Output {
425 let mut result = Output::new();
426 let preset = match self.current_preset(config) {
427 Some(p) => p,
428 None => return result,
429 };
430
431 if index >= E {
432 return result;
433 }
434
435 let steps = self.encoder_accel[index].steps(now_ms);
436 let direction = if clockwise {
437 EncoderDirection::Clockwise
438 } else {
439 EncoderDirection::CounterClockwise
440 };
441
442 let mut state = self.working_state();
443 let r = engine::process_encoder(&mut state, preset, index, direction, steps);
444 self.apply_state(&state);
445 self.merge_engine_result(&r, &mut result);
446 result
447 }
448
449 fn process_analog<const A: usize>(
450 &mut self,
451 index: usize,
452 raw: u16,
453 config: &Config<B, E, A>,
454 ) -> Output {
455 let mut result = Output::new();
456 let preset = match self.current_preset(config) {
457 Some(p) => p,
458 None => return result,
459 };
460 let (min, max) = match index {
461 0 => (config.global.exp1_min, config.global.exp1_max),
462 1 => (config.global.exp2_min, config.global.exp2_max),
463 _ => return result,
464 };
465 let r = engine::process_analog(preset, index, raw, min, max);
466 self.merge_engine_result(&r, &mut result);
467 result
468 }
469
470 fn merge_engine_result(&mut self, r: &EngineResult, result: &mut Output) {
472 for step in &r.midi {
473 result.midi.push(step.clone()).ok();
474 }
475 for d in &r.display {
476 result.display.push(d.clone()).ok();
477 }
478 if r.led_dirty {
479 result.leds_changed = true;
480 }
481 for s in &r.system {
482 result.pending_system.push(*s).ok();
483 }
484 }
485
486 fn execute_system_action<const A: usize>(
487 &mut self,
488 action: SystemAction,
489 result: &mut Output,
490 now_ms: u32,
491 config: &Config<B, E, A>,
492 ) {
493 let num_presets = config.presets.iter().filter(|p| !p.name.is_empty()).count() as u8;
494
495 match action {
496 SystemAction::PresetNext => {
497 if num_presets > 0 {
498 let next = (self.active_preset + 1) % num_presets;
499 self.do_switch_preset(next, result, config);
500 }
501 }
502 SystemAction::PresetPrev => {
503 if num_presets > 0 {
504 let prev = if self.active_preset == 0 {
505 num_presets - 1
506 } else {
507 self.active_preset - 1
508 };
509 self.do_switch_preset(prev, result, config);
510 }
511 }
512 SystemAction::PresetSelect(idx) => {
513 if (idx as usize) < config.presets.len() {
514 self.do_switch_preset(idx, result, config);
515 }
516 }
517 SystemAction::SetBpm(bpm) => {
518 result.bpm = Some(bpm);
519 result.display.push(DisplayEvent::BpmOverlay { bpm }).ok();
520 }
521 SystemAction::TapTempo => {
522 result.bpm = self.tap_tempo.tap(now_ms);
523 if let Some(bpm) = result.bpm {
524 result.display.push(DisplayEvent::BpmOverlay { bpm }).ok();
525 }
526 }
527 }
528 }
529
530 fn do_switch_preset<const A: usize>(
531 &mut self,
532 new_idx: u8,
533 result: &mut Output,
534 config: &Config<B, E, A>,
535 ) {
536 let old_preset = config.presets.get(self.active_preset as usize);
537 let new_preset = config.presets.get(new_idx as usize);
538
539 if let Some(old) = old_preset {
541 for action in &old.on_exit {
542 match action {
543 Action::Delay(ms) => {
544 result.midi.push(ActionStep::Delay(*ms)).ok();
545 }
546 _ => {
547 if let Some(msg) = action_to_midi(action) {
548 result.midi.push(ActionStep::Send(msg)).ok();
549 }
550 }
551 }
552 }
553 }
554
555 if let Some(new_p) = new_preset {
557 let mut working = self.working_state();
558 let recall = self.state_store.switch(new_idx, &mut working, new_p);
559 self.apply_state(&working);
560 self.long_press = core::array::from_fn(|_| LongPressDetector::new_fired());
561 self.encoder_accel = core::array::from_fn(|_| EncoderAccel::new());
562 self.active_preset = new_idx;
563
564 for action in &new_p.on_enter {
566 match action {
567 Action::Delay(ms) => {
568 result.midi.push(ActionStep::Delay(*ms)).ok();
569 }
570 _ => {
571 if let Some(msg) = action_to_midi(action) {
572 result.midi.push(ActionStep::Send(msg)).ok();
573 }
574 }
575 }
576 }
577
578 for msg in &recall {
580 result.midi.push(ActionStep::Send(msg.clone())).ok();
581 }
582 }
583
584 result.preset_changed = true;
585 result.leds_changed = true;
586 }
587
588 fn working_state(&self) -> PresetState<B, E> {
589 let current = self.state_store.current();
590 PresetState {
591 button_active: self.button_active,
592 cycle_index: current.cycle_index,
593 encoder_values: current.encoder_values,
594 }
595 }
596
597 fn apply_state(&mut self, state: &PresetState<B, E>) {
598 self.button_active = state.button_active;
599 self.state_store.save_working(state);
600 }
601}
602
603#[cfg(test)]
604mod tests {
605 use super::*;
606 use crate::config::*;
607 use heapless::Vec as HVec;
608
609 fn make_config(
613 buttons: HVec<ButtonConfig, MAX_BUTTONS>,
614 encoders: HVec<EncoderConfig, MAX_ENCODERS>,
615 ) -> Config {
616 make_config_with_presets(buttons, encoders, HVec::new(), HVec::new(), HVec::new())
617 }
618
619 fn make_config_with_presets(
620 buttons: HVec<ButtonConfig, MAX_BUTTONS>,
621 encoders: HVec<EncoderConfig, MAX_ENCODERS>,
622 on_enter: HVec<Action, MAX_ACTIONS>,
623 on_exit: HVec<Action, MAX_ACTIONS>,
624 triggers: HVec<Trigger, MAX_TRIGGERS>,
625 ) -> Config {
626 let mut presets: HVec<Preset, MAX_PRESETS> = HVec::new();
627 presets
628 .push(Preset {
629 name: Label::try_from("P1").unwrap(),
630 buttons,
631 encoders,
632 analog: HVec::new(),
633 defaults: Default::default(),
634 on_enter,
635 on_exit,
636 triggers,
637 })
638 .ok();
639 Config {
640 global: GlobalConfig::default(),
641 presets,
642 }
643 }
644
645 fn make_two_preset_config() -> Config {
646 let mut presets: HVec<Preset, MAX_PRESETS> = HVec::new();
647
648 let mut buttons0: HVec<ButtonConfig, MAX_BUTTONS> = HVec::new();
650 let mut on_press0: HVec<Action, MAX_ACTIONS> = HVec::new();
651 on_press0.push(Action::cc(80, 127, 1).unwrap()).ok();
652 let mut on_release0: HVec<Action, MAX_ACTIONS> = HVec::new();
653 on_release0.push(Action::cc(80, 0, 1).unwrap()).ok();
654 buttons0
655 .push(ButtonConfig {
656 label: Label::try_from("FX1").unwrap(),
657 color: LedConfig::default(),
658 mode: ButtonMode::Toggle,
659 on_press: on_press0,
660 on_release: on_release0,
661 on_long_press: HVec::new(),
662 cycle_values: HVec::new(),
663 listen_cc: None,
664 })
665 .ok();
666
667 let mut on_enter0: HVec<Action, MAX_ACTIONS> = HVec::new();
668 on_enter0.push(Action::program_change(0, 1).unwrap()).ok();
669
670 presets
671 .push(Preset {
672 name: Label::try_from("Preset0").unwrap(),
673 buttons: buttons0,
674 encoders: HVec::new(),
675 analog: HVec::new(),
676 defaults: Default::default(),
677 on_enter: on_enter0,
678 on_exit: HVec::new(),
679 triggers: HVec::new(),
680 })
681 .ok();
682
683 let mut buttons1: HVec<ButtonConfig, MAX_BUTTONS> = HVec::new();
685 let mut on_press1: HVec<Action, MAX_ACTIONS> = HVec::new();
686 on_press1.push(Action::cc(81, 127, 1).unwrap()).ok();
687 buttons1
688 .push(ButtonConfig {
689 label: Label::try_from("FX2").unwrap(),
690 color: LedConfig::default(),
691 mode: ButtonMode::Momentary,
692 on_press: on_press1,
693 on_release: HVec::new(),
694 on_long_press: HVec::new(),
695 cycle_values: HVec::new(),
696 listen_cc: None,
697 })
698 .ok();
699
700 let mut on_enter1: HVec<Action, MAX_ACTIONS> = HVec::new();
701 on_enter1.push(Action::program_change(1, 1).unwrap()).ok();
702
703 presets
704 .push(Preset {
705 name: Label::try_from("Preset1").unwrap(),
706 buttons: buttons1,
707 encoders: HVec::new(),
708 analog: HVec::new(),
709 defaults: Default::default(),
710 on_enter: on_enter1,
711 on_exit: HVec::new(),
712 triggers: HVec::new(),
713 })
714 .ok();
715
716 Config {
717 global: GlobalConfig::default(),
718 presets,
719 }
720 }
721
722 fn make_three_preset_config() -> Config {
723 let mut presets: HVec<Preset, MAX_PRESETS> = HVec::new();
724 for i in 0..3u8 {
725 let mut on_enter: HVec<Action, MAX_ACTIONS> = HVec::new();
726 on_enter.push(Action::program_change(i, 1).unwrap()).ok();
727 presets
728 .push(Preset {
729 name: Label::try_from("P").unwrap(),
730 buttons: HVec::new(),
731 encoders: HVec::new(),
732 analog: HVec::new(),
733 defaults: Default::default(),
734 on_enter,
735 on_exit: HVec::new(),
736 triggers: HVec::new(),
737 })
738 .ok();
739 }
740 Config {
741 global: GlobalConfig::default(),
742 presets,
743 }
744 }
745
746 fn momentary_button(
747 on_press: HVec<Action, MAX_ACTIONS>,
748 on_release: HVec<Action, MAX_ACTIONS>,
749 ) -> ButtonConfig {
750 ButtonConfig {
751 label: Label::new(),
752 color: LedConfig::default(),
753 mode: ButtonMode::Momentary,
754 on_press,
755 on_release,
756 on_long_press: HVec::new(),
757 cycle_values: HVec::new(),
758 listen_cc: None,
759 }
760 }
761
762 fn momentary_button_with_long_press(
763 on_press: HVec<Action, MAX_ACTIONS>,
764 on_long_press: HVec<Action, MAX_ACTIONS>,
765 ) -> ButtonConfig {
766 ButtonConfig {
767 label: Label::new(),
768 color: LedConfig::default(),
769 mode: ButtonMode::Momentary,
770 on_press,
771 on_release: HVec::new(),
772 on_long_press,
773 cycle_values: HVec::new(),
774 listen_cc: None,
775 }
776 }
777
778 fn has_midi_msg(output: &Output, expected: [u8; 3]) -> bool {
779 output
780 .midi
781 .iter()
782 .any(|step| matches!(step, ActionStep::Send(m) if m.data == expected))
783 }
784
785 #[test]
788 fn simple_button_press_generates_midi() {
789 let mut on_press: HVec<Action, MAX_ACTIONS> = HVec::new();
790 on_press.push(Action::cc(80, 127, 1).unwrap()).ok();
791
792 let mut buttons: HVec<ButtonConfig, MAX_BUTTONS> = HVec::new();
793 buttons.push(momentary_button(on_press, HVec::new())).ok();
794
795 let config = make_config(buttons, HVec::new());
796 let mut ctrl = Controller::new();
797
798 let result = ctrl.process(
799 Event::ButtonEdge {
800 index: 0,
801 edge: Edge::Activate,
802 },
803 0,
804 &config,
805 );
806
807 assert!(has_midi_msg(&result, [0xB0, 80, 127]));
808 assert!(result.leds_changed);
809 }
810
811 #[test]
814 fn button_release_generates_midi() {
815 let mut on_press: HVec<Action, MAX_ACTIONS> = HVec::new();
816 on_press.push(Action::cc(80, 127, 1).unwrap()).ok();
817 let mut on_release: HVec<Action, MAX_ACTIONS> = HVec::new();
818 on_release.push(Action::cc(80, 0, 1).unwrap()).ok();
819
820 let mut buttons: HVec<ButtonConfig, MAX_BUTTONS> = HVec::new();
821 buttons.push(momentary_button(on_press, on_release)).ok();
822
823 let config = make_config(buttons, HVec::new());
824 let mut ctrl = Controller::new();
825
826 ctrl.process(
828 Event::ButtonEdge {
829 index: 0,
830 edge: Edge::Activate,
831 },
832 0,
833 &config,
834 );
835
836 let result = ctrl.process(
838 Event::ButtonEdge {
839 index: 0,
840 edge: Edge::Deactivate,
841 },
842 100,
843 &config,
844 );
845
846 assert!(has_midi_msg(&result, [0xB0, 80, 0]));
847 }
848
849 #[test]
852 fn short_press_with_long_press_configured_fires_on_press_on_release() {
853 let mut on_press: HVec<Action, MAX_ACTIONS> = HVec::new();
854 on_press.push(Action::cc(80, 127, 1).unwrap()).ok();
855 let mut on_long_press: HVec<Action, MAX_ACTIONS> = HVec::new();
856 on_long_press.push(Action::PresetNext).ok();
857
858 let mut buttons: HVec<ButtonConfig, MAX_BUTTONS> = HVec::new();
859 buttons
860 .push(momentary_button_with_long_press(on_press, on_long_press))
861 .ok();
862
863 let config = make_config(buttons, HVec::new());
864 let mut ctrl = Controller::new();
865
866 let result = ctrl.process(
868 Event::ButtonEdge {
869 index: 0,
870 edge: Edge::Activate,
871 },
872 0,
873 &config,
874 );
875 assert!(!has_midi_msg(&result, [0xB0, 80, 127]));
877
878 let result = ctrl.process(Event::Tick, 200, &config);
880 assert!(result.midi.is_empty());
881
882 let result = ctrl.process(
884 Event::ButtonEdge {
885 index: 0,
886 edge: Edge::Deactivate,
887 },
888 300,
889 &config,
890 );
891
892 assert!(has_midi_msg(&result, [0xB0, 80, 127]));
894 }
895
896 #[test]
899 fn long_press_fires_on_long_press_actions() {
900 let mut on_press: HVec<Action, MAX_ACTIONS> = HVec::new();
901 on_press.push(Action::cc(80, 127, 1).unwrap()).ok();
902 let mut on_long_press: HVec<Action, MAX_ACTIONS> = HVec::new();
903 on_long_press.push(Action::cc(99, 127, 2).unwrap()).ok();
904
905 let mut buttons: HVec<ButtonConfig, MAX_BUTTONS> = HVec::new();
906 buttons
907 .push(momentary_button_with_long_press(on_press, on_long_press))
908 .ok();
909
910 let config = make_config(buttons, HVec::new());
911 let mut ctrl = Controller::new();
912
913 ctrl.process(
915 Event::ButtonEdge {
916 index: 0,
917 edge: Edge::Activate,
918 },
919 0,
920 &config,
921 );
922
923 let result = ctrl.process(Event::Tick, 500, &config);
925
926 assert!(has_midi_msg(&result, [0xB1, 99, 127]));
928 }
929
930 #[test]
933 fn long_press_suppresses_short_press_on_release() {
934 let mut on_press: HVec<Action, MAX_ACTIONS> = HVec::new();
935 on_press.push(Action::cc(80, 127, 1).unwrap()).ok();
936 let mut on_long_press: HVec<Action, MAX_ACTIONS> = HVec::new();
937 on_long_press.push(Action::cc(99, 127, 2).unwrap()).ok();
938
939 let mut buttons: HVec<ButtonConfig, MAX_BUTTONS> = HVec::new();
940 buttons
941 .push(momentary_button_with_long_press(on_press, on_long_press))
942 .ok();
943
944 let config = make_config(buttons, HVec::new());
945 let mut ctrl = Controller::new();
946
947 ctrl.process(
949 Event::ButtonEdge {
950 index: 0,
951 edge: Edge::Activate,
952 },
953 0,
954 &config,
955 );
956
957 ctrl.process(Event::Tick, 500, &config);
959
960 let result = ctrl.process(
962 Event::ButtonEdge {
963 index: 0,
964 edge: Edge::Deactivate,
965 },
966 600,
967 &config,
968 );
969
970 assert!(!has_midi_msg(&result, [0xB0, 80, 127]));
972 assert!(result.midi.is_empty());
973 }
974
975 #[test]
978 fn long_press_triggers_preset_switch() {
979 let mut on_long_press: HVec<Action, MAX_ACTIONS> = HVec::new();
980 on_long_press.push(Action::PresetNext).ok();
981
982 let mut buttons: HVec<ButtonConfig, MAX_BUTTONS> = HVec::new();
983 buttons
984 .push(momentary_button_with_long_press(HVec::new(), on_long_press))
985 .ok();
986
987 let mut presets: HVec<Preset, MAX_PRESETS> = HVec::new();
988 presets
989 .push(Preset {
990 name: Label::try_from("P1").unwrap(),
991 buttons,
992 encoders: HVec::new(),
993 analog: HVec::new(),
994 defaults: Default::default(),
995 on_enter: HVec::new(),
996 on_exit: HVec::new(),
997 triggers: HVec::new(),
998 })
999 .ok();
1000 presets
1001 .push(Preset {
1002 name: Label::try_from("P2").unwrap(),
1003 buttons: HVec::new(),
1004 encoders: HVec::new(),
1005 analog: HVec::new(),
1006 defaults: Default::default(),
1007 on_enter: HVec::new(),
1008 on_exit: HVec::new(),
1009 triggers: HVec::new(),
1010 })
1011 .ok();
1012 let config: Config = Config {
1013 global: GlobalConfig::default(),
1014 presets,
1015 };
1016 let mut ctrl = Controller::new();
1017
1018 assert_eq!(ctrl.active_preset(), 0);
1019
1020 ctrl.process(
1022 Event::ButtonEdge {
1023 index: 0,
1024 edge: Edge::Activate,
1025 },
1026 0,
1027 &config,
1028 );
1029
1030 let result = ctrl.process(Event::Tick, 500, &config);
1032
1033 assert!(result.preset_changed);
1034 assert_eq!(ctrl.active_preset(), 1);
1035 }
1036
1037 #[test]
1040 fn toggle_state_preserved_across_preset_switch() {
1041 let config = make_two_preset_config();
1042 let mut ctrl = Controller::new();
1043
1044 let result = ctrl.process(
1046 Event::ButtonEdge {
1047 index: 0,
1048 edge: Edge::Activate,
1049 },
1050 0,
1051 &config,
1052 );
1053 assert!(has_midi_msg(&result, [0xB0, 80, 127]));
1054 assert!(ctrl.button_states()[0]);
1055
1056 ctrl.select_preset(1, &config);
1058 assert_eq!(ctrl.active_preset(), 1);
1059 assert!(!ctrl.button_states()[0]);
1061
1062 ctrl.select_preset(0, &config);
1064 assert_eq!(ctrl.active_preset(), 0);
1065 assert!(ctrl.button_states()[0]);
1067 }
1068
1069 #[test]
1072 fn encoder_turn_generates_cc() {
1073 let mut encoders: HVec<EncoderConfig, MAX_ENCODERS> = HVec::new();
1074 encoders
1075 .push(EncoderConfig {
1076 label: Label::try_from("Vol").unwrap(),
1077 action: EncoderAction::Cc {
1078 cc: 7,
1079 channel: 1,
1080 min: 0,
1081 max: 127,
1082 },
1083 })
1084 .ok();
1085
1086 let config = make_config(HVec::new(), encoders);
1087 let mut ctrl = Controller::new();
1088 ctrl.set_encoder_value(0, 64);
1089
1090 let result = ctrl.process(
1091 Event::EncoderTurn {
1092 index: 0,
1093 clockwise: true,
1094 },
1095 1000,
1096 &config,
1097 );
1098
1099 assert!(has_midi_msg(&result, [0xB0, 7, 65]));
1101 assert_eq!(ctrl.encoder_values()[0], 65);
1102 }
1103
1104 #[test]
1107 fn encoder_acceleration_fast_turns_bigger_steps() {
1108 let mut encoders: HVec<EncoderConfig, MAX_ENCODERS> = HVec::new();
1109 encoders
1110 .push(EncoderConfig {
1111 label: Label::try_from("Vol").unwrap(),
1112 action: EncoderAction::Cc {
1113 cc: 7,
1114 channel: 1,
1115 min: 0,
1116 max: 127,
1117 },
1118 })
1119 .ok();
1120
1121 let config = make_config(HVec::new(), encoders);
1122 let mut ctrl = Controller::new();
1123 ctrl.set_encoder_value(0, 50);
1124
1125 ctrl.process(
1127 Event::EncoderTurn {
1128 index: 0,
1129 clockwise: true,
1130 },
1131 0,
1132 &config,
1133 );
1134 assert_eq!(ctrl.encoder_values()[0], 51);
1135
1136 let result = ctrl.process(
1138 Event::EncoderTurn {
1139 index: 0,
1140 clockwise: true,
1141 },
1142 30,
1143 &config,
1144 );
1145 assert_eq!(ctrl.encoder_values()[0], 55);
1147 assert!(has_midi_msg(&result, [0xB0, 7, 55]));
1148 }
1149
1150 #[test]
1153 fn tap_tempo_fires_bpm() {
1154 let mut on_press: HVec<Action, MAX_ACTIONS> = HVec::new();
1155 on_press.push(Action::TapTempo).ok();
1156
1157 let mut buttons: HVec<ButtonConfig, MAX_BUTTONS> = HVec::new();
1158 buttons.push(momentary_button(on_press, HVec::new())).ok();
1159
1160 let config = make_config(buttons, HVec::new());
1161 let mut ctrl = Controller::new();
1162
1163 let result = ctrl.process(
1165 Event::ButtonEdge {
1166 index: 0,
1167 edge: Edge::Activate,
1168 },
1169 0,
1170 &config,
1171 );
1172 assert_eq!(result.bpm, None);
1173
1174 ctrl.process(
1176 Event::ButtonEdge {
1177 index: 0,
1178 edge: Edge::Deactivate,
1179 },
1180 50,
1181 &config,
1182 );
1183
1184 let result = ctrl.process(
1186 Event::ButtonEdge {
1187 index: 0,
1188 edge: Edge::Activate,
1189 },
1190 500,
1191 &config,
1192 );
1193 assert_eq!(result.bpm, Some(120));
1194 }
1195
1196 #[test]
1199 fn incoming_midi_trigger_activates_button() {
1200 let mut triggers: HVec<Trigger, MAX_TRIGGERS> = HVec::new();
1201 triggers
1202 .push(Trigger {
1203 match_msg: TriggerMatch::Cc {
1204 cc: 100,
1205 channel: 1,
1206 value_min: 64,
1207 value_max: 127,
1208 },
1209 action: TriggerAction::Activate(0),
1210 })
1211 .ok();
1212
1213 let mut buttons: HVec<ButtonConfig, MAX_BUTTONS> = HVec::new();
1214 buttons
1215 .push(ButtonConfig {
1216 label: Label::new(),
1217 color: LedConfig::default(),
1218 mode: ButtonMode::Toggle,
1219 on_press: HVec::new(),
1220 on_release: HVec::new(),
1221 on_long_press: HVec::new(),
1222 cycle_values: HVec::new(),
1223 listen_cc: None,
1224 })
1225 .ok();
1226
1227 let config =
1228 make_config_with_presets(buttons, HVec::new(), HVec::new(), HVec::new(), triggers);
1229 let mut ctrl = Controller::new();
1230
1231 assert!(!ctrl.button_states()[0]);
1232
1233 let result = ctrl.process(
1235 Event::IncomingMidi {
1236 data: [0xB0, 100, 127],
1237 len: 3,
1238 },
1239 0,
1240 &config,
1241 );
1242
1243 assert!(ctrl.button_states()[0]);
1244 assert!(result.leds_changed);
1245 }
1246
1247 #[test]
1250 fn tick_is_noop_when_no_button_held() {
1251 let mut on_long_press: HVec<Action, MAX_ACTIONS> = HVec::new();
1252 on_long_press.push(Action::cc(99, 127, 1).unwrap()).ok();
1253
1254 let mut buttons: HVec<ButtonConfig, MAX_BUTTONS> = HVec::new();
1255 buttons
1256 .push(momentary_button_with_long_press(HVec::new(), on_long_press))
1257 .ok();
1258
1259 let config = make_config(buttons, HVec::new());
1260 let mut ctrl = Controller::new();
1261
1262 let result = ctrl.process(Event::Tick, 1000, &config);
1264
1265 assert!(result.midi.is_empty());
1266 assert!(!result.leds_changed);
1267 assert!(!result.preset_changed);
1268 assert_eq!(result.bpm, None);
1269 }
1270
1271 #[test]
1274 fn select_preset_fires_on_enter_midi() {
1275 let config = make_two_preset_config();
1276 let mut ctrl = Controller::new();
1277
1278 let result = ctrl.select_preset(1, &config);
1280
1281 assert!(result.preset_changed);
1282 assert_eq!(ctrl.active_preset(), 1);
1283 assert!(has_midi_msg(&result, [0xC0, 1, 0]));
1285 }
1286
1287 #[test]
1290 fn button_held_tracks_press_release() {
1291 let mut on_long_press: HVec<Action, MAX_ACTIONS> = HVec::new();
1293 on_long_press.push(Action::cc(99, 127, 1).unwrap()).ok();
1294
1295 let mut buttons: HVec<ButtonConfig, MAX_BUTTONS> = HVec::new();
1296 buttons
1297 .push(momentary_button_with_long_press(HVec::new(), on_long_press))
1298 .ok();
1299
1300 let config = make_config(buttons, HVec::new());
1301 let mut ctrl = Controller::new();
1302
1303 assert!(!ctrl.button_held());
1304
1305 ctrl.process(
1307 Event::ButtonEdge {
1308 index: 0,
1309 edge: Edge::Activate,
1310 },
1311 0,
1312 &config,
1313 );
1314 assert!(ctrl.button_held());
1315
1316 ctrl.process(
1318 Event::ButtonEdge {
1319 index: 0,
1320 edge: Edge::Deactivate,
1321 },
1322 100,
1323 &config,
1324 );
1325 assert!(!ctrl.button_held());
1326 }
1327
1328 #[test]
1331 fn preset_prev_wraps_around() {
1332 let mut presets: HVec<Preset, MAX_PRESETS> = HVec::new();
1334 for i in 0..3u8 {
1335 let mut btn_on_press: HVec<Action, MAX_ACTIONS> = HVec::new();
1336 btn_on_press.push(Action::PresetPrev).ok();
1337 let mut btns: HVec<ButtonConfig, MAX_BUTTONS> = HVec::new();
1338 btns.push(momentary_button(btn_on_press, HVec::new())).ok();
1339
1340 let mut on_enter: HVec<Action, MAX_ACTIONS> = HVec::new();
1341 on_enter.push(Action::program_change(i, 1).unwrap()).ok();
1342
1343 presets
1344 .push(Preset {
1345 name: Label::try_from("P").unwrap(),
1346 buttons: btns,
1347 encoders: HVec::new(),
1348 analog: HVec::new(),
1349 defaults: Default::default(),
1350 on_enter,
1351 on_exit: HVec::new(),
1352 triggers: HVec::new(),
1353 })
1354 .ok();
1355 }
1356 let config: Config = Config {
1357 global: GlobalConfig::default(),
1358 presets,
1359 };
1360 let mut ctrl = Controller::new();
1361
1362 assert_eq!(ctrl.active_preset(), 0);
1363
1364 let result = ctrl.process(
1366 Event::ButtonEdge {
1367 index: 0,
1368 edge: Edge::Activate,
1369 },
1370 0,
1371 &config,
1372 );
1373
1374 assert!(result.preset_changed);
1375 assert_eq!(ctrl.active_preset(), 2);
1376 }
1377
1378 #[test]
1381 fn button_held_false_for_button_without_long_press() {
1382 let mut on_press: HVec<Action, MAX_ACTIONS> = HVec::new();
1385 on_press.push(Action::cc(80, 127, 1).unwrap()).ok();
1386
1387 let mut buttons: HVec<ButtonConfig, MAX_BUTTONS> = HVec::new();
1388 buttons.push(momentary_button(on_press, HVec::new())).ok();
1389
1390 let config = make_config(buttons, HVec::new());
1391 let mut ctrl = Controller::new();
1392
1393 ctrl.process(
1394 Event::ButtonEdge {
1395 index: 0,
1396 edge: Edge::Activate,
1397 },
1398 0,
1399 &config,
1400 );
1401
1402 assert!(!ctrl.button_held());
1404 }
1405
1406 #[test]
1407 fn select_preset_fires_on_exit_of_old_preset() {
1408 let mut presets: HVec<Preset, MAX_PRESETS> = HVec::new();
1409
1410 let mut on_exit: HVec<Action, MAX_ACTIONS> = HVec::new();
1411 on_exit.push(Action::cc(123, 0, 1).unwrap()).ok(); presets
1414 .push(Preset {
1415 name: Label::try_from("P1").unwrap(),
1416 buttons: HVec::new(),
1417 encoders: HVec::new(),
1418 analog: HVec::new(),
1419 defaults: Default::default(),
1420 on_enter: HVec::new(),
1421 on_exit,
1422 triggers: HVec::new(),
1423 })
1424 .ok();
1425
1426 let mut on_enter: HVec<Action, MAX_ACTIONS> = HVec::new();
1427 on_enter.push(Action::program_change(5, 1).unwrap()).ok();
1428
1429 presets
1430 .push(Preset {
1431 name: Label::try_from("P2").unwrap(),
1432 buttons: HVec::new(),
1433 encoders: HVec::new(),
1434 analog: HVec::new(),
1435 defaults: Default::default(),
1436 on_enter,
1437 on_exit: HVec::new(),
1438 triggers: HVec::new(),
1439 })
1440 .ok();
1441
1442 let config: Config = Config {
1443 global: GlobalConfig::default(),
1444 presets,
1445 };
1446 let mut ctrl = Controller::new();
1447
1448 let result = ctrl.select_preset(1, &config);
1449
1450 assert!(has_midi_msg(&result, [0xB0, 123, 0]));
1452 assert!(has_midi_msg(&result, [0xC0, 5, 0]));
1454 }
1455
1456 #[test]
1457 fn encoder_values_persist_across_preset_switch() {
1458 let mut encoders: HVec<EncoderConfig, MAX_ENCODERS> = HVec::new();
1459 encoders
1460 .push(EncoderConfig {
1461 label: Label::try_from("Vol").unwrap(),
1462 action: EncoderAction::Cc {
1463 cc: 7,
1464 channel: 1,
1465 min: 0,
1466 max: 127,
1467 },
1468 })
1469 .ok();
1470
1471 let mut presets: HVec<Preset, MAX_PRESETS> = HVec::new();
1472 presets
1473 .push(Preset {
1474 name: Label::try_from("P1").unwrap(),
1475 buttons: HVec::new(),
1476 encoders: encoders.clone(),
1477 analog: HVec::new(),
1478 defaults: Default::default(),
1479 on_enter: HVec::new(),
1480 on_exit: HVec::new(),
1481 triggers: HVec::new(),
1482 })
1483 .ok();
1484 presets
1485 .push(Preset {
1486 name: Label::try_from("P2").unwrap(),
1487 buttons: HVec::new(),
1488 encoders,
1489 analog: HVec::new(),
1490 defaults: Default::default(),
1491 on_enter: HVec::new(),
1492 on_exit: HVec::new(),
1493 triggers: HVec::new(),
1494 })
1495 .ok();
1496
1497 let config: Config = Config {
1498 global: GlobalConfig::default(),
1499 presets,
1500 };
1501 let mut ctrl = Controller::new();
1502 ctrl.set_encoder_value(0, 80);
1503
1504 ctrl.select_preset(1, &config);
1506 assert_eq!(ctrl.encoder_values()[0], 0); ctrl.select_preset(0, &config);
1510 assert_eq!(ctrl.encoder_values()[0], 80); }
1512
1513 #[test]
1514 fn snapshot_store_captures_current_state() {
1515 let mut on_press: HVec<Action, MAX_ACTIONS> = HVec::new();
1516 on_press.push(Action::cc(80, 127, 1).unwrap()).ok();
1517
1518 let mut buttons: HVec<ButtonConfig, MAX_BUTTONS> = HVec::new();
1519 buttons
1520 .push(ButtonConfig {
1521 label: Label::new(),
1522 color: LedConfig::default(),
1523 mode: ButtonMode::Toggle,
1524 on_press,
1525 on_release: HVec::new(),
1526 on_long_press: HVec::new(),
1527 cycle_values: HVec::new(),
1528 listen_cc: None,
1529 })
1530 .ok();
1531
1532 let config = make_config(buttons, HVec::new());
1533 let mut ctrl = Controller::new();
1534 ctrl.set_encoder_value(0, 42);
1535
1536 ctrl.process(
1538 Event::ButtonEdge {
1539 index: 0,
1540 edge: Edge::Activate,
1541 },
1542 0,
1543 &config,
1544 );
1545
1546 let store = ctrl.snapshot_store();
1547 let state = store.current();
1548 assert!(state.button_active[0]);
1549 assert_eq!(state.encoder_values[0], 42);
1550 }
1551
1552 #[test]
1553 fn incoming_midi_trigger_below_value_min_no_match() {
1554 let mut triggers: HVec<Trigger, MAX_TRIGGERS> = HVec::new();
1555 triggers
1556 .push(Trigger {
1557 match_msg: TriggerMatch::Cc {
1558 cc: 100,
1559 channel: 1,
1560 value_min: 64,
1561 value_max: 127,
1562 },
1563 action: TriggerAction::Activate(0),
1564 })
1565 .ok();
1566
1567 let mut buttons: HVec<ButtonConfig, MAX_BUTTONS> = HVec::new();
1568 buttons
1569 .push(ButtonConfig {
1570 label: Label::new(),
1571 color: LedConfig::default(),
1572 mode: ButtonMode::Toggle,
1573 on_press: HVec::new(),
1574 on_release: HVec::new(),
1575 on_long_press: HVec::new(),
1576 cycle_values: HVec::new(),
1577 listen_cc: None,
1578 })
1579 .ok();
1580
1581 let config =
1582 make_config_with_presets(buttons, HVec::new(), HVec::new(), HVec::new(), triggers);
1583 let mut ctrl = Controller::new();
1584
1585 let result = ctrl.process(
1587 Event::IncomingMidi {
1588 data: [0xB0, 100, 63],
1589 len: 3,
1590 },
1591 0,
1592 &config,
1593 );
1594
1595 assert!(!ctrl.button_states()[0]);
1596 assert!(!result.leds_changed);
1597 }
1598
1599 #[test]
1600 fn preset_next_wraps_around() {
1601 let config = make_three_preset_config();
1602 let mut ctrl = Controller::new();
1603
1604 ctrl.select_preset(2, &config);
1606 assert_eq!(ctrl.active_preset(), 2);
1607
1608 let mut on_press: HVec<Action, MAX_ACTIONS> = HVec::new();
1610 on_press.push(Action::PresetNext).ok();
1611
1612 let mut buttons: HVec<ButtonConfig, MAX_BUTTONS> = HVec::new();
1613 buttons.push(momentary_button(on_press, HVec::new())).ok();
1614
1615 let mut presets: HVec<Preset, MAX_PRESETS> = HVec::new();
1616 for _i in 0..3u8 {
1617 let mut btns: HVec<ButtonConfig, MAX_BUTTONS> = HVec::new();
1618 let mut bp: HVec<Action, MAX_ACTIONS> = HVec::new();
1619 bp.push(Action::PresetNext).ok();
1620 btns.push(momentary_button(bp, HVec::new())).ok();
1621 presets
1622 .push(Preset {
1623 name: Label::try_from("P").unwrap(),
1624 buttons: btns,
1625 encoders: HVec::new(),
1626 analog: HVec::new(),
1627 defaults: Default::default(),
1628 on_enter: HVec::new(),
1629 on_exit: HVec::new(),
1630 triggers: HVec::new(),
1631 })
1632 .ok();
1633 }
1634 let config2 = Config {
1635 global: GlobalConfig::default(),
1636 presets,
1637 };
1638 let mut ctrl2 = Controller::new();
1639 ctrl2.select_preset(2, &config2);
1640
1641 let result = ctrl2.process(
1642 Event::ButtonEdge {
1643 index: 0,
1644 edge: Edge::Activate,
1645 },
1646 0,
1647 &config2,
1648 );
1649
1650 assert!(result.preset_changed);
1651 assert_eq!(ctrl2.active_preset(), 0); }
1653}