initial network support

pull/744/head
Ömer Şiar Baysal 2 months ago
parent 753c205991
commit 07fead823c
  1. 6
      src/Makefile
  2. 8
      src/Rules.mk
  3. 15
      src/config.cpp
  4. 8
      src/config.h
  5. 11
      src/kernel.cpp
  6. 2
      src/kernel.h
  7. 141
      src/minidexed.cpp
  8. 21
      src/minidexed.h
  9. 874
      src/net/applemidi.cpp
  10. 111
      src/net/applemidi.h
  11. 42
      src/net/byteorder.h
  12. 89
      src/net/udpmidi.cpp
  13. 57
      src/net/udpmidi.h
  14. 80
      src/rtpmididevice.cpp
  15. 69
      src/rtpmididevice.h

@ -5,11 +5,13 @@
CIRCLE_STDLIB_DIR = ../circle-stdlib CIRCLE_STDLIB_DIR = ../circle-stdlib
SYNTH_DEXED_DIR = ../Synth_Dexed/src SYNTH_DEXED_DIR = ../Synth_Dexed/src
CMSIS_DIR = ../CMSIS_5/CMSIS CMSIS_DIR = ../CMSIS_5/CMSIS
NET_DIR = ./net
OBJS = main.o kernel.o minidexed.o config.o userinterface.o uimenu.o \ OBJS = main.o kernel.o minidexed.o config.o userinterface.o uimenu.o \
mididevice.o midikeyboard.o serialmididevice.o pckeyboard.o \ mididevice.o rtpmididevice.o midikeyboard.o serialmididevice.o pckeyboard.o \
sysexfileloader.o performanceconfig.o perftimer.o \ sysexfileloader.o performanceconfig.o perftimer.o \
effect_compressor.o effect_platervbstereo.o uibuttons.o midipin.o effect_compressor.o effect_platervbstereo.o uibuttons.o midipin.o \
net/applemidi.o net/udpmidi.o
OPTIMIZE = -O3 OPTIMIZE = -O3

@ -11,7 +11,8 @@ include $(CIRCLEHOME)/Rules.mk
INCLUDE += \ INCLUDE += \
-I $(CIRCLE_STDLIB_DIR)/include \ -I $(CIRCLE_STDLIB_DIR)/include \
-I $(NEWLIBDIR)/include -I $(NEWLIBDIR)/include \
-I $(NET_DIR)
LIBS += \ LIBS += \
$(NEWLIBDIR)/lib/libm.a \ $(NEWLIBDIR)/lib/libm.a \
@ -28,6 +29,9 @@ LIBS += \
$(CIRCLEHOME)/addon/fatfs/libfatfs.a \ $(CIRCLEHOME)/addon/fatfs/libfatfs.a \
$(CIRCLEHOME)/lib/fs/libfs.a \ $(CIRCLEHOME)/lib/fs/libfs.a \
$(CIRCLEHOME)/lib/sched/libsched.a \ $(CIRCLEHOME)/lib/sched/libsched.a \
$(CIRCLEHOME)/lib/libcircle.a $(CIRCLEHOME)/lib/libcircle.a \
$(CIRCLEHOME)/addon/wlan/hostap/wpa_supplicant/libwpa_supplicant.a \
$(CIRCLEHOME)/addon/wlan/libwlan.a \
$(CIRCLEHOME)/lib/net/libnet.a
-include $(DEPS) -include $(DEPS)

@ -152,6 +152,10 @@ void CConfig::Load (void)
m_bProfileEnabled = m_Properties.GetNumber ("ProfileEnabled", 0) != 0; m_bProfileEnabled = m_Properties.GetNumber ("ProfileEnabled", 0) != 0;
m_bPerformanceSelectToLoad = m_Properties.GetNumber ("PerformanceSelectToLoad", 1) != 0; m_bPerformanceSelectToLoad = m_Properties.GetNumber ("PerformanceSelectToLoad", 1) != 0;
m_bPerformanceSelectChannel = m_Properties.GetNumber ("PerformanceSelectChannel", 0); m_bPerformanceSelectChannel = m_Properties.GetNumber ("PerformanceSelectChannel", 0);
// Network
m_bNetworkEnabled = m_Properties.GetNumber ("NetworkEnabled", 0) != 0;
m_NetworkType = m_Properties.GetString ("NetworkType", "");
} }
bool CConfig::GetUSBGadgetMode (void) const bool CConfig::GetUSBGadgetMode (void) const
@ -503,3 +507,14 @@ unsigned CConfig::GetPerformanceSelectChannel (void) const
{ {
return m_bPerformanceSelectChannel; return m_bPerformanceSelectChannel;
} }
// Network
bool CConfig::GetNetworkEnabled (void) const
{
return m_bNetworkEnabled;
}
const char *CConfig::GetNetworkType (void) const
{
return m_NetworkType.c_str();
}

@ -168,6 +168,10 @@ public:
bool GetPerformanceSelectToLoad (void) const; bool GetPerformanceSelectToLoad (void) const;
unsigned GetPerformanceSelectChannel (void) const; unsigned GetPerformanceSelectChannel (void) const;
// Network
bool GetNetworkEnabled (void) const;
const char *GetNetworkType (void) const;
private: private:
CPropertiesFatFsFile m_Properties; CPropertiesFatFsFile m_Properties;
@ -252,6 +256,10 @@ private:
bool m_bProfileEnabled; bool m_bProfileEnabled;
bool m_bPerformanceSelectToLoad; bool m_bPerformanceSelectToLoad;
unsigned m_bPerformanceSelectChannel; unsigned m_bPerformanceSelectChannel;
// Network
bool m_bNetworkEnabled;
std::string m_NetworkType;
}; };
#endif #endif

@ -24,12 +24,17 @@
#include <circle/usb/usbhcidevice.h> #include <circle/usb/usbhcidevice.h>
#include "usbminidexedmidigadget.h" #include "usbminidexedmidigadget.h"
#define NET_DEVICE_TYPE NetDeviceTypeWLAN // or: NetDeviceTypeWLAN
LOGMODULE ("kernel"); LOGMODULE ("kernel");
CKernel *CKernel::s_pThis = 0; CKernel *CKernel::s_pThis = 0;
CKernel::CKernel (void) CKernel::CKernel (void)
: CStdlibAppStdio ("minidexed"), :
//CStdlibAppStdio ("minidexed"),
CStdlibAppNetwork ("minidexed", CSTDLIBAPP_DEFAULT_PARTITION,
0, 0, 0, 0, NET_DEVICE_TYPE),
m_Config (&mFileSystem), m_Config (&mFileSystem),
m_GPIOManager (&mInterrupt), m_GPIOManager (&mInterrupt),
m_I2CMaster (CMachineInfo::Get ()->GetDevice (DeviceI2CMaster), TRUE), m_I2CMaster (CMachineInfo::Get ()->GetDevice (DeviceI2CMaster), TRUE),
@ -47,7 +52,7 @@ CKernel::~CKernel(void)
bool CKernel::Initialize (void) bool CKernel::Initialize (void)
{ {
if (!CStdlibAppStdio::Initialize ()) if (!CStdlibAppNetwork::Initialize ())
{ {
return FALSE; return FALSE;
} }
@ -109,7 +114,7 @@ CStdlibApp::TShutdownMode CKernel::Run (void)
mScreen.Update (); mScreen.Update ();
} }
m_CPUThrottle.Update (); m_CPUThrottle.Update ();
} }
return ShutdownHalt; return ShutdownHalt;

