From 72e1c6c9d37e206a40fae1cda816a31dfe5dbabf Mon Sep 17 00:00:00 2001 From: probonopd Date: Mon, 14 Apr 2025 18:55:55 +0200 Subject: [PATCH 01/12] Voice Dump, Mute, Omni Mode via MIDI Voice Dump Request via SysEx: MiniDexed now listens for the specific SysEx message (F0 43 20 00 F7) and sends the voice dump for the requested channel. Mute Operator via SysEx: MiniDexed now handles the SysEx message for muting operators (F0 43 11 01 1B 2F F7) and toggles the mute state of the specified operator. Omni Mode On/Off via MIDI CC: MIDI CC 124 and 125 are now handled to switch Omni Mode Off and On, respectively. --- src/mididevice.cpp | 44 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/src/mididevice.cpp b/src/mididevice.cpp index fefe9fc..e1f96dc 100644 --- a/src/mididevice.cpp +++ b/src/mididevice.cpp @@ -2,7 +2,7 @@ // mididevice.cpp // // MiniDexed - Dexed FM synthesizer for bare metal Raspberry Pi -// Copyright (C) 2022 The MiniDexed Team +// Copyright (C) 2022-25 The MiniDexed Team // // Original author of this class: // R. Stange @@ -51,6 +51,8 @@ LOGMODULE ("mididevice"); #define MIDI_CC_DETUNE_LEVEL 94 #define MIDI_CC_ALL_SOUND_OFF 120 #define MIDI_CC_ALL_NOTES_OFF 123 + #define MIDI_CC_OMNI_MODE_OFF 124 + #define MIDI_CC_OMNI_MODE_ON 125 #define MIDI_PROGRAM_CHANGE 0b1100 #define MIDI_PITCH_BEND 0b1110 @@ -265,6 +267,30 @@ void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsign } else { + // Add handling for Voice Dump Request SysEx + if (nLength == 5 && + pMessage[0] == MIDI_SYSTEM_EXCLUSIVE_BEGIN && + pMessage[1] == 0x43 && + (pMessage[2] & 0xF0) == 0x20 && // Check for Yamaha SysEx with channel + pMessage[3] == 0x00 && + pMessage[4] == MIDI_SYSTEM_EXCLUSIVE_END) { + LOGDBG("Voice Dump Request received for channel %d", pMessage[2] & 0x0F); + SendSystemExclusiveVoice(0, nCable, pMessage[2] & 0x0F); // Send voice dump for the requested channel + return; + } + + // Add handling for Mute Operator SysEx + if (nLength == 7 && + pMessage[0] == MIDI_SYSTEM_EXCLUSIVE_BEGIN && + pMessage[1] == 0x43 && + pMessage[3] == 0x1B && + pMessage[4] == 0x2F && + pMessage[6] == MIDI_SYSTEM_EXCLUSIVE_END) { + LOGDBG("Mute Operator SysEx received: Operator %d, Value %d", pMessage[4], pMessage[5]); + m_pSynthesizer->setOperatorMute(pMessage[5], nTG); // Implement this function in the synthesizer + return; + } + // Perform any MiniDexed level MIDI handling before specific Tone Generators unsigned nPerfCh = m_pSynthesizer->GetPerformanceSelectChannel(); switch (ucType) @@ -476,6 +502,20 @@ void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsign } break; + case MIDI_CC_OMNI_MODE_OFF: + // Sets to "Omni Off" mode + if (m_ChannelMap[nTG] == OmniMode) { + m_pSynthesizer->SetMIDIChannel(ucChannel, nTG); + LOGDBG("Omni Mode Off: TG %d set to MIDI channel %d", nTG, ucChannel+1); + } + break; + + case MIDI_CC_OMNI_MODE_ON: + // Sets to "Omni On" mode + m_pSynthesizer->SetMIDIChannel(OmniMode, nTG); + LOGDBG("Omni Mode On: TG %d set to OMNI", nTG); + break; + default: // Check for system-level, cross-TG MIDI Controls, but only do it once. // Also, if successfully handled, then no need to process other TGs, @@ -720,4 +760,4 @@ void CMIDIDevice::SendSystemExclusiveVoice(uint8_t nVoice, const unsigned nCable Iterator->second->Send (voicedump, sizeof(voicedump)*sizeof(uint8_t)); // LOGDBG("Send SYSEX voice dump %u to \"%s\"",nVoice,Iterator->first.c_str()); } -} +} From 10d278848f98ada4d903049ac0c94ecf3a430edf Mon Sep 17 00:00:00 2001 From: probonopd Date: Mon, 14 Apr 2025 19:04:18 +0200 Subject: [PATCH 02/12] setOperatorMute [ci skip] --- src/minidexed.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/minidexed.h b/src/minidexed.h index 6a7ef81..d0b2a92 100644 --- a/src/minidexed.h +++ b/src/minidexed.h @@ -2,7 +2,7 @@ // minidexed.h // // MiniDexed - Dexed FM synthesizer for bare metal Raspberry Pi -// Copyright (C) 2022 The MiniDexed Team +// Copyright (C) 2022-25 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 @@ -228,6 +228,7 @@ public: bool DoSavePerformance (void); void setMasterVolume (float32_t vol); + void setOperatorMute(uint8_t operatorIndex, unsigned nTG); private: int16_t ApplyNoteLimits (int16_t pitch, unsigned nTG); // returns < 0 to ignore note From d49875c6421a4cbf69b7d0619e848dde20935449 Mon Sep 17 00:00:00 2001 From: probonopd Date: Mon, 14 Apr 2025 19:04:47 +0200 Subject: [PATCH 03/12] setOperatorMute --- src/minidexed.cpp | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/minidexed.cpp b/src/minidexed.cpp index 4d9a53c..39c792e 100644 --- a/src/minidexed.cpp +++ b/src/minidexed.cpp @@ -2,7 +2,7 @@ // minidexed.cpp // // MiniDexed - Dexed FM synthesizer for bare metal Raspberry Pi -// Copyright (C) 2022 The MiniDexed Team +// Copyright (C) 2022-25 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 @@ -2184,3 +2184,18 @@ unsigned CMiniDexed::getModController (unsigned controller, unsigned parameter, } } + +void CMiniDexed::setOperatorMute(uint8_t operatorIndex, unsigned nTG) { + if (nTG >= CConfig::AllToneGenerators || operatorIndex >= 6) { + LOGERR("Invalid tone generator or operator index: TG=%u, Operator=%u", nTG, operatorIndex); + return; + } + + // Toggle the operator mask for the specified operator + m_uchOPMask[nTG] ^= (1 << operatorIndex); + + LOGDBG("Operator %u mute toggled for TG %u. New mask: 0x%02X", operatorIndex, nTG, m_uchOPMask[nTG]); + + // Apply the updated operator mask to the tone generator + m_pTG[nTG]->SetOperatorMask(m_uchOPMask[nTG]); +} From 7e17b538cb5e17008367acdc6f56ee25ee5ced22 Mon Sep 17 00:00:00 2001 From: probonopd Date: Mon, 14 Apr 2025 19:10:10 +0200 Subject: [PATCH 04/12] Handling for Mute Operator SysEx --- src/mididevice.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/mididevice.cpp b/src/mididevice.cpp index e1f96dc..b43d272 100644 --- a/src/mididevice.cpp +++ b/src/mididevice.cpp @@ -267,7 +267,7 @@ void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsign } else { - // Add handling for Voice Dump Request SysEx + // Handling for Voice Dump Request SysEx if (nLength == 5 && pMessage[0] == MIDI_SYSTEM_EXCLUSIVE_BEGIN && pMessage[1] == 0x43 && @@ -279,15 +279,17 @@ void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsign return; } - // Add handling for Mute Operator SysEx + // Handling for Mute Operator SysEx if (nLength == 7 && pMessage[0] == MIDI_SYSTEM_EXCLUSIVE_BEGIN && pMessage[1] == 0x43 && - pMessage[3] == 0x1B && - pMessage[4] == 0x2F && + pMessage[2] == 0x11 && + pMessage[3] == 0x01 && + pMessage[4] == 0x1B && + pMessage[5] == 0x2F && pMessage[6] == MIDI_SYSTEM_EXCLUSIVE_END) { LOGDBG("Mute Operator SysEx received: Operator %d, Value %d", pMessage[4], pMessage[5]); - m_pSynthesizer->setOperatorMute(pMessage[5], nTG); // Implement this function in the synthesizer + m_pSynthesizer->setOperatorMute(pMessage[4], pMessage[5]); return; } From 53e88d8cdc824cb92b60afab5f3bafd92334d910 Mon Sep 17 00:00:00 2001 From: probonopd Date: Mon, 14 Apr 2025 19:15:30 +0200 Subject: [PATCH 05/12] Remove SetOperatorMask --- src/minidexed.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/minidexed.cpp b/src/minidexed.cpp index 39c792e..f1d0079 100644 --- a/src/minidexed.cpp +++ b/src/minidexed.cpp @@ -2197,5 +2197,4 @@ void CMiniDexed::setOperatorMute(uint8_t operatorIndex, unsigned nTG) { LOGDBG("Operator %u mute toggled for TG %u. New mask: 0x%02X", operatorIndex, nTG, m_uchOPMask[nTG]); // Apply the updated operator mask to the tone generator - m_pTG[nTG]->SetOperatorMask(m_uchOPMask[nTG]); } From b354ec277b60c1321c812f3bdce63196f91afc40 Mon Sep 17 00:00:00 2001 From: probonopd Date: Mon, 14 Apr 2025 19:22:50 +0200 Subject: [PATCH 06/12] Apply the updated operator mask to the tone generator --- src/minidexed.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/minidexed.cpp b/src/minidexed.cpp index f1d0079..1236923 100644 --- a/src/minidexed.cpp +++ b/src/minidexed.cpp @@ -2197,4 +2197,5 @@ void CMiniDexed::setOperatorMute(uint8_t operatorIndex, unsigned nTG) { LOGDBG("Operator %u mute toggled for TG %u. New mask: 0x%02X", operatorIndex, nTG, m_uchOPMask[nTG]); // Apply the updated operator mask to the tone generator + m_pTG[nTG]->setOPAll(m_uchOPMask[nTG]); } From b1853f2ccd50cdd11fe3d5ce199353ae79a6e411 Mon Sep 17 00:00:00 2001 From: probonopd Date: Mon, 14 Apr 2025 22:33:48 +0200 Subject: [PATCH 07/12] Update mididevice.cpp [ci skip] --- src/mididevice.cpp | 55 ++++++++++++++++++++++++++++++++++------------ 1 file changed, 41 insertions(+), 14 deletions(-) diff --git a/src/mididevice.cpp b/src/mididevice.cpp index b43d272..29dcf3f 100644 --- a/src/mididevice.cpp +++ b/src/mididevice.cpp @@ -267,29 +267,56 @@ void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsign } else { - // Handling for Voice Dump Request SysEx + // Handle Voice Dump Request SysEx (Format 0) if (nLength == 5 && pMessage[0] == MIDI_SYSTEM_EXCLUSIVE_BEGIN && - pMessage[1] == 0x43 && - (pMessage[2] & 0xF0) == 0x20 && // Check for Yamaha SysEx with channel - pMessage[3] == 0x00 && + pMessage[1] == 0x43 && // Yamaha manufacturer ID + (pMessage[2] & 0xF0) == 0x20 && // Status byte with channel (0x2n) + pMessage[3] == 0x00 && // Format 0 (single voice) pMessage[4] == MIDI_SYSTEM_EXCLUSIVE_END) { - LOGDBG("Voice Dump Request received for channel %d", pMessage[2] & 0x0F); - SendSystemExclusiveVoice(0, nCable, pMessage[2] & 0x0F); // Send voice dump for the requested channel + uint8_t channel = pMessage[2] & 0x0F; + LOGDBG("Voice Dump Request (Format 0) received for channel %d", channel); + + // Find TG matching this channel + bool found = false; + for (unsigned nTG = 0; nTG < m_pConfig->GetToneGenerators(); nTG++) { + if (m_ChannelMap[nTG] == channel || m_ChannelMap[nTG] == OmniMode) { + SendSystemExclusiveVoice(0, nCable, nTG); + found = true; + // Don't break here to allow multiple TGs on same channel to respond + } + } + + if (!found) { + // If no specific TG is found for this channel, respond with TG 0 + SendSystemExclusiveVoice(0, nCable, 0); + } return; } - // Handling for Mute Operator SysEx + // Operator enable/disable SysEx handling - Format F0 43 11 g=0 h=1 1B p F7 + // Where 1B (27) is parameter for operator on/off + // And p is a bitmask for operators (bit 0=OP6, bit 1=OP5, bit 2=OP4, bit 3=OP3, bit 4=OP2, bit 5=OP1) if (nLength == 7 && pMessage[0] == MIDI_SYSTEM_EXCLUSIVE_BEGIN && - pMessage[1] == 0x43 && - pMessage[2] == 0x11 && - pMessage[3] == 0x01 && - pMessage[4] == 0x1B && - pMessage[5] == 0x2F && + pMessage[1] == 0x43 && // Yamaha manufacturer ID + pMessage[2] == 0x11 && // Sub-status byte (parameter change) + pMessage[3] == 0x01 && // Format byte (h=1, g=0) + pMessage[4] == 0x1B && // Parameter 27 (0x1B) - operator on/off + pMessage[5] <= 0x3F && // Value (6-bit mask, 0x00-0x3F valid range) pMessage[6] == MIDI_SYSTEM_EXCLUSIVE_END) { - LOGDBG("Mute Operator SysEx received: Operator %d, Value %d", pMessage[4], pMessage[5]); - m_pSynthesizer->setOperatorMute(pMessage[4], pMessage[5]); + + uint8_t operatorMask = pMessage[5]; + LOGDBG("Operator On/Off SysEx received: Mask 0x%02X", operatorMask); + + // Apply to MIDI channel-specific TGs + uint8_t channel = 0; // Default to first channel if not specified + + // Find TGs to apply this to (apply to all TGs if from UI) + for (unsigned nTG = 0; nTG < m_pConfig->GetToneGenerators(); nTG++) { + // Set the operator mask directly (no toggling) + m_pSynthesizer->setOperatorMask(operatorMask, nTG); + } return; } From 86bdc6f1736b7f28928b187af2aa6cd94dfd1b64 Mon Sep 17 00:00:00 2001 From: probonopd Date: Mon, 14 Apr 2025 22:34:46 +0200 Subject: [PATCH 08/12] setOperatorMask [ci skip] --- src/minidexed.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/minidexed.h b/src/minidexed.h index d0b2a92..695a6ea 100644 --- a/src/minidexed.h +++ b/src/minidexed.h @@ -228,7 +228,8 @@ public: bool DoSavePerformance (void); void setMasterVolume (float32_t vol); - void setOperatorMute(uint8_t operatorIndex, unsigned nTG); + + void setOperatorMask(uint8_t operatorMask, unsigned nTG); // Set operators enabled/disabled from bitmask private: int16_t ApplyNoteLimits (int16_t pitch, unsigned nTG); // returns < 0 to ignore note From ce6f3348fa9e176d82b37f2829678106fe466072 Mon Sep 17 00:00:00 2001 From: probonopd Date: Mon, 14 Apr 2025 22:35:25 +0200 Subject: [PATCH 09/12] setOperatorMask --- src/minidexed.cpp | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/src/minidexed.cpp b/src/minidexed.cpp index 1236923..72edb6f 100644 --- a/src/minidexed.cpp +++ b/src/minidexed.cpp @@ -1672,7 +1672,7 @@ void CMiniDexed::setBreathControllerTarget(uint8_t target, uint8_t nTG) assert (m_pTG[nTG]); - m_nBreathControlTarget[nTG]=target; + m_nBreathControlTarget[nTG] = target; m_pTG[nTG]->setBreathControllerTarget(constrain(target, 0, 7)); m_pTG[nTG]->ControllersRefresh(); @@ -2185,17 +2185,28 @@ unsigned CMiniDexed::getModController (unsigned controller, unsigned parameter, } -void CMiniDexed::setOperatorMute(uint8_t operatorIndex, unsigned nTG) { - if (nTG >= CConfig::AllToneGenerators || operatorIndex >= 6) { - LOGERR("Invalid tone generator or operator index: TG=%u, Operator=%u", nTG, operatorIndex); +void CMiniDexed::setOperatorMask(uint8_t operatorMask, unsigned nTG) { + if (nTG >= CConfig::AllToneGenerators) { + LOGERR("Invalid tone generator: TG=%u", nTG); return; } - // Toggle the operator mask for the specified operator - m_uchOPMask[nTG] ^= (1 << operatorIndex); - - LOGDBG("Operator %u mute toggled for TG %u. New mask: 0x%02X", operatorIndex, nTG, m_uchOPMask[nTG]); + // According to Yamaha DX7/TX SysEx format: + // Bit 0 = OP6, Bit 1 = OP5, Bit 2 = OP4, Bit 3 = OP3, Bit 4 = OP2, Bit 5 = OP1 + // For MiniDexed: Bit 0 = OP1, Bit 1 = OP2, etc. - need to reverse the bit order to match + + uint8_t reversedMask = 0; + for (int i = 0; i < 6; i++) { + if (operatorMask & (1 << i)) { + reversedMask |= (1 << (5 - i)); + } + } + + m_uchOPMask[nTG] = reversedMask; + LOGDBG("Set operator mask for TG %u: Yamaha=0x%02X, Reversed=0x%02X", nTG, operatorMask, reversedMask); // Apply the updated operator mask to the tone generator - m_pTG[nTG]->setOPAll(m_uchOPMask[nTG]); + if (nTG < m_nToneGenerators) { + m_pTG[nTG]->setOPAll(m_uchOPMask[nTG]); + } } From 9987e03b71296237c92a97790f0549223326a609 Mon Sep 17 00:00:00 2001 From: probonopd Date: Mon, 14 Apr 2025 22:41:41 +0200 Subject: [PATCH 10/12] Handle CC 126 and 127 for mono and poly mode --- src/mididevice.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/mididevice.cpp b/src/mididevice.cpp index 29dcf3f..d468455 100644 --- a/src/mididevice.cpp +++ b/src/mididevice.cpp @@ -53,6 +53,8 @@ LOGMODULE ("mididevice"); #define MIDI_CC_ALL_NOTES_OFF 123 #define MIDI_CC_OMNI_MODE_OFF 124 #define MIDI_CC_OMNI_MODE_ON 125 + #define MIDI_CC_MONO_MODE_ON 126 + #define MIDI_CC_POLY_MODE_ON 127 #define MIDI_PROGRAM_CHANGE 0b1100 #define MIDI_PITCH_BEND 0b1110 @@ -545,6 +547,18 @@ void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsign LOGDBG("Omni Mode On: TG %d set to OMNI", nTG); break; + case MIDI_CC_MONO_MODE_ON: + // Sets monophonic mode + m_pSynthesizer->setMonoMode(1, nTG); + LOGDBG("Mono Mode On: TG %d set to MONO", nTG); + break; + + case MIDI_CC_POLY_MODE_ON: + // Sets polyphonic mode + m_pSynthesizer->setMonoMode(0, nTG); + LOGDBG("Poly Mode On: TG %d set to POLY", nTG); + break; + default: // Check for system-level, cross-TG MIDI Controls, but only do it once. // Also, if successfully handled, then no need to process other TGs, From 7510e8e613d9e1276aa4520cb12c295543d51159 Mon Sep 17 00:00:00 2001 From: probonopd Date: Tue, 15 Apr 2025 21:39:00 +0200 Subject: [PATCH 11/12] Implement Poly Mode (CC 127) support and fix OMNI mode channel memory --- src/mididevice.cpp | 14 ++++++++++++-- src/mididevice.h | 1 + 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/mididevice.cpp b/src/mididevice.cpp index d468455..90fefbd 100644 --- a/src/mididevice.cpp +++ b/src/mididevice.cpp @@ -88,6 +88,7 @@ CMIDIDevice::CMIDIDevice (CMiniDexed *pSynthesizer, CConfig *pConfig, CUserInter for (unsigned nTG = 0; nTG < CConfig::AllToneGenerators; nTG++) { m_ChannelMap[nTG] = Disabled; + m_PreviousChannelMap[nTG] = Disabled; // Initialize previous channel map } m_nMIDISystemCCVol = m_pConfig->GetMIDISystemCCVol(); @@ -136,6 +137,12 @@ CMIDIDevice::~CMIDIDevice (void) void CMIDIDevice::SetChannel (u8 ucChannel, unsigned nTG) { assert (nTG < CConfig::AllToneGenerators); + + // When changing to OMNI mode, store the previous channel + if (ucChannel == OmniMode && m_ChannelMap[nTG] != OmniMode) { + m_PreviousChannelMap[nTG] = m_ChannelMap[nTG]; + } + m_ChannelMap[nTG] = ucChannel; } @@ -536,8 +543,11 @@ void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsign case MIDI_CC_OMNI_MODE_OFF: // Sets to "Omni Off" mode if (m_ChannelMap[nTG] == OmniMode) { - m_pSynthesizer->SetMIDIChannel(ucChannel, nTG); - LOGDBG("Omni Mode Off: TG %d set to MIDI channel %d", nTG, ucChannel+1); + // Restore the previous channel if available, otherwise use current channel + u8 channelToRestore = (m_PreviousChannelMap[nTG] != Disabled) ? + m_PreviousChannelMap[nTG] : ucChannel; + m_pSynthesizer->SetMIDIChannel(channelToRestore, nTG); + LOGDBG("Omni Mode Off: TG %d restored to MIDI channel %d", nTG, channelToRestore+1); } break; diff --git a/src/mididevice.h b/src/mididevice.h index a8eae40..94b789b 100644 --- a/src/mididevice.h +++ b/src/mididevice.h @@ -70,6 +70,7 @@ private: CUserInterface *m_pUI; u8 m_ChannelMap[CConfig::AllToneGenerators]; + u8 m_PreviousChannelMap[CConfig::AllToneGenerators]; // Store previous channels for OMNI OFF restore unsigned m_nMIDISystemCCVol; unsigned m_nMIDISystemCCPan; From f78132b5f5ec3bffea5a0c8a3ca6c6c6bc4c7fd7 Mon Sep 17 00:00:00 2001 From: probonopd Date: Tue, 15 Apr 2025 21:40:48 +0200 Subject: [PATCH 12/12] runs-on: ubuntu-22.04 --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6ed1513..ca76f8a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,7 +11,7 @@ on: jobs: Build: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v2