feat: proper velocity curves

This commit is contained in:
dogeystamp 2024-04-21 18:40:53 -04:00
parent 444a86a4d4
commit 4fcc682406
Signed by: dogeystamp
GPG Key ID: 7225FE3592EFFA38
4 changed files with 362 additions and 182 deletions

View File

@ -47,235 +47,400 @@ async fn piano_task(pin_driver: pins::TransparentPins) {
// Notes for each key
let keymap = [
[
NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP,
],
[
NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP,
],
[
NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP,
],
[
NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP,
N1(GS5),
N1(AS5),
N1(C6),
NOP,
N1(F5),
NOP,
NOP,
N1(G5),
N1(A5),
N1(B5),
NOP,
NOP,
NOP,
NOP,
NOP,
N1(FS5),
],
[
NOP,
NOP,
NOP,
N(A0, 64),
N1(F1),
NOP,
N(CS1, 64),
N(B0, 64),
N1(A1),
N1(G1),
NOP,
NOP,
NOP,
N(DS1, 64),
N(E1, 64),
N(C1, 64),
N(D1, 64),
N(AS0, 64),
N1(B1),
N1(C2),
N1(GS1),
N1(AS1),
N1(FS1),
NOP,
],
[
NOP,
NOP,
NOP,
N(F1, 64),
N1(A0),
NOP,
N(A1, 64),
N(G1, 64),
N1(CS1),
N1(B0),
NOP,
NOP,
NOP,
N(B1, 64),
N(C2, 64),
N(GS1, 64),
N(AS1, 64),
N(FS1, 64),
NOP,
],
[
N(GS5, 64),
N(AS5, 64),
N(C6, 64),
NOP,
N(F5, 64),
NOP,
NOP,
N(G5, 64),
N(A5, 64),
N(B5, 64),
NOP,
NOP,
NOP,
NOP,
NOP,
N(FS5, 64),
],
[
N(C7, 64),
N(D7, 64),
N(E7, 64),
NOP,
N(A6, 64),
NOP,
NOP,
N(B6, 64),
N(CS7, 64),
N(DS7, 64),
NOP,
NOP,
NOP,
NOP,
NOP,
N(AS6, 64),
],
[
N(E6, 64),
N(FS6, 64),
N(GS6, 64),
NOP,
N(CS6, 64),
NOP,
NOP,
N(DS6, 64),
N(F6, 64),
N(G6, 64),
NOP,
NOP,
NOP,
NOP,
NOP,
N(D6, 64),
],
[
NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP,
],
[
NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP,
],
[
NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP,
],
[
NOP,
NOP,
NOP,
N(A2, 64),
NOP,
N(CS3, 64),
N(B2, 64),
NOP,
NOP,
NOP,
N(DS3, 64),
N(E3, 64),
N(C3, 64),
N(D3, 64),
N(AS2, 64),
NOP,
],
[
NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP,
],
[
NOP,
NOP,
NOP,
N(A4, 64),
NOP,
N(CS5, 64),
N(B4, 64),
NOP,
NOP,
NOP,
N(DS5, 64),
N(E5, 64),
N(C5, 64),
N(D5, 64),
N(AS4, 64),
N1(DS1),
N1(E1),
N1(C1),
N1(D1),
N1(AS0),
NOP,
],
[
NOP,
NOP,
NOP,
N(F3, 64),
N1(CS2),
NOP,
N(A3, 64),
N(G3, 64),
N1(F2),
N1(DS2),
NOP,
NOP,
NOP,
N(B3, 64),
N(C4, 64),
N(GS3, 64),
N(AS3, 64),
N(FS3, 64),
N1(G2),
N1(GS2),
N1(E2),
N1(FS2),
N1(D2),
NOP,
],
[
NOP,
NOP,
NOP,
N(CS4, 64),
N2(A0),
NOP,
N(F4, 64),
N(DS4, 64),
N2(CS1),
N2(B0),
NOP,
NOP,
NOP,
N(G4, 64),
N(GS4, 64),
N(E4, 64),
N(FS4, 64),
N(D4, 64),
N2(DS1),
N2(E1),
N2(C1),
N2(D1),
N2(AS0),
NOP,
],
[
NOP,
NOP,
NOP,
N(CS2, 64),
N2(F1),
NOP,
N(F2, 64),
N(DS2, 64),
N2(A1),
N2(G1),
NOP,
NOP,
NOP,
N(G2, 64),
N(GS2, 64),
N(E2, 64),
N(FS2, 64),
N(D2, 64),
N2(B1),
N2(C2),
N2(GS1),
N2(AS1),
N2(FS1),
NOP,
],
[
NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP,
N2(GS5),
N2(AS5),
N2(C6),
NOP,
N2(F5),
NOP,
NOP,
N2(G5),
N2(A5),
N2(B5),
NOP,
NOP,
NOP,
NOP,
NOP,
N2(FS5),
],
[
NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP,
N2(C7),
N2(D7),
N2(E7),
NOP,
N2(A6),
NOP,
NOP,
N2(B6),
N2(CS7),
N2(DS7),
NOP,
NOP,
NOP,
NOP,
NOP,
N2(AS6),
],
[
NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP,
N2(E6),
N2(FS6),
N2(GS6),
NOP,
N2(CS6),
NOP,
NOP,
N2(DS6),
N2(F6),
N2(G6),
NOP,
NOP,
NOP,
NOP,
NOP,
N2(D6),
],
[
N(GS7, 64),
N(AS7, 64),
N(C8, 64),
NOP,
N(F7, 64),
NOP,
NOP,
N(G7, 64),
N(A7, 64),
N(B7, 64),
NOP,
N1(A2),
NOP,
N1(CS3),
N1(B2),
NOP,
NOP,
NOP,
N1(DS3),
N1(E3),
N1(C3),
N1(D3),
N1(AS2),
NOP,
],
[
NOP,
NOP,
NOP,
N1(CS4),
NOP,
N1(F4),
N1(DS4),
NOP,
NOP,
NOP,
N1(G4),
N1(GS4),
N1(E4),
N1(FS4),
N1(D4),
NOP,
],
[
NOP,
NOP,
NOP,
N1(F3),
NOP,
N1(A3),
N1(G3),
NOP,
NOP,
NOP,
N1(B3),
N1(C4),
N1(GS3),
N1(AS3),
N1(FS3),
NOP,
],
[
NOP,
NOP,
NOP,
N2(A2),
NOP,
N2(CS3),
N2(B2),
NOP,
NOP,
NOP,
N2(DS3),
N2(E3),
N2(C3),
N2(D3),
N2(AS2),
NOP,
],
[
NOP,
NOP,
NOP,
N1(A4),
NOP,
N1(CS5),
N1(B4),
NOP,
NOP,
NOP,
N1(DS5),
N1(E5),
N1(C5),
N1(D5),
N1(AS4),
NOP,
],
[
NOP,
NOP,
NOP,
N2(A4),
NOP,
N2(CS5),
N2(B4),
NOP,
NOP,
NOP,
N2(DS5),
N2(E5),
N2(C5),
N2(D5),
N2(AS4),
NOP,
],
[
NOP,
NOP,
NOP,
N2(F3),
NOP,
N2(A3),
N2(G3),
NOP,
NOP,
NOP,
N2(B3),
N2(C4),
N2(GS3),
N2(AS3),
N2(FS3),
NOP,
],
[
NOP,
NOP,
NOP,
N2(CS4),
NOP,
N2(F4),
N2(DS4),
NOP,
NOP,
NOP,
N2(G4),
N2(GS4),
N2(E4),
N2(FS4),
N2(D4),
NOP,
],
[
NOP,
NOP,
NOP,
N2(CS2),
NOP,
N2(F2),
N2(DS2),
NOP,
NOP,
NOP,
N2(G2),
N2(GS2),
N2(E2),
N2(FS2),
N2(D2),
NOP,
],
[
N1(E6),
N1(FS6),
N1(GS6),
NOP,
N1(CS6),
NOP,
NOP,
N1(DS6),
N1(F6),
N1(G6),
NOP,
NOP,
NOP,
NOP,
NOP,
N(FS7, 64),
N1(D6),
],
[
N1(C7),
N1(D7),
N1(E7),
NOP,
N1(A6),
NOP,
NOP,
N1(B6),
N1(CS7),
N1(DS7),
NOP,
NOP,
NOP,
NOP,
NOP,
N1(AS6),
],
[
N1(GS7),
N1(AS7),
N1(C8),
NOP,
N1(F7),
NOP,
NOP,
N1(G7),
N1(A7),
N1(B7),
NOP,
NOP,
NOP,
NOP,
NOP,
N1(FS7),
],
[
N2(GS7),
N2(AS7),
N2(C8),
NOP,
N2(F7),
NOP,
NOP,
N2(G7),
N2(A7),
N2(B7),
NOP,
NOP,
NOP,
NOP,
NOP,
N2(FS7),
],
];

View File

@ -3,7 +3,7 @@
use crate::midi;
use crate::pins;
use crate::unwrap;
use core::cmp::min;
use core::cmp::{min};
use embassy_rp::gpio;
use embassy_time::{Duration, Instant, Ticker};
@ -56,7 +56,7 @@ impl<const N_ROWS: usize, const N_COLS: usize> KeyMatrix<N_ROWS, N_COLS> {
// scan frequency
// this might(?) panic if the scan takes longer than the tick
let mut ticker = Ticker::every(Duration::from_millis(13));
let mut ticker = Ticker::every(Duration::from_millis(8));
let chan = midi::MidiChannel::new(0);
const MAX_NOTES: usize = 128;
@ -66,7 +66,14 @@ impl<const N_ROWS: usize, const N_COLS: usize> KeyMatrix<N_ROWS, N_COLS> {
// (for velocity detection) moment key is first touched
let mut note_first: [Option<Instant>; MAX_NOTES] = [None; MAX_NOTES];
let mut counter = 0;
loop {
counter += 1;
counter %= 50;
let profile = counter == 0;
let prof_start = Instant::now();
for (i, col) in self.col_pins.iter().enumerate() {
unwrap(pin_driver.set_output(*col)).await;
let input = unwrap(pin_driver.read_all()).await;
@ -79,11 +86,12 @@ impl<const N_ROWS: usize, const N_COLS: usize> KeyMatrix<N_ROWS, N_COLS> {
let key_active = mask & (1 << row) != 0;
match key_action {
midi::KeyAction::N1(note) => {
if !note_on[note as usize]
&& note_first[note as usize].is_none()
&& key_active
{
note_first[note as usize] = Some(Instant::now());
if key_active {
if note_first[note as usize].is_none() {
note_first[note as usize] = Some(Instant::now());
}
} else if note_first[note as usize].is_some() {
note_first[note as usize] = None;
}
}
midi::KeyAction::N2(note) => {
@ -92,15 +100,17 @@ impl<const N_ROWS: usize, const N_COLS: usize> KeyMatrix<N_ROWS, N_COLS> {
// millisecond duration of keypress
let dur =
note_first[note as usize].unwrap().elapsed().as_millis();
// 1905 millis is the minimum velocity
let velocity: u8 = 127 - min(dur / 15, 127) as u8;
note_first[note as usize] = None;
let velocity: u8 = if dur <= 80 {
(127 - dur) as u8
} else {
(127 - min(dur, 250) / 5 - 70) as u8
};
log::debug!("{note:?} velocity {velocity} from dur {dur}ms");
note_on[note as usize] = true;
chan.note_on(note, velocity).await;
}
} else if note_on[note as usize] {
note_on[note as usize] = false;
note_first[note as usize] = None;
chan.note_off(note, 0).await;
}
}
@ -114,11 +124,15 @@ impl<const N_ROWS: usize, const N_COLS: usize> KeyMatrix<N_ROWS, N_COLS> {
note_on[note as usize] = false;
chan.note_off(note, 0).await;
}
},
midi::KeyAction::NOP => {},
}
midi::KeyAction::NOP => {}
}
}
}
if profile {
log::trace!("profile: scan took {}ms", prof_start.elapsed().as_millis())
}
ticker.next().await;
}
}

View File

@ -12,6 +12,7 @@ Use cargo fmt to de-messify the output once pasted in the source.
"""
from dataclasses import dataclass
import sys
@dataclass
@ -62,7 +63,7 @@ for col in mat:
print(f"[{', '.join(col)}],")
print("]")
print(f"{len(row_pins)} rows, {len(col_pins)} cols")
print(f"row pins: [{', '.join([str(i) for i in row_pins])}]")
print(f"col pins: [{', '.join([str(i) for i in col_pins])}]")
print(f"{empty_counter} empty cells")
print(f"{len(row_pins)} rows, {len(col_pins)} cols", file=sys.stderr)
print(f"row pins: [{', '.join([str(i) for i in row_pins])}]", file=sys.stderr)
print(f"col pins: [{', '.join([str(i) for i in col_pins])}]", file=sys.stderr)
print(f"{empty_counter} empty cells", file=sys.stderr)

View File

@ -86,7 +86,7 @@ impl MidiMsg {
/// Note identifiers
///
/// See src/midi/note_def.py for how this is generated
#[derive(Clone, Copy)]
#[derive(Clone, Copy, Debug)]
pub enum Note {
A0 = 21,
AS0 = 22,