Compare commits
5 Commits
38e36b7bd5
...
676040a93e
Author | SHA1 | Date | |
---|---|---|---|
676040a93e | |||
6dabe79aa5 | |||
70526c44eb | |||
be6240ba3c | |||
6be52230ce |
@ -16,6 +16,8 @@ This project only attempts to expose the keyboard as a MIDI device.
|
|||||||
- `cargo run --release --bin [binary]`
|
- `cargo run --release --bin [binary]`
|
||||||
- `[binary]` can be any binary under `src/bin/`. Run `cargo run --bin` to list them.
|
- `[binary]` can be any binary under `src/bin/`. Run `cargo run --bin` to list them.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
## materials
|
## materials
|
||||||
|
|
||||||
- 1 Raspberry Pi Pico (preferably with pre-soldered headers)
|
- 1 Raspberry Pi Pico (preferably with pre-soldered headers)
|
||||||
@ -27,6 +29,8 @@ This project only attempts to expose the keyboard as a MIDI device.
|
|||||||
|
|
||||||
## wiring
|
## wiring
|
||||||
|
|
||||||
|
**Ensure all wires are well plugged in every time you use this circuit.**
|
||||||
|
|
||||||
### rails
|
### rails
|
||||||
|
|
||||||
- Pin 3 -> GND rail
|
- Pin 3 -> GND rail
|
||||||
@ -38,6 +42,8 @@ Let's call the closest MCP23017 chip to the Pico MCP A, and the further one MCP
|
|||||||
|
|
||||||
- GP16 -> MCP A SDA
|
- GP16 -> MCP A SDA
|
||||||
- GP17 -> MCP A SCL
|
- GP17 -> MCP A SCL
|
||||||
|
- MCP A SDA -> MCP B SDA
|
||||||
|
- MCP A SCL -> MCP B SCL
|
||||||
- Pull-up resistor from GP16 to power rail
|
- Pull-up resistor from GP16 to power rail
|
||||||
- Pull-up resistor from GP17 to power rail
|
- Pull-up resistor from GP17 to power rail
|
||||||
|
|
||||||
@ -48,5 +54,3 @@ For both MCP23017s:
|
|||||||
- MCP A should be 0x20 (GND, GND, GND), MCP B 0x27 (3V3, 3V3, 3V3)
|
- MCP A should be 0x20 (GND, GND, GND), MCP B 0x27 (3V3, 3V3, 3V3)
|
||||||
- MCP VDD -> power rail
|
- MCP VDD -> power rail
|
||||||
- MCP VSS -> GND rail
|
- MCP VSS -> GND rail
|
||||||
|
|
||||||
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.
|
|
||||||
|
84
src/bin/geode_piano.rs
Normal file
84
src/bin/geode_piano.rs
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
/*
|
||||||
|
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();
|
||||||
|
}
|
@ -45,9 +45,9 @@ struct Connection {
|
|||||||
#[embassy_executor::task]
|
#[embassy_executor::task]
|
||||||
async fn scanner_task(mut pin_driver: pins::TransparentPins) {
|
async fn scanner_task(mut pin_driver: pins::TransparentPins) {
|
||||||
log::info!("scanner_task: setting pins as input");
|
log::info!("scanner_task: setting pins as input");
|
||||||
for i in 0..pin_driver.n_total_pins {
|
for i in pin_driver.pins {
|
||||||
unwrap(pin_driver.set_input(i as u8)).await;
|
unwrap(pin_driver.set_input(i)).await;
|
||||||
unwrap(pin_driver.set_pull(i as u8, gpio::Pull::Up)).await;
|
unwrap(pin_driver.set_pull(i, gpio::Pull::Up)).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
@ -61,21 +61,17 @@ async fn scanner_task(mut pin_driver: pins::TransparentPins) {
|
|||||||
log::info!("");
|
log::info!("");
|
||||||
log::info!("---");
|
log::info!("---");
|
||||||
log::info!("STARTING SCAN...");
|
log::info!("STARTING SCAN...");
|
||||||
for gnd_pin in 0..pin_driver.n_total_pins {
|
for gnd_pin in pin_driver.pins {
|
||||||
let gnd_pin = gnd_pin as u8;
|
|
||||||
unwrap(pin_driver.set_output(gnd_pin)).await;
|
unwrap(pin_driver.set_output(gnd_pin)).await;
|
||||||
let input = unwrap(pin_driver.read_all()).await;
|
let input = unwrap(pin_driver.read_all()).await;
|
||||||
unwrap(pin_driver.set_input(gnd_pin)).await;
|
unwrap(pin_driver.set_input(gnd_pin)).await;
|
||||||
|
|
||||||
// this represents the pins that are different from expected
|
// this represents the pins that are different from expected
|
||||||
let mask = input ^ (((1 << pin_driver.n_total_pins) - 1) ^ (1 << gnd_pin));
|
let mask = input ^ (((1 << pin_driver.n_usable_pins()) - 1) ^ (1 << gnd_pin));
|
||||||
for input_pin in 0..pin_driver.n_total_pins {
|
for input_pin in pin_driver.pins {
|
||||||
let input_pin = input_pin as u8;
|
if ((1 << input_pin) & mask) != 0 && n_connections < MAX_CONNECTIONS {
|
||||||
if ((1 << input_pin) & mask) != 0 {
|
connections[n_connections] = Some(Connection { gnd_pin, input_pin });
|
||||||
if n_connections < MAX_CONNECTIONS {
|
n_connections += 1;
|
||||||
connections[n_connections] = Some(Connection { gnd_pin, input_pin });
|
|
||||||
n_connections += 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// this should avoid overexerting the components
|
// this should avoid overexerting the components
|
||||||
@ -84,8 +80,8 @@ async fn scanner_task(mut pin_driver: pins::TransparentPins) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
log::info!("SCAN RESULTS");
|
log::info!("SCAN RESULTS");
|
||||||
for i in 0..n_connections {
|
for v in connections.iter().take(n_connections) {
|
||||||
match connections[i] {
|
match v {
|
||||||
None => {}
|
None => {}
|
||||||
Some(con) => {
|
Some(con) => {
|
||||||
log::warn!("GND {:0>2} -> INPUT {:0>2}", con.gnd_pin, con.input_pin);
|
log::warn!("GND {:0>2} -> INPUT {:0>2}", con.gnd_pin, con.input_pin);
|
||||||
|
@ -60,9 +60,9 @@ async fn main(_spawner: Spawner) {
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
log::info!("main: setting pins as input");
|
log::info!("main: setting pins as input");
|
||||||
for i in 0..pin_driver.n_total_pins {
|
for i in pin_driver.pins {
|
||||||
unwrap(pin_driver.set_input(i as u8)).await;
|
unwrap(pin_driver.set_input(i)).await;
|
||||||
unwrap(pin_driver.set_pull(i as u8, gpio::Pull::Up)).await;
|
unwrap(pin_driver.set_pull(i, gpio::Pull::Up)).await;
|
||||||
}
|
}
|
||||||
log::debug!("main: setting pin 0 as output, active low");
|
log::debug!("main: setting pin 0 as output, active low");
|
||||||
unwrap(pin_driver.set_output(0)).await;
|
unwrap(pin_driver.set_output(0)).await;
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
|
#![doc = include_str!("../README.md")]
|
||||||
|
|
||||||
#![no_std]
|
#![no_std]
|
||||||
#![no_main]
|
#![no_main]
|
||||||
#![deny(rust_2018_idioms)]
|
#![deny(rust_2018_idioms)]
|
||||||
|
#![deny(rustdoc::broken_intra_doc_links)]
|
||||||
|
|
||||||
use embassy_time::Timer;
|
use embassy_time::Timer;
|
||||||
use {defmt_rtt as _, panic_probe as _};
|
use {defmt_rtt as _, panic_probe as _};
|
||||||
@ -9,6 +12,7 @@ pub mod blinky;
|
|||||||
pub mod midi;
|
pub mod midi;
|
||||||
pub mod pins;
|
pub mod pins;
|
||||||
pub mod usb;
|
pub mod usb;
|
||||||
|
pub mod matrix;
|
||||||
|
|
||||||
/// Unwrap, but log before panic
|
/// Unwrap, but log before panic
|
||||||
///
|
///
|
||||||
|
70
src/matrix.rs
Normal file
70
src/matrix.rs
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
//! Key matrix scanner
|
||||||
|
|
||||||
|
use crate::pins;
|
||||||
|
use crate::midi;
|
||||||
|
use crate::unwrap;
|
||||||
|
use embassy_rp::gpio;
|
||||||
|
use embassy_time::{Duration, Ticker};
|
||||||
|
|
||||||
|
/// Key matrix for the piano.
|
||||||
|
pub struct KeyMatrix<const N_ROWS: usize, const N_COLS: usize> {
|
||||||
|
/// GND pins at the top of each column
|
||||||
|
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],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N_ROWS: usize, const N_COLS: usize> KeyMatrix<N_ROWS, N_COLS> {
|
||||||
|
/// New function.
|
||||||
|
///
|
||||||
|
/// `col_pins` are GND pins at the top of the columns, and `row_pins` are the input pins at
|
||||||
|
/// the ends of the rows.
|
||||||
|
///
|
||||||
|
/// `keymap` represents the note that every combination of col/row gives.
|
||||||
|
pub fn new(
|
||||||
|
col_pins: [u8; N_COLS],
|
||||||
|
row_pins: [u8; N_ROWS],
|
||||||
|
keymap: [[midi::Note; N_ROWS]; N_COLS],
|
||||||
|
) -> Self {
|
||||||
|
KeyMatrix {
|
||||||
|
col_pins,
|
||||||
|
row_pins,
|
||||||
|
keymap,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn scan(&mut self, mut pin_driver: pins::TransparentPins) {
|
||||||
|
for i in pin_driver.pins {
|
||||||
|
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));
|
||||||
|
let chan = midi::MidiChannel::new(0);
|
||||||
|
let mut note_on = [false; 128];
|
||||||
|
|
||||||
|
loop {
|
||||||
|
for (i, col) in self.col_pins.iter().enumerate() {
|
||||||
|
unwrap(pin_driver.set_output(*col)).await;
|
||||||
|
let input = unwrap(pin_driver.read_all()).await;
|
||||||
|
unwrap(pin_driver.set_input(*col)).await;
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
} else if note_on[note as usize] {
|
||||||
|
note_on[note as usize] = false;
|
||||||
|
chan.note_off(note, 0).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ticker.next().await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -24,15 +24,21 @@ use embassy_rp::usb::{Driver, Instance};
|
|||||||
use embassy_sync::{blocking_mutex::raw::ThreadModeRawMutex, channel::Channel};
|
use embassy_sync::{blocking_mutex::raw::ThreadModeRawMutex, channel::Channel};
|
||||||
use embassy_usb::{class::midi::MidiClass, driver::EndpointError};
|
use embassy_usb::{class::midi::MidiClass, driver::EndpointError};
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
////////////////////////////////
|
||||||
|
// MIDI message types
|
||||||
|
////////////////////////////////
|
||||||
|
////////////////////////////////
|
||||||
|
|
||||||
struct NoteMsg {
|
struct NoteMsg {
|
||||||
on: bool,
|
on: bool,
|
||||||
note: u8,
|
note: Note,
|
||||||
velocity: u8,
|
velocity: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NoteMsg {
|
impl NoteMsg {
|
||||||
fn new(on: bool, note: u8, velocity: u8) -> Self {
|
fn new(on: bool, note: Note, velocity: u8) -> Self {
|
||||||
return NoteMsg { on, note, velocity };
|
NoteMsg { on, note, velocity }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,7 +54,7 @@ struct ControllerMsg {
|
|||||||
|
|
||||||
impl ControllerMsg {
|
impl ControllerMsg {
|
||||||
fn new(controller: Controller, value: u8) -> Self {
|
fn new(controller: Controller, value: u8) -> Self {
|
||||||
return ControllerMsg { controller, value };
|
ControllerMsg { controller, value }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,13 +70,125 @@ struct MidiMsg {
|
|||||||
|
|
||||||
impl MidiMsg {
|
impl MidiMsg {
|
||||||
fn new(msg: MsgType, channel: u8) -> Self {
|
fn new(msg: MsgType, channel: u8) -> Self {
|
||||||
return MidiMsg {
|
MidiMsg {
|
||||||
msg,
|
msg,
|
||||||
channel: channel & 0xf,
|
channel: channel & 0xf,
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
////////////////////////////////
|
||||||
|
// Public MIDI interface
|
||||||
|
////////////////////////////////
|
||||||
|
////////////////////////////////
|
||||||
|
|
||||||
|
/// Note identifiers
|
||||||
|
///
|
||||||
|
/// See src/midi/note_def.py for how this is generated
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub enum Note {
|
||||||
|
A0 = 21,
|
||||||
|
AS0 = 22,
|
||||||
|
B0 = 23,
|
||||||
|
C1 = 24,
|
||||||
|
CS1 = 25,
|
||||||
|
D1 = 26,
|
||||||
|
DS1 = 27,
|
||||||
|
E1 = 28,
|
||||||
|
F1 = 29,
|
||||||
|
FS1 = 30,
|
||||||
|
G1 = 31,
|
||||||
|
GS1 = 32,
|
||||||
|
A1 = 33,
|
||||||
|
AS1 = 34,
|
||||||
|
B1 = 35,
|
||||||
|
C2 = 36,
|
||||||
|
CS2 = 37,
|
||||||
|
D2 = 38,
|
||||||
|
DS2 = 39,
|
||||||
|
E2 = 40,
|
||||||
|
F2 = 41,
|
||||||
|
FS2 = 42,
|
||||||
|
G2 = 43,
|
||||||
|
GS2 = 44,
|
||||||
|
A2 = 45,
|
||||||
|
AS2 = 46,
|
||||||
|
B2 = 47,
|
||||||
|
C3 = 48,
|
||||||
|
CS3 = 49,
|
||||||
|
D3 = 50,
|
||||||
|
DS3 = 51,
|
||||||
|
E3 = 52,
|
||||||
|
F3 = 53,
|
||||||
|
FS3 = 54,
|
||||||
|
G3 = 55,
|
||||||
|
GS3 = 56,
|
||||||
|
A3 = 57,
|
||||||
|
AS3 = 58,
|
||||||
|
B3 = 59,
|
||||||
|
C4 = 60,
|
||||||
|
CS4 = 61,
|
||||||
|
D4 = 62,
|
||||||
|
DS4 = 63,
|
||||||
|
E4 = 64,
|
||||||
|
F4 = 65,
|
||||||
|
FS4 = 66,
|
||||||
|
G4 = 67,
|
||||||
|
GS4 = 68,
|
||||||
|
A4 = 69,
|
||||||
|
AS4 = 70,
|
||||||
|
B4 = 71,
|
||||||
|
C5 = 72,
|
||||||
|
CS5 = 73,
|
||||||
|
D5 = 74,
|
||||||
|
DS5 = 75,
|
||||||
|
E5 = 76,
|
||||||
|
F5 = 77,
|
||||||
|
FS5 = 78,
|
||||||
|
G5 = 79,
|
||||||
|
GS5 = 80,
|
||||||
|
A5 = 81,
|
||||||
|
AS5 = 82,
|
||||||
|
B5 = 83,
|
||||||
|
C6 = 84,
|
||||||
|
CS6 = 85,
|
||||||
|
D6 = 86,
|
||||||
|
DS6 = 87,
|
||||||
|
E6 = 88,
|
||||||
|
F6 = 89,
|
||||||
|
FS6 = 90,
|
||||||
|
G6 = 91,
|
||||||
|
GS6 = 92,
|
||||||
|
A6 = 93,
|
||||||
|
AS6 = 94,
|
||||||
|
B6 = 95,
|
||||||
|
C7 = 96,
|
||||||
|
CS7 = 97,
|
||||||
|
D7 = 98,
|
||||||
|
DS7 = 99,
|
||||||
|
E7 = 100,
|
||||||
|
F7 = 101,
|
||||||
|
FS7 = 102,
|
||||||
|
G7 = 103,
|
||||||
|
GS7 = 104,
|
||||||
|
A7 = 105,
|
||||||
|
AS7 = 106,
|
||||||
|
B7 = 107,
|
||||||
|
C8 = 108,
|
||||||
|
CS8 = 109,
|
||||||
|
D8 = 110,
|
||||||
|
DS8 = 111,
|
||||||
|
E8 = 112,
|
||||||
|
F8 = 113,
|
||||||
|
FS8 = 114,
|
||||||
|
G8 = 115,
|
||||||
|
GS8 = 116,
|
||||||
|
A8 = 117,
|
||||||
|
AS8 = 118,
|
||||||
|
B8 = 119,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Disconnected {}
|
pub struct Disconnected {}
|
||||||
|
|
||||||
impl From<EndpointError> for Disconnected {
|
impl From<EndpointError> for Disconnected {
|
||||||
@ -94,14 +212,14 @@ pub async fn midi_session<'d, T: Instance + 'd>(
|
|||||||
MsgType::Note(note) => {
|
MsgType::Note(note) => {
|
||||||
let status: u8 = (if note.on { 0b1001_0000 } else { 0b1000_0000 }) | msg.channel;
|
let status: u8 = (if note.on { 0b1001_0000 } else { 0b1000_0000 }) | msg.channel;
|
||||||
// i'll be honest i have no idea where the first number here comes from
|
// i'll be honest i have no idea where the first number here comes from
|
||||||
let packet = [8, status, note.note, note.velocity];
|
let packet = [8, status, note.note as u8, note.velocity];
|
||||||
log::debug!("midi_session: note {:?}", packet);
|
log::trace!("midi_session: note {:?}", packet);
|
||||||
midi.write_packet(&packet).await?
|
midi.write_packet(&packet).await?
|
||||||
}
|
}
|
||||||
MsgType::Controller(ctrl) => {
|
MsgType::Controller(ctrl) => {
|
||||||
let status: u8 = (0b1011_0000) | msg.channel;
|
let status: u8 = (0b1011_0000) | msg.channel;
|
||||||
let packet = [8, status, ctrl.controller as u8, ctrl.value];
|
let packet = [8, status, ctrl.controller as u8, ctrl.value];
|
||||||
log::debug!("midi_session: control {:?}", packet);
|
log::trace!("midi_session: control {:?}", packet);
|
||||||
midi.write_packet(&packet).await?
|
midi.write_packet(&packet).await?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -115,10 +233,11 @@ pub struct MidiChannel {
|
|||||||
|
|
||||||
impl MidiChannel {
|
impl MidiChannel {
|
||||||
pub fn new(channel: u8) -> Self {
|
pub fn new(channel: u8) -> Self {
|
||||||
return MidiChannel { channel };
|
MidiChannel { channel }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn note_on(&self, note: u8, velocity: u8) {
|
/// MIDI Note-On
|
||||||
|
pub async fn note_on(&self, note: Note, velocity: u8) {
|
||||||
MIDI_QUEUE
|
MIDI_QUEUE
|
||||||
.send(MidiMsg::new(
|
.send(MidiMsg::new(
|
||||||
MsgType::Note(NoteMsg::new(true, note, velocity)),
|
MsgType::Note(NoteMsg::new(true, note, velocity)),
|
||||||
@ -127,7 +246,8 @@ impl MidiChannel {
|
|||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn note_off(&self, note: u8, velocity: u8) {
|
/// MIDI Note-Off
|
||||||
|
pub async fn note_off(&self, note: Note, velocity: u8) {
|
||||||
MIDI_QUEUE
|
MIDI_QUEUE
|
||||||
.send(MidiMsg::new(
|
.send(MidiMsg::new(
|
||||||
MsgType::Note(NoteMsg::new(false, note, velocity)),
|
MsgType::Note(NoteMsg::new(false, note, velocity)),
|
||||||
@ -136,6 +256,7 @@ impl MidiChannel {
|
|||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// MIDI Controller (e.g. sustain pedal on/off)
|
||||||
pub async fn controller(&self, ctrl: Controller, value: u8) {
|
pub async fn controller(&self, ctrl: Controller, value: u8) {
|
||||||
MIDI_QUEUE
|
MIDI_QUEUE
|
||||||
.send(MidiMsg::new(
|
.send(MidiMsg::new(
|
15
src/midi/note_def.py
Normal file
15
src/midi/note_def.py
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
"""Generates the MIDI `Note` enum based on https://gist.github.com/dimitre/439f5ab75a0c2e66c8c63fc9e8f7ea77."""
|
||||||
|
|
||||||
|
import csv
|
||||||
|
|
||||||
|
with open("note_freq_440_432.csv") as f:
|
||||||
|
reader = csv.reader(f)
|
||||||
|
for row in reader:
|
||||||
|
note_code: int = int(row[0])
|
||||||
|
note_name: str = row[1]
|
||||||
|
octave: int = int(row[2])
|
||||||
|
|
||||||
|
identifier = f"{note_name.replace('#', 'S')}{octave}"
|
||||||
|
print(f"{identifier} = {note_code},")
|
48
src/pins.rs
48
src/pins.rs
@ -79,6 +79,21 @@ impl<E> From<mcp23017::Error<E>> for Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Range of pins that can be iterated over
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub struct PinCollection {
|
||||||
|
/// Number of total usable pins.
|
||||||
|
n_usable: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoIterator for PinCollection {
|
||||||
|
type Item = u8;
|
||||||
|
type IntoIter = core::ops::Range<u8>;
|
||||||
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
|
0..(self.n_usable as u8)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// "Transparent pins" to consistently interface with a GPIO extender + onboard GPIO ports.
|
/// "Transparent pins" to consistently interface with a GPIO extender + onboard GPIO ports.
|
||||||
///
|
///
|
||||||
/// This interface uses a single addressing scheme for all the pins it manages. Extender A is 0-15,
|
/// This interface uses a single addressing scheme for all the pins it manages. Extender A is 0-15,
|
||||||
@ -92,18 +107,18 @@ impl<E> From<mcp23017::Error<E>> for Error {
|
|||||||
/// pins addressing scheme.
|
/// pins addressing scheme.
|
||||||
pub struct TransparentPins {
|
pub struct TransparentPins {
|
||||||
addrs: [u8; N_PIN_EXTENDERS],
|
addrs: [u8; N_PIN_EXTENDERS],
|
||||||
pins: [Flex<'static, AnyPin>; N_REGULAR_PINS],
|
onboard_pins: [Flex<'static, AnyPin>; N_REGULAR_PINS],
|
||||||
i2c_bus: I2cBus,
|
i2c_bus: I2cBus,
|
||||||
disable_unsafe_pins: bool,
|
disable_unsafe_pins: bool,
|
||||||
/// Number of total usable pins. Transparent pins all have an address from `0..n_total_pins`.
|
|
||||||
pub n_total_pins: usize,
|
|
||||||
/// Usable pins per extender. Depends on `disable_unsafe_pins`.
|
/// Usable pins per extender. Depends on `disable_unsafe_pins`.
|
||||||
usable_pins_per_extender: usize,
|
usable_pins_per_extender: usize,
|
||||||
|
/// Iterable over all usable pins
|
||||||
|
pub pins: PinCollection,
|
||||||
/// Usable pin count on all extenders. Depends on `disable_unsafe_pins`.
|
/// Usable pin count on all extenders. Depends on `disable_unsafe_pins`.
|
||||||
usable_extended_pins: usize,
|
usable_extended_pins: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Helper to define the onboard pins in TransparentPins
|
/// Helper to define the onboard pins in [`TransparentPins`]
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! pin_array {
|
macro_rules! pin_array {
|
||||||
($($pin: expr),*) => {
|
($($pin: expr),*) => {
|
||||||
@ -122,6 +137,11 @@ macro_rules! extender {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl TransparentPins {
|
impl TransparentPins {
|
||||||
|
/// Get amount of usable pins. Transparent pins all have an address from `0..n_usable_pins()`.
|
||||||
|
pub fn n_usable_pins(&self) -> usize {
|
||||||
|
self.pins.n_usable
|
||||||
|
}
|
||||||
|
|
||||||
/// Transform addresses into a transparent pin number, taking into account pins that aren't being used.
|
/// Transform addresses into a transparent pin number, taking into account pins that aren't being used.
|
||||||
fn addr_to_pin(&self, addr: u8) -> u8 {
|
fn addr_to_pin(&self, addr: u8) -> u8 {
|
||||||
if self.disable_unsafe_pins {
|
if self.disable_unsafe_pins {
|
||||||
@ -171,12 +191,14 @@ impl TransparentPins {
|
|||||||
) -> Result<Self, Error> {
|
) -> Result<Self, Error> {
|
||||||
let mut ret = TransparentPins {
|
let mut ret = TransparentPins {
|
||||||
addrs,
|
addrs,
|
||||||
pins: pins.map(|x| Flex::new(x)),
|
onboard_pins: pins.map(Flex::new),
|
||||||
i2c_bus: shared_bus::BusManagerSimple::new(i2c),
|
i2c_bus: shared_bus::BusManagerSimple::new(i2c),
|
||||||
disable_unsafe_pins: false,
|
disable_unsafe_pins: false,
|
||||||
usable_pins_per_extender: PINS_PER_EXTENDER,
|
usable_pins_per_extender: PINS_PER_EXTENDER,
|
||||||
usable_extended_pins: N_EXTENDED_PINS,
|
usable_extended_pins: N_EXTENDED_PINS,
|
||||||
n_total_pins: N_EXTENDED_PINS + N_REGULAR_PINS,
|
pins: PinCollection {
|
||||||
|
n_usable: N_EXTENDED_PINS + N_REGULAR_PINS,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
if disable_unsafe_pins {
|
if disable_unsafe_pins {
|
||||||
for i in 0..N_PIN_EXTENDERS {
|
for i in 0..N_PIN_EXTENDERS {
|
||||||
@ -184,10 +206,10 @@ impl TransparentPins {
|
|||||||
ret.set_output((i as u8) * (PINS_PER_EXTENDER as u8) + PORT_B + 7)?;
|
ret.set_output((i as u8) * (PINS_PER_EXTENDER as u8) + PORT_B + 7)?;
|
||||||
ret.usable_pins_per_extender = PINS_PER_EXTENDER - UNSAFE_PER_EXTENDER;
|
ret.usable_pins_per_extender = PINS_PER_EXTENDER - UNSAFE_PER_EXTENDER;
|
||||||
ret.usable_extended_pins = N_PIN_EXTENDERS * ret.usable_pins_per_extender;
|
ret.usable_extended_pins = N_PIN_EXTENDERS * ret.usable_pins_per_extender;
|
||||||
ret.n_total_pins = ret.usable_extended_pins + N_REGULAR_PINS;
|
ret.pins.n_usable = ret.usable_extended_pins + N_REGULAR_PINS;
|
||||||
}
|
}
|
||||||
ret.disable_unsafe_pins = true;
|
ret.disable_unsafe_pins = true;
|
||||||
log::debug!("TransparentPins: {} usable pins", ret.n_total_pins)
|
log::debug!("TransparentPins: {} usable pins", ret.pins.n_usable)
|
||||||
}
|
}
|
||||||
Ok(ret)
|
Ok(ret)
|
||||||
}
|
}
|
||||||
@ -225,7 +247,7 @@ impl TransparentPins {
|
|||||||
extender!(self, i)?.write_gpioab(self.usable_to_raw(ext_val as u16))?;
|
extender!(self, i)?.write_gpioab(self.usable_to_raw(ext_val as u16))?;
|
||||||
}
|
}
|
||||||
for pin in 0..N_REGULAR_PINS {
|
for pin in 0..N_REGULAR_PINS {
|
||||||
self.pins[pin].set_level(match (val >> self.usable_extended_pins >> pin) & 1 {
|
self.onboard_pins[pin].set_level(match (val >> self.usable_extended_pins >> pin) & 1 {
|
||||||
0 => embassy_rp::gpio::Level::Low,
|
0 => embassy_rp::gpio::Level::Low,
|
||||||
1 => embassy_rp::gpio::Level::High,
|
1 => embassy_rp::gpio::Level::High,
|
||||||
_ => panic!("Invalid level"),
|
_ => panic!("Invalid level"),
|
||||||
@ -245,7 +267,7 @@ impl TransparentPins {
|
|||||||
ret |= (self.raw_to_usable(read_val) as u64) << (i * self.usable_pins_per_extender);
|
ret |= (self.raw_to_usable(read_val) as u64) << (i * self.usable_pins_per_extender);
|
||||||
}
|
}
|
||||||
for pin in 0..N_REGULAR_PINS {
|
for pin in 0..N_REGULAR_PINS {
|
||||||
ret |= (self.pins[pin].is_high() as u64) << (self.usable_extended_pins + pin);
|
ret |= (self.onboard_pins[pin].is_high() as u64) << (self.usable_extended_pins + pin);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(ret)
|
Ok(ret)
|
||||||
@ -259,7 +281,7 @@ impl TransparentPins {
|
|||||||
let pin = self.get_pin(pin_n)?;
|
let pin = self.get_pin(pin_n)?;
|
||||||
match pin {
|
match pin {
|
||||||
TransparentPin::Onboard(p) => {
|
TransparentPin::Onboard(p) => {
|
||||||
self.pins[p].set_pull(pull);
|
self.onboard_pins[p].set_pull(pull);
|
||||||
}
|
}
|
||||||
TransparentPin::Extended(p) => {
|
TransparentPin::Extended(p) => {
|
||||||
let pull_on: bool = match pull {
|
let pull_on: bool = match pull {
|
||||||
@ -279,7 +301,7 @@ impl TransparentPins {
|
|||||||
let pin_n = self.addr_to_pin(addr);
|
let pin_n = self.addr_to_pin(addr);
|
||||||
let pin = self.get_pin(pin_n)?;
|
let pin = self.get_pin(pin_n)?;
|
||||||
match pin {
|
match pin {
|
||||||
TransparentPin::Onboard(p) => self.pins[p].set_as_input(),
|
TransparentPin::Onboard(p) => self.onboard_pins[p].set_as_input(),
|
||||||
TransparentPin::Extended(p) => {
|
TransparentPin::Extended(p) => {
|
||||||
extender!(self, p.ext_id)?.pin_mode(p.loc_pin, mcp23017::PinMode::INPUT)?
|
extender!(self, p.ext_id)?.pin_mode(p.loc_pin, mcp23017::PinMode::INPUT)?
|
||||||
}
|
}
|
||||||
@ -292,7 +314,7 @@ impl TransparentPins {
|
|||||||
let pin_n = self.addr_to_pin(addr);
|
let pin_n = self.addr_to_pin(addr);
|
||||||
let pin = self.get_pin(pin_n)?;
|
let pin = self.get_pin(pin_n)?;
|
||||||
match pin {
|
match pin {
|
||||||
TransparentPin::Onboard(p) => self.pins[p].set_as_output(),
|
TransparentPin::Onboard(p) => self.onboard_pins[p].set_as_output(),
|
||||||
TransparentPin::Extended(p) => {
|
TransparentPin::Extended(p) => {
|
||||||
extender!(self, p.ext_id)?.pin_mode(p.loc_pin, mcp23017::PinMode::OUTPUT)?
|
extender!(self, p.ext_id)?.pin_mode(p.loc_pin, mcp23017::PinMode::OUTPUT)?
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user