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