diff --git a/Cargo.lock b/Cargo.lock index 47167af..042a5f3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -772,7 +772,7 @@ dependencies = [ [[package]] name = "geode_piano" -version = "0.2.1" +version = "0.3.0" dependencies = [ "byte-slice-cast 1.2.2", "cortex-m", diff --git a/Cargo.toml b/Cargo.toml index cfefc0b..31d17c6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "geode_piano" -version = "0.2.1" +version = "0.3.0" edition = "2021" license = "GPL-3.0-only" diff --git a/README.md b/README.md index 3e7e0f2..fc708d1 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ but is harder to set up. ### with debug probe -- Install `probe-rs`. +- Install `probe-rs-tools` (`cargo install probe-rs-tools --locked`). - Follow the wiring instructions in the [Pico Getting Started Guide](https://datasheets.raspberrypi.com/pico/getting-started-with-pico.pdf), at _Appendix A: Using Picoprobe_ in the Picoprobe Wiring section. You only need to wire GND, SWCLK and SWDIO. - If you are using a second Pico as a debug probe, diff --git a/src/bin/piano_firmware.rs b/src/bin/piano_firmware.rs index a36072b..7211518 100644 --- a/src/bin/piano_firmware.rs +++ b/src/bin/piano_firmware.rs @@ -28,7 +28,7 @@ use embassy_rp::i2c; use embassy_rp::peripherals::USB; use embassy_rp::usb::{Driver, InterruptHandler}; use geode_piano::matrix; -use geode_piano::matrix::KeyMatrix; +use geode_piano::matrix::{KeyMatrix, VelocityProfile}; use geode_piano::midi; use geode_piano::usb::usb_task; use geode_piano::{blinky, pin_array, pins, unwrap}; @@ -445,7 +445,13 @@ async fn piano_task(pin_driver: pins::TransparentPins) { ]; let mut mat = KeyMatrix::new(col_pins, row_pins, keymap); - mat.scan(pin_driver).await; + mat.scan( + pin_driver, + matrix::Config { + velocity_prof: VelocityProfile::Heavy, + }, + ) + .await; } bind_interrupts!(struct Irqs { diff --git a/src/matrix.rs b/src/matrix.rs index 61916e7..865293a 100644 --- a/src/matrix.rs +++ b/src/matrix.rs @@ -3,7 +3,7 @@ use crate::midi; use crate::pins; use crate::unwrap; -use core::cmp::min; +use core::cmp::{max, min}; use embassy_rp::gpio; use embassy_time::{Duration, Instant, Ticker}; @@ -14,6 +14,38 @@ pub enum NormalState { NC, } +/// Profile to map from key press duration to MIDI velocity. +/// https://www.desmos.com/calculator/mynk7thhzp +pub enum VelocityProfile { + Linear, + Heavy, + Light, +} + +fn velocity_light(us: u64) -> u8 { + if us <= 60000 { + min(127, (135000 - us * 6 / 5) / 1000) as u8 + } else { + (127 - min(us, 240000) / 4000 - 60) as u8 + } +} + +fn velocity_heavy(us: u64) -> u8 { + if us <= 17000 { + ((113000 - us) / 1000) as u8 + } else { + ((127000 - min(us, 190000) / 2 - 22000) / 1000) as u8 + } +} + +fn velocity_linear(us: u64) -> u8 { + max(127000 - (us as i32), 0) as u8 +} + +pub struct Config { + pub velocity_prof: VelocityProfile, +} + /// Task to handle pedals in MIDI /// /// `norm_open` represents a normally open switch @@ -63,7 +95,7 @@ impl KeyMatrix { } } - pub async fn scan(&mut self, mut pin_driver: pins::TransparentPins) { + pub async fn scan(&mut self, mut pin_driver: pins::TransparentPins, config: Config) { for i in pin_driver.pins { unwrap(pin_driver.set_input(i)).await; unwrap(pin_driver.set_pull(i, gpio::Pull::Up)).await; @@ -89,7 +121,7 @@ impl KeyMatrix { loop { let profile: bool = counter == 0; counter += 1; - counter %= 500; + counter %= 5000; let prof_start = Instant::now(); let mut prof_time_last_col = prof_start; let mut prof_dur_col = Duration::from_ticks(0); @@ -121,16 +153,16 @@ impl KeyMatrix { midi::KeyAction::N2(note) => { if key_active { if note_first[note as usize].is_some() && !note_on[note as usize] { - // millisecond duration of keypress + // microsecond duration of keypress let dur = - note_first[note as usize].unwrap().elapsed().as_millis(); - let velocity: u8 = if dur <= 60 { - (127 - dur * 6 / 5) as u8 - } else { - (127 - min(dur, 240) / 4 - 60) as u8 + note_first[note as usize].unwrap().elapsed().as_micros(); + let velocity = match config.velocity_prof { + VelocityProfile::Heavy => velocity_heavy(dur), + VelocityProfile::Linear => velocity_linear(dur), + VelocityProfile::Light => velocity_light(dur), }; defmt::debug!( - "{} velocity {} from dur {}ms", + "{} velocity {} from dur {}us", note, velocity, dur diff --git a/src/midi/mod.rs b/src/midi/mod.rs index 337fe23..55c2029 100644 --- a/src/midi/mod.rs +++ b/src/midi/mod.rs @@ -213,7 +213,7 @@ impl From for Disconnected { } } -static MIDI_QUEUE: Channel = Channel::new(); +static MIDI_QUEUE: Channel = Channel::new(); /// Handle sending MIDI until connection breaks pub async fn midi_session<'d, T: Instance + 'd>( diff --git a/src/usb.rs b/src/usb.rs index 4a0f43d..b28df7e 100644 --- a/src/usb.rs +++ b/src/usb.rs @@ -50,7 +50,7 @@ pub async fn usb_task( let mut config = Config::new(0xdead, 0xbeef); config.manufacturer = Some("dogeystamp"); config.product = Some("Geode-Piano MIDI keyboard"); - config.serial_number = Some("0.2.1"); + config.serial_number = Some("0.3.0"); config.max_power = 100; config.max_packet_size_0 = 64;