Merge pull request #744 from omersiar/network

Add network support
network
probonopd 7 days ago committed by GitHub
commit 65f7d6bba4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 7
      .gitignore
  2. 3
      .gitmodules
  3. 10
      build.sh
  4. 6
      src/Makefile
  5. 10
      src/Rules.mk
  6. 1
      src/circle_stdlib_app.h
  7. 51
      src/config.cpp
  8. 21
      src/config.h
  9. 5
      src/kernel.cpp
  10. 1
      src/kernel.h
  11. 2
      src/mididevice.cpp
  12. 114
      src/minidexed.cpp
  13. 22
      src/minidexed.h
  14. 874
      src/net/applemidi.cpp
  15. 111
      src/net/applemidi.h
  16. 42
      src/net/byteorder.h
  17. 111
      src/net/ftpdaemon.cpp
  18. 47
      src/net/ftpdaemon.h
  19. 1206
      src/net/ftpworker.cpp
  20. 157
      src/net/ftpworker.h
  21. 89
      src/net/udpmidi.cpp
  22. 57
      src/net/udpmidi.h
  23. 193
      src/net/utility.h
  24. 80
      src/rtpmididevice.cpp
  25. 69
      src/rtpmididevice.h
  26. 89
      src/udpmididevice.cpp
  27. 56
      src/udpmididevice.h

7
.gitignore vendored