@ -35,7 +35,7 @@ enum TShutdownMode
ShutdownReboot ShutdownReboot
}; };
class CKernel : public CStdlibAppStdio class CKernel : public CStdlibAppNetwork
{ {
public: public:
CKernel (void); CKernel (void);

@ -27,6 +27,19 @@
#include <string.h> #include <string.h>
#include <stdio.h> #include <stdio.h>
#include <assert.h> #include <assert.h>
#include <circle/net/netsubsystem.h>
#include <circle/sched/scheduler.h>
#include "circle_stdlib_app.h"
//#include "mididevice.h"
/*
#define DRIVE "SD:"
#define FIRMWARE_PATH DRIVE "/firmware/" // firmware files must be provided here
#define CONFIG_FILE DRIVE "/wpa_supplicant.conf"
const char WLANFirmwarePath[] = "SD:firmware/";
const char WLANConfigFile[] = "SD:wpa_supplicant.conf";
*/
LOGMODULE ("minidexed"); LOGMODULE ("minidexed");
@ -54,7 +67,17 @@ CMiniDexed::CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt,
m_bSavePerformanceNewFile (false), m_bSavePerformanceNewFile (false),
m_bSetNewPerformance (false), m_bSetNewPerformance (false),
m_bDeletePerformance (false), m_bDeletePerformance (false),
m_bLoadPerformanceBusy(false) m_bLoadPerformanceBusy(false),
/*
m_pNet(nullptr),
m_pNetDevice(nullptr),
m_WLAN(WLANFirmwarePath),
m_WPASupplicant(WLANConfigFile),
m_bNetworkReady(false),
*/
//CNetSubSystem* const pNet = CNetSubSystem::Get();
m_bNetworkReady(false),
m_RTPMIDI (this, pConfig, &m_UI)
{ {
assert (m_pConfig); assert (m_pConfig);
@ -267,7 +290,14 @@ bool CMiniDexed::Initialize (void)
return false; return false;
} }
#endif #endif
//InitNetwork();
UpdateNetwork();
//CMIDIDevice->InitializeRTP();
if (m_RTPMIDI.Initialize ())
{
LOGNOTE ("RTP MIDI interface enabled");
}
return true; return true;
} }
@ -325,6 +355,7 @@ void CMiniDexed::Process (bool bPlugAndPlayUpdated)
{ {
m_GetChunkTimer.Dump (); m_GetChunkTimer.Dump ();
} }
} }
#ifdef ARM_ALLOW_MULTI_CORE #ifdef ARM_ALLOW_MULTI_CORE
@ -1802,3 +1833,111 @@ unsigned CMiniDexed::getModController (unsigned controller, unsigned parameter,
} }
} }
void CMiniDexed::UpdateNetwork()
/*{
CNetSubSystem* const pNet = CNetSubSystem::Get();
if (!m_bNetworkReady){
m_bNetworkReady = true;
CString IPString;
pNet->GetConfig()->GetIPAddress()->Format(&IPString);
LOGNOTE("Network up and running at: %s", static_cast<const char *>(IPString));
}
if (!pNet)
return;
}*/
{
CNetSubSystem* const pNet = CNetSubSystem::Get();
if (!pNet)
return;
bool bNetIsRunning = pNet->IsRunning();
//bNetIsRunning &= m_WPASupplicant.IsConnected();
if (!m_bNetworkReady)
{
m_bNetworkReady = true;
CString IPString;
pNet->GetConfig()->GetIPAddress()->Format(&IPString);
LOGNOTE("Network up and running at: %s", static_cast<const char *>(IPString));
}
else if (m_bNetworkReady && !bNetIsRunning)
{
m_bNetworkReady = false;
LOGNOTE("Network disconnected.");
}
}
/*
void CMiniDexed::UpdateNetwork()
{
if (!m_pNet)
return;
bool bNetIsRunning = m_pNet->IsRunning();
if (strcmp(m_pConfig->GetNetworkType(), "ethernet") == 0)
bNetIsRunning &= m_pNetDevice->IsLinkUp();
else if (strcmp(m_pConfig->GetNetworkType(), "wifi") == 0)
bNetIsRunning &= m_WPASupplicant.IsConnected();
if (!m_bNetworkReady && bNetIsRunning)
{
m_bNetworkReady = true;
CString IPString;
m_pNet->GetConfig()->GetIPAddress()->Format(&IPString);
LOGNOTE("Network up and running at: %s", static_cast<const char *>(IPString));
}
else if (m_bNetworkReady && !bNetIsRunning)
{
m_bNetworkReady = false;
LOGNOTE("Network disconnected.");
}
}
bool CMiniDexed::InitNetwork()
{
assert(m_pNet == nullptr);
TNetDeviceType NetDeviceType = NetDeviceTypeWLAN;
if (m_pConfig->GetNetworkEnabled () && (strcmp(m_pConfig->GetNetworkType(), "wifi") == 0))
{
LOGNOTE("Initializing Wi-Fi");
if (m_WLAN.Initialize() && m_WPASupplicant.Initialize())
{
LOGNOTE("wlan and wpasupplicant initialized");
//NetDeviceType = NetDeviceTypeWLAN;
}
else
LOGERR("Failed to initialize Wi-Fi");
}
else if (m_pConfig->GetNetworkEnabled () && (strcmp(m_pConfig->GetNetworkType(), "ethernet") == 0))
{
LOGNOTE("Initializing Ethernet");
//NetDeviceType = NetDeviceTypeEthernet;
}
LOGNOTE("creating network with wifi and dhcp");
m_pNet = new CNetSubSystem(0, 0, 0, 0, "minidexed", NetDeviceType);
if (!m_pNet->Initialize(true))
{
LOGERR("Failed to initialize network subsystem");
delete m_pNet;
m_pNet = nullptr;
}
m_pNetDevice = CNetDevice::GetNetDevice(NetDeviceType);
return m_pNet != nullptr;
}
*/

