feat: velocity profiles
This commit is contained in:
parent
ce0949ad4f
commit
66d04dacc2
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -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",
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "geode_piano"
|
||||
version = "0.2.1"
|
||||
version = "0.3.0"
|
||||
edition = "2021"
|
||||
license = "GPL-3.0-only"
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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>(
|
||||
|
@ -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;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user