SysEx for MiniDexed performance parameters (half-working)

md-perf
probonopd 4 weeks ago
parent 6a9d3a62d9
commit 9322988fbd
  1. 5
      src/Makefile
  2. 48
      src/mididevice.cpp
  3. 36
      src/midikeyboard.cpp
  4. 2
      src/net/applemidi.cpp
  5. 205
      src/performance_sysex.cpp
  6. 35
      src/performance_sysex.h
  7. 304
      src/performanceconfig.cpp
  8. 49
      src/performanceconfig.h
  9. 34
      src/serialmididevice.cpp
  10. 5
      src/serialmididevice.h
  11. 46
      src/udpmididevice.cpp
  12. 5
      src/udpmididevice.h

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

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

@ -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,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 <http://www.gnu.org/licenses/>.
//
#include "performance_sysex.h"
#include "performanceconfig.h"
#include <circle/logger.h>
#include <cstring>
#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(&param, &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(&param, &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(&param, &value, 1);
LOGNOTE("SysEx: SET global param 0x%04X = 0x%04X (%s)", param, value, ok ? "OK" : "FAIL");
} else {
ok = perf->SetTGParameters(&param, &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.
*/

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

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

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

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

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

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

Loading…
Cancel
Save