diff --git a/src/RZA1/usb/r_usb_basic/src/driver/r_usb_hlibusbip.c b/src/RZA1/usb/r_usb_basic/src/driver/r_usb_hlibusbip.c index 0f8612c6bd..1ff3a82ff3 100644 --- a/src/RZA1/usb/r_usb_basic/src/driver/r_usb_hlibusbip.c +++ b/src/RZA1/usb/r_usb_basic/src/driver/r_usb_hlibusbip.c @@ -38,10 +38,10 @@ #include "RZA1/usb/userdef/r_usb_hmidi_config.h" #include "definitions.h" -#include "deluge/io/midi/midi_device_manager.h" - #include "deluge/drivers/uart/uart.h" +#include "deluge/io/midi/midi_device_manager.h" #include "deluge/io/midi/midi_engine.h" +#include "deluge/io/usb/usb_state.h" #if ((USB_CFG_DTC == USB_CFG_ENABLE) || (USB_CFG_DMA == USB_CFG_ENABLE)) #include "drivers/usb/r_usb_basic/src/hw/inc/r_usb_dmac.h" @@ -954,7 +954,7 @@ void usb_hstd_receive_start(usb_utr_t* ptr, uint16_t pipe) // I now just call the PSTD one instead of this - it does the same. /*********************************************************************************************************************** Function Name : usb_hstd_read_data - Description : Request to read data from USB FIFO, and manage the size of + Description : Request to read data from USB FIFO, and manage the size of : the data read. Arguments : usb_utr_t *ptr : Pointer to usb_utr_t structure. : uint16_t pipe : Pipe number. @@ -1186,8 +1186,6 @@ void usb_hstd_data_end(usb_utr_t* ptr, uint16_t pipe, uint16_t status) } } /* End of function usb_hstd_data_end() */ -extern usb_utr_t g_usb_midi_recv_utr[][MAX_NUM_USB_MIDI_DEVICES]; - // For when data has been received, as host. Hub stuff is on PIPE9 void usb_hstd_brdy_pipe_process_rohan_midi_and_hub(usb_utr_t* ptr, uint16_t bitsts) { diff --git a/src/RZA1/usb/r_usb_basic/src/driver/r_usb_plibusbip.c b/src/RZA1/usb/r_usb_basic/src/driver/r_usb_plibusbip.c index b93f3b5c1a..5ca8cc0b4e 100644 --- a/src/RZA1/usb/r_usb_basic/src/driver/r_usb_plibusbip.c +++ b/src/RZA1/usb/r_usb_basic/src/driver/r_usb_plibusbip.c @@ -38,6 +38,7 @@ #include "deluge/drivers/usb/userdef/r_usb_pmidi_config.h" #include "deluge/io/midi/midi_device_manager.h" #include "deluge/io/midi/midi_engine.h" +#include "deluge/io/usb/usb_state.h" #if ((USB_CFG_DTC == USB_CFG_ENABLE) || (USB_CFG_DMA == USB_CFG_ENABLE)) #include "RZA1/usb/r_usb_basic/src/hw/inc/r_usb_dmac.h" diff --git a/src/RZA1/usb/r_usb_hmidi/src/r_usb_hmidi_driver.c b/src/RZA1/usb/r_usb_hmidi/src/r_usb_hmidi_driver.c index a70d7766a4..fa434c94ed 100644 --- a/src/RZA1/usb/r_usb_hmidi/src/r_usb_hmidi_driver.c +++ b/src/RZA1/usb/r_usb_hmidi/src/r_usb_hmidi_driver.c @@ -40,6 +40,7 @@ #include "deluge/deluge.h" #include "deluge/drivers/uart/uart.h" +#include "deluge/io/usb/usb_state.h" /****************************************************************************** Exported global variables @@ -61,8 +62,6 @@ static void usb_hmidi_check_result(usb_utr_t* ptr, uint16_t unused, uint16_t sta static usb_er_t usb_hhid_data_trans(usb_utr_t* ptr, uint16_t pipe, uint32_t size, uint8_t* table, usb_cb_t complete); static void usb_hmidi_enumeration_sequence(usb_utr_t* mess); -extern uint8_t currentDeviceNumWithSendPipe[]; - /****************************************************************************** Exported global variables (to be accessed by other files) ******************************************************************************/ diff --git a/src/definitions_cxx.hpp b/src/definitions_cxx.hpp index c02b54e6e8..ba0e68929e 100644 --- a/src/definitions_cxx.hpp +++ b/src/definitions_cxx.hpp @@ -493,6 +493,8 @@ enum class Error { INSUFFICIENT_RAM_FOR_FOLDER_CONTENTS_SIZE, SD_CARD_NOT_PRESENT, SD_CARD_NO_FILESYSTEM, + OUT_OF_BUFFER_SPACE, + INVALID_SYSEX_FORMAT, }; enum class SampleRepeatMode { diff --git a/src/deluge/deluge.cpp b/src/deluge/deluge.cpp index f8c6a68c63..c1f087efde 100644 --- a/src/deluge/deluge.cpp +++ b/src/deluge/deluge.cpp @@ -19,6 +19,7 @@ #include "RZA1/sdhi/inc/sdif.h" #include "definitions_cxx.hpp" +#include "deluge/io/usb//usb_state.h" #include "drivers/pic/pic.h" #include "gui/ui/audio_recorder.h" #include "gui/ui/browser/browser.h" @@ -48,6 +49,8 @@ #include "io/midi/midi_device_manager.h" #include "io/midi/midi_engine.h" #include "io/midi/midi_follow.h" +#include "io/midi/root_complex/usb_hosted.h" +#include "io/midi/root_complex/usb_peripheral.h" #include "lib/printf.h" // IWYU pragma: keep this over rides printf with a non allocating version #include "memory/general_memory_allocator.h" #include "model/clip/instrument_clip.h" @@ -242,14 +245,6 @@ bool readButtonsAndPads() { usbInitializationPeriodComplete = 1; } - /* - if (!inSDRoutine && !closedPeripheral && !anythingInitiallyAttachedAsUSBHost && AudioEngine::audioSampleTimer >= - (44100 << 1)) { D_PRINTLN("closing peripheral"); closeUSBPeripheral(); D_PRINTLN("switching back to host"); - openUSBHost(); - closedPeripheral = true; - } - */ - if (waitingForSDRoutineToEnd) { if (sdRoutineLock) { return false; @@ -553,8 +548,6 @@ void setupOLED() { extern "C" void usb_pstd_pcd_task(void); extern "C" void usb_cstd_usb_task(void); -extern "C" volatile uint32_t usbLock; - extern "C" void usb_main_host(void); void registerTasks() { @@ -847,20 +840,26 @@ extern "C" int32_t deluge_main(void) { deluge::hid::display::swapDisplayType(); } - usbLock = 1; - openUSBHost(); - - // If nothing was plugged in to us as host, we'll go peripheral - // Ideally I'd like to repeatedly switch between host and peripheral mode anytime there's no USB connection. - // To do that, I'd really need to know at any point in time whether the user had just made a connection, just then, - // that hadn't fully initialized yet. I think I sorta have that for host, but not for peripheral yet. - if (!anythingInitiallyAttachedAsUSBHost) { - D_PRINTLN("switching from host to peripheral"); - closeUSBHost(); - openUSBPeripheral(); - } + { + deluge::io::usb::USBAutoLock lock; + openUSBHost(); - usbLock = 0; + if (anythingInitiallyAttachedAsUSBHost) { + MIDIDeviceManager::setUSBRoot(new MIDIRootComplexUSBHosted()); + } + else { + // If nothing was plugged in to us as host, we'll go peripheral + // Ideally I'd like to repeatedly switch between host and peripheral mode anytime there's no USB connection. + // To do that, I'd really need to know at any point in time whether the user had just made a connection, + // just then, that hadn't fully initialized yet. I think I sorta have that for host, but not for peripheral + // yet. + D_PRINTLN("switching from host to peripheral"); + closeUSBHost(); + openUSBPeripheral(); + + // configuredAsPeripheral will set the root complex. + } + } // Hopefully we can read these files now runtimeFeatureSettings.readSettingsFromFile(); diff --git a/src/deluge/gui/menu_item/midi/devices.cpp b/src/deluge/gui/menu_item/midi/devices.cpp index 0e5412df7e..35e49f0367 100644 --- a/src/deluge/gui/menu_item/midi/devices.cpp +++ b/src/deluge/gui/menu_item/midi/devices.cpp @@ -25,6 +25,9 @@ #include "io/midi/cable_types/usb_hosted.h" #include "io/midi/midi_device.h" #include "io/midi/midi_device_manager.h" +#include "io/midi/midi_root_complex.h" +#include "io/midi/root_complex/usb_hosted.h" +#include "io/midi/root_complex/usb_peripheral.h" #include "util/container/static_vector.hpp" #include @@ -37,7 +40,12 @@ static constexpr int32_t lowestDeviceNum = -3; void Devices::beginSession(MenuItem* navigatedBackwardFrom) { bool found = false; if (navigatedBackwardFrom != nullptr) { - for (int32_t idx = lowestDeviceNum; idx < MIDIDeviceManager::hostedMIDIDevices.getNumElements(); idx++) { + // This will technically do the wrong thing when we're in peripheral mode (it'll set the max index to 2 instead + // of 0, which would be accurate) but it should be harmless -- `Devices::getCable` should just return nullptr in + // that case which we handle fine already anyway. + auto maxIndex = + (MIDIDeviceManager::rootUSB != nullptr) ? MIDIDeviceManager::rootUSB->getNumCables() : lowestDeviceNum + 1; + for (int32_t idx = lowestDeviceNum; idx < maxIndex; idx++) { if (getCable(idx) == soundEditor.currentMIDICable) { found = true; this->setValue(idx); @@ -62,10 +70,12 @@ void Devices::beginSession(MenuItem* navigatedBackwardFrom) { void Devices::selectEncoderAction(int32_t offset) { offset = std::clamp(offset, -1, 1); + auto maxIndex = (MIDIDeviceManager::rootUSB == nullptr) ? 0 : MIDIDeviceManager::rootUSB->getNumCables(); + do { int32_t newValue = this->getValue() + offset; - if (newValue >= MIDIDeviceManager::hostedMIDIDevices.getNumElements()) { + if (newValue >= maxIndex) { if (display->haveOLED()) { return; } @@ -75,14 +85,14 @@ void Devices::selectEncoderAction(int32_t offset) { if (display->haveOLED()) { return; } - newValue = MIDIDeviceManager::hostedMIDIDevices.getNumElements() - 1; + newValue = maxIndex - 1; } this->setValue(newValue); soundEditor.currentMIDICable = getCable(this->getValue()); - } while (!soundEditor.currentMIDICable->connectionFlags); + } while (soundEditor.currentMIDICable == nullptr && soundEditor.currentMIDICable->connectionFlags == 0); // Don't show devices which aren't connected. Sometimes we won't even have a name to display for them. if (display->haveOLED()) { @@ -115,24 +125,31 @@ void Devices::selectEncoderAction(int32_t offset) { } MIDICable* Devices::getCable(int32_t deviceIndex) { - if (deviceIndex < lowestDeviceNum || deviceIndex >= MIDIDeviceManager::hostedMIDIDevices.getNumElements()) { + if (deviceIndex < lowestDeviceNum) { D_PRINTLN("impossible device request"); return nullptr; } - switch (deviceIndex) { - case -3: { - return &MIDIDeviceManager::dinMIDIPorts; - } - case -2: { - return &MIDIDeviceManager::upstreamUSBMIDICable1; - } - case -1: { - return &MIDIDeviceManager::upstreamUSBMIDICable2; - } - default: { - return static_cast(MIDIDeviceManager::hostedMIDIDevices.getElement(deviceIndex)); + + if (deviceIndex == -3) { + return &MIDIDeviceManager::rootDin.cable; } + + if (MIDIDeviceManager::rootUSB != nullptr) { + auto& rootUSB = *MIDIDeviceManager::rootUSB; + if (deviceIndex < 0) { + if (rootUSB.getType() == RootComplexType::RC_USB_PERIPHERAL && deviceIndex >= -2) { + return rootUSB.getCable(deviceIndex + 2); + } + return nullptr; + } + + if (rootUSB.getType() == RootComplexType::RC_USB_HOST) { + auto& usb = static_cast(rootUSB); + return usb.getCable(deviceIndex); + } } + + return nullptr; } void Devices::drawValue() { @@ -154,11 +171,12 @@ void Devices::drawPixelsForOled() { int32_t selectedRow = -1; - int32_t device_idx = currentScroll; + auto device_idx = currentScroll; size_t row = 0; - while (row < kOLEDMenuNumOptionsVisible && device_idx < MIDIDeviceManager::hostedMIDIDevices.getNumElements()) { + auto max_index = (MIDIDeviceManager::rootUSB == nullptr) ? 0 : MIDIDeviceManager::rootUSB->getNumCables(); + while (row < kOLEDMenuNumOptionsVisible && device_idx < static_cast(max_index)) { MIDICable* cable = getCable(device_idx); - if (cable && cable->connectionFlags != 0u) { + if (cable != nullptr && cable->connectionFlags != 0u) { itemNames.push_back(cable->getDisplayName()); if (device_idx == this->getValue()) { selectedRow = static_cast(row); diff --git a/src/deluge/gui/menu_item/midi/devices.h b/src/deluge/gui/menu_item/midi/devices.h index 30fedcf50a..7403bfeeef 100644 --- a/src/deluge/gui/menu_item/midi/devices.h +++ b/src/deluge/gui/menu_item/midi/devices.h @@ -32,7 +32,7 @@ class Devices final : public Value { void drawPixelsForOled(); private: - size_t currentScroll; + ptrdiff_t currentScroll; }; extern Devices devicesMenu; diff --git a/src/deluge/io/midi/cable_types/din.cpp b/src/deluge/io/midi/cable_types/din.cpp index 5e90dd2a8f..e6632a5869 100644 --- a/src/deluge/io/midi/cable_types/din.cpp +++ b/src/deluge/io/midi/cable_types/din.cpp @@ -15,6 +15,7 @@ * If not, see . */ +#include "io/debug/log.h" extern "C" { #include "RZA1/uart/sio_char.h" } @@ -38,23 +39,128 @@ char const* MIDICableDINPorts::getDisplayName() { return deluge::l10n::get(deluge::l10n::String::STRING_FOR_DIN_PORTS); } -void MIDICableDINPorts::sendMessage(MIDIMessage message) { - midiEngine.sendSerialMidi(message); +Error MIDICableDINPorts::sendMessage(MIDIMessage message) { + uint8_t statusByte = message.channel | (message.statusType << 4); + int32_t messageLength = bytesPerStatusMessage(statusByte); + + if (messageLength > sendBufferSpace()) { + return Error::OUT_OF_BUFFER_SPACE; + } + + bufferMIDIUart(statusByte); + + if (messageLength >= 2) { + bufferMIDIUart(message.data1); + + if (messageLength == 3) { + bufferMIDIUart(message.data2); + } + } + + return Error::NONE; } size_t MIDICableDINPorts::sendBufferSpace() const { return uartGetTxBufferSpace(UART_ITEM_MIDI); } -void MIDICableDINPorts::sendSysex(const uint8_t* data, int32_t len) { +Error MIDICableDINPorts::sendSysex(const uint8_t* data, int32_t len) { if (len < 3 || data[0] != 0xf0 || data[len - 1] != 0xf7) { - return; + return Error::OUT_OF_BUFFER_SPACE; + } + if (len > sendBufferSpace()) { + return Error::OUT_OF_BUFFER_SPACE; } // NB: beware of MIDI_TX_BUFFER_SIZE for (int32_t i = 0; i < len; i++) { bufferMIDIUart(data[i]); } + + return Error::NONE; +} + +Error MIDICableDINPorts::onReceiveByte(uint32_t timestamp, uint8_t thisSerialByte) { + // D_PRINTLN((uint32_t)thisSerialByte); + // If this is a status byte, then we have to store it as the first byte. + if (thisSerialByte & 0x80) { + + switch (thisSerialByte) { + + // If it's a realtime message, we have to obey it right now, separately from any other message it was + // inserted into the middle of + case 0xF8 ... 0xFF: + midiEngine.midiMessageReceived(*this, thisSerialByte >> 4, thisSerialByte & 0x0F, 0, 0, ×tamp); + return Error::NONE; + + // Or if it's a SysEx start... + case 0xF0: + currentlyReceivingSysex_ = true; + D_PRINTLN("Sysex start"); + incomingSysexBuffer[0] = thisSerialByte; + incomingSysexPos = 1; + // numSerialMidiInput = 0; // This would throw away any running status stuff... + return Error::NONE; + } + + // If we didn't return for any of those, then it's just a regular old status message (or Sysex stop + // message). All these will end any ongoing SysEx + + // If it was a Sysex stop, that's all we need to do + if (thisSerialByte == 0xF7) { + D_PRINTLN("Sysex end"); + if (currentlyReceivingSysex_) { + currentlyReceivingSysex_ = false; + if (incomingSysexPos < sizeof incomingSysexBuffer) { + incomingSysexBuffer[incomingSysexPos++] = thisSerialByte; + midiEngine.midiSysexReceived(*this, incomingSysexBuffer, incomingSysexPos); + } + } + return Error::NONE; + } + + currentlyReceivingSysex_ = false; + currentByte_ = 0; + } + + // If not a status byte... + else { + // If we're currently receiving a SysEx, don't throw it away + if (currentlyReceivingSysex_) { + // TODO: allocate a GMA buffer to some bigger size + if (incomingSysexPos < sizeof incomingSysexBuffer) { + incomingSysexBuffer[incomingSysexPos++] = thisSerialByte; + } + D_PRINTLN("Sysex: %d", thisSerialByte); + return Error::NONE; + } + + // Ensure that we're not writing to the first byte of the buffer + if (currentByte_ == 0) { + return Error::NONE; + } + } + + messageBytes_[currentByte_] = thisSerialByte; + currentByte_++; + + // If we've received the whole MIDI message, deal with it + if (bytesPerStatusMessage(messageBytes_[0]) == currentByte_) { + uint8_t type = (messageBytes_[0]) >> 4; + uint8_t channel = messageBytes_[0] & 0x0F; + + midiEngine.midiMessageReceived(*this, type, channel, messageBytes_[1], messageBytes_[2], ×tamp); + + // If message was more than 1 byte long, and was a voice or mode message, then allow for running status + if (currentByte_ > 1 && type != 0xF) { + currentByte_ = 1; + } + else { + currentByte_ = 0; + } + } + + return Error::NONE; } bool MIDICableDINPorts::wantsToOutputMIDIOnChannel(MIDIMessage message, int32_t filter) const { diff --git a/src/deluge/io/midi/cable_types/din.h b/src/deluge/io/midi/cable_types/din.h index af41f69aeb..abbd693588 100644 --- a/src/deluge/io/midi/cable_types/din.h +++ b/src/deluge/io/midi/cable_types/din.h @@ -20,18 +20,27 @@ #include "deluge/io/midi/midi_device.h" class MIDICableDINPorts final : public MIDICable { +private: + using IntermediateMessageBuffer = std::array; + IntermediateMessageBuffer messageBytes_; + IntermediateMessageBuffer::size_type currentByte_; + bool currentlyReceivingSysex_{false}; + public: MIDICableDINPorts() { connectionFlags = 1; // DIN ports are always connected } + /// Called by the DIN root complex when a byte is received + [[nodiscard]] Error onReceiveByte(uint32_t timestamp, uint8_t byte); + [[nodiscard]] bool wantsToOutputMIDIOnChannel(MIDIMessage message, int32_t filter) const override; void writeReferenceAttributesToFile(Serializer& writer) override; void writeToFlash(uint8_t* memory) override; char const* getDisplayName() override; - void sendMessage(MIDIMessage message) override; - void sendSysex(const uint8_t* data, int32_t len) override; + [[nodiscard]] Error sendMessage(MIDIMessage message) override; + [[nodiscard]] Error sendSysex(const uint8_t* data, int32_t len) override; size_t sendBufferSpace() const override; }; diff --git a/src/deluge/io/midi/cable_types/usb_common.cpp b/src/deluge/io/midi/cable_types/usb_common.cpp index e5be69e171..3aaaed8591 100644 --- a/src/deluge/io/midi/cable_types/usb_common.cpp +++ b/src/deluge/io/midi/cable_types/usb_common.cpp @@ -18,6 +18,41 @@ #include "usb_common.h" #include "io/midi/midi_engine.h" +void MIDICableUSB::checkIncomingSysex(uint8_t const* msg, int32_t ip, int32_t d) { + ConnectedUSBMIDIDevice* connected = &connectedUSBMIDIDevices[ip][d]; + + uint8_t statusType = msg[0] & 15; + int32_t to_read = 0; + bool will_end = false; + if (statusType == 0x4) { + // sysex start or continue + if (msg[1] == 0xf0) { + this->incomingSysexPos = 0; + } + to_read = 3; + } + else if (statusType >= 0x5 && statusType <= 0x7) { + to_read = statusType - 0x4; // read between 1-3 bytes + will_end = true; + } + + for (int32_t i = 0; i < to_read; i++) { + if (this->incomingSysexPos >= sizeof(this->incomingSysexBuffer)) { + // TODO: allocate a GMA buffer to some bigger size + this->incomingSysexPos = 0; + return; // bail out + } + this->incomingSysexBuffer[this->incomingSysexPos++] = msg[i + 1]; + } + + if (will_end) { + if (this->incomingSysexBuffer[0] == 0xf0) { + midiEngine.midiSysexReceived(*this, this->incomingSysexBuffer, this->incomingSysexPos); + } + this->incomingSysexPos = 0; + } +} + void MIDICableUSB::connectedNow(int32_t midiDeviceNum) { connectionFlags |= (1 << midiDeviceNum); needsToSendMCMs = 2; @@ -32,9 +67,27 @@ void MIDICableUSB::sendMCMsNowIfNeeded() { } } -void MIDICableUSB::sendMessage(MIDIMessage message) { +static uint32_t setupUSBMessage(MIDIMessage message) { + // format message per USB midi spec on virtual cable 0 + uint8_t cin; + uint8_t firstByte = (message.channel & 15) | (message.statusType << 4); + + switch (firstByte) { + case 0xF2: // Position pointer + cin = 0x03; + break; + + default: + cin = message.statusType; // Good for voice commands + break; + } + + return ((uint32_t)message.data2 << 24) | ((uint32_t)message.data1 << 16) | ((uint32_t)firstByte << 8) | cin; +} + +Error MIDICableUSB::sendMessage(MIDIMessage message) { if (!connectionFlags) { - return; + return Error::NONE; } int32_t ip = 0; @@ -46,10 +99,12 @@ void MIDICableUSB::sendMessage(MIDIMessage message) { ConnectedUSBMIDIDevice* connectedDevice = &connectedUSBMIDIDevices[ip][d]; if (connectedDevice->canHaveMIDISent) { uint32_t channeledMessage = fullMessage | (portNumber << 4); - connectedDevice->bufferMessage(fullMessage); + connectedDevice->bufferMessage(channeledMessage); } } } + + return Error::NONE; } size_t MIDICableUSB::sendBufferSpace() const { @@ -74,9 +129,13 @@ size_t MIDICableUSB::sendBufferSpace() const { extern bool developerSysexCodeReceived; -void MIDICableUSB::sendSysex(const uint8_t* data, int32_t len) { +Error MIDICableUSB::sendSysex(const uint8_t* data, int32_t len) { if (len < 6 || data[0] != 0xf0 || data[len - 1] != 0xf7) { - return; + return Error::INVALID_SYSEX_FORMAT; + } + + if (len > sendBufferSpace()) { + return Error::OUT_OF_BUFFER_SPACE; } int32_t ip = 0; @@ -92,7 +151,7 @@ void MIDICableUSB::sendSysex(const uint8_t* data, int32_t len) { } if (!connectedDevice) { - return; + return Error::NONE; } int32_t pos = 0; @@ -132,6 +191,8 @@ void MIDICableUSB::sendSysex(const uint8_t* data, int32_t len) { uint32_t packed = ((uint32_t)byte2 << 24) | ((uint32_t)byte1 << 16) | ((uint32_t)byte0 << 8) | status; connectedDevice->bufferMessage(packed); } + + return Error::NONE; } bool MIDICableUSB::wantsToOutputMIDIOnChannel(MIDIMessage message, int32_t filter) const { diff --git a/src/deluge/io/midi/cable_types/usb_common.h b/src/deluge/io/midi/cable_types/usb_common.h index ebeaed9e5e..56ac3cf31d 100644 --- a/src/deluge/io/midi/cable_types/usb_common.h +++ b/src/deluge/io/midi/cable_types/usb_common.h @@ -28,10 +28,11 @@ class MIDICableUSB : public MIDICable { [[nodiscard]] bool wantsToOutputMIDIOnChannel(MIDIMessage message, int32_t filter) const override; - void sendMessage(MIDIMessage message) override; - void sendSysex(const uint8_t* data, int32_t len) override; - size_t sendBufferSpace() const override; + [[nodiscard]] Error sendMessage(MIDIMessage message) override; + [[nodiscard]] Error sendSysex(const uint8_t* data, int32_t len) override; + [[nodiscard]] size_t sendBufferSpace() const override; + void checkIncomingSysex(uint8_t const* msg, int32_t ip, int32_t d); void connectedNow(int32_t midiDeviceNum); void sendMCMsNowIfNeeded(); uint8_t needsToSendMCMs; diff --git a/src/deluge/io/midi/device_specific/specific_midi_device.cpp b/src/deluge/io/midi/device_specific/specific_midi_device.cpp index f3be4b8698..401549999b 100644 --- a/src/deluge/io/midi/device_specific/specific_midi_device.cpp +++ b/src/deluge/io/midi/device_specific/specific_midi_device.cpp @@ -31,9 +31,12 @@ SpecificMidiDeviceType getSpecificMidiDeviceType(uint16_t vendorId, uint16_t pro void iterateAndCallSpecificDeviceHook(MIDICableUSBHosted::Hook hook) { using namespace MIDIDeviceManager; - for (int32_t i = 0; i < hostedMIDIDevices.getNumElements(); i++) { - auto* specificDevice = static_cast(hostedMIDIDevices.getElement(i)); + if (rootUSB == nullptr || rootUSB->getType() != RootComplexType::RC_USB_HOST) { + return; + } - specificDevice->callHook(hook); + for (auto& c : rootUSB->getCables()) { + auto& cable = static_cast(c); + cable.callHook(hook); } } diff --git a/src/deluge/io/midi/midi_device.h b/src/deluge/io/midi/midi_device.h index b59c334dd7..1aba6abaf1 100644 --- a/src/deluge/io/midi/midi_device.h +++ b/src/deluge/io/midi/midi_device.h @@ -115,13 +115,13 @@ class MIDICable { /// @{ /// Send a MIDI message - virtual void sendMessage(MIDIMessage message) = 0; + [[nodiscard]] virtual Error sendMessage(MIDIMessage message) = 0; /// Send a chunk of SYSEX data. /// /// @param data Data to send. Should include the 0xf0 and 0xf7 start/stop bytes. /// @param len Number of bytes in data, including the start/stop bytes. - virtual void sendSysex(const uint8_t* data, int32_t len) = 0; + [[nodiscard]] virtual Error sendSysex(const uint8_t* data, int32_t len) = 0; /// Get the number of bytes available in the send buffer. [[nodiscard]] virtual size_t sendBufferSpace() const = 0; diff --git a/src/deluge/io/midi/midi_device_manager.cpp b/src/deluge/io/midi/midi_device_manager.cpp index af6ee3e6e4..390d1eb2b9 100644 --- a/src/deluge/io/midi/midi_device_manager.cpp +++ b/src/deluge/io/midi/midi_device_manager.cpp @@ -20,22 +20,25 @@ #include "gui/menu_item/mpe/zone_num_member_channels.h" #include "gui/ui/sound_editor.h" #include "hid/display/display.h" -#include "io/midi/cable_types/din.h" +#include "io/midi/cable_types/usb_common.h" #include "io/midi/cable_types/usb_device_cable.h" +#include "io/midi/cable_types/usb_hosted.h" #include "io/midi/device_specific/specific_midi_device.h" #include "io/midi/midi_device.h" #include "io/midi/midi_engine.h" +#include "io/midi/root_complex/usb_peripheral.h" +#include "io/usb/usb_state.h" #include "mem_functions.h" #include "memory/general_memory_allocator.h" #include "storage/storage_manager.h" #include "util/container/vector/named_thing_vector.h" #include "util/misc.h" +using namespace deluge::io::usb; + extern "C" { #include "RZA1/usb/r_usb_basic/src/driver/inc/r_usb_basic_define.h" #include "drivers/uart/uart.h" - -extern uint8_t anyUSBSendingStillHappening[]; } #pragma GCC diagnostic push // This is supported by GCC and other compilers should error (not warn), so turn off for this file @@ -48,8 +51,6 @@ PLACE_INTERNAL_FRUNK ConnectedUSBMIDIDevice connectedUSBMIDIDevices[USB_NUM_USBI namespace MIDIDeviceManager { -NamedThingVector hostedMIDIDevices{__builtin_offsetof(MIDICableUSBHosted, name)}; - bool differentiatingInputsByDevice = true; struct USBDev { @@ -59,12 +60,8 @@ struct USBDev { }; std::array usbDeviceCurrentlyBeingSetUp{}; -// This class represents a thing you can send midi too, -// the virtual cable is an implementation detail -MIDICableUSBUpstream upstreamUSBMIDICable1{0}; -MIDICableUSBUpstream upstreamUSBMIDICable2{1}; -MIDICableUSBUpstream upstreamUSBMIDICable3{2}; -MIDICableDINPorts dinMIDIPorts{}; +DINRootComplex rootDin{}; +MIDIRootComplex* rootUSB{nullptr}; uint8_t lowestLastMemberChannelOfLowerZoneOnConnectedOutput = 15; uint8_t highestLastMemberChannelOfUpperZoneOnConnectedOutput = 0; @@ -73,18 +70,21 @@ bool anyChangesToSave = false; // Gets called within UITimerManager, which may get called during SD card routine. void slowRoutine() { - upstreamUSBMIDICable1.sendMCMsNowIfNeeded(); - upstreamUSBMIDICable2.sendMCMsNowIfNeeded(); - // port3 is not used for channel data - - for (int32_t d = 0; d < hostedMIDIDevices.getNumElements(); d++) { - MIDICableUSBHosted* device = (MIDICableUSBHosted*)hostedMIDIDevices.getElement(d); - device->sendMCMsNowIfNeeded(); - - // This routine placed here because for whatever reason we can't send sysex from hostedDeviceConfigured - if (device->freshly_connected) { - device->hookOnConnected(); - device->freshly_connected = false; // Must be set to false here or the hook will run forever + if (!rootUSB) { + // Nothing to do if there's no USB device connected. + return; + } + + for (auto& usbCable : rootUSB->getCables()) { + auto& cable = static_cast(usbCable); + cable.sendMCMsNowIfNeeded(); + + if (rootUSB->getType() == RootComplexType::RC_USB_HOST) { + auto& hostCable = static_cast(cable); + if (hostCable.freshly_connected) { + hostCable.hookOnConnected(); + hostCable.freshly_connected = false; + } } } } @@ -105,6 +105,12 @@ extern "C" void giveDetailsOfDeviceBeingSetUp(int32_t ip, char const* name, uint // name can be NULL, or an empty String MIDICableUSBHosted* getOrCreateHostedMIDIDeviceFromDetails(String* name, uint16_t vendorId, uint16_t productId) { + MIDIRootComplexUSBHosted* root = getHosted(); + if (getHosted() == nullptr) { + return nullptr; + } + + auto& hostedMIDIDevices = root->getHostedMIDIDevices(); // Do we know any details about this device already? @@ -121,7 +127,7 @@ MIDICableUSBHosted* getOrCreateHostedMIDIDeviceFromDetails(String* name, uint16_ auto* device = static_cast(hostedMIDIDevices.getElement(i)); // Update vendor and product id, if we have those - if (vendorId) { + if (vendorId != 0u) { device->vendorId = vendorId; device->productId = productId; } @@ -213,14 +219,13 @@ void recountSmallestMPEZones() { lowestLastMemberChannelOfLowerZoneOnConnectedOutput = 15; highestLastMemberChannelOfUpperZoneOnConnectedOutput = 0; - recountSmallestMPEZonesForCable(upstreamUSBMIDICable1); - recountSmallestMPEZonesForCable(upstreamUSBMIDICable2); - recountSmallestMPEZonesForCable(dinMIDIPorts); - - for (int32_t d = 0; d < hostedMIDIDevices.getNumElements(); d++) { - MIDICableUSBHosted* cable = (MIDICableUSBHosted*)hostedMIDIDevices.getElement(d); - recountSmallestMPEZonesForCable(*cable); + if (rootUSB) { + for (auto& cable : rootUSB->getCables()) { + recountSmallestMPEZonesForCable(cable); + } } + + recountSmallestMPEZonesForCable(rootDin.cable); } // Create the midi device configuration and add to the USB midi array @@ -293,19 +298,22 @@ extern "C" void configuredAsPeripheral(int32_t ip) { // Leave this - we'll use this device for all upstream ports ConnectedUSBMIDIDevice* connectedDevice = &connectedUSBMIDIDevices[ip][0]; + auto* root = new MIDIRootComplexUSBPeripheral(); + MIDIDeviceManager::setUSBRoot(root); + // add second port here connectedDevice->setup(); - connectedDevice->cable[0] = &upstreamUSBMIDICable1; - connectedDevice->cable[1] = &upstreamUSBMIDICable2; - connectedDevice->cable[2] = &upstreamUSBMIDICable3; + for (auto i = 0; i < 3; ++i) { + auto* cable = static_cast(root->getCable(i)); + cable->connectedNow(0); + connectedDevice->cable[i] = cable; + } + connectedDevice->maxPortConnected = 2; connectedDevice->canHaveMIDISent = 1; anyUSBSendingStillHappening[ip] = 0; // Initialize this. There's obviously nothing sending yet right now. - upstreamUSBMIDICable1.connectedNow(0); - upstreamUSBMIDICable2.connectedNow(0); - upstreamUSBMIDICable3.connectedNow(0); recountSmallestMPEZones(); } @@ -313,11 +321,10 @@ extern "C" void detachedAsPeripheral(int32_t ip) { // will need to reset all devices if more are added int32_t ports = connectedUSBMIDIDevices[ip][0].maxPortConnected; for (int32_t i = 0; i <= ports; i++) { + auto* cable = connectedUSBMIDIDevices[ip][0].cable[i]; + cable->connectionFlags = 0; connectedUSBMIDIDevices[ip][0].cable[i] = nullptr; } - upstreamUSBMIDICable1.connectionFlags = 0; - upstreamUSBMIDICable2.connectionFlags = 0; - upstreamUSBMIDICable3.connectionFlags = 0; anyUSBSendingStillHappening[ip] = 0; // Reset this again. Been meaning to do this, and can no longer quite remember // reason or whether technically essential, but adds to safety at least. @@ -345,17 +352,27 @@ MIDICable* readDeviceReferenceFromFile(Deserializer& reader) { } else if (!strcmp(tagName, "port")) { char const* port = reader.readTagOrAttributeValue(); - if (!strcmp(port, "upstreamUSB")) { - device = &upstreamUSBMIDICable1; - } - else if (!strcmp(port, "upstreamUSB2")) { - device = &upstreamUSBMIDICable2; - } - else if (!strcmp(port, "upstreamUSB3")) { - device = &upstreamUSBMIDICable3; + constexpr char const* const kUpstreamUSB = "upstreamUSB"; + constexpr auto kUpstreamUSBLen = (std::string{kUpstreamUSB}).length(); + + if (rootUSB && rootUSB->getType() == RootComplexType::RC_USB_PERIPHERAL + && !strncmp(kUpstreamUSB, port, strlen(kUpstreamUSB))) { + switch (port[kUpstreamUSBLen]) { + case '\0': + device = rootUSB->getCable(0); + break; + case '2': + device = rootUSB->getCable(1); + break; + case '3': + device = rootUSB->getCable(2); + break; + default: + break; + } } else if (!strcmp(port, "din")) { - device = &dinMIDIPorts; + device = &rootDin.cable; } } @@ -382,17 +399,19 @@ static MIDICable* readCableFromFlash(uint8_t const* memory) { if (vendorId == VENDOR_ID_NONE) { cable = nullptr; } - else if (vendorId == VENDOR_ID_UPSTREAM_USB) { - cable = &upstreamUSBMIDICable1; - } - else if (vendorId == VENDOR_ID_UPSTREAM_USB2) { - cable = &upstreamUSBMIDICable2; - } - else if (vendorId == VENDOR_ID_UPSTREAM_USB3) { - cable = &upstreamUSBMIDICable3; + else if (rootUSB && rootUSB->getType() == RootComplexType::RC_USB_PERIPHERAL) { + if (vendorId == VENDOR_ID_UPSTREAM_USB) { + cable = rootUSB->getCable(0); + } + else if (vendorId == VENDOR_ID_UPSTREAM_USB2) { + cable = rootUSB->getCable(1); + } + else if (vendorId == VENDOR_ID_UPSTREAM_USB3) { + cable = rootUSB->getCable(2); + } } else if (vendorId == VENDOR_ID_DIN) { - cable = &dinMIDIPorts; + cable = &rootDin.cable; } else { uint16_t productId = *(uint16_t const*)(memory + 2); @@ -428,12 +447,12 @@ void writeDevicesToFile() { } anyChangesToSave = false; - bool anyWorthWritting = dinMIDIPorts.worthWritingToFile() || upstreamUSBMIDICable1.worthWritingToFile() - || upstreamUSBMIDICable2.worthWritingToFile(); - if (!anyWorthWritting) { - for (int32_t d = 0; d < hostedMIDIDevices.getNumElements(); d++) { - MIDICableUSBHosted* device = (MIDICableUSBHosted*)hostedMIDIDevices.getElement(d); - if (device->worthWritingToFile()) { + bool anyWorthWritting = rootDin.cable.worthWritingToFile(); + + // First, see if it's even worth writing anything + if (!anyWorthWritting && rootUSB != nullptr) { + for (auto& cable : rootUSB->getCables()) { + if (cable.worthWritingToFile()) { anyWorthWritting = true; break; } @@ -457,23 +476,33 @@ void writeDevicesToFile() { writer.writeEarliestCompatibleFirmwareVersion("4.0.0"); writer.writeOpeningTagEnd(); - if (dinMIDIPorts.worthWritingToFile()) { - dinMIDIPorts.writeToFile(writer, "dinPorts"); - } - if (upstreamUSBMIDICable1.worthWritingToFile()) { - upstreamUSBMIDICable1.writeToFile(writer, "upstreamUSBDevice"); - } - if (upstreamUSBMIDICable2.worthWritingToFile()) { - upstreamUSBMIDICable2.writeToFile(writer, "upstreamUSBDevice2"); + if (rootDin.cable.worthWritingToFile()) { + rootDin.cable.writeToFile(writer, "dinPorts"); } - for (int32_t d = 0; d < hostedMIDIDevices.getNumElements(); d++) { - MIDICableUSBHosted* device = (MIDICableUSBHosted*)hostedMIDIDevices.getElement(d); - if (device->worthWritingToFile()) { - device->writeToFile(writer, "hostedUSBDevice"); + if (rootUSB != nullptr) { + switch (rootUSB->getType()) { + case RootComplexType::RC_DIN: + // illegal + break; + case RootComplexType::RC_USB_PERIPHERAL: + if (rootUSB->getCable(0)->worthWritingToFile()) { + rootUSB->getCable(0)->writeToFile(writer, "upstreamUSBDevice"); + } + if (rootUSB->getCable(1)->worthWritingToFile()) { + rootUSB->getCable(1)->writeToFile(writer, "upstreamUSBDevice2"); + } + break; + case RootComplexType::RC_USB_HOST: + for (auto& cable : rootUSB->getCables()) { + auto& cableHosted = static_cast(cable); + if (cableHosted.worthWritingToFile()) { + cableHosted.writeToFile(writer, "hostedUSBDevice"); + } + cableHosted.hookOnWriteHostedDeviceToFile(); + } + break; } - // Stow this for the hook point later - device->hookOnWriteHostedDeviceToFile(); } writer.writeClosingTag("midiDevices"); @@ -516,19 +545,32 @@ void readDevicesFromFile() { char const* tagName; while (*(tagName = reader.readNextTagOrAttributeName())) { if (!strcmp(tagName, "dinPorts")) { - dinMIDIPorts.readFromFile(reader); - } - else if (!strcmp(tagName, "upstreamUSBDevice")) { - upstreamUSBMIDICable1.readFromFile(reader); - } - else if (!strcmp(tagName, "upstreamUSBDevice2")) { - upstreamUSBMIDICable2.readFromFile(reader); - } - else if (!strcmp(tagName, "upstreamUSBDevice3")) { - upstreamUSBMIDICable3.readFromFile(reader); + rootDin.cable.readFromFile(reader); } - else if (!strcmp(tagName, "hostedUSBDevice")) { - readAHostedDeviceFromFile(reader); + else if (rootUSB != nullptr) { + auto type = rootUSB->getType(); + if (type == RootComplexType::RC_USB_PERIPHERAL) { + constexpr char const* const kUpstreamUSB = "upstreamUSBDevice"; + constexpr auto kUpstreamUSBLen = (std::string{kUpstreamUSB}).length(); + + if (strncmp(kUpstreamUSB, tagName, strlen(kUpstreamUSB)) == 0) { + + switch (tagName[kUpstreamUSBLen]) { + case '\0': + rootUSB->getCable(0)->readFromFile(reader); + break; + case '2': + rootUSB->getCable(1)->readFromFile(reader); + break; + case '3': + rootUSB->getCable(3)->readFromFile(reader); + break; + } + } + } + else if (type == RootComplexType::RC_USB_HOST && strcmp(tagName, "hostedUSBDevice") == 0) { + readAHostedDeviceFromFile(reader); + } } reader.exitTag(); @@ -543,6 +585,7 @@ void readDevicesFromFile() { successfullyReadDevicesFromFile = true; } +/// Read a single hosted USB device. This assumes the root complex is a MIDIRootComplexUSBHosted void readAHostedDeviceFromFile(Deserializer& reader) { MIDICableUSBHosted* device = nullptr; @@ -618,13 +661,29 @@ void readAHostedDeviceFromFile(Deserializer& reader) { if (device) {} } +void setUSBRoot(MIDIRootComplex* root) { + delete rootUSB; + rootUSB = root; +} + +MIDIRootComplexUSBHosted* getHosted() { + if (rootUSB == nullptr) { + return nullptr; + } + if (rootUSB->getType() != RootComplexType::RC_USB_HOST) { + return nullptr; + } + + return static_cast(rootUSB); +} + } // namespace MIDIDeviceManager void ConnectedUSBMIDIDevice::bufferMessage(uint32_t fullMessage) { uint32_t queued = ringBufWriteIdx - ringBufReadIdx; if (queued > 16) { if (!anyUSBSendingStillHappening[0]) { - midiEngine.flushUSBMIDIOutput(); + midiEngine.flushMIDI(); } queued = ringBufWriteIdx - ringBufReadIdx; } @@ -636,7 +695,7 @@ void ConnectedUSBMIDIDevice::bufferMessage(uint32_t fullMessage) { sendDataRingBuf[ringBufWriteIdx & MIDI_SEND_RING_MASK] = fullMessage; ringBufWriteIdx++; - anythingInUSBOutputBuffer = true; + deluge::io::usb::anythingInUSBOutputBuffer = true; } bool ConnectedUSBMIDIDevice::hasBufferedSendData() { diff --git a/src/deluge/io/midi/midi_device_manager.h b/src/deluge/io/midi/midi_device_manager.h index 1d9aa5e45b..64bd5bbfd4 100644 --- a/src/deluge/io/midi/midi_device_manager.h +++ b/src/deluge/io/midi/midi_device_manager.h @@ -18,9 +18,10 @@ #pragma once #ifdef __cplusplus #include "definitions_cxx.hpp" -#include "io/midi/cable_types/din.h" #include "io/midi/cable_types/usb_common.h" #include "io/midi/cable_types/usb_device_cable.h" +#include "io/midi/root_complex/din.h" +#include "io/midi/root_complex/usb_hosted.h" #include "util/container/vector/named_thing_vector.h" class Serializer; class Deserializer; @@ -111,14 +112,16 @@ void writeDevicesToFile(); void readAHostedDeviceFromFile(Deserializer& reader); void readDevicesFromFile(); -extern MIDICableUSBUpstream upstreamUSBMIDICable1; -extern MIDICableUSBUpstream upstreamUSBMIDICable2; -extern MIDICableUSBUpstream upstreamUSBMIDICable3; -extern MIDICableDINPorts dinMIDIPorts; +/// Configure the MIDI root complex. Should be an instance of MIDIRootComplexUSBHosted or MIDIRootCOmplexUSBPeripheral, +/// or nullptr (representing disconnection). +void setUSBRoot(MIDIRootComplex* root); -extern bool differentiatingInputsByDevice; +extern DINRootComplex rootDin; +extern MIDIRootComplex* rootUSB; + +MIDIRootComplexUSBHosted* getHosted(); -extern NamedThingVector hostedMIDIDevices; +extern bool differentiatingInputsByDevice; extern uint8_t lowestLastMemberChannelOfLowerZoneOnConnectedOutput; extern uint8_t highestLastMemberChannelOfUpperZoneOnConnectedOutput; diff --git a/src/deluge/io/midi/midi_engine.cpp b/src/deluge/io/midi/midi_engine.cpp index feb675ae76..b3ab7f1b1e 100644 --- a/src/deluge/io/midi/midi_engine.cpp +++ b/src/deluge/io/midi/midi_engine.cpp @@ -17,6 +17,7 @@ #include "io/midi/midi_engine.h" #include "definitions_cxx.hpp" +#include "deluge/io/usb/usb_state.h" #include "gui/l10n/l10n.h" #include "gui/ui/sound_editor.h" #include "hid/display/display.h" @@ -32,187 +33,16 @@ #include "storage/smsysex.h" #include "version.h" +using namespace deluge::io::usb; + extern "C" { #include "RZA1/uart/sio_char.h" -volatile uint32_t usbLock = 0; -void usb_cstd_usb_task(); - -#include "RZA1/system/iodefine.h" -#include "RZA1/usb/r_usb_basic/r_usb_basic_if.h" -#include "RZA1/usb/r_usb_basic/src/hw/inc/r_usb_bitdefine.h" -#include "drivers/usb/userdef/r_usb_pmidi_config.h" - -#include "RZA1/usb/r_usb_hmidi/src/inc/r_usb_hmidi.h" -#include "RZA1/usb/userdef/r_usb_hmidi_config.h" - -extern uint16_t g_usb_peri_connected; - -uint8_t stopSendingAfterDeviceNum[USB_NUM_USBIP]; -uint8_t usbDeviceNumBeingSentToNow[USB_NUM_USBIP]; -uint8_t anyUSBSendingStillHappening[USB_NUM_USBIP]; - -usb_utr_t g_usb_midi_send_utr[USB_NUM_USBIP]; -usb_utr_t g_usb_midi_recv_utr[USB_NUM_USBIP][MAX_NUM_USB_MIDI_DEVICES]; - -extern uint16_t g_usb_hmidi_tmp_ep_tbl[USB_NUM_USBIP][MAX_NUM_USB_MIDI_DEVICES][(USB_EPL * 2) + 1]; - -extern usb_utr_t* g_p_usb_pipe[USB_MAX_PIPE_NO + 1u]; - -usb_regadr_t usb_hstd_get_usb_ip_adr(uint16_t ipno); -void change_destination_of_send_pipe(usb_utr_t* ptr, uint16_t pipe, uint16_t* tbl, int32_t sq); -void usb_send_start_rohan(usb_utr_t* ptr, uint16_t pipe, uint8_t const* data, int32_t size); -void usb_receive_start_rohan_midi(uint16_t pipe); -void usb_pstd_set_stall(uint16_t pipe); -void usb_cstd_set_nak(usb_utr_t* ptr, uint16_t pipe); -void hw_usb_clear_pid(usb_utr_t* ptr, uint16_t pipeno, uint16_t data); -uint16_t hw_usb_read_pipectr(usb_utr_t* ptr, uint16_t pipeno); - -void flushUSBMIDIToHostedDevice(int32_t ip, int32_t d, bool resume = false); - -uint8_t currentDeviceNumWithSendPipe[USB_NUM_USBIP][2] = { - MAX_NUM_USB_MIDI_DEVICES, MAX_NUM_USB_MIDI_DEVICES}; // One without, and one with, interrupt endpoints - -// We now bypass calling this for successful as peripheral on A1 (see usb_pstd_bemp_pipe_process_rohan_midi()) -void usbSendCompleteAsHost(int32_t ip) { - - int32_t midiDeviceNum = usbDeviceNumBeingSentToNow[ip]; - - ConnectedUSBMIDIDevice* connectedDevice = &connectedUSBMIDIDevices[ip][midiDeviceNum]; - - connectedDevice->numBytesSendingNow = 0; // We just do this instead from caller on A1 (see comment above) - - // check if there was more to send on the same device, then resume sending - bool has_more = connectedDevice->consumeSendData(); - if (has_more) { - // TODO: do some cooperative scheduling here. so if there is a flood of data - // on connected device 1 and we just want to send a few notes on device 2, - // make sure device 2 ges a fair shot now and then - - flushUSBMIDIToHostedDevice(ip, midiDeviceNum, true); - return; - } - - // If that was the last device we were going to send to, that send's been done, so we can just get out. - if (midiDeviceNum == stopSendingAfterDeviceNum[ip]) { - anyUSBSendingStillHappening[ip] = 0; - return; - } - - while (true) { - midiDeviceNum++; - if (midiDeviceNum >= MAX_NUM_USB_MIDI_DEVICES) { - midiDeviceNum -= MAX_NUM_USB_MIDI_DEVICES; - } - connectedDevice = &connectedUSBMIDIDevices[ip][midiDeviceNum]; - if (connectedDevice->cable[0] && connectedDevice->numBytesSendingNow) { - // If here, we got a connected device, so flush - flushUSBMIDIToHostedDevice(ip, midiDeviceNum); - return; - } - if (midiDeviceNum == stopSendingAfterDeviceNum[ip]) { // If reached end of devices and last one got disconnected - // in the interim (very rare) - usbDeviceNumBeingSentToNow[ip] = stopSendingAfterDeviceNum[ip]; - anyUSBSendingStillHappening[ip] = 0; - return; - } - } -} - -// We now bypass calling this for successful as peripheral on A1 (see usb_pstd_bemp_pipe_process_rohan_midi()) -void usbSendCompletePeripheralOrA1(usb_utr_t* p_mess, uint16_t data1, uint16_t data2) { - - // If error, forget about device. - // No actually don't - sometimes there'll be an error if another device connected or disconnected from hub during - // fast MIDI sending. This seems to happen even though I've stopped it from setting up or down the out-pipe as it - // goes - if (p_mess->status == USB_DATA_ERR) { - uartPrintln("send error!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); - // g_usb_host_connected[deviceNum] = 0; - } - -#if USB_NUM_USBIP == 1 - int32_t ip = 0; -#else - int32_t ip = p_mess->ip; -#endif - - usbSendCompleteAsHost(ip); -} - -void usbSendCompleteAsPeripheral(int32_t ip) { - ConnectedUSBMIDIDevice* connectedDevice = &connectedUSBMIDIDevices[ip][0]; - connectedDevice->numBytesSendingNow = 0; // Even easier! - // - - // I think this could happen as part of a detach see detachedAsPeripheral() - if (anyUSBSendingStillHappening[ip] == 0) { - return; - } - - bool has_more = connectedDevice->consumeSendData(); - if (has_more) { - // this is already the case: - // anyUSBSendingStillHappening[ip] = 1; - - g_usb_midi_send_utr[ip].tranlen = connectedDevice->numBytesSendingNow; - g_usb_midi_send_utr[ip].p_tranadr = connectedDevice->dataSendingNow; - - usb_send_start_rohan(NULL, USB_CFG_PMIDI_BULK_OUT, connectedDevice->dataSendingNow, - connectedDevice->numBytesSendingNow); - } - else { - // this effectively serves as a lock, does the sending part of the device, including the read part of the - // ring buffer "belong" to ongoing/scheduled interrupts. Document this better. - anyUSBSendingStillHappening[0] = 0; - } -} - -void usbReceiveComplete(int32_t ip, int32_t deviceNum, int32_t tranlen) { - ConnectedUSBMIDIDevice* connectedDevice = &connectedUSBMIDIDevices[ip][deviceNum]; - - connectedDevice->numBytesReceived = 64 - tranlen; // Seems wack, but yet, tranlen is now how many bytes didn't get - // received out of the original transfer size - // Warning - sometimes (with a Teensy, e.g. my knob box), length will be 0. Not sure why - but we need to cope with - // that case. - - connectedDevice->currentlyWaitingToReceive = 0; // Take note that we need to set up another receive -} - -void usbReceiveCompletePeripheralOrA1(usb_utr_t* p_mess, uint16_t data1, uint16_t data2) { - -#if USB_NUM_USBIP == 1 - int32_t ip = 0; -#else - int32_t ip = p_mess->ip; -#endif - - if (p_mess->status == USB_DATA_ERR) { - return; // Can happen if user disconnects device - totally normal - } - - int32_t deviceNum = p_mess - &g_usb_midi_recv_utr[ip][0]; - - // Are there actually any other possibilities that could happen here? Can't remember. - if (p_mess->status != USB_DATA_SHT) { - uartPrint("status: "); - uartPrintNumber(p_mess->status); - } - - usbReceiveComplete(ip, deviceNum, p_mess->tranlen); -} - -uint32_t timeLastBRDY[USB_NUM_USBIP]; - -void brdyOccurred(int32_t ip) { - timeLastBRDY[ip] = DMACnNonVolatile(SSI_TX_DMA_CHANNEL).CRSA_n; // Reading this not as volatile works fine -} +extern void usb_cstd_usb_task(); } MidiEngine midiEngine{}; -bool anythingInUSBOutputBuffer = false; - MidiEngine::MidiEngine() { numSerialMidiInput = 0; currentlyReceivingSysExSerial = false; @@ -228,187 +58,11 @@ MidiEngine::MidiEngine() { midiTakeover = MIDITakeoverMode::JUMP; midiSelectKitRow = false; - g_usb_peri_connected = 0; // Needs initializing with A2 driver - - for (int32_t ip = 0; ip < USB_NUM_USBIP; ip++) { - - // This might not be used due to the change in r_usb_hlibusbip (deluge is host) to call usbSendCompleteAsHost() - // directly and the change in r_usb_plibusbip (deluge is pheriperal) to just set some variables or it might be - // used for some other interrups like error conditions??? - // TODO: try to delet this and see if something breaks.. - g_usb_midi_send_utr[ip].complete = (usb_cb_t)usbSendCompletePeripheralOrA1; - - g_usb_midi_send_utr[ip].p_setup = 0; /* Setup message address set */ - g_usb_midi_send_utr[ip].segment = USB_TRAN_END; - g_usb_midi_send_utr[ip].ip = ip; - g_usb_midi_send_utr[ip].ipp = usb_hstd_get_usb_ip_adr(ip); - - for (int32_t d = 0; d < MAX_NUM_USB_MIDI_DEVICES; d++) { - g_usb_midi_recv_utr[ip][d].p_tranadr = connectedUSBMIDIDevices[ip][d].receiveData; - g_usb_midi_recv_utr[ip][d].complete = (usb_cb_t)usbReceiveCompletePeripheralOrA1; - - g_usb_midi_recv_utr[ip][d].p_setup = 0; /* Setup message address set */ - g_usb_midi_recv_utr[ip][d].segment = USB_TRAN_END; - g_usb_midi_recv_utr[ip][d].ip = ip; - g_usb_midi_recv_utr[ip][d].ipp = usb_hstd_get_usb_ip_adr(ip); - } - } + usbSetup(); eventStackTop_ = eventStack_.begin(); } -void flushUSBMIDIToHostedDevice(int32_t ip, int32_t d, bool resume) { - - ConnectedUSBMIDIDevice* connectedDevice = &connectedUSBMIDIDevices[ip][d]; - // there was an assumption that the pipe wouldn't have changed if we were resuming a transfer but that has turned - // out not to be true if hubs are involved. A hub transaction seems to be able to run before the - // usbSendCompleteAsHost interrupt is called and changes the pipe, and then the next write doesn't go anywhere - // useful - int32_t pipeNumber = g_usb_hmidi_tmp_ep_tbl[USB_CFG_USE_USBIP][d][0]; - g_usb_midi_send_utr[USB_CFG_USE_USBIP].keyword = pipeNumber; - g_usb_midi_send_utr[USB_CFG_USE_USBIP].tranlen = connectedDevice->numBytesSendingNow; - g_usb_midi_send_utr[USB_CFG_USE_USBIP].p_tranadr = connectedDevice->dataSendingNow; - - usbDeviceNumBeingSentToNow[USB_CFG_USE_USBIP] = d; - - int32_t isInterrupt = (pipeNumber == USB_CFG_HMIDI_INT_SEND); - - if (d != currentDeviceNumWithSendPipe[USB_CFG_USE_USBIP][isInterrupt]) { - currentDeviceNumWithSendPipe[USB_CFG_USE_USBIP][isInterrupt] = d; - change_destination_of_send_pipe(&g_usb_midi_send_utr[USB_CFG_USE_USBIP], pipeNumber, - g_usb_hmidi_tmp_ep_tbl[USB_CFG_USE_USBIP][d], connectedDevice->sq); - } - - connectedDevice->sq = !connectedDevice->sq; - - g_p_usb_pipe[pipeNumber] = &g_usb_midi_send_utr[USB_CFG_USE_USBIP]; - - usb_send_start_rohan(&g_usb_midi_send_utr[USB_CFG_USE_USBIP], pipeNumber, connectedDevice->dataSendingNow, - connectedDevice->numBytesSendingNow); -} - -int32_t MidiEngine::getPotentialNumConnectedUSBMIDIDevices(int32_t ip) { - bool potentiallyAHost = (g_usb_usbmode == USB_HOST); - // bool aPeripheral = g_usb_peri_connected; - return potentiallyAHost ? MAX_NUM_USB_MIDI_DEVICES : 1; -} - -// Warning - this will sometimes (not always) be called in an ISR -void MidiEngine::flushUSBMIDIOutput() { - - if (usbLock) { - return; - } - - anythingInUSBOutputBuffer = false; - - // is this still relevant? anyUSBSendingStillHappening[ip] acts as the lock between routine and interrupt - // on the sending side. all other uses of usbLock seems to be about _receiving_. Can there be a conflict - // between sending and receiving as well?? - usbLock = 1; - - for (int32_t ip = 0; ip < USB_NUM_USBIP; ip++) { - if (anyUSBSendingStillHappening[ip]) { - // still sending, call me later maybe - anythingInUSBOutputBuffer = true; - continue; - } - - bool potentiallyAHost = (g_usb_usbmode == USB_HOST); - bool aPeripheral = g_usb_peri_connected; - - if (aPeripheral) { - ConnectedUSBMIDIDevice* connectedDevice = &connectedUSBMIDIDevices[ip][0]; - - if (!connectedDevice->consumeSendData()) { - continue; - } - - g_usb_midi_send_utr[ip].keyword = USB_CFG_PMIDI_BULK_OUT; - g_usb_midi_send_utr[ip].tranlen = connectedDevice->numBytesSendingNow; - g_usb_midi_send_utr[ip].p_tranadr = connectedDevice->dataSendingNow; - - usbDeviceNumBeingSentToNow[ip] = 0; - anyUSBSendingStillHappening[ip] = 1; - - g_p_usb_pipe[USB_CFG_PMIDI_BULK_OUT] = &g_usb_midi_send_utr[ip]; - usb_send_start_rohan(NULL, USB_CFG_PMIDI_BULK_OUT, connectedDevice->dataSendingNow, - connectedDevice->numBytesSendingNow); - - // when done, usbSendCompleteAsPeripheral() will be called in an interrupt - } - - else if (potentiallyAHost) { - // This next bit was written with multiple devices on hubs in mind, but seems to work for a single MIDI - // device too - - int32_t midiDeviceNumToSendTo = currentDeviceNumWithSendPipe[ip][0]; // This will do - if (midiDeviceNumToSendTo >= MAX_NUM_USB_MIDI_DEVICES) { - midiDeviceNumToSendTo = 0; // In case it was set to "none", I think - } - - int32_t newStopSendingAfter = midiDeviceNumToSendTo - 1; - if (newStopSendingAfter < 0) { - newStopSendingAfter += MAX_NUM_USB_MIDI_DEVICES; - } - - // Make sure that's on a connected device - it probably would be... - while (true) { - ConnectedUSBMIDIDevice* connectedDevice = &connectedUSBMIDIDevices[ip][midiDeviceNumToSendTo]; - if (connectedDevice->cable[0] && connectedDevice->hasBufferedSendData()) { - break; // We found a connected one - } - if (midiDeviceNumToSendTo == newStopSendingAfter) { - goto getOut; // If back where we started, none are connected. Could this really happen? Probably. - } - midiDeviceNumToSendTo++; - if (midiDeviceNumToSendTo >= MAX_NUM_USB_MIDI_DEVICES) { - midiDeviceNumToSendTo = 0; // Wrap back to start of list - } - } - - // Stop after a device which we know is connected - while (true) { - ConnectedUSBMIDIDevice* connectedDevice = &connectedUSBMIDIDevices[ip][newStopSendingAfter]; - - if (connectedDevice->cable[0] && connectedDevice->hasBufferedSendData()) { - break; // We found a connected one - } - - newStopSendingAfter--; - if (newStopSendingAfter < 0) { - newStopSendingAfter += MAX_NUM_USB_MIDI_DEVICES; - } - } - - // Copy the buffers for all devices - int32_t d = midiDeviceNumToSendTo; - while (true) { - ConnectedUSBMIDIDevice* connectedDevice = &connectedUSBMIDIDevices[ip][d]; - - if (connectedDevice->cable[0]) { - connectedDevice->consumeSendData(); - } - if (d == newStopSendingAfter) { - break; - } - d++; - if (d >= MAX_NUM_USB_MIDI_DEVICES) { - d = 0; - } - } - - stopSendingAfterDeviceNum[ip] = newStopSendingAfter; - anyUSBSendingStillHappening[ip] = 1; - - flushUSBMIDIToHostedDevice(ip, midiDeviceNumToSendTo); - } -getOut: {} - } - - usbLock = 0; -} - bool MidiEngine::anythingInOutputBuffer() { return anythingInUSBOutputBuffer || (bool)uartGetTxBufferFullnessByItem(UART_ITEM_MIDI); } @@ -514,242 +168,68 @@ void MidiEngine::sendMidi(MIDISource source, MIDIMessage message, int32_t filter } // Send serial MIDI - if (MIDIDeviceManager::dinMIDIPorts.wantsToOutputMIDIOnChannel(message, filter)) { - sendSerialMidi(message); + auto& dinCable = MIDIDeviceManager::rootDin.cable; + if (dinCable.wantsToOutputMIDIOnChannel(message, filter)) { + auto error = dinCable.sendMessage(message); + if (error != Error::NONE && error != Error::NO_ERROR_BUT_GET_OUT) { + D_PRINTLN("MIDI send error: %d", static_cast(error)); + } } --eventStackTop_; } -uint32_t setupUSBMessage(MIDIMessage message) { - // format message per USB midi spec on virtual cable 0 - uint8_t cin; - uint8_t firstByte = (message.channel & 15) | (message.statusType << 4); - - switch (firstByte) { - case 0xF2: // Position pointer - cin = 0x03; - break; - - default: - cin = message.statusType; // Good for voice commands - break; - } - - return ((uint32_t)message.data2 << 24) | ((uint32_t)message.data1 << 16) | ((uint32_t)firstByte << 8) | cin; -} - void MidiEngine::sendUsbMidi(MIDIMessage message, int32_t filter) { - // TODO: Differentiate between ports on usb midi - bool isSystemMessage = message.isSystemMessage(); - - // formats message per USB midi spec on virtual cable 0 - uint32_t fullMessage = setupUSBMessage(message); - for (int32_t ip = 0; ip < USB_NUM_USBIP; ip++) { - int32_t potentialNumDevices = getPotentialNumConnectedUSBMIDIDevices(ip); - - for (int32_t d = 0; d < potentialNumDevices; d++) { - ConnectedUSBMIDIDevice* connectedDevice = &connectedUSBMIDIDevices[ip][d]; - if (!connectedDevice->canHaveMIDISent) { - continue; - } - int32_t maxPort = connectedDevice->maxPortConnected; - for (int32_t p = 0; p <= maxPort; p++) { - // if device exists, it's not port 3 (for sysex) - if (connectedDevice->cable[p] - && connectedDevice->cable[p] != &MIDIDeviceManager::upstreamUSBMIDICable3) { - // if it's a clock (or sysex technically but we don't send that to this function) - // or if it's a message that this channel wants - if (connectedDevice->cable[p]->wantsToOutputMIDIOnChannel(message, filter)) { - - // Or with the port to add the cable number to the full message. This - // is a bit hacky but it works - uint32_t channeled_message = fullMessage | (p << 4); - connectedDevice->bufferMessage(channeled_message); - } - } - } - } + // If no USB device is connected, don't send anything. Otherwise, we send to all cables. + if (MIDIDeviceManager::rootUSB == nullptr) { + return; } -} - -// Warning - this will sometimes (not always) be called in an ISR -void MidiEngine::flushMIDI() { - flushUSBMIDIOutput(); - uartFlushIfNotSending(UART_ITEM_MIDI); -} - -void MidiEngine::sendSerialMidi(MIDIMessage message) { - - uint8_t statusByte = message.channel | (message.statusType << 4); - int32_t messageLength = bytesPerStatusMessage(statusByte); - bufferMIDIUart(statusByte); - - if (messageLength >= 2) { - bufferMIDIUart(message.data1); - if (messageLength == 3) { - bufferMIDIUart(message.data2); + for (auto& cable : MIDIDeviceManager::rootUSB->getCables()) { + if (cable.wantsToOutputMIDIOnChannel(message, filter)) { + cable.sendMessage(message); } } } -bool MidiEngine::checkIncomingSerialMidi() { - - uint8_t thisSerialByte; - uint32_t* timer = uartGetCharWithTiming(TIMING_CAPTURE_ITEM_MIDI, (char*)&thisSerialByte); - if (timer) { - // D_PRINTLN((uint32_t)thisSerialByte); - MIDICable& cable = MIDIDeviceManager::dinMIDIPorts; - - // If this is a status byte, then we have to store it as the first byte. - if (thisSerialByte & 0x80) { - - switch (thisSerialByte) { - - // If it's a realtime message, we have to obey it right now, separately from any other message it was - // inserted into the middle of - case 0xF8 ... 0xFF: - midiMessageReceived(cable, thisSerialByte >> 4, thisSerialByte & 0x0F, 0, 0, timer); - return true; - - // Or if it's a SysEx start... - case 0xF0: - currentlyReceivingSysExSerial = true; - D_PRINTLN("Sysex start"); - cable.incomingSysexBuffer[0] = thisSerialByte; - cable.incomingSysexPos = 1; - // numSerialMidiInput = 0; // This would throw away any running status stuff... - return true; - } - - // If we didn't return for any of those, then it's just a regular old status message (or Sysex stop - // message). All these will end any ongoing SysEx - - // If it was a Sysex stop, that's all we need to do - if (thisSerialByte == 0xF7) { - D_PRINTLN("Sysex end"); - if (currentlyReceivingSysExSerial) { - currentlyReceivingSysExSerial = false; - if (cable.incomingSysexPos < sizeof cable.incomingSysexBuffer) { - cable.incomingSysexBuffer[cable.incomingSysexPos++] = thisSerialByte; - midiSysexReceived(cable, cable.incomingSysexBuffer, cable.incomingSysexPos); - } - } - return true; - } +void MidiEngine::checkIncomingMidi() { + if (!usbLock) { + // Have to call this regularly, to do "callbacks" that will grab out the received data + USBAutoLock lock; + usb_cstd_usb_task(); + } - currentlyReceivingSysExSerial = false; - numSerialMidiInput = 0; + // Check incoming USB MIDI + if (MIDIDeviceManager::rootUSB != nullptr) { + auto error = MIDIDeviceManager::rootUSB->poll(); + if (error != Error::NONE && error != Error::NO_ERROR_BUT_GET_OUT) { + D_PRINTLN("USB poll error: %d\n", static_cast(error)); } + } - // If not a status byte... - else { - // If we're currently receiving a SysEx, don't throw it away - if (currentlyReceivingSysExSerial) { - // TODO: allocate a GMA buffer to some bigger size - if (cable.incomingSysexPos < sizeof cable.incomingSysexBuffer) { - cable.incomingSysexBuffer[cable.incomingSysexPos++] = thisSerialByte; - } - D_PRINTLN("Sysex: %d", thisSerialByte); - return true; - } - - // Ensure that we're not writing to the first byte of the buffer - if (numSerialMidiInput == 0) { - return true; - } + // Check incoming Serial MIDI + for (int32_t i = 0; i < 12; i++) { + auto error = MIDIDeviceManager::rootDin.poll(); + if (error == Error::NO_ERROR_BUT_GET_OUT) { + break; } - - serialMidiInput[numSerialMidiInput] = thisSerialByte; - numSerialMidiInput++; - - // If we've received the whole MIDI message, deal with it - if (bytesPerStatusMessage(serialMidiInput[0]) == numSerialMidiInput) { - uint8_t channel = serialMidiInput[0] & 0x0F; - - midiMessageReceived(MIDIDeviceManager::dinMIDIPorts, serialMidiInput[0] >> 4, channel, serialMidiInput[1], - serialMidiInput[2], timer); - - // If message was more than 1 byte long, and was a voice or mode message, then allow for running status - if (numSerialMidiInput > 1 && ((serialMidiInput[0] & 0xF0) != 0xF0)) { - numSerialMidiInput = 1; - } - else { - numSerialMidiInput = 0; - } + else if (error != Error::NONE) { + D_PRINTLN("MIDI poll error: %d\n", static_cast(error)); } - return true; } - return false; } -// Lock USB before calling this! -void MidiEngine::setupUSBHostReceiveTransfer(int32_t ip, int32_t midiDeviceNum) { - connectedUSBMIDIDevices[ip][midiDeviceNum].currentlyWaitingToReceive = 1; - - int32_t pipeNumber = g_usb_hmidi_tmp_ep_tbl[USB_CFG_USE_USBIP][midiDeviceNum][USB_EPL]; - - g_usb_midi_recv_utr[USB_CFG_USE_USBIP][midiDeviceNum].keyword = pipeNumber; - g_usb_midi_recv_utr[USB_CFG_USE_USBIP][midiDeviceNum].tranlen = 64; - - g_p_usb_pipe[pipeNumber] = &g_usb_midi_recv_utr[USB_CFG_USE_USBIP][midiDeviceNum]; - - // uint16_t startTime = *TCNT[TIMER_SYSTEM_SUPERFAST]; - - usb_receive_start_rohan_midi(pipeNumber); - - /* - uint16_t endTime = *TCNT[TIMER_SYSTEM_SUPERFAST]; - uint16_t duration = endTime - startTime; - uint32_t timePassedNS = superfastTimerCountToNS(duration); - uartPrint("send setup duration, nSec: "); - uartPrintNumber(timePassedNS); - */ +// Warning - this will sometimes (not always) be called in an ISR +void MidiEngine::flushMIDI() { + if (MIDIDeviceManager::rootUSB) { + MIDIDeviceManager::rootUSB->flush(); + } + MIDIDeviceManager::rootDin.flush(); } +// Lock USB before calling this! uint8_t usbCurrentlyInitialized = false; -void MidiEngine::checkIncomingUsbSysex(uint8_t const* msg, int32_t ip, int32_t d, int32_t cableIdx) { - ConnectedUSBMIDIDevice* connected = &connectedUSBMIDIDevices[ip][d]; - if (cableIdx > connectedUSBMIDIDevices[ip][d].maxPortConnected) { - // fallback to cable 0 since we don't support more than one port on hosted devices yet - cableIdx = 0; - } - MIDICable& cable = *connectedUSBMIDIDevices[ip][d].cable[cableIdx]; - - uint8_t statusType = msg[0] & 15; - int32_t to_read = 0; - bool will_end = false; - if (statusType == 0x4) { - // sysex start or continue - if (msg[1] == 0xf0) { - cable.incomingSysexPos = 0; - } - to_read = 3; - } - else if (statusType >= 0x5 && statusType <= 0x7) { - to_read = statusType - 0x4; // read between 1-3 bytes - will_end = true; - } - - for (int32_t i = 0; i < to_read; i++) { - if (cable.incomingSysexPos >= sizeof(cable.incomingSysexBuffer)) { - // TODO: allocate a GMA buffer to some bigger size - cable.incomingSysexPos = 0; - return; // bail out - } - cable.incomingSysexBuffer[cable.incomingSysexPos++] = msg[i + 1]; - } - - if (will_end) { - if (cable.incomingSysexBuffer[0] == 0xf0) { - midiSysexReceived(cable, cable.incomingSysexBuffer, cable.incomingSysexPos); - } - cable.incomingSysexPos = 0; - } -} - bool developerSysexCodeReceived = false; void MidiEngine::midiSysexReceived(MIDICable& cable, uint8_t* data, int32_t len) { @@ -826,114 +306,6 @@ void MidiEngine::midiSysexReceived(MIDICable& cable, uint8_t* data, int32_t len) } } -extern "C" { - -extern uint8_t currentlyAccessingCard; -} - -void MidiEngine::checkIncomingUsbMidi() { - - if (!usbCurrentlyInitialized - || currentlyAccessingCard != 0) { // hack to avoid SysEx handlers clashing with other sd-card activity. - if (currentlyAccessingCard != 0) { - // D_PRINTLN("checkIncomingUsbMidi seeing currentlyAccessingCard non-zero"); - } - return; - } - - bool usbLockNow = usbLock; - - if (!usbLockNow) { - // Have to call this regularly, to do "callbacks" that will grab out the received data - usbLock = 1; - usb_cstd_usb_task(); - usbLock = 0; - } - - for (int32_t ip = 0; ip < USB_NUM_USBIP; ip++) { - - bool aPeripheral = (g_usb_usbmode != USB_HOST); - if (aPeripheral && !g_usb_peri_connected) { - continue; - } - // assumes only single device in peripheral mode - int32_t numDevicesNow = aPeripheral ? 1 : MAX_NUM_USB_MIDI_DEVICES; - - for (int32_t d = 0; d < numDevicesNow; d++) { - if (connectedUSBMIDIDevices[ip][d].cable[0] && !connectedUSBMIDIDevices[ip][d].currentlyWaitingToReceive) { - - int32_t bytesReceivedHere = connectedUSBMIDIDevices[ip][d].numBytesReceived; - if (bytesReceivedHere) { - - connectedUSBMIDIDevices[ip][d].numBytesReceived = 0; - - __restrict__ uint8_t const* readPos = connectedUSBMIDIDevices[ip][d].receiveData; - const uint8_t* const stopAt = readPos + bytesReceivedHere; - - // Receive all the stuff from this device - for (; readPos < stopAt; readPos += 4) { - - uint8_t statusType = readPos[0] & 0x0F; - uint8_t cable = (readPos[0] & 0xF0) >> 4; - uint8_t channel = readPos[1] & 0x0F; - uint8_t data1 = readPos[2]; - uint8_t data2 = readPos[3]; - - if (statusType < 0x08) { - if (statusType == 2 || statusType == 3) { // 2 or 3 byte system common messages - statusType = 0x0F; - } - else { // Invalid, or sysex, or something - checkIncomingUsbSysex(readPos, ip, d, cable); - continue; - } - } - // select appropriate device based on the cable number - if (cable > connectedUSBMIDIDevices[ip][d].maxPortConnected) { - // fallback to cable 0 since we don't support more than one port on hosted devices yet - cable = 0; - } - midiMessageReceived(*connectedUSBMIDIDevices[ip][d].cable[cable], statusType, channel, data1, - data2, &timeLastBRDY[ip]); - } - } - - if (usbLockNow) { - continue; - } - - // And maybe setup transfer to receive more data - - // As peripheral - if (aPeripheral) { - - g_usb_midi_recv_utr[ip][0].keyword = USB_CFG_PMIDI_BULK_IN; - g_usb_midi_recv_utr[ip][0].tranlen = 64; - - connectedUSBMIDIDevices[ip][0].currentlyWaitingToReceive = 1; - - usbLock = 1; - g_p_usb_pipe[USB_CFG_PMIDI_BULK_IN] = &g_usb_midi_recv_utr[ip][0]; - usb_receive_start_rohan_midi(USB_CFG_PMIDI_BULK_IN); - usbLock = 0; - } - - // Or as host - else if (connectedUSBMIDIDevices[ip][d].cable[0]) { - - // Only allowed to setup receive-transfer if not in the process of sending to various devices. - // (Wait, still? Was this just because of that insane bug that's now fixed?) - if (usbDeviceNumBeingSentToNow[ip] == stopSendingAfterDeviceNum[ip]) { - usbLock = 1; - setupUSBHostReceiveTransfer(ip, d); - usbLock = 0; - } - } - } - } - } -} - #define MISSING_MESSAGE_CHECK 0 #if MISSING_MESSAGE_CHECK @@ -1062,7 +434,7 @@ void MidiEngine::midiMessageReceived(MIDICable& cable, uint8_t statusType, uint8 // other messages, rather than using our special clock-specific system if (shouldDoMidiThruNow) { // Only send out on USB if it didn't originate from USB - bool shouldSendUSB = (&cable == &MIDIDeviceManager::dinMIDIPorts); + bool shouldSendUSB = (&cable == &MIDIDeviceManager::rootDin.cable); // TODO: reconsider interaction with MPE? sendMidi(cable, MIDIMessage{ diff --git a/src/deluge/io/midi/midi_engine.h b/src/deluge/io/midi/midi_engine.h index c81ed6c29c..9bdc07b3c0 100644 --- a/src/deluge/io/midi/midi_engine.h +++ b/src/deluge/io/midi/midi_engine.h @@ -64,10 +64,6 @@ class MidiEngine { void sendNote(MIDISource source, bool on, int32_t note, uint8_t velocity, uint8_t channel, int32_t filter); void sendCC(MIDISource source, int32_t channel, int32_t cc, int32_t value, int32_t filter); - bool checkIncomingSerialMidi(); - void checkIncomingUsbMidi(); - - void checkIncomingUsbSysex(uint8_t const* message, int32_t ip, int32_t d, int32_t cable); void sendMidi(MIDISource source, MIDIMessage message, int32_t filter = kMIDIOutputFilterNoMPE, bool sendUSB = true); void sendClock(MIDISource source, bool sendUSB = true, int32_t howMany = 1); @@ -76,9 +72,9 @@ class MidiEngine { void sendPositionPointer(MIDISource source, uint16_t positionPointer); void sendContinue(MIDISource source); + void checkIncomingMidi(); void flushMIDI(); void sendUsbMidi(MIDIMessage message, int32_t filter); - void sendSerialMidi(MIDIMessage message); void sendPGMChange(MIDISource source, int32_t channel, int32_t pgm, int32_t filter); void sendAllNotesOff(MIDISource source, int32_t channel, int32_t filter); @@ -91,8 +87,6 @@ class MidiEngine { void sendChannelAftertouch(MIDISource source, int32_t channel, uint8_t value, int32_t filter); void sendPolyphonicAftertouch(MIDISource source, int32_t channel, uint8_t value, uint8_t noteCode, int32_t filter); bool anythingInOutputBuffer(); - void setupUSBHostReceiveTransfer(int32_t ip, int32_t midiDeviceNum); - void flushUSBMIDIOutput(); // If bit "16" (actually bit 4) is 1, this is a program change. (Wait, still?) LearnedMIDI globalMIDICommands[kNumGlobalMIDICommands]; @@ -142,21 +136,12 @@ class MidiEngine { EventStackStorage eventStack_; /// Top of the event stack. If this is equal to eventStack_.begin(), the stack is empty. EventStackStorage::iterator eventStackTop_; - - int32_t getPotentialNumConnectedUSBMIDIDevices(int32_t ip); }; -uint32_t setupUSBMessage(MIDIMessage message); - extern MidiEngine midiEngine; -extern bool anythingInUSBOutputBuffer; extern "C" { #endif -extern uint16_t g_usb_usbmode; - -void usbSendCompleteAsHost(int32_t ip); // used when deluge is in host mode -void usbSendCompleteAsPeripheral(int32_t ip); // used in peripheral mode #ifdef __cplusplus } #endif diff --git a/src/deluge/io/midi/midi_root_complex.h b/src/deluge/io/midi/midi_root_complex.h new file mode 100644 index 0000000000..b689917d42 --- /dev/null +++ b/src/deluge/io/midi/midi_root_complex.h @@ -0,0 +1,104 @@ +/* + * Copyright © 2024 Synthstrom Audible Limited + * + * This file is part of The Synthstrom Audible Deluge Firmware. + * + * The Synthstrom Audible Deluge Firmware is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software Foundation, + * either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. + * If not, see . + */ + +#pragma once + +#include "definitions_cxx.hpp" +#include "io/midi/midi_device.h" + +enum class RootComplexType { + RC_DIN, + RC_USB_PERIPHERAL, + /// needs the RC_ prefix even though this is an enum class because USB_HOST is a preprocessor macro in some of the + /// RZA1 headers + RC_USB_HOST, +}; + +/// Represents a group of cables we can do I/O on. +/// +/// The name is meant to be analogous to a "root complex" in a PCIe device hierarchy, which represents the physical +/// bridge to the PCIe bus by the bus controller. Similarly, this class represents the interface from the Deluge +/// software to a MIDI device connection. +class MIDIRootComplex { + class CableIterator { + private: + MIDIRootComplex& parent_; + size_t index_; + + public: + CableIterator(const CableIterator&) = default; + CableIterator(CableIterator&&) = default; + CableIterator& operator=(const CableIterator&) = delete; + CableIterator& operator=(CableIterator&&) = delete; + + CableIterator(MIDIRootComplex& parent, size_t index) : parent_{parent}, index_{index} {} + + CableIterator& operator++() { + index_++; + return *this; + } + + CableIterator operator++(int) { + auto pre = *this; + index_++; + return pre; + } + + friend bool operator==(CableIterator const& a, CableIterator const& b) { + return &a.parent_ == &b.parent_ && a.index_ == b.index_; + }; + + friend bool operator!=(CableIterator const& a, CableIterator const& b) { + return &a.parent_ != &b.parent_ || a.index_ != b.index_; + }; + + MIDICable& operator*() { return *parent_.getCable(index_); } + MIDICable* operator->() { return parent_.getCable(index_); } + }; + + class CableSet { + public: + MIDIRootComplex& parent; + + explicit CableSet(MIDIRootComplex& ccplex) : parent{ccplex} {} + + CableIterator begin() { return CableIterator{parent, 0}; } + CableIterator end() { return CableIterator{parent, parent.getNumCables()}; } + }; + +public: + MIDIRootComplex() = default; + MIDIRootComplex(const MIDIRootComplex&) = delete; + MIDIRootComplex(MIDIRootComplex&&) = delete; + MIDIRootComplex& operator=(const MIDIRootComplex&) = delete; + MIDIRootComplex& operator=(MIDIRootComplex&&) = delete; + + virtual ~MIDIRootComplex() = default; + + [[nodiscard]] virtual RootComplexType getType() const = 0; + + [[nodiscard]] virtual size_t getNumCables() const = 0; + [[nodiscard]] virtual MIDICable* getCable(size_t cableIdx) = 0; + + CableSet getCables() { return CableSet{*this}; } + + /// Flush as much data as possible from any internal buffers to hardware queues + virtual void flush() = 0; + + /// Poll the root complex, calling back in to the MIDI engine for any new messages. + [[nodiscard]] virtual Error poll() = 0; +}; diff --git a/src/deluge/io/midi/root_complex/din.cpp b/src/deluge/io/midi/root_complex/din.cpp new file mode 100644 index 0000000000..978ff5a37b --- /dev/null +++ b/src/deluge/io/midi/root_complex/din.cpp @@ -0,0 +1,49 @@ +/* + * Copyright © 2024 Synthstrom Audible Limited + * + * This file is part of The Synthstrom Audible Deluge Firmware. + * + * The Synthstrom Audible Deluge Firmware is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software Foundation, + * either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. + * If not, see . + */ + +#include "din.h" + +#include "io/debug/log.h" +#include "io/midi/midi_device_manager.h" +#include "io/midi/midi_engine.h" + +extern "C" { +#include "RZA1/uart/sio_char.h" +} + +DINRootComplex::DINRootComplex() = default; +DINRootComplex::~DINRootComplex() = default; + +void DINRootComplex::flush() { + uartFlushIfNotSending(UART_ITEM_MIDI); +} + +Error DINRootComplex::poll() { + uint8_t thisSerialByte; + uint32_t* timer = uartGetCharWithTiming(TIMING_CAPTURE_ITEM_MIDI, (char*)&thisSerialByte); + + while (timer != nullptr) { + auto err = cable.onReceiveByte(*timer, thisSerialByte); + if (err != Error::NONE) { + return err; + } + + timer = uartGetCharWithTiming(TIMING_CAPTURE_ITEM_MIDI, (char*)&thisSerialByte); + } + + return Error::NO_ERROR_BUT_GET_OUT; +} diff --git a/src/deluge/io/midi/root_complex/din.h b/src/deluge/io/midi/root_complex/din.h new file mode 100644 index 0000000000..ffbce853fe --- /dev/null +++ b/src/deluge/io/midi/root_complex/din.h @@ -0,0 +1,40 @@ +/* + * Copyright © 2024 Synthstrom Audible Limited + * + * This file is part of The Synthstrom Audible Deluge Firmware. + * + * The Synthstrom Audible Deluge Firmware is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software Foundation, + * either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. + * If not, see . + */ + +#pragma once + +#include "io/midi/cable_types/din.h" +#include "io/midi/midi_root_complex.h" +#include + +class DINRootComplex : public MIDIRootComplex { +private: +public: + /// The one and only DIN cable connection + MIDICableDINPorts cable; + + DINRootComplex(); + ~DINRootComplex() override; + + [[nodiscard]] RootComplexType getType() const override { return RootComplexType::RC_DIN; }; + + [[nodiscard]] size_t getNumCables() const override { return 1; } + [[nodiscard]] MIDICable* getCable(size_t cableIdx) override { return (cableIdx == 0) ? &cable : nullptr; } + + void flush() override; + [[nodiscard]] Error poll() override; +}; diff --git a/src/deluge/io/midi/root_complex/usb_hosted.cpp b/src/deluge/io/midi/root_complex/usb_hosted.cpp new file mode 100644 index 0000000000..caf860b592 --- /dev/null +++ b/src/deluge/io/midi/root_complex/usb_hosted.cpp @@ -0,0 +1,314 @@ +/* + * Copyright © 2024 Synthstrom Audible Limited + * + * This file is part of The Synthstrom Audible Deluge Firmware. + * + * The Synthstrom Audible Deluge Firmware is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software Foundation, + * either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. + * If not, see . + */ + +#include "usb_hosted.h" +#include "deluge/io/usb/usb_state.h" +#include "drivers/uart/uart.h" +#include "io/debug/log.h" +#include "io/midi/midi_device_manager.h" +#include "io/midi/midi_engine.h" + +using namespace deluge::io::usb; + +MIDIRootComplexUSBHosted::MIDIRootComplexUSBHosted() = default; + +MIDIRootComplexUSBHosted::~MIDIRootComplexUSBHosted() { + // Clean up the pointers to our cables + for (auto i = 0; i < MAX_NUM_USB_MIDI_DEVICES; ++i) { + auto& connectedDevice = connectedUSBMIDIDevices[0][i]; + for (auto j = 0; j < ((sizeof(connectedDevice.cable) / sizeof(connectedDevice.cable[0]))); ++j) { + connectedDevice.cable[j] = nullptr; + } + } +} + +void flushUSBMIDIToHostedDevice(int32_t ip, int32_t d, bool resume = false); + +extern "C" { + +#include "RZA1/usb/userdef/r_usb_hmidi_config.h" + +extern uint8_t currentlyAccessingCard; +extern void usb_send_start_rohan(usb_utr_t* ptr, uint16_t pipe, uint8_t const* data, int32_t size); +extern void change_destination_of_send_pipe(usb_utr_t* ptr, uint16_t pipe, uint16_t* tbl, int32_t sq); + +// We now bypass calling this for successful as peripheral on A1 (see usb_pstd_bemp_pipe_process_rohan_midi()) +void usbSendCompleteAsHost(int32_t ip) { + + int32_t midiDeviceNum = usbDeviceNumBeingSentToNow[ip]; + + ConnectedUSBMIDIDevice* connectedDevice = &connectedUSBMIDIDevices[ip][midiDeviceNum]; + + connectedDevice->numBytesSendingNow = 0; // We just do this instead from caller on A1 (see comment above) + + // check if there was more to send on the same device, then resume sending + bool has_more = connectedDevice->consumeSendData(); + if (has_more) { + // TODO: do some cooperative scheduling here. so if there is a flood of data + // on connected device 1 and we just want to send a few notes on device 2, + // make sure device 2 ges a fair shot now and then + + flushUSBMIDIToHostedDevice(ip, midiDeviceNum, true); + return; + } + + // If that was the last device we were going to send to, that send's been done, so we can just get out. + if (midiDeviceNum == stopSendingAfterDeviceNum[ip]) { + anyUSBSendingStillHappening[ip] = 0; + return; + } + + while (true) { + midiDeviceNum++; + if (midiDeviceNum >= MAX_NUM_USB_MIDI_DEVICES) { + midiDeviceNum -= MAX_NUM_USB_MIDI_DEVICES; + } + connectedDevice = &connectedUSBMIDIDevices[ip][midiDeviceNum]; + if (connectedDevice->cable[0] && connectedDevice->numBytesSendingNow) { + // If here, we got a connected device, so flush + flushUSBMIDIToHostedDevice(ip, midiDeviceNum); + return; + } + if (midiDeviceNum == stopSendingAfterDeviceNum[ip]) { // If reached end of devices and last one got disconnected + // in the interim (very rare) + usbDeviceNumBeingSentToNow[ip] = stopSendingAfterDeviceNum[ip]; + anyUSBSendingStillHappening[ip] = 0; + return; + } + } +} +} + +void setupUSBHostReceiveTransfer(int32_t ip, int32_t midiDeviceNum) { + connectedUSBMIDIDevices[ip][midiDeviceNum].currentlyWaitingToReceive = 1; + + int32_t pipeNumber = g_usb_hmidi_tmp_ep_tbl[USB_CFG_USE_USBIP][midiDeviceNum][USB_EPL]; + + g_usb_midi_recv_utr[USB_CFG_USE_USBIP][midiDeviceNum].keyword = pipeNumber; + g_usb_midi_recv_utr[USB_CFG_USE_USBIP][midiDeviceNum].tranlen = 64; + + g_p_usb_pipe[pipeNumber] = &g_usb_midi_recv_utr[USB_CFG_USE_USBIP][midiDeviceNum]; + + // uint16_t startTime = *TCNT[TIMER_SYSTEM_SUPERFAST]; + + usb_receive_start_rohan_midi(pipeNumber); + + /* + uint16_t endTime = *TCNT[TIMER_SYSTEM_SUPERFAST]; + uint16_t duration = endTime - startTime; + uint32_t timePassedNS = superfastTimerCountToNS(duration); + uartPrint("send setup duration, nSec: "); + uartPrintNumber(timePassedNS); + */ +} + +void flushUSBMIDIToHostedDevice(int32_t ip, int32_t d, bool resume) { + ConnectedUSBMIDIDevice* connectedDevice = &connectedUSBMIDIDevices[ip][d]; + // there was an assumption that the pipe wouldn't have changed if we were resuming a transfer but that has turned + // out not to be true if hubs are involved. A hub transaction seems to be able to run before the + // usbSendCompleteAsHost interrupt is called and changes the pipe, and then the next write doesn't go anywhere + // useful + int32_t pipeNumber = g_usb_hmidi_tmp_ep_tbl[USB_CFG_USE_USBIP][d][0]; + g_usb_midi_send_utr[USB_CFG_USE_USBIP].keyword = pipeNumber; + g_usb_midi_send_utr[USB_CFG_USE_USBIP].tranlen = connectedDevice->numBytesSendingNow; + g_usb_midi_send_utr[USB_CFG_USE_USBIP].p_tranadr = connectedDevice->dataSendingNow; + + usbDeviceNumBeingSentToNow[USB_CFG_USE_USBIP] = d; + + int32_t isInterrupt = (pipeNumber == USB_CFG_HMIDI_INT_SEND); + + if (d != currentDeviceNumWithSendPipe[isInterrupt]) { + currentDeviceNumWithSendPipe[isInterrupt] = d; + change_destination_of_send_pipe(&g_usb_midi_send_utr[USB_CFG_USE_USBIP], pipeNumber, + g_usb_hmidi_tmp_ep_tbl[USB_CFG_USE_USBIP][d], connectedDevice->sq); + } + + connectedDevice->sq = !connectedDevice->sq; + + g_p_usb_pipe[pipeNumber] = &g_usb_midi_send_utr[USB_CFG_USE_USBIP]; + + usb_send_start_rohan(&g_usb_midi_send_utr[USB_CFG_USE_USBIP], pipeNumber, connectedDevice->dataSendingNow, + connectedDevice->numBytesSendingNow); +} + +void MIDIRootComplexUSBHosted::flush() { + if (usbLock) { + return; + } + + constexpr int ip = 0; + + anythingInUSBOutputBuffer = false; + + // is this still relevant? anyUSBSendingStillHappening[ip] acts as the lock between routine and interrupt + // on the sending side. all other uses of usbLock seems to be about _receiving_. Can there be a conflict + // between sending and receiving as well?? + USBAutoLock lock; + + if (anyUSBSendingStillHappening[ip]) { + // still sending, call me later maybe + anythingInUSBOutputBuffer = true; + return; + } + + // This next bit was written with multiple devices on hubs in mind, but seems to work for a single MIDI + // device too + + int32_t midiDeviceNumToSendTo = currentDeviceNumWithSendPipe[0]; // This will do + if (midiDeviceNumToSendTo >= MAX_NUM_USB_MIDI_DEVICES) { + midiDeviceNumToSendTo = 0; // In case it was set to "none", I think + } + + int32_t newStopSendingAfter = midiDeviceNumToSendTo - 1; + if (newStopSendingAfter < 0) { + newStopSendingAfter += MAX_NUM_USB_MIDI_DEVICES; + } + + // Make sure that's on a connected device - it probably would be... + while (true) { + ConnectedUSBMIDIDevice* connectedDevice = &connectedUSBMIDIDevices[ip][midiDeviceNumToSendTo]; + if (connectedDevice->cable[0] && connectedDevice->hasBufferedSendData()) { + break; // We found a connected one + } + if (midiDeviceNumToSendTo == newStopSendingAfter) { + return; + } + midiDeviceNumToSendTo++; + if (midiDeviceNumToSendTo >= MAX_NUM_USB_MIDI_DEVICES) { + midiDeviceNumToSendTo = 0; // Wrap back to start of list + } + } + + // Stop after a device which we know is connected + while (true) { + ConnectedUSBMIDIDevice* connectedDevice = &connectedUSBMIDIDevices[ip][newStopSendingAfter]; + + if (connectedDevice->cable[0] && connectedDevice->hasBufferedSendData()) { + break; // We found a connected one + } + + newStopSendingAfter--; + if (newStopSendingAfter < 0) { + newStopSendingAfter += MAX_NUM_USB_MIDI_DEVICES; + } + } + + // Copy the buffers for all devices + int32_t d = midiDeviceNumToSendTo; + while (true) { + ConnectedUSBMIDIDevice* connectedDevice = &connectedUSBMIDIDevices[ip][d]; + + if (connectedDevice->cable[0]) { + connectedDevice->consumeSendData(); + } + if (d == newStopSendingAfter) { + break; + } + d++; + if (d >= MAX_NUM_USB_MIDI_DEVICES) { + d = 0; + } + } + + stopSendingAfterDeviceNum[ip] = newStopSendingAfter; + anyUSBSendingStillHappening[ip] = 1; + + flushUSBMIDIToHostedDevice(ip, midiDeviceNumToSendTo); +} + +Error MIDIRootComplexUSBHosted::poll() { + if (currentlyAccessingCard != 0) { + // D_PRINTLN("checkIncomingUsbMidi seeing currentlyAccessingCard non-zero"); + return Error::NO_ERROR_BUT_GET_OUT; + } + + bool usbLockNow = usbLock; + + // TODO: delete this, we don't ever need to iterate over IPs + constexpr auto ip = 0; + + // assumes only single device in peripheral mode + int32_t numDevicesNow = MAX_NUM_USB_MIDI_DEVICES; + + for (int32_t d = 0; d < numDevicesNow; d++) { + auto& connectedDevice = connectedUSBMIDIDevices[ip][d]; + if (connectedDevice.cable[0] != nullptr && connectedDevice.currentlyWaitingToReceive == 0u) { + + int32_t bytesReceivedHere = connectedDevice.numBytesReceived; + if (bytesReceivedHere) { + + connectedDevice.numBytesReceived = 0; + + const uint8_t* __restrict__ readPos = connectedDevice.receiveData; + const uint8_t* const stopAt = readPos + bytesReceivedHere; + + // Receive all the stuff from this device + for (; readPos < stopAt; readPos += 4) { + + uint8_t statusType = readPos[0] & 0x0F; + uint8_t cable = (readPos[0] & 0xF0) >> 4; + uint8_t channel = readPos[1] & 0x0F; + uint8_t data1 = readPos[2]; + uint8_t data2 = readPos[3]; + + if (statusType < 0x08) { + if (statusType == 2 || statusType == 3) { // 2 or 3 byte system common messages + statusType = 0x0F; + } + else { // Invalid, or sysex, or something + // XXX: this collapses all cables to cable 0, but we only technically support 1 cable on + // remote USB devices for now.. + connectedDevice.cable[0]->checkIncomingSysex(readPos, ip, d); + continue; + } + } + // select appropriate device based on the cable number + if (cable > connectedDevice.maxPortConnected) { + // fallback to cable 0 since we don't support more than one port on hosted devices yet + cable = 0; + } + midiEngine.midiMessageReceived(*connectedDevice.cable[cable], statusType, channel, data1, data2, + &timeLastBRDY[ip]); + } + } + + // If this is a reentrant invocation, don't do transfer setup + if (usbLockNow) { + continue; + } + + // And maybe setup transfer to receive more data + if (connectedDevice.cable[0] != nullptr) { + // Only allowed to setup receive-transfer if not in the process of sending to various devices. (Wait, + // still? Was this just because of that insane bug that's now fixed?) + if (usbDeviceNumBeingSentToNow[ip] == stopSendingAfterDeviceNum[ip]) { + USBAutoLock lock; + setupUSBHostReceiveTransfer(ip, d); + } + } + } + } + + return Error::NONE; +} + +MIDICable* MIDIRootComplexUSBHosted::getCable(size_t index) { + if (index < 0 || index >= hostedMIDIDevices_.getNumElements()) { + return nullptr; + } + return static_cast(hostedMIDIDevices_.getElement(static_cast(index))); +} diff --git a/src/deluge/io/midi/root_complex/usb_hosted.h b/src/deluge/io/midi/root_complex/usb_hosted.h new file mode 100644 index 0000000000..a16a37e517 --- /dev/null +++ b/src/deluge/io/midi/root_complex/usb_hosted.h @@ -0,0 +1,55 @@ +/* + * Copyright © 2024 Synthstrom Audible Limited + * + * This file is part of The Synthstrom Audible Deluge Firmware. + * + * The Synthstrom Audible Deluge Firmware is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software Foundation, + * either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. + * If not, see . + */ + +#pragma once + +#include "io/midi/cable_types/usb_hosted.h" +#include "io/midi/midi_root_complex.h" +#include "util/container/vector/named_thing_vector.h" +#include + +class MIDIRootComplexUSBHosted : public MIDIRootComplex { +private: +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Winvalid-offsetof" + NamedThingVector hostedMIDIDevices_{__builtin_offsetof(MIDICableUSBHosted, name)}; +#pragma GCC diagnostic pop + +public: + MIDIRootComplexUSBHosted(const MIDIRootComplexUSBHosted&) = delete; + MIDIRootComplexUSBHosted(MIDIRootComplexUSBHosted&&) = delete; + MIDIRootComplexUSBHosted& operator=(const MIDIRootComplexUSBHosted&) = delete; + MIDIRootComplexUSBHosted& operator=(MIDIRootComplexUSBHosted&&) = delete; + + MIDIRootComplexUSBHosted(); + ~MIDIRootComplexUSBHosted() override; + + [[nodiscard]] RootComplexType getType() const override { return RootComplexType::RC_USB_HOST; } + + [[nodiscard]] size_t getNumCables() const override { return hostedMIDIDevices_.getNumElements(); } + [[nodiscard]] MIDICable* getCable(size_t index) override; + + [[nodiscard]] NamedThingVector& getHostedMIDIDevices() { return hostedMIDIDevices_; } + [[nodiscard]] NamedThingVector const& getHostedMIDIDevices() const { return hostedMIDIDevices_; } + + void flush() override; + [[nodiscard]] Error poll() override; +}; + +extern "C" { +void usbSendCompleteAsHost(int32_t ip); +} diff --git a/src/deluge/io/midi/root_complex/usb_peripheral.cpp b/src/deluge/io/midi/root_complex/usb_peripheral.cpp new file mode 100644 index 0000000000..f6c553721d --- /dev/null +++ b/src/deluge/io/midi/root_complex/usb_peripheral.cpp @@ -0,0 +1,186 @@ +/* + * Copyright © 2024 Synthstrom Audible Limited + * + * This file is part of The Synthstrom Audible Deluge Firmware. + * + * The Synthstrom Audible Deluge Firmware is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software Foundation, + * either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. + * If not, see . + */ + +#include "usb_peripheral.h" + +#include "io/midi/midi_device_manager.h" +#include "io/midi/midi_engine.h" +#include "io/usb/usb_state.h" + +using namespace deluge::io::usb; + +extern "C" { +#include "drivers/usb/userdef/r_usb_pmidi_config.h" + +extern uint8_t currentlyAccessingCard; + +extern void usb_send_start_rohan(usb_utr_t* ptr, uint16_t pipe, uint8_t const* data, int32_t size); + +void usbSendCompleteAsPeripheral(int32_t ip) { + ConnectedUSBMIDIDevice* connectedDevice = &connectedUSBMIDIDevices[ip][0]; + connectedDevice->numBytesSendingNow = 0; // Even easier! + + // I think this could happen as part of a detach see detachedAsPeripheral() + if (anyUSBSendingStillHappening[ip] == 0) { + return; + } + + bool has_more = connectedDevice->consumeSendData(); + if (has_more) { + // this is already the case: + // anyUSBSendingStillHappening[ip] = 1; + + g_usb_midi_send_utr[ip].tranlen = connectedDevice->numBytesSendingNow; + g_usb_midi_send_utr[ip].p_tranadr = connectedDevice->dataSendingNow; + + usb_send_start_rohan(NULL, USB_CFG_PMIDI_BULK_OUT, connectedDevice->dataSendingNow, + connectedDevice->numBytesSendingNow); + } + else { + // this effectively serves as a lock, does the sending part of the device, including the read part of the + // ring buffer "belong" to ongoing/scheduled interrupts. Document this better. + anyUSBSendingStillHappening[0] = 0; + } +} +} + +MIDIRootComplexUSBPeripheral::MIDIRootComplexUSBPeripheral() : cables_{0, 1, 2} { +} + +MIDIRootComplexUSBPeripheral::~MIDIRootComplexUSBPeripheral() { + // Clean up the pointers to our cables + for (auto i = 0; i < MAX_NUM_USB_MIDI_DEVICES; ++i) { + auto& connectedDevice = connectedUSBMIDIDevices[0][i]; + for (auto j = 0; j < ((sizeof(connectedDevice.cable) / sizeof(connectedDevice.cable[0]))); ++j) { + connectedDevice.cable[j] = nullptr; + } + } +} + +void MIDIRootComplexUSBPeripheral::flush() { + constexpr int ip = 0; + + if (usbLock) { + return; + } + + anythingInUSBOutputBuffer = false; + + // is this still relevant? anyUSBSendingStillHappening[ip] acts as the lock between routine and interrupt + // on the sending side. all other uses of usbLock seems to be about _receiving_. Can there be a conflict + // between sending and receiving as well?? + USBAutoLock lock; + + ConnectedUSBMIDIDevice* connectedDevice = &connectedUSBMIDIDevices[ip][0]; + + if (anyUSBSendingStillHappening[ip]) { + // still sending, call me later maybe + anythingInUSBOutputBuffer = true; + return; + } + + if (!connectedDevice->consumeSendData()) { + return; + } + + g_usb_midi_send_utr[ip].keyword = USB_CFG_PMIDI_BULK_OUT; + g_usb_midi_send_utr[ip].tranlen = connectedDevice->numBytesSendingNow; + g_usb_midi_send_utr[ip].p_tranadr = connectedDevice->dataSendingNow; + + usbDeviceNumBeingSentToNow[ip] = 0; + anyUSBSendingStillHappening[ip] = 1; + + g_p_usb_pipe[USB_CFG_PMIDI_BULK_OUT] = &g_usb_midi_send_utr[ip]; + usb_send_start_rohan(NULL, USB_CFG_PMIDI_BULK_OUT, connectedDevice->dataSendingNow, + connectedDevice->numBytesSendingNow); + + // when done, usbSendCompleteAsPeripheral() will be called in an interrupt +} + +Error MIDIRootComplexUSBPeripheral::poll() { + if (currentlyAccessingCard != 0) { + // D_PRINTLN("checkIncomingUsbMidi seeing currentlyAccessingCard non-zero"); + return Error::NO_ERROR_BUT_GET_OUT; + } + + bool usbLockNow = usbLock; + + constexpr auto ip = 0; + constexpr auto d = 0; + + // assumes only single device in peripheral mode + int32_t numDevicesNow = 1; + + auto& connectedDevice = connectedUSBMIDIDevices[ip][d]; + + if (connectedDevice.cable[0] && !connectedDevice.currentlyWaitingToReceive) { + int32_t bytesReceivedHere = connectedDevice.numBytesReceived; + if (bytesReceivedHere) { + + connectedDevice.numBytesReceived = 0; + + uint8_t const* __restrict__ readPos = connectedDevice.receiveData; + uint8_t const* const stopAt = readPos + bytesReceivedHere; + + // Receive all the stuff from this device + for (; readPos < stopAt; readPos += 4) { + + uint8_t statusType = readPos[0] & 0x0F; + uint8_t cable = (readPos[0] & 0xF0) >> 4; + uint8_t channel = readPos[1] & 0x0F; + uint8_t data1 = readPos[2]; + uint8_t data2 = readPos[3]; + + if (statusType < 0x08) { + if (statusType == 2 || statusType == 3) { // 2 or 3 byte system common messages + statusType = 0x0F; + } + else { // Invalid, or sysex, or something + if (cable < this->cables_.size()) { + this->cables_[cable].checkIncomingSysex(readPos, ip, d); + } + continue; + } + } + // select appropriate device based on the cable number + if (cable > connectedDevice.maxPortConnected) { + // fallback to cable 0 since we don't support more than one port on hosted devices yet + cable = 0; + } + midiEngine.midiMessageReceived(*connectedDevice.cable[cable], statusType, channel, data1, data2, + &timeLastBRDY[ip]); + } + } + + if (usbLockNow) { + return Error::NONE; + } + + // And maybe setup transfer to receive more data + + g_usb_midi_recv_utr[ip][0].keyword = USB_CFG_PMIDI_BULK_IN; + g_usb_midi_recv_utr[ip][0].tranlen = 64; + + connectedUSBMIDIDevices[ip][0].currentlyWaitingToReceive = 1; + + USBAutoLock lock; + g_p_usb_pipe[USB_CFG_PMIDI_BULK_IN] = &g_usb_midi_recv_utr[ip][0]; + usb_receive_start_rohan_midi(USB_CFG_PMIDI_BULK_IN); + } + + return Error::NONE; +} diff --git a/src/deluge/io/midi/root_complex/usb_peripheral.h b/src/deluge/io/midi/root_complex/usb_peripheral.h new file mode 100644 index 0000000000..a2c82be044 --- /dev/null +++ b/src/deluge/io/midi/root_complex/usb_peripheral.h @@ -0,0 +1,50 @@ +/* + * Copyright © 2024 Synthstrom Audible Limited + * + * This file is part of The Synthstrom Audible Deluge Firmware. + * + * The Synthstrom Audible Deluge Firmware is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software Foundation, + * either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. + * If not, see . + */ + +#pragma once + +#include "io/midi/cable_types/usb_device_cable.h" +#include "io/midi/midi_root_complex.h" +#include + +class MIDIRootComplexUSBPeripheral : public MIDIRootComplex { +private: + using CableArray = std::array; + + CableArray cables_; + +public: + MIDIRootComplexUSBPeripheral(const MIDIRootComplexUSBPeripheral&) = delete; + MIDIRootComplexUSBPeripheral(MIDIRootComplexUSBPeripheral&&) = delete; + MIDIRootComplexUSBPeripheral& operator=(const MIDIRootComplexUSBPeripheral&) = delete; + MIDIRootComplexUSBPeripheral& operator=(MIDIRootComplexUSBPeripheral&&) = delete; + + MIDIRootComplexUSBPeripheral(); + ~MIDIRootComplexUSBPeripheral() override; + + [[nodiscard]] size_t getNumCables() const override { + // This returns 2, not 3, because the 3rd cable is secret (only used by sysex infrastructure) + return 2; + } + + [[nodiscard]] MIDICable* getCable(size_t index) override { return &cables_[index]; } + + [[nodiscard]] RootComplexType getType() const override { return RootComplexType::RC_USB_PERIPHERAL; } + + void flush() override; + [[nodiscard]] Error poll() override; +}; diff --git a/src/deluge/io/usb/usb_state.cpp b/src/deluge/io/usb/usb_state.cpp new file mode 100644 index 0000000000..d16206844f --- /dev/null +++ b/src/deluge/io/usb/usb_state.cpp @@ -0,0 +1,133 @@ +/* + * Copyright © 2024 Synthstrom Audible Limited + * + * This file is part of The Synthstrom Audible Deluge Firmware. + * + * The Synthstrom Audible Deluge Firmware is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software Foundation, + * either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. + * If not, see . + */ + +#include "usb_state.h" +#include "RZA1/system/iodefines/dmac_iodefine.h" +#include "io/midi/midi_device_manager.h" +#include "io/midi/root_complex/usb_hosted.h" + +extern "C" { +#include "drivers/uart/uart.h" + +// XXX: manually declared here instead of including r_usb_extern.h becuse that header doesn't compile as C++ code. +extern usb_regadr_t usb_hstd_get_usb_ip_adr(uint16_t ipno); + +usb_utr_t g_usb_midi_send_utr[USB_NUM_USBIP]; +usb_utr_t g_usb_midi_recv_utr[USB_NUM_USBIP][MAX_NUM_USB_MIDI_DEVICES]; + +uint8_t currentDeviceNumWithSendPipe[2] = {MAX_NUM_USB_MIDI_DEVICES, MAX_NUM_USB_MIDI_DEVICES}; + +uint32_t timeLastBRDY[USB_NUM_USBIP]; + +void usbReceiveComplete(int32_t ip, int32_t deviceNum, int32_t tranlen) { + ConnectedUSBMIDIDevice* connectedDevice = &connectedUSBMIDIDevices[ip][deviceNum]; + + connectedDevice->numBytesReceived = 64 - tranlen; // Seems wack, but yet, tranlen is now how many bytes didn't get + // received out of the original transfer size + // Warning - sometimes (with a Teensy, e.g. my knob box), length will be 0. Not sure why - but we need to cope with + // that case. + + connectedDevice->currentlyWaitingToReceive = 0; // Take note that we need to set up another receive +} + +// We now bypass calling this for successful as peripheral on A1 (see usb_pstd_bemp_pipe_process_rohan_midi()) +void usbSendCompletePeripheralOrA1(usb_utr_t* p_mess, uint16_t data1, uint16_t data2) { + // If error, forget about device. + // No actually don't - sometimes there'll be an error if another device connected or disconnected from hub during + // fast MIDI sending. This seems to happen even though I've stopped it from setting up or down the out-pipe as it + // goes + if (p_mess->status == USB_DATA_ERR) { + if constexpr (ALPHA_OR_BETA_VERSION) { + uartPrintln("USB Send error"); + } + // g_usb_host_connected[deviceNum] = 0; + } + +#if USB_NUM_USBIP == 1 + int32_t ip = 0; +#else + int32_t ip = p_mess->ip; +#endif + + usbSendCompleteAsHost(ip); +} + +void usbReceiveCompletePeripheralOrA1(usb_utr_t* p_mess, uint16_t data1, uint16_t data2) { + +#if USB_NUM_USBIP == 1 + int32_t ip = 0; +#else + int32_t ip = p_mess->ip; +#endif + + if (p_mess->status == USB_DATA_ERR) { + return; // Can happen if user disconnects device - totally normal + } + + int32_t deviceNum = p_mess - &g_usb_midi_recv_utr[ip][0]; + + // Are there actually any other possibilities that could happen here? Can't remember. + if (p_mess->status != USB_DATA_SHT) { + uartPrint("status: "); + uartPrintNumber(p_mess->status); + } + + usbReceiveComplete(ip, deviceNum, p_mess->tranlen); +} + +void brdyOccurred(int32_t ip) { + timeLastBRDY[ip] = DMACnNonVolatile(SSI_TX_DMA_CHANNEL).CRSA_n; // Reading this not as volatile works fine +} +} + +namespace deluge::io::usb { +volatile bool usbLock; +bool anythingInUSBOutputBuffer; + +uint8_t stopSendingAfterDeviceNum[USB_NUM_USBIP]; +uint8_t usbDeviceNumBeingSentToNow[USB_NUM_USBIP]; +uint8_t anyUSBSendingStillHappening[USB_NUM_USBIP]; + +void usbSetup() { + g_usb_peri_connected = 0; // Needs initializing with A2 driver + + for (int32_t ip = 0; ip < USB_NUM_USBIP; ip++) { + + // This might not be used due to the change in r_usb_hlibusbip (deluge is host) to call usbSendCompleteAsHost() + // directly and the change in r_usb_plibusbip (deluge is pheriperal) to just set some variables or it might be + // used for some other interrups like error conditions??? + // TODO: try to delet this and see if something breaks.. + g_usb_midi_send_utr[ip].complete = (usb_cb_t)usbSendCompletePeripheralOrA1; + + g_usb_midi_send_utr[ip].p_setup = 0; /* Setup message address set */ + g_usb_midi_send_utr[ip].segment = USB_TRAN_END; + g_usb_midi_send_utr[ip].ip = ip; + g_usb_midi_send_utr[ip].ipp = usb_hstd_get_usb_ip_adr(ip); + + for (int32_t d = 0; d < MAX_NUM_USB_MIDI_DEVICES; d++) { + g_usb_midi_recv_utr[ip][d].p_tranadr = connectedUSBMIDIDevices[ip][d].receiveData; + g_usb_midi_recv_utr[ip][d].complete = (usb_cb_t)usbReceiveCompletePeripheralOrA1; + + g_usb_midi_recv_utr[ip][d].p_setup = 0; /* Setup message address set */ + g_usb_midi_recv_utr[ip][d].segment = USB_TRAN_END; + g_usb_midi_recv_utr[ip][d].ip = ip; + g_usb_midi_recv_utr[ip][d].ipp = usb_hstd_get_usb_ip_adr(ip); + } + } +} + +} // namespace deluge::io::usb diff --git a/src/deluge/io/usb/usb_state.h b/src/deluge/io/usb/usb_state.h new file mode 100644 index 0000000000..f429503b9e --- /dev/null +++ b/src/deluge/io/usb/usb_state.h @@ -0,0 +1,97 @@ +/* + * Copyright © 2024 Synthstrom Audible Limited + * + * This file is part of The Synthstrom Audible Deluge Firmware. + * + * The Synthstrom Audible Deluge Firmware is free software: you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software Foundation, + * either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. + * If not, see . + */ + +#include "RZA1/usb/r_usb_basic/r_usb_basic_if.h" +#include "definitions.h" + +#ifdef __cplusplus + +namespace deluge::io::usb { + +/// When true, something is using the low-level USB structures. +/// +/// XXX: this is super ill-defined and seems to mostly exist to avoid reentrant ISR problems +extern volatile bool usbLock; + +/// RAII lock control for the USB lock +class USBAutoLock { +public: + USBAutoLock() { usbLock = true; } + ~USBAutoLock() { usbLock = false; } + + USBAutoLock(const USBAutoLock&) = delete; + USBAutoLock(USBAutoLock&&) = delete; + USBAutoLock& operator=(const USBAutoLock&) = delete; + USBAutoLock& operator=(USBAutoLock&&) = delete; +}; + +/// When true, some data is queued to the USB output buffer. +extern bool anythingInUSBOutputBuffer; + +/// Last device to send to +extern uint8_t stopSendingAfterDeviceNum[USB_NUM_USBIP]; + +/// Current hosted device number, used in host send completion callback +extern uint8_t usbDeviceNumBeingSentToNow[USB_NUM_USBIP]; + +/// Flag to prevent reentrant sending +extern uint8_t anyUSBSendingStillHappening[USB_NUM_USBIP]; + +/// up internal USB state +void usbSetup(); + +} // namespace deluge::io::usb + +extern "C" { +#endif + +// defined in r_usb_pdriver.c +extern uint16_t g_usb_peri_connected; + +// defined in r_usb_cdataio.c +extern usb_utr_t* g_p_usb_pipe[USB_MAX_PIPE_NO + 1u]; + +extern usb_utr_t g_usb_midi_send_utr[USB_NUM_USBIP]; +extern usb_utr_t g_usb_midi_recv_utr[USB_NUM_USBIP][MAX_NUM_USB_MIDI_DEVICES]; + +// defined in r_usb_hmidi_driver.c +extern uint16_t g_usb_hmidi_tmp_ep_tbl[USB_NUM_USBIP][MAX_NUM_USB_MIDI_DEVICES][(USB_EPL * 2) + 1]; + +// One without, and one with, interrupt endpoints +extern uint8_t currentDeviceNumWithSendPipe[2]; + +extern uint16_t g_usb_usbmode; + +/// Timestamp of the last BRDY event, from the SSI_TX_DMA_CHANNEL +extern uint32_t timeLastBRDY[USB_NUM_USBIP]; + +/// Start a receive operation on a given pipe +void usb_receive_start_rohan_midi(uint16_t pipe); + +// Used when deluge is in host mode. +// +// Implemented in root_complex/usb_hosted.cpp +void usbSendCompleteAsHost(int32_t ip); + +// Used when deluge is in peripheral mode. +// +// Implemented in root_complex/usb_peripheral.cpp +void usbSendCompleteAsPeripheral(int32_t ip); + +#ifdef __cplusplus +} +#endif diff --git a/src/deluge/playback/playback_handler.cpp b/src/deluge/playback/playback_handler.cpp index 03045f5096..3d756880ee 100644 --- a/src/deluge/playback/playback_handler.cpp +++ b/src/deluge/playback/playback_handler.cpp @@ -122,14 +122,7 @@ extern "C" uint32_t triggerClockRisingEdgesProcessed; // This function will be called repeatedly, at all times, to see if it's time to do a tick, and such void PlaybackHandler::routine() { - - // Check incoming USB MIDI - midiEngine.checkIncomingUsbMidi(); - - // Check incoming Serial MIDI - for (int32_t i = 0; i < 12 && midiEngine.checkIncomingSerialMidi(); i++) { - ; - } + midiEngine.checkIncomingMidi(); // Check analog clock input if (triggerClockRisingEdgesProcessed != triggerClockRisingEdgesReceived) { diff --git a/src/deluge/testing/hardware_testing.cpp b/src/deluge/testing/hardware_testing.cpp index 4660b4d368..a207cbfab3 100644 --- a/src/deluge/testing/hardware_testing.cpp +++ b/src/deluge/testing/hardware_testing.cpp @@ -27,6 +27,7 @@ #include "hid/led/indicator_leds.h" #include "hid/matrix/matrix_driver.h" #include "io/debug/log.h" +#include "io/midi/midi_device_manager.h" #include "io/midi/midi_engine.h" #include "processing/engines/audio_engine.h" #include "processing/engines/cv_engine.h" @@ -203,7 +204,7 @@ void readInputsForHardwareTest(bool testButtonStates[9][16]) { } } - midiEngine.checkIncomingSerialMidi(); + auto _ = MIDIDeviceManager::rootDin.poll(); midiEngine.flushMIDI(); encoders::readEncoders(); diff --git a/src/deluge/util/container/array/resizeable_array.h b/src/deluge/util/container/array/resizeable_array.h index 852e45fa11..fe4e89b63a 100644 --- a/src/deluge/util/container/array/resizeable_array.h +++ b/src/deluge/util/container/array/resizeable_array.h @@ -53,7 +53,7 @@ class ResizeableArray { return (char* __restrict__)memory + (absoluteIndex * elementSize); } - [[gnu::always_inline]] inline int32_t getNumElements() { return numElements; } + [[gnu::always_inline]] inline int32_t getNumElements() const { return numElements; } uint32_t elementSize; bool emptyingShouldFreeMemory;