Skip to content

Commit 6a9d3a6

Browse files
authored
Honor voice dump requests; make rtpMIDI bi-directional (#899)
Single Voice Dump works; tested over rtpMIDI and USB MIDI
1 parent e6b7ef5 commit 6a9d3a6

File tree

7 files changed

+142
-41
lines changed

7 files changed

+142
-41
lines changed

src/mididevice.cpp

Lines changed: 57 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,18 @@ void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsign
214214
u8 ucType = ucStatus >> 4;
215215

216216
// GLOBAL MIDI SYSEX
217-
//
217+
218+
// 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)
219+
if (nLength >= 6 && pMessage[0] == MIDI_SYSTEM_EXCLUSIVE_BEGIN && pMessage[1] == 0x43 && pMessage[3] == 0x04 && pMessage[4] == 0x01) {
220+
uint8_t mTG = pMessage[2] & 0x0F;
221+
uint8_t val = pMessage[5];
222+
LOGNOTE("MIDI-SYSEX: Set TG%d to MIDI Channel %d", mTG + 1, val & 0x0F);
223+
m_pSynthesizer->SetMIDIChannel(val & 0x0F, mTG);
224+
// Do not process this message further for any TGs
225+
m_MIDISpinLock.Release();
226+
return;
227+
}
228+
218229
// Master Volume is set using a MIDI SysEx message as follows:
219230
// F0 Start of SysEx
220231
// 7F System Realtime SysEx
@@ -320,16 +331,17 @@ void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsign
320331
// Process MIDI for each active Tone Generator
321332
bool bSystemCCHandled = false;
322333
bool bSystemCCChecked = false;
323-
for (unsigned nTG = 0; nTG < m_pConfig->GetToneGenerators() && !bSystemCCHandled; nTG++)
324-
{
325-
if (ucStatus == MIDI_SYSTEM_EXCLUSIVE_BEGIN)
326-
{
327-
// MIDI SYSEX per MIDI channel
328-
uint8_t ucSysExChannel = (pMessage[2] & 0x0F);
329-
if (m_ChannelMap[nTG] == ucSysExChannel || m_ChannelMap[nTG] == OmniMode)
330-
{
334+
if (ucStatus == MIDI_SYSTEM_EXCLUSIVE_BEGIN) {
335+
uint8_t ucSysExChannel = (pMessage[2] & 0x0F);
336+
for (unsigned nTG = 0; nTG < m_pConfig->GetToneGenerators(); nTG++) {
337+
if (m_ChannelMap[nTG] == ucSysExChannel || m_ChannelMap[nTG] == OmniMode) {
331338
LOGNOTE("MIDI-SYSEX: channel: %u, len: %u, TG: %u",m_ChannelMap[nTG],nLength,nTG);
332339

340+
HandleSystemExclusive(pMessage, nLength, nCable, nTG);
341+
if (nLength == 5) {
342+
break; // Send dump request only to the first TG that matches the MIDI channel requested via the SysEx message device ID
343+
}
344+
333345
// Check for TX216/TX816 style performance sysex messages
334346

335347
if (pMessage[3] == 0x04)
@@ -339,21 +351,12 @@ void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsign
339351
uint8_t par = pMessage[4];
340352
uint8_t val = pMessage[5];
341353

342-
// For parameter 1 (Set MIDI Channel), only process for the TG with the number in pMessage[2]
343-
if (par == 1) {
344-
if (nTG != mTG) continue;
345-
} else {
346-
// For all other parameters, process for all TGs listening on the MIDI channel mTG or OmniMode
347-
if (!(m_ChannelMap[nTG] == mTG || m_ChannelMap[nTG] == OmniMode)) continue;
348-
}
354+
if (!(m_ChannelMap[nTG] == mTG || m_ChannelMap[nTG] == OmniMode)) continue;
355+
349356
LOGNOTE("MIDI-SYSEX: Assuming TX216/TX816 style performance sysex message because 4th byte is 0x04");
350357

351358
switch (par)
352359
{
353-
case 1: // MIDI Channel
354-
LOGNOTE("MIDI-SYSEX: Set TG%d to MIDI Channel %d", mTG, val & 0x0F);
355-
m_pSynthesizer->SetMIDIChannel(val & 0x0F, mTG);
356-
break;
357360
case 2: // Poly/Mono
358361
LOGNOTE("MIDI-SYSEX: Set Poly/Mono %d to %d", nTG, val & 0x0F);
359362
m_pSynthesizer->setMonoMode(val ? true : false, nTG);
@@ -452,11 +455,10 @@ void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsign
452455
}
453456
}
454457
}
455-
else
456-
{
458+
} else {
459+
for (unsigned nTG = 0; nTG < m_pConfig->GetToneGenerators() && !bSystemCCHandled; nTG++) {
457460
if ( m_ChannelMap[nTG] == ucChannel
458-
|| m_ChannelMap[nTG] == OmniMode)
459-
{
461+
|| m_ChannelMap[nTG] == OmniMode) {
460462
switch (ucType)
461463
{
462464
case MIDI_NOTE_ON:
@@ -733,6 +735,25 @@ bool CMIDIDevice::HandleMIDISystemCC(const u8 ucCC, const u8 ucCCval)
733735

734736
void CMIDIDevice::HandleSystemExclusive(const uint8_t* pMessage, const size_t nLength, const unsigned nCable, const uint8_t nTG)
735737
{
738+
739+
LOGDBG("HandleSystemExclusive: TG %d, length %zu", nTG, nLength);
740+
741+
// Check if it is a dump request; these have the format F0 43 2n ff F7
742+
// with n = the MIDI channel and ff = 00 for voice or 09 for bank
743+
// It was confirmed that on the TX816, the device number is interpreted as the MIDI channel;
744+
if (nLength == 5 && pMessage[3] == 0x00)
745+
{
746+
LOGDBG("SysEx voice dump request: device %d", nTG);
747+
SendSystemExclusiveVoice(nTG, m_DeviceName, nCable, nTG);
748+
return;
749+
}
750+
else if (nLength == 5 && pMessage[3] == 0x09)
751+
{
752+
LOGDBG("SysEx bank dump request: device %d", nTG);
753+
LOGDBG("Still to be implemented");
754+
return;
755+
}
756+
736757
int16_t sysex_return;
737758

738759
sysex_return = m_pSynthesizer->checkSystemExclusive(pMessage, nLength, nTG);
@@ -802,25 +823,22 @@ void CMIDIDevice::HandleSystemExclusive(const uint8_t* pMessage, const size_t nL
802823
else if(sysex_return >= 500 && sysex_return < 600)
803824
{
804825
LOGDBG("SysEx send voice %u request",sysex_return-500);
805-
SendSystemExclusiveVoice(sysex_return-500, nCable, nTG);
826+
SendSystemExclusiveVoice(sysex_return-500, m_DeviceName, nCable, nTG);
806827
}
807828
break;
808829
}
809830
}
810831

811-
void CMIDIDevice::SendSystemExclusiveVoice(uint8_t nVoice, const unsigned nCable, uint8_t nTG)
832+
void CMIDIDevice::SendSystemExclusiveVoice(uint8_t nVoice, const std::string& deviceName, unsigned nCable, uint8_t nTG)
812833
{
813-
uint8_t voicedump[163];
814-
815-
// Get voice sysex dump from TG
816-
m_pSynthesizer->getSysExVoiceDump(voicedump, nTG);
817-
818-
TDeviceMap::const_iterator Iterator;
819-
820-
// send voice dump to all MIDI interfaces
821-
for(Iterator = s_DeviceMap.begin(); Iterator != s_DeviceMap.end(); ++Iterator)
822-
{
823-
Iterator->second->Send (voicedump, sizeof(voicedump)*sizeof(uint8_t));
824-
// LOGDBG("Send SYSEX voice dump %u to \"%s\"",nVoice,Iterator->first.c_str());
825-
}
834+
// Example: F0 43 20 00 F7
835+
uint8_t voicedump[163];
836+
m_pSynthesizer->getSysExVoiceDump(voicedump, nTG);
837+
TDeviceMap::const_iterator Iterator = s_DeviceMap.find(deviceName);
838+
if (Iterator != s_DeviceMap.end()) {
839+
Iterator->second->Send(voicedump, sizeof(voicedump), nCable);
840+
LOGDBG("Send SYSEX voice dump %u to \"%s\"", nVoice, deviceName.c_str());
841+
} else {
842+
LOGWARN("No device found in s_DeviceMap for name: %s", deviceName.c_str());
843+
}
826844
}

src/mididevice.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,9 @@ class CMIDIDevice
5454
u8 GetChannel (unsigned nTG) const;
5555

5656
virtual void Send (const u8 *pMessage, size_t nLength, unsigned nCable = 0) {}
57-
virtual void SendSystemExclusiveVoice(uint8_t nVoice, const unsigned nCable, uint8_t nTG);
57+
// Change signature to specify device name
58+
void SendSystemExclusiveVoice(uint8_t nVoice, const std::string& deviceName, unsigned nCable, uint8_t nTG);
59+
const std::string& GetDeviceName() const { return m_DeviceName; }
5860

5961
protected:
6062
void MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsigned nCable = 0);

src/minidexed.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -666,7 +666,7 @@ void CMiniDexed::ProgramChange (unsigned nProgram, unsigned nTG)
666666
// MIDI channel configured for this TG
667667
if (m_nMIDIChannel[nTG] < CMIDIDevice::Channels)
668668
{
669-
m_SerialMIDI.SendSystemExclusiveVoice(nProgram,0,nTG);
669+
m_SerialMIDI.SendSystemExclusiveVoice(nProgram, m_SerialMIDI.GetDeviceName(), 0, nTG);
670670
}
671671
}
672672

src/net/applemidi.cpp

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@
3131
#include "applemidi.h"
3232
#include "byteorder.h"
3333

34+
#define MAX_DX7_SYSEX_LENGTH 4104
35+
#define MAX_MIDI_MESSAGE MAX_DX7_SYSEX_LENGTH
36+
3437
// #define APPLEMIDI_DEBUG
3538

3639
LOGMODULE("applemidi");
@@ -876,4 +879,46 @@ bool CAppleMIDIParticipant::SendFeedbackPacket()
876879
#endif
877880

878881
return SendPacket(m_pControlSocket, &m_InitiatorIPAddress, m_nInitiatorControlPort, &FeedbackPacket, sizeof(FeedbackPacket));
882+
}
883+
884+
bool CAppleMIDIParticipant::SendMIDIToHost(const u8* pData, size_t nSize)
885+
{
886+
if (m_State != TState::Connected)
887+
return false;
888+
889+
// Build RTP-MIDI packet
890+
TRTPMIDI packet;
891+
packet.nFlags = htons((RTPMIDIVersion << 14) | RTPMIDIPayloadType);
892+
packet.nSequence = htons(++m_nSequence);
893+
packet.nTimestamp = htonl(0); // No timestamping for now
894+
packet.nSSRC = htonl(m_nSSRC);
895+
896+
// RTP-MIDI command section: header + MIDI data
897+
// Header: 0x80 | length (if length < 0x0F)
898+
u8 midiHeader = 0x00;
899+
size_t midiLen = nSize;
900+
if (midiLen < 0x0F) {
901+
midiHeader = midiLen & 0x0F;
902+
} else {
903+
midiHeader = 0x80 | ((midiLen >> 8) & 0x0F);
904+
}
905+
906+
u8 buffer[sizeof(TRTPMIDI) + 2 + MAX_MIDI_MESSAGE];
907+
size_t offset = 0;
908+
memcpy(buffer + offset, &packet, sizeof(TRTPMIDI));
909+
offset += sizeof(TRTPMIDI);
910+
buffer[offset++] = midiHeader;
911+
if (midiLen >= 0x0F) {
912+
buffer[offset++] = midiLen & 0xFF;
913+
}
914+
memcpy(buffer + offset, pData, midiLen);
915+
offset += midiLen;
916+
917+
if (SendPacket(m_pMIDISocket, &m_InitiatorIPAddress, m_nInitiatorMIDIPort, buffer, offset) <= 0) {
918+
LOGNOTE("Failed to send MIDI data to host");
919+
return false;
920+
}
921+
922+
LOGDBG("Successfully sent %zu bytes of MIDI data", nSize);
923+
return true;
879924
}

src/net/applemidi.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ class CAppleMIDIParticipant : protected CTask
4646

4747
virtual void Run() override;
4848

49+
public:
50+
bool SendMIDIToHost(const u8* pData, size_t nSize);
51+
4952
private:
5053
void ControlInvitationState();
5154
void MIDIInvitationState();

src/udpmididevice.cpp

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
#include <cstring>
2626
#include "udpmididevice.h"
2727
#include <assert.h>
28+
#include <circle/net/netsubsystem.h>
29+
#include <circle/net/in.h>
2830

2931
#define VIRTUALCABLE 24
3032

@@ -64,6 +66,13 @@ boolean CUDPMIDIDevice::Initialize (void)
6466
}
6567
else
6668
LOGNOTE("UDP MIDI receiver initialized");
69+
70+
// UDP MIDI send socket setup (default: broadcast 255.255.255.255:1999)
71+
CNetSubSystem* pNet = CNetSubSystem::Get();
72+
m_pUDPSendSocket = new CSocket(pNet, IPPROTO_UDP);
73+
m_UDPDestAddress.Set(0xFFFFFFFF); // Broadcast by default
74+
m_UDPDestPort = 1999;
75+
6776
return true;
6877
}
6978

@@ -87,4 +96,21 @@ void CUDPMIDIDevice::OnAppleMIDIDisconnect(const CIPAddress* pIPAddress, const c
8796
void CUDPMIDIDevice::OnUDPMIDIDataReceived(const u8* pData, size_t nSize)
8897
{
8998
MIDIMessageHandler(pData, nSize, VIRTUALCABLE);
99+
}
100+
101+
void CUDPMIDIDevice::Send(const u8 *pMessage, size_t nLength, unsigned nCable)
102+
{
103+
bool sentRTP = false;
104+
if (m_pAppleMIDIParticipant && m_pAppleMIDIParticipant->SendMIDIToHost(pMessage, nLength)) {
105+
sentRTP = true;
106+
LOGNOTE("Sent %zu bytes to RTP-MIDI host", nLength);
107+
}
108+
if (!sentRTP && m_pUDPSendSocket) {
109+
int res = m_pUDPSendSocket->SendTo(pMessage, nLength, 0, m_UDPDestAddress, m_UDPDestPort);
110+
if (res < 0) {
111+
LOGERR("Failed to send %zu bytes to UDP MIDI host", nLength);
112+
} else {
113+
LOGNOTE("Sent %zu bytes to UDP MIDI host (broadcast)", nLength);
114+
}
115+
}
90116
}

src/udpmididevice.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
#include "config.h"
3030
#include "net/applemidi.h"
3131
#include "net/udpmidi.h"
32+
#include "midi.h"
3233

3334
class CMiniDexed;
3435

@@ -43,13 +44,19 @@ class CUDPMIDIDevice : CAppleMIDIHandler, CUDPMIDIHandler, public CMIDIDevice
4344
virtual void OnAppleMIDIConnect(const CIPAddress* pIPAddress, const char* pName) override;
4445
virtual void OnAppleMIDIDisconnect(const CIPAddress* pIPAddress, const char* pName) override;
4546
virtual void OnUDPMIDIDataReceived(const u8* pData, size_t nSize) override;
47+
virtual void Send(const u8 *pMessage, size_t nLength, unsigned nCable = 0) override;
4648

4749
private:
4850
CMiniDexed *m_pSynthesizer;
4951
CConfig *m_pConfig;
5052
CBcmRandomNumberGenerator m_Random;
5153
CAppleMIDIParticipant* m_pAppleMIDIParticipant; // AppleMIDI participant instance
5254
CUDPMIDIReceiver* m_pUDPMIDIReceiver;
55+
CSocket* m_pUDPSendSocket = nullptr;
56+
CIPAddress m_UDPDestAddress;
57+
unsigned m_UDPDestPort = 1999;
58+
CIPAddress m_LastUDPSenderAddress;
59+
unsigned m_LastUDPSenderPort = 0;
5360
};
5461

5562
#endif

0 commit comments

Comments
 (0)