Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 10 additions & 52 deletions EZ_USB_MIDI_HOST.cpp
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
/**
*
/*
* The MIT License (MIT)
*
* Copyright (c) 2023 rppicomidi
* Copyright (c) 2025 rppicomidi, DisasterAreaDesigns
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
Expand All @@ -23,53 +22,12 @@
* THE SOFTWARE.
*/

/**
* @file EZ_USB_MIDI_HOST.cpp
* This file contains the C callback functions for the usb_midi_host library.
* These callbacks have to be defined here because the Arduino IDE will not
* link these functions to the usb_midi_host library if they are defined in
* the sketch directory. This adds a single function call overhead to the
* callbacks over using the raw usb_midi_host library.
*/
#include <cstdint>

static void* inst_ptr = nullptr; //!< a pointer to the instance of the EZ_USB_MIDI_HOST class that the application created
/* The following are pointers to the callback functions implemented in the EZ_USB_MIDI_HOST class */
static void (*mount_cb_fp)(uint8_t devAddr, uint8_t nInCables, uint16_t nOutCables, void* inst)=nullptr;
static void (*umount_cb_fp)(uint8_t devAddr, void* inst)=nullptr;
static void (*rx_cb_fp)(uint8_t devAddr, uint32_t numPackets, void* inst)=nullptr;

/**
* @brief Initialize the pointers to the callback functions. The EZ_USB_MIDI_HOST
* class constructor should call this. Applications probably should not.
*/
extern "C" void rppicomidi_ez_usb_midi_host_set_cbs(void (*mount_cb)(uint8_t devAddr, uint8_t nInCables, uint16_t nOutCables, void*),
void (*umount_cb)(uint8_t devAddr, void*), void (*rx_cb)(uint8_t devAddr, uint32_t numPackets, void*),
void* inst)
{
mount_cb_fp = mount_cb;
umount_cb_fp = umount_cb;
rx_cb_fp = rx_cb;
inst_ptr = inst;
}

/* The following functions override the weak functions declared in the usb_midi_host library */

extern "C" void tuh_midi_mount_cb(uint8_t devAddr, uint8_t inEP, uint8_t outEP, uint8_t nInCables, uint16_t nOutCables)
{
(void)inEP;
(void)outEP;
mount_cb_fp(devAddr, nInCables, nOutCables, inst_ptr);
}

extern "C" void tuh_midi_umount_cb(uint8_t devAddr, uint8_t unused)
{
(void)unused;
umount_cb_fp(devAddr, inst_ptr);
}

extern "C" void tuh_midi_rx_cb(uint8_t devAddr, uint32_t numPackets)
{
rx_cb_fp(devAddr, numPackets, inst_ptr);
}
#include "EZ_USB_MIDI_HOST.h"