@ -46,6 +46,7 @@ sdcard
*.swp *.swp
*.swo *.swo
CMSIS_5/ CMSIS_5/**
Synth_Dexed/ Synth_Dexed/**
circle-stdlib/ circle-stdlib/**
.vscode/

3
.gitmodules vendored

@ -1,9 +1,12 @@
[submodule "circle-stdlib"] [submodule "circle-stdlib"]
path = circle-stdlib path = circle-stdlib
url = https://github.com/smuehlst/circle-stdlib url = https://github.com/smuehlst/circle-stdlib
ignore = all
[submodule "Synth_Dexed"] [submodule "Synth_Dexed"]
path = Synth_Dexed path = Synth_Dexed
url = https://codeberg.org/dcoredump/Synth_Dexed.git url = https://codeberg.org/dcoredump/Synth_Dexed.git
ignore = all
[submodule "CMSIS_5"] [submodule "CMSIS_5"]
path = CMSIS_5 path = CMSIS_5
url = https://github.com/ARM-software/CMSIS_5 url = https://github.com/ARM-software/CMSIS_5
ignore = all

@ -20,6 +20,11 @@ if [ "${RPI}" -gt "1" ]; then
OPTIONS="${OPTIONS} -o ARM_ALLOW_MULTI_CORE" OPTIONS="${OPTIONS} -o ARM_ALLOW_MULTI_CORE"
fi fi
# For wireless access
if [ "${RPI}" == "3" ]; then
OPTIONS="${OPTIONS} -o USE_SDHOST"
fi
# USB Vendor and Device ID for use with USB Gadget Mode # USB Vendor and Device ID for use with USB Gadget Mode
source USBID.sh source USBID.sh
if [ "${USB_VID}" ] ; then if [ "${USB_VID}" ] ; then
@ -39,6 +44,11 @@ make -j
cd libs/circle/addon/display/ cd libs/circle/addon/display/
make clean || true make clean || true
make -j make -j
cd ../wlan/
make clean || true
make -j
cd ../sensor/ cd ../sensor/
make clean || true make clean || true
make -j make -j

@ -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 udpmididevice.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/ftpdaemon.o net/ftpworker.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,11 @@ 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
EXTRACLEAN += $(NET_DIR)/*.d $(NET_DIR)/*.o
-include $(DEPS) -include $(DEPS)

@ -223,6 +223,7 @@ protected:
CEMMCDevice mEMMC; CEMMCDevice mEMMC;
FATFS mFileSystem; FATFS mFileSystem;
CConsole mConsole; CConsole mConsole;
CScheduler mScheduler;
}; };
/** /**

@ -193,6 +193,16 @@ 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_bNetworkDHCP = m_Properties.GetNumber ("NetworkDHCP", 0) != 0;
m_NetworkType = m_Properties.GetString ("NetworkType", "wifi");
m_NetworkHostname = m_Properties.GetString ("NetworkHostname", "minidexed");
m_INetworkIPAddress = m_Properties.GetIPAddress("NetworkIPAddress") != 0;
m_INetworkSubnetMask = m_Properties.GetIPAddress("NetworkSubnetMask") != 0;
m_INetworkDefaultGateway = m_Properties.GetIPAddress("NetworkDefaultGateway") != 0;
m_INetworkDNSServer = m_Properties.GetIPAddress("NetworkDNSServer") != 0;
} }
unsigned CConfig::GetToneGenerators (void) const unsigned CConfig::GetToneGenerators (void) const
@ -680,3 +690,44 @@ unsigned CConfig::GetPerformanceSelectChannel (void) const
{ {
return m_bPerformanceSelectChannel; return m_bPerformanceSelectChannel;
} }
// Network
bool CConfig::GetNetworkEnabled (void) const
{
return m_bNetworkEnabled;
}
bool CConfig::GetNetworkDHCP (void) const
{
return m_bNetworkDHCP;
}
const char *CConfig::GetNetworkType (void) const
{
return m_NetworkType.c_str();
}
const char *CConfig::GetNetworkHostname (void) const
{
return m_NetworkHostname.c_str();
}
CIPAddress CConfig::GetNetworkIPAddress (void) const
{
return m_INetworkIPAddress;
}
CIPAddress CConfig::GetNetworkSubnetMask (void) const
{
return m_INetworkSubnetMask;
}
CIPAddress CConfig::GetNetworkDefaultGateway (void) const
{
return m_INetworkDefaultGateway;
}
CIPAddress CConfig::GetNetworkDNSServer (void) const
{
return m_INetworkDNSServer;
}

@ -23,6 +23,7 @@
#ifndef _config_h #ifndef _config_h
#define _config_h #define _config_h
#include <circle/net/ipaddress.h>
#include <fatfs/ff.h> #include <fatfs/ff.h>
#include <Properties/propertiesfatfsfile.h> #include <Properties/propertiesfatfsfile.h>
#include <circle/sysconfig.h> #include <circle/sysconfig.h>
@ -232,6 +233,16 @@ 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;
bool GetNetworkDHCP (void) const;
const char *GetNetworkHostname (void) const;
CIPAddress GetNetworkIPAddress (void) const;
CIPAddress GetNetworkSubnetMask (void) const;
CIPAddress GetNetworkDefaultGateway (void) const;
CIPAddress GetNetworkDNSServer (void) const;
private: private:
CPropertiesFatFsFile m_Properties; CPropertiesFatFsFile m_Properties;
@ -339,6 +350,16 @@ private:
bool m_bProfileEnabled; bool m_bProfileEnabled;
bool m_bPerformanceSelectToLoad; bool m_bPerformanceSelectToLoad;
unsigned m_bPerformanceSelectChannel; unsigned m_bPerformanceSelectChannel;
// Network
bool m_bNetworkEnabled;
bool m_bNetworkDHCP;
std::string m_NetworkType;
std::string m_NetworkHostname;
CIPAddress m_INetworkIPAddress;
CIPAddress m_INetworkSubnetMask;
CIPAddress m_INetworkDefaultGateway;
CIPAddress m_INetworkDNSServer;
}; };
#endif #endif

@ -25,12 +25,15 @@
#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"),
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),

@ -26,6 +26,7 @@
#include <circle/i2cmaster.h> #include <circle/i2cmaster.h>
#include <circle/spimaster.h> #include <circle/spimaster.h>
#include <circle/usb/usbcontroller.h> #include <circle/usb/usbcontroller.h>
#include <circle/sched/scheduler.h>
#include "config.h" #include "config.h"
#include "minidexed.h" #include "minidexed.h"

@ -273,7 +273,6 @@ void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsign
{ {
if ((ucChannel == nPerfCh) || (nPerfCh == OmniMode)) if ((ucChannel == nPerfCh) || (nPerfCh == OmniMode))
{ {
//printf("Performance Select Channel %d\n", nPerfCh);
m_pSynthesizer->ProgramChangePerformance (pMessage[1]); m_pSynthesizer->ProgramChangePerformance (pMessage[1]);
} }
} }
@ -328,7 +327,6 @@ void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsign
{ {
break; break;
} }
m_pSynthesizer->keyup (pMessage[1], nTG); m_pSynthesizer->keyup (pMessage[1], nTG);
break; break;

@ -28,6 +28,11 @@
#include <stdio.h> #include <stdio.h>
#include <assert.h> #include <assert.h>
const char WLANFirmwarePath[] = "SD:firmware/";
const char WLANConfigFile[] = "SD:wpa_supplicant.conf";
#define FTPUSERNAME "admin"
#define FTPPASSWORD "admin"
LOGMODULE ("minidexed"); LOGMODULE ("minidexed");
CMiniDexed::CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt, CMiniDexed::CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt,
@ -51,6 +56,12 @@ CMiniDexed::CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt,
m_GetChunkTimer ("GetChunk", m_GetChunkTimer ("GetChunk",
1000000U * pConfig->GetChunkSize ()/2 / pConfig->GetSampleRate ()), 1000000U * pConfig->GetChunkSize ()/2 / pConfig->GetSampleRate ()),
m_bProfileEnabled (m_pConfig->GetProfileEnabled ()), m_bProfileEnabled (m_pConfig->GetProfileEnabled ()),
m_pNet(nullptr),
m_pNetDevice(nullptr),
m_WLAN(WLANFirmwarePath),
m_WPASupplicant(WLANConfigFile),
m_bNetworkReady(false),
m_UDPMIDI (this, pConfig, &m_UI),
m_bSavePerformance (false), m_bSavePerformance (false),
m_bSavePerformanceNewFile (false), m_bSavePerformanceNewFile (false),
m_bSetNewPerformance (false), m_bSetNewPerformance (false),
@ -344,12 +355,14 @@ bool CMiniDexed::Initialize (void)
return false; return false;
} }
#endif #endif
InitNetwork();
return true; return true;
} }
void CMiniDexed::Process (bool bPlugAndPlayUpdated) void CMiniDexed::Process (bool bPlugAndPlayUpdated)
{ {
CScheduler* const pScheduler = CScheduler::Get();
#ifndef ARM_ALLOW_MULTI_CORE #ifndef ARM_ALLOW_MULTI_CORE
ProcessSound (); ProcessSound ();
#endif #endif
@ -418,6 +431,9 @@ void CMiniDexed::Process (bool bPlugAndPlayUpdated)
{ {
m_GetChunkTimer.Dump (); m_GetChunkTimer.Dump ();
} }
UpdateNetwork();
// Allow other tasks to run
pScheduler->Yield();
} }
#ifdef ARM_ALLOW_MULTI_CORE #ifdef ARM_ALLOW_MULTI_CORE
@ -750,6 +766,7 @@ void CMiniDexed::SetMIDIChannel (uint8_t uchChannel, unsigned nTG)
{ {
m_SerialMIDI.SetChannel (uchChannel, nTG); m_SerialMIDI.SetChannel (uchChannel, nTG);
} }
m_UDPMIDI.SetChannel (uchChannel, nTG);
#ifdef ARM_ALLOW_MULTI_CORE #ifdef ARM_ALLOW_MULTI_CORE
/* This doesn't appear to be used anywhere... /* This doesn't appear to be used anywhere...
@ -2164,3 +2181,100 @@ unsigned CMiniDexed::getModController (unsigned controller, unsigned parameter,
} }
} }
void CMiniDexed::UpdateNetwork()
{
//CNetSubSystem* const pNet = CNetSubSystem::Get();
if (!m_pNet)
return;
bool bNetIsRunning = m_pNet->IsRunning();
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));
m_UDPMIDI.Initialize();
m_pFTPDaemon = new CFTPDaemon(FTPUSERNAME, FTPPASSWORD);
if (!m_pFTPDaemon->Initialize())
{
LOGERR("Failed to init FTP daemon");
delete m_pFTPDaemon;
m_pFTPDaemon = nullptr;
}
else
{
LOGNOTE("FTP daemon initialized");
}
m_UI.DisplayWrite ("IP",
"Network",
IPString,
0,
1);
}
else if (m_bNetworkReady && !bNetIsRunning)
{
m_bNetworkReady = false;
LOGNOTE("Network disconnected.");
}
}
bool CMiniDexed::InitNetwork()
{
assert(m_pNet == nullptr);
TNetDeviceType NetDeviceType = NetDeviceTypeUnknown;
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;
}
if (NetDeviceType != NetDeviceTypeUnknown)
{
if (m_pConfig->GetNetworkDHCP())
m_pNet = new CNetSubSystem(0, 0, 0, 0, m_pConfig->GetNetworkHostname(), NetDeviceType);
else
m_pNet = new CNetSubSystem(
m_pConfig->GetNetworkIPAddress().Get(),
m_pConfig->GetNetworkSubnetMask().Get(),
m_pConfig->GetNetworkDefaultGateway().Get(),
m_pConfig->GetNetworkDNSServer().Get(),
m_pConfig->GetNetworkHostname(),
NetDeviceType
);
if (!m_pNet->Initialize())
{
LOGERR("Failed to initialize network subsystem");
delete m_pNet;
m_pNet = nullptr;
}
m_pNetDevice = CNetDevice::GetNetDevice(NetDeviceType);
}
return m_pNet != nullptr;
}

@ -39,11 +39,17 @@
#include <circle/spimaster.h> #include <circle/spimaster.h>
#include <circle/multicore.h> #include <circle/multicore.h>
#include <circle/sound/soundbasedevice.h> #include <circle/sound/soundbasedevice.h>
#include <circle/sched/scheduler.h>
#include <circle/net/netsubsystem.h>
#include <wlan/bcm4343.h>
#include <wlan/hostap/wpa_supplicant/wpasupplicant.h>
#include <circle/spinlock.h> #include <circle/spinlock.h>
#include "common.h" #include "common.h"
#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 "udpmididevice.h"
#include "net/ftpdaemon.h"
class CMiniDexed class CMiniDexed
#ifdef ARM_ALLOW_MULTI_CORE #ifdef ARM_ALLOW_MULTI_CORE
@ -61,7 +67,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);
CPerformanceConfig *GetPerformanceConfig (void); CPerformanceConfig *GetPerformanceConfig (void);
@ -227,12 +232,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::AllToneGenerators]; uint8_t m_uchOPMask[CConfig::AllToneGenerators];
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
@ -323,6 +331,15 @@ private:
CSpinLock m_ReverbSpinLock; CSpinLock m_ReverbSpinLock;
// Network
CNetSubSystem* m_pNet;
CNetDevice* m_pNetDevice;
CBcm4343Device m_WLAN;
CWPASupplicant m_WPASupplicant;
bool m_bNetworkReady;
CUDPMIDIDevice m_UDPMIDI;
CFTPDaemon* m_pFTPDaemon;
bool m_bSavePerformance; bool m_bSavePerformance;
bool m_bSavePerformanceNewFile; bool m_bSavePerformanceNewFile;
bool m_bSetNewPerformance; bool m_bSetNewPerformance;
@ -335,6 +352,9 @@ private:
bool m_bLoadPerformanceBusy; bool m_bLoadPerformanceBusy;
bool m_bLoadPerformanceBankBusy; bool m_bLoadPerformanceBankBusy;
bool m_bSaveAsDeault; bool m_bSaveAsDeault;
}; };
#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, "minidexed", 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,111 @@
//
// ftpdaemon.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/ipaddress.h>
#include <circle/net/netsubsystem.h>
#include <circle/string.h>
#include "ftpdaemon.h"
#include "ftpworker.h"
LOGMODULE("ftpd");
constexpr u16 ListenPort = 21;
constexpr u8 MaxConnections = 1;
CFTPDaemon::CFTPDaemon(const char* pUser, const char* pPassword)
: CTask(TASK_STACK_SIZE, true),
m_pListenSocket(nullptr),
m_pUser(pUser),
m_pPassword(pPassword)
{
}
CFTPDaemon::~CFTPDaemon()
{
if (m_pListenSocket)
delete m_pListenSocket;
}
bool CFTPDaemon::Initialize()
{
CNetSubSystem* const pNet = CNetSubSystem::Get();
if ((m_pListenSocket = new CSocket(pNet, IPPROTO_TCP)) == nullptr)
return false;
if (m_pListenSocket->Bind(ListenPort) != 0)
{
LOGERR("Couldn't bind to port %d", ListenPort);
return false;
}
if (m_pListenSocket->Listen() != 0)
{
LOGERR("Failed to listen on control socket");
return false;
}
// We started as a suspended task; run now that initialization is successful
Start();
return true;
}
void CFTPDaemon::Run()
{
assert(m_pListenSocket != nullptr);
LOGNOTE("Listener task spawned");
while (true)
{
CIPAddress ClientIPAddress;
u16 nClientPort;
LOGDBG("Listener: waiting for connection");
CSocket* pConnection = m_pListenSocket->Accept(&ClientIPAddress, &nClientPort);
if (pConnection == nullptr)
{
LOGERR("Unable to accept connection");
continue;
}
CString IPAddressString;
ClientIPAddress.Format(&IPAddressString);
LOGNOTE("Incoming connection from %s:%d", static_cast<const char*>(IPAddressString), nClientPort);
if (CFTPWorker::GetInstanceCount() >= MaxConnections)
{
pConnection->Send("421 Maximum number of connections reached.\r\n", 45, 0);
delete pConnection;
LOGWARN("Maximum number of connections reached");
continue;
}
// Spawn new worker
new CFTPWorker(pConnection, m_pUser, m_pPassword);
}
}

@ -0,0 +1,47 @@
//
// ftpdaemon.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 _ftpdaemon_h
#define _ftpdaemon_h
#include <circle/net/socket.h>
#include <circle/sched/task.h>
class CFTPDaemon : protected CTask
{
public:
CFTPDaemon(const char* pUser, const char* pPassword);
virtual ~CFTPDaemon() override;
bool Initialize();
virtual void Run() override;
private:
// TCP sockets
CSocket* m_pListenSocket;
const char* m_pUser;
const char* m_pPassword;
};
#endif

File diff suppressed because it is too large Load Diff

@ -0,0 +1,157 @@
//
// ftpworker.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 _ftpworker_h
#define _ftpworker_h
#include <circle/net/ipaddress.h>
#include <circle/net/socket.h>
#include <circle/sched/task.h>
#include <circle/string.h>
// TODO: These may be incomplete/inaccurate
enum TFTPStatus
{
FileStatusOk = 150,
Success = 200,
SystemType = 215,
ReadyForNewUser = 220,
ClosingControl = 221,
TransferComplete = 226,
EnteringPassiveMode = 227,
UserLoggedIn = 230,
FileActionOk = 250,
PathCreated = 257,
PasswordRequired = 331,
AccountRequired = 332,
PendingFurtherInfo = 350,
ServiceNotAvailable = 421,
DataConnectionFailed = 425,
FileActionNotTaken = 450,
ActionAborted = 451,
CommandUnrecognized = 500,
SyntaxError = 501,
CommandNotImplemented = 502,
BadCommandSequence = 503,
NotLoggedIn = 530,
FileNotFound = 550,
FileNameNotAllowed = 553,
};
enum class TTransferMode
{
Active,
Passive,
};
enum class TDataType
{
ASCII,
Binary,
};
struct TFTPCommand;
struct TDirectoryListEntry;
class CFTPWorker : protected CTask
{
public:
CFTPWorker(CSocket* pControlSocket, const char* pExpectedUser, const char* pExpectedPassword);
virtual ~CFTPWorker() override;
virtual void Run() override;
static u8 GetInstanceCount() { return s_nInstanceCount; }
private:
CSocket* OpenDataConnection();
bool SendStatus(TFTPStatus StatusCode, const char* pMessage);
bool CheckLoggedIn();
// Directory navigation
CString RealPath(const char* pInBuffer) const;
const TDirectoryListEntry* BuildDirectoryList(size_t& nOutEntries) const;
// FTP command handlers
bool System(const char* pArgs);
bool Username(const char* pArgs);
bool Port(const char* pArgs);
bool Passive(const char* pArgs);
bool Password(const char* pArgs);
bool Type(const char* pArgs);
bool Retrieve(const char* pArgs);
bool Store(const char* pArgs);
bool Delete(const char* pArgs);
bool MakeDirectory(const char* pArgs);
bool ChangeWorkingDirectory(const char* pArgs);
bool ChangeToParentDirectory(const char* pArgs);
bool PrintWorkingDirectory(const char* pArgs);
bool List(const char* pArgs);
bool ListFileNames(const char* pArgs);
bool RenameFrom(const char* pArgs);
bool RenameTo(const char* pArgs);
bool Bye(const char* pArgs);
bool NoOp(const char* pArgs);
CString m_LogName;
// Authentication
const char* m_pExpectedUser;
const char* m_pExpectedPassword;
// TCP sockets
CSocket* m_pControlSocket;
CSocket* m_pDataSocket;
u16 m_nDataSocketPort;
CIPAddress m_DataSocketIPAddress;
// Command/data buffers
char m_CommandBuffer[FRAME_BUFFER_SIZE];
u8 m_DataBuffer[FRAME_BUFFER_SIZE];
// Session state
CString m_User;
CString m_Password;
TDataType m_DataType;
TTransferMode m_TransferMode;
CString m_CurrentPath;
CString m_RenameFrom;
static void FatFsPathToFTPPath(const char* pInBuffer, char* pOutBuffer, size_t nSize);
static void FTPPathToFatFsPath(const char* pInBuffer, char* pOutBuffer, size_t nSize);
static void FatFsParentPath(const char* pInBuffer, char* pOutBuffer, size_t nSize);
static void FormatLastModifiedDate(u16 nDate, char* pOutBuffer, size_t nSize);
static void FormatLastModifiedTime(u16 nDate, char* pOutBuffer, size_t nSize);
static const TFTPCommand Commands[];
static u8 s_nInstanceCount;
};
#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,193 @@
//
// utility.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 _utility_h
#define _utility_h
#include <circle/string.h>
#include <circle/util.h>
// Macro to extract the string representation of an enum
#define CONFIG_ENUM_VALUE(VALUE, STRING) VALUE,
// Macro to extract the enum value
#define CONFIG_ENUM_STRING(VALUE, STRING) #STRING,
// Macro to declare the enum itself
#define CONFIG_ENUM(NAME, VALUES) enum class NAME { VALUES(CONFIG_ENUM_VALUE) }
// Macro to declare an array of string representations for an enum
#define CONFIG_ENUM_STRINGS(NAME, DATA) static const char* NAME##Strings[] = { DATA(CONFIG_ENUM_STRING) }
namespace Utility
{
// Templated function for clamping a value between a minimum and a maximum
template <class T>
constexpr T Clamp(const T& nValue, const T& nMin, const T& nMax)
{
return (nValue < nMin) ? nMin : (nValue > nMax) ? nMax : nValue;
}
// Templated function for taking the minimum of two values
template <class T>
constexpr T Min(const T& nLHS, const T& nRHS)
{
return nLHS < nRHS ? nLHS : nRHS;
}
// Templated function for taking the maximum of two values
template <class T>
constexpr T Max(const T& nLHS, const T& nRHS)
{
return nLHS > nRHS ? nLHS : nRHS;
}
// Function for performing a linear interpolation of a value
constexpr float Lerp(float nValue, float nMinA, float nMaxA, float nMinB, float nMaxB)
{
return nMinB + (nValue - nMinA) * ((nMaxB - nMinB) / (nMaxA - nMinA));
}
// Return number of elements in an array
template <class T, size_t N>
constexpr size_t ArraySize(const T(&)[N]) { return N; }
// Returns whether some value is a power of 2
template <class T>
constexpr bool IsPowerOfTwo(const T& nValue)
{
return nValue && ((nValue & (nValue - 1)) == 0);
}
// Rounds a number to a nearest multiple; only works for integer values/multiples
template <class T>
constexpr T RoundToNearestMultiple(const T& nValue, const T& nMultiple)
{
return ((nValue + nMultiple / 2) / nMultiple) * nMultiple;
}
// Convert between milliseconds and ticks of a 1MHz clock
template <class T>
constexpr T MillisToTicks(const T& nMillis)
{
return nMillis * 1000;
}
template <class T>
constexpr T TicksToMillis(const T& nTicks)
{
return nTicks / 1000;
}
// Computes the Roland checksum
constexpr u8 RolandChecksum(const u8* pData, size_t nSize)
{
u8 nSum = 0;
for (size_t i = 0; i < nSize; ++i)
nSum = (nSum + pData[i]) & 0x7F;
return 128 - nSum;
}
// Comparators for sorting
namespace Comparator
{
template<class T>
using TComparator = bool (*)(const T&, const T&);
template<class T>
inline bool LessThan(const T& ObjectA, const T& ObjectB)
{
return ObjectA < ObjectB;
}
template<class T>
inline bool GreaterThan(const T& ObjectA, const T& ObjectB)
{
return ObjectA > ObjectB;
}
inline bool CaseInsensitiveAscending(const CString& StringA, const CString& StringB)
{
return strcasecmp(StringA, StringB) < 0;
}
}
// Swaps two objects in-place
template<class T>
inline void Swap(T& ObjectA, T& ObjectB)
{
u8 Buffer[sizeof(T)];
memcpy(Buffer, &ObjectA, sizeof(T));
memcpy(&ObjectA, &ObjectB, sizeof(T));
memcpy(&ObjectB, Buffer, sizeof(T));
}
namespace
{
// Quicksort partition function (private)
template<class T>
size_t Partition(T* Items, Comparator::TComparator<T> Comparator, size_t nLow, size_t nHigh)
{
const size_t nPivotIndex = (nHigh + nLow) / 2;
T* Pivot = &Items[nPivotIndex];
while (true)
{
while (Comparator(Items[nLow], *Pivot))
++nLow;
while (Comparator(*Pivot, Items[nHigh]))
--nHigh;
if (nLow >= nHigh)
return nHigh;
Swap(Items[nLow], Items[nHigh]);
// Update pointer if pivot was swapped
if (nPivotIndex == nLow)
Pivot = &Items[nHigh];
else if (nPivotIndex == nHigh)
Pivot = &Items[nLow];
++nLow;
--nHigh;
}
}
}
// Sorts an array in-place using the Tony Hoare Quicksort algorithm
template <class T>
void QSort(T* Items, Comparator::TComparator<T> Comparator, size_t nLow, size_t nHigh)
{
if (nLow < nHigh)
{
size_t p = Partition(Items, Comparator, nLow, nHigh);
QSort(Items, Comparator, nLow, p);
QSort(Items, Comparator, p + 1, nHigh);
}
}
}
#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

@ -0,0 +1,89 @@
//
// udpmididevice.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 "udpmididevice.h"
#include <assert.h>
#define VIRTUALCABLE 24
LOGMODULE("rtpmididevice");
CUDPMIDIDevice::CUDPMIDIDevice (CMiniDexed *pSynthesizer,
CConfig *pConfig, CUserInterface *pUI)
: CMIDIDevice (pSynthesizer, pConfig, pUI),
m_pSynthesizer (pSynthesizer),
m_pConfig (pConfig)
{
AddDevice ("udp");
}
CUDPMIDIDevice::~CUDPMIDIDevice (void)
{
//m_pSynthesizer = 0;
}
boolean CUDPMIDIDevice::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;
m_pUDPMIDIReceiver = new CUDPMIDIReceiver(this);
if (!m_pUDPMIDIReceiver->Initialize())
{
LOGERR("Failed to init UDP MIDI receiver");
delete m_pUDPMIDIReceiver;
m_pUDPMIDIReceiver = nullptr;
}
else
LOGNOTE("UDP MIDI receiver initialized");
}
// Methods to handle MIDI events
void CUDPMIDIDevice::OnAppleMIDIDataReceived(const u8* pData, size_t nSize)
{
MIDIMessageHandler(pData, nSize, VIRTUALCABLE);
}
void CUDPMIDIDevice::OnAppleMIDIConnect(const CIPAddress* pIPAddress, const char* pName)
{
LOGNOTE("RTP Device connected");
}
void CUDPMIDIDevice::OnAppleMIDIDisconnect(const CIPAddress* pIPAddress, const char* pName)
{
LOGNOTE("RTP Device disconnected");
}
void CUDPMIDIDevice::OnUDPMIDIDataReceived(const u8* pData, size_t nSize)
{
MIDIMessageHandler(pData, nSize, VIRTUALCABLE);
}

@ -0,0 +1,56 @@
//
// udpmididevice.h
//
// Virtual midi device for data recieved on network
//
// 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 "net/udpmidi.h"
class CMiniDexed;
class CUDPMIDIDevice : CAppleMIDIHandler, CUDPMIDIHandler, public CMIDIDevice
{
public:
CUDPMIDIDevice (CMiniDexed *pSynthesizer, CConfig *pConfig, CUserInterface *pUI);
~CUDPMIDIDevice (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;
virtual void OnUDPMIDIDataReceived(const u8* pData, size_t nSize) override;
private:
CMiniDexed *m_pSynthesizer;
CConfig *m_pConfig;
CBcmRandomNumberGenerator m_Random;
CAppleMIDIParticipant* m_pAppleMIDIParticipant; // AppleMIDI participant instance
CUDPMIDIReceiver* m_pUDPMIDIReceiver;
};
#endif
Loading…
Cancel
Save