diff --git a/src/mididevice.cpp b/src/mididevice.cpp index fefe9fc..d468455 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,10 @@ 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_CC_MONO_MODE_ON 126 + #define MIDI_CC_POLY_MODE_ON 127 #define MIDI_PROGRAM_CHANGE 0b1100 #define MIDI_PITCH_BEND 0b1110 @@ -265,6 +269,59 @@ void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsign } else { + // Handle Voice Dump Request SysEx (Format 0) + if (nLength == 5 && + pMessage[0] == MIDI_SYSTEM_EXCLUSIVE_BEGIN && + 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) { + 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; + } + + // 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 && // 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) { + + 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; + } + // Perform any MiniDexed level MIDI handling before specific Tone Generators unsigned nPerfCh = m_pSynthesizer->GetPerformanceSelectChannel(); switch (ucType) @@ -476,6 +533,32 @@ 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; + + 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, @@ -720,4 +803,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()); } -} +} diff --git a/src/minidexed.cpp b/src/minidexed.cpp index 4d9a53c..72edb6f 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 @@ -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(); @@ -2184,3 +2184,29 @@ unsigned CMiniDexed::getModController (unsigned controller, unsigned parameter, } } + +void CMiniDexed::setOperatorMask(uint8_t operatorMask, unsigned nTG) { + if (nTG >= CConfig::AllToneGenerators) { + LOGERR("Invalid tone generator: TG=%u", nTG); + return; + } + + // 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 + if (nTG < m_nToneGenerators) { + m_pTG[nTG]->setOPAll(m_uchOPMask[nTG]); + } +} diff --git a/src/minidexed.h b/src/minidexed.h index 6a7ef81..695a6ea 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 @@ -229,6 +229,8 @@ public: void setMasterVolume (float32_t vol); + 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 uint8_t m_uchOPMask[CConfig::AllToneGenerators];