A step sequencer for Adafruit's RP2040-based macropad
1use embassy_sync::{blocking_mutex::raw::ThreadModeRawMutex, channel::Channel};
2use smart_leds::RGB;
3
4use crate::{
5 COLS, KeyGrid, NUM_KEYS, ROWS,
6 key_leds::Coord,
7 menus::{SEQUENCER_MENU, SequencerMenuValue, StepMenuValue},
8 tasks::{
9 display::{DISPLAY_CHANNEL, DisplayUpdate},
10 lights::{LIGHTS_CHANNEL, LedUpdate},
11 usb_midi::{MIDI_CHANNEL, MidiEvent},
12 },
13};
14
15pub static CONTROLS_CHANNEL: Channel<ThreadModeRawMutex, ControlEvent, 10> = Channel::new();
16
17pub enum ControlEvent {
18 Key {
19 pressed: bool,
20 held: bool,
21 coord: Coord,
22 },
23 RotaryButton {
24 pressed: bool,
25 },
26 SequencerStep,
27 RotaryEncoder {
28 increment: i32,
29 },
30 SequencerMenuChange {
31 value: SequencerMenuValue,
32 },
33 StepMenuChange {
34 value: StepMenuValue,
35 },
36}
37
38#[derive(Default, Clone, Copy)]
39struct StepState {
40 active: bool,
41 pressed: bool,
42 held: bool,
43 value: StepMenuValue,
44}
45
46#[embassy_executor::task]
47pub async fn read_controls() {
48 let mut step_state: KeyGrid<StepState> = [[StepState::default(); COLS]; ROWS];
49
50 let active = RGB { r: 5, g: 5, b: 5 };
51 let off = RGB { r: 0, g: 0, b: 0 };
52 let current = RGB { r: 32, g: 0, b: 0 };
53
54 let mut num_steps = 12;
55 let mut num_keys_pressed = 0;
56 let mut selected_step: Option<Coord> = None;
57 let mut step_index: usize = 0;
58 let mut step: Coord = (0, 0);
59 let mut last_note: Option<StepMenuValue> = None;
60 let mut play = false;
61
62 loop {
63 match CONTROLS_CHANNEL.receive().await {
64 ControlEvent::Key {
65 pressed,
66 held,
67 coord,
68 } => {
69 let state = &mut step_state[coord.1 as usize][coord.0 as usize];
70 let was_pressed = state.pressed;
71 let was_held = state.held;
72 state.pressed = pressed;
73 state.held = held;
74 if !pressed && was_pressed && !was_held {
75 state.active = !state.active;
76 let color = if state.active { active } else { off };
77 update_key_light(coord, color).await;
78 }
79
80 if !pressed {
81 num_keys_pressed -= 1;
82 if num_keys_pressed != 1 {
83 set_step_menu(None).await;
84 }
85 } else {
86 if !was_pressed && pressed {
87 num_keys_pressed += 1;
88 }
89 let value = if num_keys_pressed == 1 && held {
90 selected_step = Some(coord);
91 Some(state.value)
92 } else {
93 selected_step = None;
94 None
95 };
96
97 set_step_menu(value).await;
98 }
99 }
100 ControlEvent::RotaryButton { pressed } => {
101 if !pressed {
102 continue;
103 }
104
105 rotary_press().await;
106 }
107 ControlEvent::RotaryEncoder { increment } => rotary_change(increment).await,
108 ControlEvent::SequencerStep => {
109 let state = step_state[step.1 as usize][step.0 as usize];
110
111 if let Some(value) = last_note {
112 send_note(false, value).await;
113 }
114
115 let prev_color = if state.active { active } else { off };
116
117 let next_step = if num_keys_pressed > 0 {
118 step_index = (step_index + 1).rem_euclid(NUM_KEYS);
119 let mut next = coord_from_index(step_index);
120 while !step_state[next.1 as usize][next.0 as usize].pressed {
121 step_index = (step_index + 1).rem_euclid(NUM_KEYS);
122 next = coord_from_index(step_index)
123 }
124 next
125 } else {
126 step_index = (step_index + 1).rem_euclid(num_steps);
127 coord_from_index(step_index)
128 };
129
130 let state = step_state[next_step.1 as usize][next_step.0 as usize];
131 last_note = Some(state.value);
132
133 if state.active || num_keys_pressed > 0 {
134 send_note(true, state.value).await;
135 }
136
137 update_key_light(step, prev_color).await;
138 update_key_light(next_step, current).await;
139 step = next_step;
140 }
141 ControlEvent::SequencerMenuChange { value } => unsafe {
142 num_steps = value.steps as usize;
143 if play
144 && !value.play
145 && let Some(note) = last_note
146 {
147 send_note(false, note).await;
148 }
149 play = value.play;
150 SEQUENCER_MENU.lock_mut(|inner| *inner = Some(value));
151 },
152 ControlEvent::StepMenuChange { value } => {
153 if let Some(coord) = selected_step {
154 step_state[coord.1 as usize][coord.0 as usize].value = value;
155 }
156 }
157 }
158 }
159}
160
161fn coord_from_index(index: usize) -> Coord {
162 ((index % COLS) as u8, (index / COLS) as u8)
163}
164
165async fn send_note(on: bool, value: StepMenuValue) {
166 MIDI_CHANNEL
167 .send(MidiEvent::Note {
168 on,
169 note: value.note,
170 octave: value.octave as u8,
171 velocity: value.velocity as u8,
172 })
173 .await;
174}
175
176async fn set_step_menu(value: Option<StepMenuValue>) {
177 DISPLAY_CHANNEL
178 .send(DisplayUpdate::StepMenu { value })
179 .await;
180}
181
182async fn rotary_press() {
183 DISPLAY_CHANNEL.send(DisplayUpdate::RotaryPress).await;
184}
185
186async fn rotary_change(increment: i32) {
187 DISPLAY_CHANNEL
188 .send(DisplayUpdate::RotaryMove { increment })
189 .await;
190}
191
192async fn update_key_light(coord: Coord, color: RGB<u8>) {
193 LIGHTS_CHANNEL.send(LedUpdate { coord, color }).await;
194}