From f6a4c80703984509e95924c8052bcf5dba5a919b Mon Sep 17 00:00:00 2001 From: Frederik Walk Date: Mon, 17 Jul 2023 22:50:27 +0200 Subject: [PATCH] Add MIDI device mode --- include/tusb_config.h | 6 ++- include/usb/midi_driver.h | 43 ++++++++++++++++ include/usb/usb_driver.h | 2 + include/utils/InputState.h | 3 ++ include/utils/Menu.h | 1 + src/peripherals/Display.cpp | 2 + src/usb/midi_driver.c | 98 +++++++++++++++++++++++++++++++++++++ src/usb/usb_driver.c | 9 ++++ src/utils/InputState.cpp | 60 ++++++++++++++++++++++- src/utils/Menu.cpp | 5 ++ 10 files changed, 226 insertions(+), 3 deletions(-) create mode 100644 include/usb/midi_driver.h create mode 100644 src/usb/midi_driver.c diff --git a/include/tusb_config.h b/include/tusb_config.h index ede7511..2de5720 100644 --- a/include/tusb_config.h +++ b/include/tusb_config.h @@ -46,7 +46,7 @@ extern "C" { //------------- CLASS -------------// #define CFG_TUD_CDC (1) #define CFG_TUD_MSC (0) -#define CFG_TUD_MIDI (0) +#define CFG_TUD_MIDI (1) #define CFG_TUD_HID (1) #define CFG_TUD_VENDOR (0) @@ -57,6 +57,10 @@ extern "C" { #define CFG_TUD_HID_EP_BUFSIZE (64) +#define CFG_TUD_MIDI_RX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64) +#define CFG_TUD_MIDI_TX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64) +#define CFG_TUD_MIDI_EP_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64) + #ifdef __cplusplus } #endif diff --git a/include/usb/midi_driver.h b/include/usb/midi_driver.h new file mode 100644 index 0000000..7fcd371 --- /dev/null +++ b/include/usb/midi_driver.h @@ -0,0 +1,43 @@ +#ifndef _USB_MIDI_DRIVER_H_ +#define _USB_MIDI_DRIVER_H_ + +#include "usb/usb_driver.h" + +#include "device/usbd_pvt.h" + +#include + +#define USBD_MIDI_NAME "MIDI Controller" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct __attribute((packed, aligned(1))) { + struct { + bool acoustic_bass_drum; + bool electric_bass_drum; + bool drumsticks; + bool side_stick; + } status; + + struct { + uint8_t acoustic_bass_drum; + uint8_t electric_bass_drum; + uint8_t drumsticks; + uint8_t side_stick; + } velocity; +} midi_report_t; + +bool receive_midi_report(void); +bool send_midi_report(usb_report_t report); + +extern const tusb_desc_device_t midi_desc_device; +extern const uint8_t midi_desc_cfg[]; +extern const usbd_class_driver_t midi_app_driver; + +#ifdef __cplusplus +} +#endif + +#endif // _USB_MIDI_DRIVER_H_ \ No newline at end of file diff --git a/include/usb/usb_driver.h b/include/usb/usb_driver.h index efbf100..16eb2d3 100644 --- a/include/usb/usb_driver.h +++ b/include/usb/usb_driver.h @@ -21,6 +21,7 @@ typedef enum { USB_MODE_PS4_TATACON, USB_MODE_DUALSHOCK4, USB_MODE_XBOX360, + USB_MODE_MIDI, USB_MODE_DEBUG, } usb_mode_t; @@ -34,6 +35,7 @@ enum { USBD_STR_PS3, USBD_STR_PS4, USBD_STR_XINPUT, + USBD_STR_MIDI, USBD_STR_RPI_RESET, }; diff --git a/include/utils/InputState.h b/include/utils/InputState.h index 8722e5f..73671fd 100644 --- a/include/utils/InputState.h +++ b/include/utils/InputState.h @@ -2,6 +2,7 @@ #define _UTILS_INPUTSTATE_H_ #include "usb/hid_driver.h" +#include "usb/midi_driver.h" #include "usb/usb_driver.h" #include "usb/xinput_driver.h" @@ -45,12 +46,14 @@ struct InputState { hid_ps3_report_t m_ps3_report; hid_ps4_report_t m_ps4_report; xinput_report_t m_xinput_report; + midi_report_t m_midi_report; std::string m_debug_report; usb_report_t getSwitchReport(); usb_report_t getPS3InputReport(); usb_report_t getPS4InputReport(); usb_report_t getXinputReport(); + usb_report_t getMidiReport(); usb_report_t getDebugReport(); public: diff --git a/include/utils/Menu.h b/include/utils/Menu.h index 8c6a40e..04f2ae6 100644 --- a/include/utils/Menu.h +++ b/include/utils/Menu.h @@ -64,6 +64,7 @@ class Menu { ChangeUsbModePS4Tatacon, ChangeUsbModeDS4, ChangeUsbModeXbox360, + ChangeUsbModeMidi, ChangeUsbModeDebug, SetTriggerThresholdKaLeft, diff --git a/src/peripherals/Display.cpp b/src/peripherals/Display.cpp index 14e136e..854df2f 100644 --- a/src/peripherals/Display.cpp +++ b/src/peripherals/Display.cpp @@ -42,6 +42,8 @@ static std::string modeToString(usb_mode_t mode) { return "Dualshock 4"; case USB_MODE_XBOX360: return "Xbox 360"; + case USB_MODE_MIDI: + return "MIDI"; case USB_MODE_DEBUG: return "Debug"; } diff --git a/src/usb/midi_driver.c b/src/usb/midi_driver.c new file mode 100644 index 0000000..4332cc6 --- /dev/null +++ b/src/usb/midi_driver.c @@ -0,0 +1,98 @@ +#include "usb/midi_driver.h" +#include "usb/usb_driver.h" + +#include "class/midi/midi_device.h" + +#include "tusb.h" + +const tusb_desc_device_t midi_desc_device = { + .bLength = sizeof(tusb_desc_device_t), + .bDescriptorType = TUSB_DESC_DEVICE, + .bcdUSB = 0x0200, + .bDeviceClass = 0x00, + .bDeviceSubClass = 0x00, + .bDeviceProtocol = 0x00, + .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, + .idVendor = 0x1209, + .idProduct = 0x3939, + .bcdDevice = 0x0100, + .iManufacturer = USBD_STR_MANUFACTURER, + .iProduct = USBD_STR_PRODUCT, + .iSerialNumber = USBD_STR_SERIAL, + .bNumConfigurations = 1, +}; + +enum { + USBD_ITF_MIDI, + USBD_ITF_MIDI_STREAMING, + USBD_ITF_MAX, +}; + +#define EPNUM_MIDI_OUT 0x01 +#define EPNUM_MIDI_IN 0x01 + +#define USBD_DESC_LEN (TUD_CONFIG_DESC_LEN + TUD_MIDI_DESC_LEN) + +const uint8_t midi_desc_cfg[USBD_DESC_LEN] = { + TUD_CONFIG_DESCRIPTOR(1, USBD_ITF_MAX, USBD_STR_LANGUAGE, USBD_DESC_LEN, 0, USBD_MAX_POWER_MAX), + TUD_MIDI_DESCRIPTOR(USBD_ITF_MIDI, 0, EPNUM_MIDI_OUT, (0x80 | EPNUM_MIDI_IN), CFG_TUD_MIDI_EP_BUFSIZE), +}; + +static midi_report_t last_report = {}; + +bool receive_midi_report(void) { + // Read and discard incoming data to avoid blocking the sender + uint8_t packet[4]; + while (tud_midi_available()) { + tud_midi_packet_read(packet); + } + + return true; +} + +static void write_midi_message(uint8_t status, uint8_t byte1, uint8_t byte2) { + uint8_t midi_message[3] = {status, byte1, byte2}; + tud_midi_stream_write(0, midi_message, sizeof(midi_message)); +} + +static void set_note(uint8_t channel, bool on, uint8_t pitch, uint8_t velocity) { + uint8_t status = on ? (0x90 | channel) : (0x80 | channel); + velocity = velocity > 127 ? 127 : velocity; + write_midi_message(status, pitch, velocity); +} + +bool send_midi_report(usb_report_t report) { + static uint8_t percussion_channel = 9; + + midi_report_t *midi_report = (midi_report_t *)report.data; + + if (midi_report->status.acoustic_bass_drum != last_report.status.acoustic_bass_drum) { + set_note(percussion_channel, midi_report->status.acoustic_bass_drum, 35, + midi_report->velocity.acoustic_bass_drum); + } + if (midi_report->status.electric_bass_drum != last_report.status.electric_bass_drum) { + set_note(percussion_channel, midi_report->status.electric_bass_drum, 36, + midi_report->velocity.electric_bass_drum); + } + if (midi_report->status.drumsticks != last_report.status.drumsticks) { + set_note(percussion_channel, midi_report->status.drumsticks, 31, midi_report->velocity.drumsticks); + } + if (midi_report->status.side_stick != last_report.status.side_stick) { + set_note(percussion_channel, midi_report->status.side_stick, 37, midi_report->velocity.side_stick); + } + + memcpy(&last_report, report.data, tu_min16(report.size, sizeof(midi_report_t))); + + return true; +} + +const usbd_class_driver_t midi_app_driver = { +#if CFG_TUSB_DEBUG >= 2 + .name = "MIDI", +#endif + .init = midid_init, + .reset = midid_reset, + .open = midid_open, + .control_xfer_cb = midid_control_xfer_cb, + .xfer_cb = midid_xfer_cb, + .sof = NULL}; diff --git a/src/usb/usb_driver.c b/src/usb/usb_driver.c index 1588070..0b291bc 100644 --- a/src/usb/usb_driver.c +++ b/src/usb/usb_driver.c @@ -1,6 +1,7 @@ #include "usb/usb_driver.h" #include "usb/debug_driver.h" #include "usb/hid_driver.h" +#include "usb/midi_driver.h" #include "usb/xinput_driver.h" #include "bsp/board.h" @@ -26,6 +27,7 @@ char *const usbd_desc_str[] = { [USBD_STR_PS3] = USBD_PS3_NAME, // [USBD_STR_PS4] = USBD_PS4_NAME, // [USBD_STR_XINPUT] = USBD_XINPUT_NAME, // + [USBD_STR_MIDI] = USBD_MIDI_NAME, // [USBD_STR_CDC] = USBD_DEBUG_CDC_NAME, // [USBD_STR_RPI_RESET] = USBD_DEBUG_RESET_NAME, // }; @@ -81,6 +83,13 @@ void usb_driver_init(usb_mode_t mode) { usbd_send_report = send_xinput_report; usbd_receive_report = receive_xinput_report; break; + case USB_MODE_MIDI: + usbd_desc_device = &midi_desc_device; + usbd_desc_cfg = midi_desc_cfg; + usbd_app_driver = &midi_app_driver; + usbd_send_report = send_midi_report; + usbd_receive_report = receive_midi_report; + break; case USB_MODE_DEBUG: usbd_desc_device = &debug_desc_device; usbd_desc_cfg = debug_desc_cfg; diff --git a/src/utils/InputState.cpp b/src/utils/InputState.cpp index ece5095..7aca00f 100644 --- a/src/utils/InputState.cpp +++ b/src/utils/InputState.cpp @@ -10,7 +10,8 @@ InputState::InputState() controller( {{false, false, false, false}, {false, false, false, false, false, false, false, false, false, false}}), m_switch_report({}), m_ps3_report({}), m_ps4_report({}), - m_xinput_report({0x00, sizeof(xinput_report_t), 0, 0, 0, 0, 0, 0, 0, 0, {}}) {} + m_xinput_report({0x00, sizeof(xinput_report_t), 0, 0, 0, 0, 0, 0, 0, 0, {}}), + m_midi_report({{false, false, false, false}, {0, 0, 0, 0}}) {} usb_report_t InputState::getReport(usb_mode_t mode) { switch (mode) { @@ -24,10 +25,13 @@ usb_report_t InputState::getReport(usb_mode_t mode) { return getPS4InputReport(); case USB_MODE_XBOX360: return getXinputReport(); + case USB_MODE_MIDI: + return getMidiReport(); case USB_MODE_DEBUG: - default: return getDebugReport(); } + + return getDebugReport(); } static uint8_t getHidHat(const InputState::Controller::DPad dpad) { @@ -235,6 +239,58 @@ usb_report_t InputState::getXinputReport() { return {(uint8_t *)&m_xinput_report, sizeof(xinput_report_t)}; } +usb_report_t InputState::getMidiReport() { + struct state { + bool last_triggered; + bool on; + uint16_t velocity; + }; + + static state acoustic_bass_drum = {}; + static state electric_bass_drum = {}; + static state drumsticks = {}; + static state side_stick = {}; + + auto set_state = [](state &target, const Drum::Pad &new_state) { + if (new_state.triggered && !target.last_triggered) { + target.velocity = 0; + target.on = false; + } else if (!new_state.triggered && target.last_triggered) { + target.on = true; + } else if (!new_state.triggered && !target.last_triggered) { + target.on = false; + } + + if (new_state.triggered && new_state.raw > target.velocity) { + target.velocity = new_state.raw; + } + + target.last_triggered = new_state.triggered; + }; + + set_state(acoustic_bass_drum, drum.don_left); + set_state(electric_bass_drum, drum.don_right); + set_state(drumsticks, drum.ka_left); + set_state(side_stick, drum.ka_right); + + m_midi_report.status.acoustic_bass_drum = acoustic_bass_drum.on; + m_midi_report.status.electric_bass_drum = electric_bass_drum.on; + m_midi_report.status.drumsticks = drumsticks.on; + m_midi_report.status.side_stick = side_stick.on; + + auto convert_range = [](uint16_t in) { + uint16_t out = in / 16; + return uint8_t(out > 127 ? 127 : out); + }; + + m_midi_report.velocity.acoustic_bass_drum = convert_range(acoustic_bass_drum.velocity); + m_midi_report.velocity.electric_bass_drum = convert_range(electric_bass_drum.velocity); + m_midi_report.velocity.drumsticks = convert_range(drumsticks.velocity); + m_midi_report.velocity.side_stick = convert_range(side_stick.velocity); + + return {(uint8_t *)&m_midi_report, sizeof(midi_report_t)}; +} + usb_report_t InputState::getDebugReport() { std::stringstream out; diff --git a/src/utils/Menu.cpp b/src/utils/Menu.cpp index 57e7cee..33f033f 100644 --- a/src/utils/Menu.cpp +++ b/src/utils/Menu.cpp @@ -23,6 +23,7 @@ const std::map Menu::descriptors = { {"PS4 Tata", Menu::Descriptor::Action::ChangeUsbModePS4Tatacon}, // {"Dualshock4", Menu::Descriptor::Action::ChangeUsbModeDS4}, // {"Xbox 360", Menu::Descriptor::Action::ChangeUsbModeXbox360}, // + {"MIDI", Menu::Descriptor::Action::ChangeUsbModeMidi}, // {"Debug", Menu::Descriptor::Action::ChangeUsbModeDebug}}, // 0, // Menu::Page::Main}}, // @@ -290,6 +291,10 @@ void Menu::performSelectionAction(Menu::Descriptor::Action action) { m_store->setUsbMode(USB_MODE_XBOX360); gotoPage(descriptor_it->second.parent); break; + case Descriptor::Action::ChangeUsbModeMidi: + m_store->setUsbMode(USB_MODE_MIDI); + gotoPage(descriptor_it->second.parent); + break; case Descriptor::Action::ChangeUsbModeDebug: m_store->setUsbMode(USB_MODE_DEBUG); gotoPage(descriptor_it->second.parent);