@ -43,6 +43,10 @@
#include "effect_mixer.hpp" #include "effect_mixer.hpp"
#include "effect_platervbstereo.h" #include "effect_platervbstereo.h"
#include "effect_compressor.h" #include "effect_compressor.h"
//#include <circle/net/netsubsystem.h>
//#include <wlan/bcm4343.h>
//#include <wlan/hostap/wpa_supplicant/wpasupplicant.h>
#include "rtpmididevice.h"
class CMiniDexed class CMiniDexed
#ifdef ARM_ALLOW_MULTI_CORE #ifdef ARM_ALLOW_MULTI_CORE
@ -60,7 +64,6 @@ public:
#ifdef ARM_ALLOW_MULTI_CORE #ifdef ARM_ALLOW_MULTI_CORE
void Run (unsigned nCore); void Run (unsigned nCore);
#endif #endif
CSysExFileLoader *GetSysExFileLoader (void); CSysExFileLoader *GetSysExFileLoader (void);
void BankSelect (unsigned nBank, unsigned nTG); void BankSelect (unsigned nBank, unsigned nTG);
@ -211,12 +214,15 @@ public:
bool DoSavePerformance (void); bool DoSavePerformance (void);
void setMasterVolume (float32_t vol); void setMasterVolume (float32_t vol);
//bool InitNetwork();
void UpdateNetwork();
private: private:
int16_t ApplyNoteLimits (int16_t pitch, unsigned nTG); // returns < 0 to ignore note int16_t ApplyNoteLimits (int16_t pitch, unsigned nTG); // returns < 0 to ignore note
uint8_t m_uchOPMask[CConfig::ToneGenerators]; uint8_t m_uchOPMask[CConfig::ToneGenerators];
void LoadPerformanceParameters(void); void LoadPerformanceParameters(void);
void ProcessSound (void); void ProcessSound (void);
const char* GetNetworkDeviceShortName() const;
#ifdef ARM_ALLOW_MULTI_CORE #ifdef ARM_ALLOW_MULTI_CORE
enum TCoreStatus enum TCoreStatus
@ -309,6 +315,19 @@ private:
unsigned m_nDeletePerformanceID; unsigned m_nDeletePerformanceID;
bool m_bLoadPerformanceBusy; bool m_bLoadPerformanceBusy;
bool m_bSaveAsDeault; bool m_bSaveAsDeault;
bool m_bNetworkReady;
//CWPASupplicant m_WPASupplicant;
// Networking
//CNetSubSystem &mNet;
/*
CNetSubSystem* m_pNet;
CNetDevice* m_pNetDevice;
CBcm4343Device m_WLAN;
CWPASupplicant m_WPASupplicant;
bool m_bNetworkReady;
CBcmRandomNumberGenerator m_Random;
*/
CRTPMIDIDevice m_RTPMIDI;
}; };
#endif #endif

