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