diff --git a/src/mididevice.cpp b/src/mididevice.cpp index f387dbc..413dd13 100644 --- a/src/mididevice.cpp +++ b/src/mididevice.cpp @@ -214,7 +214,18 @@ void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsign u8 ucType = ucStatus >> 4; // GLOBAL MIDI SYSEX - // + + // Set MIDI Channel for TX816/TX216 SysEx; in MiniDexed, we interpret the device parameter as the number of the TG (unlike the TX816/TX216 which has a hardware switch to select the TG) + if (nLength >= 6 && pMessage[0] == MIDI_SYSTEM_EXCLUSIVE_BEGIN && pMessage[1] == 0x43 && pMessage[3] == 0x04 && pMessage[4] == 0x01) { + uint8_t mTG = pMessage[2] & 0x0F; + uint8_t val = pMessage[5]; + LOGNOTE("MIDI-SYSEX: Set TG%d to MIDI Channel %d", mTG + 1, val & 0x0F); + m_pSynthesizer->SetMIDIChannel(val & 0x0F, mTG); + // Do not process this message further for any TGs + m_MIDISpinLock.Release(); + return; + } + // Master Volume is set using a MIDI SysEx message as follows: // F0 Start of SysEx // 7F System Realtime SysEx @@ -320,16 +331,17 @@ 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); + if (nLength == 5) { + break; // Send dump request only to the first TG that matches the MIDI channel requested via the SysEx message device ID + } + // Check for TX216/TX816 style performance sysex messages if (pMessage[3] == 0x04) @@ -339,21 +351,12 @@ void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsign uint8_t par = pMessage[4]; uint8_t val = pMessage[5]; - // For parameter 1 (Set MIDI Channel), only process for the TG with the number in pMessage[2] - if (par == 1) { - if (nTG != mTG) continue; - } else { - // For all other parameters, process for all TGs listening on the MIDI channel mTG or OmniMode - if (!(m_ChannelMap[nTG] == mTG || m_ChannelMap[nTG] == OmniMode)) continue; - } + if (!(m_ChannelMap[nTG] == mTG || m_ChannelMap[nTG] == OmniMode)) continue; + LOGNOTE("MIDI-SYSEX: Assuming TX216/TX816 style performance sysex message because 4th byte is 0x04"); switch (par) { - case 1: // MIDI Channel - LOGNOTE("MIDI-SYSEX: Set TG%d to MIDI Channel %d", mTG, val & 0x0F); - m_pSynthesizer->SetMIDIChannel(val & 0x0F, mTG); - break; case 2: // Poly/Mono LOGNOTE("MIDI-SYSEX: Set Poly/Mono %d to %d", nTG, val & 0x0F); m_pSynthesizer->setMonoMode(val ? true : false, nTG); @@ -452,11 +455,10 @@ void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsign } } } - 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: @@ -733,6 +735,25 @@ 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) { + + LOGDBG("HandleSystemExclusive: TG %d, length %zu", nTG, nLength); + + // 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); @@ -802,25 +823,22 @@ 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()); - } + // Example: F0 43 20 00 F7 + 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()); + } 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 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 40c69b7..51e6444 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 82014e1..8cbd3a4 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"); @@ -876,4 +879,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 a774592..492be10 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 1934e42..000bc3d 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