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