pull/909/head
probonopd 7 days ago
commit 513363b938
  1. 24
      .github/workflows/build.yml
  2. 3
      src/minidexed.cpp
  3. 13
      src/net/applemidi.cpp
  4. 4
      src/net/applemidi.h
  5. 8
      src/net/ftpdaemon.cpp
  6. 6
      src/net/ftpdaemon.h
  7. 27
      src/net/ftpworker.cpp
  8. 7
      src/net/ftpworker.h
  9. 39
      src/net/mdnspublisher.cpp
  10. 7
      src/net/mdnspublisher.h
  11. 81
      src/patches/dx7note.patch
  12. 2
      src/udpmididevice.cpp
  13. 2
      submod.sh
  14. 8
      syslogserver.py
  15. 15
      updater.py

@ -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

@ -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())
{

@ -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");

@ -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

@ -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);
}
}

@ -25,11 +25,13 @@
#include <circle/net/socket.h>
#include <circle/sched/task.h>
#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

@ -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;
}

@ -27,6 +27,8 @@
#include <circle/net/socket.h>
#include <circle/sched/task.h>
#include <circle/string.h>
#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);

@ -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<TService *>(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);

@ -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;

@ -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];

@ -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");

@ -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 -

@ -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.")
print("Syslog server stopped.")

@ -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}")

Loading…
Cancel
Save