@ -0,0 +1,874 @@
//
// applemidi.cpp
//
// mt32-pi - A baremetal MIDI synthesizer for Raspberry Pi
// Copyright (C) 2020-2023 Dale Whinham <daleyo@gmail.com>
//
// This file is part of mt32-pi.
//
// mt32-pi 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.
//
// mt32-pi 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
// mt32-pi. If not, see <http://www.gnu.org/licenses/>.
//
#include <circle/logger.h>
#include <circle/macros.h>
#include <circle/net/in.h>
#include <circle/net/netsubsystem.h>
#include <circle/sched/scheduler.h>
#include <circle/timer.h>
#include <circle/util.h>
#include "applemidi.h"
#include "byteorder.h"
// #define APPLEMIDI_DEBUG
LOGMODULE("applemidi");
constexpr u16 ControlPort = 5004;
constexpr u16 MIDIPort = ControlPort + 1;
constexpr u16 AppleMIDISignature = 0xFFFF;
constexpr u8 AppleMIDIVersion = 2;
constexpr u8 RTPMIDIPayloadType = 0x61;
constexpr u8 RTPMIDIVersion = 2;
// Arbitrary value
constexpr size_t MaxNameLength = 256;
// Timeout period for invitation (5 seconds in 100 microsecond units)
constexpr unsigned int InvitationTimeout = 5 * 10000;
// Timeout period for sync packets (60 seconds in 100 microsecond units)
constexpr unsigned int SyncTimeout = 60 * 10000;
// Receiver feedback packet frequency (1 second in 100 microsecond units)
constexpr unsigned int ReceiverFeedbackPeriod = 1 * 10000;
constexpr u16 CommandWord(const char Command[2]) { return Command[0] << 8 | Command[1]; }
enum TAppleMIDICommand : u16
{
Invitation = CommandWord("IN"),
InvitationAccepted = CommandWord("OK"),
InvitationRejected = CommandWord("NO"),
Sync = CommandWord("CK"),
ReceiverFeedback = CommandWord("RS"),
EndSession = CommandWord("BY"),
};
struct TAppleMIDISession
{
u16 nSignature;
u16 nCommand;
u32 nVersion;
u32 nInitiatorToken;
u32 nSSRC;
char Name[MaxNameLength];
}
PACKED;
// The Name field is optional
constexpr size_t NamelessSessionPacketSize = sizeof(TAppleMIDISession) - sizeof(TAppleMIDISession::Name);
struct TAppleMIDISync
{
u16 nSignature;
u16 nCommand;
u32 nSSRC;
u8 nCount;
u8 Padding[3];
u64 Timestamps[3];
}
PACKED;
struct TAppleMIDIReceiverFeedback
{
u16 nSignature;
u16 nCommand;
u32 nSSRC;
u32 nSequence;
}
PACKED;
struct TRTPMIDI
{
u16 nFlags;
u16 nSequence;
u32 nTimestamp;
u32 nSSRC;
}
PACKED;
u64 GetSyncClock()
{
static const u64 nStartTime = CTimer::GetClockTicks();
const u64 nMicrosSinceEpoch = CTimer::GetClockTicks();
// Units of 100 microseconds
return (nMicrosSinceEpoch - nStartTime ) / 100;
}
bool ParseInvitationPacket(const u8* pBuffer, size_t nSize, TAppleMIDISession* pOutPacket)
{
const TAppleMIDISession* const pInPacket = reinterpret_cast<const TAppleMIDISession*>(pBuffer);
if (nSize < NamelessSessionPacketSize)
return false;
const u16 nSignature = ntohs(pInPacket->nSignature);
if (nSignature != AppleMIDISignature)
return false;
const u16 nCommand = ntohs(pInPacket->nCommand);
if (nCommand != Invitation)
return false;
const u32 nVersion = ntohl(pInPacket->nVersion);
if (nVersion != AppleMIDIVersion)
return false;
pOutPacket->nSignature = nSignature;
pOutPacket->nCommand = nCommand;
pOutPacket->nVersion = nVersion;
pOutPacket->nInitiatorToken = ntohl(pInPacket->nInitiatorToken);
pOutPacket->nSSRC = ntohl(pInPacket->nSSRC);
if (nSize > NamelessSessionPacketSize)
strncpy(pOutPacket->Name, pInPacket->Name, sizeof(pOutPacket->Name));
else
strncpy(pOutPacket->Name, "<unknown>", sizeof(pOutPacket->Name));
return true;
}
bool ParseEndSessionPacket(const u8* pBuffer, size_t nSize, TAppleMIDISession* pOutPacket)
{
const TAppleMIDISession* const pInPacket = reinterpret_cast<const TAppleMIDISession*>(pBuffer);
if (nSize < NamelessSessionPacketSize)
return false;
const u16 nSignature = ntohs(pInPacket->nSignature);
if (nSignature != AppleMIDISignature)
return false;
const u16 nCommand = ntohs(pInPacket->nCommand);
if (nCommand != EndSession)
return false;
const u32 nVersion = ntohl(pInPacket->nVersion);
if (nVersion != AppleMIDIVersion)
return false;
pOutPacket->nSignature = nSignature;
pOutPacket->nCommand = nCommand;
pOutPacket->nVersion = nVersion;
pOutPacket->nInitiatorToken = ntohl(pInPacket->nInitiatorToken);
pOutPacket->nSSRC = ntohl(pInPacket->nSSRC);
return true;
}
bool ParseSyncPacket(const u8* pBuffer, size_t nSize, TAppleMIDISync* pOutPacket)
{
const TAppleMIDISync* const pInPacket = reinterpret_cast<const TAppleMIDISync*>(pBuffer);
if (nSize < sizeof(TAppleMIDISync))
return false;
const u32 nSignature = ntohs(pInPacket->nSignature);
if (nSignature != AppleMIDISignature)
return false;
const u32 nCommand = ntohs(pInPacket->nCommand);
if (nCommand != Sync)
return false;
pOutPacket->nSignature = nSignature;
pOutPacket->nCommand = nCommand;
pOutPacket->nSSRC = ntohl(pInPacket->nSSRC);
pOutPacket->nCount = pInPacket->nCount;
pOutPacket->Timestamps[0] = ntohll(pInPacket->Timestamps[0]);
pOutPacket->Timestamps[1] = ntohll(pInPacket->Timestamps[1]);
pOutPacket->Timestamps[2] = ntohll(pInPacket->Timestamps[2]);
return true;
}
u8 ParseMIDIDeltaTime(const u8* pBuffer)
{
u8 nLength = 0;
u32 nDeltaTime = 0;
while (nLength < 4)
{
nDeltaTime <<= 7;
nDeltaTime |= pBuffer[nLength] & 0x7F;
// Upper bit not set; end of timestamp
if ((pBuffer[nLength++] & (1 << 7)) == 0)
break;
}
return nLength;
}
size_t ParseSysExCommand(const u8* pBuffer, size_t nSize, CAppleMIDIHandler* pHandler)
{
size_t nBytesParsed = 1;
const u8 nHead = pBuffer[0];
u8 nTail = 0;
while (nBytesParsed < nSize && !(nTail == 0xF0 || nTail == 0xF7 || nTail == 0xF4))
nTail = pBuffer[nBytesParsed++];
size_t nReceiveLength = nBytesParsed;
// First segmented SysEx packet
if (nHead == 0xF0 && nTail == 0xF0)
{
#ifdef APPLEMIDI_DEBUG
LOGNOTE("Received segmented SysEx (first)");
#endif
--nReceiveLength;
}
// Middle segmented SysEx packet
else if (nHead == 0xF7 && nTail == 0xF0)
{
#ifdef APPLEMIDI_DEBUG
LOGNOTE("Received segmented SysEx (middle)");
#endif
++pBuffer;
nBytesParsed -= 2;
}
// Last segmented SysEx packet
else if (nHead == 0xF7 && nTail == 0xF7)
{
#ifdef APPLEMIDI_DEBUG
LOGNOTE("Received segmented SysEx (last)");
#endif
++pBuffer;
--nReceiveLength;
}
// Cancelled segmented SysEx packet
else if (nHead == 0xF7 && nTail == 0xF4)
{
#ifdef APPLEMIDI_DEBUG
LOGNOTE("Received cancelled SysEx");
#endif
nReceiveLength = 1;
}
#ifdef APPLEMIDI_DEBUG
else
{
LOGNOTE("Received complete SysEx");
}
#endif
pHandler->OnAppleMIDIDataReceived(pBuffer, nReceiveLength);
return nBytesParsed;
}
size_t ParseMIDICommand(const u8* pBuffer, size_t nSize, u8& nRunningStatus, CAppleMIDIHandler* pHandler)
{
size_t nBytesParsed = 0;
u8 nByte = pBuffer[0];
// System Real-Time message - single byte, handle immediately
// Can appear anywhere in the stream, even in between status/data bytes
if (nByte >= 0xF8)
{
// Ignore undefined System Real-Time
if (nByte != 0xF9 && nByte != 0xFD)
pHandler->OnAppleMIDIDataReceived(&nByte, 1);
return 1;
}
// Is it a status byte?
if (nByte & 0x80)
{
// Update running status if non Real-Time System status
if (nByte < 0xF0)
nRunningStatus = nByte;
else
nRunningStatus = 0;
++nBytesParsed;
}
else
{
// First byte not a status byte and no running status - invalid
if (!nRunningStatus)
return 0;
// Use running status
nByte = nRunningStatus;
}
// Channel messages
if (nByte < 0xF0)
{
// How many data bytes?
switch (nByte & 0xF0)
{
case 0x80: // Note off
case 0x90: // Note on
case 0xA0: // Polyphonic key pressure/aftertouch
case 0xB0: // Control change
case 0xE0: // Pitch bend
nBytesParsed += 2;
break;
case 0xC0: // Program change
case 0xD0: // Channel pressure/aftertouch
nBytesParsed += 1;
break;
}
// Handle command
pHandler->OnAppleMIDIDataReceived(pBuffer, nBytesParsed);
return nBytesParsed;
}
// System common commands
switch (nByte)
{
case 0xF0: // Start of System Exclusive
case 0xF7: // End of Exclusive
return ParseSysExCommand(pBuffer, nSize, pHandler);
case 0xF1: // MIDI Time Code Quarter Frame
case 0xF3: // Song Select
++nBytesParsed;
break;
case 0xF2: // Song Position Pointer
nBytesParsed += 2;
break;
}
pHandler->OnAppleMIDIDataReceived(pBuffer, nBytesParsed);
return nBytesParsed;
}
bool ParseMIDICommandSection(const u8* pBuffer, size_t nSize, CAppleMIDIHandler* pHandler)
{
// Must have at least a header byte and a single status byte
if (nSize < 2)
return false;
size_t nMIDICommandsProcessed = 0;
size_t nBytesRemaining = nSize - 1;
u8 nRunningStatus = 0;
const u8 nMIDIHeader = pBuffer[0];
const u8* pMIDICommands = pBuffer + 1;
// Lower 4 bits of the header is length
u16 nMIDICommandLength = nMIDIHeader & 0x0F;
// If B flag is set, length value is 12 bits
if (nMIDIHeader & (1 << 7))
{
nMIDICommandLength <<= 8;
nMIDICommandLength |= pMIDICommands[0];
++pMIDICommands;
--nBytesRemaining;
}
if (nMIDICommandLength > nBytesRemaining)
{
LOGERR("Invalid MIDI command length");
return false;
}
// Begin decoding the command list
while (nMIDICommandLength)
{
// If Z flag is set, first list entry is a delta time
if (nMIDICommandsProcessed || nMIDIHeader & (1 << 5))
{
const u8 nBytesParsed = ParseMIDIDeltaTime(pMIDICommands);
nMIDICommandLength -= nBytesParsed;
pMIDICommands += nBytesParsed;
}
if (nMIDICommandLength)
{
const size_t nBytesParsed = ParseMIDICommand(pMIDICommands, nMIDICommandLength, nRunningStatus, pHandler);
nMIDICommandLength -= nBytesParsed;
pMIDICommands += nBytesParsed;
++nMIDICommandsProcessed;
}
}
return true;
}
bool ParseMIDIPacket(const u8* pBuffer, size_t nSize, TRTPMIDI* pOutPacket, CAppleMIDIHandler* pHandler)
{
assert(pHandler != nullptr);
const TRTPMIDI* const pInPacket = reinterpret_cast<const TRTPMIDI*>(pBuffer);
const u16 nRTPFlags = ntohs(pInPacket->nFlags);
// Check size (RTP-MIDI header plus MIDI command section header)
if (nSize < sizeof(TRTPMIDI) + 1)
return false;
// Check version
if (((nRTPFlags >> 14) & 0x03) != RTPMIDIVersion)
return false;
// Ensure no CSRC identifiers
if (((nRTPFlags >> 8) & 0x0F) != 0)
return false;
// Check payload type
if ((nRTPFlags & 0xFF) != RTPMIDIPayloadType)
return false;
pOutPacket->nFlags = nRTPFlags;
pOutPacket->nSequence = ntohs(pInPacket->nSequence);
pOutPacket->nTimestamp = ntohl(pInPacket->nTimestamp);
pOutPacket->nSSRC = ntohl(pInPacket->nSSRC);
// RTP-MIDI variable-length header
const u8* const pMIDICommandSection = pBuffer + sizeof(TRTPMIDI);
size_t nRemaining = nSize - sizeof(TRTPMIDI);
return ParseMIDICommandSection(pMIDICommandSection, nRemaining, pHandler);
}
CAppleMIDIParticipant::CAppleMIDIParticipant(CBcmRandomNumberGenerator* pRandom, CAppleMIDIHandler* pHandler)
: CTask(TASK_STACK_SIZE, true),
m_pRandom(pRandom),
m_pControlSocket(nullptr),
m_pMIDISocket(nullptr),
m_nForeignControlPort(0),
m_nForeignMIDIPort(0),
m_nInitiatorControlPort(0),
m_nInitiatorMIDIPort(0),
m_ControlBuffer{0},
m_MIDIBuffer{0},
m_nControlResult(0),
m_nMIDIResult(0),
m_pHandler(pHandler),
m_State(TState::ControlInvitation),
m_nInitiatorToken(0),
m_nInitiatorSSRC(0),
m_nSSRC(0),
m_nLastMIDISequenceNumber(0),
m_nOffsetEstimate(0),
m_nLastSyncTime(0),
m_nSequence(0),
m_nLastFeedbackSequence(0),
m_nLastFeedbackTime(0)
{
}
CAppleMIDIParticipant::~CAppleMIDIParticipant()
{
if (m_pControlSocket)
delete m_pControlSocket;
if (m_pMIDISocket)
delete m_pMIDISocket;
}
bool CAppleMIDIParticipant::Initialize()
{
assert(m_pControlSocket == nullptr);
assert(m_pMIDISocket == nullptr);
CNetSubSystem* const pNet = CNetSubSystem::Get();
if ((m_pControlSocket = new CSocket(pNet, IPPROTO_UDP)) == nullptr)
return false;
if ((m_pMIDISocket = new CSocket(pNet, IPPROTO_UDP)) == nullptr)
return false;
if (m_pControlSocket->Bind(ControlPort) != 0)
{
LOGERR("Couldn't bind to port %d", ControlPort);
return false;
}
if (m_pMIDISocket->Bind(MIDIPort) != 0)
{
LOGERR("Couldn't bind to port %d", MIDIPort);
return false;
}
// We started as a suspended task; run now that initialization is successful
Start();
return true;
}
void CAppleMIDIParticipant::Run()
{
assert(m_pControlSocket != nullptr);
assert(m_pMIDISocket != nullptr);
CScheduler* const pScheduler = CScheduler::Get();
while (true)
{
if ((m_nControlResult = m_pControlSocket->ReceiveFrom(m_ControlBuffer, sizeof(m_ControlBuffer), MSG_DONTWAIT, &m_ForeignControlIPAddress, &m_nForeignControlPort)) < 0)
LOGERR("Control socket receive error: %d", m_nControlResult);
if ((m_nMIDIResult = m_pMIDISocket->ReceiveFrom(m_MIDIBuffer, sizeof(m_MIDIBuffer), MSG_DONTWAIT, &m_ForeignMIDIIPAddress, &m_nForeignMIDIPort)) < 0)
LOGERR("MIDI socket receive error: %d", m_nMIDIResult);
switch (m_State)
{
case TState::ControlInvitation:
ControlInvitationState();
break;
case TState::MIDIInvitation:
MIDIInvitationState();
break;
case TState::Connected:
ConnectedState();
break;
}
// Allow other tasks to run
pScheduler->Yield();
}
}
void CAppleMIDIParticipant::ControlInvitationState()
{
TAppleMIDISession SessionPacket;
if (m_nControlResult == 0)
return;
if (!ParseInvitationPacket(m_ControlBuffer, m_nControlResult, &SessionPacket))
{
LOGERR("Unexpected packet");
return;
}
#ifdef APPLEMIDI_DEBUG
LOGNOTE("<-- Control invitation");
#endif
// Store initiator details
m_InitiatorIPAddress.Set(m_ForeignControlIPAddress);
m_nInitiatorControlPort = m_nForeignControlPort;
m_nInitiatorToken = SessionPacket.nInitiatorToken;
m_nInitiatorSSRC = SessionPacket.nSSRC;
// Generate random SSRC and accept
m_nSSRC = m_pRandom->GetNumber();
if (!SendAcceptInvitationPacket(m_pControlSocket, &m_InitiatorIPAddress, m_nInitiatorControlPort))
{
LOGERR("Couldn't accept control invitation");
return;
}
m_nLastSyncTime = GetSyncClock();
m_State = TState::MIDIInvitation;
}
void CAppleMIDIParticipant::MIDIInvitationState()
{
TAppleMIDISession SessionPacket;
if (m_nControlResult > 0)
{
if (ParseInvitationPacket(m_ControlBuffer, m_nControlResult, &SessionPacket))
{
// Unexpected peer; reject invitation
if (m_ForeignControlIPAddress != m_InitiatorIPAddress || m_nForeignControlPort != m_nInitiatorControlPort)
SendRejectInvitationPacket(m_pControlSocket, &m_ForeignControlIPAddress, m_nForeignControlPort, SessionPacket.nInitiatorToken);
else
LOGERR("Unexpected packet");
}
}
if (m_nMIDIResult > 0)
{
if (!ParseInvitationPacket(m_MIDIBuffer, m_nMIDIResult, &SessionPacket))
{
LOGERR("Unexpected packet");
return;
}
// Unexpected peer; reject invitation
if (m_ForeignMIDIIPAddress != m_InitiatorIPAddress)
{
SendRejectInvitationPacket(m_pMIDISocket, &m_ForeignMIDIIPAddress, m_nForeignMIDIPort, SessionPacket.nInitiatorToken);
return;
}
#ifdef APPLEMIDI_DEBUG
LOGNOTE("<-- MIDI invitation");
#endif
m_nInitiatorMIDIPort = m_nForeignMIDIPort;
if (SendAcceptInvitationPacket(m_pMIDISocket, &m_InitiatorIPAddress, m_nInitiatorMIDIPort))
{
CString IPAddressString;
m_InitiatorIPAddress.Format(&IPAddressString);
LOGNOTE("Connection to %s (%s) established", SessionPacket.Name, static_cast<const char*>(IPAddressString));
m_nLastSyncTime = GetSyncClock();
m_State = TState::Connected;
m_pHandler->OnAppleMIDIConnect(&m_InitiatorIPAddress, SessionPacket.Name);
}
else
{
LOGERR("Couldn't accept MIDI invitation");
Reset();
}
}
// Timeout
else if ((GetSyncClock() - m_nLastSyncTime) > InvitationTimeout)
{
LOGERR("MIDI port invitation timed out");
Reset();
}
}
void CAppleMIDIParticipant::ConnectedState()
{
TAppleMIDISession SessionPacket;
TRTPMIDI MIDIPacket;
TAppleMIDISync SyncPacket;
if (m_nControlResult > 0)
{
if (ParseEndSessionPacket(m_ControlBuffer, m_nControlResult, &SessionPacket))
{
#ifdef APPLEMIDI_DEBUG
LOGNOTE("<-- End session");
#endif
if (m_ForeignControlIPAddress == m_InitiatorIPAddress &&
m_nForeignControlPort == m_nInitiatorControlPort &&
SessionPacket.nSSRC == m_nInitiatorSSRC)
{
LOGNOTE("Initiator ended session");
m_pHandler->OnAppleMIDIDisconnect(&m_InitiatorIPAddress, SessionPacket.Name);
Reset();
return;
}
}
else if (ParseInvitationPacket(m_ControlBuffer, m_nControlResult, &SessionPacket))
{
// Unexpected peer; reject invitation
if (m_ForeignControlIPAddress != m_InitiatorIPAddress || m_nForeignControlPort != m_nInitiatorControlPort)
SendRejectInvitationPacket(m_pControlSocket, &m_ForeignControlIPAddress, m_nForeignControlPort, SessionPacket.nInitiatorToken);
else
LOGERR("Unexpected packet");
}
}
if (m_nMIDIResult > 0)
{
if (m_ForeignMIDIIPAddress != m_InitiatorIPAddress || m_nForeignMIDIPort != m_nInitiatorMIDIPort)
LOGERR("Unexpected packet");
else if (ParseMIDIPacket(m_MIDIBuffer, m_nMIDIResult, &MIDIPacket, m_pHandler))
m_nSequence = MIDIPacket.nSequence;
else if (ParseSyncPacket(m_MIDIBuffer, m_nMIDIResult, &SyncPacket))
{
#ifdef APPLEMIDI_DEBUG
LOGNOTE("<-- Sync %d", SyncPacket.nCount);
#endif
if (SyncPacket.nSSRC == m_nInitiatorSSRC && (SyncPacket.nCount == 0 || SyncPacket.nCount == 2))
{
if (SyncPacket.nCount == 0)
SendSyncPacket(SyncPacket.Timestamps[0], GetSyncClock());
else if (SyncPacket.nCount == 2)
{
m_nOffsetEstimate = ((SyncPacket.Timestamps[2] + SyncPacket.Timestamps[0]) / 2) - SyncPacket.Timestamps[1];
#ifdef APPLEMIDI_DEBUG
LOGNOTE("Offset estimate: %llu", m_nOffsetEstimate);
#endif
}
m_nLastSyncTime = GetSyncClock();
}
else
{
LOGERR("Unexpected sync packet");
}
}
}
const u64 nTicks = GetSyncClock();
if ((nTicks - m_nLastFeedbackTime) > ReceiverFeedbackPeriod)
{
if (m_nSequence != m_nLastFeedbackSequence)
{
SendFeedbackPacket();
m_nLastFeedbackSequence = m_nSequence;
}
m_nLastFeedbackTime = nTicks;
}
if ((nTicks - m_nLastSyncTime) > SyncTimeout)
{
LOGERR("Initiator timed out");
Reset();
}
}
void CAppleMIDIParticipant::Reset()
{
m_State = TState::ControlInvitation;
m_nInitiatorToken = 0;
m_nInitiatorSSRC = 0;
m_nSSRC = 0;
m_nLastMIDISequenceNumber = 0;
m_nOffsetEstimate = 0;
m_nLastSyncTime = 0;
m_nSequence = 0;
m_nLastFeedbackSequence = 0;
m_nLastFeedbackTime = 0;
}
bool CAppleMIDIParticipant::SendPacket(CSocket* pSocket, CIPAddress* pIPAddress, u16 nPort, const void* pData, size_t nSize)
{
const int nResult = pSocket->SendTo(pData, nSize, MSG_DONTWAIT, *pIPAddress, nPort);
if (nResult < 0)
{
LOGERR("Send failure, error code: %d", nResult);
return false;
}
if (static_cast<size_t>(nResult) != nSize)
{
LOGERR("Send failure, only %d/%d bytes sent", nResult, nSize);
return false;
}
#ifdef APPLEMIDI_DEBUG
LOGNOTE("Sent %d bytes to port %d", nResult, nPort);
#endif
return true;
}
bool CAppleMIDIParticipant::SendAcceptInvitationPacket(CSocket* pSocket, CIPAddress* pIPAddress, u16 nPort)
{
TAppleMIDISession AcceptPacket =
{
htons(AppleMIDISignature),
htons(InvitationAccepted),
htonl(AppleMIDIVersion),
htonl(m_nInitiatorToken),
htonl(m_nSSRC),
{'\0'}
};
// TODO: configurable name
strncpy(AcceptPacket.Name, "mt32-pi", sizeof(AcceptPacket.Name));
#ifdef APPLEMIDI_DEBUG
LOGNOTE("--> Accept invitation");
#endif
const size_t nSendSize = NamelessSessionPacketSize + strlen(AcceptPacket.Name) + 1;
return SendPacket(pSocket, pIPAddress, nPort, &AcceptPacket, nSendSize);
}
bool CAppleMIDIParticipant::SendRejectInvitationPacket(CSocket* pSocket, CIPAddress* pIPAddress, u16 nPort, u32 nInitiatorToken)
{
TAppleMIDISession RejectPacket =
{
htons(AppleMIDISignature),
htons(InvitationRejected),
htonl(AppleMIDIVersion),
htonl(nInitiatorToken),
htonl(m_nSSRC),
{'\0'}
};
#ifdef APPLEMIDI_DEBUG
LOGNOTE("--> Reject invitation");
#endif
// Send without name
return SendPacket(pSocket, pIPAddress, nPort, &RejectPacket, NamelessSessionPacketSize);
}
bool CAppleMIDIParticipant::SendSyncPacket(u64 nTimestamp1, u64 nTimestamp2)
{
const TAppleMIDISync SyncPacket =
{
htons(AppleMIDISignature),
htons(Sync),
htonl(m_nSSRC),
1,
{0},
{
htonll(nTimestamp1),
htonll(nTimestamp2),
0
}
};
#ifdef APPLEMIDI_DEBUG
LOGNOTE("--> Sync 1");
#endif
return SendPacket(m_pMIDISocket, &m_InitiatorIPAddress, m_nInitiatorMIDIPort, &SyncPacket, sizeof(SyncPacket));
}
bool CAppleMIDIParticipant::SendFeedbackPacket()
{
const TAppleMIDIReceiverFeedback FeedbackPacket =
{
htons(AppleMIDISignature),
htons(ReceiverFeedback),
htonl(m_nSSRC),
htonl(m_nSequence << 16)
};
#ifdef APPLEMIDI_DEBUG
LOGNOTE("--> Feedback");
#endif
return SendPacket(m_pControlSocket, &m_InitiatorIPAddress, m_nInitiatorControlPort, &FeedbackPacket, sizeof(FeedbackPacket));
}

