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]]
|
[[package]]
|
||||||
name = "geode_piano"
|
name = "geode_piano"
|
||||||
version = "0.2.1"
|
version = "0.3.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"byte-slice-cast 1.2.2",
|
"byte-slice-cast 1.2.2",
|
||||||
"cortex-m",
|
"cortex-m",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "geode_piano"
|
name = "geode_piano"
|
||||||
version = "0.2.1"
|
version = "0.3.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "GPL-3.0-only"
|
license = "GPL-3.0-only"
|
||||||
|
|
||||||
|
@ -44,7 +44,7 @@ but is harder to set up.
|
|||||||
|
|
||||||
### with debug probe
|
### 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.
|
- 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.
|
You only need to wire GND, SWCLK and SWDIO.
|
||||||
- If you are using a second Pico as a debug probe,
|
- 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::peripherals::USB;
|
||||||
use embassy_rp::usb::{Driver, InterruptHandler};
|
use embassy_rp::usb::{Driver, InterruptHandler};
|
||||||
use geode_piano::matrix;
|
use geode_piano::matrix;
|
||||||
use geode_piano::matrix::KeyMatrix;
|
use geode_piano::matrix::{KeyMatrix, VelocityProfile};
|
||||||
use geode_piano::midi;
|
use geode_piano::midi;
|
||||||
use geode_piano::usb::usb_task;
|
use geode_piano::usb::usb_task;
|
||||||
use geode_piano::{blinky, pin_array, pins, unwrap};
|
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);
|
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 {
|
bind_interrupts!(struct Irqs {
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
use crate::midi;
|
use crate::midi;
|
||||||
use crate::pins;
|
use crate::pins;
|
||||||
use crate::unwrap;
|
use crate::unwrap;
|
||||||
use core::cmp::min;
|
use core::cmp::{max, min};
|
||||||
use embassy_rp::gpio;
|
use embassy_rp::gpio;
|
||||||
use embassy_time::{Duration, Instant, Ticker};
|
use embassy_time::{Duration, Instant, Ticker};
|
||||||
|
|
||||||
@ -14,6 +14,38 @@ pub enum NormalState {
|
|||||||
NC,
|
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
|
/// Task to handle pedals in MIDI
|
||||||
///
|
///
|
||||||
/// `norm_open` represents a normally open switch
|
/// `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 {
|
for i in pin_driver.pins {
|
||||||
unwrap(pin_driver.set_input(i)).await;
|
unwrap(pin_driver.set_input(i)).await;
|
||||||
unwrap(pin_driver.set_pull(i, gpio::Pull::Up)).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 {
|
loop {
|
||||||
let profile: bool = counter == 0;
|
let profile: bool = counter == 0;
|
||||||
counter += 1;
|
counter += 1;
|
||||||
counter %= 500;
|
counter %= 5000;
|
||||||
let prof_start = Instant::now();
|
let prof_start = Instant::now();
|
||||||
let mut prof_time_last_col = prof_start;
|
let mut prof_time_last_col = prof_start;
|
||||||
let mut prof_dur_col = Duration::from_ticks(0);
|
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) => {
|
midi::KeyAction::N2(note) => {
|
||||||
if key_active {
|
if key_active {
|
||||||
if note_first[note as usize].is_some() && !note_on[note as usize] {
|
if note_first[note as usize].is_some() && !note_on[note as usize] {
|
||||||
// millisecond duration of keypress
|
// microsecond duration of keypress
|
||||||
let dur =
|
let dur =
|
||||||
note_first[note as usize].unwrap().elapsed().as_millis();
|
note_first[note as usize].unwrap().elapsed().as_micros();
|
||||||
let velocity: u8 = if dur <= 60 {
|
let velocity = match config.velocity_prof {
|
||||||
(127 - dur * 6 / 5) as u8
|
VelocityProfile::Heavy => velocity_heavy(dur),
|
||||||
} else {
|
VelocityProfile::Linear => velocity_linear(dur),
|
||||||
(127 - min(dur, 240) / 4 - 60) as u8
|
VelocityProfile::Light => velocity_light(dur),
|
||||||
};
|
};
|
||||||
defmt::debug!(
|
defmt::debug!(
|
||||||
"{} velocity {} from dur {}ms",
|
"{} velocity {} from dur {}us",
|
||||||
note,
|
note,
|
||||||
velocity,
|
velocity,
|
||||||
dur
|
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
|
/// Handle sending MIDI until connection breaks
|
||||||
pub async fn midi_session<'d, T: Instance + 'd>(
|
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);
|
let mut config = Config::new(0xdead, 0xbeef);
|
||||||
config.manufacturer = Some("dogeystamp");
|
config.manufacturer = Some("dogeystamp");
|
||||||
config.product = Some("Geode-Piano MIDI keyboard");
|
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_power = 100;
|
||||||
config.max_packet_size_0 = 64;
|
config.max_packet_size_0 = 64;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user