feat: velocity profiles

This commit is contained in:
dogeystamp 2024-11-10 18:56:08 -05:00
parent ce0949ad4f
commit 66d04dacc2
7 changed files with 55 additions and 17 deletions

2
Cargo.lock generated
View File

@ -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",

View File

@ -1,6 +1,6 @@
[package]
name = "geode_piano"
version = "0.2.1"
version = "0.3.0"
edition = "2021"
license = "GPL-3.0-only"

View File

@ -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,

View File

@ -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 {

View File

@ -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<const N_ROWS: usize, const N_COLS: usize> KeyMatrix<N_ROWS, N_COLS> {
}
}
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<const N_ROWS: usize, const N_COLS: usize> KeyMatrix<N_ROWS, N_COLS> {
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<const N_ROWS: usize, const N_COLS: usize> KeyMatrix<N_ROWS, N_COLS> {
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

View File

@ -213,7 +213,7 @@ impl From<EndpointError> for Disconnected {
}
}
static MIDI_QUEUE: Channel<ThreadModeRawMutex, MidiMsg, 3> = Channel::new();
static MIDI_QUEUE: Channel<ThreadModeRawMutex, MidiMsg, 10> = Channel::new();
/// Handle sending MIDI until connection breaks
pub async fn midi_session<'d, T: Instance + 'd>(

View File

@ -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;