@ -0,0 +1,111 @@
//
// applemidi.h
//
// mt32-pi - A baremetal MIDI synthesizer for Raspberry Pi
// Copyright (C) 2020-2023 Dale Whinham <daleyo@gmail.com>
//
// This file is part of mt32-pi.
//
// mt32-pi 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.
//
// mt32-pi 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
// mt32-pi. If not, see <http://www.gnu.org/licenses/>.
//
#ifndef _applemidi_h
#define _applemidi_h
#include <circle/bcmrandom.h>
#include <circle/net/ipaddress.h>
#include <circle/net/socket.h>
#include <circle/sched/task.h>
class CAppleMIDIHandler
{
public:
virtual void OnAppleMIDIDataReceived(const u8* pData, size_t nSize) = 0;
virtual void OnAppleMIDIConnect(const CIPAddress* pIPAddress, const char* pName) = 0;
virtual void OnAppleMIDIDisconnect(const CIPAddress* pIPAddress, const char* pName) = 0;
};
class CAppleMIDIParticipant : protected CTask
{
public:
CAppleMIDIParticipant(CBcmRandomNumberGenerator* pRandom, CAppleMIDIHandler* pHandler);
virtual ~CAppleMIDIParticipant() override;
bool Initialize();
virtual void Run() override;
private:
void ControlInvitationState();
void MIDIInvitationState();
void ConnectedState();
void Reset();
bool SendPacket(CSocket* pSocket, CIPAddress* pIPAddress, u16 nPort, const void* pData, size_t nSize);
bool SendAcceptInvitationPacket(CSocket* pSocket, CIPAddress* pIPAddress, u16 nPort);
bool SendRejectInvitationPacket(CSocket* pSocket, CIPAddress* pIPAddress, u16 nPort, u32 nInitiatorToken);
bool SendSyncPacket(u64 nTimestamp1, u64 nTimestamp2);
bool SendFeedbackPacket();
CBcmRandomNumberGenerator* m_pRandom;
// UDP sockets
CSocket* m_pControlSocket;
CSocket* m_pMIDISocket;
// Foreign peers
CIPAddress m_ForeignControlIPAddress;
CIPAddress m_ForeignMIDIIPAddress;
u16 m_nForeignControlPort;
u16 m_nForeignMIDIPort;
// Connected peer
CIPAddress m_InitiatorIPAddress;
u16 m_nInitiatorControlPort;
u16 m_nInitiatorMIDIPort;
// Socket receive buffers
u8 m_ControlBuffer[FRAME_BUFFER_SIZE];
u8 m_MIDIBuffer[FRAME_BUFFER_SIZE];
int m_nControlResult;
int m_nMIDIResult;
// Callback handler
CAppleMIDIHandler* m_pHandler;
// Participant state machine
enum class TState
{
ControlInvitation,
MIDIInvitation,
Connected
};
TState m_State;
u32 m_nInitiatorToken = 0;
u32 m_nInitiatorSSRC = 0;
u32 m_nSSRC = 0;
u32 m_nLastMIDISequenceNumber = 0;
u64 m_nOffsetEstimate = 0;
u64 m_nLastSyncTime = 0;
u16 m_nSequence = 0;
u16 m_nLastFeedbackSequence = 0;
u64 m_nLastFeedbackTime = 0;
};
#endif

