Skip to content

Commit

Permalink
ESP32-S3 example: added SysEx processing and some comments
Browse files Browse the repository at this point in the history
  • Loading branch information
sourcebox committed Dec 22, 2024
1 parent 1f2203f commit d19443f
Show file tree
Hide file tree
Showing 2 changed files with 86 additions and 7 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ This release focuses on:

- Increased usability by simplifying the internal module structure.
- Interfacing with third-party crates like `midi-types`.
- Support for System Exclusive messages (SysEx).

**NOTE:** The `message` module containing the `Message` struct and related types is now gated behind the `message-types` feature. This feature is enabled by default.

Expand Down
92 changes: 85 additions & 7 deletions examples/example-esp32s3/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//! Example for ESP32-S3.
//! Example for ESP32-S3. Tested on ESP32-S3-DevKitC-1.
#![no_std]
#![no_main]
Expand All @@ -16,8 +16,12 @@ use usbd_midi::{CableNumber, MidiClass, MidiPacketBufferReader, UsbMidiEventPack

static mut EP_MEMORY: [u32; 1024] = [0; 1024];

// Size of the used SysEx buffers in bytes.
const SYSEX_BUFFER_SIZE: usize = 64;

#[xtensa_lx_rt::entry]
fn main() -> ! {
// Some basic setup to run the MCU at maximum clock speed.
let mut config = Config::default();
config.cpu_clock = clock::CpuClock::Clock240MHz;
let peripherals = esp_hal::init(config);
Expand All @@ -27,8 +31,11 @@ fn main() -> ! {
unsafe { &mut *addr_of_mut!(EP_MEMORY) },
);

// Create a MIDI class with 1 input and 1 output jack.
let mut midi_class = MidiClass::new(&usb_bus_allocator, 1, 1).unwrap();

// Build the device. It's important to use `0` for the class and subclass fields because
// otherwise the device will not enumerate correctly on certain hosts.
let mut usb_dev = UsbDeviceBuilder::new(&usb_bus_allocator, UsbVidPid(0x16c0, 0x5e4))
.device_class(0)
.device_sub_class(0)
Expand All @@ -39,10 +46,12 @@ fn main() -> ! {
.unwrap()
.build();

// This is the *BOOT* button on the ESP32-S3-DevKitC-1.
let button = gpio::Input::new(peripherals.GPIO0, gpio::Pull::Up);
let mut last_button_level = button.level();

let mut sysex_buffer = Vec::<u8, 64>::new();
// Buffer for received SysEx messages from the host.
let mut sysex_receive_buffer = Vec::<u8, SYSEX_BUFFER_SIZE>::new();

loop {
if usb_dev.poll(&mut [&mut midi_class]) {
Expand All @@ -53,23 +62,59 @@ fn main() -> ! {
let buffer_reader = MidiPacketBufferReader::new(&buffer, size);
for packet in buffer_reader.into_iter().flatten() {
if !packet.is_sysex() {
// Just a regular 3-byte message that can be processed directly.
let message = MidiMessage::try_parse_slice(packet.payload_bytes());
println!(
"Regular Message, cable: {:?}, message: {:?}",
packet.cable_number(),
message
);
} else {
// If a packet containing a SysEx payload is detected, the data is saved
// into a buffer and processed after the message is complete.
if packet.is_sysex_start() {
println!("SysEx message start");
sysex_buffer.clear();
sysex_receive_buffer.clear();
}

match sysex_buffer.extend_from_slice(packet.payload_bytes()) {
match sysex_receive_buffer.extend_from_slice(packet.payload_bytes()) {
Ok(_) => {
if packet.is_sysex_end() {
println!("SysEx message end");
println!("Buffered SysEx message: {:?}", sysex_buffer);
println!("Buffered SysEx message: {:?}", sysex_receive_buffer);

// Process the SysEx message as request in a separate function
// and send an optional response back to the host.
if let Some(response) =
process_sysex(sysex_receive_buffer.as_ref())
{
for chunk in response.chunks(3) {
let packet = UsbMidiEventPacket::try_from_payload_bytes(
CableNumber::Cable0,
chunk,
);
match packet {
Ok(packet) => loop {
// Make sure to add some timeout in case the host
// does not read the data.
let result =
midi_class.send_packet(packet.clone());
match result {
Ok(_) => break,
Err(err) => {
if err != UsbError::WouldBlock {
break;
}
}
}
},
Err(err) => println!(
"SysEx response packet error: {:?}",
err
),
}
}
}
}
}
Err(_) => {
Expand All @@ -82,9 +127,9 @@ fn main() -> ! {
}
}

// Send messages on button press.
let button_level = button.level();

// Send a message when the button state changes.
if button_level != last_button_level {
last_button_level = button_level;

Expand All @@ -100,9 +145,42 @@ fn main() -> ! {

let packet =
UsbMidiEventPacket::try_from_payload_bytes(CableNumber::Cable0, &bytes).unwrap();
let result = midi_class.send_packet(packet);

// Try to send the packet.
// An `UsbError::WouldBlock` is returned if the host has not read previous data.
let result = midi_class.send_packet(packet);
println!("Send result {:?}", result);
}
}
}

/// Processes a SysEx request and returns an optional response.
pub fn process_sysex(request: &[u8]) -> Option<Vec<u8, SYSEX_BUFFER_SIZE>> {
/// Identity request message.
///
/// See section *DEVICE INQUIRY* of the *MIDI 1.0 Detailed Specification* for further details.
const IDENTITY_REQUEST: [u8; 6] = [0xF0, 0x7E, 0x7F, 0x06, 0x01, 0xF7];

if request == IDENTITY_REQUEST {
let mut response = Vec::<u8, SYSEX_BUFFER_SIZE>::new();
response
.extend_from_slice(&[
0xF0, 0x7E, 0x7F, 0x06, 0x02, // Header
0x01, // Manufacturer ID
0x02, // Family code
0x03, // Family code
0x04, // Family member code
0x05, // Family member code
0x00, // Software revision level
0x00, // Software revision level
0x00, // Software revision level
0x00, // Software revision level
0xF7,
])
.ok();

return Some(response);
}

None
}

0 comments on commit d19443f

Please sign in to comment.