feat: basic piano functionality complete

piano is janky but can play sounds now
This commit is contained in:
dogeystamp 2024-04-21 11:41:16 -04:00
parent 574105de7b
commit 444a86a4d4
Signed by: dogeystamp
GPG Key ID: 7225FE3592EFFA38
5 changed files with 321 additions and 9 deletions

View File

@ -112,4 +112,10 @@ I used male-to-female jumpers with the female end trimmed to reveal the metal pa
This was necessary to attach to the short pins on the jumpers.
The opening in the plastic on the female end should face inwards when connected to the sockets.
Then, plug the ribbon cables from your piano into the sockets.
Plugging this many jumper cables into a single socket can cause [crosstalk](https://en.m.wikipedia.org/wiki/Crosstalk).
Twisting cables and spacing them from each other may help prevent this.
To test for crosstalk, run `pin_scanner` and connect the socket contacts with each other with a jumper cable.
Each scan should return exactly two connections, these being the forward and reverse connection over the jumper cable.
If there are more connections, that means there is crosstalk.
Once the wiring is done, plug the ribbon cables from your piano into the sockets.

View File

@ -39,11 +39,246 @@ async fn piano_task(pin_driver: pins::TransparentPins) {
use geode_piano::midi::Note::*;
// GND pins
let col_pins = [23, 24];
let col_pins = [32, 33, 34, 4, 36, 6, 7, 37, 38, 39, 15, 19, 24, 25, 26, 31];
// Input pins
let row_pins = [20, 15, 4];
let row_pins = [
1, 2, 3, 5, 8, 9, 10, 12, 13, 14, 16, 17, 18, 20, 21, 22, 23, 27, 28, 29, 30, 35,
];
// Notes for each key
let keymap = [[N1(C4), N1(D4), N1(E4)], [N1(C4), N2(D4), N2(E4)]];
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,
],
[
NOP,
NOP,
NOP,
N(A0, 64),
NOP,
N(CS1, 64),
N(B0, 64),
NOP,
NOP,
NOP,
N(DS1, 64),
N(E1, 64),
N(C1, 64),
N(D1, 64),
N(AS0, 64),
NOP,
],
[
NOP,
NOP,
NOP,
N(F1, 64),
NOP,
N(A1, 64),
N(G1, 64),
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),
NOP,
],
[
NOP,
NOP,
NOP,
N(F3, 64),
NOP,
N(A3, 64),
N(G3, 64),
NOP,
NOP,
NOP,
N(B3, 64),
N(C4, 64),
N(GS3, 64),
N(AS3, 64),
N(FS3, 64),
NOP,
],
[
NOP,
NOP,
NOP,
N(CS4, 64),
NOP,
N(F4, 64),
N(DS4, 64),
NOP,
NOP,
NOP,
N(G4, 64),
N(GS4, 64),
N(E4, 64),
N(FS4, 64),
N(D4, 64),
NOP,
],
[
NOP,
NOP,
NOP,
N(CS2, 64),
NOP,
N(F2, 64),
N(DS2, 64),
NOP,
NOP,
NOP,
N(G2, 64),
N(GS2, 64),
N(E2, 64),
N(FS2, 64),
N(D2, 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,
],
[
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,
NOP,
NOP,
NOP,
NOP,
N(FS7, 64),
],
];
let mut mat = KeyMatrix::new(col_pins, row_pins, keymap);
mat.scan(pin_driver).await;
}

View File

@ -26,7 +26,7 @@ pub struct KeyMatrix<const N_ROWS: usize, const N_COLS: usize> {
col_pins: [u8; N_COLS],
/// Input pins at the left of each row
row_pins: [u8; N_ROWS],
keymap: [[midi::KeyAction; N_ROWS]; N_COLS],
keymap: [[midi::KeyAction; N_COLS]; N_ROWS],
}
impl<const N_ROWS: usize, const N_COLS: usize> KeyMatrix<N_ROWS, N_COLS> {
@ -39,7 +39,7 @@ impl<const N_ROWS: usize, const N_COLS: usize> KeyMatrix<N_ROWS, N_COLS> {
pub fn new(
col_pins: [u8; N_COLS],
row_pins: [u8; N_ROWS],
keymap: [[midi::KeyAction; N_ROWS]; N_COLS],
keymap: [[midi::KeyAction; N_COLS]; N_ROWS],
) -> Self {
KeyMatrix {
col_pins,
@ -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(2));
let mut ticker = Ticker::every(Duration::from_millis(13));
let chan = midi::MidiChannel::new(0);
const MAX_NOTES: usize = 128;
@ -75,7 +75,7 @@ impl<const N_ROWS: usize, const N_COLS: usize> KeyMatrix<N_ROWS, N_COLS> {
// values that are logical ON
let mask = input ^ (((1 << pin_driver.n_usable_pins()) - 1) ^ (1 << col));
for (j, row) in self.row_pins.iter().enumerate() {
let key_action = self.keymap[i][j];
let key_action = self.keymap[j][i];
let key_active = mask & (1 << row) != 0;
match key_action {
midi::KeyAction::N1(note) => {
@ -114,7 +114,8 @@ 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 => {},
}
}
}

68
src/midi/keymap.py Normal file
View File

@ -0,0 +1,68 @@
#!/usr/bin/env python3
"""
Helper to generate keymaps.
Takes in data through stdin with this format:
[note name] [GND pin]
[n1 input pin]
[n2 input pin]
Use cargo fmt to de-messify the output once pasted in the source.
"""
from dataclasses import dataclass
@dataclass
class Note:
note_name: str
gnd_pin: int
n1_pin: int
n2_pin: int
data: list[Note] = []
col_dedup: set[int] = set()
row_dedup: set[int] = set()
for i in range(88):
note_name, gnd_pin = input().split()
gnd_pin = int(gnd_pin)
n1_pin = int(input())
n2_pin = int(input())
data.append(Note(note_name, gnd_pin, n1_pin, n2_pin))
col_dedup.add(gnd_pin)
row_dedup.add(n1_pin)
row_dedup.add(n2_pin)
col_pins = list(col_dedup)
row_pins = list(row_dedup)
# [row][column]
mat = [["" for _ in range(len(col_pins))] for _ in range(len(row_pins))]
for d in data:
# this is inefficient but whatever
mat[row_pins.index(d.n1_pin)][col_pins.index(d.gnd_pin)] = f"N1({d.note_name})"
mat[row_pins.index(d.n1_pin)][col_pins.index(d.gnd_pin)] = f"NOP"
mat[row_pins.index(d.n2_pin)][col_pins.index(d.gnd_pin)] = f"N({d.note_name}, 64)"
empty_counter = 0
for i in range(len(mat)):
for j in range(len(mat[i])):
if mat[i][j] == "":
mat[i][j] = "NOP"
empty_counter += 1
print("[")
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")

View File

@ -197,6 +197,8 @@ pub enum KeyAction {
N2(Note),
/// Basic switch with fixed velocity. Be careful not to mix with actions with velocity detection.
N(Note, u8),
/// NOP
NOP,
}
pub struct Disconnected {}