@ -0,0 +1,42 @@
//
// byteorder.h
//
// mt32-pi - A baremetal MIDI synthesizer for Raspberry Pi
// Copyright (C) 2020-2023 Dale Whinham <daleyo@gmail.com>
//
// This file is part of mt32-pi.
//
// mt32-pi 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.
//
// mt32-pi 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
// mt32-pi. If not, see <http://www.gnu.org/licenses/>.
//
#ifndef _byteorder_h
#define _byteorder_h
#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
#define htons(VALUE) (VALUE)
#define htonl(VALUE) (VALUE)
#define htonll(VALUE) (VALUE)
#define ntohs(VALUE) (VALUE)
#define ntohl(VALUE) (VALUE)
#define ntohll(VALUE) (VALUE)
#else
#define htons(VALUE) __builtin_bswap16(VALUE)
#define htonl(VALUE) __builtin_bswap32(VALUE)
#define htonll(VALUE) __builtin_bswap64(VALUE)
#define ntohs(VALUE) __builtin_bswap16(VALUE)
#define ntohl(VALUE) __builtin_bswap32(VALUE)
#define ntohll(VALUE) __builtin_bswap64(VALUE)
#endif
#endif

@ -0,0 +1,89 @@
//
// udpmidi.cpp
//
// mt32-pi - A baremetal MIDI synthesizer for Raspberry Pi
// Copyright (C) 2020-2023 Dale Whinham <daleyo@gmail.com>
//
// This file is part of mt32-pi.
//
// mt32-pi 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.
//
// mt32-pi 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
// mt32-pi. If not, see <http://www.gnu.org/licenses/>.
//
#include <circle/logger.h>
#include <circle/net/in.h>
#include <circle/net/netsubsystem.h>
#include <circle/sched/scheduler.h>
#include "udpmidi.h"
LOGMODULE("udpmidi");
constexpr u16 MIDIPort = 1999;
CUDPMIDIReceiver::CUDPMIDIReceiver(CUDPMIDIHandler* pHandler)
: CTask(TASK_STACK_SIZE, true),
m_pMIDISocket(nullptr),
m_MIDIBuffer{0},
m_pHandler(pHandler)
{
}
CUDPMIDIReceiver::~CUDPMIDIReceiver()
{
if (m_pMIDISocket)
delete m_pMIDISocket;
}
bool CUDPMIDIReceiver::Initialize()
{
assert(m_pMIDISocket == nullptr);
CNetSubSystem* const pNet = CNetSubSystem::Get();
if ((m_pMIDISocket = new CSocket(pNet, IPPROTO_UDP)) == nullptr)
return false;
if (m_pMIDISocket->Bind(MIDIPort) != 0)
{
LOGERR("Couldn't bind to port %d", MIDIPort);
return false;
}
// We started as a suspended task; run now that initialization is successful
Start();
return true;
}
void CUDPMIDIReceiver::Run()
{
assert(m_pHandler != nullptr);
assert(m_pMIDISocket != nullptr);
CScheduler* const pScheduler = CScheduler::Get();
while (true)
{
// Blocking call
const int nMIDIResult = m_pMIDISocket->Receive(m_MIDIBuffer, sizeof(m_MIDIBuffer), 0);
if (nMIDIResult < 0)
LOGERR("MIDI socket receive error: %d", nMIDIResult);
else if (nMIDIResult > 0)
m_pHandler->OnUDPMIDIDataReceived(m_MIDIBuffer, nMIDIResult);
// Allow other tasks to run
pScheduler->Yield();
}
}

