// components/midi/midi_transport.cpp #include "midi/midi_transport.h" #include "esp_log.h" #include "tusb.h" static const char* TAG = "midi_transport"; UsbMidiTransport::UsbMidiTransport() : event_queue(nullptr), initialized(false) { } UsbMidiTransport::~UsbMidiTransport() { if (event_queue != NULL) { vQueueDelete(event_queue); } } bool UsbMidiTransport::begin() { // Create event queue event_queue = xQueueCreate(32, sizeof(MidiEvent)); if (event_queue == NULL) { ESP_LOGE(TAG, "Failed to create event queue"); return false; } // Initialize TinyUSB tusb_init(); initialized = true; ESP_LOGI(TAG, "USB MIDI transport initialized"); return true; } void UsbMidiTransport::task() { if (!initialized) return; // TinyUSB device task handling tuh_task(); // Check for MIDI data on the USB host interface uint8_t cable_num; uint8_t midi_packet[4]; while (tud_midi_available()) { if (tud_midi_packet_read(midi_packet)) { MidiEvent event; parse_midi_packet(midi_packet, 4, event); log_incoming("USB", event); if (xQueueSend(event_queue, &event, 0) != pdPASS) { ESP_LOGW(TAG, "Failed to queue MIDI event (queue full)"); } } } } void UsbMidiTransport::log_incoming(const char* source, const MidiEvent& event) { const char* type_str; switch (event.type) { case MidiEvent::NOTE_ON: type_str = "NOTE_ON"; break; case MidiEvent::NOTE_OFF: type_str = "NOTE_OFF"; break; case MidiEvent::CONTROL_CHANGE: type_str = "CC"; break; case MidiEvent::PROGRAM_CHANGE: type_str = "PC"; break; case MidiEvent::PITCH_BEND: type_str = "PITCH_BEND"; break; default: type_str = "UNKNOWN"; break; } ESP_LOGI(TAG, "MIDI IN: %s Ch:%d %s:%d:%d", source, event.channel, type_str, event.data1, event.data2); } void UsbMidiTransport::parse_midi_packet(const uint8_t* buffer, uint32_t size, MidiEvent& event) { if (size < 4) return; // USB MIDI packet format: [cable_num | CIN], [status], [data1], [data2] uint8_t cin = buffer[0] & 0x0F; uint8_t status = buffer[1]; uint8_t type = status & 0xF0; uint8_t channel = (status & 0x0F) + 1; // Convert to 1-16 range event.channel = channel; event.data1 = buffer[2]; event.data2 = buffer[3]; switch (cin) { case 0x8: // Note Off event.type = MidiEvent::NOTE_OFF; break; case 0x9: // Note On event.type = MidiEvent::NOTE_ON; if (event.data2 == 0) { event.type = MidiEvent::NOTE_OFF; } break; case 0xB: // Control Change event.type = MidiEvent::CONTROL_CHANGE; break; case 0xC: // Program Change event.type = MidiEvent::PROGRAM_CHANGE; break; case 0xE: // Pitch Bend event.type = MidiEvent::PITCH_BEND; break; default: // Try to infer from status byte switch (type) { case 0x80: event.type = MidiEvent::NOTE_OFF; break; case 0x90: event.type = MidiEvent::NOTE_ON; break; case 0xB0: event.type = MidiEvent::CONTROL_CHANGE; break; case 0xC0: event.type = MidiEvent::PROGRAM_CHANGE; break; case 0xE0: event.type = MidiEvent::PITCH_BEND; break; default: event.type = MidiEvent::NOTE_ON; break; } break; } } void usb_midi_task(void* pvParameters) { UsbMidiTransport* transport = static_cast(pvParameters); ESP_LOGI(TAG, "USB MIDI task started"); while (true) { transport->task(); vTaskDelay(pdMS_TO_TICKS(1)); } }