From 7b5629db321d00c3b075098f75041adddbb29646 Mon Sep 17 00:00:00 2001
From: heck <heck@heck.live>
Date: Sat, 26 Oct 2024 05:11:26 +0200
Subject: [PATCH] starting point: usb midi device class drivers

only copied and renamed so far, but still only serve device class midi. working.
---
 examples/main_launchpad.cc          | 141 +++++++++++
 src/usb_midi_launchpad_transport.cc | 362 ++++++++++++++++++++++++++++
 src/usb_midi_launchpad_transport.hh |  60 +++++
 src/usbh_midi_launchpad.c           | 236 ++++++++++++++++++
 src/usbh_midi_launchpad.h           |  64 +++++
 5 files changed, 863 insertions(+)
 create mode 100644 examples/main_launchpad.cc
 create mode 100644 src/usb_midi_launchpad_transport.cc
 create mode 100644 src/usb_midi_launchpad_transport.hh
 create mode 100644 src/usbh_midi_launchpad.c
 create mode 100644 src/usbh_midi_launchpad.h

diff --git a/examples/main_launchpad.cc b/examples/main_launchpad.cc
new file mode 100644
index 0000000..f68b3c8
--- /dev/null
+++ b/examples/main_launchpad.cc
@@ -0,0 +1,141 @@
+/** Example of setting reading MIDI Input via USB Host
+*
+*
+*  This requires a USB-A connector
+*
+*  This example will also log incoming messages to the serial port for general MIDI troubleshooting
+*/
+#include "usb_midi_launchpad_transport.hh"
+#include "daisy_seed.h"
+#include "usbh_midi_launchpad.h"
+
+/** This prevents us from having to type "daisy::" in front of a lot of things. */
+using namespace daisy;
+using MidiUsbLaunchpadHandler = MidiHandler<UsbMidiLaunchpadTransport>;
+
+
+/** Global Hardware access */
+DaisySeed hw{};
+//MidiUsbHandler midi;
+MidiUsbLaunchpadHandler midi;
+USBHostHandle usbHost;
+
+/** FIFO to hold messages as we're ready to print them */
+FIFO<MidiEvent, 128> event_log;
+
+void USBH_Connect(void* data)
+{
+    hw.PrintLine("device connected");
+}
+
+void USBH_Disconnect(void* data)
+{
+    hw.PrintLine("device disconnected");
+}
+
+void USBH_ClassActive(void* data)
+{
+    if (usbHost.IsActiveClass(USBH_MIDI_LAUNCHPAD_CLASS)) {
+        hw.PrintLine("MIDI device class active");
+        MidiUsbLaunchpadHandler::Config midi_config{};
+        midi_config.transport_config.periph = UsbMidiLaunchpadTransport::Config::Periph::HOST;
+        midi.Init(midi_config);
+        midi.StartReceive();
+    }
+}
+
+void USBH_Error(void* data)
+{
+    hw.PrintLine("USB device error");
+}
+
+int main(void)
+{
+    /** Initialize our hardware */
+    hw.Init();
+
+    hw.StartLog(true);
+
+    hw.PrintLine("MIDI USB Host start");
+
+    /** Configure USB host */
+    USBHostHandle::Config usbhConfig;
+    usbhConfig.connect_callback = USBH_Connect, usbhConfig.disconnect_callback = USBH_Disconnect,
+    usbhConfig.class_active_callback = USBH_ClassActive, usbhConfig.error_callback = USBH_Error,
+    usbHost.Init(usbhConfig);
+
+    usbHost.RegisterClass(USBH_MIDI_LAUNCHPAD_CLASS);
+
+    uint32_t now = System::GetNow();
+    uint32_t log_time = System::GetNow();
+    uint32_t blink_time = 0;
+    bool ledState = false;
+
+    hw.PrintLine("MIDI USB Host initialized");
+
+    /** Infinite Loop */
+    while (1) {
+        now = System::GetNow();
+
+        if (now > blink_time) {
+            hw.SetLed(ledState);
+            ledState = !ledState;
+            if (usbHost.GetPresent())
+                blink_time = now + 400;
+            else
+                blink_time = now + 80;
+        }
+        /** Run USB host process */
+        usbHost.Process();
+
+        if (usbHost.IsActiveClass(USBH_MIDI_LAUNCHPAD_CLASS) && midi.RxActive()) {
+            /** Process MIDI in the background */
+            midi.Listen();
+
+            /** Loop through any MIDI Events */
+            while (midi.HasEvents()) {
+                MidiEvent msg = midi.PopEvent();
+
+                /** Handle messages as they come in
+                *  See DaisyExamples for some examples of this
+                */
+                switch (msg.type) {
+                    case NoteOn:
+                        // Do something on Note On events
+                        {
+                            uint8_t bytes[3] = { 0x90, 0x00, 0x00 };
+                            bytes[1] = msg.data[0];
+                            bytes[2] = msg.data[1];
+                            midi.SendMessage(bytes, 3);
+                        }
+                        break;
+                    default:
+                        break;
+                }
+
+                /** Regardless of message, let's add the message data to our queue to output */
+                event_log.PushBack(msg);
+            }
+
+            /** Now separately, every 5ms we'll print the top message in our queue if there is one */
+            if (now - log_time > 5) {
+                log_time = now;
+                if (!event_log.IsEmpty()) {
+                    auto msg = event_log.PopFront();
+                    char outstr[128];
+                    const char* type_str = MidiEvent::GetTypeAsString(msg);
+                    sprintf(
+                        outstr,
+                        "time:\t%ld\ttype: %s\tChannel:  %d\tData MSB: "
+                        "%d\tData LSB: %d\n",
+                        now,
+                        type_str,
+                        msg.channel,
+                        msg.data[0],
+                        msg.data[1]);
+                    hw.PrintLine(outstr);
+                }
+            }
+        }
+    }
+}
diff --git a/src/usb_midi_launchpad_transport.cc b/src/usb_midi_launchpad_transport.cc
new file mode 100644
index 0000000..2fc1902
--- /dev/null
+++ b/src/usb_midi_launchpad_transport.cc
@@ -0,0 +1,362 @@
+#include "system.h"
+#include "usbd_cdc.h"
+#include "usbh_midi_launchpad.h"
+#include "usb_midi_launchpad_transport.hh"
+#include <cassert>
+
+extern "C"
+{
+    extern USBH_HandleTypeDef hUsbHostHS;
+}
+
+#define pUSB_Host &hUsbHostHS
+
+using namespace daisy;
+
+class UsbMidiLaunchpadTransport::Impl
+{
+public:
+    void Init(Config config);
+
+    void StartRx(MidiRxParseCallback callback, void* context)
+    {
+        FlushRx();
+        rx_active_      = true;
+        parse_callback_ = callback;
+        parse_context_  = context;
+    }
+
+    bool RxActive() { return rx_active_; }
+    void FlushRx() { rx_buffer_.Flush(); }
+    void Tx(uint8_t* buffer, size_t size);
+
+    void UsbToMidi(uint8_t* buffer, uint8_t length);
+    void MidiToUsb(uint8_t* buffer, size_t length);
+    void Parse();
+
+private:
+    void MidiToUsbSingle(uint8_t* buffer, size_t length);
+
+    /** USB Handle for CDC transfers
+         */
+    UsbHandle usb_handle_;
+    Config    config_;
+
+    static constexpr size_t kBufferSize = 1024;
+    bool                    rx_active_;
+    // This corresponds to 256 midi messages
+    RingBuffer<uint8_t, kBufferSize> rx_buffer_;
+    MidiRxParseCallback              parse_callback_;
+    void*                            parse_context_;
+
+    // simple, self-managed buffer
+    uint8_t tx_buffer_[kBufferSize];
+    size_t  tx_ptr_;
+
+    // MIDI message size determined by the
+    // code index number. You can find this
+    // table in the MIDI USB spec 1.0
+    const uint8_t code_index_size_[16]
+        = {3, 3, 2, 3, 3, 1, 2, 3, 3, 3, 3, 3, 2, 2, 3, 1};
+
+    const uint8_t midi_message_size_[16]
+        = {0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 1, 1, 2, 0};
+
+    // Masks to check for message type, and byte content
+    const uint8_t kStatusByteMask     = 0x80;
+    const uint8_t kMessageMask        = 0x70;
+    const uint8_t kDataByteMask       = 0x7F;
+    const uint8_t kSystemCommonMask   = 0xF0;
+    const uint8_t kChannelMask        = 0x0F;
+    const uint8_t kRealTimeMask       = 0xF8;
+    const uint8_t kSystemRealTimeMask = 0x07;
+};
+
+// Global Impl
+static UsbMidiLaunchpadTransport::Impl midi_usb_handle;
+
+void ReceiveCallback(uint8_t* buffer, uint32_t* length)
+{
+    if(midi_usb_handle.RxActive())
+    {
+        for(uint16_t i = 0; i < *length; i += 4)
+        {
+            size_t  remaining_bytes = *length - i;
+            uint8_t packet_length   = remaining_bytes > 4 ? 4 : remaining_bytes;
+            midi_usb_handle.UsbToMidi(buffer + i, packet_length);
+            midi_usb_handle.Parse();
+        }
+    }
+}
+
+static void HostReceiveCallback(uint8_t* buffer, size_t sz, void* pUser)
+{
+    uint32_t len = sz;
+    ReceiveCallback(buffer, &len);
+}
+
+void UsbMidiLaunchpadTransport::Impl::Init(Config config)
+{
+    // Borrowed from logger
+    /** this implementation relies on the fact that UsbHandle class has no member variables and can be shared
+     * assert this statement:
+     */
+    // static_assert(1u == sizeof(UsbMidiLaunchpadTransport::Impl::usb_handle_), "UsbHandle is not static");
+
+    config_    = config;
+    rx_active_ = false;
+
+    if(config_.periph == Config::HOST)
+    {
+        System::Delay(10);
+        USBH_MIDI_Launchpad_SetReceiveCallback(pUSB_Host, HostReceiveCallback, nullptr);
+    }
+    else
+    {
+        // This tells the USB middleware to send out MIDI descriptors instead of CDC
+        usbd_mode = USBD_MODE_MIDI;
+
+        UsbHandle::UsbPeriph periph = UsbHandle::FS_INTERNAL;
+        if(config_.periph == Config::EXTERNAL)
+            periph = UsbHandle::FS_EXTERNAL;
+
+        usb_handle_.Init(periph);
+
+        System::Delay(10);
+        usb_handle_.SetReceiveCallback(ReceiveCallback, periph);
+    }
+}
+
+void UsbMidiLaunchpadTransport::Impl::Tx(uint8_t* buffer, size_t size)
+{
+    int  attempt_count = config_.tx_retry_count;
+    bool should_retry;
+
+    MidiToUsb(buffer, size);
+    do
+    {
+        if(config_.periph == Config::HOST)
+        {
+            MIDI_Launchpad_ErrorTypeDef result;
+            result       = USBH_MIDI_Launchpad_Transmit(pUSB_Host, tx_buffer_, tx_ptr_);
+            should_retry = (result == MIDI_BUSY) && attempt_count--;
+        }
+        else
+        {
+            UsbHandle::Result result;
+            if(config_.periph == Config::EXTERNAL)
+                result = usb_handle_.TransmitExternal(tx_buffer_, tx_ptr_);
+            else
+                result = usb_handle_.TransmitInternal(tx_buffer_, tx_ptr_);
+            should_retry
+                = (result == UsbHandle::Result::ERR) && attempt_count--;
+        }
+
+
+        if(should_retry)
+            System::DelayUs(100);
+    } while(should_retry);
+
+    tx_ptr_ = 0;
+}
+
+void UsbMidiLaunchpadTransport::Impl::UsbToMidi(uint8_t* buffer, uint8_t length)
+{
+    // A length of less than four in the buffer indicates
+    // a garbled message, since USB MIDI packets usually*
+    // require 4 bytes per message
+    if(length < 4)
+        return;
+
+    // Right now, Daisy only supports a single cable, so we don't
+    // need to extract that value from the upper nibble
+    uint8_t code_index = buffer[0] & 0xF;
+    if(code_index == 0x0 || code_index == 0x1)
+    {
+        // 0x0 and 0x1 are reserved codes, and if they come up,
+        // there's probably been an error. *0xF indicates data is
+        // sent one byte at a time, rather than in packets of four.
+        // This functionality could be supported later.
+        // The single-byte mode does still come through as 32-bit messages
+        return;
+    }
+
+    // Only writing as many bytes as necessary
+    for(uint8_t i = 0; i < code_index_size_[code_index]; i++)
+    {
+        if(rx_buffer_.writable() > 0)
+            rx_buffer_.Write(buffer[1 + i]);
+        else
+        {
+            rx_active_ = false; // disable on overflow
+            break;
+        }
+    }
+}
+
+void UsbMidiLaunchpadTransport::Impl::MidiToUsbSingle(uint8_t* buffer, size_t size)
+{
+    if(size == 0)
+        return;
+
+    // Channel voice messages
+    if((buffer[0] & 0xF0) != 0xF0)
+    {
+        // Check message validity
+        if((buffer[0] & 0xF0) == 0xC0 || (buffer[0] & 0xF0) == 0xD0)
+        {
+            if(size != 2)
+                return; // error
+        }
+        else
+        {
+            if(size != 3)
+                return; //error
+        }
+
+        // CIN is the same as status byte for channel voice messages
+        tx_buffer_[tx_ptr_ + 0] = (buffer[0] & 0xF0) >> 4;
+        tx_buffer_[tx_ptr_ + 1] = buffer[0];
+        tx_buffer_[tx_ptr_ + 2] = buffer[1];
+        tx_buffer_[tx_ptr_ + 3] = size == 3 ? buffer[2] : 0;
+
+        tx_ptr_ += 4;
+    }
+    else // buffer[0] & 0xF0 == 0xF0 aka System common or realtime
+    {
+        if(0xF2 == buffer[0])
+        // three byte message
+        {
+            if(size != 3)
+                return; // error
+
+            tx_buffer_[tx_ptr_ + 0] = 0x03;
+            tx_buffer_[tx_ptr_ + 1] = buffer[0];
+            tx_buffer_[tx_ptr_ + 2] = buffer[1];
+            tx_buffer_[tx_ptr_ + 3] = buffer[2];
+
+            tx_ptr_ += 4;
+        }
+        if(0xF1 == buffer[0] || 0xF3 == buffer[0])
+        // two byte messages
+        {
+            if(size != 2)
+                return; // error
+
+            tx_buffer_[tx_ptr_ + 0] = 0x02;
+            tx_buffer_[tx_ptr_ + 1] = buffer[0];
+            tx_buffer_[tx_ptr_ + 2] = buffer[1];
+            tx_buffer_[tx_ptr_ + 3] = 0;
+
+            tx_ptr_ += 4;
+        }
+        else if(0xF4 <= buffer[0])
+        // one byte message
+        {
+            if(size != 1)
+                return; // error
+
+            tx_buffer_[tx_ptr_ + 0] = 0x05;
+            tx_buffer_[tx_ptr_ + 1] = buffer[0];
+            tx_buffer_[tx_ptr_ + 2] = 0;
+            tx_buffer_[tx_ptr_ + 3] = 0;
+
+            tx_ptr_ += 4;
+        }
+        else // sysex
+        {
+            size_t i = 0;
+            // Sysex messages are split up into several 4 bytes packets
+            // first ones use CIN 0x04
+            // but packet containing the SysEx stop byte use a different CIN
+            for(i = 0; i + 3 < size; i += 3, tx_ptr_ += 4)
+            {
+                tx_buffer_[tx_ptr_]     = 0x04;
+                tx_buffer_[tx_ptr_ + 1] = buffer[i];
+                tx_buffer_[tx_ptr_ + 2] = buffer[i + 1];
+                tx_buffer_[tx_ptr_ + 3] = buffer[i + 2];
+            }
+
+            // Fill CIN for terminating bytes
+            // 0x05 for 1 remaining byte
+            // 0x06 for 2
+            // 0x07 for 3
+            tx_buffer_[tx_ptr_] = 0x05 + (size - i - 1);
+            tx_ptr_++;
+            for(; i < size; ++i, ++tx_ptr_)
+                tx_buffer_[tx_ptr_] = buffer[i];
+            for(; (tx_ptr_ % 4) != 0; ++tx_ptr_)
+                tx_buffer_[tx_ptr_] = 0;
+        }
+    }
+}
+
+void UsbMidiLaunchpadTransport::Impl::MidiToUsb(uint8_t* buffer, size_t size)
+{
+    // We'll assume your message starts with a status byte!
+    size_t status_index = 0;
+    while(status_index < size)
+    {
+        // Search for next status byte or end
+        size_t next_status = status_index;
+        for(size_t j = status_index + 1; j < size; j++)
+        {
+            if(buffer[j] & 0x80)
+            {
+                next_status = j;
+                break;
+            }
+        }
+        if(next_status == status_index)
+        {
+            // Either we're at the end or it's malformed
+            next_status = size;
+        }
+        MidiToUsbSingle(buffer + status_index, next_status - status_index);
+        status_index = next_status;
+    }
+}
+
+void UsbMidiLaunchpadTransport::Impl::Parse()
+{
+    if(parse_callback_)
+    {
+        uint8_t bytes[kBufferSize];
+        size_t  i = 0;
+        while(!rx_buffer_.isEmpty())
+        {
+            bytes[i++] = rx_buffer_.Read();
+        }
+        parse_callback_(bytes, i, parse_context_);
+    }
+}
+
+////////////////////////////////////////////////
+// UsbMidiLaunchpadTransport -> UsbMidiLaunchpadTransport::Impl
+////////////////////////////////////////////////
+
+void UsbMidiLaunchpadTransport::Init(UsbMidiLaunchpadTransport::Config config)
+{
+    pimpl_ = &midi_usb_handle;
+    pimpl_->Init(config);
+}
+
+void UsbMidiLaunchpadTransport::StartRx(MidiRxParseCallback callback, void* context)
+{
+    pimpl_->StartRx(callback, context);
+}
+
+bool UsbMidiLaunchpadTransport::RxActive()
+{
+    return pimpl_->RxActive();
+}
+
+void UsbMidiLaunchpadTransport::FlushRx()
+{
+    pimpl_->FlushRx();
+}
+
+void UsbMidiLaunchpadTransport::Tx(uint8_t* buffer, size_t size)
+{
+    pimpl_->Tx(buffer, size);
+}
diff --git a/src/usb_midi_launchpad_transport.hh b/src/usb_midi_launchpad_transport.hh
new file mode 100644
index 0000000..da9b7ab
--- /dev/null
+++ b/src/usb_midi_launchpad_transport.hh
@@ -0,0 +1,60 @@
+#ifndef LIBDIZZY_USB_MIDI_LAUNCHPAD_TRANSPORT_HH
+#define LIBDIZZY_USB_MIDI_LAUNCHPAD_TRANSPORT_HH
+
+#include "hid/usb.h"
+#include "sys/system.h"
+#include "util/ringbuffer.h"
+
+namespace daisy {
+    /** @brief USB Transport for MIDI
+ *  @ingroup midi
+ */
+    class UsbMidiLaunchpadTransport {
+    public:
+        typedef void (*MidiRxParseCallback)(uint8_t* data, size_t size, void* context);
+
+        struct Config {
+            enum Periph {
+                INTERNAL = 0,
+                EXTERNAL,
+                HOST
+            };
+
+            Periph periph;
+
+        /**
+         * When sending MIDI messages immediately back-to-back in user code,
+         * sometimes the USB CDC driver is still "busy".
+         *
+         * This option configures the number of times to retry a Tx after
+         * delaying for 100 microseconds (default = 3 retries).
+         *
+         * If you set this to zero, Tx will not retry so the attempt will block
+         * for slightly less time, but transmit can fail if the Tx state is busy.
+         */
+            uint8_t tx_retry_count;
+
+            Config() : periph(INTERNAL), tx_retry_count(3) {}
+        };
+
+        void Init(Config config);
+
+        void StartRx(MidiRxParseCallback callback, void* context);
+        bool RxActive();
+        void FlushRx();
+        void Tx(uint8_t* buffer, size_t size);
+
+        class Impl;
+
+        UsbMidiLaunchpadTransport() : pimpl_(nullptr) {}
+        ~UsbMidiLaunchpadTransport() {}
+        UsbMidiLaunchpadTransport(const UsbMidiLaunchpadTransport& other) = default;
+        UsbMidiLaunchpadTransport& operator=(const UsbMidiLaunchpadTransport& other) = default;
+
+    private:
+        Impl* pimpl_;
+    };
+
+} // namespace daisy
+
+#endif
diff --git a/src/usbh_midi_launchpad.c b/src/usbh_midi_launchpad.c
new file mode 100644
index 0000000..cb4c8d5
--- /dev/null
+++ b/src/usbh_midi_launchpad.c
@@ -0,0 +1,236 @@
+#include "usbh_midi_launchpad.h"
+#include "daisy_core.h"
+
+static USB_MIDI_Launchpad_HandleTypeDef DMA_BUFFER_MEM_SECTION static_midi;
+
+static USBH_StatusTypeDef USBH_MIDI_InterfaceInit(USBH_HandleTypeDef *phost);
+static USBH_StatusTypeDef USBH_MIDI_InterfaceDeInit(USBH_HandleTypeDef *phost);
+static USBH_StatusTypeDef USBH_MIDI_Process(USBH_HandleTypeDef *phost);
+static USBH_StatusTypeDef USBH_MIDI_ClassRequest(USBH_HandleTypeDef *phost);
+static USBH_StatusTypeDef USBH_MIDI_SOFProcess(USBH_HandleTypeDef *phost);
+
+USBH_ClassTypeDef USBH_Launchpad_midi = {
+   "MIDI",
+   USB_LAUNCHPAD_CLASS,
+   USBH_MIDI_InterfaceInit,
+   USBH_MIDI_InterfaceDeInit,
+   USBH_MIDI_ClassRequest,
+   USBH_MIDI_Process,
+   USBH_MIDI_SOFProcess,
+   NULL,
+};
+
+#define EP_IN 0x80U
+
+/**
+* @brief  USBH_MIDI_InterfaceInit
+*         The function init the MIDI class.
+* @param  phost: Host handle
+* @retval USBH Status
+*/
+static USBH_StatusTypeDef USBH_MIDI_InterfaceInit(USBH_HandleTypeDef *phost)
+{
+   USBH_UsrLog(__FUNCTION__);
+   USBH_StatusTypeDef status;
+   USB_MIDI_Launchpad_HandleTypeDef *MIDI_Handle;
+
+   // Single static instance of midi handle
+   phost->pActiveClass->pData = &static_midi;
+   MIDI_Handle = (USB_MIDI_Launchpad_HandleTypeDef *)phost->pActiveClass->pData;
+   USBH_memset(MIDI_Handle, 0, sizeof(USB_MIDI_Launchpad_HandleTypeDef));
+
+   uint8_t interface = USBH_FindInterface(phost, phost->pActiveClass->ClassCode,
+                                          USB_LAUNCHPAD_STREAMING_SUBCLASS, 0xFFU);
+
+   if ((interface == 0xFFU) || (interface >= USBH_MAX_NUM_INTERFACES)) {
+       USBH_DbgLog("Cannot find interface for %s class.", phost->pActiveClass->Name);
+       return USBH_FAIL;
+   }
+   status = USBH_SelectInterface(phost, interface);
+   if (status != USBH_OK) {
+       return USBH_FAIL;
+   }
+
+   /* Find the endpoints */
+   for (int ep = 0; ep < phost->device.CfgDesc.Itf_Desc[interface].bNumEndpoints; ++ep) {
+       if (phost->device.CfgDesc.Itf_Desc[interface].Ep_Desc[ep].bEndpointAddress & EP_IN) {
+           MIDI_Handle->InEp = phost->device.CfgDesc.Itf_Desc[interface].Ep_Desc[ep].bEndpointAddress;
+           MIDI_Handle->InEpSize = phost->device.CfgDesc.Itf_Desc[interface].Ep_Desc[ep].wMaxPacketSize & 0x03FFU;
+           if (MIDI_Handle->InEpSize > USBH_MIDI_LAUNCHPAD_RX_BUF_SIZE) {
+               MIDI_Handle->InEpSize = USBH_MIDI_LAUNCHPAD_RX_BUF_SIZE;
+           }
+           /* Allocate and open input pipe */
+           MIDI_Handle->InPipe = USBH_AllocPipe(phost, MIDI_Handle->InEp);
+           USBH_OpenPipe(phost, MIDI_Handle->InPipe, MIDI_Handle->InEp,
+                         phost->device.address, phost->device.speed, USB_EP_TYPE_BULK,
+                         MIDI_Handle->InEpSize);
+           (void)USBH_LL_SetToggle(phost, MIDI_Handle->InPipe, 0U);
+           USBH_UsrLog("InEP[%d] %02x size=%u", ep, MIDI_Handle->InEp, MIDI_Handle->InEpSize);
+       } else {
+           MIDI_Handle->OutEp = phost->device.CfgDesc.Itf_Desc[interface].Ep_Desc[ep].bEndpointAddress;
+           MIDI_Handle->OutEpSize = phost->device.CfgDesc.Itf_Desc[interface].Ep_Desc[ep].wMaxPacketSize & 0x03FFU;
+           /* Allocate and open output pipe */
+           MIDI_Handle->OutPipe = USBH_AllocPipe(phost, MIDI_Handle->OutEp);
+           USBH_OpenPipe(phost, MIDI_Handle->OutPipe, MIDI_Handle->OutEp,
+                         phost->device.address, phost->device.speed, USB_EP_TYPE_BULK,
+                         MIDI_Handle->OutEpSize);
+           (void)USBH_LL_SetToggle(phost, MIDI_Handle->OutPipe, 0U);
+           USBH_UsrLog("OutEP[%d] %02x size=%u", ep, MIDI_Handle->OutEp, MIDI_Handle->OutEpSize);
+       }
+   }
+
+   MIDI_Handle->state = MIDI_INIT;
+   MIDI_Handle->error = MIDI_OK;
+
+   return USBH_OK;
+}
+
+/**
+* @brief  USBH_MIDI_InterfaceDeInit
+*         The function DeInit the Pipes used for the MIDI class.
+* @param  phost: Host handle
+* @retval USBH Status
+*/
+static USBH_StatusTypeDef USBH_MIDI_InterfaceDeInit(USBH_HandleTypeDef *phost)
+{
+   USBH_UsrLog(__FUNCTION__);
+   USB_MIDI_Launchpad_HandleTypeDef *MIDI_Handle = (USB_MIDI_Launchpad_HandleTypeDef *) phost->pActiveClass->pData;
+   if (MIDI_Handle) {
+       if (MIDI_Handle->InPipe) {
+           USBH_ClosePipe(phost, MIDI_Handle->InPipe);
+           USBH_FreePipe(phost, MIDI_Handle->InPipe);
+           MIDI_Handle->InPipe = 0U;     /* Reset the Channel as Free */
+       }
+       if (MIDI_Handle->OutPipe) {
+           USBH_ClosePipe(phost, MIDI_Handle->OutPipe);
+           USBH_FreePipe(phost, MIDI_Handle->OutPipe);
+           MIDI_Handle->InPipe = 0U;     /* Reset the Channel as Free */
+       }
+       phost->pActiveClass->pData = 0U;
+       MIDI_Handle->state = MIDI_INIT;
+       MIDI_Handle->error = MIDI_OK;
+   }
+   return USBH_OK;
+}
+
+/**
+* @brief  USBH_MIDI_ClassRequest
+*         The function is responsible for handling Standard requests
+*         for MIDI class.
+* @param  phost: Host handle
+* @retval USBH Status
+*/
+static USBH_StatusTypeDef USBH_MIDI_ClassRequest(USBH_HandleTypeDef *phost)
+{
+   phost->pUser(phost, HOST_USER_CLASS_ACTIVE);
+   return USBH_OK;
+}
+
+/**
+* @brief  USBH_MIDI_Process
+*         The function is for managing state machine for MIDI data transfers
+* @param  phost: Host handle
+* @retval USBH Status
+*/
+static USBH_StatusTypeDef USBH_MIDI_Process(USBH_HandleTypeDef *phost)
+{
+   if (!phost->pActiveClass || !phost->pActiveClass->pData)
+       return USBH_FAIL;
+
+   USB_MIDI_Launchpad_HandleTypeDef *hMidi = (USB_MIDI_Launchpad_HandleTypeDef *)phost->pActiveClass->pData;
+   USBH_StatusTypeDef error = USBH_OK;
+   USBH_URBStateTypeDef rxStatus;
+
+   switch (hMidi->state) {
+       case MIDI_INIT:
+           hMidi->state = MIDI_IDLE;
+           break;
+       case MIDI_IDLE:
+           hMidi->state = MIDI_RX;
+           break;
+       case MIDI_RX:
+           // Always returns USBH_OK, call USBH_LL_GetURBState() for status
+           USBH_BulkReceiveData(phost, hMidi->rxBuffer, hMidi->InEpSize, hMidi->InPipe);
+           hMidi->state = MIDI_RX_POLL;
+           break;
+       case MIDI_RX_POLL:
+           rxStatus = USBH_LL_GetURBState(phost, hMidi->InPipe);
+           if (rxStatus == USBH_URB_NOTREADY || rxStatus == USBH_URB_IDLE) {
+               hMidi->state = MIDI_RX_POLL;
+           } else if (rxStatus == USBH_URB_DONE) {
+               size_t sz = USBH_LL_GetLastXferSize(phost, hMidi->InPipe);
+               hMidi->state = MIDI_RX;
+               if (hMidi->callback) {
+                   hMidi->callback(hMidi->rxBuffer, sz, hMidi->pUser);
+               }
+           } else {
+               hMidi->state = MIDI_RX_ERROR;
+               error = USBH_FAIL;
+           }
+           break;
+       case MIDI_RX_ERROR:
+           error = USBH_ClrFeature(phost, hMidi->InEp);
+           if (error == USBH_FAIL) {
+               USBH_MIDI_InterfaceDeInit(phost);
+               hMidi->state = MIDI_FATAL_ERROR;
+           } else {
+               hMidi->state = MIDI_IDLE;
+           }
+           break;
+       case MIDI_FATAL_ERROR:
+           return USBH_FAIL;
+   }
+   return error;
+}
+
+/**
+* @brief  USBH_MIDI_SOFProcess
+*         The function is for SOF state
+* @param  phost: Host handle
+* @retval USBH Status
+*/
+static USBH_StatusTypeDef USBH_MIDI_SOFProcess(USBH_HandleTypeDef *phost)
+{
+   /* Prevent unused argument(s) compilation warning */
+   UNUSED(phost);
+   return USBH_OK;
+}
+
+void USBH_MIDI_Launchpad_SetReceiveCallback(USBH_HandleTypeDef *phost, USBH_MIDI_Launchpad_RxCallback cb, void* pUser)
+{
+   USBH_UsrLog(__FUNCTION__);
+   USB_MIDI_Launchpad_HandleTypeDef *hMidi = (USB_MIDI_Launchpad_HandleTypeDef *)phost->pActiveClass->pData;
+   hMidi->callback = cb;
+   hMidi->pUser = pUser;
+}
+
+MIDI_Launchpad_ErrorTypeDef USBH_MIDI_Launchpad_Transmit(USBH_HandleTypeDef *phost, uint8_t* data, size_t len)
+{
+    USB_MIDI_Launchpad_HandleTypeDef *hMidi = (USB_MIDI_Launchpad_HandleTypeDef *)phost->pActiveClass->pData;
+   int numUrbs = 0;
+   // This only blocks if data won't fit into one URB
+   while(len)
+   {
+       USBH_URBStateTypeDef txStatus = USBH_LL_GetURBState(phost, hMidi->OutPipe);
+       while(txStatus != USBH_URB_IDLE && txStatus != USBH_URB_DONE)
+       {
+           if(txStatus == USBH_URB_ERROR || txStatus == USBH_URB_STALL)
+           {
+               USBH_ClrFeature(phost, hMidi->OutEp);
+               return MIDI_ERROR;
+           }
+           if(numUrbs == 0)
+               return MIDI_BUSY;
+
+           // Give previous URB time to complete
+           USBH_Delay(2);
+       }
+       size_t sz = (len <= hMidi->OutEpSize) ? len : hMidi->OutEpSize;
+       USBH_BulkSendData(phost, data, sz, hMidi->OutPipe, 1);
+       len -= sz;
+       ++numUrbs;
+   }
+   return MIDI_OK;
+}
+
+/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
diff --git a/src/usbh_midi_launchpad.h b/src/usbh_midi_launchpad.h
new file mode 100644
index 0000000..02e27f0
--- /dev/null
+++ b/src/usbh_midi_launchpad.h
@@ -0,0 +1,64 @@
+#ifndef __USBH_MIDI_H
+#define __USBH_MIDI_H
+
+#include "usbh_core.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+    typedef enum {
+        MIDI_INIT = 0,
+        MIDI_IDLE,
+        MIDI_RX,
+        MIDI_RX_POLL,
+        MIDI_RX_ERROR,
+        MIDI_FATAL_ERROR
+    } USB_MIDI_Launchpad_StateTypeDef;
+
+    typedef enum {
+        MIDI_OK,
+        MIDI_BUSY,
+        MIDI_ERROR
+    } MIDI_Launchpad_ErrorTypeDef;
+
+    typedef void (*USBH_MIDI_Launchpad_RxCallback)(uint8_t* buff, size_t len, void* pUser);
+
+#define USBH_MIDI_LAUNCHPAD_RX_BUF_SIZE 64
+
+    /* Structure for MIDI process */
+    typedef struct _MIDI_Process {
+        uint8_t InPipe;
+        uint8_t InEp;
+        uint16_t InEpSize;
+        uint8_t OutPipe;
+        uint8_t OutEp;
+        uint16_t OutEpSize;
+        USB_MIDI_Launchpad_StateTypeDef state;
+        MIDI_Launchpad_ErrorTypeDef error;
+        USBH_MIDI_Launchpad_RxCallback callback;
+        void* pUser;
+        uint8_t rxBuffer[USBH_MIDI_LAUNCHPAD_RX_BUF_SIZE];
+    } USB_MIDI_Launchpad_HandleTypeDef;
+
+/* MIDI Class Codes */
+#define USB_LAUNCHPAD_CLASS 0x01U
+#define USB_LAUNCHPAD_STREAMING_SUBCLASS 0x03U
+
+    extern USBH_ClassTypeDef USBH_Launchpad_midi;
+#define USBH_MIDI_LAUNCHPAD_CLASS &USBH_Launchpad_midi
+
+    uint8_t USBH_MIDI_IsReady(USBH_HandleTypeDef* phost);
+
+    void USBH_MIDI_Launchpad_SetReceiveCallback(
+        USBH_HandleTypeDef* phost,
+        USBH_MIDI_Launchpad_RxCallback cb,
+        void* pUser);
+
+    MIDI_Launchpad_ErrorTypeDef USBH_MIDI_Launchpad_Transmit(USBH_HandleTypeDef* phost, uint8_t* data, size_t len);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __USBH_MIDI_H */