From 07fead823c33b229f4bf9787bb4ba34cf67fd65f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20=C5=9Eiar=20Baysal?= Date: Tue, 5 Nov 2024 09:13:36 +0000 Subject: [PATCH 01/13] initial network support --- src/Makefile | 6 +- src/Rules.mk | 8 +- src/config.cpp | 15 + src/config.h | 8 + src/kernel.cpp | 11 +- src/kernel.h | 2 +- src/minidexed.cpp | 141 ++++++- src/minidexed.h | 21 +- src/net/applemidi.cpp | 874 ++++++++++++++++++++++++++++++++++++++++++ src/net/applemidi.h | 111 ++++++ src/net/byteorder.h | 42 ++ src/net/udpmidi.cpp | 89 +++++ src/net/udpmidi.h | 57 +++ src/rtpmididevice.cpp | 80 ++++ src/rtpmididevice.h | 69 ++++ 15 files changed, 1524 insertions(+), 10 deletions(-) create mode 100644 src/net/applemidi.cpp create mode 100644 src/net/applemidi.h create mode 100644 src/net/byteorder.h create mode 100644 src/net/udpmidi.cpp create mode 100644 src/net/udpmidi.h create mode 100644 src/rtpmididevice.cpp create mode 100644 src/rtpmididevice.h diff --git a/src/Makefile b/src/Makefile index 540ae68..f078fad 100644 --- a/src/Makefile +++ b/src/Makefile @@ -5,11 +5,13 @@ CIRCLE_STDLIB_DIR = ../circle-stdlib SYNTH_DEXED_DIR = ../Synth_Dexed/src CMSIS_DIR = ../CMSIS_5/CMSIS +NET_DIR = ./net 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 \ - 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 diff --git a/src/Rules.mk b/src/Rules.mk index 2ebc132..6461833 100644 --- a/src/Rules.mk +++ b/src/Rules.mk @@ -11,7 +11,8 @@ include $(CIRCLEHOME)/Rules.mk INCLUDE += \ -I $(CIRCLE_STDLIB_DIR)/include \ - -I $(NEWLIBDIR)/include + -I $(NEWLIBDIR)/include \ + -I $(NET_DIR) LIBS += \ $(NEWLIBDIR)/lib/libm.a \ @@ -28,6 +29,9 @@ LIBS += \ $(CIRCLEHOME)/addon/fatfs/libfatfs.a \ $(CIRCLEHOME)/lib/fs/libfs.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) diff --git a/src/config.cpp b/src/config.cpp index 526d8ad..ac6a2e4 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -152,6 +152,10 @@ void CConfig::Load (void) m_bProfileEnabled = m_Properties.GetNumber ("ProfileEnabled", 0) != 0; m_bPerformanceSelectToLoad = m_Properties.GetNumber ("PerformanceSelectToLoad", 1) != 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 @@ -503,3 +507,14 @@ unsigned CConfig::GetPerformanceSelectChannel (void) const { return m_bPerformanceSelectChannel; } + +// Network +bool CConfig::GetNetworkEnabled (void) const +{ + return m_bNetworkEnabled; +} + +const char *CConfig::GetNetworkType (void) const +{ + return m_NetworkType.c_str(); +} \ No newline at end of file diff --git a/src/config.h b/src/config.h index f83c177..30ee5ed 100644 --- a/src/config.h +++ b/src/config.h @@ -168,6 +168,10 @@ public: bool GetPerformanceSelectToLoad (void) const; unsigned GetPerformanceSelectChannel (void) const; + // Network + bool GetNetworkEnabled (void) const; + const char *GetNetworkType (void) const; + private: CPropertiesFatFsFile m_Properties; @@ -252,6 +256,10 @@ private: bool m_bProfileEnabled; bool m_bPerformanceSelectToLoad; unsigned m_bPerformanceSelectChannel; + + // Network + bool m_bNetworkEnabled; + std::string m_NetworkType; }; #endif diff --git a/src/kernel.cpp b/src/kernel.cpp index c06c386..5c7a7a9 100644 --- a/src/kernel.cpp +++ b/src/kernel.cpp @@ -24,12 +24,17 @@ #include #include "usbminidexedmidigadget.h" +#define NET_DEVICE_TYPE NetDeviceTypeWLAN // or: NetDeviceTypeWLAN + LOGMODULE ("kernel"); CKernel *CKernel::s_pThis = 0; CKernel::CKernel (void) -: CStdlibAppStdio ("minidexed"), +: + //CStdlibAppStdio ("minidexed"), + CStdlibAppNetwork ("minidexed", CSTDLIBAPP_DEFAULT_PARTITION, + 0, 0, 0, 0, NET_DEVICE_TYPE), m_Config (&mFileSystem), m_GPIOManager (&mInterrupt), m_I2CMaster (CMachineInfo::Get ()->GetDevice (DeviceI2CMaster), TRUE), @@ -47,7 +52,7 @@ CKernel::~CKernel(void) bool CKernel::Initialize (void) { - if (!CStdlibAppStdio::Initialize ()) + if (!CStdlibAppNetwork::Initialize ()) { return FALSE; } @@ -109,7 +114,7 @@ CStdlibApp::TShutdownMode CKernel::Run (void) mScreen.Update (); } - m_CPUThrottle.Update (); + m_CPUThrottle.Update (); } return ShutdownHalt; diff --git a/src/kernel.h b/src/kernel.h index 7d2f346..31757cd 100644 --- a/src/kernel.h +++ b/src/kernel.h @@ -35,7 +35,7 @@ enum TShutdownMode ShutdownReboot }; -class CKernel : public CStdlibAppStdio +class CKernel : public CStdlibAppNetwork { public: CKernel (void); diff --git a/src/minidexed.cpp b/src/minidexed.cpp index 71b7247..d736ed3 100644 --- a/src/minidexed.cpp +++ b/src/minidexed.cpp @@ -27,6 +27,19 @@ #include #include #include +#include +#include +#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"); @@ -54,7 +67,17 @@ CMiniDexed::CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt, m_bSavePerformanceNewFile (false), m_bSetNewPerformance (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); @@ -267,7 +290,14 @@ bool CMiniDexed::Initialize (void) return false; } #endif + //InitNetwork(); + UpdateNetwork(); + //CMIDIDevice->InitializeRTP(); + if (m_RTPMIDI.Initialize ()) + { + LOGNOTE ("RTP MIDI interface enabled"); + } return true; } @@ -325,6 +355,7 @@ void CMiniDexed::Process (bool bPlugAndPlayUpdated) { m_GetChunkTimer.Dump (); } + } #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(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(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(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; +} +*/ \ No newline at end of file diff --git a/src/minidexed.h b/src/minidexed.h index 1aa3096..65d4034 100644 --- a/src/minidexed.h +++ b/src/minidexed.h @@ -43,6 +43,10 @@ #include "effect_mixer.hpp" #include "effect_platervbstereo.h" #include "effect_compressor.h" +//#include +//#include +//#include +#include "rtpmididevice.h" class CMiniDexed #ifdef ARM_ALLOW_MULTI_CORE @@ -60,7 +64,6 @@ public: #ifdef ARM_ALLOW_MULTI_CORE void Run (unsigned nCore); #endif - CSysExFileLoader *GetSysExFileLoader (void); void BankSelect (unsigned nBank, unsigned nTG); @@ -211,12 +214,15 @@ public: bool DoSavePerformance (void); void setMasterVolume (float32_t vol); + //bool InitNetwork(); + void UpdateNetwork(); private: int16_t ApplyNoteLimits (int16_t pitch, unsigned nTG); // returns < 0 to ignore note uint8_t m_uchOPMask[CConfig::ToneGenerators]; void LoadPerformanceParameters(void); void ProcessSound (void); + const char* GetNetworkDeviceShortName() const; #ifdef ARM_ALLOW_MULTI_CORE enum TCoreStatus @@ -309,6 +315,19 @@ private: unsigned m_nDeletePerformanceID; bool m_bLoadPerformanceBusy; 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 diff --git a/src/net/applemidi.cpp b/src/net/applemidi.cpp new file mode 100644 index 0000000..b484a07 --- /dev/null +++ b/src/net/applemidi.cpp @@ -0,0 +1,874 @@ +// +// applemidi.cpp +// +// mt32-pi - A baremetal MIDI synthesizer for Raspberry Pi +// Copyright (C) 2020-2023 Dale Whinham +// +// 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 . +// + +#include +#include +#include +#include +#include +#include +#include + +#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(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, "", sizeof(pOutPacket->Name)); + + return true; +} + +bool ParseEndSessionPacket(const u8* pBuffer, size_t nSize, TAppleMIDISession* pOutPacket) +{ + const TAppleMIDISession* const pInPacket = reinterpret_cast(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(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(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(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(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)); +} \ No newline at end of file diff --git a/src/net/applemidi.h b/src/net/applemidi.h new file mode 100644 index 0000000..3df68ae --- /dev/null +++ b/src/net/applemidi.h @@ -0,0 +1,111 @@ +// +// applemidi.h +// +// mt32-pi - A baremetal MIDI synthesizer for Raspberry Pi +// Copyright (C) 2020-2023 Dale Whinham +// +// 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 . +// + +#ifndef _applemidi_h +#define _applemidi_h + +#include +#include +#include +#include + +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 \ No newline at end of file diff --git a/src/net/byteorder.h b/src/net/byteorder.h new file mode 100644 index 0000000..5160119 --- /dev/null +++ b/src/net/byteorder.h @@ -0,0 +1,42 @@ +// +// byteorder.h +// +// mt32-pi - A baremetal MIDI synthesizer for Raspberry Pi +// Copyright (C) 2020-2023 Dale Whinham +// +// 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 . +// + +#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 \ No newline at end of file diff --git a/src/net/udpmidi.cpp b/src/net/udpmidi.cpp new file mode 100644 index 0000000..2f25eda --- /dev/null +++ b/src/net/udpmidi.cpp @@ -0,0 +1,89 @@ +// +// udpmidi.cpp +// +// mt32-pi - A baremetal MIDI synthesizer for Raspberry Pi +// Copyright (C) 2020-2023 Dale Whinham +// +// 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 . +// + +#include +#include +#include +#include + +#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(); + } +} \ No newline at end of file diff --git a/src/net/udpmidi.h b/src/net/udpmidi.h new file mode 100644 index 0000000..102d339 --- /dev/null +++ b/src/net/udpmidi.h @@ -0,0 +1,57 @@ +// +// udpmidi.h +// +// mt32-pi - A baremetal MIDI synthesizer for Raspberry Pi +// Copyright (C) 2020-2023 Dale Whinham +// +// 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 . +// + +#ifndef _udpmidi_h +#define _udpmidi_h + +#include +#include +#include + +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 \ No newline at end of file diff --git a/src/rtpmididevice.cpp b/src/rtpmididevice.cpp new file mode 100644 index 0000000..2c29a94 --- /dev/null +++ b/src/rtpmididevice.cpp @@ -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 +// +// 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 . +// + +#include +#include +#include "rtpmididevice.h" +#include + +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 +} diff --git a/src/rtpmididevice.h b/src/rtpmididevice.h new file mode 100644 index 0000000..418fcde --- /dev/null +++ b/src/rtpmididevice.h @@ -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 +// +// 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 . +// +#ifndef _rtpmididevice_h +#define _rtpmididevice_h + +#include "mididevice.h" +#include "config.h" +#include "net/applemidi.h" + +#include +#include +#include +#include + +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 From 0a9eb7f56581aa3eb9b015cea3de64041bedc398 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20=C5=9Eiar=20Baysal?= Date: Tue, 5 Nov 2024 09:14:57 +0000 Subject: [PATCH 02/13] add build.sh --- build.sh | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/build.sh b/build.sh index b69ba6b..25d9dfc 100755 --- a/build.sh +++ b/build.sh @@ -15,7 +15,7 @@ else fi # Define system options -OPTIONS="-o USE_PWM_AUDIO_ON_ZERO -o SAVE_VFP_REGS_ON_IRQ -o REALTIME -o SCREEN_DMA_BURST_LENGTH=1" +OPTIONS="-o USE_PWM_AUDIO_ON_ZERO -o SAVE_VFP_REGS_ON_IRQ -o REALTIME -o USE_SDHOST -o SCREEN_DMA_BURST_LENGTH=1" if [ "${RPI}" -gt "1" ]; then OPTIONS="${OPTIONS} -o ARM_ALLOW_MULTI_CORE" fi @@ -39,6 +39,11 @@ make -j cd libs/circle/addon/display/ make clean || true make -j + +cd ../wlan/ +make clean || true +make -j + cd ../sensor/ make clean || true make -j From 893b9f60f11843353cfbe6210097cb4ce13cad85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20=C5=9Eiar=20Baysal?= Date: Tue, 5 Nov 2024 20:13:23 +0000 Subject: [PATCH 03/13] working RTP and UDP poc --- src/Makefile | 4 +- src/kernel.h | 8 +- src/mididevice.cpp | 12 +- src/minidexed.cpp | 35 +- src/minidexed.h | 10 +- src/net/ftpdaemon.cpp | 111 ++++ src/net/ftpdaemon.h | 47 ++ src/net/ftpworker.cpp | 1206 +++++++++++++++++++++++++++++++++++++++++ src/net/ftpworker.h | 157 ++++++ src/net/utility.h | 193 +++++++ src/udpmididevice.cpp | 106 ++++ src/udpmididevice.h | 76 +++ 12 files changed, 1946 insertions(+), 19 deletions(-) create mode 100644 src/net/ftpdaemon.cpp create mode 100644 src/net/ftpdaemon.h create mode 100644 src/net/ftpworker.cpp create mode 100644 src/net/ftpworker.h create mode 100644 src/net/utility.h create mode 100644 src/udpmididevice.cpp create mode 100644 src/udpmididevice.h diff --git a/src/Makefile b/src/Makefile index f078fad..fa4ab10 100644 --- a/src/Makefile +++ b/src/Makefile @@ -8,10 +8,10 @@ CMSIS_DIR = ../CMSIS_5/CMSIS NET_DIR = ./net OBJS = main.o kernel.o minidexed.o config.o userinterface.o uimenu.o \ - mididevice.o rtpmididevice.o midikeyboard.o serialmididevice.o pckeyboard.o \ + mididevice.o udpmididevice.o midikeyboard.o serialmididevice.o pckeyboard.o \ sysexfileloader.o performanceconfig.o perftimer.o \ effect_compressor.o effect_platervbstereo.o uibuttons.o midipin.o \ - net/applemidi.o net/udpmidi.o + net/ftpdaemon.o net/ftpworker.o net/applemidi.o net/udpmidi.o OPTIMIZE = -O3 diff --git a/src/kernel.h b/src/kernel.h index 31757cd..000985b 100644 --- a/src/kernel.h +++ b/src/kernel.h @@ -50,12 +50,12 @@ private: private: // do not change this order - CConfig m_Config; + CConfig m_Config; CCPUThrottle m_CPUThrottle; CGPIOManager m_GPIOManager; - CI2CMaster m_I2CMaster; - CMiniDexed *m_pDexed; - CUSBController *m_pUSB; + CI2CMaster m_I2CMaster; + CMiniDexed *m_pDexed; + CUSBController *m_pUSB; static CKernel *s_pThis; }; diff --git a/src/mididevice.cpp b/src/mididevice.cpp index 5f231d6..917f674 100644 --- a/src/mididevice.cpp +++ b/src/mididevice.cpp @@ -169,6 +169,7 @@ void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsign } m_MIDISpinLock.Acquire (); + printf ("MIDI-DEBUG: SPINLOCK ACQUIRED\n"); u8 ucStatus = pMessage[0]; u8 ucChannel = ucStatus & 0x0F; @@ -213,8 +214,10 @@ void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsign } // Process MIDI for each Tone Generator + printf ("MIDI-DEBUG: EACH TONEGENERATOR LOOP\n"); for (unsigned nTG = 0; nTG < CConfig::ToneGenerators; nTG++) { + printf ("%u TONE GENERATOR", nTG); if (ucStatus == MIDI_SYSTEM_EXCLUSIVE_BEGIN) { // MIDI SYSEX per MIDI channel @@ -227,12 +230,15 @@ void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsign } else { + printf ("NOT AN SYSEX"); + if ( m_ChannelMap[nTG] == ucChannel || m_ChannelMap[nTG] == OmniMode) { switch (ucType) { case MIDI_NOTE_ON: + printf ("MIDI-DEBUG: CASE MIDI NOTE ON\n"); if (nLength < 3) { break; @@ -242,12 +248,15 @@ void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsign { if (pMessage[2] <= 127) { + printf ("MIDI-DEBUG: KEYDOWN EVENT\n"); m_pSynthesizer->keydown (pMessage[1], pMessage[2], nTG); } } else { + printf ("MIDI-DEBUG: KEYUP EVENT\n"); + //printf ("MIDI-RTP: %02X\n", m_pSynthesizer); m_pSynthesizer->keyup (pMessage[1], nTG); } break; @@ -257,7 +266,7 @@ void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsign { break; } - + printf ("MIDI-DEBUG: MIDI NOTE OFF\n"); m_pSynthesizer->keyup (pMessage[1], nTG); break; @@ -379,6 +388,7 @@ void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsign } } m_MIDISpinLock.Release (); + printf ("MIDI-DEBUG: SPINLOCK RELEASED\n"); } void CMIDIDevice::AddDevice (const char *pDeviceName) diff --git a/src/minidexed.cpp b/src/minidexed.cpp index d736ed3..7e0c930 100644 --- a/src/minidexed.cpp +++ b/src/minidexed.cpp @@ -28,15 +28,17 @@ #include #include #include -#include -#include "circle_stdlib_app.h" +//#include +//#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" - +#define FTPUSERNAME "admin" +#define FTPPASSWORD "admin" +/* const char WLANFirmwarePath[] = "SD:firmware/"; const char WLANConfigFile[] = "SD:wpa_supplicant.conf"; */ @@ -77,7 +79,7 @@ CMiniDexed::CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt, */ //CNetSubSystem* const pNet = CNetSubSystem::Get(); m_bNetworkReady(false), - m_RTPMIDI (this, pConfig, &m_UI) + m_UDPMIDI (this, pConfig, &m_UI) { assert (m_pConfig); @@ -291,18 +293,24 @@ bool CMiniDexed::Initialize (void) } #endif //InitNetwork(); - UpdateNetwork(); + //CMIDIDevice->InitializeRTP(); - if (m_RTPMIDI.Initialize ()) + m_pFTPDaemon = new CFTPDaemon(FTPUSERNAME, FTPPASSWORD); + if (!m_pFTPDaemon->Initialize()) { - LOGNOTE ("RTP MIDI interface enabled"); + LOGERR("Failed to init FTP daemon"); + delete m_pFTPDaemon; + m_pFTPDaemon = nullptr; } + else + LOGNOTE("FTP daemon initialized"); return true; } void CMiniDexed::Process (bool bPlugAndPlayUpdated) { + CScheduler* const pScheduler = CScheduler::Get(); #ifndef ARM_ALLOW_MULTI_CORE ProcessSound (); #endif @@ -355,7 +363,9 @@ void CMiniDexed::Process (bool bPlugAndPlayUpdated) { m_GetChunkTimer.Dump (); } - + UpdateNetwork(); + // Allow other tasks to run + pScheduler->Yield(); } #ifdef ARM_ALLOW_MULTI_CORE @@ -629,6 +639,7 @@ void CMiniDexed::SetMIDIChannel (uint8_t uchChannel, unsigned nTG) { m_SerialMIDI.SetChannel (uchChannel, nTG); } + m_UDPMIDI.SetChannel (uchChannel, nTG); #ifdef ARM_ALLOW_MULTI_CORE unsigned nActiveTGs = 0; @@ -1860,11 +1871,15 @@ void CMiniDexed::UpdateNetwork() if (!m_bNetworkReady) { m_bNetworkReady = true; - CString IPString; pNet->GetConfig()->GetIPAddress()->Format(&IPString); LOGNOTE("Network up and running at: %s", static_cast(IPString)); + + if (m_UDPMIDI.Initialize ()) + { + LOGNOTE ("RTP MIDI interface enabled"); + } } else if (m_bNetworkReady && !bNetIsRunning) { diff --git a/src/minidexed.h b/src/minidexed.h index 65d4034..45c3797 100644 --- a/src/minidexed.h +++ b/src/minidexed.h @@ -38,6 +38,9 @@ #include #include #include +#include +////#include +//#include #include #include "common.h" #include "effect_mixer.hpp" @@ -46,7 +49,8 @@ //#include //#include //#include -#include "rtpmididevice.h" +#include "udpmididevice.h" +#include "net/ftpdaemon.h" class CMiniDexed #ifdef ARM_ALLOW_MULTI_CORE @@ -316,6 +320,7 @@ private: bool m_bLoadPerformanceBusy; bool m_bSaveAsDeault; bool m_bNetworkReady; + //CNetSubSystem* m_pNet; //CWPASupplicant m_WPASupplicant; // Networking //CNetSubSystem &mNet; @@ -327,7 +332,8 @@ private: bool m_bNetworkReady; CBcmRandomNumberGenerator m_Random; */ - CRTPMIDIDevice m_RTPMIDI; + CUDPMIDIDevice m_UDPMIDI; + CFTPDaemon* m_pFTPDaemon; }; #endif diff --git a/src/net/ftpdaemon.cpp b/src/net/ftpdaemon.cpp new file mode 100644 index 0000000..0cab51c --- /dev/null +++ b/src/net/ftpdaemon.cpp @@ -0,0 +1,111 @@ +// +// ftpdaemon.cpp +// +// mt32-pi - A baremetal MIDI synthesizer for Raspberry Pi +// Copyright (C) 2020-2023 Dale Whinham +// +// 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 . +// + +#include +#include +#include +#include +#include + +#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(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); + } +} \ No newline at end of file diff --git a/src/net/ftpdaemon.h b/src/net/ftpdaemon.h new file mode 100644 index 0000000..4d75762 --- /dev/null +++ b/src/net/ftpdaemon.h @@ -0,0 +1,47 @@ +// +// ftpdaemon.h +// +// mt32-pi - A baremetal MIDI synthesizer for Raspberry Pi +// Copyright (C) 2020-2023 Dale Whinham +// +// 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 . +// + +#ifndef _ftpdaemon_h +#define _ftpdaemon_h + +#include +#include + +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 \ No newline at end of file diff --git a/src/net/ftpworker.cpp b/src/net/ftpworker.cpp new file mode 100644 index 0000000..e3fd1f3 --- /dev/null +++ b/src/net/ftpworker.cpp @@ -0,0 +1,1206 @@ +// +// ftpworker.cpp +// +// mt32-pi - A baremetal MIDI synthesizer for Raspberry Pi +// Copyright (C) 2020-2023 Dale Whinham +// +// 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 . +// + +//#define FTPDAEMON_DEBUG + +#include +#include +#include +#include +#include +#include + +#include + +#include "ftpworker.h" +#include "utility.h" + +// Use a per-instance name for the log macros +#define From m_LogName + +constexpr u16 PassivePortBase = 9000; +constexpr size_t TextBufferSize = 512; +constexpr unsigned int SocketTimeout = 20; +constexpr unsigned int NumRetries = 3; + +#ifndef MT32_PI_VERSION +#define MT32_PI_VERSION "(version unknown)" +#endif + +const char MOTDBanner[] = "Welcome to the mt32-pi " MT32_PI_VERSION " embedded FTP server!"; + +enum class TDirectoryListEntryType +{ + File, + Directory, +}; + +struct TDirectoryListEntry +{ + char Name[FF_LFN_BUF + 1]; + TDirectoryListEntryType Type; + u32 nSize; + u16 nLastModifedDate; + u16 nLastModifedTime; +}; + +using TCommandHandler = bool (CFTPWorker::*)(const char* pArgs); + +struct TFTPCommand +{ + const char* pCmdStr; + TCommandHandler pHandler; +}; + +const TFTPCommand CFTPWorker::Commands[] = +{ + { "SYST", &CFTPWorker::System }, + { "USER", &CFTPWorker::Username }, + { "PASS", &CFTPWorker::Password }, + { "TYPE", &CFTPWorker::Type }, + { "PASV", &CFTPWorker::Passive }, + { "PORT", &CFTPWorker::Port }, + { "RETR", &CFTPWorker::Retrieve }, + { "STOR", &CFTPWorker::Store }, + { "DELE", &CFTPWorker::Delete }, + { "RMD", &CFTPWorker::Delete }, + { "MKD", &CFTPWorker::MakeDirectory }, + { "CWD", &CFTPWorker::ChangeWorkingDirectory }, + { "CDUP", &CFTPWorker::ChangeToParentDirectory }, + { "PWD", &CFTPWorker::PrintWorkingDirectory }, + { "LIST", &CFTPWorker::List }, + { "NLST", &CFTPWorker::ListFileNames }, + { "RNFR", &CFTPWorker::RenameFrom }, + { "RNTO", &CFTPWorker::RenameTo }, + { "BYE", &CFTPWorker::Bye }, + { "QUIT", &CFTPWorker::Bye }, + { "NOOP", &CFTPWorker::NoOp }, +}; + +u8 CFTPWorker::s_nInstanceCount = 0; + +// Volume names from ffconf.h +// TODO: Share with soundfontmanager.cpp +const char* const VolumeNames[] = { FF_VOLUME_STRS }; + +bool ValidateVolumeName(const char* pVolumeName) +{ + for (const auto pName : VolumeNames) + { + if (strcasecmp(pName, pVolumeName) == 0) + return true; + } + + return false; +} + +// Comparator for sorting directory listings +inline bool DirectoryCaseInsensitiveAscending(const TDirectoryListEntry& EntryA, const TDirectoryListEntry& EntryB) +{ + // Directories first in ascending order + if (EntryA.Type != EntryB.Type) + return EntryA.Type == TDirectoryListEntryType::Directory; + + return strncasecmp(EntryA.Name, EntryB.Name, sizeof(TDirectoryListEntry::Name)) < 0; +} + + +CFTPWorker::CFTPWorker(CSocket* pControlSocket, const char* pExpectedUser, const char* pExpectedPassword) + : CTask(TASK_STACK_SIZE), + m_LogName(), + m_pExpectedUser(pExpectedUser), + m_pExpectedPassword(pExpectedPassword), + m_pControlSocket(pControlSocket), + m_pDataSocket(nullptr), + m_nDataSocketPort(0), + m_DataSocketIPAddress(), + m_CommandBuffer{'\0'}, + m_DataBuffer{0}, + m_User(), + m_Password(), + m_DataType(TDataType::ASCII), + m_TransferMode(TTransferMode::Active), + m_CurrentPath(), + m_RenameFrom() +{ + ++s_nInstanceCount; + m_LogName.Format("ftpd[%d]", s_nInstanceCount); +} + +CFTPWorker::~CFTPWorker() +{ + if (m_pControlSocket) + delete m_pControlSocket; + + if (m_pDataSocket) + delete m_pDataSocket; + + --s_nInstanceCount; + + LOGNOTE("Instance count is now %d", s_nInstanceCount); +} + +void CFTPWorker::Run() +{ + assert(m_pControlSocket != nullptr); + + const size_t nWorkerNumber = s_nInstanceCount; + CScheduler* const pScheduler = CScheduler::Get(); + + LOGNOTE("Worker task %d spawned", nWorkerNumber); + + if (!SendStatus(TFTPStatus::ReadyForNewUser, MOTDBanner)) + return; + + CTimer* const pTimer = CTimer::Get(); + unsigned int nTimeout = pTimer->GetTicks(); + + while (m_pControlSocket) + { + // Block while waiting to receive +#ifdef FTPDAEMON_DEBUG + LOGDBG("Waiting for command"); +#endif + const int nReceiveBytes = m_pControlSocket->Receive(m_CommandBuffer, sizeof(m_CommandBuffer), MSG_DONTWAIT); + + if (nReceiveBytes == 0) + { + if (pTimer->GetTicks() - nTimeout >= SocketTimeout * HZ) + { + LOGERR("Socket timed out"); + break; + } + + pScheduler->Yield(); + continue; + } + + if (nReceiveBytes < 0) + { + LOGNOTE("Connection closed"); + break; + } + + // FIXME + m_CommandBuffer[nReceiveBytes - 2] = '\0'; + +#ifdef FTPDAEMON_DEBUG + const u8* pIPAddress = m_pControlSocket->GetForeignIP(); + LOGDBG("<-- Received %d bytes from %d.%d.%d.%d: '%s'", nReceiveBytes, pIPAddress[0], pIPAddress[1], pIPAddress[2], pIPAddress[3], m_CommandBuffer); +#endif + + char* pSavePtr; + char* pToken = strtok_r(m_CommandBuffer, " \r\n", &pSavePtr); + + if (!pToken) + { + LOGERR("String tokenization error (received: '%s')", m_CommandBuffer); + continue; + } + + TCommandHandler pHandler = nullptr; + for (size_t i = 0; i < Utility::ArraySize(Commands); ++i) + { + if (strcasecmp(pToken, Commands[i].pCmdStr) == 0) + { + pHandler = Commands[i].pHandler; + break; + } + } + + if (pHandler) + (this->*pHandler)(pSavePtr); + else + SendStatus(TFTPStatus::CommandNotImplemented, "Command not implemented."); + + nTimeout = pTimer->GetTicks(); + } + + LOGNOTE("Worker task %d shutting down", nWorkerNumber); + + delete m_pControlSocket; + m_pControlSocket = nullptr; +} + +CSocket* CFTPWorker::OpenDataConnection() +{ + CSocket* pDataSocket = nullptr; + u8 nRetries = NumRetries; + + while (pDataSocket == nullptr && nRetries > 0) + { + // Active: Create new socket and connect to client + if (m_TransferMode == TTransferMode::Active) + { + CNetSubSystem* const pNet = CNetSubSystem::Get(); + pDataSocket = new CSocket(pNet, IPPROTO_TCP); + + if (pDataSocket == nullptr) + { + SendStatus(TFTPStatus::DataConnectionFailed, "Could not open socket."); + return nullptr; + } + + if (pDataSocket->Connect(m_DataSocketIPAddress, m_nDataSocketPort) < 0) + { + SendStatus(TFTPStatus::DataConnectionFailed, "Could not connect to data port."); + delete pDataSocket; + pDataSocket = nullptr; + } + } + + // Passive: Use previously-created socket and accept connection from client + else if (m_TransferMode == TTransferMode::Passive && m_pDataSocket != nullptr) + { + CIPAddress ClientIPAddress; + u16 nClientPort; + pDataSocket = m_pDataSocket->Accept(&ClientIPAddress, &nClientPort); + } + + --nRetries; + } + + if (pDataSocket == nullptr) + { + LOGERR("Unable to open data socket after %d attempts", NumRetries); + SendStatus(TFTPStatus::DataConnectionFailed, "Couldn't open data connection."); + } + + return pDataSocket; +} + +bool CFTPWorker::SendStatus(TFTPStatus StatusCode, const char* pMessage) +{ + assert(m_pControlSocket != nullptr); + + const int nLength = snprintf(m_CommandBuffer, sizeof(m_CommandBuffer), "%d %s\r\n", StatusCode, pMessage); + if (m_pControlSocket->Send(m_CommandBuffer, nLength, 0) < 0) + { + LOGERR("Failed to send status"); + return false; + } +#ifdef FTPDAEMON_DEBUG + else + { + m_CommandBuffer[nLength - 2] = '\0'; + LOGDBG("--> Sent: '%s'", m_CommandBuffer); + } +#endif + + return true; +} + +bool CFTPWorker::CheckLoggedIn() +{ +#ifdef FTPDAEMON_DEBUG + LOGDBG("Username compare: expected '%s', actual '%s'", static_cast(m_pExpectedUser), static_cast(m_User)); + LOGDBG("Password compare: expected '%s', actual '%s'", static_cast(m_pExpectedPassword), static_cast(m_Password)); +#endif + + if (m_User.Compare(m_pExpectedUser) == 0 && m_Password.Compare(m_pExpectedPassword) == 0) + return true; + + SendStatus(TFTPStatus::NotLoggedIn, "Not logged in."); + return false; +} + +CString CFTPWorker::RealPath(const char* pInBuffer) const +{ + assert(pInBuffer != nullptr); + + CString Path; + const bool bAbsolute = pInBuffer[0] == '/'; + + if (bAbsolute) + { + char Buffer[TextBufferSize]; + FTPPathToFatFsPath(pInBuffer, Buffer, sizeof(Buffer)); + Path = Buffer; + } + else + Path.Format("%s/%s", static_cast(m_CurrentPath), pInBuffer); + + return Path; +} + +const TDirectoryListEntry* CFTPWorker::BuildDirectoryList(size_t& nOutEntries) const +{ + DIR Dir; + FILINFO FileInfo; + FRESULT Result; + + TDirectoryListEntry* pEntries = nullptr; + nOutEntries = 0; + + // Volume list + if (m_CurrentPath.GetLength() == 0) + { + constexpr size_t nVolumes = Utility::ArraySize(VolumeNames); + bool VolumesAvailable[nVolumes] = { false }; + + for (size_t i = 0; i < nVolumes; ++i) + { + char VolumeName[6]; + strncpy(VolumeName, VolumeNames[i], sizeof(VolumeName)); + strcat(VolumeName, ":"); + + // Returns FR_ + if ((Result = f_opendir(&Dir, VolumeName)) == FR_OK) + { + f_closedir(&Dir); + VolumesAvailable[i] = true; + ++nOutEntries; + } + } + + pEntries = new TDirectoryListEntry[nOutEntries]; + + size_t nCurrentEntry = 0; + for (size_t i = 0; i < nVolumes && nCurrentEntry < nOutEntries; ++i) + { + if (VolumesAvailable[i]) + { + TDirectoryListEntry& Entry = pEntries[nCurrentEntry++]; + strncpy(Entry.Name, VolumeNames[i], sizeof(Entry.Name)); + Entry.Type = TDirectoryListEntryType::Directory; + Entry.nSize = 0; + Entry.nLastModifedDate = 0; + Entry.nLastModifedTime = 0; + } + } + + return pEntries; + } + + // Directory list + Result = f_findfirst(&Dir, &FileInfo, m_CurrentPath, "*"); + if (Result == FR_OK && *FileInfo.fname) + { + // Count how many entries we need + do + { + ++nOutEntries; + Result = f_findnext(&Dir, &FileInfo); + } while (Result == FR_OK && *FileInfo.fname); + + f_closedir(&Dir); + + if (nOutEntries && (pEntries = new TDirectoryListEntry[nOutEntries])) + { + size_t nCurrentEntry = 0; + Result = f_findfirst(&Dir, &FileInfo, m_CurrentPath, "*"); + while (Result == FR_OK && *FileInfo.fname) + { + TDirectoryListEntry& Entry = pEntries[nCurrentEntry++]; + strncpy(Entry.Name, FileInfo.fname, sizeof(Entry.Name)); + + if (FileInfo.fattrib & AM_DIR) + { + Entry.Type = TDirectoryListEntryType::Directory; + Entry.nSize = 0; + } + else + { + Entry.Type = TDirectoryListEntryType::File; + Entry.nSize = FileInfo.fsize; + } + + Entry.nLastModifedDate = FileInfo.fdate; + Entry.nLastModifedTime = FileInfo.ftime; + + Result = f_findnext(&Dir, &FileInfo); + } + + f_closedir(&Dir); + + Utility::QSort(pEntries, DirectoryCaseInsensitiveAscending, 0, nOutEntries - 1); + } + } + + return pEntries; +} + +bool CFTPWorker::System(const char* pArgs) +{ + // Some FTP clients (e.g. Directory Opus) will only attempt to parse LIST responses as IIS/DOS-style if we pretend to be Windows NT + SendStatus(TFTPStatus::SystemType, "Windows_NT"); + return true; +} + +bool CFTPWorker::Username(const char* pArgs) +{ + m_User = pArgs; + char Buffer[TextBufferSize]; + snprintf(Buffer, sizeof(Buffer), "Password required for '%s'.", static_cast(m_User)); + SendStatus(TFTPStatus::PasswordRequired, Buffer); + return true; +} + +bool CFTPWorker::Port(const char* pArgs) +{ + if (!CheckLoggedIn()) + return false; + + char Buffer[TextBufferSize]; + strncpy(Buffer, pArgs, sizeof(Buffer)); + + if (m_pDataSocket != nullptr) + { + delete m_pDataSocket; + m_pDataSocket = nullptr; + } + + m_TransferMode = TTransferMode::Active; + + // TODO: PORT IP Address should match original IP address + + u8 PortBytes[6]; + char* pSavePtr; + char* pToken = strtok_r(Buffer, " ,", &pSavePtr); + bool bParseError = (pToken == nullptr); + + if (!bParseError) + { + PortBytes[0] = static_cast(atoi(pToken)); + + for (u8 i = 0; i < 5; ++i) + { + pToken = strtok_r(nullptr, " ,", &pSavePtr); + if (pToken == nullptr) + { + bParseError = true; + break; + } + + PortBytes[i + 1] = static_cast(atoi(pToken)); + } + } + + if (bParseError) + { + SendStatus(TFTPStatus::SyntaxError, "Syntax error."); + return false; + } + + m_DataSocketIPAddress.Set(PortBytes); + m_nDataSocketPort = (PortBytes[4] << 8) + PortBytes[5]; + +#ifdef FTPDAEMON_DEBUG + CString IPAddressString; + m_DataSocketIPAddress.Format(&IPAddressString); + LOGDBG("PORT set to: %s:%d", static_cast(IPAddressString), m_nDataSocketPort); +#endif + + SendStatus(TFTPStatus::Success, "Command OK."); + return true; +} + +bool CFTPWorker::Passive(const char* pArgs) +{ + if (!CheckLoggedIn()) + return false; + + if (m_pDataSocket == nullptr) + { + m_TransferMode = TTransferMode::Passive; + m_nDataSocketPort = PassivePortBase + s_nInstanceCount - 1; + + CNetSubSystem* const pNet = CNetSubSystem::Get(); + m_pDataSocket = new CSocket(pNet, IPPROTO_TCP); + + if (m_pDataSocket == nullptr) + { + SendStatus(TFTPStatus::ServiceNotAvailable, "Failed to open port for passive mode."); + return false; + } + + if (m_pDataSocket->Bind(m_nDataSocketPort) < 0) + { + SendStatus(TFTPStatus::DataConnectionFailed, "Could not bind to data port."); + delete m_pDataSocket; + m_pDataSocket = nullptr; + return false; + } + + if (m_pDataSocket->Listen() < 0) + { + SendStatus(TFTPStatus::DataConnectionFailed, "Could not listen on data port."); + delete m_pDataSocket; + m_pDataSocket = nullptr; + return false; + } + } + + u8 IPAddress[IP_ADDRESS_SIZE]; + CNetSubSystem::Get()->GetConfig()->GetIPAddress()->CopyTo(IPAddress); + + char Buffer[TextBufferSize]; + snprintf(Buffer, sizeof(Buffer), "Entering passive mode (%d,%d,%d,%d,%d,%d).", + IPAddress[0], + IPAddress[1], + IPAddress[2], + IPAddress[3], + (m_nDataSocketPort >> 8) & 0xFF, + m_nDataSocketPort & 0xFF + ); + + SendStatus(TFTPStatus::EnteringPassiveMode, Buffer); + return true; +} + +bool CFTPWorker::Password(const char* pArgs) +{ + if (m_User.GetLength() == 0) + { + SendStatus(TFTPStatus::AccountRequired, "Need account for login."); + return false; + } + + m_Password = pArgs; + + if (!CheckLoggedIn()) + return false; + + SendStatus(TFTPStatus::UserLoggedIn, "User logged in."); + return true; +} + +bool CFTPWorker::Type(const char* pArgs) +{ + if (!CheckLoggedIn()) + return false; + + if (strcasecmp(pArgs, "A") == 0) + { + m_DataType = TDataType::ASCII; + SendStatus(TFTPStatus::Success, "Type set to ASCII."); + return true; + } + + if (strcasecmp(pArgs, "I") == 0) + { + m_DataType = TDataType::Binary; + SendStatus(TFTPStatus::Success, "Type set to binary."); + return true; + } + + SendStatus(TFTPStatus::SyntaxError, "Syntax error."); + return false; +} + +bool CFTPWorker::Retrieve(const char* pArgs) +{ + if (!CheckLoggedIn()) + return false; + + FIL File; + CString Path = RealPath(pArgs); + + if (f_open(&File, Path, FA_READ) != FR_OK) + { + SendStatus(TFTPStatus::FileActionNotTaken, "Could not open file for reading."); + return false; + } + + if (!SendStatus(TFTPStatus::FileStatusOk, "Command OK.")) + return false; + + CSocket* pDataSocket = OpenDataConnection(); + if (pDataSocket == nullptr) + return false; + + size_t nSize = f_size(&File); + size_t nSent = 0; + + while (nSent < nSize) + { + UINT nBytesRead; +#ifdef FTPDAEMON_DEBUG + LOGDBG("Sending data"); +#endif + if (f_read(&File, m_DataBuffer, sizeof(m_DataBuffer), &nBytesRead) != FR_OK || pDataSocket->Send(m_DataBuffer, nBytesRead, 0) < 0) + { + delete pDataSocket; + f_close(&File); + SendStatus(TFTPStatus::ActionAborted, "File action aborted, local error."); + return false; + } + + nSent += nBytesRead; + assert(nSent <= nSize); + } + + delete pDataSocket; + f_close(&File); + SendStatus(TFTPStatus::TransferComplete, "Transfer complete."); + + return false; +} + +bool CFTPWorker::Store(const char* pArgs) +{ + if (!CheckLoggedIn()) + return false; + + FIL File; + CString Path = RealPath(pArgs); + + if (f_open(&File, Path, FA_CREATE_ALWAYS | FA_WRITE) != FR_OK) + { + SendStatus(TFTPStatus::FileActionNotTaken, "Could not open file for writing."); + return false; + } + + f_sync(&File); + + if (!SendStatus(TFTPStatus::FileStatusOk, "Command OK.")) + return false; + + CSocket* pDataSocket = OpenDataConnection(); + if (pDataSocket == nullptr) + return false; + + bool bSuccess = true; + + CTimer* const pTimer = CTimer::Get(); + unsigned int nTimeout = pTimer->GetTicks(); + + while (true) + { +#ifdef FTPDAEMON_DEBUG + LOGDBG("Waiting to receive"); +#endif + int nReceiveResult = pDataSocket->Receive(m_DataBuffer, sizeof(m_DataBuffer), MSG_DONTWAIT); + FRESULT nWriteResult; + UINT nWritten; + + if (nReceiveResult == 0) + { + if (pTimer->GetTicks() - nTimeout >= SocketTimeout * HZ) + { + LOGERR("Socket timed out"); + bSuccess = false; + break; + } + CScheduler::Get()->Yield(); + continue; + } + + // All done + if (nReceiveResult < 0) + { + LOGNOTE("Receive done, no more data"); + break; + } + +#ifdef FTPDAEMON_DEBUG + //LOGDBG("Received %d bytes", nReceiveResult); +#endif + + if ((nWriteResult = f_write(&File, m_DataBuffer, nReceiveResult, &nWritten)) != FR_OK) + { + LOGERR("Write FAILED, return code %d", nWriteResult); + bSuccess = false; + break; + } + + f_sync(&File); + CScheduler::Get()->Yield(); + + nTimeout = pTimer->GetTicks(); + } + + if (bSuccess) + SendStatus(TFTPStatus::TransferComplete, "Transfer complete."); + else + SendStatus(TFTPStatus::ActionAborted, "File action aborted, local error."); + +#ifdef FTPDAEMON_DEBUG + LOGDBG("Closing socket/file"); +#endif + delete pDataSocket; + f_close(&File); + + return true; +} + +bool CFTPWorker::Delete(const char* pArgs) +{ + if (!CheckLoggedIn()) + return false; + + CString Path = RealPath(pArgs); + + if (f_unlink(Path) != FR_OK) + SendStatus(TFTPStatus::FileActionNotTaken, "File was not deleted."); + else + SendStatus(TFTPStatus::FileActionOk, "File deleted."); + + return true; +} + +bool CFTPWorker::MakeDirectory(const char* pArgs) +{ + if (!CheckLoggedIn()) + return false; + + CString Path = RealPath(pArgs); + + if (f_mkdir(Path) != FR_OK) + SendStatus(TFTPStatus::FileActionNotTaken, "Directory creation failed."); + else + { + char Buffer[TextBufferSize]; + FatFsPathToFTPPath(Path, Buffer, sizeof(Buffer)); + strcat(Buffer, " directory created."); + SendStatus(TFTPStatus::PathCreated, Buffer); + } + + return true; +} + +bool CFTPWorker::ChangeWorkingDirectory(const char* pArgs) +{ + if (!CheckLoggedIn()) + return false; + + char Buffer[TextBufferSize]; + bool bSuccess = false; + + const bool bAbsolute = pArgs[0] == '/'; + if (bAbsolute) + { + // Root + if (pArgs[1] == '\0') + { + m_CurrentPath = ""; + bSuccess = true; + } + else + { + DIR Dir; + FTPPathToFatFsPath(pArgs, Buffer, sizeof(Buffer)); + + // f_stat() will fail if we're trying to CWD to the root of a volume, so use f_opendir() + if (f_opendir(&Dir, Buffer) == FR_OK) + { + f_closedir(&Dir); + m_CurrentPath = Buffer; + bSuccess = true; + } + } + } + else + { + const bool bAtRoot = m_CurrentPath.GetLength() == 0; + if (bAtRoot) + { + if (ValidateVolumeName(pArgs)) + { + m_CurrentPath.Format("%s:", pArgs); + bSuccess = true; + } + } + else + { + CString NewPath; + NewPath.Format("%s/%s", static_cast(m_CurrentPath), pArgs); + + if (f_stat(NewPath, nullptr) == FR_OK) + { + m_CurrentPath = NewPath; + bSuccess = true; + } + } + } + + if (bSuccess) + { + const bool bAtRoot = m_CurrentPath.GetLength() == 0; + if (bAtRoot) + strncpy(Buffer, "\"/\"", sizeof(Buffer)); + else + FatFsPathToFTPPath(m_CurrentPath, Buffer, sizeof(Buffer)); + SendStatus(TFTPStatus::FileActionOk, Buffer); + } + else + SendStatus(TFTPStatus::FileNotFound, "Directory unavailable."); + + return bSuccess; +} + +bool CFTPWorker::ChangeToParentDirectory(const char* pArgs) +{ + if (!CheckLoggedIn()) + return false; + + char Buffer[TextBufferSize]; + bool bSuccess = false; + bool bAtRoot = m_CurrentPath.GetLength() == 0; + + if (!bAtRoot) + { + DIR Dir; + FatFsParentPath(m_CurrentPath, Buffer, sizeof(Buffer)); + + bAtRoot = Buffer[0] == '\0'; + if (bAtRoot) + { + m_CurrentPath = Buffer; + bSuccess = true; + } + else if (f_opendir(&Dir, Buffer) == FR_OK) + { + f_closedir(&Dir); + m_CurrentPath = Buffer; + bSuccess = true; + } + } + + if (bSuccess) + { + bAtRoot = m_CurrentPath.GetLength() == 0; + if (bAtRoot) + strncpy(Buffer, "\"/\"", sizeof(Buffer)); + else + FatFsPathToFTPPath(m_CurrentPath, Buffer, sizeof(Buffer)); + SendStatus(TFTPStatus::FileActionOk, Buffer); + } + else + SendStatus(TFTPStatus::FileNotFound, "Directory unavailable."); + + return false; +} + +bool CFTPWorker::PrintWorkingDirectory(const char* pArgs) +{ + if (!CheckLoggedIn()) + return false; + + char Buffer[TextBufferSize]; + + const bool bAtRoot = m_CurrentPath.GetLength() == 0; + if (bAtRoot) + strncpy(Buffer, "\"/\"", sizeof(Buffer)); + else + FatFsPathToFTPPath(m_CurrentPath, Buffer, sizeof(Buffer)); + + SendStatus(TFTPStatus::PathCreated, Buffer); + + return true; +} + +bool CFTPWorker::List(const char* pArgs) +{ + if (!CheckLoggedIn()) + return false; + + if (!SendStatus(TFTPStatus::FileStatusOk, "Command OK.")) + return false; + + CSocket* pDataSocket = OpenDataConnection(); + if (pDataSocket == nullptr) + return false; + + char Buffer[TextBufferSize]; + char Date[9]; + char Time[8]; + + size_t nEntries; + const TDirectoryListEntry* pDirEntries = BuildDirectoryList(nEntries); + + if (pDirEntries) + { + for (size_t i = 0; i < nEntries; ++i) + { + const TDirectoryListEntry& Entry = pDirEntries[i]; + int nLength; + + // Mimic the Microsoft IIS LIST format + FormatLastModifiedDate(Entry.nLastModifedDate, Date, sizeof(Date)); + FormatLastModifiedTime(Entry.nLastModifedTime, Time, sizeof(Time)); + + if (Entry.Type == TDirectoryListEntryType::Directory) + nLength = snprintf(Buffer, sizeof(Buffer), "%-9s %-13s %-14s %s\r\n", Date, Time, "", Entry.Name); + else + nLength = snprintf(Buffer, sizeof(Buffer), "%-9s %-13s %14d %s\r\n", Date, Time, Entry.nSize, Entry.Name); + + if (pDataSocket->Send(Buffer, nLength, 0) < 0) + { + delete[] pDirEntries; + delete pDataSocket; + SendStatus(TFTPStatus::DataConnectionFailed, "Transfer error."); + return false; + } + } + + delete[] pDirEntries; + } + + delete pDataSocket; + SendStatus(TFTPStatus::TransferComplete, "Transfer complete."); + return true; +} + +bool CFTPWorker::ListFileNames(const char* pArgs) +{ + if (!CheckLoggedIn()) + return false; + + if (!SendStatus(TFTPStatus::FileStatusOk, "Command OK.")) + return false; + + CSocket* pDataSocket = OpenDataConnection(); + if (pDataSocket == nullptr) + return false; + + char Buffer[TextBufferSize]; + size_t nEntries; + const TDirectoryListEntry* pDirEntries = BuildDirectoryList(nEntries); + + if (pDirEntries) + { + for (size_t i = 0; i < nEntries; ++i) + { + const TDirectoryListEntry& Entry = pDirEntries[i]; + if (Entry.Type == TDirectoryListEntryType::Directory) + continue; + + const int nLength = snprintf(Buffer, sizeof(Buffer), "%s\r\n", Entry.Name); + if (pDataSocket->Send(Buffer, nLength, 0) < 0) + { + delete[] pDirEntries; + delete pDataSocket; + SendStatus(TFTPStatus::DataConnectionFailed, "Transfer error."); + return false; + } + } + + delete[] pDirEntries; + } + + delete pDataSocket; + SendStatus(TFTPStatus::TransferComplete, "Transfer complete."); + return true; +} + +bool CFTPWorker::RenameFrom(const char* pArgs) +{ + if (!CheckLoggedIn()) + return false; + + m_RenameFrom = pArgs; + SendStatus(TFTPStatus::PendingFurtherInfo, "Requested file action pending further information."); + + return false; +} + +bool CFTPWorker::RenameTo(const char* pArgs) +{ + if (!CheckLoggedIn()) + return false; + + if (m_RenameFrom.GetLength() == 0) + { + SendStatus(TFTPStatus::BadCommandSequence, "Bad sequence of commands."); + return false; + } + + CString SourcePath = RealPath(m_RenameFrom); + CString DestPath = RealPath(pArgs); + + if (f_rename(SourcePath, DestPath) != FR_OK) + SendStatus(TFTPStatus::FileNameNotAllowed, "File name not allowed."); + else + SendStatus(TFTPStatus::FileActionOk, "File renamed."); + + m_RenameFrom = ""; + + return false; +} + +bool CFTPWorker::Bye(const char* pArgs) +{ + SendStatus(TFTPStatus::ClosingControl, "Goodbye."); + delete m_pControlSocket; + m_pControlSocket = nullptr; + return true; +} + +bool CFTPWorker::NoOp(const char* pArgs) +{ + SendStatus(TFTPStatus::Success, "Command OK."); + return true; +} + +void CFTPWorker::FatFsPathToFTPPath(const char* pInBuffer, char* pOutBuffer, size_t nSize) +{ + assert(pOutBuffer && nSize > 2); + const char* pEnd = pOutBuffer + nSize; + const char* pInChar = pInBuffer; + char* pOutChar = pOutBuffer; + + *pOutChar++ = '"'; + *pOutChar++ = '/'; + + while (*pInChar != '\0' && pOutChar < pEnd) + { + // Kill the volume colon + if (*pInChar == ':') + { + *pOutChar++ = '/'; + ++pInChar; + + // Kill any slashes after the colon + while (*pInChar == '/') ++pInChar; + continue; + } + + // Kill duplicate slashes + if (*pInChar == '/') + { + *pOutChar++ = *pInChar++; + while (*pInChar == '/') ++pInChar; + continue; + } + + *pOutChar++ = *pInChar++; + } + + // Kill trailing slash + if (*(pOutChar - 1) == '/') + --pOutChar; + + assert(pOutChar < pEnd - 2); + *pOutChar++ = '"'; + *pOutChar++ = '\0'; +} + +void CFTPWorker::FTPPathToFatFsPath(const char* pInBuffer, char* pOutBuffer, size_t nSize) +{ + assert(pInBuffer && pOutBuffer); + const char* pEnd = pOutBuffer + nSize; + const char* pInChar = pInBuffer; + char* pOutChar = pOutBuffer; + + // Kill leading slashes + while (*pInChar == '/') ++pInChar; + + bool bGotVolume = false; + while (*pInChar != '\0' && pOutChar < pEnd) + { + // Kill the volume colon + if (!bGotVolume && *pInChar == '/') + { + bGotVolume = true; + *pOutChar++ = ':'; + ++pInChar; + + // Kill any slashes after the colon + while (*pInChar == '/') ++pInChar; + continue; + } + + // Kill duplicate slashes + if (*pInChar == '/') + { + *pOutChar++ = *pInChar++; + while (*pInChar == '/') ++pInChar; + continue; + } + + *pOutChar++ = *pInChar++; + } + + assert(pOutChar < pEnd - 2); + + // Kill trailing slash + if (*(pOutChar - 1) == '/') + --pOutChar; + + // Add volume colon + if (!bGotVolume) + *pOutChar++ = ':'; + + *pOutChar++ = '\0'; +} + +void CFTPWorker::FatFsParentPath(const char* pInBuffer, char* pOutBuffer, size_t nSize) +{ + assert(pInBuffer != nullptr && pOutBuffer != nullptr); + + size_t nLength = strlen(pInBuffer); + assert(nLength > 0 && nSize >= nLength); + + const char* pLastChar = pInBuffer + nLength - 1; + const char* pInChar = pLastChar; + + // Kill trailing slashes + while (*pInChar == '/' && pInChar > pInBuffer) --pInChar; + + // Kill subdirectory name + while (*pInChar != '/' && *pInChar != ':' && pInChar > pInBuffer) --pInChar; + + // Kill trailing slashes + while (*pInChar == '/' && pInChar > pInBuffer) --pInChar; + + // Pointer didn't move (we're already at a volume root), or we reached the start of the string (path invalid) + if (pInChar == pLastChar || pInChar == pInBuffer) + { + *pOutBuffer = '\0'; + return; + } + + // Truncate string + nLength = pInChar - pInBuffer + 1; + memcpy(pOutBuffer, pInBuffer, nLength); + pOutBuffer[nLength] = '\0'; +} + +void CFTPWorker::FormatLastModifiedDate(u16 nDate, char* pOutBuffer, size_t nSize) +{ + // 2-digit year + const u16 nYear = (1980 + (nDate >> 9)) % 100; + u16 nMonth = (nDate >> 5) & 0x0F; + u16 nDay = nDate & 0x1F; + + if (nMonth == 0) + nMonth = 1; + if (nDay == 0) + nDay = 1; + + snprintf(pOutBuffer, nSize, "%02d-%02d-%02d", nMonth, nDay, nYear); +} + +void CFTPWorker::FormatLastModifiedTime(u16 nDate, char* pOutBuffer, size_t nSize) +{ + u16 nHour = (nDate >> 11) & 0x1F; + const u16 nMinute = (nDate >> 5) & 0x3F; + const char* pSuffix = nHour < 12 ? "AM" : "PM"; + + if (nHour == 0) + nHour = 12; + else if (nHour >= 12) + nHour -= 12; + + snprintf(pOutBuffer, nSize, "%02d:%02d%s", nHour, nMinute, pSuffix); +} \ No newline at end of file diff --git a/src/net/ftpworker.h b/src/net/ftpworker.h new file mode 100644 index 0000000..62e60ed --- /dev/null +++ b/src/net/ftpworker.h @@ -0,0 +1,157 @@ +// +// ftpworker.h +// +// mt32-pi - A baremetal MIDI synthesizer for Raspberry Pi +// Copyright (C) 2020-2023 Dale Whinham +// +// 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 . +// + +#ifndef _ftpworker_h +#define _ftpworker_h + +#include +#include +#include +#include + +// 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 \ No newline at end of file diff --git a/src/net/utility.h b/src/net/utility.h new file mode 100644 index 0000000..3b64395 --- /dev/null +++ b/src/net/utility.h @@ -0,0 +1,193 @@ + +// +// utility.h +// +// mt32-pi - A baremetal MIDI synthesizer for Raspberry Pi +// Copyright (C) 2020-2023 Dale Whinham +// +// 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 . +// + +#ifndef _utility_h +#define _utility_h + +#include +#include + +// 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 + 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 + constexpr T Min(const T& nLHS, const T& nRHS) + { + return nLHS < nRHS ? nLHS : nRHS; + } + + // Templated function for taking the maximum of two values + template + 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 + constexpr size_t ArraySize(const T(&)[N]) { return N; } + + // Returns whether some value is a power of 2 + template + 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 + 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 + constexpr T MillisToTicks(const T& nMillis) + { + return nMillis * 1000; + } + + template + 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 + using TComparator = bool (*)(const T&, const T&); + + template + inline bool LessThan(const T& ObjectA, const T& ObjectB) + { + return ObjectA < ObjectB; + } + + template + 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 + 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 + size_t Partition(T* Items, Comparator::TComparator 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 + void QSort(T* Items, Comparator::TComparator 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 diff --git a/src/udpmididevice.cpp b/src/udpmididevice.cpp new file mode 100644 index 0000000..97355b7 --- /dev/null +++ b/src/udpmididevice.cpp @@ -0,0 +1,106 @@ +// +// udpmididevice.cpp +// +// MiniDexed - Dexed FM synthesizer for bare metal Raspberry Pi +// Copyright (C) 2022 The MiniDexed Team +// +// Original author of this class: +// R. Stange +// +// 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 . +// + +#include +#include +#include "udpmididevice.h" +#include + +//#define VIRTUALCABLE 24 + +LOGMODULE("rtpmididevice"); + +CUDPMIDIDevice::CUDPMIDIDevice (CMiniDexed *pSynthesizer, + CConfig *pConfig, CUserInterface *pUI) +: CMIDIDevice (pSynthesizer, pConfig, pUI), + m_pSynthesizer (pSynthesizer), + m_pConfig (pConfig) + + + //m_Serial (pInterrupt, TRUE), + //m_nSerialState (0), + //m_nSysEx (0), + //m_SendBuffer (&m_Serial) +{ + AddDevice ("udp"); + /*for (unsigned nTG = 0; nTG < CConfig::ToneGenerators; nTG++) + { + m_ChannelMap[nTG] = Disabled; + }*/ +} + +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) +{ + LOGNOTE("Recieved RTPUDP MIDI Data"); + printf ("MIDI-RTP: %02X %02X\n", + (unsigned) pData[0], (unsigned) pData[1]); + MIDIMessageHandler(pData, nSize); +} + +void CUDPMIDIDevice::OnAppleMIDIConnect(const CIPAddress* pIPAddress, const char* pName) +{ + LOGNOTE("RTP Device connected"); + //AddDevice ("udp1"); +} + +void CUDPMIDIDevice::OnAppleMIDIDisconnect(const CIPAddress* pIPAddress, const char* pName) +{ + LOGNOTE("RTP Device disconnected"); +} + +void CUDPMIDIDevice::OnUDPMIDIDataReceived(const u8* pData, size_t nSize) +{ + LOGNOTE("Recieved UDP MIDI Data"); + printf ("MIDI-UDP: %02X %02X\n", + (unsigned) pData[0], (unsigned) pData[1]); + MIDIMessageHandler(pData, nSize); +} \ No newline at end of file diff --git a/src/udpmididevice.h b/src/udpmididevice.h new file mode 100644 index 0000000..e0119e1 --- /dev/null +++ b/src/udpmididevice.h @@ -0,0 +1,76 @@ +// +// 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 +// +// 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 . +// +#ifndef _rtpmididevice_h +#define _rtpmididevice_h + +#include "mididevice.h" +#include "config.h" +#include "net/applemidi.h" +#include "net/udpmidi.h" + +//#include +//#include +//#include +//#include + +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; + //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: + CMiniDexed *m_pSynthesizer; + CConfig *m_pConfig; + //u8 m_ChannelMap[CConfig::ToneGenerators]; + //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 + CUDPMIDIReceiver* m_pUDPMIDIReceiver; + +}; + +#endif From 2046d81d589d630c5de6936f4e9079e16a9ff73a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20=C5=9Eiar=20Baysal?= Date: Wed, 6 Nov 2024 14:49:31 +0100 Subject: [PATCH 04/13] init network after minidexed starts --- build.sh | 9 ++++- src/circle_stdlib_app.h | 1 + src/config.cpp | 38 +++++++++++++++++- src/config.h | 13 ++++++ src/kernel.cpp | 9 +++-- src/kernel.h | 4 +- src/mididevice.cpp | 12 ------ src/minidexed.cpp | 87 +++++++++++++++++++++++------------------ src/minidexed.h | 15 +++++-- src/net/applemidi.cpp | 2 +- src/net/ftpworker.cpp | 4 +- src/udpmididevice.cpp | 14 ++----- 12 files changed, 135 insertions(+), 73 deletions(-) diff --git a/build.sh b/build.sh index 25d9dfc..c2122be 100755 --- a/build.sh +++ b/build.sh @@ -14,12 +14,19 @@ else export TOOLCHAIN_PREFIX="arm-none-eabi-" fi +SDHOST=$([ "${RPI}" == 3 ] && echo "" || echo "") + # Define system options -OPTIONS="-o USE_PWM_AUDIO_ON_ZERO -o SAVE_VFP_REGS_ON_IRQ -o REALTIME -o USE_SDHOST -o SCREEN_DMA_BURST_LENGTH=1" +OPTIONS="-o USE_PWM_AUDIO_ON_ZERO -o SAVE_VFP_REGS_ON_IRQ -o REALTIME -o SCREEN_DMA_BURST_LENGTH=1" if [ "${RPI}" -gt "1" ]; then OPTIONS="${OPTIONS} -o ARM_ALLOW_MULTI_CORE" 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 source USBID.sh if [ "${USB_VID}" ] ; then diff --git a/src/circle_stdlib_app.h b/src/circle_stdlib_app.h index 8a69c82..bf4534a 100644 --- a/src/circle_stdlib_app.h +++ b/src/circle_stdlib_app.h @@ -223,6 +223,7 @@ protected: CEMMCDevice mEMMC; FATFS mFileSystem; CConsole mConsole; + CScheduler mScheduler; }; /** diff --git a/src/config.cpp b/src/config.cpp index ac6a2e4..ca958ef 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -155,7 +155,13 @@ void CConfig::Load (void) // Network m_bNetworkEnabled = m_Properties.GetNumber ("NetworkEnabled", 0) != 0; - m_NetworkType = m_Properties.GetString ("NetworkType", ""); + 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; } bool CConfig::GetUSBGadgetMode (void) const @@ -514,7 +520,37 @@ 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; } \ No newline at end of file diff --git a/src/config.h b/src/config.h index 30ee5ed..794b9ce 100644 --- a/src/config.h +++ b/src/config.h @@ -23,6 +23,7 @@ #ifndef _config_h #define _config_h +#include #include #include #include @@ -171,6 +172,12 @@ public: // 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: CPropertiesFatFsFile m_Properties; @@ -259,7 +266,13 @@ private: // 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 diff --git a/src/kernel.cpp b/src/kernel.cpp index 5c7a7a9..8874cf7 100644 --- a/src/kernel.cpp +++ b/src/kernel.cpp @@ -32,12 +32,13 @@ CKernel *CKernel::s_pThis = 0; CKernel::CKernel (void) : - //CStdlibAppStdio ("minidexed"), - CStdlibAppNetwork ("minidexed", CSTDLIBAPP_DEFAULT_PARTITION, - 0, 0, 0, 0, NET_DEVICE_TYPE), + CStdlibAppStdio ("minidexed"), + //CStdlibAppNetwork ("minidexed", CSTDLIBAPP_DEFAULT_PARTITION, + // 0, 0, 0, 0, NET_DEVICE_TYPE), m_Config (&mFileSystem), m_GPIOManager (&mInterrupt), m_I2CMaster (CMachineInfo::Get ()->GetDevice (DeviceI2CMaster), TRUE), + //m_Scheduler(), m_pDexed (0) { s_pThis = this; @@ -52,7 +53,7 @@ CKernel::~CKernel(void) bool CKernel::Initialize (void) { - if (!CStdlibAppNetwork::Initialize ()) + if (!CStdlibAppStdio::Initialize ()) { return FALSE; } diff --git a/src/kernel.h b/src/kernel.h index 000985b..f5e080e 100644 --- a/src/kernel.h +++ b/src/kernel.h @@ -25,6 +25,7 @@ #include #include #include +#include #include "config.h" #include "minidexed.h" @@ -35,7 +36,7 @@ enum TShutdownMode ShutdownReboot }; -class CKernel : public CStdlibAppNetwork +class CKernel : public CStdlibAppStdio { public: CKernel (void); @@ -54,6 +55,7 @@ private: CCPUThrottle m_CPUThrottle; CGPIOManager m_GPIOManager; CI2CMaster m_I2CMaster; + //CScheduler m_Scheduler; CMiniDexed *m_pDexed; CUSBController *m_pUSB; diff --git a/src/mididevice.cpp b/src/mididevice.cpp index 917f674..cc5a79c 100644 --- a/src/mididevice.cpp +++ b/src/mididevice.cpp @@ -169,7 +169,6 @@ void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsign } m_MIDISpinLock.Acquire (); - printf ("MIDI-DEBUG: SPINLOCK ACQUIRED\n"); u8 ucStatus = pMessage[0]; u8 ucChannel = ucStatus & 0x0F; @@ -205,7 +204,6 @@ void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsign { if ((ucChannel == nPerfCh) || (nPerfCh == OmniMode)) { - //printf("Performance Select Channel %d\n", nPerfCh); m_pSynthesizer->ProgramChangePerformance (pMessage[1]); } } @@ -214,10 +212,8 @@ void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsign } // Process MIDI for each Tone Generator - printf ("MIDI-DEBUG: EACH TONEGENERATOR LOOP\n"); for (unsigned nTG = 0; nTG < CConfig::ToneGenerators; nTG++) { - printf ("%u TONE GENERATOR", nTG); if (ucStatus == MIDI_SYSTEM_EXCLUSIVE_BEGIN) { // MIDI SYSEX per MIDI channel @@ -230,15 +226,12 @@ void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsign } else { - printf ("NOT AN SYSEX"); - if ( m_ChannelMap[nTG] == ucChannel || m_ChannelMap[nTG] == OmniMode) { switch (ucType) { case MIDI_NOTE_ON: - printf ("MIDI-DEBUG: CASE MIDI NOTE ON\n"); if (nLength < 3) { break; @@ -248,15 +241,12 @@ void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsign { if (pMessage[2] <= 127) { - printf ("MIDI-DEBUG: KEYDOWN EVENT\n"); m_pSynthesizer->keydown (pMessage[1], pMessage[2], nTG); } } else { - printf ("MIDI-DEBUG: KEYUP EVENT\n"); - //printf ("MIDI-RTP: %02X\n", m_pSynthesizer); m_pSynthesizer->keyup (pMessage[1], nTG); } break; @@ -266,7 +256,6 @@ void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsign { break; } - printf ("MIDI-DEBUG: MIDI NOTE OFF\n"); m_pSynthesizer->keyup (pMessage[1], nTG); break; @@ -388,7 +377,6 @@ void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsign } } m_MIDISpinLock.Release (); - printf ("MIDI-DEBUG: SPINLOCK RELEASED\n"); } void CMIDIDevice::AddDevice (const char *pDeviceName) diff --git a/src/minidexed.cpp b/src/minidexed.cpp index 7e0c930..1c0c89f 100644 --- a/src/minidexed.cpp +++ b/src/minidexed.cpp @@ -27,15 +27,14 @@ #include #include #include -#include +//#include //#include //#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"; #define FTPUSERNAME "admin" #define FTPPASSWORD "admin" /* @@ -78,6 +77,10 @@ CMiniDexed::CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt, m_bNetworkReady(false), */ //CNetSubSystem* const pNet = CNetSubSystem::Get(); + m_pNet(nullptr), + m_pNetDevice(nullptr), + m_WLAN(WLANFirmwarePath), + m_WPASupplicant(WLANConfigFile), m_bNetworkReady(false), m_UDPMIDI (this, pConfig, &m_UI) { @@ -292,19 +295,10 @@ bool CMiniDexed::Initialize (void) return false; } #endif - //InitNetwork(); + InitNetwork(); //CMIDIDevice->InitializeRTP(); - 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"); return true; } @@ -1861,18 +1855,18 @@ void CMiniDexed::UpdateNetwork() }*/ { - CNetSubSystem* const pNet = CNetSubSystem::Get(); - if (!pNet) + //CNetSubSystem* const pNet = CNetSubSystem::Get(); + if (!m_pNet) return; - bool bNetIsRunning = pNet->IsRunning(); - //bNetIsRunning &= m_WPASupplicant.IsConnected(); + bool bNetIsRunning = m_pNet->IsRunning(); + bNetIsRunning &= m_WPASupplicant.IsConnected(); - if (!m_bNetworkReady) + if (!m_bNetworkReady && bNetIsRunning) { m_bNetworkReady = true; CString IPString; - pNet->GetConfig()->GetIPAddress()->Format(&IPString); + m_pNet->GetConfig()->GetIPAddress()->Format(&IPString); LOGNOTE("Network up and running at: %s", static_cast(IPString)); @@ -1880,6 +1874,15 @@ void CMiniDexed::UpdateNetwork() { LOGNOTE ("RTP MIDI interface enabled"); } + 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"); } else if (m_bNetworkReady && !bNetIsRunning) { @@ -1914,13 +1917,13 @@ void CMiniDexed::UpdateNetwork() m_bNetworkReady = false; LOGNOTE("Network disconnected."); } -} +}*/ bool CMiniDexed::InitNetwork() { assert(m_pNet == nullptr); - TNetDeviceType NetDeviceType = NetDeviceTypeWLAN; + TNetDeviceType NetDeviceType = NetDeviceTypeUnknown; if (m_pConfig->GetNetworkEnabled () && (strcmp(m_pConfig->GetNetworkType(), "wifi") == 0)) { @@ -1929,7 +1932,7 @@ bool CMiniDexed::InitNetwork() if (m_WLAN.Initialize() && m_WPASupplicant.Initialize()) { LOGNOTE("wlan and wpasupplicant initialized"); - //NetDeviceType = NetDeviceTypeWLAN; + NetDeviceType = NetDeviceTypeWLAN; } else @@ -1938,21 +1941,31 @@ bool CMiniDexed::InitNetwork() else if (m_pConfig->GetNetworkEnabled () && (strcmp(m_pConfig->GetNetworkType(), "ethernet") == 0)) { LOGNOTE("Initializing Ethernet"); - //NetDeviceType = NetDeviceTypeEthernet; + 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; + } - 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); - - + m_pNetDevice = CNetDevice::GetNetDevice(NetDeviceType); + } return m_pNet != nullptr; -} -*/ \ No newline at end of file +} \ No newline at end of file diff --git a/src/minidexed.h b/src/minidexed.h index 45c3797..d566e82 100644 --- a/src/minidexed.h +++ b/src/minidexed.h @@ -39,8 +39,9 @@ #include #include #include -////#include -//#include +#include +#include +#include #include #include "common.h" #include "effect_mixer.hpp" @@ -218,7 +219,7 @@ public: bool DoSavePerformance (void); void setMasterVolume (float32_t vol); - //bool InitNetwork(); + bool InitNetwork(); void UpdateNetwork(); private: @@ -319,7 +320,6 @@ private: unsigned m_nDeletePerformanceID; bool m_bLoadPerformanceBusy; bool m_bSaveAsDeault; - bool m_bNetworkReady; //CNetSubSystem* m_pNet; //CWPASupplicant m_WPASupplicant; // Networking @@ -332,8 +332,15 @@ private: bool m_bNetworkReady; CBcmRandomNumberGenerator m_Random; */ + // Networking + CNetSubSystem* m_pNet; + CNetDevice* m_pNetDevice; + CBcm4343Device m_WLAN; + CWPASupplicant m_WPASupplicant; + bool m_bNetworkReady; CUDPMIDIDevice m_UDPMIDI; CFTPDaemon* m_pFTPDaemon; + }; #endif diff --git a/src/net/applemidi.cpp b/src/net/applemidi.cpp index b484a07..3c96629 100644 --- a/src/net/applemidi.cpp +++ b/src/net/applemidi.cpp @@ -803,7 +803,7 @@ bool CAppleMIDIParticipant::SendAcceptInvitationPacket(CSocket* pSocket, CIPAddr }; // TODO: configurable name - strncpy(AcceptPacket.Name, "mt32-pi", sizeof(AcceptPacket.Name)); + strncpy(AcceptPacket.Name, "minidexed", sizeof(AcceptPacket.Name)); #ifdef APPLEMIDI_DEBUG LOGNOTE("--> Accept invitation"); diff --git a/src/net/ftpworker.cpp b/src/net/ftpworker.cpp index e3fd1f3..f61b07b 100644 --- a/src/net/ftpworker.cpp +++ b/src/net/ftpworker.cpp @@ -43,10 +43,10 @@ constexpr unsigned int SocketTimeout = 20; constexpr unsigned int NumRetries = 3; #ifndef MT32_PI_VERSION -#define MT32_PI_VERSION "(version unknown)" +#define MT32_PI_VERSION "alpha version" #endif -const char MOTDBanner[] = "Welcome to the mt32-pi " MT32_PI_VERSION " embedded FTP server!"; +const char MOTDBanner[] = "Welcome to the minidexed " MT32_PI_VERSION " embedded FTP server!"; enum class TDirectoryListEntryType { diff --git a/src/udpmididevice.cpp b/src/udpmididevice.cpp index 97355b7..f24d580 100644 --- a/src/udpmididevice.cpp +++ b/src/udpmididevice.cpp @@ -26,7 +26,7 @@ #include "udpmididevice.h" #include -//#define VIRTUALCABLE 24 +#define VIRTUALCABLE 24 LOGMODULE("rtpmididevice"); @@ -51,7 +51,7 @@ CUDPMIDIDevice::CUDPMIDIDevice (CMiniDexed *pSynthesizer, CUDPMIDIDevice::~CUDPMIDIDevice (void) { - m_pSynthesizer = 0; + //m_pSynthesizer = 0; } boolean CUDPMIDIDevice::Initialize (void) @@ -80,10 +80,7 @@ boolean CUDPMIDIDevice::Initialize (void) void CUDPMIDIDevice::OnAppleMIDIDataReceived(const u8* pData, size_t nSize) { - LOGNOTE("Recieved RTPUDP MIDI Data"); - printf ("MIDI-RTP: %02X %02X\n", - (unsigned) pData[0], (unsigned) pData[1]); - MIDIMessageHandler(pData, nSize); + MIDIMessageHandler(pData, nSize, VIRTUALCABLE); } void CUDPMIDIDevice::OnAppleMIDIConnect(const CIPAddress* pIPAddress, const char* pName) @@ -99,8 +96,5 @@ void CUDPMIDIDevice::OnAppleMIDIDisconnect(const CIPAddress* pIPAddress, const c void CUDPMIDIDevice::OnUDPMIDIDataReceived(const u8* pData, size_t nSize) { - LOGNOTE("Recieved UDP MIDI Data"); - printf ("MIDI-UDP: %02X %02X\n", - (unsigned) pData[0], (unsigned) pData[1]); - MIDIMessageHandler(pData, nSize); + MIDIMessageHandler(pData, nSize, VIRTUALCABLE); } \ No newline at end of file From 2ad3301129d746cf95e20da512005d0cfee40e0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20=C5=9Eiar=20Baysal?= Date: Wed, 6 Nov 2024 15:01:22 +0100 Subject: [PATCH 05/13] some cleanup --- .gitignore | 3 ++- build.sh | 2 -- src/minidexed.cpp | 50 +---------------------------------------------- src/minidexed.h | 17 +--------------- 4 files changed, 4 insertions(+), 68 deletions(-) diff --git a/.gitignore b/.gitignore index 93bd001..11bef8b 100644 --- a/.gitignore +++ b/.gitignore @@ -48,4 +48,5 @@ sdcard CMSIS_5/ Synth_Dexed/ -circle-stdlib/ \ No newline at end of file +circle-stdlib/ +.vscode/ \ No newline at end of file diff --git a/build.sh b/build.sh index c2122be..2db6d59 100755 --- a/build.sh +++ b/build.sh @@ -14,8 +14,6 @@ else export TOOLCHAIN_PREFIX="arm-none-eabi-" fi -SDHOST=$([ "${RPI}" == 3 ] && echo "" || echo "") - # Define system options OPTIONS="-o USE_PWM_AUDIO_ON_ZERO -o SAVE_VFP_REGS_ON_IRQ -o REALTIME -o SCREEN_DMA_BURST_LENGTH=1" if [ "${RPI}" -gt "1" ]; then diff --git a/src/minidexed.cpp b/src/minidexed.cpp index 1c0c89f..e4e5387 100644 --- a/src/minidexed.cpp +++ b/src/minidexed.cpp @@ -69,14 +69,7 @@ CMiniDexed::CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt, m_bSetNewPerformance (false), m_bDeletePerformance (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_pNet(nullptr), m_pNetDevice(nullptr), m_WLAN(WLANFirmwarePath), @@ -1840,20 +1833,6 @@ 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(IPString)); - } - if (!pNet) - return; - -}*/ - { //CNetSubSystem* const pNet = CNetSubSystem::Get(); if (!m_pNet) @@ -1891,33 +1870,6 @@ void CMiniDexed::UpdateNetwork() } } -/* -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(IPString)); - } - else if (m_bNetworkReady && !bNetIsRunning) - { - m_bNetworkReady = false; - LOGNOTE("Network disconnected."); - } -}*/ bool CMiniDexed::InitNetwork() { diff --git a/src/minidexed.h b/src/minidexed.h index d566e82..b28f9b4 100644 --- a/src/minidexed.h +++ b/src/minidexed.h @@ -47,9 +47,6 @@ #include "effect_mixer.hpp" #include "effect_platervbstereo.h" #include "effect_compressor.h" -//#include -//#include -//#include #include "udpmididevice.h" #include "net/ftpdaemon.h" @@ -320,19 +317,7 @@ private: unsigned m_nDeletePerformanceID; bool m_bLoadPerformanceBusy; bool m_bSaveAsDeault; - //CNetSubSystem* m_pNet; - //CWPASupplicant m_WPASupplicant; - // Networking - //CNetSubSystem &mNet; - /* - CNetSubSystem* m_pNet; - CNetDevice* m_pNetDevice; - CBcm4343Device m_WLAN; - CWPASupplicant m_WPASupplicant; - bool m_bNetworkReady; - CBcmRandomNumberGenerator m_Random; - */ - // Networking + CNetSubSystem* m_pNet; CNetDevice* m_pNetDevice; CBcm4343Device m_WLAN; From 8d5eef90b4b6b2adc6165c8346c5af5f346589e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20=C5=9Eiar=20Baysal?= Date: Thu, 7 Nov 2024 16:04:05 +0100 Subject: [PATCH 06/13] write IP address to LCD --- src/minidexed.cpp | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/minidexed.cpp b/src/minidexed.cpp index e4e5387..b9fb752 100644 --- a/src/minidexed.cpp +++ b/src/minidexed.cpp @@ -1853,15 +1853,25 @@ void CMiniDexed::UpdateNetwork() { LOGNOTE ("RTP MIDI interface enabled"); } - m_pFTPDaemon = new CFTPDaemon(FTPUSERNAME, FTPPASSWORD); + + 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"); + else + { + LOGNOTE("FTP daemon initialized"); + } + + m_UI.DisplayWrite ("IP", + "Network", + IPString, + 0, + 1); } else if (m_bNetworkReady && !bNetIsRunning) { From aeeceae12f0df76b610a46a25ef92e83fccf5172 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20=C5=9Eiar=20Baysal?= Date: Fri, 8 Nov 2024 10:28:28 +0100 Subject: [PATCH 07/13] some more cleanup --- .gitignore | 6 +++--- .gitmodules | 3 +++ src/minidexed.cpp | 18 ++---------------- src/udpmididevice.cpp | 11 ----------- src/udpmididevice.h | 20 -------------------- 5 files changed, 8 insertions(+), 50 deletions(-) diff --git a/.gitignore b/.gitignore index 11bef8b..6ad1469 100644 --- a/.gitignore +++ b/.gitignore @@ -46,7 +46,7 @@ sdcard *.swp *.swo -CMSIS_5/ -Synth_Dexed/ -circle-stdlib/ +CMSIS_5/** +Synth_Dexed/** +circle-stdlib/** .vscode/ \ No newline at end of file diff --git a/.gitmodules b/.gitmodules index 064ffe4..a70e358 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,9 +1,12 @@ [submodule "circle-stdlib"] path = circle-stdlib url = https://github.com/smuehlst/circle-stdlib + ignore = all [submodule "Synth_Dexed"] path = Synth_Dexed url = https://codeberg.org/dcoredump/Synth_Dexed.git + ignore = all [submodule "CMSIS_5"] path = CMSIS_5 url = https://github.com/ARM-software/CMSIS_5 + ignore = all diff --git a/src/minidexed.cpp b/src/minidexed.cpp index b9fb752..ac3b79a 100644 --- a/src/minidexed.cpp +++ b/src/minidexed.cpp @@ -27,20 +27,11 @@ #include #include #include -//#include -//#include -//#include "circle_stdlib_app.h" -//#include "mididevice.h" - const char WLANFirmwarePath[] = "SD:firmware/"; const char WLANConfigFile[] = "SD:wpa_supplicant.conf"; #define FTPUSERNAME "admin" #define FTPPASSWORD "admin" -/* -const char WLANFirmwarePath[] = "SD:firmware/"; -const char WLANConfigFile[] = "SD:wpa_supplicant.conf"; -*/ LOGMODULE ("minidexed"); @@ -289,9 +280,7 @@ bool CMiniDexed::Initialize (void) } #endif InitNetwork(); - - - //CMIDIDevice->InitializeRTP(); + return true; } @@ -1849,10 +1838,7 @@ void CMiniDexed::UpdateNetwork() LOGNOTE("Network up and running at: %s", static_cast(IPString)); - if (m_UDPMIDI.Initialize ()) - { - LOGNOTE ("RTP MIDI interface enabled"); - } + m_UDPMIDI.Initialize(); m_pFTPDaemon = new CFTPDaemon(FTPUSERNAME, FTPPASSWORD); diff --git a/src/udpmididevice.cpp b/src/udpmididevice.cpp index f24d580..6080132 100644 --- a/src/udpmididevice.cpp +++ b/src/udpmididevice.cpp @@ -35,18 +35,8 @@ CUDPMIDIDevice::CUDPMIDIDevice (CMiniDexed *pSynthesizer, : CMIDIDevice (pSynthesizer, pConfig, pUI), m_pSynthesizer (pSynthesizer), m_pConfig (pConfig) - - - //m_Serial (pInterrupt, TRUE), - //m_nSerialState (0), - //m_nSysEx (0), - //m_SendBuffer (&m_Serial) { AddDevice ("udp"); - /*for (unsigned nTG = 0; nTG < CConfig::ToneGenerators; nTG++) - { - m_ChannelMap[nTG] = Disabled; - }*/ } CUDPMIDIDevice::~CUDPMIDIDevice (void) @@ -86,7 +76,6 @@ void CUDPMIDIDevice::OnAppleMIDIDataReceived(const u8* pData, size_t nSize) void CUDPMIDIDevice::OnAppleMIDIConnect(const CIPAddress* pIPAddress, const char* pName) { LOGNOTE("RTP Device connected"); - //AddDevice ("udp1"); } void CUDPMIDIDevice::OnAppleMIDIDisconnect(const CIPAddress* pIPAddress, const char* pName) diff --git a/src/udpmididevice.h b/src/udpmididevice.h index e0119e1..f20cc79 100644 --- a/src/udpmididevice.h +++ b/src/udpmididevice.h @@ -30,11 +30,6 @@ #include "net/applemidi.h" #include "net/udpmidi.h" -//#include -//#include -//#include -//#include - class CMiniDexed; class CUDPMIDIDevice : CAppleMIDIHandler, CUDPMIDIHandler, public CMIDIDevice @@ -48,26 +43,11 @@ public: virtual void OnAppleMIDIConnect(const CIPAddress* pIPAddress, const char* pName) override; virtual void OnAppleMIDIDisconnect(const CIPAddress* pIPAddress, const char* pName) override; virtual void OnUDPMIDIDataReceived(const u8* pData, size_t nSize) override; - //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: CMiniDexed *m_pSynthesizer; CConfig *m_pConfig; - //u8 m_ChannelMap[CConfig::ToneGenerators]; - //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 CUDPMIDIReceiver* m_pUDPMIDIReceiver; From df387faa674ca3ea198cb59d791627fa8824286d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20=C5=9Eiar=20Baysal?= Date: Fri, 8 Nov 2024 10:31:37 +0100 Subject: [PATCH 08/13] clean up --- src/kernel.cpp | 5 +---- src/kernel.h | 1 - src/udpmididevice.h | 2 +- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/kernel.cpp b/src/kernel.cpp index 8874cf7..3d80d71 100644 --- a/src/kernel.cpp +++ b/src/kernel.cpp @@ -33,12 +33,9 @@ CKernel *CKernel::s_pThis = 0; CKernel::CKernel (void) : CStdlibAppStdio ("minidexed"), - //CStdlibAppNetwork ("minidexed", CSTDLIBAPP_DEFAULT_PARTITION, - // 0, 0, 0, 0, NET_DEVICE_TYPE), m_Config (&mFileSystem), m_GPIOManager (&mInterrupt), m_I2CMaster (CMachineInfo::Get ()->GetDevice (DeviceI2CMaster), TRUE), - //m_Scheduler(), m_pDexed (0) { s_pThis = this; @@ -115,7 +112,7 @@ CStdlibApp::TShutdownMode CKernel::Run (void) mScreen.Update (); } - m_CPUThrottle.Update (); + m_CPUThrottle.Update (); } return ShutdownHalt; diff --git a/src/kernel.h b/src/kernel.h index f5e080e..3d9d70b 100644 --- a/src/kernel.h +++ b/src/kernel.h @@ -55,7 +55,6 @@ private: CCPUThrottle m_CPUThrottle; CGPIOManager m_GPIOManager; CI2CMaster m_I2CMaster; - //CScheduler m_Scheduler; CMiniDexed *m_pDexed; CUSBController *m_pUSB; diff --git a/src/udpmididevice.h b/src/udpmididevice.h index f20cc79..55bc7a2 100644 --- a/src/udpmididevice.h +++ b/src/udpmididevice.h @@ -47,7 +47,7 @@ public: private: CMiniDexed *m_pSynthesizer; CConfig *m_pConfig; - CBcmRandomNumberGenerator m_Random; + CBcmRandomNumberGenerator m_Random; CAppleMIDIParticipant* m_pAppleMIDIParticipant; // AppleMIDI participant instance CUDPMIDIReceiver* m_pUDPMIDIReceiver; From b66058b17f8b0ad47605e598d21acd3644258b0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20=C5=9Eiar=20Baysal?= Date: Fri, 8 Nov 2024 11:19:07 +0100 Subject: [PATCH 09/13] try to resolve conflicts --- src/kernel.h | 8 ++++---- src/minidexed.cpp | 1 - 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/kernel.h b/src/kernel.h index 3d9d70b..8167c61 100644 --- a/src/kernel.h +++ b/src/kernel.h @@ -51,12 +51,12 @@ private: private: // do not change this order - CConfig m_Config; + CConfig m_Config; CCPUThrottle m_CPUThrottle; CGPIOManager m_GPIOManager; - CI2CMaster m_I2CMaster; - CMiniDexed *m_pDexed; - CUSBController *m_pUSB; + CI2CMaster m_I2CMaster; + CMiniDexed *m_pDexed; + CUSBController *m_pUSB; static CKernel *s_pThis; }; diff --git a/src/minidexed.cpp b/src/minidexed.cpp index ac3b79a..aa014df 100644 --- a/src/minidexed.cpp +++ b/src/minidexed.cpp @@ -60,7 +60,6 @@ CMiniDexed::CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt, m_bSetNewPerformance (false), m_bDeletePerformance (false), m_bLoadPerformanceBusy(false), - m_pNet(nullptr), m_pNetDevice(nullptr), m_WLAN(WLANFirmwarePath), From dc2a8b4a43ac4d353577f95414e5fe864bb7193a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20=C5=9Eiar=20Baysal?= Date: Fri, 8 Nov 2024 11:20:30 +0100 Subject: [PATCH 10/13] comma conflict --- src/minidexed.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/minidexed.cpp b/src/minidexed.cpp index aa014df..6c0b46f 100644 --- a/src/minidexed.cpp +++ b/src/minidexed.cpp @@ -59,13 +59,13 @@ CMiniDexed::CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt, m_bSavePerformanceNewFile (false), m_bSetNewPerformance (false), m_bDeletePerformance (false), - m_bLoadPerformanceBusy(false), m_pNet(nullptr), m_pNetDevice(nullptr), m_WLAN(WLANFirmwarePath), m_WPASupplicant(WLANConfigFile), m_bNetworkReady(false), - m_UDPMIDI (this, pConfig, &m_UI) + m_UDPMIDI (this, pConfig, &m_UI), + m_bLoadPerformanceBusy(false) { assert (m_pConfig); From db2534d8a61370727b0e36057b963a49a2ea0783 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20=C5=9Eiar=20Baysal?= Date: Fri, 8 Nov 2024 11:25:33 +0100 Subject: [PATCH 11/13] try to resolve conflict --- src/minidexed.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/minidexed.cpp b/src/minidexed.cpp index 6c0b46f..f26c8a1 100644 --- a/src/minidexed.cpp +++ b/src/minidexed.cpp @@ -55,16 +55,16 @@ CMiniDexed::CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt, m_GetChunkTimer ("GetChunk", 1000000U * pConfig->GetChunkSize ()/2 / pConfig->GetSampleRate ()), m_bProfileEnabled (m_pConfig->GetProfileEnabled ()), - m_bSavePerformance (false), - m_bSavePerformanceNewFile (false), - m_bSetNewPerformance (false), - m_bDeletePerformance (false), 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_bSavePerformanceNewFile (false), + m_bSetNewPerformance (false), + m_bDeletePerformance (false), m_bLoadPerformanceBusy(false) { assert (m_pConfig); From d6a2c6a320fd823755d338b55b5d837698b344c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20=C5=9Eiar=20Baysal?= Date: Fri, 8 Nov 2024 11:47:28 +0100 Subject: [PATCH 12/13] fix init order --- src/minidexed.h | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/minidexed.h b/src/minidexed.h index b28f9b4..c109fac 100644 --- a/src/minidexed.h +++ b/src/minidexed.h @@ -309,6 +309,15 @@ private: 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_bSavePerformanceNewFile; bool m_bSetNewPerformance; @@ -318,13 +327,7 @@ private: bool m_bLoadPerformanceBusy; bool m_bSaveAsDeault; - CNetSubSystem* m_pNet; - CNetDevice* m_pNetDevice; - CBcm4343Device m_WLAN; - CWPASupplicant m_WPASupplicant; - bool m_bNetworkReady; - CUDPMIDIDevice m_UDPMIDI; - CFTPDaemon* m_pFTPDaemon; + }; From ea8f5f7b7ead65468a6d6afa67b06b0eb653b928 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20=C5=9Eiar=20Baysal?= Date: Fri, 8 Nov 2024 15:43:28 +0100 Subject: [PATCH 13/13] clean net directory as well --- src/Rules.mk | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Rules.mk b/src/Rules.mk index 6461833..d86f51a 100644 --- a/src/Rules.mk +++ b/src/Rules.mk @@ -34,4 +34,6 @@ LIBS += \ $(CIRCLEHOME)/addon/wlan/libwlan.a \ $(CIRCLEHOME)/lib/net/libnet.a +EXTRACLEAN += $(NET_DIR)/*.d $(NET_DIR)/*.o + -include $(DEPS)