mirror of https://github.com/probonopd/MiniDexed
commit
65f7d6bba4
@ -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 |
||||
|
@ -0,0 +1,874 @@ |
||||
//
|
||||
// applemidi.cpp
|
||||
//
|
||||
// mt32-pi - A baremetal MIDI synthesizer for Raspberry Pi
|
||||
// Copyright (C) 2020-2023 Dale Whinham <daleyo@gmail.com>
|
||||
//
|
||||
// This file is part of mt32-pi.
|
||||
//
|
||||
// mt32-pi is free software: you can redistribute it and/or modify it under the
|
||||
// terms of the GNU General Public License as published by the Free Software
|
||||
// Foundation, either version 3 of the License, or (at your option) any later
|
||||
// version.
|
||||
//
|
||||
// mt32-pi is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
// details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along with
|
||||
// mt32-pi. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
#include <circle/logger.h> |
||||
#include <circle/macros.h> |
||||
#include <circle/net/in.h> |
||||
#include <circle/net/netsubsystem.h> |
||||
#include <circle/sched/scheduler.h> |
||||
#include <circle/timer.h> |
||||
#include <circle/util.h> |
||||
|
||||
#include "applemidi.h" |
||||
#include "byteorder.h" |
||||
|
||||
// #define APPLEMIDI_DEBUG
|
||||
|
||||
LOGMODULE("applemidi"); |
||||
|
||||
constexpr u16 ControlPort = 5004; |
||||
constexpr u16 MIDIPort = ControlPort + 1; |
||||
|
||||
constexpr u16 AppleMIDISignature = 0xFFFF; |
||||
constexpr u8 AppleMIDIVersion = 2; |
||||
|
||||
constexpr u8 RTPMIDIPayloadType = 0x61; |
||||
constexpr u8 RTPMIDIVersion = 2; |
||||
|
||||
// Arbitrary value
|
||||
constexpr size_t MaxNameLength = 256; |
||||
|
||||
// Timeout period for invitation (5 seconds in 100 microsecond units)
|
||||
constexpr unsigned int InvitationTimeout = 5 * 10000; |
||||
|
||||
// Timeout period for sync packets (60 seconds in 100 microsecond units)
|
||||
constexpr unsigned int SyncTimeout = 60 * 10000; |
||||
|
||||
// Receiver feedback packet frequency (1 second in 100 microsecond units)
|
||||
constexpr unsigned int ReceiverFeedbackPeriod = 1 * 10000; |
||||
|
||||
constexpr u16 CommandWord(const char Command[2]) { return Command[0] << 8 | Command[1]; } |
||||
|
||||
enum TAppleMIDICommand : u16 |
||||
{ |
||||
Invitation = CommandWord("IN"), |
||||
InvitationAccepted = CommandWord("OK"), |
||||
InvitationRejected = CommandWord("NO"), |
||||
Sync = CommandWord("CK"), |
||||
ReceiverFeedback = CommandWord("RS"), |
||||
EndSession = CommandWord("BY"), |
||||
}; |
||||
|
||||
struct TAppleMIDISession |
||||
{ |
||||
u16 nSignature; |
||||
u16 nCommand; |
||||
u32 nVersion; |
||||
u32 nInitiatorToken; |
||||
u32 nSSRC; |
||||
char Name[MaxNameLength]; |
||||
} |
||||
PACKED; |
||||
|
||||
// The Name field is optional
|
||||
constexpr size_t NamelessSessionPacketSize = sizeof(TAppleMIDISession) - sizeof(TAppleMIDISession::Name); |
||||
|
||||
struct TAppleMIDISync |
||||
{ |
||||
u16 nSignature; |
||||
u16 nCommand; |
||||
u32 nSSRC; |
||||
u8 nCount; |
||||
u8 Padding[3]; |
||||
u64 Timestamps[3]; |
||||
} |
||||
PACKED; |
||||
|
||||
struct TAppleMIDIReceiverFeedback |
||||
{ |
||||
u16 nSignature; |
||||
u16 nCommand; |
||||
u32 nSSRC; |
||||
u32 nSequence; |
||||
} |
||||
PACKED; |
||||
|
||||
struct TRTPMIDI |
||||
{ |
||||
u16 nFlags; |
||||
u16 nSequence; |
||||
u32 nTimestamp; |
||||
u32 nSSRC; |
||||
} |
||||
PACKED; |
||||
|
||||
u64 GetSyncClock() |
||||
{ |
||||
static const u64 nStartTime = CTimer::GetClockTicks(); |
||||
const u64 nMicrosSinceEpoch = CTimer::GetClockTicks(); |
||||
|
||||
// Units of 100 microseconds
|
||||
return (nMicrosSinceEpoch - nStartTime ) / 100; |
||||
} |
||||
|
||||
bool ParseInvitationPacket(const u8* pBuffer, size_t nSize, TAppleMIDISession* pOutPacket) |
||||
{ |
||||
const TAppleMIDISession* const pInPacket = reinterpret_cast<const TAppleMIDISession*>(pBuffer); |
||||
|
||||
if (nSize < NamelessSessionPacketSize) |
||||
return false; |
||||
|
||||
const u16 nSignature = ntohs(pInPacket->nSignature); |
||||
if (nSignature != AppleMIDISignature) |
||||
return false; |
||||
|
||||
const u16 nCommand = ntohs(pInPacket->nCommand); |
||||
if (nCommand != Invitation) |
||||
return false; |
||||
|
||||
const u32 nVersion = ntohl(pInPacket->nVersion); |
||||
if (nVersion != AppleMIDIVersion) |
||||
return false; |
||||
|
||||
pOutPacket->nSignature = nSignature; |
||||
pOutPacket->nCommand = nCommand; |
||||
pOutPacket->nVersion = nVersion; |
||||
pOutPacket->nInitiatorToken = ntohl(pInPacket->nInitiatorToken); |
||||
pOutPacket->nSSRC = ntohl(pInPacket->nSSRC); |
||||
|
||||
if (nSize > NamelessSessionPacketSize) |
||||
strncpy(pOutPacket->Name, pInPacket->Name, sizeof(pOutPacket->Name)); |
||||
else |
||||
strncpy(pOutPacket->Name, "<unknown>", sizeof(pOutPacket->Name)); |
||||
|
||||
return true; |
||||
} |
||||
|
||||
bool ParseEndSessionPacket(const u8* pBuffer, size_t nSize, TAppleMIDISession* pOutPacket) |
||||
{ |
||||
const TAppleMIDISession* const pInPacket = reinterpret_cast<const TAppleMIDISession*>(pBuffer); |
||||
|
||||
if (nSize < NamelessSessionPacketSize) |
||||
return false; |
||||
|
||||
const u16 nSignature = ntohs(pInPacket->nSignature); |
||||
if (nSignature != AppleMIDISignature) |
||||
return false; |
||||
|
||||
const u16 nCommand = ntohs(pInPacket->nCommand); |
||||
if (nCommand != EndSession) |
||||
return false; |
||||
|
||||
const u32 nVersion = ntohl(pInPacket->nVersion); |
||||
if (nVersion != AppleMIDIVersion) |
||||
return false; |
||||
|
||||
pOutPacket->nSignature = nSignature; |
||||
pOutPacket->nCommand = nCommand; |
||||
pOutPacket->nVersion = nVersion; |
||||
pOutPacket->nInitiatorToken = ntohl(pInPacket->nInitiatorToken); |
||||
pOutPacket->nSSRC = ntohl(pInPacket->nSSRC); |
||||
|
||||
return true; |
||||
} |
||||
|
||||
bool ParseSyncPacket(const u8* pBuffer, size_t nSize, TAppleMIDISync* pOutPacket) |
||||
{ |
||||
const TAppleMIDISync* const pInPacket = reinterpret_cast<const TAppleMIDISync*>(pBuffer); |
||||
|
||||
if (nSize < sizeof(TAppleMIDISync)) |
||||
return false; |
||||
|
||||
const u32 nSignature = ntohs(pInPacket->nSignature); |
||||
if (nSignature != AppleMIDISignature) |
||||
return false; |
||||
|
||||
const u32 nCommand = ntohs(pInPacket->nCommand); |
||||
if (nCommand != Sync) |
||||
return false; |
||||
|
||||
pOutPacket->nSignature = nSignature; |
||||
pOutPacket->nCommand = nCommand; |
||||
pOutPacket->nSSRC = ntohl(pInPacket->nSSRC); |
||||
pOutPacket->nCount = pInPacket->nCount; |
||||
pOutPacket->Timestamps[0] = ntohll(pInPacket->Timestamps[0]); |
||||
pOutPacket->Timestamps[1] = ntohll(pInPacket->Timestamps[1]); |
||||
pOutPacket->Timestamps[2] = ntohll(pInPacket->Timestamps[2]); |
||||
|
||||
return true; |
||||
} |
||||
|
||||
u8 ParseMIDIDeltaTime(const u8* pBuffer) |
||||
{ |
||||
u8 nLength = 0; |
||||
u32 nDeltaTime = 0; |
||||
|
||||
while (nLength < 4) |
||||
{ |
||||
nDeltaTime <<= 7; |
||||
nDeltaTime |= pBuffer[nLength] & 0x7F; |
||||
|
||||
// Upper bit not set; end of timestamp
|
||||
if ((pBuffer[nLength++] & (1 << 7)) == 0) |
||||
break; |
||||
} |
||||
|
||||
return nLength; |
||||
} |
||||
|
||||
size_t ParseSysExCommand(const u8* pBuffer, size_t nSize, CAppleMIDIHandler* pHandler) |
||||
{ |
||||
size_t nBytesParsed = 1; |
||||
const u8 nHead = pBuffer[0]; |
||||
u8 nTail = 0; |
||||
|
||||
while (nBytesParsed < nSize && !(nTail == 0xF0 || nTail == 0xF7 || nTail == 0xF4)) |
||||
nTail = pBuffer[nBytesParsed++]; |
||||
|
||||
size_t nReceiveLength = nBytesParsed; |
||||
|
||||
// First segmented SysEx packet
|
||||
if (nHead == 0xF0 && nTail == 0xF0) |
||||
{ |
||||
#ifdef APPLEMIDI_DEBUG |
||||
LOGNOTE("Received segmented SysEx (first)"); |
||||
#endif |
||||
--nReceiveLength; |
||||
} |
||||
|
||||
// Middle segmented SysEx packet
|
||||
else if (nHead == 0xF7 && nTail == 0xF0) |
||||
{ |
||||
#ifdef APPLEMIDI_DEBUG |
||||
LOGNOTE("Received segmented SysEx (middle)"); |
||||
#endif |
||||
++pBuffer; |
||||
nBytesParsed -= 2; |
||||
} |
||||
|
||||
// Last segmented SysEx packet
|
||||
else if (nHead == 0xF7 && nTail == 0xF7) |
||||
{ |
||||
#ifdef APPLEMIDI_DEBUG |
||||
LOGNOTE("Received segmented SysEx (last)"); |
||||
#endif |
||||
++pBuffer; |
||||
--nReceiveLength; |
||||
} |
||||
|
||||
// Cancelled segmented SysEx packet
|
||||
else if (nHead == 0xF7 && nTail == 0xF4) |
||||
{ |
||||
#ifdef APPLEMIDI_DEBUG |
||||
LOGNOTE("Received cancelled SysEx"); |
||||
#endif |
||||
nReceiveLength = 1; |
||||
} |
||||
|
||||
#ifdef APPLEMIDI_DEBUG |
||||
else |
||||
{ |
||||
LOGNOTE("Received complete SysEx"); |
||||
} |
||||
#endif |
||||
|
||||
pHandler->OnAppleMIDIDataReceived(pBuffer, nReceiveLength); |
||||
|
||||
return nBytesParsed; |
||||
} |
||||
|
||||
size_t ParseMIDICommand(const u8* pBuffer, size_t nSize, u8& nRunningStatus, CAppleMIDIHandler* pHandler) |
||||
{ |
||||
size_t nBytesParsed = 0; |
||||
u8 nByte = pBuffer[0]; |
||||
|
||||
// System Real-Time message - single byte, handle immediately
|
||||
// Can appear anywhere in the stream, even in between status/data bytes
|
||||
if (nByte >= 0xF8) |
||||
{ |
||||
// Ignore undefined System Real-Time
|
||||
if (nByte != 0xF9 && nByte != 0xFD) |
||||
pHandler->OnAppleMIDIDataReceived(&nByte, 1); |
||||
|
||||
return 1; |
||||
} |
||||
|
||||
// Is it a status byte?
|
||||
if (nByte & 0x80) |
||||
{ |
||||
// Update running status if non Real-Time System status
|
||||
if (nByte < 0xF0) |
||||
nRunningStatus = nByte; |
||||
else |
||||
nRunningStatus = 0; |
||||
|
||||
++nBytesParsed; |
||||
} |
||||
else |
||||
{ |
||||
// First byte not a status byte and no running status - invalid
|
||||
if (!nRunningStatus) |
||||
return 0; |
||||
|
||||
// Use running status
|
||||
nByte = nRunningStatus; |
||||
} |
||||
|
||||
// Channel messages
|
||||
if (nByte < 0xF0) |
||||
{ |
||||
// How many data bytes?
|
||||
switch (nByte & 0xF0) |
||||
{ |
||||
case 0x80: // Note off
|
||||
case 0x90: // Note on
|
||||
case 0xA0: // Polyphonic key pressure/aftertouch
|
||||
case 0xB0: // Control change
|
||||
case 0xE0: // Pitch bend
|
||||
nBytesParsed += 2; |
||||
break; |
||||
|
||||
case 0xC0: // Program change
|
||||
case 0xD0: // Channel pressure/aftertouch
|
||||
nBytesParsed += 1; |
||||
break; |
||||
} |
||||
|
||||
// Handle command
|
||||
pHandler->OnAppleMIDIDataReceived(pBuffer, nBytesParsed); |
||||
return nBytesParsed; |
||||
} |
||||
|
||||
// System common commands
|
||||
switch (nByte) |
||||
{ |
||||
case 0xF0: // Start of System Exclusive
|
||||
case 0xF7: // End of Exclusive
|
||||
return ParseSysExCommand(pBuffer, nSize, pHandler); |
||||
|
||||
case 0xF1: // MIDI Time Code Quarter Frame
|
||||
case 0xF3: // Song Select
|
||||
++nBytesParsed; |
||||
break; |
||||
|
||||
case 0xF2: // Song Position Pointer
|
||||
nBytesParsed += 2; |
||||
break; |
||||
} |
||||
|
||||
pHandler->OnAppleMIDIDataReceived(pBuffer, nBytesParsed); |
||||
return nBytesParsed; |
||||
} |
||||
|
||||
bool ParseMIDICommandSection(const u8* pBuffer, size_t nSize, CAppleMIDIHandler* pHandler) |
||||
{ |
||||
// Must have at least a header byte and a single status byte
|
||||
if (nSize < 2) |
||||
return false; |
||||
|
||||
size_t nMIDICommandsProcessed = 0; |
||||
size_t nBytesRemaining = nSize - 1; |
||||
u8 nRunningStatus = 0; |
||||
|
||||
const u8 nMIDIHeader = pBuffer[0]; |
||||
const u8* pMIDICommands = pBuffer + 1; |
||||
|
||||
// Lower 4 bits of the header is length
|
||||
u16 nMIDICommandLength = nMIDIHeader & 0x0F; |
||||
|
||||
// If B flag is set, length value is 12 bits
|
||||
if (nMIDIHeader & (1 << 7)) |
||||
{ |
||||
nMIDICommandLength <<= 8; |
||||
nMIDICommandLength |= pMIDICommands[0]; |
||||
++pMIDICommands; |
||||
--nBytesRemaining; |
||||
} |
||||
|
||||
if (nMIDICommandLength > nBytesRemaining) |
||||
{ |
||||
LOGERR("Invalid MIDI command length"); |
||||
return false; |
||||
} |
||||
|
||||
// Begin decoding the command list
|
||||
while (nMIDICommandLength) |
||||
{ |
||||
// If Z flag is set, first list entry is a delta time
|
||||
if (nMIDICommandsProcessed || nMIDIHeader & (1 << 5)) |
||||
{ |
||||
const u8 nBytesParsed = ParseMIDIDeltaTime(pMIDICommands); |
||||
nMIDICommandLength -= nBytesParsed; |
||||
pMIDICommands += nBytesParsed; |
||||
} |
||||
|
||||
if (nMIDICommandLength) |
||||
{ |
||||
const size_t nBytesParsed = ParseMIDICommand(pMIDICommands, nMIDICommandLength, nRunningStatus, pHandler); |
||||
nMIDICommandLength -= nBytesParsed; |
||||
pMIDICommands += nBytesParsed; |
||||
++nMIDICommandsProcessed; |
||||
} |
||||
} |
||||
|
||||
return true; |
||||
} |
||||
|
||||
bool ParseMIDIPacket(const u8* pBuffer, size_t nSize, TRTPMIDI* pOutPacket, CAppleMIDIHandler* pHandler) |
||||
{ |
||||
assert(pHandler != nullptr); |
||||
|
||||
const TRTPMIDI* const pInPacket = reinterpret_cast<const TRTPMIDI*>(pBuffer); |
||||
const u16 nRTPFlags = ntohs(pInPacket->nFlags); |
||||
|
||||
// Check size (RTP-MIDI header plus MIDI command section header)
|
||||
if (nSize < sizeof(TRTPMIDI) + 1) |
||||
return false; |
||||
|
||||
// Check version
|
||||
if (((nRTPFlags >> 14) & 0x03) != RTPMIDIVersion) |
||||
return false; |
||||
|
||||
// Ensure no CSRC identifiers
|
||||
if (((nRTPFlags >> 8) & 0x0F) != 0) |
||||
return false; |
||||
|
||||
// Check payload type
|
||||
if ((nRTPFlags & 0xFF) != RTPMIDIPayloadType) |
||||
return false; |
||||
|
||||
pOutPacket->nFlags = nRTPFlags; |
||||
pOutPacket->nSequence = ntohs(pInPacket->nSequence); |
||||
pOutPacket->nTimestamp = ntohl(pInPacket->nTimestamp); |
||||
pOutPacket->nSSRC = ntohl(pInPacket->nSSRC); |
||||
|
||||
// RTP-MIDI variable-length header
|
||||
const u8* const pMIDICommandSection = pBuffer + sizeof(TRTPMIDI); |
||||
size_t nRemaining = nSize - sizeof(TRTPMIDI); |
||||
return ParseMIDICommandSection(pMIDICommandSection, nRemaining, pHandler); |
||||
} |
||||
|
||||
CAppleMIDIParticipant::CAppleMIDIParticipant(CBcmRandomNumberGenerator* pRandom, CAppleMIDIHandler* pHandler) |
||||
: CTask(TASK_STACK_SIZE, true), |
||||
|
||||
m_pRandom(pRandom), |
||||
|
||||
m_pControlSocket(nullptr), |
||||
m_pMIDISocket(nullptr), |
||||
|
||||
m_nForeignControlPort(0), |
||||
m_nForeignMIDIPort(0), |
||||
m_nInitiatorControlPort(0), |
||||
m_nInitiatorMIDIPort(0), |
||||
m_ControlBuffer{0}, |
||||
m_MIDIBuffer{0}, |
||||
|
||||
m_nControlResult(0), |
||||
m_nMIDIResult(0), |
||||
|
||||
m_pHandler(pHandler), |
||||
|
||||
m_State(TState::ControlInvitation), |
||||
|
||||
m_nInitiatorToken(0), |
||||
m_nInitiatorSSRC(0), |
||||
m_nSSRC(0), |
||||
m_nLastMIDISequenceNumber(0), |
||||
|
||||
m_nOffsetEstimate(0), |
||||
m_nLastSyncTime(0), |
||||
|
||||
m_nSequence(0), |
||||
m_nLastFeedbackSequence(0), |
||||
m_nLastFeedbackTime(0) |
||||
{ |
||||
} |
||||
|
||||
CAppleMIDIParticipant::~CAppleMIDIParticipant() |
||||
{ |
||||
if (m_pControlSocket) |
||||
delete m_pControlSocket; |
||||
|
||||
if (m_pMIDISocket) |
||||
delete m_pMIDISocket; |
||||
} |
||||
|
||||
bool CAppleMIDIParticipant::Initialize() |
||||
{ |
||||
assert(m_pControlSocket == nullptr); |
||||
assert(m_pMIDISocket == nullptr); |
||||
|
||||
CNetSubSystem* const pNet = CNetSubSystem::Get(); |
||||
|
||||
if ((m_pControlSocket = new CSocket(pNet, IPPROTO_UDP)) == nullptr) |
||||
return false; |
||||
|
||||
if ((m_pMIDISocket = new CSocket(pNet, IPPROTO_UDP)) == nullptr) |
||||
return false; |
||||
|
||||
if (m_pControlSocket->Bind(ControlPort) != 0) |
||||
{ |
||||
LOGERR("Couldn't bind to port %d", ControlPort); |
||||
return false; |
||||
} |
||||
|
||||
if (m_pMIDISocket->Bind(MIDIPort) != 0) |
||||
{ |
||||
LOGERR("Couldn't bind to port %d", MIDIPort); |
||||
return false; |
||||
} |
||||
|
||||
// We started as a suspended task; run now that initialization is successful
|
||||
Start(); |
||||
|
||||
return true; |
||||
} |
||||
|
||||
void CAppleMIDIParticipant::Run() |
||||
{ |
||||
assert(m_pControlSocket != nullptr); |
||||
assert(m_pMIDISocket != nullptr); |
||||
|
||||
CScheduler* const pScheduler = CScheduler::Get(); |
||||
|
||||
while (true) |
||||
{ |
||||
if ((m_nControlResult = m_pControlSocket->ReceiveFrom(m_ControlBuffer, sizeof(m_ControlBuffer), MSG_DONTWAIT, &m_ForeignControlIPAddress, &m_nForeignControlPort)) < 0) |
||||
LOGERR("Control socket receive error: %d", m_nControlResult); |
||||
|
||||
if ((m_nMIDIResult = m_pMIDISocket->ReceiveFrom(m_MIDIBuffer, sizeof(m_MIDIBuffer), MSG_DONTWAIT, &m_ForeignMIDIIPAddress, &m_nForeignMIDIPort)) < 0) |
||||
LOGERR("MIDI socket receive error: %d", m_nMIDIResult); |
||||
|
||||
switch (m_State) |
||||
{ |
||||
case TState::ControlInvitation: |
||||
ControlInvitationState(); |
||||
break; |
||||
|
||||
case TState::MIDIInvitation: |
||||
MIDIInvitationState(); |
||||
break; |
||||
|
||||
case TState::Connected: |
||||
ConnectedState(); |
||||
break; |
||||
} |
||||
|
||||
// Allow other tasks to run
|
||||
pScheduler->Yield(); |
||||
} |
||||
} |
||||
|
||||
void CAppleMIDIParticipant::ControlInvitationState() |
||||
{ |
||||
TAppleMIDISession SessionPacket; |
||||
|
||||
if (m_nControlResult == 0) |
||||
return; |
||||
|
||||
if (!ParseInvitationPacket(m_ControlBuffer, m_nControlResult, &SessionPacket)) |
||||
{ |
||||
LOGERR("Unexpected packet"); |
||||
return; |
||||
} |
||||
|
||||
#ifdef APPLEMIDI_DEBUG |
||||
LOGNOTE("<-- Control invitation"); |
||||
#endif |
||||
|
||||
// Store initiator details
|
||||
m_InitiatorIPAddress.Set(m_ForeignControlIPAddress); |
||||
m_nInitiatorControlPort = m_nForeignControlPort; |
||||
m_nInitiatorToken = SessionPacket.nInitiatorToken; |
||||
m_nInitiatorSSRC = SessionPacket.nSSRC; |
||||
|
||||
// Generate random SSRC and accept
|
||||
m_nSSRC = m_pRandom->GetNumber(); |
||||
if (!SendAcceptInvitationPacket(m_pControlSocket, &m_InitiatorIPAddress, m_nInitiatorControlPort)) |
||||
{ |
||||
LOGERR("Couldn't accept control invitation"); |
||||
return; |
||||
} |
||||
|
||||
m_nLastSyncTime = GetSyncClock(); |
||||
m_State = TState::MIDIInvitation; |
||||
} |
||||
|
||||
void CAppleMIDIParticipant::MIDIInvitationState() |
||||
{ |
||||
TAppleMIDISession SessionPacket; |
||||
|
||||
if (m_nControlResult > 0) |
||||
{ |
||||
if (ParseInvitationPacket(m_ControlBuffer, m_nControlResult, &SessionPacket)) |
||||
{ |
||||
// Unexpected peer; reject invitation
|
||||
if (m_ForeignControlIPAddress != m_InitiatorIPAddress || m_nForeignControlPort != m_nInitiatorControlPort) |
||||
SendRejectInvitationPacket(m_pControlSocket, &m_ForeignControlIPAddress, m_nForeignControlPort, SessionPacket.nInitiatorToken); |
||||
else |
||||
LOGERR("Unexpected packet"); |
||||
} |
||||
} |
||||
|
||||
if (m_nMIDIResult > 0) |
||||
{ |
||||
if (!ParseInvitationPacket(m_MIDIBuffer, m_nMIDIResult, &SessionPacket)) |
||||
{ |
||||
LOGERR("Unexpected packet"); |
||||
return; |
||||
} |
||||
|
||||
// Unexpected peer; reject invitation
|
||||
if (m_ForeignMIDIIPAddress != m_InitiatorIPAddress) |
||||
{ |
||||
SendRejectInvitationPacket(m_pMIDISocket, &m_ForeignMIDIIPAddress, m_nForeignMIDIPort, SessionPacket.nInitiatorToken); |
||||
return; |
||||
} |
||||
|
||||
#ifdef APPLEMIDI_DEBUG |
||||
LOGNOTE("<-- MIDI invitation"); |
||||
#endif |
||||
|
||||
m_nInitiatorMIDIPort = m_nForeignMIDIPort; |
||||
|
||||
if (SendAcceptInvitationPacket(m_pMIDISocket, &m_InitiatorIPAddress, m_nInitiatorMIDIPort)) |
||||
{ |
||||
CString IPAddressString; |
||||
m_InitiatorIPAddress.Format(&IPAddressString); |
||||
LOGNOTE("Connection to %s (%s) established", SessionPacket.Name, static_cast<const char*>(IPAddressString)); |
||||
m_nLastSyncTime = GetSyncClock(); |
||||
m_State = TState::Connected; |
||||
m_pHandler->OnAppleMIDIConnect(&m_InitiatorIPAddress, SessionPacket.Name); |
||||
} |
||||
else |
||||
{ |
||||
LOGERR("Couldn't accept MIDI invitation"); |
||||
Reset(); |
||||
} |
||||
} |
||||
|
||||
// Timeout
|
||||
else if ((GetSyncClock() - m_nLastSyncTime) > InvitationTimeout) |
||||
{ |
||||
LOGERR("MIDI port invitation timed out"); |
||||
Reset(); |
||||
} |
||||
} |
||||
|
||||
void CAppleMIDIParticipant::ConnectedState() |
||||
{ |
||||
TAppleMIDISession SessionPacket; |
||||
TRTPMIDI MIDIPacket; |
||||
TAppleMIDISync SyncPacket; |
||||
|
||||
if (m_nControlResult > 0) |
||||
{ |
||||
if (ParseEndSessionPacket(m_ControlBuffer, m_nControlResult, &SessionPacket)) |
||||
{ |
||||
#ifdef APPLEMIDI_DEBUG |
||||
LOGNOTE("<-- End session"); |
||||
#endif |
||||
|
||||
if (m_ForeignControlIPAddress == m_InitiatorIPAddress && |
||||
m_nForeignControlPort == m_nInitiatorControlPort && |
||||
SessionPacket.nSSRC == m_nInitiatorSSRC) |
||||
{ |
||||
LOGNOTE("Initiator ended session"); |
||||
m_pHandler->OnAppleMIDIDisconnect(&m_InitiatorIPAddress, SessionPacket.Name); |
||||
Reset(); |
||||
return; |
||||
} |
||||
} |
||||
else if (ParseInvitationPacket(m_ControlBuffer, m_nControlResult, &SessionPacket)) |
||||
{ |
||||
// Unexpected peer; reject invitation
|
||||
if (m_ForeignControlIPAddress != m_InitiatorIPAddress || m_nForeignControlPort != m_nInitiatorControlPort) |
||||
SendRejectInvitationPacket(m_pControlSocket, &m_ForeignControlIPAddress, m_nForeignControlPort, SessionPacket.nInitiatorToken); |
||||
else |
||||
LOGERR("Unexpected packet"); |
||||
} |
||||
} |
||||
|
||||
if (m_nMIDIResult > 0) |
||||
{ |
||||
if (m_ForeignMIDIIPAddress != m_InitiatorIPAddress || m_nForeignMIDIPort != m_nInitiatorMIDIPort) |
||||
LOGERR("Unexpected packet"); |
||||
else if (ParseMIDIPacket(m_MIDIBuffer, m_nMIDIResult, &MIDIPacket, m_pHandler)) |
||||
m_nSequence = MIDIPacket.nSequence; |
||||
else if (ParseSyncPacket(m_MIDIBuffer, m_nMIDIResult, &SyncPacket)) |
||||
{ |
||||
#ifdef APPLEMIDI_DEBUG |
||||
LOGNOTE("<-- Sync %d", SyncPacket.nCount); |
||||
#endif |
||||
|
||||
if (SyncPacket.nSSRC == m_nInitiatorSSRC && (SyncPacket.nCount == 0 || SyncPacket.nCount == 2)) |
||||
{ |
||||
if (SyncPacket.nCount == 0) |
||||
SendSyncPacket(SyncPacket.Timestamps[0], GetSyncClock()); |
||||
else if (SyncPacket.nCount == 2) |
||||
{ |
||||
m_nOffsetEstimate = ((SyncPacket.Timestamps[2] + SyncPacket.Timestamps[0]) / 2) - SyncPacket.Timestamps[1]; |
||||
#ifdef APPLEMIDI_DEBUG |
||||
LOGNOTE("Offset estimate: %llu", m_nOffsetEstimate); |
||||
#endif |
||||
} |
||||
|
||||
m_nLastSyncTime = GetSyncClock(); |
||||
} |
||||
else |
||||
{ |
||||
LOGERR("Unexpected sync packet"); |
||||
} |
||||
} |
||||
} |
||||
|
||||
const u64 nTicks = GetSyncClock(); |
||||
|
||||
if ((nTicks - m_nLastFeedbackTime) > ReceiverFeedbackPeriod) |
||||
{ |
||||
if (m_nSequence != m_nLastFeedbackSequence) |
||||
{ |
||||
SendFeedbackPacket(); |
||||
m_nLastFeedbackSequence = m_nSequence; |
||||
} |
||||
m_nLastFeedbackTime = nTicks; |
||||
} |
||||
|
||||
if ((nTicks - m_nLastSyncTime) > SyncTimeout) |
||||
{ |
||||
LOGERR("Initiator timed out"); |
||||
Reset(); |
||||
} |
||||
} |
||||
|
||||
void CAppleMIDIParticipant::Reset() |
||||
{ |
||||
m_State = TState::ControlInvitation; |
||||
|
||||
m_nInitiatorToken = 0; |
||||
m_nInitiatorSSRC = 0; |
||||
m_nSSRC = 0; |
||||
m_nLastMIDISequenceNumber = 0; |
||||
|
||||
m_nOffsetEstimate = 0; |
||||
m_nLastSyncTime = 0; |
||||
|
||||
m_nSequence = 0; |
||||
m_nLastFeedbackSequence = 0; |
||||
m_nLastFeedbackTime = 0; |
||||
} |
||||
|
||||
bool CAppleMIDIParticipant::SendPacket(CSocket* pSocket, CIPAddress* pIPAddress, u16 nPort, const void* pData, size_t nSize) |
||||
{ |
||||
const int nResult = pSocket->SendTo(pData, nSize, MSG_DONTWAIT, *pIPAddress, nPort); |
||||
|
||||
if (nResult < 0) |
||||
{ |
||||
LOGERR("Send failure, error code: %d", nResult); |
||||
return false; |
||||
} |
||||
|
||||
if (static_cast<size_t>(nResult) != nSize) |
||||
{ |
||||
LOGERR("Send failure, only %d/%d bytes sent", nResult, nSize); |
||||
return false; |
||||
} |
||||
|
||||
#ifdef APPLEMIDI_DEBUG |
||||
LOGNOTE("Sent %d bytes to port %d", nResult, nPort); |
||||
#endif |
||||
|
||||
return true; |
||||
} |
||||
|
||||
bool CAppleMIDIParticipant::SendAcceptInvitationPacket(CSocket* pSocket, CIPAddress* pIPAddress, u16 nPort) |
||||
{ |
||||
TAppleMIDISession AcceptPacket = |
||||
{ |
||||
htons(AppleMIDISignature), |
||||
htons(InvitationAccepted), |
||||
htonl(AppleMIDIVersion), |
||||
htonl(m_nInitiatorToken), |
||||
htonl(m_nSSRC), |
||||
{'\0'} |
||||
}; |
||||
|
||||
// TODO: configurable name
|
||||
strncpy(AcceptPacket.Name, "minidexed", sizeof(AcceptPacket.Name)); |
||||
|
||||
#ifdef APPLEMIDI_DEBUG |
||||
LOGNOTE("--> Accept invitation"); |
||||
#endif |
||||
|
||||
const size_t nSendSize = NamelessSessionPacketSize + strlen(AcceptPacket.Name) + 1; |
||||
return SendPacket(pSocket, pIPAddress, nPort, &AcceptPacket, nSendSize); |
||||
} |
||||
|
||||
bool CAppleMIDIParticipant::SendRejectInvitationPacket(CSocket* pSocket, CIPAddress* pIPAddress, u16 nPort, u32 nInitiatorToken) |
||||
{ |
||||
TAppleMIDISession RejectPacket = |
||||
{ |
||||
htons(AppleMIDISignature), |
||||
htons(InvitationRejected), |
||||
htonl(AppleMIDIVersion), |
||||
htonl(nInitiatorToken), |
||||
htonl(m_nSSRC), |
||||
{'\0'} |
||||
}; |
||||
|
||||
#ifdef APPLEMIDI_DEBUG |
||||
LOGNOTE("--> Reject invitation"); |
||||
#endif |
||||
|
||||
// Send without name
|
||||
return SendPacket(pSocket, pIPAddress, nPort, &RejectPacket, NamelessSessionPacketSize); |
||||
} |
||||
|
||||
bool CAppleMIDIParticipant::SendSyncPacket(u64 nTimestamp1, u64 nTimestamp2) |
||||
{ |
||||
const TAppleMIDISync SyncPacket = |
||||
{ |
||||
htons(AppleMIDISignature), |
||||
htons(Sync), |
||||
htonl(m_nSSRC), |
||||
1, |
||||
{0}, |
||||
{ |
||||
htonll(nTimestamp1), |
||||
htonll(nTimestamp2), |
||||
0 |
||||
} |
||||
}; |
||||
|
||||
#ifdef APPLEMIDI_DEBUG |
||||
LOGNOTE("--> Sync 1"); |
||||
#endif |
||||
|
||||
return SendPacket(m_pMIDISocket, &m_InitiatorIPAddress, m_nInitiatorMIDIPort, &SyncPacket, sizeof(SyncPacket)); |
||||
} |
||||
|
||||
bool CAppleMIDIParticipant::SendFeedbackPacket() |
||||
{ |
||||
const TAppleMIDIReceiverFeedback FeedbackPacket = |
||||
{ |
||||
htons(AppleMIDISignature), |
||||
htons(ReceiverFeedback), |
||||
htonl(m_nSSRC), |
||||
htonl(m_nSequence << 16) |
||||
}; |
||||
|
||||
#ifdef APPLEMIDI_DEBUG |
||||
LOGNOTE("--> Feedback"); |
||||
#endif |
||||
|
||||
return SendPacket(m_pControlSocket, &m_InitiatorIPAddress, m_nInitiatorControlPort, &FeedbackPacket, sizeof(FeedbackPacket)); |
||||
} |
@ -0,0 +1,111 @@ |
||||
//
|
||||
// applemidi.h
|
||||
//
|
||||
// mt32-pi - A baremetal MIDI synthesizer for Raspberry Pi
|
||||
// Copyright (C) 2020-2023 Dale Whinham <daleyo@gmail.com>
|
||||
//
|
||||
// This file is part of mt32-pi.
|
||||
//
|
||||
// mt32-pi is free software: you can redistribute it and/or modify it under the
|
||||
// terms of the GNU General Public License as published by the Free Software
|
||||
// Foundation, either version 3 of the License, or (at your option) any later
|
||||
// version.
|
||||
//
|
||||
// mt32-pi is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
// details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along with
|
||||
// mt32-pi. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
#ifndef _applemidi_h |
||||
#define _applemidi_h |
||||
|
||||
#include <circle/bcmrandom.h> |
||||
#include <circle/net/ipaddress.h> |
||||
#include <circle/net/socket.h> |
||||
#include <circle/sched/task.h> |
||||
|
||||
class CAppleMIDIHandler |
||||
{ |
||||
public: |
||||
virtual void OnAppleMIDIDataReceived(const u8* pData, size_t nSize) = 0; |
||||
virtual void OnAppleMIDIConnect(const CIPAddress* pIPAddress, const char* pName) = 0; |
||||
virtual void OnAppleMIDIDisconnect(const CIPAddress* pIPAddress, const char* pName) = 0; |
||||
}; |
||||
|
||||
class CAppleMIDIParticipant : protected CTask |
||||
{ |
||||
public: |
||||
CAppleMIDIParticipant(CBcmRandomNumberGenerator* pRandom, CAppleMIDIHandler* pHandler); |
||||
virtual ~CAppleMIDIParticipant() override; |
||||
|
||||
bool Initialize(); |
||||
|
||||
virtual void Run() override; |
||||
|
||||
private: |
||||
void ControlInvitationState(); |
||||
void MIDIInvitationState(); |
||||
void ConnectedState(); |
||||
void Reset(); |
||||
|
||||
bool SendPacket(CSocket* pSocket, CIPAddress* pIPAddress, u16 nPort, const void* pData, size_t nSize); |
||||
bool SendAcceptInvitationPacket(CSocket* pSocket, CIPAddress* pIPAddress, u16 nPort); |
||||
bool SendRejectInvitationPacket(CSocket* pSocket, CIPAddress* pIPAddress, u16 nPort, u32 nInitiatorToken); |
||||
bool SendSyncPacket(u64 nTimestamp1, u64 nTimestamp2); |
||||
bool SendFeedbackPacket(); |
||||
|
||||
CBcmRandomNumberGenerator* m_pRandom; |
||||
|
||||
// UDP sockets
|
||||
CSocket* m_pControlSocket; |
||||
CSocket* m_pMIDISocket; |
||||
|
||||
// Foreign peers
|
||||
CIPAddress m_ForeignControlIPAddress; |
||||
CIPAddress m_ForeignMIDIIPAddress; |
||||
u16 m_nForeignControlPort; |
||||
u16 m_nForeignMIDIPort; |
||||
|
||||
// Connected peer
|
||||
CIPAddress m_InitiatorIPAddress; |
||||
u16 m_nInitiatorControlPort; |
||||
u16 m_nInitiatorMIDIPort; |
||||
|
||||
// Socket receive buffers
|
||||
u8 m_ControlBuffer[FRAME_BUFFER_SIZE]; |
||||
u8 m_MIDIBuffer[FRAME_BUFFER_SIZE]; |
||||
|
||||
int m_nControlResult; |
||||
int m_nMIDIResult; |
||||
|
||||
// Callback handler
|
||||
CAppleMIDIHandler* m_pHandler; |
||||
|
||||
// Participant state machine
|
||||
enum class TState |
||||
{ |
||||
ControlInvitation, |
||||
MIDIInvitation, |
||||
Connected |
||||
}; |
||||
|
||||
TState m_State; |
||||
|
||||
u32 m_nInitiatorToken = 0; |
||||
u32 m_nInitiatorSSRC = 0; |
||||
u32 m_nSSRC = 0; |
||||
u32 m_nLastMIDISequenceNumber = 0; |
||||
|
||||
u64 m_nOffsetEstimate = 0; |
||||
u64 m_nLastSyncTime = 0; |
||||
|
||||
u16 m_nSequence = 0; |
||||
u16 m_nLastFeedbackSequence = 0; |
||||
u64 m_nLastFeedbackTime = 0; |
||||
}; |
||||
|
||||
#endif |
@ -0,0 +1,42 @@ |
||||
//
|
||||
// byteorder.h
|
||||
//
|
||||
// mt32-pi - A baremetal MIDI synthesizer for Raspberry Pi
|
||||
// Copyright (C) 2020-2023 Dale Whinham <daleyo@gmail.com>
|
||||
//
|
||||
// This file is part of mt32-pi.
|
||||
//
|
||||
// mt32-pi is free software: you can redistribute it and/or modify it under the
|
||||
// terms of the GNU General Public License as published by the Free Software
|
||||
// Foundation, either version 3 of the License, or (at your option) any later
|
||||
// version.
|
||||
//
|
||||
// mt32-pi is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
// details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along with
|
||||
// mt32-pi. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
#ifndef _byteorder_h |
||||
#define _byteorder_h |
||||
|
||||
#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ |
||||
#define htons(VALUE) (VALUE) |
||||
#define htonl(VALUE) (VALUE) |
||||
#define htonll(VALUE) (VALUE) |
||||
#define ntohs(VALUE) (VALUE) |
||||
#define ntohl(VALUE) (VALUE) |
||||
#define ntohll(VALUE) (VALUE) |
||||
#else |
||||
#define htons(VALUE) __builtin_bswap16(VALUE) |
||||
#define htonl(VALUE) __builtin_bswap32(VALUE) |
||||
#define htonll(VALUE) __builtin_bswap64(VALUE) |
||||
#define ntohs(VALUE) __builtin_bswap16(VALUE) |
||||
#define ntohl(VALUE) __builtin_bswap32(VALUE) |
||||
#define ntohll(VALUE) __builtin_bswap64(VALUE) |
||||
#endif |
||||
|
||||
#endif |
@ -0,0 +1,111 @@ |
||||
//
|
||||
// ftpdaemon.cpp
|
||||
//
|
||||
// mt32-pi - A baremetal MIDI synthesizer for Raspberry Pi
|
||||
// Copyright (C) 2020-2023 Dale Whinham <daleyo@gmail.com>
|
||||
//
|
||||
// This file is part of mt32-pi.
|
||||
//
|
||||
// mt32-pi is free software: you can redistribute it and/or modify it under the
|
||||
// terms of the GNU General Public License as published by the Free Software
|
||||
// Foundation, either version 3 of the License, or (at your option) any later
|
||||
// version.
|
||||
//
|
||||
// mt32-pi is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
// details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along with
|
||||
// mt32-pi. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
#include <circle/logger.h> |
||||
#include <circle/net/in.h> |
||||
#include <circle/net/ipaddress.h> |
||||
#include <circle/net/netsubsystem.h> |
||||
#include <circle/string.h> |
||||
|
||||
#include "ftpdaemon.h" |
||||
#include "ftpworker.h" |
||||
|
||||
LOGMODULE("ftpd"); |
||||
|
||||
constexpr u16 ListenPort = 21; |
||||
constexpr u8 MaxConnections = 1; |
||||
|
||||
CFTPDaemon::CFTPDaemon(const char* pUser, const char* pPassword) |
||||
: CTask(TASK_STACK_SIZE, true), |
||||
m_pListenSocket(nullptr), |
||||
m_pUser(pUser), |
||||
m_pPassword(pPassword) |
||||
{ |
||||
} |
||||
|
||||
CFTPDaemon::~CFTPDaemon() |
||||
{ |
||||
if (m_pListenSocket) |
||||
delete m_pListenSocket; |
||||
} |
||||
|
||||
bool CFTPDaemon::Initialize() |
||||
{ |
||||
CNetSubSystem* const pNet = CNetSubSystem::Get(); |
||||
|
||||
if ((m_pListenSocket = new CSocket(pNet, IPPROTO_TCP)) == nullptr) |
||||
return false; |
||||
|
||||
if (m_pListenSocket->Bind(ListenPort) != 0) |
||||
{ |
||||
LOGERR("Couldn't bind to port %d", ListenPort); |
||||
return false; |
||||
} |
||||
|
||||
if (m_pListenSocket->Listen() != 0) |
||||
{ |
||||
LOGERR("Failed to listen on control socket"); |
||||
return false; |
||||
} |
||||
|
||||
// We started as a suspended task; run now that initialization is successful
|
||||
Start(); |
||||
|
||||
return true; |
||||
} |
||||
|
||||
void CFTPDaemon::Run() |
||||
{ |
||||
assert(m_pListenSocket != nullptr); |
||||
|
||||
LOGNOTE("Listener task spawned"); |
||||
|
||||
while (true) |
||||
{ |
||||
CIPAddress ClientIPAddress; |
||||
u16 nClientPort; |
||||
|
||||
LOGDBG("Listener: waiting for connection"); |
||||
CSocket* pConnection = m_pListenSocket->Accept(&ClientIPAddress, &nClientPort); |
||||
|
||||
if (pConnection == nullptr) |
||||
{ |
||||
LOGERR("Unable to accept connection"); |
||||
continue; |
||||
} |
||||
|
||||
CString IPAddressString; |
||||
ClientIPAddress.Format(&IPAddressString); |
||||
LOGNOTE("Incoming connection from %s:%d", static_cast<const char*>(IPAddressString), nClientPort); |
||||
|
||||
if (CFTPWorker::GetInstanceCount() >= MaxConnections) |
||||
{ |
||||
pConnection->Send("421 Maximum number of connections reached.\r\n", 45, 0); |
||||
delete pConnection; |
||||
LOGWARN("Maximum number of connections reached"); |
||||
continue; |
||||
} |
||||
|
||||
// Spawn new worker
|
||||
new CFTPWorker(pConnection, m_pUser, m_pPassword); |
||||
} |
||||
} |
@ -0,0 +1,47 @@ |
||||
//
|
||||
// ftpdaemon.h
|
||||
//
|
||||
// mt32-pi - A baremetal MIDI synthesizer for Raspberry Pi
|
||||
// Copyright (C) 2020-2023 Dale Whinham <daleyo@gmail.com>
|
||||
//
|
||||
// This file is part of mt32-pi.
|
||||
//
|
||||
// mt32-pi is free software: you can redistribute it and/or modify it under the
|
||||
// terms of the GNU General Public License as published by the Free Software
|
||||
// Foundation, either version 3 of the License, or (at your option) any later
|
||||
// version.
|
||||
//
|
||||
// mt32-pi is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
// details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along with
|
||||
// mt32-pi. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
#ifndef _ftpdaemon_h |
||||
#define _ftpdaemon_h |
||||
|
||||
#include <circle/net/socket.h> |
||||
#include <circle/sched/task.h> |
||||
|
||||
class CFTPDaemon : protected CTask |
||||
{ |
||||
public: |
||||
CFTPDaemon(const char* pUser, const char* pPassword); |
||||
virtual ~CFTPDaemon() override; |
||||
|
||||
bool Initialize(); |
||||
|
||||
virtual void Run() override; |
||||
|
||||
private: |
||||
// TCP sockets
|
||||
CSocket* m_pListenSocket; |
||||
|
||||
const char* m_pUser; |
||||
const char* m_pPassword; |
||||
}; |
||||
|
||||
#endif |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,157 @@ |
||||
//
|
||||
// ftpworker.h
|
||||
//
|
||||
// mt32-pi - A baremetal MIDI synthesizer for Raspberry Pi
|
||||
// Copyright (C) 2020-2023 Dale Whinham <daleyo@gmail.com>
|
||||
//
|
||||
// This file is part of mt32-pi.
|
||||
//
|
||||
// mt32-pi is free software: you can redistribute it and/or modify it under the
|
||||
// terms of the GNU General Public License as published by the Free Software
|
||||
// Foundation, either version 3 of the License, or (at your option) any later
|
||||
// version.
|
||||
//
|
||||
// mt32-pi is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
// details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along with
|
||||
// mt32-pi. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
#ifndef _ftpworker_h |
||||
#define _ftpworker_h |
||||
|
||||
#include <circle/net/ipaddress.h> |
||||
#include <circle/net/socket.h> |
||||
#include <circle/sched/task.h> |
||||
#include <circle/string.h> |
||||
|
||||
// TODO: These may be incomplete/inaccurate
|
||||
enum TFTPStatus |
||||
{ |
||||
FileStatusOk = 150, |
||||
|
||||
Success = 200, |
||||
SystemType = 215, |
||||
ReadyForNewUser = 220, |
||||
ClosingControl = 221, |
||||
TransferComplete = 226, |
||||
EnteringPassiveMode = 227, |
||||
UserLoggedIn = 230, |
||||
FileActionOk = 250, |
||||
PathCreated = 257, |
||||
|
||||
PasswordRequired = 331, |
||||
AccountRequired = 332, |
||||
PendingFurtherInfo = 350, |
||||
|
||||
ServiceNotAvailable = 421, |
||||
DataConnectionFailed = 425, |
||||
FileActionNotTaken = 450, |
||||
ActionAborted = 451, |
||||
|
||||
CommandUnrecognized = 500, |
||||
SyntaxError = 501, |
||||
CommandNotImplemented = 502, |
||||
BadCommandSequence = 503, |
||||
NotLoggedIn = 530, |
||||
FileNotFound = 550, |
||||
FileNameNotAllowed = 553, |
||||
}; |
||||
|
||||
enum class TTransferMode |
||||
{ |
||||
Active, |
||||
Passive, |
||||
}; |
||||
|
||||
enum class TDataType |
||||
{ |
||||
ASCII, |
||||
Binary, |
||||
}; |
||||
|
||||
struct TFTPCommand; |
||||
struct TDirectoryListEntry; |
||||
|
||||
class CFTPWorker : protected CTask |
||||
{ |
||||
public: |
||||
CFTPWorker(CSocket* pControlSocket, const char* pExpectedUser, const char* pExpectedPassword); |
||||
virtual ~CFTPWorker() override; |
||||
|
||||
virtual void Run() override; |
||||
|
||||
static u8 GetInstanceCount() { return s_nInstanceCount; } |
||||
|
||||
private: |
||||
CSocket* OpenDataConnection(); |
||||
|
||||
bool SendStatus(TFTPStatus StatusCode, const char* pMessage); |
||||
|
||||
bool CheckLoggedIn(); |
||||
|
||||
// Directory navigation
|
||||
CString RealPath(const char* pInBuffer) const; |
||||
const TDirectoryListEntry* BuildDirectoryList(size_t& nOutEntries) const; |
||||
|
||||
// FTP command handlers
|
||||
bool System(const char* pArgs); |
||||
bool Username(const char* pArgs); |
||||
bool Port(const char* pArgs); |
||||
bool Passive(const char* pArgs); |
||||
bool Password(const char* pArgs); |
||||
bool Type(const char* pArgs); |
||||
bool Retrieve(const char* pArgs); |
||||
bool Store(const char* pArgs); |
||||
bool Delete(const char* pArgs); |
||||
bool MakeDirectory(const char* pArgs); |
||||
bool ChangeWorkingDirectory(const char* pArgs); |
||||
bool ChangeToParentDirectory(const char* pArgs); |
||||
bool PrintWorkingDirectory(const char* pArgs); |
||||
bool List(const char* pArgs); |
||||
bool ListFileNames(const char* pArgs); |
||||
bool RenameFrom(const char* pArgs); |
||||
bool RenameTo(const char* pArgs); |
||||
bool Bye(const char* pArgs); |
||||
bool NoOp(const char* pArgs); |
||||
|
||||
CString m_LogName; |
||||
|
||||
// Authentication
|
||||
const char* m_pExpectedUser; |
||||
const char* m_pExpectedPassword; |
||||
|
||||
// TCP sockets
|
||||
CSocket* m_pControlSocket; |
||||
CSocket* m_pDataSocket; |
||||
u16 m_nDataSocketPort; |
||||
CIPAddress m_DataSocketIPAddress; |
||||
|
||||
// Command/data buffers
|
||||
char m_CommandBuffer[FRAME_BUFFER_SIZE]; |
||||
u8 m_DataBuffer[FRAME_BUFFER_SIZE]; |
||||
|
||||
// Session state
|
||||
CString m_User; |
||||
CString m_Password; |
||||
TDataType m_DataType; |
||||
TTransferMode m_TransferMode; |
||||
CString m_CurrentPath; |
||||
CString m_RenameFrom; |
||||
|
||||
static void FatFsPathToFTPPath(const char* pInBuffer, char* pOutBuffer, size_t nSize); |
||||
static void FTPPathToFatFsPath(const char* pInBuffer, char* pOutBuffer, size_t nSize); |
||||
|
||||
static void FatFsParentPath(const char* pInBuffer, char* pOutBuffer, size_t nSize); |
||||
|
||||
static void FormatLastModifiedDate(u16 nDate, char* pOutBuffer, size_t nSize); |
||||
static void FormatLastModifiedTime(u16 nDate, char* pOutBuffer, size_t nSize); |
||||
|
||||
static const TFTPCommand Commands[]; |
||||
static u8 s_nInstanceCount; |
||||
}; |
||||
|
||||
#endif |
@ -0,0 +1,89 @@ |
||||
//
|
||||
// udpmidi.cpp
|
||||
//
|
||||
// mt32-pi - A baremetal MIDI synthesizer for Raspberry Pi
|
||||
// Copyright (C) 2020-2023 Dale Whinham <daleyo@gmail.com>
|
||||
//
|
||||
// This file is part of mt32-pi.
|
||||
//
|
||||
// mt32-pi is free software: you can redistribute it and/or modify it under the
|
||||
// terms of the GNU General Public License as published by the Free Software
|
||||
// Foundation, either version 3 of the License, or (at your option) any later
|
||||
// version.
|
||||
//
|
||||
// mt32-pi is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
// details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along with
|
||||
// mt32-pi. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
#include <circle/logger.h> |
||||
#include <circle/net/in.h> |
||||
#include <circle/net/netsubsystem.h> |
||||
#include <circle/sched/scheduler.h> |
||||
|
||||
#include "udpmidi.h" |
||||
|
||||
LOGMODULE("udpmidi"); |
||||
|
||||
constexpr u16 MIDIPort = 1999; |
||||
|
||||
CUDPMIDIReceiver::CUDPMIDIReceiver(CUDPMIDIHandler* pHandler) |
||||
: CTask(TASK_STACK_SIZE, true), |
||||
m_pMIDISocket(nullptr), |
||||
m_MIDIBuffer{0}, |
||||
m_pHandler(pHandler) |
||||
{ |
||||
} |
||||
|
||||
CUDPMIDIReceiver::~CUDPMIDIReceiver() |
||||
{ |
||||
if (m_pMIDISocket) |
||||
delete m_pMIDISocket; |
||||
} |
||||
|
||||
bool CUDPMIDIReceiver::Initialize() |
||||
{ |
||||
assert(m_pMIDISocket == nullptr); |
||||
|
||||
CNetSubSystem* const pNet = CNetSubSystem::Get(); |
||||
|
||||
if ((m_pMIDISocket = new CSocket(pNet, IPPROTO_UDP)) == nullptr) |
||||
return false; |
||||
|
||||
if (m_pMIDISocket->Bind(MIDIPort) != 0) |
||||
{ |
||||
LOGERR("Couldn't bind to port %d", MIDIPort); |
||||
return false; |
||||
} |
||||
|
||||
// We started as a suspended task; run now that initialization is successful
|
||||
Start(); |
||||
|
||||
return true; |
||||
} |
||||
|
||||
void CUDPMIDIReceiver::Run() |
||||
{ |
||||
assert(m_pHandler != nullptr); |
||||
assert(m_pMIDISocket != nullptr); |
||||
|
||||
CScheduler* const pScheduler = CScheduler::Get(); |
||||
|
||||
while (true) |
||||
{ |
||||
// Blocking call
|
||||
const int nMIDIResult = m_pMIDISocket->Receive(m_MIDIBuffer, sizeof(m_MIDIBuffer), 0); |
||||
|
||||
if (nMIDIResult < 0) |
||||
LOGERR("MIDI socket receive error: %d", nMIDIResult); |
||||
else if (nMIDIResult > 0) |
||||
m_pHandler->OnUDPMIDIDataReceived(m_MIDIBuffer, nMIDIResult); |
||||
|
||||
// Allow other tasks to run
|
||||
pScheduler->Yield(); |
||||
} |
||||
} |
@ -0,0 +1,57 @@ |
||||
//
|
||||
// udpmidi.h
|
||||
//
|
||||
// mt32-pi - A baremetal MIDI synthesizer for Raspberry Pi
|
||||
// Copyright (C) 2020-2023 Dale Whinham <daleyo@gmail.com>
|
||||
//
|
||||
// This file is part of mt32-pi.
|
||||
//
|
||||
// mt32-pi is free software: you can redistribute it and/or modify it under the
|
||||
// terms of the GNU General Public License as published by the Free Software
|
||||
// Foundation, either version 3 of the License, or (at your option) any later
|
||||
// version.
|
||||
//
|
||||
// mt32-pi is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
// details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along with
|
||||
// mt32-pi. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
#ifndef _udpmidi_h |
||||
#define _udpmidi_h |
||||
|
||||
#include <circle/net/ipaddress.h> |
||||
#include <circle/net/socket.h> |
||||
#include <circle/sched/task.h> |
||||
|
||||
class CUDPMIDIHandler |
||||
{ |
||||
public: |
||||
virtual void OnUDPMIDIDataReceived(const u8* pData, size_t nSize) = 0; |
||||
}; |
||||
|
||||
class CUDPMIDIReceiver : protected CTask |
||||
{ |
||||
public: |
||||
CUDPMIDIReceiver(CUDPMIDIHandler* pHandler); |
||||
virtual ~CUDPMIDIReceiver() override; |
||||
|
||||
bool Initialize(); |
||||
|
||||
virtual void Run() override; |
||||
|
||||
private: |
||||
// UDP sockets
|
||||
CSocket* m_pMIDISocket; |
||||
|
||||
// Socket receive buffer
|
||||
u8 m_MIDIBuffer[FRAME_BUFFER_SIZE]; |
||||
|
||||
// Callback handler
|
||||
CUDPMIDIHandler* m_pHandler; |
||||
}; |
||||
|
||||
#endif |
@ -0,0 +1,193 @@ |
||||
|
||||
//
|
||||
// utility.h
|
||||
//
|
||||
// mt32-pi - A baremetal MIDI synthesizer for Raspberry Pi
|
||||
// Copyright (C) 2020-2023 Dale Whinham <daleyo@gmail.com>
|
||||
//
|
||||
// This file is part of mt32-pi.
|
||||
//
|
||||
// mt32-pi is free software: you can redistribute it and/or modify it under the
|
||||
// terms of the GNU General Public License as published by the Free Software
|
||||
// Foundation, either version 3 of the License, or (at your option) any later
|
||||
// version.
|
||||
//
|
||||
// mt32-pi is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
// details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along with
|
||||
// mt32-pi. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
#ifndef _utility_h |
||||
#define _utility_h |
||||
|
||||
#include <circle/string.h> |
||||
#include <circle/util.h> |
||||
|
||||
// Macro to extract the string representation of an enum
|
||||
#define CONFIG_ENUM_VALUE(VALUE, STRING) VALUE, |
||||
|
||||
// Macro to extract the enum value
|
||||
#define CONFIG_ENUM_STRING(VALUE, STRING) #STRING, |
||||
|
||||
// Macro to declare the enum itself
|
||||
#define CONFIG_ENUM(NAME, VALUES) enum class NAME { VALUES(CONFIG_ENUM_VALUE) } |
||||
|
||||
// Macro to declare an array of string representations for an enum
|
||||
#define CONFIG_ENUM_STRINGS(NAME, DATA) static const char* NAME##Strings[] = { DATA(CONFIG_ENUM_STRING) } |
||||
|
||||
namespace Utility |
||||
{ |
||||
// Templated function for clamping a value between a minimum and a maximum
|
||||
template <class T> |
||||
constexpr T Clamp(const T& nValue, const T& nMin, const T& nMax) |
||||
{ |
||||
return (nValue < nMin) ? nMin : (nValue > nMax) ? nMax : nValue; |
||||
} |
||||
|
||||
// Templated function for taking the minimum of two values
|
||||
template <class T> |
||||
constexpr T Min(const T& nLHS, const T& nRHS) |
||||
{ |
||||
return nLHS < nRHS ? nLHS : nRHS; |
||||
} |
||||
|
||||
// Templated function for taking the maximum of two values
|
||||
template <class T> |
||||
constexpr T Max(const T& nLHS, const T& nRHS) |
||||
{ |
||||
return nLHS > nRHS ? nLHS : nRHS; |
||||
} |
||||
|
||||
// Function for performing a linear interpolation of a value
|
||||
constexpr float Lerp(float nValue, float nMinA, float nMaxA, float nMinB, float nMaxB) |
||||
{ |
||||
return nMinB + (nValue - nMinA) * ((nMaxB - nMinB) / (nMaxA - nMinA)); |
||||
} |
||||
|
||||
// Return number of elements in an array
|
||||
template <class T, size_t N> |
||||
constexpr size_t ArraySize(const T(&)[N]) { return N; } |
||||
|
||||
// Returns whether some value is a power of 2
|
||||
template <class T> |
||||
constexpr bool IsPowerOfTwo(const T& nValue) |
||||
{ |
||||
return nValue && ((nValue & (nValue - 1)) == 0); |
||||
} |
||||
|
||||
// Rounds a number to a nearest multiple; only works for integer values/multiples
|
||||
template <class T> |
||||
constexpr T RoundToNearestMultiple(const T& nValue, const T& nMultiple) |
||||
{ |
||||
return ((nValue + nMultiple / 2) / nMultiple) * nMultiple; |
||||
} |
||||
|
||||
// Convert between milliseconds and ticks of a 1MHz clock
|
||||
template <class T> |
||||
constexpr T MillisToTicks(const T& nMillis) |
||||
{ |
||||
return nMillis * 1000; |
||||
} |
||||
|
||||
template <class T> |
||||
constexpr T TicksToMillis(const T& nTicks) |
||||
{ |
||||
return nTicks / 1000; |
||||
} |
||||
|
||||
// Computes the Roland checksum
|
||||
constexpr u8 RolandChecksum(const u8* pData, size_t nSize) |
||||
{ |
||||
u8 nSum = 0; |
||||
for (size_t i = 0; i < nSize; ++i) |
||||
nSum = (nSum + pData[i]) & 0x7F; |
||||
|
||||
return 128 - nSum; |
||||
} |
||||
|
||||
// Comparators for sorting
|
||||
namespace Comparator |
||||
{ |
||||
template<class T> |
||||
using TComparator = bool (*)(const T&, const T&); |
||||
|
||||
template<class T> |
||||
inline bool LessThan(const T& ObjectA, const T& ObjectB) |
||||
{ |
||||
return ObjectA < ObjectB; |
||||
} |
||||
|
||||
template<class T> |
||||
inline bool GreaterThan(const T& ObjectA, const T& ObjectB) |
||||
{ |
||||
return ObjectA > ObjectB; |
||||
} |
||||
|
||||
inline bool CaseInsensitiveAscending(const CString& StringA, const CString& StringB) |
||||
{ |
||||
return strcasecmp(StringA, StringB) < 0; |
||||
} |
||||
} |
||||
|
||||
// Swaps two objects in-place
|
||||
template<class T> |
||||
inline void Swap(T& ObjectA, T& ObjectB) |
||||
{ |
||||
u8 Buffer[sizeof(T)]; |
||||
memcpy(Buffer, &ObjectA, sizeof(T)); |
||||
memcpy(&ObjectA, &ObjectB, sizeof(T)); |
||||
memcpy(&ObjectB, Buffer, sizeof(T)); |
||||
} |
||||
|
||||
namespace |
||||
{ |
||||
// Quicksort partition function (private)
|
||||
template<class T> |
||||
size_t Partition(T* Items, Comparator::TComparator<T> Comparator, size_t nLow, size_t nHigh) |
||||
{ |
||||
const size_t nPivotIndex = (nHigh + nLow) / 2; |
||||
T* Pivot = &Items[nPivotIndex]; |
||||
|
||||
while (true) |
||||
{ |
||||
while (Comparator(Items[nLow], *Pivot)) |
||||
++nLow; |
||||
|
||||
while (Comparator(*Pivot, Items[nHigh])) |
||||
--nHigh; |
||||
|
||||
if (nLow >= nHigh) |
||||
return nHigh; |
||||
|
||||
Swap(Items[nLow], Items[nHigh]); |
||||
|
||||
// Update pointer if pivot was swapped
|
||||
if (nPivotIndex == nLow) |
||||
Pivot = &Items[nHigh]; |
||||
else if (nPivotIndex == nHigh) |
||||
Pivot = &Items[nLow]; |
||||
|
||||
++nLow; |
||||
--nHigh; |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Sorts an array in-place using the Tony Hoare Quicksort algorithm
|
||||
template <class T> |
||||
void QSort(T* Items, Comparator::TComparator<T> Comparator, size_t nLow, size_t nHigh) |
||||
{ |
||||
if (nLow < nHigh) |
||||
{ |
||||
size_t p = Partition(Items, Comparator, nLow, nHigh); |
||||
QSort(Items, Comparator, nLow, p); |
||||
QSort(Items, Comparator, p + 1, nHigh); |
||||
} |
||||
} |
||||
} |
||||
|
||||
#endif |
@ -0,0 +1,80 @@ |
||||
//
|
||||
// serialmididevice.cpp
|
||||
//
|
||||
// MiniDexed - Dexed FM synthesizer for bare metal Raspberry Pi
|
||||
// Copyright (C) 2022 The MiniDexed Team
|
||||
//
|
||||
// Original author of this class:
|
||||
// R. Stange <rsta2@o2online.de>
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
#include <circle/logger.h> |
||||
#include <cstring> |
||||
#include "rtpmididevice.h" |
||||
#include <assert.h> |
||||
|
||||
LOGMODULE("rtpmididevice"); |
||||
|
||||
CRTPMIDIDevice::CRTPMIDIDevice (CMiniDexed *pSynthesizer, |
||||
CConfig *pConfig, CUserInterface *pUI) |
||||
: CMIDIDevice (pSynthesizer, pConfig, pUI), |
||||
m_pConfig (pConfig) |
||||
|
||||
//m_Serial (pInterrupt, TRUE),
|
||||
//m_nSerialState (0),
|
||||
//m_nSysEx (0),
|
||||
//m_SendBuffer (&m_Serial)
|
||||
{ |
||||
AddDevice ("rtpdummy"); |
||||
} |
||||
|
||||
CRTPMIDIDevice::~CRTPMIDIDevice (void) |
||||
{ |
||||
//m_nSerialState = 255;
|
||||
} |
||||
|
||||
boolean CRTPMIDIDevice::Initialize (void) |
||||
{ |
||||
m_pAppleMIDIParticipant = new CAppleMIDIParticipant(&m_Random, this); |
||||
if (!m_pAppleMIDIParticipant->Initialize()) |
||||
{ |
||||
LOGERR("Failed to init RTP listener"); |
||||
return false; //continue without rtp midi
|
||||
} |
||||
else |
||||
LOGNOTE("RTP Listener initialized"); |
||||
return true; |
||||
} |
||||
|
||||
// Methods to handle MIDI events
|
||||
|
||||
void CRTPMIDIDevice::OnAppleMIDIDataReceived(const u8* pData, size_t nSize) |
||||
{ |
||||
LOGNOTE("Recieved RTP MIDI Data"); |
||||
printf ("MIDI-RTP: %02X %02X\n", |
||||
(unsigned) pData[0], (unsigned) pData[1]); |
||||
MIDIMessageHandler(pData, nSize); |
||||
} |
||||
|
||||
void CRTPMIDIDevice::OnAppleMIDIConnect(const CIPAddress* pIPAddress, const char* pName) |
||||
{ |
||||
LOGNOTE("RTP Device connected"); |
||||
} |
||||
|
||||
void CRTPMIDIDevice::OnAppleMIDIDisconnect(const CIPAddress* pIPAddress, const char* pName) |
||||
{ |
||||
// RemoveRTPDevice
|
||||
} |
@ -0,0 +1,69 @@ |
||||
//
|
||||
// .h
|
||||
//
|
||||
// MiniDexed - Dexed FM synthesizer for bare metal Raspberry Pi
|
||||
// Copyright (C) 2022 The MiniDexed Team
|
||||
//
|
||||
// Original author of this class:
|
||||
// R. Stange <rsta2@o2online.de>
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
#ifndef _rtpmididevice_h |
||||
#define _rtpmididevice_h |
||||
|
||||
#include "mididevice.h" |
||||
#include "config.h" |
||||
#include "net/applemidi.h" |
||||
|
||||
#include <circle/interrupt.h> |
||||
#include <circle/serial.h> |
||||
#include <circle/writebuffer.h> |
||||
#include <circle/types.h> |
||||
|
||||
class CMiniDexed; |
||||
|
||||
class CRTPMIDIDevice : CAppleMIDIHandler, CMIDIDevice |
||||
{ |
||||
public: |
||||
CRTPMIDIDevice (CMiniDexed *pSynthesizer, CConfig *pConfig, CUserInterface *pUI); |
||||
~CRTPMIDIDevice (void); |
||||
|
||||
boolean Initialize (void); |
||||
virtual void OnAppleMIDIDataReceived(const u8* pData, size_t nSize) override; |
||||
virtual void OnAppleMIDIConnect(const CIPAddress* pIPAddress, const char* pName) override; |
||||
virtual void OnAppleMIDIDisconnect(const CIPAddress* pIPAddress, const char* pName) override; |
||||
//void OnAppleMIDIDataReceived(const u8* pData, size_t nSize);
|
||||
//void OnAppleMIDIConnect(const CIPAddress* pIPAddress, const char* pName);
|
||||
//void OnAppleMIDIDisconnect(const CIPAddress* pIPAddress, const char* pName);
|
||||
|
||||
//void Process (void);
|
||||
|
||||
//void Send (const u8 *pMessage, size_t nLength, unsigned nCable = 0) override;
|
||||
|
||||
private: |
||||
CConfig *m_pConfig; |
||||
|
||||
//CSerialDevice m_Serial;
|
||||
//unsigned m_nSerialState;
|
||||
//unsigned m_nSysEx;
|
||||
//u8 m_SerialMessage[MAX_MIDI_MESSAGE];
|
||||
|
||||
//CWriteBufferDevice m_SendBuffer;
|
||||
CBcmRandomNumberGenerator m_Random; |
||||
//CAppleMIDIHandler* m_MIDIHandler;
|
||||
CAppleMIDIParticipant* m_pAppleMIDIParticipant; // AppleMIDI participant instance
|
||||
}; |
||||
|
||||
#endif |
@ -0,0 +1,89 @@ |
||||
//
|
||||
// udpmididevice.cpp
|
||||
//
|
||||
// MiniDexed - Dexed FM synthesizer for bare metal Raspberry Pi
|
||||
// Copyright (C) 2022 The MiniDexed Team
|
||||
//
|
||||
// Original author of this class:
|
||||
// R. Stange <rsta2@o2online.de>
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
#include <circle/logger.h> |
||||
#include <cstring> |
||||
#include "udpmididevice.h" |
||||
#include <assert.h> |
||||
|
||||
#define VIRTUALCABLE 24 |
||||
|
||||
LOGMODULE("rtpmididevice"); |
||||
|
||||
CUDPMIDIDevice::CUDPMIDIDevice (CMiniDexed *pSynthesizer, |
||||
CConfig *pConfig, CUserInterface *pUI) |
||||
: CMIDIDevice (pSynthesizer, pConfig, pUI), |
||||
m_pSynthesizer (pSynthesizer), |
||||
m_pConfig (pConfig) |
||||
{ |
||||
AddDevice ("udp"); |
||||
} |
||||
|
||||
CUDPMIDIDevice::~CUDPMIDIDevice (void) |
||||
{ |
||||
//m_pSynthesizer = 0;
|
||||
} |
||||
|
||||
boolean CUDPMIDIDevice::Initialize (void) |
||||
{ |
||||
m_pAppleMIDIParticipant = new CAppleMIDIParticipant(&m_Random, this); |
||||
if (!m_pAppleMIDIParticipant->Initialize()) |
||||
{ |
||||
LOGERR("Failed to init RTP listener"); |
||||
return false; //continue without rtp midi
|
||||
} |
||||
else |
||||
LOGNOTE("RTP Listener initialized"); |
||||
return true; |
||||
m_pUDPMIDIReceiver = new CUDPMIDIReceiver(this); |
||||
if (!m_pUDPMIDIReceiver->Initialize()) |
||||
{ |
||||
LOGERR("Failed to init UDP MIDI receiver"); |
||||
delete m_pUDPMIDIReceiver; |
||||
m_pUDPMIDIReceiver = nullptr; |
||||
} |
||||
else |
||||
LOGNOTE("UDP MIDI receiver initialized"); |
||||
} |
||||
|
||||
// Methods to handle MIDI events
|
||||
|
||||
void CUDPMIDIDevice::OnAppleMIDIDataReceived(const u8* pData, size_t nSize) |
||||
{ |
||||
MIDIMessageHandler(pData, nSize, VIRTUALCABLE); |
||||
} |
||||
|
||||
void CUDPMIDIDevice::OnAppleMIDIConnect(const CIPAddress* pIPAddress, const char* pName) |
||||
{ |
||||
LOGNOTE("RTP Device connected"); |
||||
} |
||||
|
||||
void CUDPMIDIDevice::OnAppleMIDIDisconnect(const CIPAddress* pIPAddress, const char* pName) |
||||
{ |
||||
LOGNOTE("RTP Device disconnected"); |
||||
} |
||||
|
||||
void CUDPMIDIDevice::OnUDPMIDIDataReceived(const u8* pData, size_t nSize) |
||||
{ |
||||
MIDIMessageHandler(pData, nSize, VIRTUALCABLE); |
||||
} |
@ -0,0 +1,56 @@ |
||||
//
|
||||
// udpmididevice.h
|
||||
//
|
||||
// Virtual midi device for data recieved on network
|
||||
//
|
||||
// MiniDexed - Dexed FM synthesizer for bare metal Raspberry Pi
|
||||
// Copyright (C) 2022 The MiniDexed Team
|
||||
//
|
||||
// Original author of this class:
|
||||
// R. Stange <rsta2@o2online.de>
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
#ifndef _rtpmididevice_h |
||||
#define _rtpmididevice_h |
||||
|
||||
#include "mididevice.h" |
||||
#include "config.h" |
||||
#include "net/applemidi.h" |
||||
#include "net/udpmidi.h" |
||||
|
||||
class CMiniDexed; |
||||
|
||||
class CUDPMIDIDevice : CAppleMIDIHandler, CUDPMIDIHandler, public CMIDIDevice |
||||
{ |
||||
public: |
||||
CUDPMIDIDevice (CMiniDexed *pSynthesizer, CConfig *pConfig, CUserInterface *pUI); |
||||
~CUDPMIDIDevice (void); |
||||
|
||||
boolean Initialize (void); |
||||
virtual void OnAppleMIDIDataReceived(const u8* pData, size_t nSize) override; |
||||
virtual void OnAppleMIDIConnect(const CIPAddress* pIPAddress, const char* pName) override; |
||||
virtual void OnAppleMIDIDisconnect(const CIPAddress* pIPAddress, const char* pName) override; |
||||
virtual void OnUDPMIDIDataReceived(const u8* pData, size_t nSize) override; |
||||
|
||||
private: |
||||
CMiniDexed *m_pSynthesizer; |
||||
CConfig *m_pConfig; |
||||
CBcmRandomNumberGenerator m_Random; |
||||
CAppleMIDIParticipant* m_pAppleMIDIParticipant; // AppleMIDI participant instance
|
||||
CUDPMIDIReceiver* m_pUDPMIDIReceiver; |
||||
|
||||
}; |
||||
|
||||
#endif |
Loading…
Reference in new issue