pull/915/merge
probonopd 1 week ago committed by GitHub
commit 61d1dccc6a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 5
      src/Makefile
  2. 53
      src/mididevice.cpp
  3. 36
      src/midikeyboard.cpp
  4. 21
      src/minidexed.cpp
  5. 15
      src/minidexed.h
  6. 2
      src/net/applemidi.cpp
  7. 242
      src/performance_sysex.cpp
  8. 36
      src/performance_sysex.h
  9. 340
      src/performanceconfig.cpp
  10. 51
      src/performanceconfig.h
  11. 43
      src/serialmididevice.cpp
  12. 5
      src/serialmididevice.h
  13. 85
      src/udpmididevice.cpp
  14. 9
      src/udpmididevice.h
  15. 11
      src/uimenu.cpp

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

@ -29,6 +29,17 @@
#include <assert.h>
#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<CMiniDexed*>(m_pSynthesizer);
if (pMiniDexed) {
pMiniDexed->SetPendingVoicePerformanceUpdate(nTG);
}
}
break;
case 200:
LOGDBG("Bank bulk upload.");

@ -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<USB_SYSEX_BUFFER_SIZE; i++) {
m_SysEx[i] = 0;
}
for (unsigned i=0; i<nLength; i++) {
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 if (m_nSysExIdx != 0)
{
// Continue processing SysEx message
//printf("SysEx Packet Idx=%d, (%d)\n", m_nSysExIdx, nLength);
for (unsigned i=0; i<nLength; i++) {
if (pPacket[i] == 0xF8 || pPacket[i] == 0xFA || pPacket[i] == 0xFB || pPacket[i] == 0xFC || pPacket[i] == 0xFE || pPacket[i] == 0xFF) {
// Singe-byte System Realtime Messages can happen at any time!
MIDIMessageHandler (&pPacket[i], 1, nCable);
}
else if (m_nSysExIdx >= 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);
}
}

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

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

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

@ -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 <http://www.gnu.org/licenses/>.
//
#include "performance_sysex.h"
#include "performanceconfig.h"
#include <circle/logger.h>
#include <cstring>
#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 <TG> F7 (all params) and F0 7D 11 <TG> <param> <param> 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(&param, &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(&param, &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(&param, &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(&param, &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();
}
}
}
}

@ -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 <http://www.gnu.org/licenses/>.
//
#ifndef PERFORMANCE_SYSEX_H
#define PERFORMANCE_SYSEX_H
#include <cstdint>
#include <cstddef>
#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

@ -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<uint16_t>(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<uint16_t>(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<int16_t>(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<int16_t>(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;
}

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

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

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

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

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

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

Loading…
Cancel
Save