A step sequencer for Adafruit's RP2040-based macropad
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

hook menus up to sequencer

+150 -63
+1
Cargo.lock
··· 1013 1013 "itoa", 1014 1014 "log", 1015 1015 "midi-convert", 1016 + "num_enum", 1016 1017 "panic-probe", 1017 1018 "pio", 1018 1019 "pio-proc",
+1
Cargo.toml
··· 64 64 midi-convert = "0.2.0" 65 65 usbd-midi = "0.5.0" 66 66 itoa = "1.0.17" 67 + num_enum = { version = "0.7.4", default-features = false } 67 68 68 69 [profile.release] 69 70 debug = true
+6 -2
src/main.rs
··· 1 1 #![no_std] 2 2 #![no_main] 3 3 4 - use core::sync::atomic::AtomicU32; 4 + use core::sync::atomic::{AtomicBool, AtomicU8, AtomicU32}; 5 5 6 6 use embassy_executor::Spawner; 7 7 use embassy_rp::{ ··· 32 32 mod key_leds; 33 33 mod menus; 34 34 mod rotary_encoder; 35 + mod sequencer_timer; 35 36 mod usb; 36 37 37 38 bind_interrupts!(struct Irqs { ··· 43 44 const ROWS: usize = 4; 44 45 type KeyGrid<T> = [[T; COLS]; ROWS]; 45 46 46 - static SPEED_MS: AtomicU32 = AtomicU32::new(120); 47 + static PLAY: AtomicBool = AtomicBool::new(false); 48 + static BPM: AtomicU32 = AtomicU32::new(120); 49 + static TIMING: AtomicU8 = AtomicU8::new(0); 50 + static SWING: AtomicU32 = AtomicU32::new(0); 47 51 48 52 #[embassy_executor::main] 49 53 async fn main(spawner: Spawner) {
+25 -24
src/menus/mod.rs
··· 1 1 mod render; 2 2 mod sequencer; 3 + use core::sync::atomic::{AtomicBool, AtomicU8, AtomicU32, Ordering}; 4 + 3 5 pub use render::*; 4 6 pub use sequencer::SequencerMenu; 5 7 ··· 22 24 23 25 pub struct NumericMenuItem<'a> { 24 26 title: &'a str, 25 - value: u32, 27 + value: &'a AtomicU32, 26 28 buffer: itoa::Buffer, 27 29 } 28 30 29 31 impl<'a> NumericMenuItem<'a> { 30 - pub fn new(title: &'a str, value: u32) -> Self { 32 + pub fn new(title: &'a str, value: &'a AtomicU32) -> Self { 31 33 let buffer = itoa::Buffer::new(); 34 + 32 35 Self { 33 36 title, 34 37 value, 35 38 buffer, 36 39 } 37 40 } 38 - 39 - pub fn inner(&self) -> &u32 { 40 - &self.value 41 - } 42 41 } 43 42 44 43 impl<'a> MenuItem for NumericMenuItem<'a> { 45 44 fn as_str(&mut self) -> (&str, &str) { 46 - (self.title, self.buffer.format(self.value)) 45 + ( 46 + self.title, 47 + self.buffer.format(self.value.load(Ordering::Relaxed)), 48 + ) 47 49 } 48 50 49 51 fn on_change(&mut self, step: i32) { 50 - let mut intermediate = self.value as i32; 52 + let mut intermediate = self.value.load(Ordering::Relaxed) as i32; 51 53 intermediate += step; 52 54 if intermediate < 0 { 53 55 intermediate = 0; 54 56 } 55 57 56 - self.value = intermediate as u32; 58 + self.value.store(intermediate as u32, Ordering::Relaxed); 57 59 } 58 60 } 59 61 60 62 pub struct BooleanMenuItem<'a> { 61 63 title: &'a str, 62 - value: bool, 64 + value: &'a AtomicBool, 63 65 on_str: &'a str, 64 66 off_str: &'a str, 65 67 } 66 68 67 69 impl<'a> BooleanMenuItem<'a> { 68 - pub fn new(title: &'a str, on_str: &'a str, off_str: &'a str) -> Self { 70 + pub fn new(title: &'a str, on_str: &'a str, off_str: &'a str, value: &'a AtomicBool) -> Self { 69 71 Self { 70 72 title, 71 - value: false, 73 + value, 72 74 on_str, 73 75 off_str, 74 76 } 75 77 } 76 - 77 - pub fn inner(&self) -> &bool { 78 - &self.value 79 - } 80 78 } 81 79 82 80 impl MenuItem for BooleanMenuItem<'static> { 83 81 fn as_str(&mut self) -> (&str, &str) { 84 - let value = if self.value { 82 + let value = if self.value.load(Ordering::Relaxed) { 85 83 self.on_str 86 84 } else { 87 85 self.off_str ··· 90 88 } 91 89 92 90 fn on_change(&mut self, _step: i32) { 93 - self.value = !self.value; 91 + self.value 92 + .store(!self.value.load(Ordering::Relaxed), Ordering::Relaxed); 94 93 } 95 94 } 96 95 ··· 105 104 title: &'a str, 106 105 options: [T; SIZE], 107 106 index: usize, 107 + value: &'a AtomicU8, 108 108 } 109 109 110 110 impl<'a, const SIZE: usize, T> EnumMenuItem<'a, SIZE, T> 111 111 where 112 112 T: Stringable, 113 113 { 114 - pub fn new(title: &'a str, options: [T; SIZE]) -> Self { 114 + pub fn new(title: &'a str, options: [T; SIZE], value: &'a AtomicU8) -> Self { 115 + // TODO: set index to currently selected! 116 + 115 117 Self { 116 118 title, 117 119 options, 118 120 index: 0, 121 + value, 119 122 } 120 123 } 121 - 122 - pub fn inner(&self) -> &T { 123 - &self.options[self.index] 124 - } 125 124 } 126 125 127 126 impl<'a, const SIZE: usize, T> MenuItem for EnumMenuItem<'a, SIZE, T> 128 127 where 129 - T: Stringable + Copy, 128 + T: Stringable + Copy + Into<u8>, 130 129 { 131 130 fn as_str(&mut self) -> (&str, &str) { 132 131 (self.title, self.options[self.index].as_str()) ··· 135 134 fn on_change(&mut self, step: i32) { 136 135 let next = (self.index as i32 + step).rem_euclid(SIZE as i32); 137 136 self.index = next as usize; 137 + self.value 138 + .store(self.options[self.index].into(), Ordering::Relaxed); 138 139 } 139 140 }
+18 -1
src/menus/sequencer.rs
··· 1 1 use crate::{ 2 2 display::MonoDisplay, 3 - menus::{Menu, MenuItem, MenuItemRender, MenuItemState, render_menu_heading, render_menu_item}, 3 + menus::{ 4 + Menu, MenuItem, MenuItemRender, MenuItemState, Stringable, render_menu_heading, 5 + render_menu_item, 6 + }, 7 + sequencer_timer::TimingOption, 4 8 }; 9 + 10 + impl Stringable for TimingOption { 11 + fn as_str(&self) -> &str { 12 + match self { 13 + TimingOption::Quarter => "1/4", 14 + TimingOption::QuarterTriplet => "1/4 triplet", 15 + TimingOption::Eighth => "1/8", 16 + TimingOption::EighthTriplet => "1/8 triplet", 17 + TimingOption::Sixteenth => "1/16", 18 + TimingOption::SixteenthTriplet => "1/16 triplet", 19 + } 20 + } 21 + } 5 22 6 23 pub struct SequencerMenu<'a, const SIZE: usize> { 7 24 index: usize,
+65
src/sequencer_timer.rs
··· 1 + use embassy_time::Timer; 2 + use num_enum::{FromPrimitive, IntoPrimitive}; 3 + 4 + use crate::{COLS, ROWS}; 5 + 6 + #[derive(Clone, Copy, IntoPrimitive, FromPrimitive)] 7 + #[repr(u8)] 8 + pub enum TimingOption { 9 + #[default] 10 + Quarter, 11 + QuarterTriplet, 12 + Eighth, 13 + EighthTriplet, 14 + Sixteenth, 15 + SixteenthTriplet, 16 + } 17 + 18 + const NUM_STEPS: usize = ROWS * COLS; 19 + 20 + pub struct SequencerConfig { 21 + pub bpm: u32, 22 + pub timing: TimingOption, 23 + pub swing: u32, 24 + } 25 + 26 + pub struct SequencerTimer { 27 + config: SequencerConfig, 28 + speed_ms: u32, 29 + } 30 + 31 + impl SequencerTimer { 32 + pub fn new() -> Self { 33 + let config = SequencerConfig { 34 + bpm: 120, 35 + timing: TimingOption::Quarter, 36 + swing: 0, 37 + }; 38 + 39 + let speed_ms = config_to_ms(&config); 40 + 41 + Self { config, speed_ms } 42 + } 43 + 44 + pub fn set(&mut self, config: SequencerConfig) { 45 + self.config = config; 46 + self.speed_ms = config_to_ms(&self.config); 47 + } 48 + 49 + pub async fn next_step(&mut self) { 50 + Timer::after_millis(self.speed_ms as u64).await; 51 + } 52 + } 53 + 54 + fn config_to_ms(config: &SequencerConfig) -> u32 { 55 + // TODO: swing 56 + // TODO: does this need to be µS to preserve timing? 57 + match config.timing { 58 + TimingOption::Quarter => 60_000 / config.bpm, 59 + TimingOption::QuarterTriplet => 40_000 / config.bpm, 60 + TimingOption::Eighth => 30_000 / config.bpm, 61 + TimingOption::EighthTriplet => 20_000 / config.bpm, 62 + TimingOption::Sixteenth => 15_000 / config.bpm, 63 + TimingOption::SixteenthTriplet => 10_000 / config.bpm, 64 + } 65 + }
+1 -2
src/tasks/controls.rs
··· 4 4 use smart_leds::RGB; 5 5 6 6 use crate::{ 7 - COLS, KeyGrid, ROWS, SPEED_MS, 7 + COLS, KeyGrid, ROWS, 8 8 key_leds::Coord, 9 9 tasks::{ 10 10 display::{DISPLAY_CHANNEL, DisplayUpdate}, ··· 30 30 let current = RGB { r: 32, g: 0, b: 0 }; 31 31 32 32 let mut step: Coord = (0, 0); 33 - let mut bpm: i32 = 120; 34 33 35 34 loop { 36 35 match CONTROLS_CHANNEL.receive().await {
+16 -23
src/tasks/display.rs
··· 1 - use crate::menus::{ 2 - BooleanMenuItem, EnumMenuItem, Menu, NumericMenuItem, SequencerMenu, Stringable, 1 + use core::sync::atomic::Ordering; 2 + 3 + use crate::{ 4 + BPM, PLAY, SWING, TIMING, 5 + menus::{BooleanMenuItem, EnumMenuItem, Menu, NumericMenuItem, SequencerMenu}, 6 + sequencer_timer::TimingOption, 3 7 }; 4 8 use embassy_sync::{blocking_mutex::raw::ThreadModeRawMutex, channel::Channel}; 5 9 ··· 22 26 } 23 27 } 24 28 25 - #[derive(Clone, Copy)] 26 - pub enum TimingOption { 27 - Quarter, 28 - Eighth, 29 - Sixteenth, 30 - } 31 - 32 - impl Stringable for TimingOption { 33 - fn as_str(&self) -> &str { 34 - match self { 35 - TimingOption::Quarter => "1/4", 36 - TimingOption::Eighth => "1/8", 37 - TimingOption::Sixteenth => "1/16", 38 - } 39 - } 40 - } 41 - 42 29 #[embassy_executor::task] 43 30 pub async fn drive_display(mut display: Display) { 44 31 display.init(); 45 - 46 - let mut play_menu = BooleanMenuItem::new("STATUS", "PLAYING", "PAUSED"); 47 - let mut bpm_menu = NumericMenuItem::new("BPM", 120); 32 + TIMING.store(TimingOption::Quarter.into(), Ordering::Relaxed); 33 + let mut play_menu = BooleanMenuItem::new("STATUS", "PLAYING", "PAUSED", &PLAY); 34 + let mut bpm_menu = NumericMenuItem::new("BPM", &BPM); 48 35 let mut timing_menu = EnumMenuItem::new( 49 36 "TIMING", 50 37 [ 51 38 TimingOption::Quarter, 39 + TimingOption::QuarterTriplet, 52 40 TimingOption::Eighth, 41 + TimingOption::EighthTriplet, 53 42 TimingOption::Sixteenth, 43 + TimingOption::SixteenthTriplet, 54 44 ], 45 + &TIMING, 55 46 ); 56 - let mut swing_menu = NumericMenuItem::new("SWING", 0); 47 + let mut swing_menu = NumericMenuItem::new("SWING", &SWING); 57 48 58 49 let sequencer = SequencerMenu::new([ 59 50 &mut play_menu, ··· 63 54 ]); 64 55 65 56 let mut menus = Menus::new(sequencer); 57 + menus.sequencer.render(&mut display.display); 58 + display.flush(); 66 59 67 60 loop { 68 61 match DISPLAY_CHANNEL.receive().await {
+17 -11
src/tasks/sequencer.rs
··· 1 1 use core::sync::atomic::Ordering; 2 2 3 - use embassy_time::Timer; 4 - 5 3 use crate::{ 6 - COLS, SPEED_MS, 4 + BPM, COLS, PLAY, SWING, TIMING, 5 + sequencer_timer::{SequencerConfig, SequencerTimer}, 7 6 tasks::{CONTROLS_CHANNEL, controls::ControlEvent}, 8 7 }; 9 8 ··· 12 11 let mut step: u8 = 0; 13 12 let cols = COLS as u8; 14 13 14 + let mut timer = SequencerTimer::new(); 15 15 loop { 16 - let coord = (step % cols, step / cols); 17 - CONTROLS_CHANNEL 18 - .send(ControlEvent::SequencerStep { coord }) 19 - .await; 16 + let play = PLAY.load(Ordering::Relaxed); 17 + if play { 18 + let coord = (step % cols, step / cols); 19 + CONTROLS_CHANNEL 20 + .send(ControlEvent::SequencerStep { coord }) 21 + .await; 22 + 23 + step = (step + 1).rem_euclid(12); 20 24 21 - step += 1; 22 - if step == 12 { 23 - step = 0; 25 + timer.set(SequencerConfig { 26 + bpm: BPM.load(Ordering::Relaxed), 27 + timing: TIMING.load(Ordering::Relaxed).into(), 28 + swing: SWING.load(Ordering::Relaxed), 29 + }); 24 30 } 25 31 26 - Timer::after_millis(SPEED_MS.load(Ordering::Relaxed) as u64).await; 32 + timer.next_step().await; 27 33 } 28 34 }