@ -0,0 +1,57 @@
//
// udpmidi.h
//
// mt32-pi - A baremetal MIDI synthesizer for Raspberry Pi
// Copyright (C) 2020-2023 Dale Whinham <daleyo@gmail.com>
//
// This file is part of mt32-pi.
//
// mt32-pi 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.
//
// mt32-pi 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
// mt32-pi. If not, see <http://www.gnu.org/licenses/>.
//
#ifndef _udpmidi_h
#define _udpmidi_h
#include <circle/net/ipaddress.h>
#include <circle/net/socket.h>
#include <circle/sched/task.h>
class CUDPMIDIHandler
{
public:
virtual void OnUDPMIDIDataReceived(const u8* pData, size_t nSize) = 0;
};
class CUDPMIDIReceiver : protected CTask
{
public:
CUDPMIDIReceiver(CUDPMIDIHandler* pHandler);
virtual ~CUDPMIDIReceiver() override;
bool Initialize();
virtual void Run() override;
private:
// UDP sockets
CSocket* m_pMIDISocket;
// Socket receive buffer
u8 m_MIDIBuffer[FRAME_BUFFER_SIZE];
// Callback handler
CUDPMIDIHandler* m_pHandler;
};
#endif

@ -0,0 +1,80 @@
//
// serialmididevice.cpp
//
// MiniDexed - Dexed FM synthesizer for bare metal Raspberry Pi
// Copyright (C) 2022 The MiniDexed Team
//
// Original author of this class:
// R. Stange <rsta2@o2online.de>
//
// 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 <http://www.gnu.org/licenses/>.
//
#include <circle/logger.h>
#include <cstring>
#include "rtpmididevice.h"
#include <assert.h>
LOGMODULE("rtpmididevice");
CRTPMIDIDevice::CRTPMIDIDevice (CMiniDexed *pSynthesizer,
CConfig *pConfig, CUserInterface *pUI)
: CMIDIDevice (pSynthesizer, pConfig, pUI),
m_pConfig (pConfig)
//m_Serial (pInterrupt, TRUE),
//m_nSerialState (0),
//m_nSysEx (0),
//m_SendBuffer (&m_Serial)
{
AddDevice ("rtpdummy");
}
CRTPMIDIDevice::~CRTPMIDIDevice (void)
{
//m_nSerialState = 255;
}
boolean CRTPMIDIDevice::Initialize (void)
{
m_pAppleMIDIParticipant = new CAppleMIDIParticipant(&m_Random, this);
if (!m_pAppleMIDIParticipant->Initialize())
{
LOGERR("Failed to init RTP listener");
return false; //continue without rtp midi
}
else
LOGNOTE("RTP Listener initialized");
return true;
}
// Methods to handle MIDI events
void CRTPMIDIDevice::OnAppleMIDIDataReceived(const u8* pData, size_t nSize)
{
LOGNOTE("Recieved RTP MIDI Data");
printf ("MIDI-RTP: %02X %02X\n",
(unsigned) pData[0], (unsigned) pData[1]);
MIDIMessageHandler(pData, nSize);
}
void CRTPMIDIDevice::OnAppleMIDIConnect(const CIPAddress* pIPAddress, const char* pName)
{
LOGNOTE("RTP Device connected");
}
void CRTPMIDIDevice::OnAppleMIDIDisconnect(const CIPAddress* pIPAddress, const char* pName)
{
// RemoveRTPDevice
}

