From 9322988fbd6c2567445f7c67634afd19348aed5c Mon Sep 17 00:00:00 2001 From: probonopd Date: Sun, 11 May 2025 15:29:48 +0200 Subject: [PATCH 01/10] SysEx for MiniDexed performance parameters (half-working) --- src/Makefile | 5 +- src/mididevice.cpp | 48 ++++-- src/midikeyboard.cpp | 36 +++-- src/net/applemidi.cpp | 2 +- src/performance_sysex.cpp | 205 +++++++++++++++++++++++++ src/performance_sysex.h | 35 +++++ src/performanceconfig.cpp | 304 ++++++++++++++++++++++++++++++++++++++ src/performanceconfig.h | 49 ++++++ src/serialmididevice.cpp | 34 ++--- src/serialmididevice.h | 5 + src/udpmididevice.cpp | 46 +++++- src/udpmididevice.h | 5 + 12 files changed, 720 insertions(+), 54 deletions(-) create mode 100644 src/performance_sysex.cpp create mode 100644 src/performance_sysex.h diff --git a/src/Makefile b/src/Makefile index 73dbddc..f3b878a 100644 --- a/src/Makefile +++ b/src/Makefile @@ -11,7 +11,8 @@ OBJS = main.o kernel.o minidexed.o config.o userinterface.o uimenu.o \ sysexfileloader.o performanceconfig.o perftimer.o \ effect_compressor.o effect_platervbstereo.o uibuttons.o midipin.o \ arm_float_to_q23.o \ - net/ftpdaemon.o net/ftpworker.o net/applemidi.o net/udpmidi.o net/mdnspublisher.o udpmididevice.o + net/ftpdaemon.o net/ftpworker.o net/applemidi.o net/udpmidi.o net/mdnspublisher.o udpmididevice.o \ + performance_sysex.o OPTIMIZE = -O3 @@ -23,4 +24,4 @@ include ./Rules.mk clean: @echo "Cleaning up..." - rm -f $(OBJS) *.o *.d *~ core + rm -f $(OBJS) *.o *.d *~ core \ No newline at end of file diff --git a/src/mididevice.cpp b/src/mididevice.cpp index 413dd13..318c5ae 100644 --- a/src/mididevice.cpp +++ b/src/mididevice.cpp @@ -29,6 +29,17 @@ #include #include "midi.h" #include "userinterface.h" +#include "performanceconfig.h" +#include "performance_sysex.h" + +// RAII guard for MIDI spinlock +class MIDISpinLockGuard { +public: + MIDISpinLockGuard(CSpinLock& lock) : m_lock(lock) { m_lock.Acquire(); } + ~MIDISpinLockGuard() { m_lock.Release(); } +private: + CSpinLock& m_lock; +}; LOGMODULE ("mididevice"); @@ -128,6 +139,8 @@ u8 CMIDIDevice::GetChannel (unsigned nTG) const void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsigned nCable) { + MIDISpinLockGuard guard(m_MIDISpinLock); + // The packet contents are just normal MIDI data - see // https://www.midi.org/specifications/item/table-1-summary-of-midi-message @@ -207,8 +220,6 @@ void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsign return; } - m_MIDISpinLock.Acquire (); - u8 ucStatus = pMessage[0]; u8 ucChannel = ucStatus & 0x0F; u8 ucType = ucStatus >> 4; @@ -222,14 +233,23 @@ void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsign LOGNOTE("MIDI-SYSEX: Set TG%d to MIDI Channel %d", mTG + 1, val & 0x0F); m_pSynthesizer->SetMIDIChannel(val & 0x0F, mTG); // Do not process this message further for any TGs - m_MIDISpinLock.Release(); return; } + // Handle MiniDexed performance SysEx messages + if (pMessage[0] == MIDI_SYSTEM_EXCLUSIVE_BEGIN && pMessage[1] == 0x7D ) { + LOGNOTE("MiniDexed SysEx handler entered, nLength=%u", nLength); + // Use performance_sysex.h to handle the SysEx message + handle_performance_sysex(pMessage, this, m_pSynthesizer->GetPerformanceConfig(), nCable); + return; + } else { + LOGNOTE("MiniDexed SysEx handler NOT entered, nLength=%u", nLength); + } + // Master Volume is set using a MIDI SysEx message as follows: // F0 Start of SysEx // 7F System Realtime SysEx - // 7F SysEx "channel" - 7F = all devices + // 7F SysEx "channel" - 7F = all devices // 04 Device Control // 01 Master Volume Device Control // LL Low 7-bits of 14-bit volume @@ -449,11 +469,8 @@ void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsign break; } } - else - { - HandleSystemExclusive(pMessage, nLength, nCable, nTG); - } } + } } else { for (unsigned nTG = 0; nTG < m_pConfig->GetToneGenerators() && !bSystemCCHandled; nTG++) { @@ -671,7 +688,6 @@ void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsign } } } - m_MIDISpinLock.Release (); } void CMIDIDevice::AddDevice (const char *pDeviceName) @@ -700,7 +716,7 @@ bool CMIDIDevice::HandleMIDISystemCC(const u8 ucCC, const u8 ucCCval) } // Not looking for duplicate CCs so return once handled - for (unsigned tg=0; tg<8; tg++) { +for (unsigned tg=0; tg<8; tg++) { if (m_nMIDISystemCCVol != 0) { if (ucCC == MIDISystemCCMap[m_nMIDISystemCCVol][tg]) { m_pSynthesizer->SetVolume (ucCCval, tg); @@ -736,7 +752,17 @@ bool CMIDIDevice::HandleMIDISystemCC(const u8 ucCC, const u8 ucCCval) void CMIDIDevice::HandleSystemExclusive(const uint8_t* pMessage, const size_t nLength, const unsigned nCable, const uint8_t nTG) { - LOGDBG("HandleSystemExclusive: TG %d, length %zu", nTG, nLength); + LOGDBG("HandleSystemExclusive: TG %d, length %u", nTG, nLength); + + // Only handle Yamaha (0x43) or MiniDexed (0x7D) SysEx + if (nLength < 2) { + LOGDBG("SysEx too short, ignoring"); + return; + } + if (pMessage[1] != 0x43 && pMessage[1] != 0x7D) { + LOGDBG("Ignoring SysEx with manufacturer 0x%02X (not Yamaha or MiniDexed)", pMessage[1]); + return; + } // Check if it is a dump request; these have the format F0 43 2n ff F7 // with n = the MIDI channel and ff = 00 for voice or 09 for bank diff --git a/src/midikeyboard.cpp b/src/midikeyboard.cpp index db71168..3d127ed 100644 --- a/src/midikeyboard.cpp +++ b/src/midikeyboard.cpp @@ -95,52 +95,58 @@ void CMIDIKeyboard::USBMIDIMessageHandler (u8 *pPacket, unsigned nLength, unsign if ((pPacket[0] == 0xF0) && (m_nSysExIdx == 0)) { // Start of SysEx message - //printf("SysEx Start Idx=%d, (%d)\n", m_nSysExIdx, nLength); for (unsigned i=0; i 2 && m_SysEx[1] != 0x43 && m_SysEx[1] != 0x7D) { + // LOGNOTE("Aborting SysEx assembly: manufacturer 0x%02X not Yamaha or MiniDexed", m_SysEx[1]); + m_nSysExIdx = 0; + // Do not process remaining bytes as MIDI events + } } else if (m_nSysExIdx != 0) { - // Continue processing SysEx message - //printf("SysEx Packet Idx=%d, (%d)\n", m_nSysExIdx, nLength); for (unsigned i=0; i= USB_SYSEX_BUFFER_SIZE) { - // Run out of space, so reset and ignore rest of the message + // LOGERR("SysEx buffer overflow, resetting SysEx assembly"); m_nSysExIdx = 0; break; } else if (pPacket[i] == 0xF7) { - // End of SysEx message m_SysEx[m_nSysExIdx++] = pPacket[i]; - //printf ("SysEx End Idx=%d\n", m_nSysExIdx); - MIDIMessageHandler (m_SysEx, m_nSysExIdx, nCable); - // Reset ready for next time + // Check manufacturer ID before passing to handler + if (m_SysEx[1] == 0x43 || m_SysEx[1] == 0x7D) { + MIDIMessageHandler (m_SysEx, m_nSysExIdx, nCable); + } else { + // LOGNOTE("Ignoring completed SysEx: manufacturer 0x%02X not Yamaha or MiniDexed", m_SysEx[1]); + } m_nSysExIdx = 0; } else if ((pPacket[i] & 0x80) != 0) { - // Received another command, so reset processing as something has gone wrong - //printf ("SysEx Reset\n"); + // LOGERR("Unexpected status byte 0x%02X in SysEx, resetting", pPacket[i]); m_nSysExIdx = 0; break; } - else - { - // Store the byte + else { m_SysEx[m_nSysExIdx++] = pPacket[i]; + // Early check for manufacturer ID after at least 2 bytes + if (m_nSysExIdx == 2 && m_SysEx[1] != 0x43 && m_SysEx[1] != 0x7D) { + // LOGNOTE("Aborting SysEx assembly: manufacturer 0x%02X not Yamaha or MiniDexed", m_SysEx[1]); + m_nSysExIdx = 0; + // Do not process remaining bytes as MIDI events + } } } } else { - // Assume it is a standard message MIDIMessageHandler (pPacket, nLength, nCable); } } diff --git a/src/net/applemidi.cpp b/src/net/applemidi.cpp index 8cbd3a4..d047025 100644 --- a/src/net/applemidi.cpp +++ b/src/net/applemidi.cpp @@ -919,6 +919,6 @@ bool CAppleMIDIParticipant::SendMIDIToHost(const u8* pData, size_t nSize) return false; } - LOGDBG("Successfully sent %zu bytes of MIDI data", nSize); + LOGDBG("Successfully sent %u bytes of MIDI data", nSize); return true; } \ No newline at end of file diff --git a/src/performance_sysex.cpp b/src/performance_sysex.cpp new file mode 100644 index 0000000..efda130 --- /dev/null +++ b/src/performance_sysex.cpp @@ -0,0 +1,205 @@ +// +// performance_sysex_handler.cpp +// +// MiniDexed - Dexed FM synthesizer for bare metal Raspberry Pi +// Copyright (C) 2025 The MiniDexed Team +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#include "performance_sysex.h" +#include "performanceconfig.h" +#include +#include +#include "minidexed.h" +#include "mididevice.h" + +LOGMODULE ("PerformanceSysEx"); + +static constexpr uint8_t SYSEX_START = 0xF0; +static constexpr uint8_t SYSEX_END = 0xF7; +static constexpr uint8_t MANUFACTURER_ID = 0x7D; +static constexpr uint8_t CMD_GET = 0x10; +static constexpr uint8_t CMD_SET = 0x20; + +static void send_sysex_response(const uint8_t* data, size_t len, CMIDIDevice* pDevice, unsigned nCable) { + if (pDevice) { + pDevice->Send(data, len, nCable); + LOGNOTE("Sent SysEx response (%u bytes) on cable %u", (unsigned)len, nCable); + } +} + +void handle_performance_sysex(const uint8_t* pMessage, CMIDIDevice* pDevice, CPerformanceConfig* perf, unsigned nCable) { + if (!pMessage || !perf) { + LOGERR("handle_performance_sysex: Null pointer or PerformanceConfig not set"); + return; + } + if (pMessage[0] != SYSEX_START) { + LOGERR("SysEx: Invalid start byte: 0x%02X", pMessage[0]); + return; + } + int len = 0; + while (len < 256 && pMessage[len] != SYSEX_END) ++len; + if (len < 3 || pMessage[len] != SYSEX_END) { + LOGERR("SysEx: No end byte or too short"); + return; + } + if (pMessage[1] != MANUFACTURER_ID) { + LOGERR("SysEx: Invalid manufacturer ID: 0x%02X", pMessage[1]); + return; + } + + uint8_t tg = 0xFF; // 0xFF = global + uint8_t cmd = 0; + uint8_t offset = 0; + + if (pMessage[2] == CMD_GET || pMessage[2] == CMD_SET) { + cmd = pMessage[2]; + offset = 3; + LOGNOTE("SysEx: Global %s request", cmd == CMD_GET ? "GET" : "SET"); + } else if (pMessage[2] < 0x80 && (pMessage[3] == CMD_GET || pMessage[3] == CMD_SET)) { + tg = pMessage[2]; + cmd = pMessage[3]; + offset = 4; + LOGNOTE("SysEx: TG-specific %s request for TG %u", cmd == CMD_GET ? "GET" : "SET", tg); + } else { + LOGERR("SysEx: Unrecognized message structure"); + return; + } + + if (offset == len) { + // Dump all global or all TG parameters + if (cmd == CMD_GET) { + if (tg == 0xFF) { + // Dump all global + size_t count = 0; + const uint16_t* params = CPerformanceConfig::GetAllGlobalParams(count); + uint16_t values[16] = {0}; + if (perf->GetGlobalParameters(params, values, count)) { + LOGNOTE("SysEx: Dumping all global parameters"); + uint8_t resp[64] = {0}; + resp[0] = SYSEX_START; + resp[1] = MANUFACTURER_ID; + resp[2] = CMD_GET; + size_t idx = 3; + for (size_t i = 0; i < count; ++i) { + resp[idx++] = (params[i] >> 8) & 0xFF; + resp[idx++] = params[i] & 0xFF; + resp[idx++] = (values[i] >> 8) & 0xFF; + resp[idx++] = values[i] & 0xFF; + LOGNOTE(" Param 0x%04X = 0x%04X", params[i], values[i]); + } + resp[idx++] = SYSEX_END; + send_sysex_response(resp, idx, pDevice, nCable); + } else { + LOGERR("SysEx: Failed to get all global parameters"); + } + } else { + // Dump all TG + size_t count = 0; + const uint16_t* params = CPerformanceConfig::GetAllTGParams(count); + uint16_t values[32] = {0}; + if (perf->GetTGParameters(params, values, count, tg)) { + LOGNOTE("SysEx: Dumping all TG parameters for TG %u", tg); + uint8_t resp[128] = {0}; + resp[0] = SYSEX_START; + resp[1] = MANUFACTURER_ID; + resp[2] = tg; + resp[3] = CMD_GET; + size_t idx = 4; + for (size_t i = 0; i < count; ++i) { + resp[idx++] = (params[i] >> 8) & 0xFF; + resp[idx++] = params[i] & 0xFF; + resp[idx++] = (values[i] >> 8) & 0xFF; + resp[idx++] = values[i] & 0xFF; + LOGNOTE(" Param 0x%04X = 0x%04X", params[i], values[i]); + } + resp[idx++] = SYSEX_END; + send_sysex_response(resp, idx, pDevice, nCable); + } else { + LOGERR("SysEx: Failed to get all TG parameters for TG %u", tg); + } + } + } + return; + } + while (offset + 1 < len) { + uint16_t param = (pMessage[offset] << 8) | pMessage[offset+1]; + offset += 2; + if (cmd == CMD_GET) { + uint16_t value = 0; + bool ok = false; + if (tg == 0xFF) { + ok = perf->GetGlobalParameters(¶m, &value, 1); + LOGNOTE("SysEx: GET global param 0x%04X -> 0x%04X (%s)", param, value, ok ? "OK" : "FAIL"); + // Build and send response + uint8_t resp[9] = {SYSEX_START, MANUFACTURER_ID, CMD_GET, (uint8_t)(param >> 8), (uint8_t)(param & 0xFF), (uint8_t)(value >> 8), (uint8_t)(value & 0xFF), SYSEX_END}; + send_sysex_response(resp, 8, pDevice, nCable); + } else { + ok = perf->GetTGParameters(¶m, &value, 1, tg); + LOGNOTE("SysEx: GET TG %u param 0x%04X -> 0x%04X (%s)", tg, param, value, ok ? "OK" : "FAIL"); + uint8_t resp[10] = {SYSEX_START, MANUFACTURER_ID, tg, CMD_GET, (uint8_t)(param >> 8), (uint8_t)(param & 0xFF), (uint8_t)(value >> 8), (uint8_t)(value & 0xFF), SYSEX_END}; + send_sysex_response(resp, 9, pDevice, nCable); + } + } else if (cmd == CMD_SET) { + if (offset + 1 >= len) { + LOGERR("SysEx: SET param 0x%04X missing value bytes", param); + break; + } + uint16_t value = (pMessage[offset] << 8) | pMessage[offset+1]; + offset += 2; + bool ok = false; + if (tg == 0xFF) { + ok = perf->SetGlobalParameters(¶m, &value, 1); + LOGNOTE("SysEx: SET global param 0x%04X = 0x%04X (%s)", param, value, ok ? "OK" : "FAIL"); + } else { + ok = perf->SetTGParameters(¶m, &value, 1, tg); + LOGNOTE("SysEx: SET TG %u param 0x%04X = 0x%04X (%s)", tg, param, value, ok ? "OK" : "FAIL"); + } + } + } +} + +/* +Examples of MiniDexed Performance SysEx messages and expected handler behavior: + +1. Get a Global Parameter (e.g., CompressorEnable) + Send: F0 7D 10 00 00 F7 + - Logs GET for global param 0x0000, queries value, logs result. + +2. Set a Global Parameter (e.g., ReverbEnable ON) + Send: F0 7D 20 00 01 00 01 F7 + - Logs SET for global param 0x0001, updates value, logs result. + +3. Get All Global Parameters + Send: F0 7D 10 F7 + - Logs and dumps all global parameters and values. + +4. Get a TG Parameter (e.g., Volume for TG 2) + Send: F0 7D 02 10 00 03 F7 + - Logs GET for TG 2 param 0x0003, queries value, logs result. + +5. Set a TG Parameter (e.g., Pan for TG 1 to 64) + Send: F0 7D 01 20 00 04 00 40 F7 + - Logs SET for TG 1 param 0x0004, updates value, logs result. + +6. Get All Parameters for TG 0 + Send: F0 7D 00 10 F7 + - Logs and dumps all TG 0 parameters and values. + +7. Malformed or Unknown Parameter + Send: F0 7D 10 12 34 F7 + - Logs GET for global param 0x1234, logs FAIL if unsupported. + +*/ diff --git a/src/performance_sysex.h b/src/performance_sysex.h new file mode 100644 index 0000000..3a85de7 --- /dev/null +++ b/src/performance_sysex.h @@ -0,0 +1,35 @@ +// +// performance_sysex_handler.h +// +// MiniDexed - Dexed FM synthesizer for bare metal Raspberry Pi +// Copyright (C) 2025 The MiniDexed Team +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#ifndef PERFORMANCE_SYSEX_H +#define PERFORMANCE_SYSEX_H + +#include +#include +#include "performanceconfig.h" + +class CMIDIDevice; +// Handles a MiniDexed performance SysEx message. +// pMessage: pointer to the SysEx message (must be at least 2 bytes) +// pDevice: the MIDI device to send the response to +// perf: the performance config to operate on +void handle_performance_sysex(const uint8_t* pMessage, CMIDIDevice* pDevice, CPerformanceConfig* perf, unsigned nCable = 0); + +#endif // PERFORMANCE_SYSEX_H diff --git a/src/performanceconfig.cpp b/src/performanceconfig.cpp index dfff8a9..03732dc 100644 --- a/src/performanceconfig.cpp +++ b/src/performanceconfig.cpp @@ -1314,3 +1314,307 @@ bool CPerformanceConfig::IsValidPerformanceBank(unsigned nBankID) } return true; } + +// Static arrays for all parameter numbers +static const uint16_t s_globalParams[] = { + CPerformanceConfig::PARAM_COMPRESSOR_ENABLE, + CPerformanceConfig::PARAM_REVERB_ENABLE, + CPerformanceConfig::PARAM_REVERB_SIZE, + CPerformanceConfig::PARAM_REVERB_HIGHDAMP, + CPerformanceConfig::PARAM_REVERB_LOWDAMP, + CPerformanceConfig::PARAM_REVERB_LOWPASS, + CPerformanceConfig::PARAM_REVERB_DIFFUSION, + CPerformanceConfig::PARAM_REVERB_LEVEL +}; +static const uint16_t s_tgParams[] = { + CPerformanceConfig::PARAM_BANK_NUMBER, + CPerformanceConfig::PARAM_VOICE_NUMBER, + CPerformanceConfig::PARAM_MIDI_CHANNEL, + CPerformanceConfig::PARAM_VOLUME, + CPerformanceConfig::PARAM_PAN, + CPerformanceConfig::PARAM_DETUNE, + CPerformanceConfig::PARAM_CUTOFF, + CPerformanceConfig::PARAM_RESONANCE, + CPerformanceConfig::PARAM_NOTE_LIMIT_LOW, + CPerformanceConfig::PARAM_NOTE_LIMIT_HIGH, + CPerformanceConfig::PARAM_NOTE_SHIFT, + CPerformanceConfig::PARAM_REVERB_SEND, + CPerformanceConfig::PARAM_PITCH_BEND_RANGE, + CPerformanceConfig::PARAM_PITCH_BEND_STEP, + CPerformanceConfig::PARAM_PORTAMENTO_MODE, + CPerformanceConfig::PARAM_PORTAMENTO_GLISSANDO, + CPerformanceConfig::PARAM_PORTAMENTO_TIME, + CPerformanceConfig::PARAM_MONO_MODE, + CPerformanceConfig::PARAM_MODWHEEL_RANGE, + CPerformanceConfig::PARAM_MODWHEEL_TARGET, + CPerformanceConfig::PARAM_FOOTCTRL_RANGE, + CPerformanceConfig::PARAM_FOOTCTRL_TARGET, + CPerformanceConfig::PARAM_BREATHCTRL_RANGE, + CPerformanceConfig::PARAM_BREATHCTRL_TARGET, + CPerformanceConfig::PARAM_AFTERTOUCH_RANGE, + CPerformanceConfig::PARAM_AFTERTOUCH_TARGET +}; + +const uint16_t* CPerformanceConfig::GetAllGlobalParams(size_t& count) { + count = sizeof(s_globalParams) / sizeof(s_globalParams[0]); + return s_globalParams; +} +const uint16_t* CPerformanceConfig::GetAllTGParams(size_t& count) { + count = sizeof(s_tgParams) / sizeof(s_tgParams[0]); + return s_tgParams; +} + +// Generic parameter access for SysEx (pp pp, vv vv scheme) +bool CPerformanceConfig::GetGlobalParameters(const uint16_t *params, uint16_t *values, size_t count) const { + bool allOk = true; + for (size_t i = 0; i < count; ++i) { + switch (params[i]) { + case PARAM_COMPRESSOR_ENABLE: + values[i] = GetCompressorEnable() ? 1 : 0; + break; + case PARAM_REVERB_ENABLE: + values[i] = GetReverbEnable() ? 1 : 0; + break; + case PARAM_REVERB_SIZE: + values[i] = GetReverbSize(); + break; + case PARAM_REVERB_HIGHDAMP: + values[i] = GetReverbHighDamp(); + break; + case PARAM_REVERB_LOWDAMP: + values[i] = GetReverbLowDamp(); + break; + case PARAM_REVERB_LOWPASS: + values[i] = GetReverbLowPass(); + break; + case PARAM_REVERB_DIFFUSION: + values[i] = GetReverbDiffusion(); + break; + case PARAM_REVERB_LEVEL: + values[i] = GetReverbLevel(); + break; + default: + allOk = false; + break; + } + } + return allOk; +} + +bool CPerformanceConfig::SetGlobalParameters(const uint16_t *params, const uint16_t *values, size_t count) { + bool allOk = true; + for (size_t i = 0; i < count; ++i) { + switch (params[i]) { + case PARAM_COMPRESSOR_ENABLE: + SetCompressorEnable(values[i] != 0); + break; + case PARAM_REVERB_ENABLE: + SetReverbEnable(values[i] != 0); + break; + case PARAM_REVERB_SIZE: + SetReverbSize(values[i]); + break; + case PARAM_REVERB_HIGHDAMP: + SetReverbHighDamp(values[i]); + break; + case PARAM_REVERB_LOWDAMP: + SetReverbLowDamp(values[i]); + break; + case PARAM_REVERB_LOWPASS: + SetReverbLowPass(values[i]); + break; + case PARAM_REVERB_DIFFUSION: + SetReverbDiffusion(values[i]); + break; + case PARAM_REVERB_LEVEL: + SetReverbLevel(values[i]); + break; + default: + allOk = false; + break; + } + } + return allOk; +} + +bool CPerformanceConfig::GetTGParameters(const uint16_t *params, uint16_t *values, size_t count, unsigned tg) const { + if (tg >= CConfig::AllToneGenerators) return false; + bool allOk = true; + for (size_t i = 0; i < count; ++i) { + switch (params[i]) { + case PARAM_BANK_NUMBER: + values[i] = GetBankNumber(tg); + break; + case PARAM_VOICE_NUMBER: + values[i] = GetVoiceNumber(tg); + break; + case PARAM_MIDI_CHANNEL: + values[i] = GetMIDIChannel(tg); + break; + case PARAM_VOLUME: + values[i] = GetVolume(tg); + break; + case PARAM_PAN: + values[i] = GetPan(tg); + break; + case PARAM_DETUNE: + values[i] = static_cast(GetDetune(tg)); + break; + case PARAM_CUTOFF: + values[i] = GetCutoff(tg); + break; + case PARAM_RESONANCE: + values[i] = GetResonance(tg); + break; + case PARAM_NOTE_LIMIT_LOW: + values[i] = GetNoteLimitLow(tg); + break; + case PARAM_NOTE_LIMIT_HIGH: + values[i] = GetNoteLimitHigh(tg); + break; + case PARAM_NOTE_SHIFT: + values[i] = static_cast(GetNoteShift(tg)); + break; + case PARAM_REVERB_SEND: + values[i] = GetReverbSend(tg); + break; + case PARAM_PITCH_BEND_RANGE: + values[i] = GetPitchBendRange(tg); + break; + case PARAM_PITCH_BEND_STEP: + values[i] = GetPitchBendStep(tg); + break; + case PARAM_PORTAMENTO_MODE: + values[i] = GetPortamentoMode(tg); + break; + case PARAM_PORTAMENTO_GLISSANDO: + values[i] = GetPortamentoGlissando(tg); + break; + case PARAM_PORTAMENTO_TIME: + values[i] = GetPortamentoTime(tg); + break; + case PARAM_MONO_MODE: + values[i] = GetMonoMode(tg) ? 1 : 0; + break; + case PARAM_MODWHEEL_RANGE: + values[i] = GetModulationWheelRange(tg); + break; + case PARAM_MODWHEEL_TARGET: + values[i] = GetModulationWheelTarget(tg); + break; + case PARAM_FOOTCTRL_RANGE: + values[i] = GetFootControlRange(tg); + break; + case PARAM_FOOTCTRL_TARGET: + values[i] = GetFootControlTarget(tg); + break; + case PARAM_BREATHCTRL_RANGE: + values[i] = GetBreathControlRange(tg); + break; + case PARAM_BREATHCTRL_TARGET: + values[i] = GetBreathControlTarget(tg); + break; + case PARAM_AFTERTOUCH_RANGE: + values[i] = GetAftertouchRange(tg); + break; + case PARAM_AFTERTOUCH_TARGET: + values[i] = GetAftertouchTarget(tg); + break; + default: + allOk = false; + break; + } + } + return allOk; +} + +bool CPerformanceConfig::SetTGParameters(const uint16_t *params, const uint16_t *values, size_t count, unsigned tg) { + if (tg >= CConfig::AllToneGenerators) return false; + bool allOk = true; + for (size_t i = 0; i < count; ++i) { + switch (params[i]) { + case PARAM_BANK_NUMBER: + SetBankNumber(values[i], tg); + break; + case PARAM_VOICE_NUMBER: + SetVoiceNumber(values[i], tg); + break; + case PARAM_MIDI_CHANNEL: + SetMIDIChannel(values[i], tg); + break; + case PARAM_VOLUME: + SetVolume(values[i], tg); + break; + case PARAM_PAN: + SetPan(values[i], tg); + break; + case PARAM_DETUNE: + SetDetune(static_cast(values[i]), tg); + break; + case PARAM_CUTOFF: + SetCutoff(values[i], tg); + break; + case PARAM_RESONANCE: + SetResonance(values[i], tg); + break; + case PARAM_NOTE_LIMIT_LOW: + SetNoteLimitLow(values[i], tg); + break; + case PARAM_NOTE_LIMIT_HIGH: + SetNoteLimitHigh(values[i], tg); + break; + case PARAM_NOTE_SHIFT: + SetNoteShift(static_cast(values[i]), tg); + break; + case PARAM_REVERB_SEND: + SetReverbSend(values[i], tg); + break; + case PARAM_PITCH_BEND_RANGE: + SetPitchBendRange(values[i], tg); + break; + case PARAM_PITCH_BEND_STEP: + SetPitchBendStep(values[i], tg); + break; + case PARAM_PORTAMENTO_MODE: + SetPortamentoMode(values[i], tg); + break; + case PARAM_PORTAMENTO_GLISSANDO: + SetPortamentoGlissando(values[i], tg); + break; + case PARAM_PORTAMENTO_TIME: + SetPortamentoTime(values[i], tg); + break; + case PARAM_MONO_MODE: + SetMonoMode(values[i] != 0, tg); + break; + case PARAM_MODWHEEL_RANGE: + SetModulationWheelRange(values[i], tg); + break; + case PARAM_MODWHEEL_TARGET: + SetModulationWheelTarget(values[i], tg); + break; + case PARAM_FOOTCTRL_RANGE: + SetFootControlRange(values[i], tg); + break; + case PARAM_FOOTCTRL_TARGET: + SetFootControlTarget(values[i], tg); + break; + case PARAM_BREATHCTRL_RANGE: + SetBreathControlRange(values[i], tg); + break; + case PARAM_BREATHCTRL_TARGET: + SetBreathControlTarget(values[i], tg); + break; + case PARAM_AFTERTOUCH_RANGE: + SetAftertouchRange(values[i], tg); + break; + case PARAM_AFTERTOUCH_TARGET: + SetAftertouchTarget(values[i], tg); + break; + default: + allOk = false; + break; + } + } + return allOk; +} diff --git a/src/performanceconfig.h b/src/performanceconfig.h index 916a2ee..9428e53 100644 --- a/src/performanceconfig.h +++ b/src/performanceconfig.h @@ -33,6 +33,47 @@ class CPerformanceConfig // Performance configuration { public: + // Parameter numbers for SysEx (MiniDexed Format) + enum ParameterNumber { + // Global parameters (pp pp) + PARAM_COMPRESSOR_ENABLE = 0x0000, + PARAM_REVERB_ENABLE = 0x0001, + PARAM_REVERB_SIZE = 0x0002, + PARAM_REVERB_HIGHDAMP = 0x0003, + PARAM_REVERB_LOWDAMP = 0x0004, + PARAM_REVERB_LOWPASS = 0x0005, + PARAM_REVERB_DIFFUSION = 0x0006, + PARAM_REVERB_LEVEL = 0x0007, + + // TG-specific parameters (pp pp) + PARAM_BANK_NUMBER = 0x0000, + PARAM_VOICE_NUMBER = 0x0001, + PARAM_MIDI_CHANNEL = 0x0002, + PARAM_VOLUME = 0x0003, + PARAM_PAN = 0x0004, + PARAM_DETUNE = 0x0005, + PARAM_CUTOFF = 0x0006, + PARAM_RESONANCE = 0x0007, + PARAM_NOTE_LIMIT_LOW = 0x0008, + PARAM_NOTE_LIMIT_HIGH = 0x0009, + PARAM_NOTE_SHIFT = 0x000A, + PARAM_REVERB_SEND = 0x000B, + PARAM_PITCH_BEND_RANGE = 0x000C, + PARAM_PITCH_BEND_STEP = 0x000D, + PARAM_PORTAMENTO_MODE = 0x000E, + PARAM_PORTAMENTO_GLISSANDO = 0x000F, + PARAM_PORTAMENTO_TIME = 0x0010, + PARAM_MONO_MODE = 0x0011, + PARAM_MODWHEEL_RANGE = 0x0012, + PARAM_MODWHEEL_TARGET = 0x0013, + PARAM_FOOTCTRL_RANGE = 0x0014, + PARAM_FOOTCTRL_TARGET = 0x0015, + PARAM_BREATHCTRL_RANGE = 0x0016, + PARAM_BREATHCTRL_TARGET = 0x0017, + PARAM_AFTERTOUCH_RANGE = 0x0018, + PARAM_AFTERTOUCH_TARGET = 0x0019 + }; + CPerformanceConfig (FATFS *pFileSystem); ~CPerformanceConfig (void); @@ -149,6 +190,14 @@ public: std::string GetPerformanceBankName(unsigned nBankID); bool IsValidPerformanceBank(unsigned nBankID); + bool GetGlobalParameters(const uint16_t *params, uint16_t *values, size_t count) const; + bool SetGlobalParameters(const uint16_t *params, const uint16_t *values, size_t count); + bool GetTGParameters(const uint16_t *params, uint16_t *values, size_t count, unsigned tg) const; + bool SetTGParameters(const uint16_t *params, const uint16_t *values, size_t count, unsigned tg); + + static const uint16_t* GetAllGlobalParams(size_t& count); + static const uint16_t* GetAllTGParams(size_t& count); + private: CPropertiesFatFsFile m_Properties; diff --git a/src/serialmididevice.cpp b/src/serialmididevice.cpp index 3fe01e8..239cfd6 100644 --- a/src/serialmididevice.cpp +++ b/src/serialmididevice.cpp @@ -75,30 +75,24 @@ void CSerialMIDIDevice::Process (void) return; } -/* if (m_pConfig->GetMIDIDumpEnabled ()) - { - printf("Incoming MIDI data:"); - for (uint16_t i = 0; i < nResult; i++) - { - if((i % 8) == 0) - printf("\n%04d:",i); - printf(" 0x%02x",Buffer[i]); - } - printf("\n"); - }*/ - - // Process MIDI messages - // See: https://www.midi.org/specifications/item/table-1-summary-of-midi-message - // "Running status" see: https://www.lim.di.unimi.it/IEEE/MIDI/SOT5.HTM#Running- - for (int i = 0; i < nResult; i++) { u8 uchData = Buffer[i]; - if(uchData == 0xF0) - { - // SYSEX found - m_SerialMessage[m_nSysEx++]=uchData; + // SysEx reassembly logic + if (uchData == 0xF0 && !m_SysExActive) { + m_SysExActive = true; + m_SysExLen = 0; + } + if (m_SysExActive) { + if (m_SysExLen < MAX_MIDI_MESSAGE) { + m_SysExBuffer[m_SysExLen++] = uchData; + } + if (uchData == 0xF7 || m_SysExLen >= MAX_MIDI_MESSAGE) { + MIDIMessageHandler(m_SysExBuffer, m_SysExLen, 0); + m_SysExActive = false; + m_SysExLen = 0; + } continue; } diff --git a/src/serialmididevice.h b/src/serialmididevice.h index fdf61b2..a59ae64 100644 --- a/src/serialmididevice.h +++ b/src/serialmididevice.h @@ -53,6 +53,11 @@ private: u8 m_SerialMessage[MAX_MIDI_MESSAGE]; CWriteBufferDevice m_SendBuffer; + + // SysEx reassembly buffer for serial MIDI + uint8_t m_SysExBuffer[MAX_MIDI_MESSAGE]; + size_t m_SysExLen = 0; + bool m_SysExActive = false; }; #endif diff --git a/src/udpmididevice.cpp b/src/udpmididevice.cpp index 000bc3d..57b87c3 100644 --- a/src/udpmididevice.cpp +++ b/src/udpmididevice.cpp @@ -80,7 +80,25 @@ boolean CUDPMIDIDevice::Initialize (void) void CUDPMIDIDevice::OnAppleMIDIDataReceived(const u8* pData, size_t nSize) { - MIDIMessageHandler(pData, nSize, VIRTUALCABLE); + for (size_t i = 0; i < nSize; ++i) { + u8 byte = pData[i]; + if (byte == 0xF0 && !m_SysExActive) { + m_SysExActive = true; + m_SysExLen = 0; + } + if (m_SysExActive) { + if (m_SysExLen < MAX_MIDI_MESSAGE) { + m_SysExBuffer[m_SysExLen++] = byte; + } + if (byte == 0xF7 || m_SysExLen >= MAX_MIDI_MESSAGE) { + MIDIMessageHandler(m_SysExBuffer, m_SysExLen, VIRTUALCABLE); + m_SysExActive = false; + m_SysExLen = 0; + } + continue; + } + MIDIMessageHandler(&byte, 1, VIRTUALCABLE); + } } void CUDPMIDIDevice::OnAppleMIDIConnect(const CIPAddress* pIPAddress, const char* pName) @@ -95,7 +113,25 @@ void CUDPMIDIDevice::OnAppleMIDIDisconnect(const CIPAddress* pIPAddress, const c void CUDPMIDIDevice::OnUDPMIDIDataReceived(const u8* pData, size_t nSize) { - MIDIMessageHandler(pData, nSize, VIRTUALCABLE); + for (size_t i = 0; i < nSize; ++i) { + u8 byte = pData[i]; + if (byte == 0xF0 && !m_SysExActive) { + m_SysExActive = true; + m_SysExLen = 0; + } + if (m_SysExActive) { + if (m_SysExLen < MAX_MIDI_MESSAGE) { + m_SysExBuffer[m_SysExLen++] = byte; + } + if (byte == 0xF7 || m_SysExLen >= MAX_MIDI_MESSAGE) { + MIDIMessageHandler(m_SysExBuffer, m_SysExLen, VIRTUALCABLE); + m_SysExActive = false; + m_SysExLen = 0; + } + continue; + } + MIDIMessageHandler(&byte, 1, VIRTUALCABLE); + } } void CUDPMIDIDevice::Send(const u8 *pMessage, size_t nLength, unsigned nCable) @@ -103,14 +139,14 @@ void CUDPMIDIDevice::Send(const u8 *pMessage, size_t nLength, unsigned nCable) bool sentRTP = false; if (m_pAppleMIDIParticipant && m_pAppleMIDIParticipant->SendMIDIToHost(pMessage, nLength)) { sentRTP = true; - LOGNOTE("Sent %zu bytes to RTP-MIDI host", nLength); + LOGNOTE("Sent %u bytes to RTP-MIDI host", nLength); } if (!sentRTP && m_pUDPSendSocket) { int res = m_pUDPSendSocket->SendTo(pMessage, nLength, 0, m_UDPDestAddress, m_UDPDestPort); if (res < 0) { - LOGERR("Failed to send %zu bytes to UDP MIDI host", nLength); + LOGERR("Failed to send %u bytes to UDP MIDI host", nLength); } else { - LOGNOTE("Sent %zu bytes to UDP MIDI host (broadcast)", nLength); + LOGNOTE("Sent %u bytes to UDP MIDI host (broadcast)", nLength); } } } \ No newline at end of file diff --git a/src/udpmididevice.h b/src/udpmididevice.h index 9895c9d..3c399f8 100644 --- a/src/udpmididevice.h +++ b/src/udpmididevice.h @@ -57,6 +57,11 @@ private: unsigned m_UDPDestPort = 1999; CIPAddress m_LastUDPSenderAddress; unsigned m_LastUDPSenderPort = 0; + + // SysEx reassembly buffer for UDP/AppleMIDI + uint8_t m_SysExBuffer[MAX_MIDI_MESSAGE]; + size_t m_SysExLen = 0; + bool m_SysExActive = false; }; #endif From 69b2e7d56987629c4da703f1a56c6800df78d96e Mon Sep 17 00:00:00 2001 From: probonopd Date: Sun, 11 May 2025 19:59:21 +0200 Subject: [PATCH 02/10] We can see values in the Performance Editor of the Utility! --- src/mididevice.cpp | 4 +- src/minidexed.h | 4 +- src/performance_sysex.cpp | 201 ++++++++++++++++++++++---------------- src/performance_sysex.h | 5 +- 4 files changed, 125 insertions(+), 89 deletions(-) diff --git a/src/mididevice.cpp b/src/mididevice.cpp index 318c5ae..09d2c50 100644 --- a/src/mididevice.cpp +++ b/src/mididevice.cpp @@ -239,8 +239,8 @@ void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsign // Handle MiniDexed performance SysEx messages if (pMessage[0] == MIDI_SYSTEM_EXCLUSIVE_BEGIN && pMessage[1] == 0x7D ) { LOGNOTE("MiniDexed SysEx handler entered, nLength=%u", nLength); - // Use performance_sysex.h to handle the SysEx message - handle_performance_sysex(pMessage, this, m_pSynthesizer->GetPerformanceConfig(), nCable); + // Update: Pass m_pSynthesizer to handle_performance_sysex for synth/GUI update + handle_performance_sysex(pMessage, this, m_pSynthesizer, nCable); return; } else { LOGNOTE("MiniDexed SysEx handler NOT entered, nLength=%u", nLength); diff --git a/src/minidexed.h b/src/minidexed.h index 1658482..8b0ee9d 100644 --- a/src/minidexed.h +++ b/src/minidexed.h @@ -244,10 +244,12 @@ public: bool InitNetwork(); void UpdateNetwork(); +public: + void LoadPerformanceParameters(void); + private: int16_t ApplyNoteLimits (int16_t pitch, unsigned nTG); // returns < 0 to ignore note uint8_t m_uchOPMask[CConfig::AllToneGenerators]; - void LoadPerformanceParameters(void); void ProcessSound (void); const char* GetNetworkDeviceShortName() const; diff --git a/src/performance_sysex.cpp b/src/performance_sysex.cpp index efda130..f952a2d 100644 --- a/src/performance_sysex.cpp +++ b/src/performance_sysex.cpp @@ -30,8 +30,10 @@ LOGMODULE ("PerformanceSysEx"); static constexpr uint8_t SYSEX_START = 0xF0; static constexpr uint8_t SYSEX_END = 0xF7; static constexpr uint8_t MANUFACTURER_ID = 0x7D; -static constexpr uint8_t CMD_GET = 0x10; -static constexpr uint8_t CMD_SET = 0x20; +static constexpr uint8_t GET_GLOBAL = 0x10; +static constexpr uint8_t GET_TG = 0x11; +static constexpr uint8_t SET_GLOBAL = 0x20; +static constexpr uint8_t SET_TG = 0x21; static void send_sysex_response(const uint8_t* data, size_t len, CMIDIDevice* pDevice, unsigned nCable) { if (pDevice) { @@ -40,9 +42,14 @@ static void send_sysex_response(const uint8_t* data, size_t len, CMIDIDevice* pD } } -void handle_performance_sysex(const uint8_t* pMessage, CMIDIDevice* pDevice, CPerformanceConfig* perf, unsigned nCable) { - if (!pMessage || !perf) { - LOGERR("handle_performance_sysex: Null pointer or PerformanceConfig not set"); +void handle_performance_sysex(const uint8_t* pMessage, CMIDIDevice* pDevice, CMiniDexed* miniDexed, unsigned nCable) { + if (!pMessage || !miniDexed) { + LOGERR("handle_performance_sysex: Null pointer or MiniDexed not set"); + return; + } + CPerformanceConfig* perf = miniDexed->GetPerformanceConfig(); + if (!perf) { + LOGERR("handle_performance_sysex: PerformanceConfig not set in MiniDexed"); return; } if (pMessage[0] != SYSEX_START) { @@ -64,15 +71,34 @@ void handle_performance_sysex(const uint8_t* pMessage, CMIDIDevice* pDevice, CPe uint8_t cmd = 0; uint8_t offset = 0; - if (pMessage[2] == CMD_GET || pMessage[2] == CMD_SET) { - cmd = pMessage[2]; + // New protocol: 0x10 = global GET, 0x11 = per-TG GET, 0x20 = global SET, 0x21 = per-TG SET + if (pMessage[2] == GET_GLOBAL) { + cmd = GET_GLOBAL; + offset = 3; + LOGNOTE("SysEx: Global GET request"); + } else if (pMessage[2] == GET_TG) { + cmd = GET_TG; + // Allow both F0 7D 11 F7 (all params) and F0 7D 11 F7 (single param) + if (len < 4) { + LOGERR("SysEx: Per-TG GET request too short"); + return; + } + tg = pMessage[3]; + offset = 4; + LOGNOTE("SysEx: TG-specific GET request for TG %u", tg); + } else if (pMessage[2] == SET_GLOBAL) { + cmd = SET_GLOBAL; offset = 3; - LOGNOTE("SysEx: Global %s request", cmd == CMD_GET ? "GET" : "SET"); - } else if (pMessage[2] < 0x80 && (pMessage[3] == CMD_GET || pMessage[3] == CMD_SET)) { - tg = pMessage[2]; - cmd = pMessage[3]; + LOGNOTE("SysEx: Global SET request"); + } else if (pMessage[2] == SET_TG) { + cmd = SET_TG; + if (len < 5) { + LOGERR("SysEx: Per-TG SET request too short"); + return; + } + tg = pMessage[3]; offset = 4; - LOGNOTE("SysEx: TG-specific %s request for TG %u", cmd == CMD_GET ? "GET" : "SET", tg); + LOGNOTE("SysEx: TG-specific SET request for TG %u", tg); } else { LOGERR("SysEx: Unrecognized message structure"); return; @@ -80,92 +106,100 @@ void handle_performance_sysex(const uint8_t* pMessage, CMIDIDevice* pDevice, CPe if (offset == len) { // Dump all global or all TG parameters - if (cmd == CMD_GET) { - if (tg == 0xFF) { - // Dump all global - size_t count = 0; - const uint16_t* params = CPerformanceConfig::GetAllGlobalParams(count); - uint16_t values[16] = {0}; - if (perf->GetGlobalParameters(params, values, count)) { - LOGNOTE("SysEx: Dumping all global parameters"); - uint8_t resp[64] = {0}; - resp[0] = SYSEX_START; - resp[1] = MANUFACTURER_ID; - resp[2] = CMD_GET; - size_t idx = 3; - for (size_t i = 0; i < count; ++i) { - resp[idx++] = (params[i] >> 8) & 0xFF; - resp[idx++] = params[i] & 0xFF; - resp[idx++] = (values[i] >> 8) & 0xFF; - resp[idx++] = values[i] & 0xFF; - LOGNOTE(" Param 0x%04X = 0x%04X", params[i], values[i]); - } - resp[idx++] = SYSEX_END; - send_sysex_response(resp, idx, pDevice, nCable); - } else { - LOGERR("SysEx: Failed to get all global parameters"); + if (cmd == GET_GLOBAL) { + // Dump all global + size_t count = 0; + const uint16_t* params = CPerformanceConfig::GetAllGlobalParams(count); + uint16_t values[16] = {0}; + if (perf->GetGlobalParameters(params, values, count)) { + LOGNOTE("SysEx: Dumping all global parameters"); + uint8_t resp[64] = {0}; + resp[0] = SYSEX_START; + resp[1] = MANUFACTURER_ID; + resp[2] = SET_GLOBAL; // F0 7D 20 ... + size_t idx = 3; + for (size_t i = 0; i < count; ++i) { + resp[idx++] = (params[i] >> 8) & 0xFF; + resp[idx++] = params[i] & 0xFF; + resp[idx++] = (values[i] >> 8) & 0xFF; + resp[idx++] = values[i] & 0xFF; + LOGNOTE(" Param 0x%04X = 0x%04X", params[i], values[i]); } + resp[idx++] = SYSEX_END; + send_sysex_response(resp, idx, pDevice, nCable); } else { - // Dump all TG - size_t count = 0; - const uint16_t* params = CPerformanceConfig::GetAllTGParams(count); - uint16_t values[32] = {0}; - if (perf->GetTGParameters(params, values, count, tg)) { - LOGNOTE("SysEx: Dumping all TG parameters for TG %u", tg); - uint8_t resp[128] = {0}; - resp[0] = SYSEX_START; - resp[1] = MANUFACTURER_ID; - resp[2] = tg; - resp[3] = CMD_GET; - size_t idx = 4; - for (size_t i = 0; i < count; ++i) { - resp[idx++] = (params[i] >> 8) & 0xFF; - resp[idx++] = params[i] & 0xFF; - resp[idx++] = (values[i] >> 8) & 0xFF; - resp[idx++] = values[i] & 0xFF; - LOGNOTE(" Param 0x%04X = 0x%04X", params[i], values[i]); - } - resp[idx++] = SYSEX_END; - send_sysex_response(resp, idx, pDevice, nCable); - } else { - LOGERR("SysEx: Failed to get all TG parameters for TG %u", tg); + LOGERR("SysEx: Failed to get all global parameters"); + } + } else if (cmd == GET_TG) { + // Dump all TG + size_t count = 0; + const uint16_t* params = CPerformanceConfig::GetAllTGParams(count); + uint16_t values[32] = {0}; + if (perf->GetTGParameters(params, values, count, tg)) { + LOGNOTE("SysEx: Dumping all TG parameters for TG %u", tg); + uint8_t resp[128] = {0}; + resp[0] = SYSEX_START; + resp[1] = MANUFACTURER_ID; + resp[2] = SET_TG; // F0 7D 21 nn ... + resp[3] = tg; + size_t idx = 4; + for (size_t i = 0; i < count; ++i) { + resp[idx++] = (params[i] >> 8) & 0xFF; + resp[idx++] = params[i] & 0xFF; + resp[idx++] = (values[i] >> 8) & 0xFF; + resp[idx++] = values[i] & 0xFF; + LOGNOTE(" Param 0x%04X = 0x%04X", params[i], values[i]); } + resp[idx++] = SYSEX_END; + send_sysex_response(resp, idx, pDevice, nCable); + } else { + LOGERR("SysEx: Failed to get all TG parameters for TG %u", tg); } } return; } while (offset + 1 < len) { + if (pMessage[offset] == SYSEX_END) break; uint16_t param = (pMessage[offset] << 8) | pMessage[offset+1]; offset += 2; - if (cmd == CMD_GET) { + if (cmd == GET_GLOBAL) { uint16_t value = 0; bool ok = false; - if (tg == 0xFF) { - ok = perf->GetGlobalParameters(¶m, &value, 1); - LOGNOTE("SysEx: GET global param 0x%04X -> 0x%04X (%s)", param, value, ok ? "OK" : "FAIL"); - // Build and send response - uint8_t resp[9] = {SYSEX_START, MANUFACTURER_ID, CMD_GET, (uint8_t)(param >> 8), (uint8_t)(param & 0xFF), (uint8_t)(value >> 8), (uint8_t)(value & 0xFF), SYSEX_END}; - send_sysex_response(resp, 8, pDevice, nCable); - } else { - ok = perf->GetTGParameters(¶m, &value, 1, tg); - LOGNOTE("SysEx: GET TG %u param 0x%04X -> 0x%04X (%s)", tg, param, value, ok ? "OK" : "FAIL"); - uint8_t resp[10] = {SYSEX_START, MANUFACTURER_ID, tg, CMD_GET, (uint8_t)(param >> 8), (uint8_t)(param & 0xFF), (uint8_t)(value >> 8), (uint8_t)(value & 0xFF), SYSEX_END}; - send_sysex_response(resp, 9, pDevice, nCable); + ok = perf->GetGlobalParameters(¶m, &value, 1); + LOGNOTE("SysEx: GET global param 0x%04X -> 0x%04X (%s)", param, value, ok ? "OK" : "FAIL"); + // Build and send response (use SET_GLOBAL as response type) + uint8_t resp[8] = {SYSEX_START, MANUFACTURER_ID, SET_GLOBAL, (uint8_t)(param >> 8), (uint8_t)(param & 0xFF), (uint8_t)(value >> 8), (uint8_t)(value & 0xFF), SYSEX_END}; + send_sysex_response(resp, 8, pDevice, nCable); + } else if (cmd == GET_TG) { + uint16_t value = 0; + bool ok = false; + ok = perf->GetTGParameters(¶m, &value, 1, tg); + LOGNOTE("SysEx: GET TG %u param 0x%04X -> 0x%04X (%s)", tg, param, value, ok ? "OK" : "FAIL"); + uint8_t resp[10] = {SYSEX_START, MANUFACTURER_ID, SET_TG, tg, (uint8_t)(param >> 8), (uint8_t)(param & 0xFF), (uint8_t)(value >> 8), (uint8_t)(value & 0xFF), SYSEX_END}; + send_sysex_response(resp, 9, pDevice, nCable); + } else if (cmd == SET_GLOBAL) { + if (offset + 1 >= len) { + LOGERR("SysEx: Global SET param 0x%04X missing value bytes", param); + break; } - } else if (cmd == CMD_SET) { + uint16_t value = (pMessage[offset] << 8) | pMessage[offset+1]; + offset += 2; + bool ok = perf->SetGlobalParameters(¶m, &value, 1); + LOGNOTE("SysEx: SET global param 0x%04X = 0x%04X (%s)", param, value, ok ? "OK" : "FAIL"); + if (ok && miniDexed) { + miniDexed->LoadPerformanceParameters(); + } + } else if (cmd == SET_TG) { if (offset + 1 >= len) { - LOGERR("SysEx: SET param 0x%04X missing value bytes", param); + LOGERR("SysEx: TG SET param 0x%04X missing value bytes", param); break; } uint16_t value = (pMessage[offset] << 8) | pMessage[offset+1]; offset += 2; - bool ok = false; - if (tg == 0xFF) { - ok = perf->SetGlobalParameters(¶m, &value, 1); - LOGNOTE("SysEx: SET global param 0x%04X = 0x%04X (%s)", param, value, ok ? "OK" : "FAIL"); - } else { - ok = perf->SetTGParameters(¶m, &value, 1, tg); - LOGNOTE("SysEx: SET TG %u param 0x%04X = 0x%04X (%s)", tg, param, value, ok ? "OK" : "FAIL"); + bool ok = perf->SetTGParameters(¶m, &value, 1, tg); + LOGNOTE("SysEx: SET TG %u param 0x%04X = 0x%04X (%s)", tg, param, value, ok ? "OK" : "FAIL"); + if (ok && miniDexed) { + miniDexed->LoadPerformanceParameters(); } } } @@ -187,19 +221,18 @@ Examples of MiniDexed Performance SysEx messages and expected handler behavior: - Logs and dumps all global parameters and values. 4. Get a TG Parameter (e.g., Volume for TG 2) - Send: F0 7D 02 10 00 03 F7 + Send: F0 7D 11 02 00 03 F7 - Logs GET for TG 2 param 0x0003, queries value, logs result. 5. Set a TG Parameter (e.g., Pan for TG 1 to 64) - Send: F0 7D 01 20 00 04 00 40 F7 + Send: F0 7D 21 01 00 04 00 40 F7 - Logs SET for TG 1 param 0x0004, updates value, logs result. 6. Get All Parameters for TG 0 - Send: F0 7D 00 10 F7 + Send: F0 7D 11 00 F7 - Logs and dumps all TG 0 parameters and values. 7. Malformed or Unknown Parameter Send: F0 7D 10 12 34 F7 - Logs GET for global param 0x1234, logs FAIL if unsupported. - */ diff --git a/src/performance_sysex.h b/src/performance_sysex.h index 3a85de7..b541eaa 100644 --- a/src/performance_sysex.h +++ b/src/performance_sysex.h @@ -29,7 +29,8 @@ class CMIDIDevice; // Handles a MiniDexed performance SysEx message. // pMessage: pointer to the SysEx message (must be at least 2 bytes) // pDevice: the MIDI device to send the response to -// perf: the performance config to operate on -void handle_performance_sysex(const uint8_t* pMessage, CMIDIDevice* pDevice, CPerformanceConfig* perf, unsigned nCable = 0); +// miniDexed: pointer to the main synth instance (for applying changes) +// Protocol: 0x10 = global, 0x11 = per-TG +void handle_performance_sysex(const uint8_t* pMessage, CMIDIDevice* pDevice, class CMiniDexed* miniDexed, unsigned nCable = 0); #endif // PERFORMANCE_SYSEX_H From a8b6c0e12ffd87fac58c2ff39b3efacd28ba0bca Mon Sep 17 00:00:00 2001 From: probonopd Date: Sun, 11 May 2025 20:34:05 +0200 Subject: [PATCH 03/10] MIDI message reassemby; we can play notes over rtpMIDI again! --- src/mididevice.cpp | 4 --- src/serialmididevice.cpp | 19 ++++++++---- src/udpmididevice.cpp | 67 +++++++++++++++++++++++++++++++--------- src/udpmididevice.h | 4 +++ 4 files changed, 70 insertions(+), 24 deletions(-) diff --git a/src/mididevice.cpp b/src/mididevice.cpp index 09d2c50..7f7d388 100644 --- a/src/mididevice.cpp +++ b/src/mididevice.cpp @@ -238,12 +238,8 @@ void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsign // Handle MiniDexed performance SysEx messages if (pMessage[0] == MIDI_SYSTEM_EXCLUSIVE_BEGIN && pMessage[1] == 0x7D ) { - LOGNOTE("MiniDexed SysEx handler entered, nLength=%u", nLength); - // Update: Pass m_pSynthesizer to handle_performance_sysex for synth/GUI update handle_performance_sysex(pMessage, this, m_pSynthesizer, nCable); return; - } else { - LOGNOTE("MiniDexed SysEx handler NOT entered, nLength=%u", nLength); } // Master Volume is set using a MIDI SysEx message as follows: diff --git a/src/serialmididevice.cpp b/src/serialmididevice.cpp index 239cfd6..3c97aac 100644 --- a/src/serialmididevice.cpp +++ b/src/serialmididevice.cpp @@ -85,15 +85,22 @@ void CSerialMIDIDevice::Process (void) m_SysExLen = 0; } if (m_SysExActive) { - if (m_SysExLen < MAX_MIDI_MESSAGE) { - m_SysExBuffer[m_SysExLen++] = uchData; - } - if (uchData == 0xF7 || m_SysExLen >= MAX_MIDI_MESSAGE) { - MIDIMessageHandler(m_SysExBuffer, m_SysExLen, 0); + if ((uchData & 0x80) && uchData != 0xF0 && uchData != 0xF7) { + // Abort SysEx on new status byte (except 0xF0/0xF7) m_SysExActive = false; m_SysExLen = 0; + // Process this byte as normal MIDI below + } else { + if (m_SysExLen < MAX_MIDI_MESSAGE) { + m_SysExBuffer[m_SysExLen++] = uchData; + } + if (uchData == 0xF7 || m_SysExLen >= MAX_MIDI_MESSAGE) { + MIDIMessageHandler(m_SysExBuffer, m_SysExLen, 0); + m_SysExActive = false; + m_SysExLen = 0; + } + if (m_SysExActive) continue; } - continue; } // System Real Time messages may appear anywhere in the byte stream, so handle them specially diff --git a/src/udpmididevice.cpp b/src/udpmididevice.cpp index 57b87c3..f656f2e 100644 --- a/src/udpmididevice.cpp +++ b/src/udpmididevice.cpp @@ -76,6 +76,35 @@ boolean CUDPMIDIDevice::Initialize (void) return true; } +void CUDPMIDIDevice::UdpMidiReassembly(uint8_t byte, unsigned cable) { + // System Real Time messages (single byte) + if (byte == 0xF8 || byte == 0xFA || byte == 0xFB || byte == 0xFC || byte == 0xFE || byte == 0xFF) { + MIDIMessageHandler(&byte, 1, cable); + return; + } + // Status byte + if ((byte & 0x80) == 0x80 && (byte & 0xF0) != 0xF0) { + m_udpMidiMsg[0] = byte; + m_udpMidiState = 1; + return; + } + // Data byte + if (m_udpMidiState > 0) { + m_udpMidiMsg[m_udpMidiState++] = byte; + if ((m_udpMidiMsg[0] & 0xE0) == 0xC0 || (m_udpMidiMsg[0] & 0xF0) == 0xD0) { + // Program Change or Channel Pressure (2 bytes) + if (m_udpMidiState == 2) { + MIDIMessageHandler(m_udpMidiMsg, 2, cable); + m_udpMidiState = 0; + } + } else if (m_udpMidiState == 3) { + // All other channel messages (3 bytes) + MIDIMessageHandler(m_udpMidiMsg, 3, cable); + m_udpMidiState = 0; + } + } +} + // Methods to handle MIDI events void CUDPMIDIDevice::OnAppleMIDIDataReceived(const u8* pData, size_t nSize) @@ -87,17 +116,22 @@ void CUDPMIDIDevice::OnAppleMIDIDataReceived(const u8* pData, size_t nSize) m_SysExLen = 0; } if (m_SysExActive) { - if (m_SysExLen < MAX_MIDI_MESSAGE) { - m_SysExBuffer[m_SysExLen++] = byte; - } - if (byte == 0xF7 || m_SysExLen >= MAX_MIDI_MESSAGE) { - MIDIMessageHandler(m_SysExBuffer, m_SysExLen, VIRTUALCABLE); + if ((byte & 0x80) && byte != 0xF0 && byte != 0xF7) { m_SysExActive = false; m_SysExLen = 0; + } else { + if (m_SysExLen < MAX_MIDI_MESSAGE) { + m_SysExBuffer[m_SysExLen++] = byte; + } + if (byte == 0xF7 || m_SysExLen >= MAX_MIDI_MESSAGE) { + MIDIMessageHandler(m_SysExBuffer, m_SysExLen, VIRTUALCABLE); + m_SysExActive = false; + m_SysExLen = 0; + } + if (m_SysExActive) continue; } - continue; } - MIDIMessageHandler(&byte, 1, VIRTUALCABLE); + UdpMidiReassembly(byte, VIRTUALCABLE); } } @@ -120,17 +154,22 @@ void CUDPMIDIDevice::OnUDPMIDIDataReceived(const u8* pData, size_t nSize) m_SysExLen = 0; } if (m_SysExActive) { - if (m_SysExLen < MAX_MIDI_MESSAGE) { - m_SysExBuffer[m_SysExLen++] = byte; - } - if (byte == 0xF7 || m_SysExLen >= MAX_MIDI_MESSAGE) { - MIDIMessageHandler(m_SysExBuffer, m_SysExLen, VIRTUALCABLE); + if ((byte & 0x80) && byte != 0xF0 && byte != 0xF7) { m_SysExActive = false; m_SysExLen = 0; + } else { + if (m_SysExLen < MAX_MIDI_MESSAGE) { + m_SysExBuffer[m_SysExLen++] = byte; + } + if (byte == 0xF7 || m_SysExLen >= MAX_MIDI_MESSAGE) { + MIDIMessageHandler(m_SysExBuffer, m_SysExLen, VIRTUALCABLE); + m_SysExActive = false; + m_SysExLen = 0; + } + if (m_SysExActive) continue; } - continue; } - MIDIMessageHandler(&byte, 1, VIRTUALCABLE); + UdpMidiReassembly(byte, VIRTUALCABLE); } } diff --git a/src/udpmididevice.h b/src/udpmididevice.h index 3c399f8..40d83e5 100644 --- a/src/udpmididevice.h +++ b/src/udpmididevice.h @@ -62,6 +62,10 @@ private: uint8_t m_SysExBuffer[MAX_MIDI_MESSAGE]; size_t m_SysExLen = 0; bool m_SysExActive = false; + + void UdpMidiReassembly(uint8_t byte, unsigned cable); + uint8_t m_udpMidiMsg[4] = {0}; + uint8_t m_udpMidiState = 0; }; #endif From d5d8ccd9f8085db6987409ad5d4072eedfaff5b7 Mon Sep 17 00:00:00 2001 From: probonopd Date: Sun, 11 May 2025 21:30:40 +0200 Subject: [PATCH 04/10] MIDI-safe 7-bit bytes --- src/performance_sysex.cpp | 69 +++++++++++++++++++++------------------ 1 file changed, 37 insertions(+), 32 deletions(-) diff --git a/src/performance_sysex.cpp b/src/performance_sysex.cpp index f952a2d..bb5bbe7 100644 --- a/src/performance_sysex.cpp +++ b/src/performance_sysex.cpp @@ -25,6 +25,21 @@ #include "minidexed.h" #include "mididevice.h" +// Helper functions for 2's complement 14-bit MIDI-safe encoding/decoding +static void encode_midi14_signed_7bit(int value, uint8_t& msb, uint8_t& lsb) { + // Clamp to 14-bit signed range + if (value < -8192) value = -8192; + if (value > 8191) value = 8191; + uint16_t midi14 = (value < 0) ? (0x2000 + value) : value; + midi14 &= 0x3FFF; + msb = (midi14 >> 7) & 0x7F; + lsb = midi14 & 0x7F; +} +static int decode_midi14_signed_7bit(uint8_t msb, uint8_t lsb) { + uint16_t midi14 = ((msb & 0x7F) << 7) | (lsb & 0x7F); + return (midi14 & 0x2000) ? (midi14 - 0x4000) : midi14; +} + LOGMODULE ("PerformanceSysEx"); static constexpr uint8_t SYSEX_START = 0xF0; @@ -166,6 +181,13 @@ void handle_performance_sysex(const uint8_t* pMessage, CMIDIDevice* pDevice, CMi uint16_t value = 0; bool ok = false; ok = perf->GetGlobalParameters(¶m, &value, 1); + if (param == CPerformanceConfig::PARAM_DETUNE || param == CPerformanceConfig::PARAM_NOTE_SHIFT) { + uint8_t msb, lsb; + encode_midi14_signed_7bit((int16_t)value, msb, lsb); + uint8_t resp[9] = {SYSEX_START, MANUFACTURER_ID, SET_GLOBAL, (uint8_t)(param >> 8), (uint8_t)(param & 0xFF), msb, lsb, SYSEX_END}; + send_sysex_response(resp, 8, pDevice, nCable); + continue; + } LOGNOTE("SysEx: GET global param 0x%04X -> 0x%04X (%s)", param, value, ok ? "OK" : "FAIL"); // Build and send response (use SET_GLOBAL as response type) uint8_t resp[8] = {SYSEX_START, MANUFACTURER_ID, SET_GLOBAL, (uint8_t)(param >> 8), (uint8_t)(param & 0xFF), (uint8_t)(value >> 8), (uint8_t)(value & 0xFF), SYSEX_END}; @@ -174,6 +196,13 @@ void handle_performance_sysex(const uint8_t* pMessage, CMIDIDevice* pDevice, CMi uint16_t value = 0; bool ok = false; ok = perf->GetTGParameters(¶m, &value, 1, tg); + if (param == CPerformanceConfig::PARAM_DETUNE || param == CPerformanceConfig::PARAM_NOTE_SHIFT) { + uint8_t msb, lsb; + encode_midi14_signed_7bit((int16_t)value, msb, lsb); + uint8_t resp[11] = {SYSEX_START, MANUFACTURER_ID, SET_TG, tg, (uint8_t)(param >> 8), (uint8_t)(param & 0xFF), msb, lsb, SYSEX_END}; + send_sysex_response(resp, 9, pDevice, nCable); + continue; + } LOGNOTE("SysEx: GET TG %u param 0x%04X -> 0x%04X (%s)", tg, param, value, ok ? "OK" : "FAIL"); uint8_t resp[10] = {SYSEX_START, MANUFACTURER_ID, SET_TG, tg, (uint8_t)(param >> 8), (uint8_t)(param & 0xFF), (uint8_t)(value >> 8), (uint8_t)(value & 0xFF), SYSEX_END}; send_sysex_response(resp, 9, pDevice, nCable); @@ -183,6 +212,10 @@ void handle_performance_sysex(const uint8_t* pMessage, CMIDIDevice* pDevice, CMi break; } uint16_t value = (pMessage[offset] << 8) | pMessage[offset+1]; + if (param == CPerformanceConfig::PARAM_DETUNE || param == CPerformanceConfig::PARAM_NOTE_SHIFT) { + int decoded = decode_midi14_signed_7bit(pMessage[offset], pMessage[offset+1]); + value = (uint16_t)decoded; + } offset += 2; bool ok = perf->SetGlobalParameters(¶m, &value, 1); LOGNOTE("SysEx: SET global param 0x%04X = 0x%04X (%s)", param, value, ok ? "OK" : "FAIL"); @@ -195,6 +228,10 @@ void handle_performance_sysex(const uint8_t* pMessage, CMIDIDevice* pDevice, CMi break; } uint16_t value = (pMessage[offset] << 8) | pMessage[offset+1]; + if (param == CPerformanceConfig::PARAM_DETUNE || param == CPerformanceConfig::PARAM_NOTE_SHIFT) { + int decoded = decode_midi14_signed_7bit(pMessage[offset], pMessage[offset+1]); + value = (uint16_t)decoded; + } offset += 2; bool ok = perf->SetTGParameters(¶m, &value, 1, tg); LOGNOTE("SysEx: SET TG %u param 0x%04X = 0x%04X (%s)", tg, param, value, ok ? "OK" : "FAIL"); @@ -204,35 +241,3 @@ void handle_performance_sysex(const uint8_t* pMessage, CMIDIDevice* pDevice, CMi } } } - -/* -Examples of MiniDexed Performance SysEx messages and expected handler behavior: - -1. Get a Global Parameter (e.g., CompressorEnable) - Send: F0 7D 10 00 00 F7 - - Logs GET for global param 0x0000, queries value, logs result. - -2. Set a Global Parameter (e.g., ReverbEnable ON) - Send: F0 7D 20 00 01 00 01 F7 - - Logs SET for global param 0x0001, updates value, logs result. - -3. Get All Global Parameters - Send: F0 7D 10 F7 - - Logs and dumps all global parameters and values. - -4. Get a TG Parameter (e.g., Volume for TG 2) - Send: F0 7D 11 02 00 03 F7 - - Logs GET for TG 2 param 0x0003, queries value, logs result. - -5. Set a TG Parameter (e.g., Pan for TG 1 to 64) - Send: F0 7D 21 01 00 04 00 40 F7 - - Logs SET for TG 1 param 0x0004, updates value, logs result. - -6. Get All Parameters for TG 0 - Send: F0 7D 11 00 F7 - - Logs and dumps all TG 0 parameters and values. - -7. Malformed or Unknown Parameter - Send: F0 7D 10 12 34 F7 - - Logs GET for global param 0x1234, logs FAIL if unsupported. -*/ From 3ce444506725df96144cebee00f3703a6bdb7e96 Mon Sep 17 00:00:00 2001 From: probonopd Date: Mon, 12 May 2025 18:53:58 +0200 Subject: [PATCH 05/10] Update performance config when new voice is received via SysEx --- src/mididevice.cpp | 8 +++++++- src/minidexed.cpp | 6 ++++++ src/minidexed.h | 3 ++- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/mididevice.cpp b/src/mididevice.cpp index 7f7d388..185a31c 100644 --- a/src/mididevice.cpp +++ b/src/mididevice.cpp @@ -816,9 +816,15 @@ void CMIDIDevice::HandleSystemExclusive(const uint8_t* pMessage, const size_t nL LOGERR("Unknown SysEx message."); break; case 100: - // load sysex-data into voice memory + // Load sysex-data into voice memory LOGDBG("One Voice bulk upload"); m_pSynthesizer->loadVoiceParameters(pMessage,nTG); + // Also update performance config so the new voice is not lost + if (m_pSynthesizer && m_pSynthesizer->GetPerformanceConfig()) { + uint8_t unpackedVoice[156]; + m_pSynthesizer->GetCurrentVoiceData(unpackedVoice, nTG); + m_pSynthesizer->GetPerformanceConfig()->SetVoiceDataToTxt(unpackedVoice, nTG); + } break; case 200: LOGDBG("Bank bulk upload."); diff --git a/src/minidexed.cpp b/src/minidexed.cpp index 51e6444..6c4c11e 100644 --- a/src/minidexed.cpp +++ b/src/minidexed.cpp @@ -2498,3 +2498,9 @@ bool CMiniDexed::InitNetwork() return false; } } + +void CMiniDexed::GetCurrentVoiceData(uint8_t* dest, unsigned nTG) { + if (nTG < m_nToneGenerators && m_pTG[nTG]) { + m_pTG[nTG]->getVoiceData(dest); + } +} diff --git a/src/minidexed.h b/src/minidexed.h index 8b0ee9d..55f45e8 100644 --- a/src/minidexed.h +++ b/src/minidexed.h @@ -244,9 +244,10 @@ public: bool InitNetwork(); void UpdateNetwork(); -public: void LoadPerformanceParameters(void); + void GetCurrentVoiceData(uint8_t* dest, unsigned nTG); + private: int16_t ApplyNoteLimits (int16_t pitch, unsigned nTG); // returns < 0 to ignore note uint8_t m_uchOPMask[CConfig::AllToneGenerators]; From b4f4e8fdd4f80df9b8883e7a01d819198f8c4353 Mon Sep 17 00:00:00 2001 From: probonopd Date: Thu, 15 May 2025 00:41:38 +0200 Subject: [PATCH 06/10] No more crackle after voice uploading --- src/mididevice.cpp | 7 +++++-- src/minidexed.cpp | 6 ++++++ src/minidexed.h | 10 ++++++++++ 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/mididevice.cpp b/src/mididevice.cpp index 185a31c..c1ccd2a 100644 --- a/src/mididevice.cpp +++ b/src/mididevice.cpp @@ -819,11 +819,14 @@ void CMIDIDevice::HandleSystemExclusive(const uint8_t* pMessage, const size_t nL // Load sysex-data into voice memory LOGDBG("One Voice bulk upload"); m_pSynthesizer->loadVoiceParameters(pMessage,nTG); - // Also update performance config so the new voice is not lost + // Defer performance config update to main loop if (m_pSynthesizer && m_pSynthesizer->GetPerformanceConfig()) { uint8_t unpackedVoice[156]; m_pSynthesizer->GetCurrentVoiceData(unpackedVoice, nTG); - m_pSynthesizer->GetPerformanceConfig()->SetVoiceDataToTxt(unpackedVoice, nTG); + CMiniDexed* pMiniDexed = static_cast(m_pSynthesizer); + if (pMiniDexed) { + pMiniDexed->SetPendingVoicePerformanceUpdate(unpackedVoice, nTG); + } } break; case 200: diff --git a/src/minidexed.cpp b/src/minidexed.cpp index 6c4c11e..6b21fe6 100644 --- a/src/minidexed.cpp +++ b/src/minidexed.cpp @@ -2504,3 +2504,9 @@ void CMiniDexed::GetCurrentVoiceData(uint8_t* dest, unsigned nTG) { m_pTG[nTG]->getVoiceData(dest); } } + +void CMiniDexed::SetPendingVoicePerformanceUpdate(const uint8_t* voiceData, uint8_t tg) { + m_PendingVoicePerformanceUpdate.pending = true; + memcpy(m_PendingVoicePerformanceUpdate.voiceData, voiceData, 156); + m_PendingVoicePerformanceUpdate.tg = tg; +} diff --git a/src/minidexed.h b/src/minidexed.h index 55f45e8..3f85bac 100644 --- a/src/minidexed.h +++ b/src/minidexed.h @@ -248,6 +248,9 @@ public: void GetCurrentVoiceData(uint8_t* dest, unsigned nTG); +public: + void SetPendingVoicePerformanceUpdate(const uint8_t* voiceData, uint8_t tg); + private: int16_t ApplyNoteLimits (int16_t pitch, unsigned nTG); // returns < 0 to ignore note uint8_t m_uchOPMask[CConfig::AllToneGenerators]; @@ -367,6 +370,13 @@ private: bool m_bLoadPerformanceBusy; bool m_bLoadPerformanceBankBusy; bool m_bSaveAsDeault; + + // Add for deferred performance update after SysEx voice load + struct PendingVoicePerformanceUpdate { + bool pending = false; + uint8_t voiceData[156]; + uint8_t tg = 0; + } m_PendingVoicePerformanceUpdate; }; #endif From 457906267ca9c6bbabe055668579859b51b4b07b Mon Sep 17 00:00:00 2001 From: probonopd Date: Thu, 15 May 2025 00:52:56 +0200 Subject: [PATCH 07/10] MIDI standard: encode as unsigned with center at 8192 --- src/performance_sysex.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/performance_sysex.cpp b/src/performance_sysex.cpp index bb5bbe7..9df78fc 100644 --- a/src/performance_sysex.cpp +++ b/src/performance_sysex.cpp @@ -25,19 +25,18 @@ #include "minidexed.h" #include "mididevice.h" -// Helper functions for 2's complement 14-bit MIDI-safe encoding/decoding +// Helper functions for standard MIDI 14-bit signed value encoding/decoding (offset method) static void encode_midi14_signed_7bit(int value, uint8_t& msb, uint8_t& lsb) { // Clamp to 14-bit signed range if (value < -8192) value = -8192; if (value > 8191) value = 8191; - uint16_t midi14 = (value < 0) ? (0x2000 + value) : value; - midi14 &= 0x3FFF; + uint16_t midi14 = (uint16_t)(value + 8192); // MIDI standard: center is 8192 msb = (midi14 >> 7) & 0x7F; lsb = midi14 & 0x7F; } static int decode_midi14_signed_7bit(uint8_t msb, uint8_t lsb) { uint16_t midi14 = ((msb & 0x7F) << 7) | (lsb & 0x7F); - return (midi14 & 0x2000) ? (midi14 - 0x4000) : midi14; + return (int)midi14 - 8192; } LOGMODULE ("PerformanceSysEx"); From 7f4f4f8ce8e0c3d32af378513b859ab1af4d24dc Mon Sep 17 00:00:00 2001 From: probonopd Date: Sun, 18 May 2025 07:30:52 +0200 Subject: [PATCH 08/10] Fix SetPendingVoicePerformanceUpdate --- src/mididevice.cpp | 6 ++---- src/minidexed.cpp | 17 +++++++++++++---- src/minidexed.h | 2 +- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/mididevice.cpp b/src/mididevice.cpp index c1ccd2a..a339b33 100644 --- a/src/mididevice.cpp +++ b/src/mididevice.cpp @@ -819,13 +819,11 @@ void CMIDIDevice::HandleSystemExclusive(const uint8_t* pMessage, const size_t nL // Load sysex-data into voice memory LOGDBG("One Voice bulk upload"); m_pSynthesizer->loadVoiceParameters(pMessage,nTG); - // Defer performance config update to main loop + // Defer performance config update to main loop because it would be too slow to do it here if (m_pSynthesizer && m_pSynthesizer->GetPerformanceConfig()) { - uint8_t unpackedVoice[156]; - m_pSynthesizer->GetCurrentVoiceData(unpackedVoice, nTG); CMiniDexed* pMiniDexed = static_cast(m_pSynthesizer); if (pMiniDexed) { - pMiniDexed->SetPendingVoicePerformanceUpdate(unpackedVoice, nTG); + pMiniDexed->SetPendingVoicePerformanceUpdate(nTG); } } break; diff --git a/src/minidexed.cpp b/src/minidexed.cpp index 6b21fe6..ae89c75 100644 --- a/src/minidexed.cpp +++ b/src/minidexed.cpp @@ -2505,8 +2505,17 @@ void CMiniDexed::GetCurrentVoiceData(uint8_t* dest, unsigned nTG) { } } -void CMiniDexed::SetPendingVoicePerformanceUpdate(const uint8_t* voiceData, uint8_t tg) { - m_PendingVoicePerformanceUpdate.pending = true; - memcpy(m_PendingVoicePerformanceUpdate.voiceData, voiceData, 156); - m_PendingVoicePerformanceUpdate.tg = tg; +void CMiniDexed::SetPendingVoicePerformanceUpdate(unsigned nTG) { + if (nTG < m_nToneGenerators && m_pTG[nTG]) { + // Get the current voice data from the synthesizer + uint8_t currentVoiceData[155]; + m_pTG[nTG]->getVoiceData(currentVoiceData); + // We need to set the voice data in the synthesizer's performance config + // to ensure that the performance is not changed back to the previous one + // when a parameter is changed. + // This is a workaround for the fact that the performance config + // is not updated when the voice data is changed because it would be + // too costly to do in the thread. + this->GetPerformanceConfig()->SetVoiceDataToTxt(currentVoiceData, nTG); + } } diff --git a/src/minidexed.h b/src/minidexed.h index 3f85bac..5fc380b 100644 --- a/src/minidexed.h +++ b/src/minidexed.h @@ -249,7 +249,7 @@ public: void GetCurrentVoiceData(uint8_t* dest, unsigned nTG); public: - void SetPendingVoicePerformanceUpdate(const uint8_t* voiceData, uint8_t tg); + void SetPendingVoicePerformanceUpdate(unsigned nTG); private: int16_t ApplyNoteLimits (int16_t pitch, unsigned nTG); // returns < 0 to ignore note From 2a63518131483b5ecec2c2ec504b91705e654609 Mon Sep 17 00:00:00 2001 From: probonopd Date: Sun, 18 May 2025 07:50:11 +0200 Subject: [PATCH 09/10] WLAN firmware also fails now --- .github/workflows/build.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 33f24a8..696493d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -87,10 +87,10 @@ jobs: mkdir -p ./sdcard/hardware/ cp -r ./hwconfig/minidexed_* ./sdcard/minidexed.ini ./sdcard/hardware/ # WLAN firmware - mkdir -p sdcard/firmware - cp circle-stdlib/libs/circle/addon/wlan/sample/hello_wlan/wpa_supplicant.conf sdcard/ - cd sdcard/firmware - make -f ../../circle-stdlib/libs/circle/addon/wlan/firmware/Makefile + # mkdir -p sdcard/firmware + # cp circle-stdlib/libs/circle/addon/wlan/sample/hello_wlan/wpa_supplicant.conf sdcard/ + # cd sdcard/firmware + # make -f ../../circle-stdlib/libs/circle/addon/wlan/firmware/Makefile cd - - name: Upload 64-bit artifacts From 3492a0e33440062b1421b7df9ad047f9e93bdb87 Mon Sep 17 00:00:00 2001 From: probonopd Date: Wed, 28 May 2025 23:59:04 +0200 Subject: [PATCH 10/10] Relax filename length limit for performances --- src/performanceconfig.cpp | 36 ++++++++++++++++++++++++++++-------- src/performanceconfig.h | 2 ++ src/uimenu.cpp | 11 ++++++----- 3 files changed, 36 insertions(+), 13 deletions(-) diff --git a/src/performanceconfig.cpp b/src/performanceconfig.cpp index 03732dc..2dae19f 100644 --- a/src/performanceconfig.cpp +++ b/src/performanceconfig.cpp @@ -830,6 +830,26 @@ std::string CPerformanceConfig::GetPerformanceName(unsigned nID) } } +std::string CPerformanceConfig::GetPerformanceDisplayName(unsigned nID, unsigned maxLength) +{ + std::string fullName = GetPerformanceName(nID); + if (fullName.length() > maxLength && maxLength > 3) + { + return fullName.substr(0, maxLength - 3) + "..."; + } + return fullName; +} + +std::string CPerformanceConfig::GetPerformanceBankDisplayName(unsigned nBankID, unsigned maxLength) +{ + std::string fullName = GetPerformanceBankName(nBankID); + if (fullName.length() > maxLength && maxLength > 3) + { + return fullName.substr(0, maxLength - 3) + "..."; + } + return fullName; +} + unsigned CPerformanceConfig::GetLastPerformance() { return m_nLastPerformance; @@ -938,7 +958,7 @@ bool CPerformanceConfig::CreateNewPerformanceFile(void) } else { - nFileName +=sPerformanceName.substr(0,14); + nFileName += sPerformanceName; } nFileName += ".ini"; m_PerformanceFileName[nNewPerformance]= sPerformanceName; @@ -965,7 +985,7 @@ bool CPerformanceConfig::CreateNewPerformanceFile(void) m_nLastPerformance = nNewPerformance; m_nActualPerformance = nNewPerformance; - new (&m_Properties) CPropertiesFatFsFile(nFileName.c_str(), m_pFileSystem); + m_Properties = CPropertiesFatFsFile(nFileName.c_str(), m_pFileSystem); return true; } @@ -1007,7 +1027,7 @@ bool CPerformanceConfig::ListPerformances() { std::string OriFileName = FileInfo.fname; size_t nLen = OriFileName.length(); - if ( nLen > 8 && nLen <26 && strcmp(OriFileName.substr(6,1).c_str(), "_")==0) + if ( nLen > 8 && nLen < 255 && strcmp(OriFileName.substr(6,1).c_str(), "_")==0) { // Note: m_nLastPerformance - refers to the number (index) of the last performance in memory, // which includes a default performance. @@ -1033,12 +1053,12 @@ bool CPerformanceConfig::ListPerformances() nPIndex = nPIndex-1; if (m_PerformanceFileName[nPIndex].empty()) { - if(nPIndex > m_nLastPerformance) + if (nPIndex > m_nLastPerformance) { m_nLastPerformance=nPIndex; } - std::string FileName = OriFileName.substr(0,OriFileName.length()-4).substr(7,14); + std::string FileName = OriFileName.substr(0,OriFileName.length()-4).substr(7); m_PerformanceFileName[nPIndex] = FileName; #ifdef VERBOSE_DEBUG @@ -1067,7 +1087,7 @@ void CPerformanceConfig::SetNewPerformance (unsigned nID) m_nActualPerformance=nID; std::string FileN = GetPerformanceFullFilePath(nID); - new (&m_Properties) CPropertiesFatFsFile(FileN.c_str(), m_pFileSystem); + m_Properties = CPropertiesFatFsFile(FileN.c_str(), m_pFileSystem); #ifdef VERBOSE_DEBUG LOGNOTE("Selecting Performance: %d (%s)", nID+1, FileN.c_str()); #endif @@ -1183,7 +1203,7 @@ bool CPerformanceConfig::ListPerformanceBanks() // std::string OriFileName = FileInfo.fname; size_t nLen = OriFileName.length(); - if ( nLen > 4 && nLen <26 && strcmp(OriFileName.substr(3,1).c_str(), "_")==0) + if ( nLen > 4 && nLen < 255 && strcmp(OriFileName.substr(3,1).c_str(), "_")==0) { unsigned nBankIndex = stoi(OriFileName.substr(0,3)); // Recall user index numbered 002..NUM_PERFORMANCE_BANKS @@ -1194,7 +1214,7 @@ bool CPerformanceConfig::ListPerformanceBanks() nBankIndex = nBankIndex-1; if (m_PerformanceBankName[nBankIndex].empty()) { - std::string BankName = OriFileName.substr(4,nLen); + std::string BankName = OriFileName.substr(4); m_PerformanceBankName[nBankIndex] = BankName; #ifdef VERBOSE_DEBUG diff --git a/src/performanceconfig.h b/src/performanceconfig.h index 9428e53..445bbca 100644 --- a/src/performanceconfig.h +++ b/src/performanceconfig.h @@ -169,6 +169,8 @@ public: std::string GetPerformanceFileName(unsigned nID); std::string GetPerformanceFullFilePath(unsigned nID); std::string GetPerformanceName(unsigned nID); + std::string GetPerformanceDisplayName(unsigned nID, unsigned maxLength = 14); + std::string GetPerformanceBankDisplayName(unsigned nBankID, unsigned maxLength = 20); unsigned GetLastPerformance(); unsigned GetLastPerformanceBank(); void SetActualPerformanceID(unsigned nID); diff --git a/src/uimenu.cpp b/src/uimenu.cpp index 730f320..75ae385 100644 --- a/src/uimenu.cpp +++ b/src/uimenu.cpp @@ -1614,7 +1614,7 @@ void CUIMenu::PerformanceMenu (CUIMenu *pUIMenu, TMenuEvent Event) break; case MenuEventStepUp: - do + do { if (nValue == nLastPerformance) { @@ -1821,7 +1821,7 @@ void CUIMenu::InputTxt (CUIMenu *pUIMenu, TMenuEvent Event) { case 1: // save new performance NoValidChars = {92, 47, 58, 42, 63, 34, 60,62, 124}; - MaxChars=14; + MaxChars=64; // Support longer performance names MenuTitleL="Performance Name"; MenuTitleR=""; OkTitleL="New Performance"; // \E[?25l @@ -1830,7 +1830,7 @@ void CUIMenu::InputTxt (CUIMenu *pUIMenu, TMenuEvent Event) case 2: // Rename performance - NOT Implemented yet NoValidChars = {92, 47, 58, 42, 63, 34, 60,62, 124}; - MaxChars=14; + MaxChars=64; // Support longer performance names MenuTitleL="Performance Name"; MenuTitleR=""; OkTitleL="Rename Perf."; // \E[?25l @@ -1863,8 +1863,9 @@ void CUIMenu::InputTxt (CUIMenu *pUIMenu, TMenuEvent Event) if(pUIMenu->m_nCurrentParameter == 1 || pUIMenu->m_nCurrentParameter == 2) { pUIMenu->m_InputText = pUIMenu->m_pMiniDexed->GetNewPerformanceDefaultName(); - pUIMenu->m_InputText += " "; - pUIMenu->m_InputText = pUIMenu->m_InputText.substr(0,14); + // Pad to MaxChars instead of hardcoded 14 + pUIMenu->m_InputText += std::string(MaxChars, ' '); + pUIMenu->m_InputText = pUIMenu->m_InputText.substr(0, MaxChars); pUIMenu->m_InputTextPosition=0; nPosition=pUIMenu->m_InputTextPosition; nChar = pUIMenu->m_InputText[nPosition];