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..a339b33 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,19 @@ 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 ) { + handle_performance_sysex(pMessage, this, m_pSynthesizer, nCable); + return; + } + // 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 +465,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 +684,6 @@ void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsign } } } - m_MIDISpinLock.Release (); } void CMIDIDevice::AddDevice (const char *pDeviceName) @@ -700,7 +712,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 +748,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 @@ -794,9 +816,16 @@ 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); + // Defer performance config update to main loop because it would be too slow to do it here + if (m_pSynthesizer && m_pSynthesizer->GetPerformanceConfig()) { + CMiniDexed* pMiniDexed = static_cast(m_pSynthesizer); + if (pMiniDexed) { + pMiniDexed->SetPendingVoicePerformanceUpdate(nTG); + } + } break; case 200: LOGDBG("Bank bulk upload."); 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/minidexed.cpp b/src/minidexed.cpp index 51e6444..ae89c75 100644 --- a/src/minidexed.cpp +++ b/src/minidexed.cpp @@ -2498,3 +2498,24 @@ 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); + } +} + +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 1658482..5fc380b 100644 --- a/src/minidexed.h +++ b/src/minidexed.h @@ -244,10 +244,16 @@ public: bool InitNetwork(); void UpdateNetwork(); + void LoadPerformanceParameters(void); + + void GetCurrentVoiceData(uint8_t* dest, unsigned nTG); + +public: + void SetPendingVoicePerformanceUpdate(unsigned nTG); + 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; @@ -364,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 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..9df78fc --- /dev/null +++ b/src/performance_sysex.cpp @@ -0,0 +1,242 @@ +// +// 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" + +// 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 = (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 (int)midi14 - 8192; +} + +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 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) { + 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, 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) { + 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; + + // 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 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 SET request for TG %u", tg); + } else { + LOGERR("SysEx: Unrecognized message structure"); + return; + } + + if (offset == len) { + // Dump all global or all TG 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 { + 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 == GET_GLOBAL) { + 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}; + 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); + 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); + } else if (cmd == SET_GLOBAL) { + if (offset + 1 >= len) { + LOGERR("SysEx: Global SET param 0x%04X missing value bytes", param); + 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"); + if (ok && miniDexed) { + miniDexed->LoadPerformanceParameters(); + } + } else if (cmd == SET_TG) { + if (offset + 1 >= len) { + LOGERR("SysEx: TG SET param 0x%04X missing value bytes", param); + 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"); + if (ok && miniDexed) { + miniDexed->LoadPerformanceParameters(); + } + } + } +} diff --git a/src/performance_sysex.h b/src/performance_sysex.h new file mode 100644 index 0000000..b541eaa --- /dev/null +++ b/src/performance_sysex.h @@ -0,0 +1,36 @@ +// +// 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 +// 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 diff --git a/src/performanceconfig.cpp b/src/performanceconfig.cpp index dfff8a9..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 @@ -1314,3 +1334,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..445bbca 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); @@ -128,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); @@ -149,6 +192,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..3c97aac 100644 --- a/src/serialmididevice.cpp +++ b/src/serialmididevice.cpp @@ -75,31 +75,32 @@ 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; - continue; + // SysEx reassembly logic + if (uchData == 0xF0 && !m_SysExActive) { + m_SysExActive = true; + m_SysExLen = 0; + } + if (m_SysExActive) { + 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; + } } // System Real Time messages may appear anywhere in the byte stream, so handle them specially 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..f656f2e 100644 --- a/src/udpmididevice.cpp +++ b/src/udpmididevice.cpp @@ -76,11 +76,63 @@ 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) { - 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 ((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; + } + } + UdpMidiReassembly(byte, VIRTUALCABLE); + } } void CUDPMIDIDevice::OnAppleMIDIConnect(const CIPAddress* pIPAddress, const char* pName) @@ -95,7 +147,30 @@ 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 ((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; + } + } + UdpMidiReassembly(byte, VIRTUALCABLE); + } } void CUDPMIDIDevice::Send(const u8 *pMessage, size_t nLength, unsigned nCable) @@ -103,14 +178,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..40d83e5 100644 --- a/src/udpmididevice.h +++ b/src/udpmididevice.h @@ -57,6 +57,15 @@ 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; + + void UdpMidiReassembly(uint8_t byte, unsigned cable); + uint8_t m_udpMidiMsg[4] = {0}; + uint8_t m_udpMidiState = 0; }; #endif 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];