Skip to content
Merged
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
Binary file not shown.
16 changes: 16 additions & 0 deletions source/components/usb_host_midi/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# 1. IDF version >= 6.0 does not have usb component: usb from IDF component manager will be used
# 2. For linux target, we can't use IDF component manager to get usb component, we need to add it 'the old way'
# with EXTRA_COMPONENT_DIRS because mocking of managed components is not supported yet.
# This is acceptable workaround for testing.
set(requires "")
if((${IDF_VERSION_MAJOR} LESS 6) OR ("${IDF_TARGET}" STREQUAL "linux"))
list(APPEND requires usb)
endif()

idf_component_register(SRCS
"midi_host.c"
"midi_host_descriptor_parsing.c"
INCLUDE_DIRS "include"
PRIV_INCLUDE_DIRS "private_include" "include/esp_private"
REQUIRES "${requires}"
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/

#pragma once

#include <stdint.h>
#include <stdbool.h>
#include <sys/queue.h> // For singly linked list

#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h" // For mutexes and semaphores

#include "usb/usb_host.h" // For USB device handle and transfers

// MIDI check macros
#define MIDI_CHECK(cond, ret_val) ({ \
if (!(cond)) { \
return (ret_val); \
} \
})

#define MIDI_CHECK_FROM_CRIT(cond, ret_val) ({ \
if (!(cond)) { \
MIDI_EXIT_CRITICAL(); \
return ret_val; \
} \
})

typedef struct midi_dev_s midi_dev_t;
struct midi_dev_s {
usb_device_handle_t dev_hdl; // USB device handle
void *cb_arg; // Common argument for user's callbacks (data IN and Notification)
struct {
usb_transfer_t *out_xfer; // OUT data transfer
usb_transfer_t *in_xfer; // IN data transfer
midi_data_callback_t in_cb; // User's callback for async (non-blocking) data IN
uint16_t in_mps; // IN endpoint Maximum Packet Size
uint8_t *in_data_buffer_base; // Pointer to IN data buffer in usb_transfer_t
const usb_intf_desc_t *intf_desc; // Pointer to data interface descriptor
SemaphoreHandle_t out_mux; // OUT mutex
} data;

struct {
usb_transfer_t *xfer; // IN notification transfer
const usb_intf_desc_t *intf_desc; // Pointer to notification interface descriptor, can be NULL if there is no notification channel in the device
midi_host_dev_callback_t cb; // User's callback for device events
} notif; // Structure with Notif pipe data

usb_transfer_t *ctrl_transfer; // CTRL (endpoint 0) transfer
SemaphoreHandle_t ctrl_mux; // CTRL mutex
int midi_func_desc_cnt; // Number of Midi Functional descriptors in following array
const usb_standard_desc_t *(*midi_func_desc)[]; // Pointer to array of pointers to const usb_standard_desc_t
SLIST_ENTRY(midi_dev_s) list_entry;
};
148 changes: 148 additions & 0 deletions source/components/usb_host_midi/include/usb/midi_host.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
/*
* SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/

#pragma once

#include <stdbool.h>
#include "esp_err.h"
#include "usb/usb_host.h"
#include "usb/midi_host_types.h"

// Pass these to midi_host_open() to signal that you don't care about VID/PID of the opened device
#define MIDI_HOST_ANY_VID (0)
#define MIDI_HOST_ANY_PID (0)

#ifdef __cplusplus
extern "C" {
#endif

/**
* @brief New USB device callback
*
* Provides already opened usb_dev, that will be closed after this callback returns.
* This is useful for peeking device's descriptors, e.g. peeking VID/PID and loading proper driver.
*
* @attention This callback is called from USB Host context, so the MIDI device can't be opened here.
*/
typedef void (*midi_new_dev_callback_t)(usb_device_handle_t usb_dev);

/**
* @brief Configuration structure of USB Host MIDI-ACM driver
*
*/
typedef struct {
size_t driver_task_stack_size; /**< Stack size of the driver's task */
unsigned driver_task_priority; /**< Priority of the driver's task */
int xCoreID; /**< Core affinity of the driver's task */
midi_new_dev_callback_t new_dev_cb; /**< New USB device connected callback. Can be NULL. */
} midi_host_driver_config_t;

/**
* @brief Install MIDI driver
*
* - USB Host Library must already be installed before calling this function (via usb_host_install())
* - This function should be called before calling any other MIDI driver functions
*
* @param[in] driver_config Driver configuration structure. If set to NULL, a default configuration will be used.
* @return
* - ESP_OK: Success
* - ESP_ERR_INVALID_STATE: The MIDI driver is already installed or USB host library is not installed
* - ESP_ERR_NO_MEM: Not enough memory for installing the driver
*/
esp_err_t midi_host_install(const midi_host_driver_config_t *driver_config);

/**
* @brief Uninstall MIDI driver
*
* - Users must ensure that all MIDI devices must be closed via midi_host_close() before calling this function
*
* @return
* - ESP_OK: Success
* - ESP_ERR_INVALID_STATE: The MIDI driver is not installed or not all MIDI devices are closed
* - ESP_ERR_NOT_FINISHED: The MIDI driver failed to uninstall completely
*/
esp_err_t midi_host_uninstall(void);

/**
* @brief Register new USB device callback
*
* The callback will be called for every new USB device, not just MIDI-ACM class.
*
* @param[in] new_dev_cb New device callback function
* @return
* - ESP_OK: Success
*/
esp_err_t midi_host_register_new_dev_callback(midi_new_dev_callback_t new_dev_cb);

