geode_midi.rs geode_usb.rs: created
implements queue-based midi and merges all usb-related functions into one embassy task.
This commit is contained in:
parent
cb80b0976a
commit
5a24257af5
137
src/geode_midi.rs
Normal file
137
src/geode_midi.rs
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
use embassy_rp::{
|
||||||
|
peripherals::USB,
|
||||||
|
usb::{Driver, Instance},
|
||||||
|
};
|
||||||
|
use embassy_sync::{blocking_mutex::raw::ThreadModeRawMutex, channel::Channel};
|
||||||
|
use embassy_time::Timer;
|
||||||
|
use embassy_usb::{class::midi::MidiClass, driver::EndpointError};
|
||||||
|
|
||||||
|
struct NoteMsg {
|
||||||
|
on: bool,
|
||||||
|
note: u8,
|
||||||
|
velocity: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NoteMsg {
|
||||||
|
fn new(on: bool, note: u8, velocity: u8) -> Self {
|
||||||
|
return NoteMsg { on, note, velocity };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub enum Controller {
|
||||||
|
SustainPedal = 64,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ControllerMsg {
|
||||||
|
controller: Controller,
|
||||||
|
value: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ControllerMsg {
|
||||||
|
fn new(controller: Controller, value: u8) -> Self {
|
||||||
|
return ControllerMsg { controller, value };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum MsgType {
|
||||||
|
Note(NoteMsg),
|
||||||
|
Controller(ControllerMsg),
|
||||||
|
}
|
||||||
|
|
||||||
|
struct MidiMsg {
|
||||||
|
msg: MsgType,
|
||||||
|
channel: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MidiMsg {
|
||||||
|
fn new(msg: MsgType, channel: u8) -> Self {
|
||||||
|
return MidiMsg {
|
||||||
|
msg,
|
||||||
|
channel: channel & 0xf,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Disconnected {}
|
||||||
|
|
||||||
|
impl From<EndpointError> for Disconnected {
|
||||||
|
fn from(val: EndpointError) -> Self {
|
||||||
|
match val {
|
||||||
|
EndpointError::BufferOverflow => panic!("Buffer overflow"),
|
||||||
|
EndpointError::Disabled => Disconnected {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static MIDI_QUEUE: Channel<ThreadModeRawMutex, MidiMsg, 3> = Channel::new();
|
||||||
|
|
||||||
|
/// Handle sending MIDI until connection breaks
|
||||||
|
pub async fn midi_session<'d, T: Instance + 'd>(
|
||||||
|
midi: &mut MidiClass<'d, Driver<'d, T>>,
|
||||||
|
) -> Result<(), Disconnected> {
|
||||||
|
loop {
|
||||||
|
let msg = MIDI_QUEUE.receive().await;
|
||||||
|
match msg.msg {
|
||||||
|
MsgType::Note(note) => {
|
||||||
|
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
|
||||||
|
let packet = [8, status, note.note, note.velocity];
|
||||||
|
log::debug!("midi_session: note {:?}", packet);
|
||||||
|
midi.write_packet(&packet).await?
|
||||||
|
}
|
||||||
|
MsgType::Controller(ctrl) => {
|
||||||
|
let status: u8 = (0b1011_0000) | msg.channel;
|
||||||
|
let packet = [8, status, ctrl.controller as u8, ctrl.value];
|
||||||
|
log::debug!("midi_session: control {:?}", packet);
|
||||||
|
midi.write_packet(&packet).await?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[embassy_executor::task]
|
||||||
|
pub async fn midi_task(mut midi: MidiClass<'static, Driver<'static, USB>>) -> ! {
|
||||||
|
loop {
|
||||||
|
log::info!("Connected");
|
||||||
|
midi_session(&mut midi);
|
||||||
|
log::info!("Disconnected");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct MidiChannel {
|
||||||
|
channel: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MidiChannel {
|
||||||
|
pub fn new(channel: u8) -> Self {
|
||||||
|
return MidiChannel { channel };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn note_on(&self, note: u8, velocity: u8) {
|
||||||
|
MIDI_QUEUE
|
||||||
|
.send(MidiMsg::new(
|
||||||
|
MsgType::Note(NoteMsg::new(true, note, velocity)),
|
||||||
|
self.channel,
|
||||||
|
))
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn note_off(&self, note: u8, velocity: u8) {
|
||||||
|
MIDI_QUEUE
|
||||||
|
.send(MidiMsg::new(
|
||||||
|
MsgType::Note(NoteMsg::new(false, note, velocity)),
|
||||||
|
self.channel,
|
||||||
|
))
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn controller(&self, ctrl: Controller, value: u8) {
|
||||||
|
MIDI_QUEUE
|
||||||
|
.send(MidiMsg::new(
|
||||||
|
MsgType::Controller(ControllerMsg::new(ctrl, value)),
|
||||||
|
self.channel,
|
||||||
|
))
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
}
|
78
src/geode_usb.rs
Normal file
78
src/geode_usb.rs
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
//! Handle all USB communcation in this task.
|
||||||
|
//! 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.
|
||||||
|
|
||||||
|
use embassy_futures::join::join;
|
||||||
|
use embassy_rp::{peripherals::USB, usb::Driver};
|
||||||
|
|
||||||
|
use crate::geode_midi::midi_session;
|
||||||
|
use crate::geode_midi;
|
||||||
|
use embassy_usb::class::cdc_acm::CdcAcmClass;
|
||||||
|
use embassy_usb::class::cdc_acm::State;
|
||||||
|
use embassy_usb::class::midi::MidiClass;
|
||||||
|
use embassy_usb::driver::EndpointError;
|
||||||
|
use embassy_usb::{Builder, Config};
|
||||||
|
|
||||||
|
#[embassy_executor::task]
|
||||||
|
pub async fn usb_task(
|
||||||
|
// remember this is the Driver struct not the trait
|
||||||
|
driver: Driver<'static, USB>
|
||||||
|
) {
|
||||||
|
// Create embassy-usb Config
|
||||||
|
let mut config = Config::new(0xc0de, 0xcafe);
|
||||||
|
config.manufacturer = Some("dogeystamp");
|
||||||
|
config.product = Some("Geode-Piano MIDI keyboard");
|
||||||
|
config.serial_number = Some("alpha-12345");
|
||||||
|
config.max_power = 100;
|
||||||
|
config.max_packet_size_0 = 64;
|
||||||
|
|
||||||
|
// Required for windows compatibility.
|
||||||
|
// https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help
|
||||||
|
config.device_class = 0xEF;
|
||||||
|
config.device_sub_class = 0x02;
|
||||||
|
config.device_protocol = 0x01;
|
||||||
|
config.composite_with_iads = true;
|
||||||
|
|
||||||
|
// Create embassy-usb DeviceBuilder using the driver and config.
|
||||||
|
// It needs some buffers for building the descriptors.
|
||||||
|
let mut config_descriptor = [0; 256];
|
||||||
|
let mut device_descriptor = [0; 256];
|
||||||
|
let mut bos_descriptor = [0; 256];
|
||||||
|
let mut control_buf = [0; 64];
|
||||||
|
|
||||||
|
let mut logger_state = State::new();
|
||||||
|
|
||||||
|
let mut builder = Builder::new(
|
||||||
|
driver,
|
||||||
|
config,
|
||||||
|
&mut device_descriptor,
|
||||||
|
&mut config_descriptor,
|
||||||
|
&mut bos_descriptor,
|
||||||
|
&mut [], // no msos descriptors
|
||||||
|
&mut control_buf,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create classes on the builder.
|
||||||
|
let mut midi_class = MidiClass::new(&mut builder, 1, 1, 64);
|
||||||
|
let logger_class = CdcAcmClass::new(&mut builder, &mut logger_state, 64);
|
||||||
|
let log_fut = embassy_usb_logger::with_class!(1024, log::LevelFilter::Info, logger_class);
|
||||||
|
|
||||||
|
// The `MidiClass` can be split into `Sender` and `Receiver`, to be used in separate tasks.
|
||||||
|
// let (sender, receiver) = class.split();
|
||||||
|
|
||||||
|
// Build the builder.
|
||||||
|
let mut usb = builder.build();
|
||||||
|
|
||||||
|
// Run the USB device.
|
||||||
|
let usb_fut = usb.run();
|
||||||
|
|
||||||
|
let midi_fut = async {
|
||||||
|
loop {
|
||||||
|
log::info!("Connected");
|
||||||
|
midi_session(&mut midi_class).await;
|
||||||
|
log::info!("Disconnected");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
join(usb_fut, join(log_fut, midi_fut)).await;
|
||||||
|
}
|
120
src/main.rs
120
src/main.rs
@ -1,5 +1,7 @@
|
|||||||
#![no_std]
|
#![no_std]
|
||||||
#![no_main]
|
#![no_main]
|
||||||
|
#![allow(dead_code)]
|
||||||
|
#![allow(unused)]
|
||||||
|
|
||||||
use embassy_executor::Spawner;
|
use embassy_executor::Spawner;
|
||||||
use embassy_futures::join::join;
|
use embassy_futures::join::join;
|
||||||
@ -12,14 +14,13 @@ use embassy_rp::peripherals::USB;
|
|||||||
use embassy_rp::usb::Instance;
|
use embassy_rp::usb::Instance;
|
||||||
use embassy_rp::usb::{Driver, InterruptHandler};
|
use embassy_rp::usb::{Driver, InterruptHandler};
|
||||||
use embassy_time::Timer;
|
use embassy_time::Timer;
|
||||||
use embassy_usb::class::cdc_acm::CdcAcmClass;
|
use geode_usb::usb_task;
|
||||||
use embassy_usb::class::cdc_acm::State;
|
|
||||||
use embassy_usb::class::midi::MidiClass;
|
|
||||||
use embassy_usb::driver::EndpointError;
|
|
||||||
use embassy_usb::{Builder, Config};
|
|
||||||
use gpio::{Level, Output};
|
use gpio::{Level, Output};
|
||||||
use {defmt_rtt as _, panic_probe as _};
|
use {defmt_rtt as _, panic_probe as _};
|
||||||
|
|
||||||
|
mod geode_midi;
|
||||||
|
mod geode_usb;
|
||||||
|
|
||||||
bind_interrupts!(struct Irqs {
|
bind_interrupts!(struct Irqs {
|
||||||
USBCTRL_IRQ => InterruptHandler<USB>;
|
USBCTRL_IRQ => InterruptHandler<USB>;
|
||||||
});
|
});
|
||||||
@ -29,29 +30,23 @@ async fn blink_task(pin: embassy_rp::gpio::AnyPin) {
|
|||||||
let mut led = Output::new(pin, Level::Low);
|
let mut led = Output::new(pin, Level::Low);
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
log::info!("led on from task!");
|
|
||||||
led.set_high();
|
led.set_high();
|
||||||
Timer::after_millis(100).await;
|
Timer::after_millis(100).await;
|
||||||
|
|
||||||
log::info!("led off!");
|
|
||||||
led.set_low();
|
led.set_low();
|
||||||
Timer::after_secs(5).await;
|
Timer::after_secs(5).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Disconnected {}
|
enum Note {
|
||||||
|
C,
|
||||||
impl From<EndpointError> for Disconnected {
|
Pedal,
|
||||||
fn from(val: EndpointError) -> Self {
|
|
||||||
match val {
|
|
||||||
EndpointError::BufferOverflow => panic!("Buffer overflow"),
|
|
||||||
EndpointError::Disabled => Disconnected {},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn button<'d, T: Instance + 'd>(pin: AnyPin, midi: &mut MidiClass<'d, Driver<'d, T>>) -> Result<(), Disconnected> {
|
#[embassy_executor::task(pool_size = 2)]
|
||||||
|
async fn button(pin: AnyPin, note: Note) {
|
||||||
let mut button = Input::new(pin, Pull::Up);
|
let mut button = Input::new(pin, Pull::Up);
|
||||||
|
let mut chan = geode_midi::MidiChannel::new(0);
|
||||||
loop {
|
loop {
|
||||||
let mut counter = 10;
|
let mut counter = 10;
|
||||||
button.wait_for_falling_edge().await;
|
button.wait_for_falling_edge().await;
|
||||||
@ -66,9 +61,14 @@ async fn button<'d, T: Instance + 'd>(pin: AnyPin, midi: &mut MidiClass<'d, Driv
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
match note {
|
||||||
|
Note::C => chan.note_on(72, 64).await,
|
||||||
|
Note::Pedal => {
|
||||||
|
chan.controller(geode_midi::Controller::SustainPedal, 64)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
log::info!("button press");
|
log::info!("button press");
|
||||||
let note_on = [9, 0x90, 72, 64];
|
|
||||||
midi.write_packet(¬e_on).await?;
|
|
||||||
counter = 10;
|
counter = 10;
|
||||||
button.wait_for_rising_edge().await;
|
button.wait_for_rising_edge().await;
|
||||||
loop {
|
loop {
|
||||||
@ -82,9 +82,14 @@ async fn button<'d, T: Instance + 'd>(pin: AnyPin, midi: &mut MidiClass<'d, Driv
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
match note {
|
||||||
|
Note::C => chan.note_off(72, 0).await,
|
||||||
|
Note::Pedal => {
|
||||||
|
chan.controller(geode_midi::Controller::SustainPedal, 0)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
log::info!("button release");
|
log::info!("button release");
|
||||||
let note_on = [9, 0x80, 72, 0];
|
|
||||||
midi.write_packet(¬e_on).await?;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,73 +99,10 @@ async fn main(_spawner: Spawner) {
|
|||||||
|
|
||||||
let driver = Driver::new(p.USB, Irqs);
|
let driver = Driver::new(p.USB, Irqs);
|
||||||
|
|
||||||
// Create embassy-usb Config
|
|
||||||
let mut config = Config::new(0xc0de, 0xcafe);
|
|
||||||
config.manufacturer = Some("Embassy");
|
|
||||||
config.product = Some("USB-MIDI example");
|
|
||||||
config.serial_number = Some("12345678");
|
|
||||||
config.max_power = 100;
|
|
||||||
config.max_packet_size_0 = 64;
|
|
||||||
|
|
||||||
// Required for windows compatibility.
|
|
||||||
// https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help
|
|
||||||
config.device_class = 0xEF;
|
|
||||||
config.device_sub_class = 0x02;
|
|
||||||
config.device_protocol = 0x01;
|
|
||||||
config.composite_with_iads = true;
|
|
||||||
|
|
||||||
// Create embassy-usb DeviceBuilder using the driver and config.
|
|
||||||
// It needs some buffers for building the descriptors.
|
|
||||||
let mut config_descriptor = [0; 256];
|
|
||||||
let mut device_descriptor = [0; 256];
|
|
||||||
let mut bos_descriptor = [0; 256];
|
|
||||||
let mut control_buf = [0; 64];
|
|
||||||
|
|
||||||
let mut logger_state = State::new();
|
|
||||||
|
|
||||||
let mut builder = Builder::new(
|
|
||||||
driver,
|
|
||||||
config,
|
|
||||||
&mut device_descriptor,
|
|
||||||
&mut config_descriptor,
|
|
||||||
&mut bos_descriptor,
|
|
||||||
&mut [], // no msos descriptors
|
|
||||||
&mut control_buf,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Create classes on the builder.
|
|
||||||
let mut midi_class = MidiClass::new(&mut builder, 1, 1, 64);
|
|
||||||
let logger_class = CdcAcmClass::new(&mut builder, &mut logger_state, 64);
|
|
||||||
let log_fut = embassy_usb_logger::with_class!(1024, log::LevelFilter::Debug, logger_class);
|
|
||||||
|
|
||||||
// The `MidiClass` can be split into `Sender` and `Receiver`, to be used in separate tasks.
|
|
||||||
// let (sender, receiver) = class.split();
|
|
||||||
|
|
||||||
// Build the builder.
|
|
||||||
let mut usb = builder.build();
|
|
||||||
|
|
||||||
// Run the USB device.
|
|
||||||
let usb_fut = usb.run();
|
|
||||||
|
|
||||||
let midi_fut = async {
|
|
||||||
midi_class.wait_connection().await;
|
|
||||||
log::info!("Connected");
|
|
||||||
let _ = button(p.PIN_16.into(), &mut midi_class).await;
|
|
||||||
// let _ = midi_echo(&mut midi_class).await;
|
|
||||||
log::info!("Disconnected");
|
|
||||||
};
|
|
||||||
|
|
||||||
_spawner.spawn(blink_task(p.PIN_25.into())).unwrap();
|
_spawner.spawn(blink_task(p.PIN_25.into())).unwrap();
|
||||||
|
_spawner.spawn(button(p.PIN_16.into(), Note::C)).unwrap();
|
||||||
join(usb_fut, join(log_fut, midi_fut)).await;
|
_spawner
|
||||||
}
|
.spawn(button(p.PIN_17.into(), Note::Pedal))
|
||||||
|
.unwrap();
|
||||||
async fn midi_echo<'d, T: Instance + 'd>(class: &mut MidiClass<'d, Driver<'d, T>>) -> Result<(), Disconnected> {
|
_spawner.spawn(usb_task(driver)).unwrap();
|
||||||
let mut buf = [0; 64];
|
|
||||||
loop {
|
|
||||||
let n = class.read_packet(&mut buf).await?;
|
|
||||||
let data = &buf[..n];
|
|
||||||
log::info!("data: {:#?}", data);
|
|
||||||
class.write_packet(data).await?;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user