@ -0,0 +1,69 @@
//
// .h
//
// MiniDexed - Dexed FM synthesizer for bare metal Raspberry Pi
// Copyright (C) 2022 The MiniDexed Team
//
// Original author of this class:
// R. Stange <rsta2@o2online.de>
//
// 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 <http://www.gnu.org/licenses/>.
//
#ifndef _rtpmididevice_h
#define _rtpmididevice_h
#include "mididevice.h"
#include "config.h"
#include "net/applemidi.h"
#include <circle/interrupt.h>
#include <circle/serial.h>
#include <circle/writebuffer.h>
#include <circle/types.h>
class CMiniDexed;
class CRTPMIDIDevice : CAppleMIDIHandler, CMIDIDevice
{
public:
CRTPMIDIDevice (CMiniDexed *pSynthesizer, CConfig *pConfig, CUserInterface *pUI);
~CRTPMIDIDevice (void);
boolean Initialize (void);
virtual void OnAppleMIDIDataReceived(const u8* pData, size_t nSize) override;
virtual void OnAppleMIDIConnect(const CIPAddress* pIPAddress, const char* pName) override;
virtual void OnAppleMIDIDisconnect(const CIPAddress* pIPAddress, const char* pName) override;
//void OnAppleMIDIDataReceived(const u8* pData, size_t nSize);
//void OnAppleMIDIConnect(const CIPAddress* pIPAddress, const char* pName);
//void OnAppleMIDIDisconnect(const CIPAddress* pIPAddress, const char* pName);
//void Process (void);
//void Send (const u8 *pMessage, size_t nLength, unsigned nCable = 0) override;
private:
CConfig *m_pConfig;
//CSerialDevice m_Serial;
//unsigned m_nSerialState;
//unsigned m_nSysEx;
//u8 m_SerialMessage[MAX_MIDI_MESSAGE];
//CWriteBufferDevice m_SendBuffer;
CBcmRandomNumberGenerator m_Random;
//CAppleMIDIHandler* m_MIDIHandler;
CAppleMIDIParticipant* m_pAppleMIDIParticipant; // AppleMIDI participant instance
};
#endif
Loading…
Cancel
Save