From bd8926a5f3b8a776ebce453477b49535525e316e Mon Sep 17 00:00:00 2001 From: probonopd Date: Tue, 29 Apr 2025 23:43:33 +0200 Subject: [PATCH 1/5] SysEx voice dump request sends something back over rtpMIDI --- src/mididevice.cpp | 44 +++++++++++++++++++++++++++--------------- src/mididevice.h | 4 +++- src/minidexed.cpp | 2 +- src/net/applemidi.cpp | 45 +++++++++++++++++++++++++++++++++++++++++++ src/net/applemidi.h | 3 +++ src/udpmididevice.cpp | 26 +++++++++++++++++++++++++ src/udpmididevice.h | 7 +++++++ 7 files changed, 113 insertions(+), 18 deletions(-) diff --git a/src/mididevice.cpp b/src/mididevice.cpp index 576821f..cdb2f3e 100644 --- a/src/mididevice.cpp +++ b/src/mididevice.cpp @@ -330,6 +330,7 @@ void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsign { LOGNOTE("MIDI-SYSEX: channel: %u, len: %u, TG: %u",m_ChannelMap[nTG],nLength,nTG); HandleSystemExclusive(pMessage, nLength, nCable, nTG); + break; // Only the first TG listening to the MIDI channel can handle the SysEx message (e.g., voice dump); may need to restrict this to dump requests only } } else @@ -610,6 +611,23 @@ bool CMIDIDevice::HandleMIDISystemCC(const u8 ucCC, const u8 ucCCval) void CMIDIDevice::HandleSystemExclusive(const uint8_t* pMessage, const size_t nLength, const unsigned nCable, const uint8_t nTG) { + + // Check if it is a dump request; these have the format F0 43 2n ff F7 + // with n = the MIDI channel and ff = 00 for voice or 09 for bank + // It was confirmed that on the TX816, the device number is interpreted as the MIDI channel; + if (nLength == 5 && pMessage[3] == 0x00) + { + LOGDBG("SysEx voice dump request: device %d", nTG); + SendSystemExclusiveVoice(nTG, m_DeviceName, nCable, nTG); + return; + } + else if (nLength == 5 && pMessage[3] == 0x09) + { + LOGDBG("SysEx bank dump request: device %d", nTG); + LOGDBG("Still to be implemented"); + return; + } + int16_t sysex_return; sysex_return = m_pSynthesizer->checkSystemExclusive(pMessage, nLength, nTG); @@ -735,25 +753,19 @@ void CMIDIDevice::HandleSystemExclusive(const uint8_t* pMessage, const size_t nL else if(sysex_return >= 500 && sysex_return < 600) { LOGDBG("SysEx send voice %u request",sysex_return-500); - SendSystemExclusiveVoice(sysex_return-500, nCable, nTG); + SendSystemExclusiveVoice(sysex_return-500, m_DeviceName, nCable, nTG); } break; } } -void CMIDIDevice::SendSystemExclusiveVoice(uint8_t nVoice, const unsigned nCable, uint8_t nTG) +void CMIDIDevice::SendSystemExclusiveVoice(uint8_t nVoice, const std::string& deviceName, unsigned nCable, uint8_t nTG) { - uint8_t voicedump[163]; - - // Get voice sysex dump from TG - m_pSynthesizer->getSysExVoiceDump(voicedump, nTG); - - TDeviceMap::const_iterator Iterator; - - // send voice dump to all MIDI interfaces - for(Iterator = s_DeviceMap.begin(); Iterator != s_DeviceMap.end(); ++Iterator) - { - Iterator->second->Send (voicedump, sizeof(voicedump)*sizeof(uint8_t)); - // LOGDBG("Send SYSEX voice dump %u to \"%s\"",nVoice,Iterator->first.c_str()); - } -} + uint8_t voicedump[163]; + m_pSynthesizer->getSysExVoiceDump(voicedump, nTG); + TDeviceMap::const_iterator Iterator = s_DeviceMap.find(deviceName); + if (Iterator != s_DeviceMap.end()) { + Iterator->second->Send(voicedump, sizeof(voicedump), nCable); + LOGDBG("Send SYSEX voice dump %u to \"%s\"", nVoice, deviceName.c_str()); + } +} diff --git a/src/mididevice.h b/src/mididevice.h index 94b789b..7da8717 100644 --- a/src/mididevice.h +++ b/src/mididevice.h @@ -54,7 +54,9 @@ public: u8 GetChannel (unsigned nTG) const; virtual void Send (const u8 *pMessage, size_t nLength, unsigned nCable = 0) {} - virtual void SendSystemExclusiveVoice(uint8_t nVoice, const unsigned nCable, uint8_t nTG); + // Change signature to specify device name + void SendSystemExclusiveVoice(uint8_t nVoice, const std::string& deviceName, unsigned nCable, uint8_t nTG); + const std::string& GetDeviceName() const { return m_DeviceName; } protected: void MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsigned nCable = 0); diff --git a/src/minidexed.cpp b/src/minidexed.cpp index 1e925ed..f7c341d 100644 --- a/src/minidexed.cpp +++ b/src/minidexed.cpp @@ -666,7 +666,7 @@ void CMiniDexed::ProgramChange (unsigned nProgram, unsigned nTG) // MIDI channel configured for this TG if (m_nMIDIChannel[nTG] < CMIDIDevice::Channels) { - m_SerialMIDI.SendSystemExclusiveVoice(nProgram,0,nTG); + m_SerialMIDI.SendSystemExclusiveVoice(nProgram, m_SerialMIDI.GetDeviceName(), 0, nTG); } } diff --git a/src/net/applemidi.cpp b/src/net/applemidi.cpp index e14b216..8440426 100644 --- a/src/net/applemidi.cpp +++ b/src/net/applemidi.cpp @@ -31,6 +31,9 @@ #include "applemidi.h" #include "byteorder.h" +#define MAX_DX7_SYSEX_LENGTH 4104 +#define MAX_MIDI_MESSAGE MAX_DX7_SYSEX_LENGTH + // #define APPLEMIDI_DEBUG LOGMODULE("applemidi"); @@ -871,4 +874,46 @@ bool CAppleMIDIParticipant::SendFeedbackPacket() #endif return SendPacket(m_pControlSocket, &m_InitiatorIPAddress, m_nInitiatorControlPort, &FeedbackPacket, sizeof(FeedbackPacket)); +} + +bool CAppleMIDIParticipant::SendMIDIToHost(const u8* pData, size_t nSize) +{ + if (m_State != TState::Connected) + return false; + + // Build RTP-MIDI packet + TRTPMIDI packet; + packet.nFlags = htons((RTPMIDIVersion << 14) | RTPMIDIPayloadType); + packet.nSequence = htons(++m_nSequence); + packet.nTimestamp = htonl(0); // No timestamping for now + packet.nSSRC = htonl(m_nSSRC); + + // RTP-MIDI command section: header + MIDI data + // Header: 0x80 | length (if length < 0x0F) + u8 midiHeader = 0x00; + size_t midiLen = nSize; + if (midiLen < 0x0F) { + midiHeader = midiLen & 0x0F; + } else { + midiHeader = 0x80 | ((midiLen >> 8) & 0x0F); + } + + u8 buffer[sizeof(TRTPMIDI) + 2 + MAX_MIDI_MESSAGE]; + size_t offset = 0; + memcpy(buffer + offset, &packet, sizeof(TRTPMIDI)); + offset += sizeof(TRTPMIDI); + buffer[offset++] = midiHeader; + if (midiLen >= 0x0F) { + buffer[offset++] = midiLen & 0xFF; + } + memcpy(buffer + offset, pData, midiLen); + offset += midiLen; + + if (SendPacket(m_pMIDISocket, &m_InitiatorIPAddress, m_nInitiatorMIDIPort, buffer, offset) <= 0) { + LOGNOTE("Failed to send MIDI data to host"); + return false; + } + + LOGDBG("Successfully sent %zu bytes of MIDI data", nSize); + return true; } \ No newline at end of file diff --git a/src/net/applemidi.h b/src/net/applemidi.h index 3df68ae..bc8d2b8 100644 --- a/src/net/applemidi.h +++ b/src/net/applemidi.h @@ -46,6 +46,9 @@ public: virtual void Run() override; +public: + bool SendMIDIToHost(const u8* pData, size_t nSize); + private: void ControlInvitationState(); void MIDIInvitationState(); diff --git a/src/udpmididevice.cpp b/src/udpmididevice.cpp index 4b0d1c7..4c46649 100644 --- a/src/udpmididevice.cpp +++ b/src/udpmididevice.cpp @@ -25,6 +25,8 @@ #include #include "udpmididevice.h" #include +#include +#include #define VIRTUALCABLE 24 @@ -64,6 +66,13 @@ boolean CUDPMIDIDevice::Initialize (void) } else LOGNOTE("UDP MIDI receiver initialized"); + + // UDP MIDI send socket setup (default: broadcast 255.255.255.255:1999) + CNetSubSystem* pNet = CNetSubSystem::Get(); + m_pUDPSendSocket = new CSocket(pNet, IPPROTO_UDP); + m_UDPDestAddress.Set(0xFFFFFFFF); // Broadcast by default + m_UDPDestPort = 1999; + return true; } @@ -87,4 +96,21 @@ void CUDPMIDIDevice::OnAppleMIDIDisconnect(const CIPAddress* pIPAddress, const c void CUDPMIDIDevice::OnUDPMIDIDataReceived(const u8* pData, size_t nSize) { MIDIMessageHandler(pData, nSize, VIRTUALCABLE); +} + +void CUDPMIDIDevice::Send(const u8 *pMessage, size_t nLength, unsigned nCable) +{ + bool sentRTP = false; + if (m_pAppleMIDIParticipant && m_pAppleMIDIParticipant->SendMIDIToHost(pMessage, nLength)) { + sentRTP = true; + LOGNOTE("Sent %zu bytes to RTP-MIDI host", nLength); + } + if (!sentRTP && m_pUDPSendSocket) { + int res = m_pUDPSendSocket->SendTo(pMessage, nLength, 0, m_UDPDestAddress, m_UDPDestPort); + if (res < 0) { + LOGERR("Failed to send %zu bytes to UDP MIDI host", nLength); + } else { + LOGNOTE("Sent %zu bytes to UDP MIDI host (broadcast)", nLength); + } + } } \ No newline at end of file diff --git a/src/udpmididevice.h b/src/udpmididevice.h index de50172..9895c9d 100644 --- a/src/udpmididevice.h +++ b/src/udpmididevice.h @@ -29,6 +29,7 @@ #include "config.h" #include "net/applemidi.h" #include "net/udpmidi.h" +#include "midi.h" class CMiniDexed; @@ -43,6 +44,7 @@ public: virtual void OnAppleMIDIConnect(const CIPAddress* pIPAddress, const char* pName) override; virtual void OnAppleMIDIDisconnect(const CIPAddress* pIPAddress, const char* pName) override; virtual void OnUDPMIDIDataReceived(const u8* pData, size_t nSize) override; + virtual void Send(const u8 *pMessage, size_t nLength, unsigned nCable = 0) override; private: CMiniDexed *m_pSynthesizer; @@ -50,6 +52,11 @@ private: CBcmRandomNumberGenerator m_Random; CAppleMIDIParticipant* m_pAppleMIDIParticipant; // AppleMIDI participant instance CUDPMIDIReceiver* m_pUDPMIDIReceiver; + CSocket* m_pUDPSendSocket = nullptr; + CIPAddress m_UDPDestAddress; + unsigned m_UDPDestPort = 1999; + CIPAddress m_LastUDPSenderAddress; + unsigned m_LastUDPSenderPort = 0; }; #endif From 4ffed80497d634381ded553058f2a2f41c19d283 Mon Sep 17 00:00:00 2001 From: probonopd Date: Thu, 1 May 2025 09:10:21 +0200 Subject: [PATCH 2/5] Add a warning log when no matching device is found --- src/mididevice.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/mididevice.cpp b/src/mididevice.cpp index cdb2f3e..ce7e920 100644 --- a/src/mididevice.cpp +++ b/src/mididevice.cpp @@ -767,5 +767,7 @@ void CMIDIDevice::SendSystemExclusiveVoice(uint8_t nVoice, const std::string& de if (Iterator != s_DeviceMap.end()) { Iterator->second->Send(voicedump, sizeof(voicedump), nCable); LOGDBG("Send SYSEX voice dump %u to \"%s\"", nVoice, deviceName.c_str()); + } else { + LOGWARN("No device found in s_DeviceMap for name: %s", deviceName.c_str()); } } From 32b3b57ca1a12acc49f91bb872b6727b79714f6e Mon Sep 17 00:00:00 2001 From: probonopd Date: Thu, 1 May 2025 09:23:25 +0200 Subject: [PATCH 3/5] Limit to first matching TG only for dump requests Send dump request only to the first TG that matches the MIDI channel requested via the SysEx message device ID but send other SysEx to all, not only the first, matching TGs --- src/mididevice.cpp | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/src/mididevice.cpp b/src/mididevice.cpp index ce7e920..f20cefc 100644 --- a/src/mididevice.cpp +++ b/src/mididevice.cpp @@ -320,24 +320,21 @@ void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsign // Process MIDI for each active Tone Generator bool bSystemCCHandled = false; bool bSystemCCChecked = false; - for (unsigned nTG = 0; nTG < m_pConfig->GetToneGenerators() && !bSystemCCHandled; nTG++) - { - if (ucStatus == MIDI_SYSTEM_EXCLUSIVE_BEGIN) - { - // MIDI SYSEX per MIDI channel - uint8_t ucSysExChannel = (pMessage[2] & 0x0F); - if (m_ChannelMap[nTG] == ucSysExChannel || m_ChannelMap[nTG] == OmniMode) - { + if (ucStatus == MIDI_SYSTEM_EXCLUSIVE_BEGIN) { + uint8_t ucSysExChannel = (pMessage[2] & 0x0F); + for (unsigned nTG = 0; nTG < m_pConfig->GetToneGenerators(); nTG++) { + if (m_ChannelMap[nTG] == ucSysExChannel || m_ChannelMap[nTG] == OmniMode) { LOGNOTE("MIDI-SYSEX: channel: %u, len: %u, TG: %u",m_ChannelMap[nTG],nLength,nTG); HandleSystemExclusive(pMessage, nLength, nCable, nTG); - break; // Only the first TG listening to the MIDI channel can handle the SysEx message (e.g., voice dump); may need to restrict this to dump requests only + if (nLength == 5) { + break; // Send dump request only to the first TG that matches the MIDI channel requested via the SysEx message device ID + } } } - else - { + } else { + for (unsigned nTG = 0; nTG < m_pConfig->GetToneGenerators() && !bSystemCCHandled; nTG++) { if ( m_ChannelMap[nTG] == ucChannel - || m_ChannelMap[nTG] == OmniMode) - { + || m_ChannelMap[nTG] == OmniMode) { switch (ucType) { case MIDI_NOTE_ON: @@ -761,6 +758,7 @@ void CMIDIDevice::HandleSystemExclusive(const uint8_t* pMessage, const size_t nL void CMIDIDevice::SendSystemExclusiveVoice(uint8_t nVoice, const std::string& deviceName, unsigned nCable, uint8_t nTG) { + // Example: F0 43 20 00 F7 uint8_t voicedump[163]; m_pSynthesizer->getSysExVoiceDump(voicedump, nTG); TDeviceMap::const_iterator Iterator = s_DeviceMap.find(deviceName); From 507a336b02c04a183a1be9e4968b9cac97343721 Mon Sep 17 00:00:00 2001 From: probonopd Date: Sat, 3 May 2025 10:01:22 +0200 Subject: [PATCH 4/5] Try to send bank dump (does not work yet) --- src/midichunker.cpp | 48 ++++++++++++++++++ src/midichunker.h | 37 ++++++++++++++ src/mididevice.cpp | 27 ++++++---- src/mididevice.h | 1 + src/midikeyboard.cpp | 116 ++++++++++++++++++++++++++++++++++++++++--- src/midikeyboard.h | 14 ++++++ src/minidexed.cpp | 43 ++++++++++++++++ src/minidexed.h | 1 + syslogserver.py | 3 ++ 9 files changed, 272 insertions(+), 18 deletions(-) create mode 100644 src/midichunker.cpp create mode 100644 src/midichunker.h diff --git a/src/midichunker.cpp b/src/midichunker.cpp new file mode 100644 index 0000000..b069aae --- /dev/null +++ b/src/midichunker.cpp @@ -0,0 +1,48 @@ +// +// midichunker.cpp +// +// MiniDexed - Dexed FM synthesizer for bare metal Raspberry Pi +// Copyright (C) 2022-25 The MiniDexed Team +// +// This program 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 "midichunker.h" +#include + +MIDISysExChunker::MIDISysExChunker(const uint8_t* data, size_t length, size_t chunkSize) + : m_data(data), m_length(length), m_chunkSize(chunkSize), m_offset(0) {} + +bool MIDISysExChunker::hasNext() const { + return m_offset < m_length; +} + +std::vector MIDISysExChunker::next() { + if (!hasNext()) return {}; + size_t remaining = m_length - m_offset; + size_t chunkLen = std::min(m_chunkSize, remaining); + // Only the last chunk should contain the final 0xF7 + if (m_offset + chunkLen >= m_length && m_data[m_length-1] == 0xF7) { + chunkLen = m_length - m_offset; + } else if (m_offset + chunkLen > 0 && m_data[m_offset + chunkLen - 1] == 0xF7) { + chunkLen--; + } + std::vector chunk(m_data + m_offset, m_data + m_offset + chunkLen); + m_offset += chunkLen; + return chunk; +} + +void MIDISysExChunker::reset() { + m_offset = 0; +} diff --git a/src/midichunker.h b/src/midichunker.h new file mode 100644 index 0000000..06f49ac --- /dev/null +++ b/src/midichunker.h @@ -0,0 +1,37 @@ +// +// midichunker.h +// +// MiniDexed - Dexed FM synthesizer for bare metal Raspberry Pi +// Copyright (C) 2022-25 The MiniDexed Team +// +// This program 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 +#include +#include + +class MIDISysExChunker { +public: + MIDISysExChunker(const uint8_t* data, size_t length, size_t chunkSize = 256); + bool hasNext() const; + std::vector next(); + void reset(); +private: + const uint8_t* m_data; + size_t m_length; + size_t m_chunkSize; + size_t m_offset; +}; diff --git a/src/mididevice.cpp b/src/mididevice.cpp index f20cefc..633ea69 100644 --- a/src/mididevice.cpp +++ b/src/mididevice.cpp @@ -30,7 +30,7 @@ #include "midi.h" #include "userinterface.h" -LOGMODULE ("mididevice"); +LOGMODULE("midikeyboard"); // MIDI "System" level (i.e. all TG) custom CC maps // Note: Even if number of TGs is not 8, there are only 8 @@ -249,8 +249,7 @@ void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsign m_pSynthesizer->setMasterVolume(fMasterVolume); } else - { - // Perform any MiniDexed level MIDI handling before specific Tone Generators + { // Perform any MiniDexed level MIDI handling before specific Tone Generators unsigned nPerfCh = m_pSynthesizer->GetPerformanceSelectChannel(); switch (ucType) { @@ -621,7 +620,7 @@ void CMIDIDevice::HandleSystemExclusive(const uint8_t* pMessage, const size_t nL else if (nLength == 5 && pMessage[3] == 0x09) { LOGDBG("SysEx bank dump request: device %d", nTG); - LOGDBG("Still to be implemented"); + SendSystemExclusiveBank(nTG, m_DeviceName, nCable, nTG); return; } @@ -747,12 +746,6 @@ void CMIDIDevice::HandleSystemExclusive(const uint8_t* pMessage, const size_t nL break; } } - else if(sysex_return >= 500 && sysex_return < 600) - { - LOGDBG("SysEx send voice %u request",sysex_return-500); - SendSystemExclusiveVoice(sysex_return-500, m_DeviceName, nCable, nTG); - } - break; } } @@ -769,3 +762,17 @@ void CMIDIDevice::SendSystemExclusiveVoice(uint8_t nVoice, const std::string& de LOGWARN("No device found in s_DeviceMap for name: %s", deviceName.c_str()); } } + +void CMIDIDevice::SendSystemExclusiveBank(uint8_t nVoice, const std::string& deviceName, unsigned nCable, uint8_t nTG) +{ + // Example: F0 43 20 09 F7 + static uint8_t voicedump[4104]; // Use static buffer, correct size for DX7 bank dump + m_pSynthesizer->getSysExBankDump(voicedump, nTG); + TDeviceMap::const_iterator Iterator = s_DeviceMap.find(deviceName); + if (Iterator != s_DeviceMap.end()) { + Iterator->second->Send(voicedump, 4104, nCable); + LOGDBG("Send SYSEX bank dump %u to \"%s\"", nVoice, deviceName.c_str()); + } else { + LOGWARN("No device found in s_DeviceMap for name: %s", deviceName.c_str()); + } +} diff --git a/src/mididevice.h b/src/mididevice.h index 7da8717..104275a 100644 --- a/src/mididevice.h +++ b/src/mididevice.h @@ -56,6 +56,7 @@ public: virtual void Send (const u8 *pMessage, size_t nLength, unsigned nCable = 0) {} // Change signature to specify device name void SendSystemExclusiveVoice(uint8_t nVoice, const std::string& deviceName, unsigned nCable, uint8_t nTG); + void SendSystemExclusiveBank(uint8_t nVoice, const std::string& deviceName, unsigned nCable, uint8_t nTG); const std::string& GetDeviceName() const { return m_DeviceName; } protected: diff --git a/src/midikeyboard.cpp b/src/midikeyboard.cpp index db71168..6d1f410 100644 --- a/src/midikeyboard.cpp +++ b/src/midikeyboard.cpp @@ -21,15 +21,24 @@ // along with this program. If not, see . // #include "midikeyboard.h" +#include "midichunker.h" #include +#include +#include +#include #include #include +#include +#include + +LOGMODULE("midikeyboard"); CMIDIKeyboard::CMIDIKeyboard (CMiniDexed *pSynthesizer, CConfig *pConfig, CUserInterface *pUI, unsigned nInstance) : CMIDIDevice (pSynthesizer, pConfig, pUI), m_nSysExIdx (0), m_nInstance (nInstance), - m_pMIDIDevice (0) + m_pMIDIDevice (0), + m_HasQueuedSysEx(false) { m_DeviceName.Format ("umidi%u", nInstance+1); @@ -42,6 +51,33 @@ CMIDIKeyboard::~CMIDIKeyboard (void) void CMIDIKeyboard::Process (boolean bPlugAndPlayUpdated) { + // Send any queued SysEx response in a safe context + if (m_HasQueuedSysEx && m_pMIDIDevice) { + // Pad to multiple of 4 bytes for USB MIDI event packets + size_t sysexLen = m_QueuedSysEx.size(); + size_t paddedLen = (sysexLen + 3) & ~3; // round up to next multiple of 4 + if (paddedLen > sysexLen) { + m_QueuedSysEx.resize(paddedLen, 0x00); + } + // Send in safe chunks to avoid USB lockup + static constexpr size_t kUSBMIDIMaxChunk = 256; // or 512 if your stack allows + size_t offset = 0; + // Only send one chunk per Process() call to avoid blocking or watchdog reset + if (offset < m_QueuedSysEx.size()) { + size_t chunk = std::min(kUSBMIDIMaxChunk, m_QueuedSysEx.size() - offset); + m_pMIDIDevice->SendEventPackets(m_QueuedSysEx.data() + offset, chunk); + offset += chunk; + // Save progress for next Process() call + if (offset < m_QueuedSysEx.size()) { + // Not done yet, keep queued SysEx and return + m_QueuedSysEx.erase(m_QueuedSysEx.begin(), m_QueuedSysEx.begin() + chunk); + return; + } + } + m_QueuedSysEx.clear(); + m_HasQueuedSysEx = false; + } + while (!m_SendQueue.empty ()) { TSendQueueEntry Entry = m_SendQueue.front (); @@ -73,16 +109,80 @@ void CMIDIKeyboard::Process (boolean bPlugAndPlayUpdated) } } -void CMIDIKeyboard::Send (const u8 *pMessage, size_t nLength, unsigned nCable) +// Helper: Convert SysEx to USB MIDI event packets +std::vector> SysExToUSBMIDIPackets(const uint8_t* data, size_t length, unsigned cable) { - TSendQueueEntry Entry; - Entry.pMessage = new u8[nLength]; - Entry.nLength = nLength; - Entry.nCable = nCable; + LOGNOTE("SysExToUSBMIDIPackets: length=%u, cable=%u", (unsigned)length, cable); + std::vector> packets; + size_t idx = 0; + while (idx < length) { + size_t remaining = length - idx; + uint8_t cin; + uint8_t packet[4] = {0}; + packet[0] = (uint8_t)(cable << 4); // Upper nibble: cable number, lower: CIN + if (remaining >= 3) { + if (idx == 0) { + cin = 0x4; // SysEx Start or continue + } else { + cin = 0x4; // SysEx continue + } + packet[0] |= cin; + packet[1] = data[idx]; + packet[2] = data[idx+1]; + packet[3] = data[idx+2]; + LOGNOTE(" Packet: [%02X %02X %02X %02X] (idx=%u)", packet[0], packet[1], packet[2], packet[3], (unsigned)idx); + idx += 3; + } else if (remaining == 2) { + cin = 0x6; // SysEx ends with 2 bytes + packet[0] |= cin; + packet[1] = data[idx]; + packet[2] = data[idx+1]; + packet[3] = 0; + LOGNOTE(" Packet: [%02X %02X %02X %02X] (last 2 bytes)", packet[0], packet[1], packet[2], packet[3]); + idx += 2; + } else if (remaining == 1) { + cin = 0x5; // SysEx ends with 1 byte + packet[0] |= cin; + packet[1] = data[idx]; + packet[2] = 0; + packet[3] = 0; + LOGNOTE(" Packet: [%02X %02X %02X %02X] (last 1 byte)", packet[0], packet[1], packet[2], packet[3]); + idx += 1; + } + packets.push_back({packet[0], packet[1], packet[2], packet[3]}); + } + LOGNOTE("SysExToUSBMIDIPackets: total packets=%u", (unsigned)packets.size()); + return packets; +} - memcpy (Entry.pMessage, pMessage, nLength); +void CMIDIKeyboard::Send(const u8 *pMessage, size_t nLength, unsigned nCable) +{ + // NOTE: For USB MIDI, we do NOT use MIDISysExChunker for SysEx sending. + // The chunker splits SysEx into arbitrary chunks for traditional MIDI (e.g., serial/DIN), + // but USB MIDI requires SysEx to be split into 4-byte USB MIDI event packets with specific CIN headers. + // Therefore, for USB MIDI, we packetize SysEx according to the USB MIDI spec and send with SendEventPackets(). + // See: https://www.usb.org/sites/default/files/midi10.pdf (USB MIDI 1.0 spec) + // This is why the chunker is bypassed for USB MIDI SysEx sending. - m_SendQueue.push (Entry); + // Check for valid SysEx + if (nLength >= 2 && pMessage[0] == 0xF0 && pMessage[nLength-1] == 0xF7 && m_pMIDIDevice) { + // Convert to USB MIDI event packets and send directly + auto packets = SysExToUSBMIDIPackets(pMessage, nLength, nCable); + std::vector flat; + for (const auto& pkt : packets) { + flat.insert(flat.end(), pkt.begin(), pkt.end()); + } + m_QueuedSysEx = flat; + m_HasQueuedSysEx = true; + return; + } + // Not a SysEx, send as-is + TSendQueueEntry Entry; + Entry.pMessage = new u8[nLength]; + Entry.nLength = nLength; + Entry.nCable = nCable; + memcpy(Entry.pMessage, pMessage, nLength); + m_SendQueue.push(Entry); } // Most packets will be passed straight onto the main MIDI message handler diff --git a/src/midikeyboard.h b/src/midikeyboard.h index bf62689..642c726 100644 --- a/src/midikeyboard.h +++ b/src/midikeyboard.h @@ -30,6 +30,7 @@ #include #include #include +#include #define USB_SYSEX_BUFFER_SIZE (MAX_DX7_SYSEX_LENGTH+128) // Allow a bit spare to handle unexpected SysEx messages @@ -45,6 +46,14 @@ public: void Send (const u8 *pMessage, size_t nLength, unsigned nCable = 0) override; + void QueueSysExResponse(const uint8_t* data, size_t len) { + m_QueuedSysEx.assign(data, data + len); + m_HasQueuedSysEx = true; + } + bool HasQueuedSysExResponse() const { return m_HasQueuedSysEx; } + void ClearQueuedSysExResponse() { m_QueuedSysEx.clear(); m_HasQueuedSysEx = false; } + const std::vector& GetQueuedSysExResponse() const { return m_QueuedSysEx; } + private: static void MIDIPacketHandler (unsigned nCable, u8 *pPacket, unsigned nLength, unsigned nDevice, void *pParam); static void DeviceRemovedHandler (CDevice *pDevice, void *pContext); @@ -68,6 +77,11 @@ private: CUSBMIDIDevice * volatile m_pMIDIDevice; std::queue m_SendQueue; + + std::vector m_QueuedSysEx; + bool m_HasQueuedSysEx = false; }; +std::vector> SysExToUSBMIDIPackets(const uint8_t* data, size_t length, unsigned cable); + #endif diff --git a/src/minidexed.cpp b/src/minidexed.cpp index f7c341d..9843174 100644 --- a/src/minidexed.cpp +++ b/src/minidexed.cpp @@ -1868,6 +1868,49 @@ void CMiniDexed::getSysExVoiceDump(uint8_t* dest, uint8_t nTG) dest[162] = 0xF7; // SysEx end } +void CMiniDexed::getSysExBankDump(uint8_t* dest, uint8_t nTG) +{ + // DX7 Bulk Dump: 32 voices + // Header: F0 43 00 09 20 00 + // Data: 4096 bytes (32 voices x 128 bytes packed) + // Checksum: 1 byte (2's complement of sum of 4096 data bytes, masked to 7 bits) + // Footer: F7 + // Total: 4104 bytes + + constexpr size_t kVoices = 32; + constexpr size_t kPackedVoiceSize = 128; + constexpr size_t kBulkDataSize = kVoices * kPackedVoiceSize; // 4096 + constexpr size_t kHeaderSize = 6; + constexpr size_t kTotalSize = kHeaderSize + kBulkDataSize + 2; // +checksum +F7 = 4104 + + // Header (Yamaha DX7 standard) + dest[0] = 0xF0; // SysEx start + dest[1] = 0x43; // Yamaha ID + dest[2] = 0x00; // Sub-status (0), device/channel (0) + dest[3] = 0x09; // Format number (9 = 32 voices) + dest[4] = 0x20; // Byte count MSB (4096 = 0x1000, MSB=0x20) + dest[5] = 0x00; // Byte count LSB + + // Fill packed voice data + uint8_t* pData = dest + kHeaderSize; + uint8_t checksum = 0; + for (size_t v = 0; v < kVoices; ++v) { + uint8_t packedVoice[kPackedVoiceSize]; + m_SysExFileLoader.GetVoice(m_nVoiceBankID[nTG], v, packedVoice); + for (size_t b = 0; b < kPackedVoiceSize; ++b) { + pData[v * kPackedVoiceSize + b] = packedVoice[b]; + checksum += packedVoice[b]; + } + } + + // Checksum: 2's complement, masked to 7 bits + checksum = (~checksum + 1) & 0x7F; + dest[kHeaderSize + kBulkDataSize] = checksum; + + // Footer + dest[kHeaderSize + kBulkDataSize + 1] = 0xF7; +} + void CMiniDexed::setOPMask(uint8_t uchOPMask, uint8_t nTG) { m_uchOPMask[nTG] = uchOPMask; diff --git a/src/minidexed.h b/src/minidexed.h index 1658482..3e877a2 100644 --- a/src/minidexed.h +++ b/src/minidexed.h @@ -124,6 +124,7 @@ public: void loadVoiceParameters(const uint8_t* data, uint8_t nTG); void setVoiceDataElement(uint8_t data, uint8_t number, uint8_t nTG); void getSysExVoiceDump(uint8_t* dest, uint8_t nTG); + void getSysExBankDump(uint8_t* dest, uint8_t nTG); void setOPMask(uint8_t uchOPMask, uint8_t nTG); void setModController (unsigned controller, unsigned parameter, uint8_t value, uint8_t nTG); diff --git a/syslogserver.py b/syslogserver.py index feffaa6..16f86f1 100644 --- a/syslogserver.py +++ b/syslogserver.py @@ -34,6 +34,9 @@ class SyslogServer: def handle_message(self, data): message = data[2:].decode('utf-8').strip() + if "Time exceeded (0)" in message: + return + if self.start_time is None: self.start_time = time.time() relative_time = "0:00:00.000" From 13b92752fc8e17f70d4ada4fd2a353b442adac25 Mon Sep 17 00:00:00 2001 From: probonopd Date: Sat, 3 May 2025 12:04:41 +0200 Subject: [PATCH 5/5] Try to send bank dump (does not work yet) 00:15:54.281 >1 - 192.168.0.61 midikeyboard - - - MIDI-SYSEX: channel: 0, len: 5, TG: 0 00:15:54.281 >1 - 192.168.0.61 midikeyboard - - - SysEx bank dump request: device 0 00:15:54.281 >1 - 192.168.0.61 midikeyboard - - - SendSystemExclusiveBank 00:15:54.282 >1 - 192.168.0.61 minidexed - - - getSysExBankDump: called for TG=0 00:15:54.282 >1 - 192.168.0.61 midikeyboard - - - SendSystemExclusiveBank: after getSysExBankDump 00:15:54.282 >1 - 192.168.0.61 midikeyboard - - - SendSystemExclusiveBank: before chunking 00:15:54.282 >1 - 192.168.0.61 midikeyboard - - - SendSystemExclusiveBank: after chunker creation 00:15:54.282 >1 - 192.168.0.61 midikeyboard - - - SendSystemExclusiveBank: device found, starting chunk send loop 00:15:54.283 >1 - 192.168.0.61 midikeyboard - - - SendSystemExclusiveBank: sending chunk 0, size=512 00:15:54.283 >1 - 192.168.0.61 midikeyboard - - - SendSystemExclusiveBank: sending chunk 1, size=512 00:15:54.283 >1 - 192.168.0.61 midikeyboard - - - SendSystemExclusiveBank: sending chunk 2, size=512 00:15:54.284 >1 - 192.168.0.61 midikeyboard - - - SendSystemExclusiveBank: sending chunk 3, size=512 00:15:54.284 >1 - 192.168.0.61 midikeyboard - - - SendSystemExclusiveBank: sending chunk 4, size=512 00:15:54.284 >1 - 192.168.0.61 midikeyboard - - - SendSystemExclusiveBank: sending chunk 5, size=512 00:15:54.284 >1 - 192.168.0.61 midikeyboard - - - SendSystemExclusiveBank: sending chunk 6, size=512 00:15:54.284 >1 - 192.168.0.61 midikeyboard - - - SendSystemExclusiveBank: sending chunk 7, size=512 00:15:54.284 >1 - 192.168.0.61 midikeyboard - - - SendSystemExclusiveBank: all chunks sent, total=8 00:15:54.284 >1 - 192.168.0.61 midikeyboard - - - Send SYSEX bank dump 0 to "umidi1" in 512-byte chunks 00:15:54.284 >1 - 192.168.0.61 midikeyboard - - - SendSystemExclusiveBank: exit 00:15:54.285 >1 - 192.168.0.61 usbmidigadgetendpoint.cpp(134) - - - stack[1] is 0x113E94 00:15:54.285 >1 - 192.168.0.61 usbmidigadgetendpoint.cpp(134) - - - stack[11] is 0xF7CC4 00:15:54.285 >1 - 192.168.0.61 usbmidigadgetendpoint.cpp(134) - - - assertion failed: !(nLength % CUSBMIDIDevice::EventPacketSize) --- src/Makefile | 3 +- src/mididevice.cpp | 309 +++++++++++++++++++++++------------------- src/midikeyboard.cpp | 1 + src/minidexed.cpp | 32 ++++- src/udpmididevice.cpp | 6 +- 5 files changed, 197 insertions(+), 154 deletions(-) diff --git a/src/Makefile b/src/Makefile index 73dbddc..86105c0 100644 --- a/src/Makefile +++ b/src/Makefile @@ -11,7 +11,8 @@ OBJS = main.o kernel.o minidexed.o config.o userinterface.o uimenu.o \ sysexfileloader.o performanceconfig.o perftimer.o \ effect_compressor.o effect_platervbstereo.o uibuttons.o midipin.o \ arm_float_to_q23.o \ - net/ftpdaemon.o net/ftpworker.o net/applemidi.o net/udpmidi.o net/mdnspublisher.o udpmididevice.o + net/ftpdaemon.o net/ftpworker.o net/applemidi.o net/udpmidi.o net/mdnspublisher.o udpmididevice.o \ + midichunker.o OPTIMIZE = -O3 diff --git a/src/mididevice.cpp b/src/mididevice.cpp index 633ea69..4039afd 100644 --- a/src/mididevice.cpp +++ b/src/mididevice.cpp @@ -29,6 +29,7 @@ #include #include "midi.h" #include "userinterface.h" +#include "midichunker.h" LOGMODULE("midikeyboard"); @@ -607,146 +608,146 @@ bool CMIDIDevice::HandleMIDISystemCC(const u8 ucCC, const u8 ucCCval) void CMIDIDevice::HandleSystemExclusive(const uint8_t* pMessage, const size_t nLength, const unsigned nCable, const uint8_t nTG) { - - // Check if it is a dump request; these have the format F0 43 2n ff F7 - // with n = the MIDI channel and ff = 00 for voice or 09 for bank + + // Check if it is a dump request; these have the format F0 43 2n ff F7 +// with n = the MIDI channel and ff = 00 for voice or 09 for bank // It was confirmed that on the TX816, the device number is interpreted as the MIDI channel; - if (nLength == 5 && pMessage[3] == 0x00) - { - LOGDBG("SysEx voice dump request: device %d", nTG); - SendSystemExclusiveVoice(nTG, m_DeviceName, nCable, nTG); - return; - } - else if (nLength == 5 && pMessage[3] == 0x09) - { - LOGDBG("SysEx bank dump request: device %d", nTG); - SendSystemExclusiveBank(nTG, m_DeviceName, nCable, nTG); - return; - } - - int16_t sysex_return; - - sysex_return = m_pSynthesizer->checkSystemExclusive(pMessage, nLength, nTG); - LOGDBG("SYSEX handler return value: %d", sysex_return); - - switch (sysex_return) - { - case -1: - LOGERR("SysEx end status byte not detected."); - break; - case -2: - LOGERR("SysEx vendor not Yamaha."); - break; - case -3: - LOGERR("Unknown SysEx parameter change."); - break; - case -4: - LOGERR("Unknown SysEx voice or function."); - break; - case -5: - LOGERR("Not a SysEx voice bulk upload."); - break; - case -6: - LOGERR("Wrong length for SysEx voice bulk upload (not 155)."); - break; - case -7: - LOGERR("Checksum error for one voice."); - break; - case -8: - LOGERR("Not a SysEx bank bulk upload."); - break; - case -9: - LOGERR("Wrong length for SysEx bank bulk upload (not 4096)."); - case -10: - LOGERR("Checksum error for bank."); - break; - case -11: - LOGERR("Unknown SysEx message."); - break; - case 64: - LOGDBG("SysEx Function parameter change: %d Value %d",pMessage[4],pMessage[5]); - m_pSynthesizer->setMonoMode(pMessage[5],nTG); - break; - case 65: - LOGDBG("SysEx Function parameter change: %d Value %d",pMessage[4],pMessage[5]); - m_pSynthesizer->setPitchbendRange(pMessage[5],nTG); - break; - case 66: - LOGDBG("SysEx Function parameter change: %d Value %d",pMessage[4],pMessage[5]); - m_pSynthesizer->setPitchbendStep(pMessage[5],nTG); - break; - case 67: - LOGDBG("SysEx Function parameter change: %d Value %d",pMessage[4],pMessage[5]); - m_pSynthesizer->setPortamentoMode(pMessage[5],nTG); - break; - case 68: - LOGDBG("SysEx Function parameter change: %d Value %d",pMessage[4],pMessage[5]); - m_pSynthesizer->setPortamentoGlissando(pMessage[5],nTG); - break; - case 69: - LOGDBG("SysEx Function parameter change: %d Value %d",pMessage[4],pMessage[5]); - m_pSynthesizer->setPortamentoTime(pMessage[5],nTG); - break; - case 70: - LOGDBG("SysEx Function parameter change: %d Value %d",pMessage[4],pMessage[5]); - m_pSynthesizer->setModWheelRange(pMessage[5],nTG); - break; - case 71: - LOGDBG("SysEx Function parameter change: %d Value %d",pMessage[4],pMessage[5]); - m_pSynthesizer->setModWheelTarget(pMessage[5],nTG); - break; - case 72: - LOGDBG("SysEx Function parameter change: %d Value %d",pMessage[4],pMessage[5]); - m_pSynthesizer->setFootControllerRange(pMessage[5],nTG); - break; - case 73: - LOGDBG("SysEx Function parameter change: %d Value %d",pMessage[4],pMessage[5]); - m_pSynthesizer->setFootControllerTarget(pMessage[5],nTG); - break; - case 74: - LOGDBG("SysEx Function parameter change: %d Value %d",pMessage[4],pMessage[5]); - m_pSynthesizer->setBreathControllerRange(pMessage[5],nTG); - break; - case 75: - LOGDBG("SysEx Function parameter change: %d Value %d",pMessage[4],pMessage[5]); - m_pSynthesizer->setBreathControllerTarget(pMessage[5],nTG); - break; - case 76: - LOGDBG("SysEx Function parameter change: %d Value %d",pMessage[4],pMessage[5]); - m_pSynthesizer->setAftertouchRange(pMessage[5],nTG); - break; - case 77: - LOGDBG("SysEx Function parameter change: %d Value %d",pMessage[4],pMessage[5]); - m_pSynthesizer->setAftertouchTarget(pMessage[5],nTG); - break; - case 100: - // load sysex-data into voice memory - LOGDBG("One Voice bulk upload"); - m_pSynthesizer->loadVoiceParameters(pMessage,nTG); - break; - case 200: - LOGDBG("Bank bulk upload."); - //TODO: add code for storing a bank bulk upload - LOGNOTE("Currently code for storing a bulk bank upload is missing!"); - break; - case 455: - // Parameter 155 + 300 added by Synth_Dexed = 455 - LOGDBG("Operators enabled: %d%d%d%d%d%d", (pMessage[5] & 0x20) ? 1 : 0, (pMessage[5] & 0x10) ? 1 : 0, (pMessage[5] & 0x08) ? 1 : 0, (pMessage[5] & 0x04) ? 1 : 0, (pMessage[5] & 0x02) ? 1 : 0, (pMessage[5] & 0x01) ? 1 : 0); - m_pSynthesizer->setOPMask(pMessage[5], nTG); - break; - default: - if(sysex_return >= 300 && sysex_return < 500) - { - LOGDBG("SysEx voice parameter change: Parameter %d value: %d",pMessage[4] + ((pMessage[3] & 0x03) * 128), pMessage[5]); - m_pSynthesizer->setVoiceDataElement(pMessage[4] + ((pMessage[3] & 0x03) * 128), pMessage[5],nTG); - switch(pMessage[4] + ((pMessage[3] & 0x03) * 128)) - { - case 134: - m_pSynthesizer->notesOff(0,nTG); + if (nLength == 5 && pMessage[3] == 0x00) + { + LOGDBG("SysEx voice dump request: device %d", nTG); + SendSystemExclusiveVoice(nTG, m_DeviceName, nCable, nTG); + return; + } + else if (nLength == 5 && pMessage[3] == 0x09) + { + LOGDBG("SysEx bank dump request: device %d", nTG); + SendSystemExclusiveBank(nTG, m_DeviceName, nCable, nTG); + return; + } + + int16_t sysex_return; + + sysex_return = m_pSynthesizer->checkSystemExclusive(pMessage, nLength, nTG); + LOGDBG("SYSEX handler return value: %d", sysex_return); + + switch (sysex_return) + { + case -1: + LOGERR("SysEx end status byte not detected."); break; - } - } - } + case -2: + LOGERR("SysEx vendor not Yamaha."); + break; + case -3: + LOGERR("Unknown SysEx parameter change."); + break; + case -4: + LOGERR("Unknown SysEx voice or function."); + break; + case -5: + LOGERR("Not a SysEx voice bulk upload."); + break; + case -6: + LOGERR("Wrong length for SysEx voice bulk upload (not 155)."); + break; + case -7: + LOGERR("Checksum error for one voice."); + break; + case -8: + LOGERR("Not a SysEx bank bulk upload."); + break; + case -9: + LOGERR("Wrong length for SysEx bank bulk upload (not 4096)."); + case -10: + LOGERR("Checksum error for bank."); + break; + case -11: + LOGERR("Unknown SysEx message."); + break; + case 64: + LOGDBG("SysEx Function parameter change: %d Value %d",pMessage[4],pMessage[5]); + m_pSynthesizer->setMonoMode(pMessage[5],nTG); + break; + case 65: + LOGDBG("SysEx Function parameter change: %d Value %d",pMessage[4],pMessage[5]); + m_pSynthesizer->setPitchbendRange(pMessage[5],nTG); + break; + case 66: + LOGDBG("SysEx Function parameter change: %d Value %d",pMessage[4],pMessage[5]); + m_pSynthesizer->setPitchbendStep(pMessage[5],nTG); + break; + case 67: + LOGDBG("SysEx Function parameter change: %d Value %d",pMessage[4],pMessage[5]); + m_pSynthesizer->setPortamentoMode(pMessage[5],nTG); + break; + case 68: + LOGDBG("SysEx Function parameter change: %d Value %d",pMessage[4],pMessage[5]); + m_pSynthesizer->setPortamentoGlissando(pMessage[5],nTG); + break; + case 69: + LOGDBG("SysEx Function parameter change: %d Value %d",pMessage[4],pMessage[5]); + m_pSynthesizer->setPortamentoTime(pMessage[5],nTG); + break; + case 70: + LOGDBG("SysEx Function parameter change: %d Value %d",pMessage[4],pMessage[5]); + m_pSynthesizer->setModWheelRange(pMessage[5],nTG); + break; + case 71: + LOGDBG("SysEx Function parameter change: %d Value %d",pMessage[4],pMessage[5]); + m_pSynthesizer->setModWheelTarget(pMessage[5],nTG); + break; + case 72: + LOGDBG("SysEx Function parameter change: %d Value %d",pMessage[4],pMessage[5]); + m_pSynthesizer->setFootControllerRange(pMessage[5],nTG); + break; + case 73: + LOGDBG("SysEx Function parameter change: %d Value %d",pMessage[4],pMessage[5]); + m_pSynthesizer->setFootControllerTarget(pMessage[5],nTG); + break; + case 74: + LOGDBG("SysEx Function parameter change: %d Value %d",pMessage[4],pMessage[5]); + m_pSynthesizer->setBreathControllerRange(pMessage[5],nTG); + break; + case 75: + LOGDBG("SysEx Function parameter change: %d Value %d",pMessage[4],pMessage[5]); + m_pSynthesizer->setBreathControllerTarget(pMessage[5],nTG); + break; + case 76: + LOGDBG("SysEx Function parameter change: %d Value %d",pMessage[4],pMessage[5]); + m_pSynthesizer->setAftertouchRange(pMessage[5],nTG); + break; + case 77: + LOGDBG("SysEx Function parameter change: %d Value %d",pMessage[4],pMessage[5]); + m_pSynthesizer->setAftertouchTarget(pMessage[5],nTG); + break; + case 100: + // load sysex-data into voice memory + LOGDBG("One Voice bulk upload"); + m_pSynthesizer->loadVoiceParameters(pMessage,nTG); + break; + case 200: + LOGDBG("Bank bulk upload."); + //TODO: add code for storing a bank bulk upload + LOGNOTE("Currently code for storing a bulk bank upload is missing!"); + break; + case 455: + // Parameter 155 + 300 added by Synth_Dexed = 455 + LOGDBG("Operators enabled: %d%d%d%d%d%d", (pMessage[5] & 0x20) ? 1 : 0, (pMessage[5] & 0x10) ? 1 : 0, (pMessage[5] & 0x08) ? 1 : 0, (pMessage[5] & 0x04) ? 1 : 0, (pMessage[5] & 0x02) ? 1 : 0, (pMessage[5] & 0x01) ? 1 : 0); + m_pSynthesizer->setOPMask(pMessage[5], nTG); + break; + default: + if(sysex_return >= 300 && sysex_return < 500) + { + LOGDBG("SysEx voice parameter change: Parameter %d value: %d",pMessage[4] + ((pMessage[3] & 0x03) * 128), pMessage[5]); + m_pSynthesizer->setVoiceDataElement(pMessage[4] + ((pMessage[3] & 0x03) * 128), pMessage[5],nTG); + switch(pMessage[4] + ((pMessage[3] & 0x03) * 128)) + { + case 134: + m_pSynthesizer->notesOff(0,nTG); + break; + } + } + } } void CMIDIDevice::SendSystemExclusiveVoice(uint8_t nVoice, const std::string& deviceName, unsigned nCable, uint8_t nTG) @@ -765,14 +766,36 @@ void CMIDIDevice::SendSystemExclusiveVoice(uint8_t nVoice, const std::string& de void CMIDIDevice::SendSystemExclusiveBank(uint8_t nVoice, const std::string& deviceName, unsigned nCable, uint8_t nTG) { - // Example: F0 43 20 09 F7 - static uint8_t voicedump[4104]; // Use static buffer, correct size for DX7 bank dump + LOGNOTE("SendSystemExclusiveBank"); + static uint8_t voicedump[4096]; // Correct size for DX7 bank dump m_pSynthesizer->getSysExBankDump(voicedump, nTG); + LOGNOTE("SendSystemExclusiveBank: after getSysExBankDump"); + LOGNOTE("SendSystemExclusiveBank: before chunking"); + MIDISysExChunker chunker(voicedump, 4096, 512); + LOGNOTE("SendSystemExclusiveBank: after chunker creation"); TDeviceMap::const_iterator Iterator = s_DeviceMap.find(deviceName); if (Iterator != s_DeviceMap.end()) { - Iterator->second->Send(voicedump, 4104, nCable); - LOGDBG("Send SYSEX bank dump %u to \"%s\"", nVoice, deviceName.c_str()); + LOGNOTE("SendSystemExclusiveBank: device found, starting chunk send loop"); + int chunkCount = 0; + while (chunker.hasNext()) { + std::vector chunk = chunker.next(); + // Pad chunk to a multiple of 4 bytes for USB MIDI + size_t pad = chunk.size() % 4; + if (pad != 0) { + chunk.resize(chunk.size() + (4 - pad), 0x00); + } + if (chunk.size() % 4 != 0) { + LOGERR("Chunk size %u is not a multiple of 4 before Send!", (unsigned)chunk.size()); + assert(chunk.size() % 4 == 0); + } + LOGNOTE("SendSystemExclusiveBank: sending chunk %d, size=%u", chunkCount, (unsigned)chunk.size()); + Iterator->second->Send(chunk.data(), chunk.size(), nCable); + chunkCount++; + } + LOGNOTE("SendSystemExclusiveBank: all chunks sent, total=%d", chunkCount); + LOGDBG("Send SYSEX bank dump %u to \"%s\" in 512-byte chunks", nVoice, deviceName.c_str()); } else { - LOGWARN("No device found in s_DeviceMap for name: %s", deviceName.c_str()); + LOGWARN("SendSystemExclusiveBank: No device found in s_DeviceMap for name: %s", deviceName.c_str()); } + LOGNOTE("SendSystemExclusiveBank: exit"); } diff --git a/src/midikeyboard.cpp b/src/midikeyboard.cpp index 6d1f410..b581210 100644 --- a/src/midikeyboard.cpp +++ b/src/midikeyboard.cpp @@ -65,6 +65,7 @@ void CMIDIKeyboard::Process (boolean bPlugAndPlayUpdated) // Only send one chunk per Process() call to avoid blocking or watchdog reset if (offset < m_QueuedSysEx.size()) { size_t chunk = std::min(kUSBMIDIMaxChunk, m_QueuedSysEx.size() - offset); + LOGNOTE("SendEventPackets: about to send chunk at offset %u, length=%u", offset, chunk); m_pMIDIDevice->SendEventPackets(m_QueuedSysEx.data() + offset, chunk); offset += chunk; // Save progress for next Process() call diff --git a/src/minidexed.cpp b/src/minidexed.cpp index 9843174..0f28f37 100644 --- a/src/minidexed.cpp +++ b/src/minidexed.cpp @@ -1870,13 +1870,8 @@ void CMiniDexed::getSysExVoiceDump(uint8_t* dest, uint8_t nTG) void CMiniDexed::getSysExBankDump(uint8_t* dest, uint8_t nTG) { - // DX7 Bulk Dump: 32 voices - // Header: F0 43 00 09 20 00 - // Data: 4096 bytes (32 voices x 128 bytes packed) - // Checksum: 1 byte (2's complement of sum of 4096 data bytes, masked to 7 bits) - // Footer: F7 - // Total: 4104 bytes - + LOGNOTE("getSysExBankDump: called for TG=%u", nTG); + return; constexpr size_t kVoices = 32; constexpr size_t kPackedVoiceSize = 128; constexpr size_t kBulkDataSize = kVoices * kPackedVoiceSize; // 4096 @@ -1884,6 +1879,7 @@ void CMiniDexed::getSysExBankDump(uint8_t* dest, uint8_t nTG) constexpr size_t kTotalSize = kHeaderSize + kBulkDataSize + 2; // +checksum +F7 = 4104 // Header (Yamaha DX7 standard) + LOGNOTE("getSysExBankDump: writing header"); dest[0] = 0xF0; // SysEx start dest[1] = 0x43; // Yamaha ID dest[2] = 0x00; // Sub-status (0), device/channel (0) @@ -1891,6 +1887,8 @@ void CMiniDexed::getSysExBankDump(uint8_t* dest, uint8_t nTG) dest[4] = 0x20; // Byte count MSB (4096 = 0x1000, MSB=0x20) dest[5] = 0x00; // Byte count LSB + LOGNOTE("getSysExBankDump: header: %02X %02X %02X %02X %02X %02X", dest[0], dest[1], dest[2], dest[3], dest[4], dest[5]); + // Fill packed voice data uint8_t* pData = dest + kHeaderSize; uint8_t checksum = 0; @@ -1902,13 +1900,33 @@ void CMiniDexed::getSysExBankDump(uint8_t* dest, uint8_t nTG) checksum += packedVoice[b]; } } + LOGNOTE("getSysExBankDump: packed data filled, checksum before complement: %02X", checksum); // Checksum: 2's complement, masked to 7 bits checksum = (~checksum + 1) & 0x7F; dest[kHeaderSize + kBulkDataSize] = checksum; + LOGNOTE("getSysExBankDump: checksum after complement: %02X", checksum); // Footer dest[kHeaderSize + kBulkDataSize + 1] = 0xF7; + LOGNOTE("getSysExBankDump: footer: %02X", dest[kHeaderSize + kBulkDataSize + 1]); + + // Log summary of dump + LOGNOTE("getSysExBankDump: total size: %d bytes", kTotalSize); + std::string dumpStart, dumpEnd; + char buf[8]; + for (size_t i = 0; i < 16; ++i) { + snprintf(buf, sizeof(buf), "%02X", dest[i]); + dumpStart += buf; + if (i < 15) dumpStart += " "; + } + for (size_t i = kTotalSize - 16; i < kTotalSize; ++i) { + snprintf(buf, sizeof(buf), "%02X", dest[i]); + dumpEnd += buf; + if (i < kTotalSize - 1) dumpEnd += " "; + } + LOGNOTE("getSysExBankDump: first 16 bytes: %s", dumpStart.c_str()); + LOGNOTE("getSysExBankDump: last 16 bytes: %s", dumpEnd.c_str()); } void CMiniDexed::setOPMask(uint8_t uchOPMask, uint8_t nTG) diff --git a/src/udpmididevice.cpp b/src/udpmididevice.cpp index 4c46649..8a33e98 100644 --- a/src/udpmididevice.cpp +++ b/src/udpmididevice.cpp @@ -103,14 +103,14 @@ void CUDPMIDIDevice::Send(const u8 *pMessage, size_t nLength, unsigned nCable) bool sentRTP = false; if (m_pAppleMIDIParticipant && m_pAppleMIDIParticipant->SendMIDIToHost(pMessage, nLength)) { sentRTP = true; - LOGNOTE("Sent %zu bytes to RTP-MIDI host", nLength); + LOGNOTE("Sent %d bytes to RTP-MIDI host", nLength); } if (!sentRTP && m_pUDPSendSocket) { int res = m_pUDPSendSocket->SendTo(pMessage, nLength, 0, m_UDPDestAddress, m_UDPDestPort); if (res < 0) { - LOGERR("Failed to send %zu bytes to UDP MIDI host", nLength); + LOGERR("Failed to send %d bytes to UDP MIDI host", nLength); } else { - LOGNOTE("Sent %zu bytes to UDP MIDI host (broadcast)", nLength); + LOGNOTE("Sent %d bytes to UDP MIDI host (broadcast)", nLength); } } } \ No newline at end of file