/**
* @brief Open MIDI device
*
* The driver first looks for MIDI compliant descriptor, if it is not found the driver checks if the interface has 2 Bulk endpoints that can be used for data
*
* Use MIDI_HOST_ANY_* macros to signal that you don't care about the device's VID and PID. In this case, first USB device will be opened.
* It is recommended to use this feature if only one device can ever be in the system (there is no USB HUB connected).
*
* @param[in] vid Device's Vendor ID, set to MIDI_HOST_ANY_VID for any
* @param[in] pid Device's Product ID, set to MIDI_HOST_ANY_PID for any
* @param[in] interface_idx Index of device's interface used for MIDI-ACM communication
* @param[in] dev_config Configuration structure of the device
* @param[out] midi_hdl_ret MIDI device handle
* @return
* - ESP_OK: Success
* - ESP_ERR_INVALID_STATE: The MIDI driver is not installed
* - ESP_ERR_INVALID_ARG: dev_config or midi_hdl_ret is NULL
* - ESP_ERR_NO_MEM: Not enough memory for opening the device
* - ESP_ERR_NOT_FOUND: USB device with specified VID/PID is not connected or does not have specified interface
*/
esp_err_t midi_host_open(uint16_t vid, uint16_t pid, uint8_t interface_idx, const midi_host_device_config_t *dev_config, midi_dev_hdl_t *midi_hdl_ret);

/**
* @brief Close MIDI device and release its resources
*
* @note All in-flight transfers will be prematurely canceled.
* @param[in] midi_hdl MIDI handle obtained from midi_host_open()
* @return
* - ESP_OK: Success - device closed
* - ESP_ERR_INVALID_STATE: midi_hdl is NULL or the MIDI driver is not installed
*/
esp_err_t midi_host_close(midi_dev_hdl_t midi_hdl);

/**
* @brief Transmit data - blocking mode
*
* @param midi_hdl MIDI handle obtained from midi_host_open()
* @param[in] data Data to be sent
* @param[in] data_len Data length
* @param[in] timeout_ms Timeout in [ms]
* @return esp_err_t
*/
esp_err_t midi_host_data_tx_blocking(midi_dev_hdl_t midi_hdl, const uint8_t *data, size_t data_len, uint32_t timeout_ms);

/**
* @brief Print device's descriptors
*
* Device and full Configuration descriptors are printed in human readable format to stdout.
*
* @param midi_hdl MIDI handle obtained from midi_host_open()
*/
void midi_host_desc_print(midi_dev_hdl_t midi_hdl);

/**
* @brief Get MIDI functional descriptor
*
* @param midi_hdl MIDI handle obtained from midi_host_open()
* @param[in] desc_type Type of functional descriptor
* @param[out] desc_out Pointer to the required descriptor
* @return
* - ESP_OK: Success
* - ESP_ERR_INVALID_ARG: Invalid device or descriptor type
* - ESP_ERR_NOT_FOUND: The required descriptor is not present in the device
*/
esp_err_t midi_host_midi_desc_get(midi_dev_hdl_t midi_hdl, const usb_standard_desc_t **desc_out);

#ifdef __cplusplus
}
#endif
65 changes: 65 additions & 0 deletions source/components/usb_host_midi/include/usb/midi_host_types.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/

#pragma once

#include <stdint.h>
#include <stdbool.h>

typedef struct midi_dev_s *midi_dev_hdl_t;

/**
* @brief MIDI- Device Event types to upper layer
*/
typedef enum {
MIDI_HOST_ERROR,
MIDI_HOST_SERIAL_STATE,
MIDI_HOST_NETWORK_CONNECTION,
MIDI_HOST_DEVICE_DISCONNECTED
} midi_host_dev_event_t;

/**
* @brief MIDI- Device Event data structure
*/
typedef struct {
midi_host_dev_event_t type;
union {
int error; //!< Error code from USB Host
bool network_connected; //!< Network connection event
midi_dev_hdl_t midi_hdl; //!< Disconnection event
} data;
} midi_host_dev_event_data_t;

/**
* @brief Data receive callback type
*
* @param[in] data Pointer to received data
* @param[in] data_len Length of received data in bytes
* @param[in] user_arg User's argument passed to open function
* @return true Received data was processed -> Flush RX buffer
* @return false Received data was NOT processed -> Append new data to the buffer
*/
typedef bool (*midi_data_callback_t)(const uint8_t *data, size_t data_len, void *user_arg);

/**
* @brief Device event callback type
*
* @param[in] event Event structure
* @param[in] user_arg User's argument passed to open function
*/
typedef void (*midi_host_dev_callback_t)(const midi_host_dev_event_data_t *event, void *user_ctx);

/**
* @brief Configuration structure of MIDI- device
*/
typedef struct {
uint32_t connection_timeout_ms; /**< Timeout for USB device connection in [ms] */
size_t out_buffer_size; /**< Maximum size of USB bulk out transfer, set to 0 for read-only devices */
size_t in_buffer_size; /**< Maximum size of USB bulk in transfer */
midi_host_dev_callback_t event_cb; /**< Device's event callback function. Can be NULL */
midi_data_callback_t data_cb; /**< Device's data RX callback function. Can be NULL for write-only devices */
void *user_arg; /**< User's argument that will be passed to the callbacks */
} midi_host_device_config_t;
Loading