// This file intentionally left mostly empty.
// The TinyUSB callbacks (tuh_midi_mount_cb, tuh_midi_umount_cb, tuh_midi_rx_cb)
// are defined by the RPPICOMIDI_EZ_USB_MIDI_HOST_INSTANCE macro in the user's sketch.
//
// Previous versions of this library defined these callbacks here, but with the
// migration to built-in TinyUSB MIDI host, they must be defined in the sketch
// to properly link to the specific instance created by the macro.
131 changes: 84 additions & 47 deletions EZ_USB_MIDI_HOST.h
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
/*
* @file EZ_USB_MIDI_HOST.h
* @brief Arduino MIDI Library compatible wrapper for usb_midi_host
* application driver
* @brief Arduino MIDI Library compatible wrapper for TinyUSB built-in MIDI host
*
* This library manages all USB MIDI devices connected to the
* single USB Root hub for TinyUSB for the whole plug in,
Expand Down Expand Up @@ -47,9 +46,6 @@

#pragma once

extern "C" void rppicomidi_ez_usb_midi_host_set_cbs(void (*mount_cb)(uint8_t devAddr, uint8_t nInCables, uint16_t nOutCables, void*),
void (*umount_cb)(uint8_t devAddr, void*), void (*rx_cb)(uint8_t devAddr, uint32_t numPackets, void*), void*);

#include "EZ_USB_MIDI_HOST_Config.h"

#include "EZ_USB_MIDI_HOST_Device.h"
Expand All @@ -73,9 +69,10 @@ template<class settings>
class EZ_USB_MIDI_HOST {
public:
EZ_USB_MIDI_HOST() : appOnConnect{nullptr}, appOnDisconnect{nullptr} {
rppicomidi_ez_usb_midi_host_set_cbs(onConnect, onDisconnect, onRx, reinterpret_cast<void*>(this));
for (uint8_t idx = 0; idx < CFG_TUH_DEVICE_MAX; idx++)
for (uint8_t idx = 0; idx < CFG_TUH_DEVICE_MAX; idx++) {
devAddr2DeviceMap[idx] = nullptr;
idx2devAddr[idx] = 0;
}
}
~EZ_USB_MIDI_HOST() = default;
EZ_USB_MIDI_HOST(EZ_USB_MIDI_HOST const &) = delete;
Expand All @@ -85,14 +82,16 @@ class EZ_USB_MIDI_HOST {
void begin(Adafruit_USBH_Host* usbHost, uint8_t rhPort, ConnectCallback cfptr, DisconnectCallback dfptr) {
setAppOnConnect(cfptr);
setAppOnDisconnect(dfptr);
tuh_midih_define_limits(settings::MidiRxBufsize, settings::MidiTxBufsize, settings::MaxCables);
// Note: tuh_midih_define_limits() does not exist in built-in TinyUSB MIDI host
// Buffer sizes are configured via CFG_TUH_MIDI_RX_BUFSIZE and CFG_TUH_MIDI_TX_BUFSIZE
usbHost->begin(rhPort);
}
#else
void begin(uint8_t rhPort, ConnectCallback cfptr, DisconnectCallback dfptr) {
setAppOnConnect(cfptr);
setAppOnDisconnect(dfptr);
tuh_midih_define_limits(settings::MidiRxBufsize, settings::MidiTxBufsize, settings::MaxCables);
// Note: tuh_midih_define_limits() does not exist in built-in TinyUSB MIDI host
// Buffer sizes are configured via CFG_TUH_MIDI_RX_BUFSIZE and CFG_TUH_MIDI_TX_BUFSIZE
tuh_init(rhPort);
}
#endif
Expand Down Expand Up @@ -213,54 +212,74 @@ class EZ_USB_MIDI_HOST {
return nullptr;
}

// The following 3 functions should only be used by the tuh_midi_*_cb()
// callback functions in file EZ_USB_MIDI_HOST.cpp.
// They are declared public because the tuh_midi_*cb() callbacks are not
// associated with any object and this class is accessed via the getInstance()
// function.
static void onConnect(uint8_t devAddr, uint8_t nInCables, uint16_t nOutCables, void* inst) {
auto me = reinterpret_cast<EZ_USB_MIDI_HOST<settings>*>(inst);
/// @brief Get the TinyUSB MIDI interface index from a device address
/// @param devAddr the USB device address
/// @return the interface index or 0xFF if not found
uint8_t getIdxFromDevAddr(uint8_t devAddr) {
if (devAddr == 0) return 0xFF;
for (uint8_t idx = 0; idx < CFG_TUH_DEVICE_MAX; idx++) {
if (idx2devAddr[idx] == devAddr) return idx;
}
return 0xFF;
}

// The following 3 functions are called by tuh_midi callbacks
void onConnect(uint8_t idx, const tuh_midi_mount_cb_t* mount_cb_data) {
uint8_t devAddr = mount_cb_data->daddr;
uint8_t nInCables = mount_cb_data->rx_cable_count;
uint8_t nOutCables = mount_cb_data->tx_cable_count;

// Store the idx to devAddr mapping
idx2devAddr[idx] = devAddr;

// try to allocate a EZ_USB_MIDI_HOST_Device object for the connected device
uint8_t idx = 0;
for (; idx < RPPICOMIDI_TUH_MIDI_MAX_DEV && me->devAddr2DeviceMap[idx] != nullptr; idx++) {}
if (idx < RPPICOMIDI_TUH_MIDI_MAX_DEV && me->devAddr2DeviceMap[idx] == nullptr) {
me->devAddr2DeviceMap[idx] = me->devices + idx;
me->devAddr2DeviceMap[idx]->onConnect(devAddr, nInCables, nOutCables);
if (me->appOnConnect) me->appOnConnect(devAddr, nInCables, nOutCables);
uint8_t dev_idx = 0;
for (; dev_idx < RPPICOMIDI_TUH_MIDI_MAX_DEV && devAddr2DeviceMap[dev_idx] != nullptr; dev_idx++) {}
if (dev_idx < RPPICOMIDI_TUH_MIDI_MAX_DEV && devAddr2DeviceMap[dev_idx] == nullptr) {
devAddr2DeviceMap[dev_idx] = devices + dev_idx;
devAddr2DeviceMap[dev_idx]->onConnect(devAddr, idx, nInCables, nOutCables);
if (appOnConnect) appOnConnect(devAddr, nInCables, nOutCables);
}
}
static void onDisconnect(uint8_t devAddr, void* inst) {
auto me = reinterpret_cast<EZ_USB_MIDI_HOST<settings>*>(inst);

void onDisconnect(uint8_t idx) {
uint8_t devAddr = idx2devAddr[idx];
if (devAddr == 0) return;

// Clear the mapping
idx2devAddr[idx] = 0;

// find the EZ_USB_MIDI_HOST_Device object allocated for this device
auto ptr = me->getDevFromDevAddr(devAddr);
auto ptr = getDevFromDevAddr(devAddr);
if (ptr != nullptr) {
ptr->onDisconnect(devAddr);
if (me->appOnDisconnect)
me->appOnDisconnect(devAddr);
uint8_t idx = 0;
for (; idx < RPPICOMIDI_TUH_MIDI_MAX_DEV && (me->devAddr2DeviceMap[idx] == nullptr || me->devAddr2DeviceMap[idx]->getDevAddr() != devAddr); idx++) {}
if (idx < RPPICOMIDI_TUH_MIDI_MAX_DEV && me->devAddr2DeviceMap[idx] != nullptr && me->devAddr2DeviceMap[idx]->getDevAddr() == devAddr) {
me->devAddr2DeviceMap[idx] = nullptr;
ptr->onDisconnect(devAddr);
if (appOnDisconnect)
appOnDisconnect(devAddr);
uint8_t dev_idx = 0;
for (; dev_idx < RPPICOMIDI_TUH_MIDI_MAX_DEV && (devAddr2DeviceMap[dev_idx] == nullptr || devAddr2DeviceMap[dev_idx]->getDevAddr() != devAddr); dev_idx++) {}
if (dev_idx < RPPICOMIDI_TUH_MIDI_MAX_DEV && devAddr2DeviceMap[dev_idx] != nullptr && devAddr2DeviceMap[dev_idx]->getDevAddr() == devAddr) {
devAddr2DeviceMap[dev_idx] = nullptr;
}
}
}
static void onRx(uint8_t devAddr, uint32_t numPackets, void* inst) {
auto me = reinterpret_cast<EZ_USB_MIDI_HOST<settings>*>(inst);
if (numPackets != 0)
{
uint8_t cable;
uint8_t buffer[48];
while (1) {
uint16_t bytesRead = tuh_midi_stream_read(devAddr, &cable, buffer, sizeof(buffer));
if (bytesRead == 0)
return;
auto dev = me->getDevFromDevAddr(devAddr);
if (dev != nullptr) {
dev->writeToInFIFO(cable, buffer, bytesRead);
}

void onRx(uint8_t idx, uint32_t numPackets) {
uint8_t devAddr = idx2devAddr[idx];
if (devAddr == 0 || numPackets == 0) return;

uint8_t cable;
uint8_t buffer[48];
while (1) {
uint32_t bytesRead = tuh_midi_stream_read(idx, &cable, buffer, sizeof(buffer));
if (bytesRead == 0)
return;
auto dev = getDevFromDevAddr(devAddr);
if (dev != nullptr) {
dev->writeToInFIFO(cable, buffer, bytesRead);
}
}
}

private:
EZ_USB_MIDI_HOST_Device<settings> devices[RPPICOMIDI_TUH_MIDI_MAX_DEV];
ConnectCallback appOnConnect;
Expand All @@ -272,10 +291,28 @@ class EZ_USB_MIDI_HOST {
// has been connected or nullptr if not.
// The problem this solves is RPPICOMIDI_TUH_MIDI_MAX_DEV < CFG_TUH_DEVICE_MAX
EZ_USB_MIDI_HOST_Device<settings>* devAddr2DeviceMap[CFG_TUH_DEVICE_MAX];

// Map TinyUSB interface index to device address
uint8_t idx2devAddr[CFG_TUH_DEVICE_MAX];
};

END_EZ_USB_MIDI_HOST_NAMESPACE

// Macro to instantiate EZ_USB_MIDI_HOST and define required TinyUSB callbacks
// Usage: RPPICOMIDI_EZ_USB_MIDI_HOST_INSTANCE(usbhMIDI, MidiHostSettingsDefault)
// Note: The callbacks override TinyUSB's weak symbol implementations
#define RPPICOMIDI_EZ_USB_MIDI_HOST_INSTANCE(name_, settings) \
static EZ_USB_MIDI_HOST<settings> name_;
EZ_USB_MIDI_HOST_NAMESPACE::EZ_USB_MIDI_HOST<settings> name_; \
void tuh_midi_mount_cb(uint8_t idx, const tuh_midi_mount_cb_t* mount_cb_data) { \
name_.onConnect(idx, mount_cb_data); \
} \
void tuh_midi_umount_cb(uint8_t idx) { \
name_.onDisconnect(idx); \
} \
void tuh_midi_rx_cb(uint8_t idx, uint32_t xferred_bytes) { \
name_.onRx(idx, xferred_bytes); \
}




35 changes: 21 additions & 14 deletions EZ_USB_MIDI_HOST_Device.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,19 +37,19 @@ BEGIN_EZ_USB_MIDI_HOST_NAMESPACE
template<class settings>
class EZ_USB_MIDI_HOST_Device {
public:
EZ_USB_MIDI_HOST_Device() : devAddr{0}, nInCables{0}, nOutCables{0}, vid{0}, pid{0}, onMidiInWriteFail{nullptr} {
EZ_USB_MIDI_HOST_Device() : devAddr{0}, idx{0xFF}, nInCables{0}, nOutCables{0}, vid{0}, pid{0}, onMidiInWriteFail{nullptr} {
clearTransports();
for (unsigned idx=0;idx < settings::MaxCables; idx++) {
interfaces[idx] = new MIDI_NAMESPACE::MidiInterface<EZ_USB_MIDI_HOST_Transport<settings>, settings>(transports[idx]);
for (unsigned idx_=0;idx_ < settings::MaxCables; idx_++) {
interfaces[idx_] = new MIDI_NAMESPACE::MidiInterface<EZ_USB_MIDI_HOST_Transport<settings>, settings>(transports[idx_]);
}
productStr[0] = 0;
manufacturerStr[0] = 0;
serialStr[0] = 0;
}

~EZ_USB_MIDI_HOST_Device() {
for (unsigned idx=0;idx < settings::MaxCables; idx++) {
delete interfaces[idx];
for (unsigned idx_=0;idx_ < settings::MaxCables; idx_++) {
delete interfaces[idx_];
}
}

Expand Down Expand Up @@ -175,18 +175,20 @@ class EZ_USB_MIDI_HOST_Device {
/// @brief Call this function to configure the MIDI interface objects
/// associated with the device's virtual MIDI cables
/// @param devAddr_ the connected device's address
/// @param idx_ the TinyUSB interface index for this device
/// @param nInCables_ the number of virtual MIDI IN cables the device supports
/// @param nOutCables_ the number of virtual MIDI OUT cables the device supports
void onConnect(uint8_t devAddr_, uint8_t nInCables_, uint8_t nOutCables_) {
void onConnect(uint8_t devAddr_, uint8_t idx_, uint8_t nInCables_, uint8_t nOutCables_) {
if (devAddr_ > 0 && devAddr_ <= RPPICOMIDI_TUH_MIDI_MAX_DEV) {
devAddr = devAddr_;
idx = idx_;
nInCables = nInCables_;
nOutCables = nOutCables_;
clearTransports(); // make sure all transports are initialized
uint8_t maxCables = nInCables > nOutCables ? nInCables : nOutCables;
for (uint8_t idx = 0; idx < maxCables; idx++) {
transports[idx].setConfiguration(devAddr, idx, idx < nInCables, idx < nOutCables);
interfaces[idx]->begin(MIDI_CHANNEL_OMNI);
for (uint8_t cable_idx = 0; cable_idx < maxCables; cable_idx++) {
transports[cable_idx].setConfiguration(devAddr, idx, cable_idx, cable_idx < nInCables, cable_idx < nOutCables);
interfaces[cable_idx]->begin(MIDI_CHANNEL_OMNI);
}
tuh_vid_pid_get(devAddr, &vid, &pid);

Expand Down Expand Up @@ -233,6 +235,10 @@ class EZ_USB_MIDI_HOST_Device {
/// @return the device address for this device object
uint8_t getDevAddr() { return devAddr; }

/// @brief
/// @return the TinyUSB interface index for this device object
uint8_t getIdx() { return idx; }

/// @brief
/// @return the number of virtual MIDI IN cables for this device object
uint8_t getNumInCables() { return nInCables; }
Expand Down Expand Up @@ -271,8 +277,8 @@ class EZ_USB_MIDI_HOST_Device {
/// if the host bus is ready to do it. Does nothing if
/// there is nothing to send or if the host bus is busy
void writeFlush() {
if (devAddr != 0)
tuh_midi_stream_flush(devAddr);
if (idx != 0xFF)
tuh_midi_write_flush(idx);
}

/// @brief
Expand All @@ -298,11 +304,12 @@ class EZ_USB_MIDI_HOST_Device {
const uint8_t* getSerialString() {return serialStr; }
private:
void clearTransports() {
for (uint8_t idx = 0; idx < settings::MaxCables; idx++) {
transports[idx].end();
for (uint8_t idx_ = 0; idx_ < settings::MaxCables; idx_++) {
transports[idx_].end();
}
}
uint8_t devAddr;
uint8_t idx; // TinyUSB interface index
uint8_t nInCables;
uint8_t nOutCables;
uint16_t vid;
Expand All @@ -316,4 +323,4 @@ class EZ_USB_MIDI_HOST_Device {
MIDI_NAMESPACE::MidiInterface<EZ_USB_MIDI_HOST_Transport<settings>, settings>* interfaces[settings::MaxCables];
};

END_EZ_USB_MIDI_HOST_NAMESPACE
END_EZ_USB_MIDI_HOST_NAMESPACE
Loading