Compare commits

...

4 Commits

Author SHA1 Message Date
ea70d75837
feat: sustain pedal 2024-04-21 21:23:14 -04:00
4fcc682406
feat: proper velocity curves 2024-04-21 21:23:00 -04:00
444a86a4d4
feat: basic piano functionality complete
piano is janky but can play sounds now
2024-04-21 16:58:35 -04:00
574105de7b
feat: more piano features
- basic velocity detection
- pedal
2024-04-21 16:57:40 -04:00
10 changed files with 752 additions and 110 deletions

2
Cargo.lock generated
View File

@ -771,7 +771,7 @@ dependencies = [
]
[[package]]
name = "geode-piano"
name = "geode_piano"
version = "0.1.0"
dependencies = [
"byte-slice-cast 1.2.2",

View File

@ -1,5 +1,5 @@
[package]
name = "geode-piano"
name = "geode_piano"
version = "0.1.0"
edition = "2021"
license = "GPL-3.0-only"

View File

@ -2,15 +2,17 @@
Digital piano firmware for the Raspberry Pi Pico.
This project only attempts to expose the keyboard as a MIDI device.
The purpose is to revive digital pianos that have working keys, but faulty electronics.
## installation
- Clone project.
- Go into project directory.
- Install the `thumbv6m-none-eabi` target using rustup.
- Install `elf2uf2-rs`.
- Follow the materials and wiring sections below.
- Set the Pico into BOOTSEL mode:
- Hold down the BOOTSEL button on the Pico. Keep holding it during the following steps.
- Hold down the BOOTSEL button on the Pico. Keep holding it during the following step.
- Reset the Pico: either replug the power, or short Pin 30 (RUN) to GND through a button or wire.
- Mount the Pico's storage on your device.
- `cargo run --release --bin [binary]`
@ -18,18 +20,48 @@ This project only attempts to expose the keyboard as a MIDI device.
If you are missing dependencies, consult [Alex Wilson's guide](https://www.alexdwilson.dev/learning-in-public/how-to-program-a-raspberry-pi-pico) on Rust Pico development.
## usage
The intended usage is to first plug the device into the piano keyboard, then use the `pin_scanner` binary to
scan the key-matrix. (See the next sections on how to wire it up.)
On every key, press it down half-way and then fully and note the pins connections detected at each level.
These correspond to the [`midi::KeyAction::N1`] and [`midi::KeyAction::N2`] actions respectively.
There should be two switches per key for velocity detection.
If there isn't, then the key is an [`midi::KeyAction::N`] (it will be stuck at a fixed velocity).
Put the connections in a spreadsheet and reorganize it so that GND pins are column headers, and the Input pins are row headers.
This will comprise the keymap.
The keymap is a an array with the same dimensions as the spreadsheet grid.
This is comprised of N1, N2, and N entries, indicating which note a key corresponds to.
Once the keymap is done, run the `piano_firmware` binary and plug the USB cable to your computer.
Open up a DAW and select Geode-Piano as a MIDI input device.
I use LMMS with the [Maestro Concert Grand v2](https://www.linuxsampler.org/instruments.html) samples.
You should be able to play now.
## materials
- 1 Raspberry Pi Pico (preferably with pre-soldered headers)
- 2 MCP23017 I/O extender chips, DIP format
- 2 pull-up resistors for I²C (1-10kΩ), these are optional but recommended
- 2 pull-up resistors for I²C (1-10kΩ), these are optional but [recommended](https://www.joshmcguigan.com/blog/internal-pull-up-resistor-i2c/)
- 1 USB to Micro-USB cable with data transfer
- Many jumper cables
- Ribbon cable sockets. The following is for my own piano, yours might be different:
- 18-pin 1.25mm pitch FFC connector
- 22-pin 1.25mm pitch FFC connector
- Many jumper cables (40 male-to-female, ? male-to-male)
- Two alligator clips
- Breadboard
For the ribbon cable sockets, open up your piano and find the ribbon cables.
Unplug them from the PCB, and count the amount of pins on them.
Also, measure the distance between each pin,
or the distance between the first and last pin.
This will help you find the right pin pitch and pin count.
Usually, these measurements can be found on the datasheets for FFC sockets.
## wiring
**Ensure all wires are well plugged in every time you use this circuit.**
**Ensure all wires, especially GND and power wires, are well plugged in every time you use this circuit.**
### rails
@ -54,3 +86,46 @@ For both MCP23017s:
- MCP A should be 0x20 (GND, GND, GND), MCP B 0x27 (3V3, 3V3, 3V3)
- MCP VDD -> power rail
- MCP VSS -> GND rail
### ribbon cables
Connect the following pins to the ribbon cable sockets in any order (use more or less pins depending on how many you need):
- GP15
- GP14
- GP13
- GP12
- GP11
- GP10
- GP9
- GP18
- GP19
- GP20
- GP21
- GP22
- All MCP GPIO pins except GPB7 and GPA7 on both chips (see [datasheet](https://ww1.microchip.com/downloads/aemDocuments/documents/APID/ProductDocuments/DataSheets/MCP23017-Data-Sheet-DS20001952.pdf) for diagram of pins)
GPB7 and GPA7 have known issues and therefore can not be inputs.
Again, refer to the datasheet about this.
It is simpler to exclude them instead of working around that limitation.
I used male-to-female jumpers with the female end trimmed to reveal the metal part inside.
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.
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.
## sustain pedal
Using jumper wires and alligator clips, wire the Tip of the pedal's TRS jack into the GND rail.
Then, wire the Ring (middle metal part, surrounded by two black bands), into the pedal pin (by default GP8).
To attach the alligator clips to the [TRS jack](https://en.m.wikipedia.org/wiki/Phone_connector_(audio)#Design), you can strip the outer layer of a paperclip and wrap the metallic part around the jack.
Because the sustain pedal is normally-closed, failure to wire this appropriately could result in the sustain pedal being constantly on.
To disable the sustain pedal, comment out the `pedal_task` in `src/bin/piano_firmware.rs`.

View File

@ -1,84 +0,0 @@
/*
geode-piano
Copyright (C) 2024 dogeystamp <dogeystamp@disroot.org>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
//! Main firmware for geode-piano. Reads key-matrix and sends MIDI output.
#![no_std]
#![no_main]
#![deny(rust_2018_idioms)]
use embassy_executor::Spawner;
use embassy_rp::bind_interrupts;
use embassy_rp::i2c;
use embassy_rp::peripherals::USB;
use embassy_rp::usb::{Driver, InterruptHandler};
use geode_piano::usb::usb_task;
use geode_piano::matrix::KeyMatrix;
use geode_piano::{blinky, pin_array, pins, unwrap};
#[embassy_executor::task]
async fn piano_task(pin_driver: pins::TransparentPins) {
use geode_piano::midi::Note::*;
// GND pins
let col_pins = [23];
// Input pins
let row_pins = [20, 15, 4];
// Notes for each key
let keymap = [[C4, D4, E4]];
let mut mat = KeyMatrix::new(col_pins, row_pins, keymap);
mat.scan(pin_driver).await;
}
bind_interrupts!(struct Irqs {
USBCTRL_IRQ => InterruptHandler<USB>;
});
#[embassy_executor::main]
async fn main(_spawner: Spawner) {
let p = embassy_rp::init(Default::default());
let driver = Driver::new(p.USB, Irqs);
unwrap(_spawner.spawn(usb_task(driver, log::LevelFilter::Debug))).await;
unwrap(_spawner.spawn(blinky::blink_task(p.PIN_25.into()))).await;
log::debug!("main: init i2c");
let sda = p.PIN_16;
let scl = p.PIN_17;
let mut i2c_config = i2c::Config::default();
let freq = 100_000;
i2c_config.frequency = freq;
let i2c = i2c::I2c::new_blocking(p.I2C0, scl, sda, i2c_config);
log::debug!("main: starting transparent pin driver");
let pin_driver = unwrap(pins::TransparentPins::new(
i2c,
[0x20, 0x27],
pin_array!(
p.PIN_15, p.PIN_14, p.PIN_13, p.PIN_12, p.PIN_11, p.PIN_10, p.PIN_9, p.PIN_18,
p.PIN_19, p.PIN_20, p.PIN_21, p.PIN_22
),
true,
))
.await;
log::info!("main: starting piano task");
_spawner.spawn(piano_task(pin_driver)).unwrap();
}

495
src/bin/piano_firmware.rs Normal file
View File

@ -0,0 +1,495 @@
/*
geode-piano
Copyright (C) 2024 dogeystamp <dogeystamp@disroot.org>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
//! Main firmware for geode-piano. Reads key-matrix and sends MIDI output.
#![no_std]
#![no_main]
#![deny(rust_2018_idioms)]
use embassy_executor::Spawner;
use embassy_rp::bind_interrupts;
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::midi;
use geode_piano::usb::usb_task;
use geode_piano::{blinky, pin_array, pins, unwrap};
#[embassy_executor::task]
async fn piano_task(pin_driver: pins::TransparentPins) {
use geode_piano::midi::KeyAction::*;
use geode_piano::midi::Note::*;
// GND pins
let col_pins = [32, 33, 34, 4, 36, 6, 7, 37, 38, 39, 15, 19, 24, 25, 26, 31];
// Input pins
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(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,
N1(F1),
NOP,
N1(A1),
N1(G1),
NOP,
NOP,
NOP,
N1(B1),
N1(C2),
N1(GS1),
N1(AS1),
N1(FS1),
NOP,
],
[
NOP,
NOP,
NOP,
N1(A0),
NOP,
N1(CS1),
N1(B0),
NOP,
NOP,
NOP,
N1(DS1),
N1(E1),
N1(C1),
N1(D1),
N1(AS0),
NOP,
],
[
NOP,
NOP,
NOP,
N1(CS2),
NOP,
N1(F2),
N1(DS2),
NOP,
NOP,
NOP,
N1(G2),
N1(GS2),
N1(E2),
N1(FS2),
N1(D2),
NOP,
],
[
NOP,
NOP,
NOP,
N2(A0),
NOP,
N2(CS1),
N2(B0),
NOP,
NOP,
NOP,
N2(DS1),
N2(E1),
N2(C1),
N2(D1),
N2(AS0),
NOP,
],
[
NOP,
NOP,
NOP,
N2(F1),
NOP,
N2(A1),
N2(G1),
NOP,
NOP,
NOP,
N2(B1),
N2(C2),
N2(GS1),
N2(AS1),
N2(FS1),
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),
],
[
N2(C7),
N2(D7),
N2(E7),
NOP,
N2(A6),
NOP,
NOP,
N2(B6),
N2(CS7),
N2(DS7),
NOP,
NOP,
NOP,
NOP,
NOP,
N2(AS6),
],
[
N2(E6),
N2(FS6),
N2(GS6),
NOP,
N2(CS6),
NOP,
NOP,
N2(DS6),
N2(F6),
N2(G6),
NOP,
NOP,
NOP,
NOP,
NOP,
N2(D6),
],
[
NOP,
NOP,
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,
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),
],
];
let mut mat = KeyMatrix::new(col_pins, row_pins, keymap);
mat.scan(pin_driver).await;
}
bind_interrupts!(struct Irqs {
USBCTRL_IRQ => InterruptHandler<USB>;
});
#[embassy_executor::main]
async fn main(_spawner: Spawner) {
let p = embassy_rp::init(Default::default());
let driver = Driver::new(p.USB, Irqs);
unwrap(_spawner.spawn(usb_task(driver, log::LevelFilter::Debug))).await;
unwrap(_spawner.spawn(blinky::blink_task(p.PIN_25.into()))).await;
log::debug!("main: init i2c");
let sda = p.PIN_16;
let scl = p.PIN_17;
let mut i2c_config = i2c::Config::default();
let freq = 400_000;
i2c_config.frequency = freq;
let i2c = i2c::I2c::new_blocking(p.I2C0, scl, sda, i2c_config);
log::debug!("main: starting transparent pin driver");
let pin_driver = unwrap(pins::TransparentPins::new(
i2c,
[0x20, 0x27],
pin_array!(
p.PIN_15, p.PIN_14, p.PIN_13, p.PIN_12, p.PIN_11, p.PIN_10, p.PIN_9, p.PIN_18,
p.PIN_19, p.PIN_20, p.PIN_21, p.PIN_22
),
true,
))
.await;
log::info!("main: starting piano task");
_spawner.spawn(piano_task(pin_driver)).unwrap();
log::info!("main: starting sustain pedal task");
_spawner
.spawn(matrix::pedal(
midi::Controller::SustainPedal,
p.PIN_8.into(),
false,
))
.unwrap();
}

View File

@ -1,5 +1,4 @@
#![doc = include_str!("../README.md")]
#![no_std]
#![no_main]
#![deny(rust_2018_idioms)]
@ -9,10 +8,10 @@ use embassy_time::Timer;
use {defmt_rtt as _, panic_probe as _};
pub mod blinky;
pub mod matrix;
pub mod midi;
pub mod pins;
pub mod usb;
pub mod matrix;
/// Unwrap, but log before panic
///

View File

@ -1,10 +1,30 @@
//! Key matrix scanner
//! Key matrix scanner + other interfacing utilities
use crate::pins;
use crate::midi;
use crate::pins;
use crate::unwrap;
use core::cmp::min;
use embassy_rp::gpio;
use embassy_time::{Duration, Ticker};
use embassy_time::{Duration, Instant, Ticker};
/// Task to handle pedals in MIDI
///
/// `norm_open` represents a normally open switch
#[embassy_executor::task]
pub async fn pedal(pedal: midi::Controller, pin: gpio::AnyPin, norm_open: bool) {
let mut inp = gpio::Input::new(pin, gpio::Pull::Up);
let chan = midi::MidiChannel::new(0);
loop {
let on_val = if norm_open { 64 } else { 0 };
let off_val = if norm_open { 0 } else { 64 };
inp.wait_for_low().await;
chan.controller(pedal, on_val).await;
log::debug!("{pedal:?} set to {on_val}");
inp.wait_for_high().await;
chan.controller(pedal, off_val).await;
log::debug!("{pedal:?} set to {off_val}");
}
}
/// Key matrix for the piano.
pub struct KeyMatrix<const N_ROWS: usize, const N_COLS: usize> {
@ -12,7 +32,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::Note; 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> {
@ -25,7 +45,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::Note; N_ROWS]; N_COLS],
keymap: [[midi::KeyAction; N_COLS]; N_ROWS],
) -> Self {
KeyMatrix {
col_pins,
@ -39,11 +59,27 @@ impl<const N_ROWS: usize, const N_COLS: usize> KeyMatrix<N_ROWS, N_COLS> {
unwrap(pin_driver.set_input(i)).await;
unwrap(pin_driver.set_pull(i, gpio::Pull::Up)).await;
}
let mut ticker = Ticker::every(Duration::from_millis(10));
// scan frequency
// this might(?) panic if the scan takes longer than the tick
let mut ticker = Ticker::every(Duration::from_millis(8));
let chan = midi::MidiChannel::new(0);
let mut note_on = [false; 128];
const MAX_NOTES: usize = 128;
// is note currently on
let mut note_on = [false; MAX_NOTES];
// (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;
@ -52,18 +88,57 @@ 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 note = self.keymap[i][j];
if mask & (1 << row) != 0 {
if !note_on[note as usize] {
note_on[note as usize] = true;
chan.note_on(note, 40).await;
let key_action = self.keymap[j][i];
let key_active = mask & (1 << row) != 0;
match key_action {
midi::KeyAction::N1(note) => {
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;
}
}
} else if note_on[note as usize] {
note_on[note as usize] = false;
chan.note_off(note, 0).await;
midi::KeyAction::N2(note) => {
if key_active {
if note_first[note as usize].is_some() && !note_on[note as usize] {
// millisecond duration of keypress
let dur =
note_first[note as usize].unwrap().elapsed().as_millis();
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;
chan.note_off(note, 0).await;
}
}
midi::KeyAction::N(note, velocity) => {
if key_active {
if !note_on[note as usize] {
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;
chan.note_off(note, 0).await;
}
}
midi::KeyAction::NOP => {}
}
}
}
if profile {
log::trace!("profile: scan took {}ms", prof_start.elapsed().as_millis())
}
ticker.next().await;
}
}

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

@ -0,0 +1,69 @@
#!/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
import sys
@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", 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

@ -42,7 +42,7 @@ impl NoteMsg {
}
}
#[derive(Copy, Clone)]
#[derive(Copy, Clone, Debug)]
pub enum Controller {
SustainPedal = 64,
}
@ -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,
@ -189,6 +189,18 @@ pub enum Note {
B8 = 119,
}
#[derive(Clone, Copy)]
pub enum KeyAction {
/// Switch that is first triggered when pressing a key.
N1(Note),
/// Switch triggered when key bottoms out.
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 {}
impl From<EndpointError> for Disconnected {

View File

@ -1,5 +1,6 @@
//! Handle all USB communcation in this task.
//! If USB is handled in multiple tasks the code gets weird and unwieldy (`'static` everywhere)
//!
//! If USB is handled in multiple tasks the code gets weird and unwieldy (`'static` everywhere).
//! Code in this file is mostly from the examples folder in embassy-rs.
/*