tangled
alpha
login
or
join now
ericwood.org
/
macropad-sequencer
1
fork
atom
A step sequencer for Adafruit's RP2040-based macropad
1
fork
atom
overview
issues
pulls
pipelines
revamp menus
ericwood.org
2 months ago
2777e0ef
b7e7a6fd
+271
-123
7 changed files
expand all
collapse all
unified
split
src
main.rs
menus
mod.rs
sequencer.rs
step.rs
sequencer_timer.rs
tasks
display.rs
sequencer.rs
-5
src/main.rs
···
44
44
const ROWS: usize = 4;
45
45
type KeyGrid<T> = [[T; COLS]; ROWS];
46
46
47
47
-
static PLAY: AtomicBool = AtomicBool::new(false);
48
48
-
static BPM: AtomicU32 = AtomicU32::new(120);
49
49
-
static TIMING: AtomicU8 = AtomicU8::new(0);
50
50
-
static SWING: AtomicU32 = AtomicU32::new(0);
51
51
-
52
47
#[embassy_executor::main]
53
48
async fn main(spawner: Spawner) {
54
49
let p = embassy_rp::init(Default::default());
+89
-24
src/menus/mod.rs
···
1
1
mod render;
2
2
mod sequencer;
3
3
-
use core::sync::atomic::{AtomicBool, AtomicU8, AtomicU32, Ordering};
3
3
+
mod step;
4
4
5
5
pub use render::*;
6
6
-
pub use sequencer::SequencerMenu;
6
6
+
pub use sequencer::{SEQUENCER_MENU, SequencerMenuItems, SequencerMenuValue};
7
7
8
8
use crate::display::MonoDisplay;
9
9
10
10
const WIDTH: u32 = 128;
11
11
const HEIGHT: u32 = 64;
12
12
13
13
-
pub trait Menu {
14
14
-
fn title(&self) -> &str;
15
15
-
fn on_change(&mut self, step: i32);
16
16
-
fn on_select(&mut self);
17
17
-
fn render(&mut self, display: &mut MonoDisplay);
13
13
+
pub struct Menu<'a, const SIZE: usize> {
14
14
+
title: &'a str,
15
15
+
index: usize,
16
16
+
items: [&'a mut dyn MenuItem; SIZE],
17
17
+
selecting: bool,
18
18
+
}
19
19
+
20
20
+
impl<'a, const SIZE: usize> Menu<'a, SIZE> {
21
21
+
pub fn new(title: &'a str, items: [&'a mut dyn MenuItem; SIZE]) -> Self {
22
22
+
let index = 0;
23
23
+
let selecting = false;
24
24
+
25
25
+
Self {
26
26
+
title,
27
27
+
index,
28
28
+
items,
29
29
+
selecting,
30
30
+
}
31
31
+
}
32
32
+
33
33
+
pub fn on_change(&mut self, step: i32) {
34
34
+
if self.selecting {
35
35
+
let next = (self.index as i32 + step).rem_euclid(SIZE as i32);
36
36
+
self.index = next as usize;
37
37
+
} else {
38
38
+
self.items[self.index].on_change(step);
39
39
+
}
40
40
+
}
41
41
+
42
42
+
pub fn on_select(&mut self) {
43
43
+
self.selecting = !self.selecting;
44
44
+
}
45
45
+
46
46
+
pub fn render(&mut self, display: &mut MonoDisplay) {
47
47
+
render_menu_heading(display, self.title);
48
48
+
49
49
+
for (i, item) in self.items.iter_mut().enumerate() {
50
50
+
let (title, value) = item.as_str();
51
51
+
52
52
+
let state = if i == self.index {
53
53
+
if self.selecting {
54
54
+
MenuItemState::Selecting
55
55
+
} else {
56
56
+
MenuItemState::Selected
57
57
+
}
58
58
+
} else {
59
59
+
MenuItemState::None
60
60
+
};
61
61
+
62
62
+
render_menu_item(
63
63
+
display,
64
64
+
&MenuItemRender {
65
65
+
position: i,
66
66
+
title,
67
67
+
value,
68
68
+
state,
69
69
+
},
70
70
+
);
71
71
+
}
72
72
+
}
18
73
}
19
74
20
75
pub trait MenuItem {
···
24
79
25
80
pub struct NumericMenuItem<'a> {
26
81
title: &'a str,
27
27
-
value: &'a AtomicU32,
82
82
+
value: u32,
28
83
buffer: itoa::Buffer,
84
84
+
callback: &'a dyn Fn(u32),
29
85
}
30
86
31
87
impl<'a> NumericMenuItem<'a> {
32
32
-
pub fn new(title: &'a str, value: &'a AtomicU32) -> Self {
88
88
+
pub fn new(title: &'a str, value: u32, on_change: &'a dyn Fn(u32)) -> Self {
33
89
let buffer = itoa::Buffer::new();
34
90
35
91
Self {
36
92
title,
37
93
value,
38
94
buffer,
95
95
+
callback: on_change,
39
96
}
40
97
}
41
98
}
42
99
43
100
impl<'a> MenuItem for NumericMenuItem<'a> {
44
101
fn as_str(&mut self) -> (&str, &str) {
45
45
-
(
46
46
-
self.title,
47
47
-
self.buffer.format(self.value.load(Ordering::Relaxed)),
48
48
-
)
102
102
+
(self.title, self.buffer.format(self.value))
49
103
}
50
104
51
105
fn on_change(&mut self, step: i32) {
52
52
-
let mut intermediate = self.value.load(Ordering::Relaxed) as i32;
106
106
+
let mut intermediate = self.value as i32;
53
107
intermediate += step;
54
108
if intermediate < 0 {
55
109
intermediate = 0;
56
110
}
57
111
58
58
-
self.value.store(intermediate as u32, Ordering::Relaxed);
112
112
+
self.value = intermediate as u32;
113
113
+
(*self.callback)(self.value);
59
114
}
60
115
}
61
116
62
117
pub struct BooleanMenuItem<'a> {
63
118
title: &'a str,
64
64
-
value: &'a AtomicBool,
119
119
+
value: bool,
65
120
on_str: &'a str,
66
121
off_str: &'a str,
122
122
+
callback: &'a dyn Fn(bool),
67
123
}
68
124
69
125
impl<'a> BooleanMenuItem<'a> {
70
70
-
pub fn new(title: &'a str, on_str: &'a str, off_str: &'a str, value: &'a AtomicBool) -> Self {
126
126
+
pub fn new(
127
127
+
title: &'a str,
128
128
+
on_str: &'a str,
129
129
+
off_str: &'a str,
130
130
+
value: bool,
131
131
+
on_change: &'a dyn Fn(bool),
132
132
+
) -> Self {
71
133
Self {
72
134
title,
73
135
value,
74
136
on_str,
75
137
off_str,
138
138
+
callback: on_change,
76
139
}
77
140
}
78
141
}
79
142
80
143
impl MenuItem for BooleanMenuItem<'static> {
81
144
fn as_str(&mut self) -> (&str, &str) {
82
82
-
let value = if self.value.load(Ordering::Relaxed) {
145
145
+
let value = if self.value {
83
146
self.on_str
84
147
} else {
85
148
self.off_str
···
88
151
}
89
152
90
153
fn on_change(&mut self, _step: i32) {
91
91
-
self.value
92
92
-
.store(!self.value.load(Ordering::Relaxed), Ordering::Relaxed);
154
154
+
self.value = !self.value;
155
155
+
(*self.callback)(self.value);
93
156
}
94
157
}
95
158
···
104
167
title: &'a str,
105
168
options: [T; SIZE],
106
169
index: usize,
107
107
-
value: &'a AtomicU8,
170
170
+
value: T,
171
171
+
callback: &'a dyn Fn(T),
108
172
}
109
173
110
174
impl<'a, const SIZE: usize, T> EnumMenuItem<'a, SIZE, T>
111
175
where
112
176
T: Stringable,
113
177
{
114
114
-
pub fn new(title: &'a str, options: [T; SIZE], value: &'a AtomicU8) -> Self {
178
178
+
pub fn new(title: &'a str, options: [T; SIZE], value: T, on_change: &'a dyn Fn(T)) -> Self {
115
179
// TODO: set index to currently selected!
116
180
117
181
Self {
···
119
183
options,
120
184
index: 0,
121
185
value,
186
186
+
callback: on_change,
122
187
}
123
188
}
124
189
}
···
134
199
fn on_change(&mut self, step: i32) {
135
200
let next = (self.index as i32 + step).rem_euclid(SIZE as i32);
136
201
self.index = next as usize;
137
137
-
self.value
138
138
-
.store(self.options[self.index].into(), Ordering::Relaxed);
202
202
+
self.value = self.options[self.index];
203
203
+
(*self.callback)(self.value);
139
204
}
140
205
}
+87
-54
src/menus/sequencer.rs
···
1
1
+
use core::cell::RefCell;
2
2
+
3
3
+
use embassy_sync::blocking_mutex::{Mutex, raw::ThreadModeRawMutex};
4
4
+
1
5
use crate::{
2
2
-
display::MonoDisplay,
3
3
-
menus::{
4
4
-
Menu, MenuItem, MenuItemRender, MenuItemState, Stringable, render_menu_heading,
5
5
-
render_menu_item,
6
6
-
},
6
6
+
menus::{BooleanMenuItem, EnumMenuItem, NumericMenuItem, Stringable},
7
7
sequencer_timer::TimingOption,
8
8
};
9
9
···
20
20
}
21
21
}
22
22
23
23
-
pub struct SequencerMenu<'a, const SIZE: usize> {
24
24
-
index: usize,
25
25
-
items: [&'a mut dyn MenuItem; SIZE],
26
26
-
selecting: bool,
23
23
+
#[derive(Clone, Copy)]
24
24
+
pub struct SequencerMenuValue {
25
25
+
pub play: bool,
26
26
+
pub bpm: u32,
27
27
+
pub timing: TimingOption,
28
28
+
pub swing: u32,
27
29
}
28
30
29
29
-
impl<'a, const SIZE: usize> SequencerMenu<'a, SIZE> {
30
30
-
pub fn new(items: [&'a mut dyn MenuItem; SIZE]) -> Self {
31
31
-
let index = 0;
32
32
-
let selecting = false;
33
33
-
31
31
+
impl Default for SequencerMenuValue {
32
32
+
fn default() -> Self {
34
33
Self {
35
35
-
index,
36
36
-
items,
37
37
-
selecting,
34
34
+
play: false,
35
35
+
bpm: 120,
36
36
+
timing: TimingOption::Eighth,
37
37
+
swing: 0,
38
38
}
39
39
}
40
40
}
41
41
42
42
-
impl<'a, const SIZE: usize> Menu for SequencerMenu<'a, SIZE> {
43
43
-
fn title(&self) -> &str {
44
44
-
"Sequencer"
45
45
-
}
42
42
+
type SequencerMenuMutex = Mutex<ThreadModeRawMutex, Option<SequencerMenuValue>>;
43
43
+
pub static SEQUENCER_MENU: SequencerMenuMutex = Mutex::new(None);
46
44
47
47
-
fn on_change(&mut self, step: i32) {
48
48
-
if self.selecting {
49
49
-
let next = (self.index as i32 + step).rem_euclid(SIZE as i32);
50
50
-
self.index = next as usize;
51
51
-
} else {
52
52
-
self.items[self.index].on_change(step);
53
53
-
}
54
54
-
}
45
45
+
pub struct SequencerMenuItems<'a> {
46
46
+
pub play_menu: BooleanMenuItem<'a>,
47
47
+
pub bpm_menu: NumericMenuItem<'a>,
48
48
+
pub timing_menu: EnumMenuItem<'a, 6, TimingOption>,
49
49
+
pub swing_menu: NumericMenuItem<'a>,
50
50
+
}
55
51
56
56
-
fn on_select(&mut self) {
57
57
-
self.selecting = !self.selecting;
58
58
-
}
52
52
+
impl<'a> SequencerMenuItems<'a> {
53
53
+
pub fn new() -> Self {
54
54
+
let defaults = SequencerMenuValue::default();
55
55
+
let play_menu =
56
56
+
BooleanMenuItem::new("STATUS", "PLAYING", "PAUSED", defaults.play, &|value| {
57
57
+
unsafe {
58
58
+
SEQUENCER_MENU.lock_mut(|inner| {
59
59
+
if let Some(menu_value) = inner {
60
60
+
menu_value.play = value;
61
61
+
}
62
62
+
})
63
63
+
};
64
64
+
});
59
65
60
60
-
fn render(&mut self, display: &mut MonoDisplay) {
61
61
-
render_menu_heading(display, self.title());
66
66
+
let bpm_menu = NumericMenuItem::new("BPM", defaults.bpm, &|value| {
67
67
+
unsafe {
68
68
+
SEQUENCER_MENU.lock_mut(|inner| {
69
69
+
if let Some(menu_value) = inner {
70
70
+
menu_value.bpm = value;
71
71
+
}
72
72
+
})
73
73
+
};
74
74
+
});
62
75
63
63
-
for (i, item) in self.items.iter_mut().enumerate() {
64
64
-
let (title, value) = item.as_str();
65
65
-
66
66
-
let state = if i == self.index {
67
67
-
if self.selecting {
68
68
-
MenuItemState::Selecting
69
69
-
} else {
70
70
-
MenuItemState::Selected
71
71
-
}
72
72
-
} else {
73
73
-
MenuItemState::None
76
76
+
let timing_menu = EnumMenuItem::new(
77
77
+
"TIMING",
78
78
+
[
79
79
+
TimingOption::Quarter,
80
80
+
TimingOption::QuarterTriplet,
81
81
+
TimingOption::Eighth,
82
82
+
TimingOption::EighthTriplet,
83
83
+
TimingOption::Sixteenth,
84
84
+
TimingOption::SixteenthTriplet,
85
85
+
],
86
86
+
defaults.timing,
87
87
+
&|value| {
88
88
+
unsafe {
89
89
+
SEQUENCER_MENU.lock_mut(|inner| {
90
90
+
if let Some(menu_value) = inner {
91
91
+
menu_value.timing = value;
92
92
+
}
93
93
+
})
94
94
+
};
95
95
+
},
96
96
+
);
97
97
+
let swing_menu = NumericMenuItem::new("SWING", defaults.swing, &|value| {
98
98
+
unsafe {
99
99
+
SEQUENCER_MENU.lock_mut(|inner| {
100
100
+
if let Some(menu_value) = inner {
101
101
+
menu_value.swing = value;
102
102
+
}
103
103
+
})
74
104
};
105
105
+
});
75
106
76
76
-
render_menu_item(
77
77
-
display,
78
78
-
&MenuItemRender {
79
79
-
position: i,
80
80
-
title,
81
81
-
value,
82
82
-
state,
83
83
-
},
84
84
-
);
107
107
+
unsafe {
108
108
+
SEQUENCER_MENU.lock_mut(|value| {
109
109
+
*value = Some(defaults);
110
110
+
});
111
111
+
}
112
112
+
113
113
+
Self {
114
114
+
play_menu,
115
115
+
bpm_menu,
116
116
+
timing_menu,
117
117
+
swing_menu,
85
118
}
86
119
}
87
120
}
+69
src/menus/step.rs
···
1
1
+
use crate::{
2
2
+
menus::{BooleanMenuItem, EnumMenuItem, NumericMenuItem, Stringable},
3
3
+
sequencer_timer::TimingOption,
4
4
+
};
5
5
+
6
6
+
enum Note {
7
7
+
A,
8
8
+
BFlat,
9
9
+
B,
10
10
+
C,
11
11
+
CSharp,
12
12
+
D,
13
13
+
EFlat,
14
14
+
E,
15
15
+
F,
16
16
+
FSharp,
17
17
+
G,
18
18
+
AFlat,
19
19
+
}
20
20
+
21
21
+
impl Stringable for Note {
22
22
+
fn as_str(&self) -> &str {
23
23
+
match self {
24
24
+
Note::A => "A",
25
25
+
Note::BFlat => "Bb",
26
26
+
Note::B => "B",
27
27
+
Note::C => "C",
28
28
+
Note::CSharp => "C#",
29
29
+
Note::D => "D",
30
30
+
Note::EFlat => "Eb",
31
31
+
Note::E => "E",
32
32
+
Note::F => "F",
33
33
+
Note::FSharp => "F#",
34
34
+
Note::G => "G",
35
35
+
Note::AFlat => "Ab",
36
36
+
}
37
37
+
}
38
38
+
}
39
39
+
40
40
+
//pub struct StepMenuItems<'a> {
41
41
+
// pub note_menu: EnumMenuItem<'a, 12>,
42
42
+
// pub octave_menu: NumericMenuItem<'a>,
43
43
+
// pub velocity_menu: NumericMenuItem<'a>,
44
44
+
//}
45
45
+
//
46
46
+
//impl<'a> StepMenuItems<'a> {
47
47
+
// pub fn new() -> Self {
48
48
+
//
49
49
+
// let note_menu = EnumMenuItem::new("NOTE", &[
50
50
+
// Note::BFlat
51
51
+
// Note::B,
52
52
+
// Note::C,
53
53
+
// Note::CSharp,
54
54
+
// Note::D,
55
55
+
// Note::EFlat,
56
56
+
// Note::E,
57
57
+
// Note::F,
58
58
+
// Note::FSharp,
59
59
+
// Note::G,
60
60
+
// Note::AFlat,
61
61
+
// ]);
62
62
+
//
63
63
+
// let octave_menu = NumericMenuItem::new()
64
64
+
//
65
65
+
// Self {
66
66
+
// note_menu,
67
67
+
// }
68
68
+
// }
69
69
+
//}
+1
-1
src/sequencer_timer.rs
···
3
3
4
4
use crate::{COLS, ROWS};
5
5
6
6
-
#[derive(Clone, Copy, IntoPrimitive, FromPrimitive)]
6
6
+
#[derive(Clone, Copy, IntoPrimitive, FromPrimitive, Default)]
7
7
#[repr(u8)]
8
8
pub enum TimingOption {
9
9
#[default]
+11
-29
src/tasks/display.rs
···
1
1
-
use core::sync::atomic::Ordering;
2
2
-
3
3
-
use crate::{
4
4
-
BPM, PLAY, SWING, TIMING,
5
5
-
menus::{BooleanMenuItem, EnumMenuItem, Menu, NumericMenuItem, SequencerMenu},
6
6
-
sequencer_timer::TimingOption,
7
7
-
};
1
1
+
use crate::menus::{Menu, SequencerMenuItems};
8
2
use embassy_sync::{blocking_mutex::raw::ThreadModeRawMutex, channel::Channel};
9
3
10
4
use crate::display::Display;
···
17
11
}
18
12
19
13
struct Menus<'a> {
20
20
-
pub sequencer: SequencerMenu<'a, 4>,
14
14
+
pub sequencer: Menu<'a, 4>,
21
15
}
22
16
23
17
impl<'a> Menus<'a> {
24
24
-
pub fn new(sequencer: SequencerMenu<'a, 4>) -> Self {
18
18
+
pub fn new(sequencer: Menu<'a, 4>) -> Self {
25
19
Self { sequencer }
26
20
}
27
21
}
···
29
23
#[embassy_executor::task]
30
24
pub async fn drive_display(mut display: Display) {
31
25
display.init();
32
32
-
TIMING.store(TimingOption::Quarter.into(), Ordering::Relaxed);
33
33
-
let mut play_menu = BooleanMenuItem::new("STATUS", "PLAYING", "PAUSED", &PLAY);
34
34
-
let mut bpm_menu = NumericMenuItem::new("BPM", &BPM);
35
35
-
let mut timing_menu = EnumMenuItem::new(
36
36
-
"TIMING",
26
26
+
27
27
+
let mut sequencer_items = SequencerMenuItems::new();
28
28
+
let sequencer = Menu::new(
29
29
+
"Sequencer",
37
30
[
38
38
-
TimingOption::Quarter,
39
39
-
TimingOption::QuarterTriplet,
40
40
-
TimingOption::Eighth,
41
41
-
TimingOption::EighthTriplet,
42
42
-
TimingOption::Sixteenth,
43
43
-
TimingOption::SixteenthTriplet,
31
31
+
&mut sequencer_items.play_menu,
32
32
+
&mut sequencer_items.bpm_menu,
33
33
+
&mut sequencer_items.timing_menu,
34
34
+
&mut sequencer_items.swing_menu,
44
35
],
45
45
-
&TIMING,
46
36
);
47
47
-
let mut swing_menu = NumericMenuItem::new("SWING", &SWING);
48
48
-
49
49
-
let sequencer = SequencerMenu::new([
50
50
-
&mut play_menu,
51
51
-
&mut bpm_menu,
52
52
-
&mut timing_menu,
53
53
-
&mut swing_menu,
54
54
-
]);
55
37
56
38
let mut menus = Menus::new(sequencer);
57
39
menus.sequencer.render(&mut display.display);
+14
-10
src/tasks/sequencer.rs
···
1
1
-
use core::sync::atomic::Ordering;
2
2
-
3
1
use crate::{
4
4
-
BPM, COLS, PLAY, SWING, TIMING,
2
2
+
COLS,
3
3
+
menus::{SEQUENCER_MENU, SequencerMenuValue},
5
4
sequencer_timer::{SequencerConfig, SequencerTimer},
6
5
tasks::{CONTROLS_CHANNEL, controls::ControlEvent},
7
6
};
···
12
11
let cols = COLS as u8;
13
12
14
13
let mut timer = SequencerTimer::new();
14
14
+
let mut play = false;
15
15
loop {
16
16
-
let play = PLAY.load(Ordering::Relaxed);
16
16
+
SEQUENCER_MENU.lock(|value| {
17
17
+
let SequencerMenuValue {
18
18
+
play: temp_play,
19
19
+
bpm,
20
20
+
timing,
21
21
+
swing,
22
22
+
} = value.unwrap();
23
23
+
timer.set(SequencerConfig { bpm, timing, swing });
24
24
+
play = temp_play;
25
25
+
});
26
26
+
17
27
if play {
18
28
let coord = (step % cols, step / cols);
19
29
CONTROLS_CHANNEL
···
21
31
.await;
22
32
23
33
step = (step + 1).rem_euclid(12);
24
24
-
25
25
-
timer.set(SequencerConfig {
26
26
-
bpm: BPM.load(Ordering::Relaxed),
27
27
-
timing: TIMING.load(Ordering::Relaxed).into(),
28
28
-
swing: SWING.load(Ordering::Relaxed),
29
29
-
});
30
34
}
31
35
32
36
timer.next_step().await;