diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c4030af..33f24a8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -39,11 +39,6 @@ jobs: wget -q https://developer.arm.com/-/media/Files/downloads/gnu-a/10.3-2021.07/binrel/gcc-arm-10.3-2021.07-x86_64-aarch64-none-elf.tar.xz tar xf gcc-arm-10.3-2021.07-x86_64-aarch64-none-elf.tar.xz - # Patch Synth_Dexed until the fix is merged upstream; see https://github.com/probonopd/MiniDexed/issues/889 - - name: Patch Synth_Dexed - run: | - ( cd Synth_Dexed ; patch -p1 < ../src/patches/dx7note.patch ) - - name: Build for Raspberry Pi 5 (64-bit) run: | set -ex @@ -69,11 +64,15 @@ jobs: run: | set -ex export PATH=$(readlink -f ./gcc-arm-10.3-2021.07-x86_64-aarch64-none-elf/bin):$PATH - cd ./circle-stdlib/libs/circle/boot - make - make armstub64 - cd - - cp -r ./circle-stdlib/libs/circle/boot/* sdcard + # cd ./circle-stdlib/libs/circle/boot + # make + # make armstub64 + # cd - + # cp -r ./circle-stdlib/libs/circle/boot/* sdcard + cd ./sdcard + wget https://github.com/probonopd/MiniDexed/releases/download/assets/boot.zip + unzip boot.zip && rm boot.zip + cd .. rm -rf sdcard/config*.txt sdcard/README sdcard/Makefile sdcard/armstub sdcard/COPYING.linux cp ./src/config.txt ./src/minidexed.ini ./src/performance.ini sdcard/ cp ./getsysex.sh sdcard/ @@ -129,11 +128,6 @@ jobs: wget -q https://developer.arm.com/-/media/Files/downloads/gnu-a/10.3-2021.07/binrel/gcc-arm-10.3-2021.07-x86_64-arm-none-eabi.tar.xz tar xf gcc-arm-10.3-2021.07-x86_64-arm-none-eabi.tar.xz - # Patch Synth_Dexed until the fix is merged upstream; see https://github.com/probonopd/MiniDexed/issues/889 - - name: Patch Synth_Dexed - run: | - ( cd Synth_Dexed ; patch -p1 < ../src/patches/dx7note.patch ) - - name: Build for Raspberry Pi 2 (32-bit) run: | set -ex diff --git a/src/minidexed.cpp b/src/minidexed.cpp index 1e925ed..4a4ce46 100644 --- a/src/minidexed.cpp +++ b/src/minidexed.cpp @@ -1123,6 +1123,7 @@ void CMiniDexed::SetTGParameter (TTGParameter Parameter, int nValue, unsigned nT case TGParameterATAmplitude: setModController(3, 2, nValue, nTG); break; case TGParameterATEGBias: setModController(3, 3, nValue, nTG); break; + case TGParameterMIDIChannel: assert (0 <= nValue && nValue <= 255); SetMIDIChannel ((uint8_t) nValue, nTG); @@ -2306,7 +2307,7 @@ void CMiniDexed::UpdateNetwork() } if (m_pConfig->GetNetworkFTPEnabled()) { - m_pFTPDaemon = new CFTPDaemon(FTPUSERNAME, FTPPASSWORD); + m_pFTPDaemon = new CFTPDaemon(FTPUSERNAME, FTPPASSWORD, m_pmDNSPublisher, m_pConfig); if (!m_pFTPDaemon->Initialize()) { diff --git a/src/net/applemidi.cpp b/src/net/applemidi.cpp index e14b216..82014e1 100644 --- a/src/net/applemidi.cpp +++ b/src/net/applemidi.cpp @@ -457,7 +457,7 @@ bool ParseMIDIPacket(const u8* pBuffer, size_t nSize, TRTPMIDI* pOutPacket, CApp return ParseMIDICommandSection(pMIDICommandSection, nRemaining, pHandler); } -CAppleMIDIParticipant::CAppleMIDIParticipant(CBcmRandomNumberGenerator* pRandom, CAppleMIDIHandler* pHandler) +CAppleMIDIParticipant::CAppleMIDIParticipant(CBcmRandomNumberGenerator* pRandom, CAppleMIDIHandler* pHandler, const char* pSessionName) : CTask(TASK_STACK_SIZE, true), m_pRandom(pRandom), @@ -489,7 +489,9 @@ CAppleMIDIParticipant::CAppleMIDIParticipant(CBcmRandomNumberGenerator* pRandom, m_nSequence(0), m_nLastFeedbackSequence(0), - m_nLastFeedbackTime(0) + m_nLastFeedbackTime(0), + + m_pSessionName(pSessionName) { } @@ -802,8 +804,11 @@ bool CAppleMIDIParticipant::SendAcceptInvitationPacket(CSocket* pSocket, CIPAddr {'\0'} }; - // TODO: configurable name - strncpy(AcceptPacket.Name, "MiniDexed", sizeof(AcceptPacket.Name)); + // Use hostname as the session name + if (m_pSessionName && m_pSessionName[0]) + strncpy(AcceptPacket.Name, m_pSessionName, sizeof(AcceptPacket.Name)); + else + strncpy(AcceptPacket.Name, "MiniDexed", sizeof(AcceptPacket.Name)); #ifdef APPLEMIDI_DEBUG LOGNOTE("--> Accept invitation"); diff --git a/src/net/applemidi.h b/src/net/applemidi.h index 3df68ae..a774592 100644 --- a/src/net/applemidi.h +++ b/src/net/applemidi.h @@ -39,7 +39,7 @@ public: class CAppleMIDIParticipant : protected CTask { public: - CAppleMIDIParticipant(CBcmRandomNumberGenerator* pRandom, CAppleMIDIHandler* pHandler); + CAppleMIDIParticipant(CBcmRandomNumberGenerator* pRandom, CAppleMIDIHandler* pHandler, const char* pSessionName); virtual ~CAppleMIDIParticipant() override; bool Initialize(); @@ -106,6 +106,8 @@ private: u16 m_nSequence = 0; u16 m_nLastFeedbackSequence = 0; u64 m_nLastFeedbackTime = 0; + + const char* m_pSessionName; }; #endif \ No newline at end of file diff --git a/src/net/ftpdaemon.cpp b/src/net/ftpdaemon.cpp index 0cab51c..015dbc9 100644 --- a/src/net/ftpdaemon.cpp +++ b/src/net/ftpdaemon.cpp @@ -34,11 +34,13 @@ LOGMODULE("ftpd"); constexpr u16 ListenPort = 21; constexpr u8 MaxConnections = 1; -CFTPDaemon::CFTPDaemon(const char* pUser, const char* pPassword) +CFTPDaemon::CFTPDaemon(const char* pUser, const char* pPassword, CmDNSPublisher* pMDNSPublisher, CConfig* pConfig) : CTask(TASK_STACK_SIZE, true), m_pListenSocket(nullptr), m_pUser(pUser), - m_pPassword(pPassword) + m_pPassword(pPassword), + m_pmDNSPublisher(pMDNSPublisher), + m_pConfig(pConfig) { } @@ -106,6 +108,6 @@ void CFTPDaemon::Run() } // Spawn new worker - new CFTPWorker(pConnection, m_pUser, m_pPassword); + new CFTPWorker(pConnection, m_pUser, m_pPassword, m_pmDNSPublisher, m_pConfig); } } \ No newline at end of file diff --git a/src/net/ftpdaemon.h b/src/net/ftpdaemon.h index 4d75762..77afb14 100644 --- a/src/net/ftpdaemon.h +++ b/src/net/ftpdaemon.h @@ -25,11 +25,13 @@ #include #include +#include "mdnspublisher.h" +#include "../config.h" class CFTPDaemon : protected CTask { public: - CFTPDaemon(const char* pUser, const char* pPassword); + CFTPDaemon(const char* pUser, const char* pPassword, CmDNSPublisher* pMDNSPublisher, CConfig* pConfig); virtual ~CFTPDaemon() override; bool Initialize(); @@ -42,6 +44,8 @@ private: const char* m_pUser; const char* m_pPassword; + CmDNSPublisher* m_pmDNSPublisher; + CConfig* m_pConfig; }; #endif \ No newline at end of file diff --git a/src/net/ftpworker.cpp b/src/net/ftpworker.cpp index 28415e2..a19cfde 100644 --- a/src/net/ftpworker.cpp +++ b/src/net/ftpworker.cpp @@ -126,7 +126,7 @@ inline bool DirectoryCaseInsensitiveAscending(const TDirectoryListEntry& EntryA, } -CFTPWorker::CFTPWorker(CSocket* pControlSocket, const char* pExpectedUser, const char* pExpectedPassword) +CFTPWorker::CFTPWorker(CSocket* pControlSocket, const char* pExpectedUser, const char* pExpectedPassword, CmDNSPublisher* pMDNSPublisher, CConfig* pConfig) : CTask(TASK_STACK_SIZE), m_LogName(), m_pExpectedUser(pExpectedUser), @@ -142,7 +142,9 @@ CFTPWorker::CFTPWorker(CSocket* pControlSocket, const char* pExpectedUser, const m_DataType(TDataType::ASCII), m_TransferMode(TTransferMode::Active), m_CurrentPath(), - m_RenameFrom() + m_RenameFrom(), + m_pmDNSPublisher(pMDNSPublisher), + m_pConfig(pConfig) { ++s_nInstanceCount; m_LogName.Format("ftpd[%d]", s_nInstanceCount); @@ -562,7 +564,7 @@ bool CFTPWorker::Passive(const char* pArgs) IPAddress[2], IPAddress[3], (m_nDataSocketPort >> 8) & 0xFF, - m_nDataSocketPort & 0xFF + (m_nDataSocketPort & 0xFF) ); SendStatus(TFTPStatus::EnteringPassiveMode, Buffer); @@ -1058,9 +1060,24 @@ bool CFTPWorker::Bye(const char* pArgs) SendStatus(TFTPStatus::ClosingControl, "Goodbye."); delete m_pControlSocket; m_pControlSocket = nullptr; - + + // Unpublish the mDNS services + if (m_pmDNSPublisher && m_pConfig) + { + m_pmDNSPublisher->UnpublishService(m_pConfig->GetNetworkHostname()); + m_pmDNSPublisher->UnpublishService(m_pConfig->GetNetworkHostname(), CmDNSPublisher::ServiceTypeAppleMIDI, 5004); + m_pmDNSPublisher->UnpublishService(m_pConfig->GetNetworkHostname(), CmDNSPublisher::ServiceTypeFTP, 21); + } + + // Non-blocking 2 second delay before reboot + CTimer* const pTimer = CTimer::Get(); + unsigned int start = pTimer->GetTicks(); + while ((pTimer->GetTicks() - start) < 2 * HZ) { + CScheduler::Get()->Yield(); + } + // Reboot the system if the user disconnects in order to apply any changes made - reboot (); + reboot(); return true; } diff --git a/src/net/ftpworker.h b/src/net/ftpworker.h index 62e60ed..17abfda 100644 --- a/src/net/ftpworker.h +++ b/src/net/ftpworker.h @@ -27,6 +27,8 @@ #include #include #include +#include "../config.h" +#include "mdnspublisher.h" // TODO: These may be incomplete/inaccurate enum TFTPStatus @@ -79,7 +81,7 @@ struct TDirectoryListEntry; class CFTPWorker : protected CTask { public: - CFTPWorker(CSocket* pControlSocket, const char* pExpectedUser, const char* pExpectedPassword); + CFTPWorker(CSocket* pControlSocket, const char* pExpectedUser, const char* pExpectedPassword, CmDNSPublisher* pMDNSPublisher, CConfig* pConfig); virtual ~CFTPWorker() override; virtual void Run() override; @@ -142,6 +144,9 @@ private: CString m_CurrentPath; CString m_RenameFrom; + CmDNSPublisher* m_pmDNSPublisher; + CConfig* m_pConfig; + static void FatFsPathToFTPPath(const char* pInBuffer, char* pOutBuffer, size_t nSize); static void FTPPathToFatFsPath(const char* pInBuffer, char* pOutBuffer, size_t nSize); diff --git a/src/net/mdnspublisher.cpp b/src/net/mdnspublisher.cpp index fbeb549..49ae79b 100644 --- a/src/net/mdnspublisher.cpp +++ b/src/net/mdnspublisher.cpp @@ -131,6 +131,45 @@ boolean CmDNSPublisher::UnpublishService (const char *pServiceName) delete pService; return TRUE; } +boolean CmDNSPublisher::UnpublishService(const char *pServiceName, const char *pServiceType, u16 usServicePort) +{ + if (!m_bRunning) + { + return FALSE; + } + assert(pServiceName); + assert(pServiceType); + m_Mutex.Acquire(); + TService *pService = nullptr; + TPtrListElement *pElement = m_ServiceList.GetFirst(); + while (pElement) + { + pService = static_cast(CPtrList::GetPtr(pElement)); + assert(pService); + if (pService->ServiceName.Compare(pServiceName) == 0 && + pService->ServiceType.Compare(pServiceType) == 0 && + pService->usServicePort == usServicePort) + { + m_ServiceList.Remove(pElement); + break; + } + pService = nullptr; + pElement = m_ServiceList.GetNext(pElement); + } + m_Mutex.Release(); + if (!pService) + { + return FALSE; + } + LOGDBG("Unpublish service %s %s %u", (const char *)pService->ServiceName, (const char *)pService->ServiceType, pService->usServicePort); + SendResponse(pService, FALSE); + for (unsigned i = 0; i < pService->nTextRecords; i++) + { + delete pService->ppText[i]; + } + delete pService; + return TRUE; +} void CmDNSPublisher::Run (void) { assert (m_pNet); diff --git a/src/net/mdnspublisher.h b/src/net/mdnspublisher.h index 6b132a7..c26f6ea 100644 --- a/src/net/mdnspublisher.h +++ b/src/net/mdnspublisher.h @@ -32,6 +32,7 @@ class CmDNSPublisher : public CTask /// mDNS / Bonjour client task { public: static constexpr const char *ServiceTypeAppleMIDI = "_apple-midi._udp"; + static constexpr const char *ServiceTypeFTP = "_ftp._tcp"; public: /// \param pNet Pointer to the network subsystem object CmDNSPublisher (CNetSubSystem *pNet); @@ -50,6 +51,12 @@ public: /// \param pServiceName Name of the service to be unpublished (same as when published) /// \return Operation successful? boolean UnpublishService (const char *pServiceName); + /// \brief Stop publishing a service + /// \param pServiceName Name of the service to be unpublished + /// \param pServiceType Type of the service to be unpublished + /// \param usServicePort Port number of the service to be unpublished + /// \return Operation successful? + boolean UnpublishService (const char *pServiceName, const char *pServiceType, u16 usServicePort); void Run (void) override; private: static const unsigned MaxTextRecords = 10; diff --git a/src/patches/dx7note.patch b/src/patches/dx7note.patch deleted file mode 100644 index a4a151c..0000000 --- a/src/patches/dx7note.patch +++ /dev/null @@ -1,81 +0,0 @@ -diff --git a/src/dx7note.cpp b/src/dx7note.cpp -index b5ed6db..2a07836 100644 ---- a/src/dx7note.cpp -+++ b/src/dx7note.cpp -@@ -184,11 +184,14 @@ void Dx7Note::init(const uint8_t patch[156], int midinote, int velocity, int src - int32_t freq = osc_freq(midinote, mode, coarse, fine, detune); - opMode[op] = mode; - basepitch_[op] = freq; -- porta_curpitch_[op] = freq; - ampmodsens_[op] = ampmodsenstab[patch[off + 14] & 3]; - -- if (porta >= 0) -+ // Always set porta_curpitch_ to basepitch_ if no portamento transition -+ if (porta < 0 || porta >= 128 || srcnote == midinote) { -+ porta_curpitch_[op] = freq; -+ } else { - porta_curpitch_[op] = osc_freq(srcnote, mode, coarse, fine, detune); -+ } - } - for (int i = 0; i < 4; i++) { - rates[i] = patch[126 + i]; -@@ -253,13 +256,17 @@ void Dx7Note::compute(int32_t *buf, int32_t lfo_val, int32_t lfo_delay, const Co - - int32_t basepitch = basepitch_[op]; - -- if ( opMode[op] ) -+ if (opMode[op]) { - params_[op].freq = Freqlut::lookup(basepitch + pitch_base); -- else { -- if ( porta_rateindex_ >= 0 ) { -- basepitch = porta_curpitch_[op]; -- if ( porta_gliss_ ) -- basepitch = logfreq_round2semi(basepitch); -+ } else { -+ // If portamento is enabled but there is no transition, use basepitch_ -+ if (porta_rateindex_ >= 0) { -+ if (porta_curpitch_[op] != basepitch_[op]) { -+ basepitch = porta_curpitch_[op]; -+ if (porta_gliss_) -+ basepitch = logfreq_round2semi(basepitch); -+ } -+ // else: no transition, use basepitch_ as is - } - params_[op].freq = Freqlut::lookup(basepitch + pitch_mod); - } -@@ -280,7 +287,7 @@ void Dx7Note::compute(int32_t *buf, int32_t lfo_val, int32_t lfo_delay, const Co - - // ==== PORTAMENTO ==== - int porta = porta_rateindex_; -- if ( porta >= 0 ) { -+ if (porta >= 0) { - int32_t rate = Porta::rates[porta]; - for (int op = 0; op < 6; op++) { - int32_t cur = porta_curpitch_[op]; -@@ -289,7 +296,8 @@ void Dx7Note::compute(int32_t *buf, int32_t lfo_val, int32_t lfo_delay, const Co - bool going_up = cur < dst; - int32_t newpitch = cur + (going_up ? +rate : -rate); - -- if ( going_up ? (cur > dst) : (cur < dst) ) -+ // Clamp to destination if we would overshoot/undershoot -+ if ((going_up && newpitch > dst) || (!going_up && newpitch < dst)) - newpitch = dst; - - porta_curpitch_[op] = newpitch; -@@ -317,10 +325,15 @@ void Dx7Note::update(const uint8_t patch[156], int midinote, int velocity, int p - int detune = patch[off + 20]; - int32_t freq = osc_freq(midinote, mode, coarse, fine, detune); - basepitch_[op] = freq; -- porta_curpitch_[op] = freq; - ampmodsens_[op] = ampmodsenstab[patch[off + 14] & 3]; - opMode[op] = mode; - -+ // Always set porta_curpitch_ to basepitch_ if no portamento transition -+ if (porta < 0 || porta >= 128) { -+ porta_curpitch_[op] = freq; -+ } -+ // else: porta_curpitch_ will be handled by portamento logic -+ - for (int i = 0; i < 4; i++) { - rates[i] = patch[off + i]; - levels[i] = patch[off + 4 + i]; diff --git a/src/udpmididevice.cpp b/src/udpmididevice.cpp index 4b0d1c7..1934e42 100644 --- a/src/udpmididevice.cpp +++ b/src/udpmididevice.cpp @@ -46,7 +46,7 @@ CUDPMIDIDevice::~CUDPMIDIDevice (void) boolean CUDPMIDIDevice::Initialize (void) { - m_pAppleMIDIParticipant = new CAppleMIDIParticipant(&m_Random, this); + m_pAppleMIDIParticipant = new CAppleMIDIParticipant(&m_Random, this, m_pConfig->GetNetworkHostname()); if (!m_pAppleMIDIParticipant->Initialize()) { LOGERR("Failed to init RTP listener"); diff --git a/submod.sh b/submod.sh index 6ef767a..32408ef 100755 --- a/submod.sh +++ b/submod.sh @@ -23,5 +23,5 @@ cd - # Use fixed master branch of Synth_Dexed cd Synth_Dexed/ git reset --hard -git checkout 65d8383ad5 -f +git checkout 2ad9e43095 -f cd - diff --git a/syslogserver.py b/syslogserver.py index feffaa6..db45e7f 100644 --- a/syslogserver.py +++ b/syslogserver.py @@ -15,6 +15,7 @@ class SyslogServer: self.port = port self.server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) self.server.bind((self.host, self.port)) + self.server.settimeout(0.5) # Set timeout to allow checking self.running self.start_time = None self.running = True @@ -28,12 +29,17 @@ class SyslogServer: try: data, address = self.server.recvfrom(1024) self.handle_message(data) + except socket.timeout: + continue # Check self.running again except KeyboardInterrupt: self.running = False def handle_message(self, data): message = data[2:].decode('utf-8').strip() + if "Time exceeded (0)" in message: + return + if self.start_time is None: self.start_time = time.time() relative_time = "0:00:00.000" @@ -57,4 +63,4 @@ class SyslogServer: if __name__ == "__main__": server = SyslogServer() server.start() - print("Syslog server stopped.") \ No newline at end of file + print("Syslog server stopped.") diff --git a/updater.py b/updater.py index 30e6749..bc33058 100644 --- a/updater.py +++ b/updater.py @@ -345,13 +345,18 @@ if __name__ == "__main__": print(f"\nUploaded {file} to {selected_ip}.") except ftplib.all_errors as e: print(f"FTP error: {e}") + ftp = None # Mark ftp as unusable + # Only attempt to send BYE if ftp is connected and has a socket try: - ftp.sendcmd("BYE") - print(f"Disconnected from {selected_ip}.") - except ftplib.all_errors as e: - if str(e).strip().lower() == "timed out": - # Suppress expected timeout after BYE + if ftp is not None and getattr(ftp, 'sock', None) is not None: + ftp.sendcmd("BYE") print(f"Disconnected from {selected_ip}.") + else: + print(f"No active FTP connection to disconnect from.") + except (*ftplib.all_errors, AttributeError) as e: + # Handle timeout or already closed connection + if isinstance(e, TimeoutError) or (str(e).strip().lower() == "timed out"): + print(f"Disconnected from {selected_ip} (timeout after BYE, device likely restarted).") else: print(f"FTP error after BYE: {e}")