Index: mptrack/EffectInfo.cpp =================================================================== --- mptrack/EffectInfo.cpp (revision 16687) +++ mptrack/EffectInfo.cpp (working copy) @@ -640,7 +640,7 @@ if(chn != CHANNELINDEX_INVALID) { const uint8 macroIndex = sndFile.m_PlayState.Chn[chn].nActiveMacro; - const PLUGINDEX plugin = sndFile.GetBestPlugin(chn, PrioritiseChannel, EvenIfMuted) - 1; + const PLUGINDEX plugin = sndFile.GetBestPlugin(sndFile.m_PlayState, chn, PrioritiseChannel, EvenIfMuted) - 1; IMixPlugin *pPlugin = (plugin < MAX_MIXPLUGINS ? sndFile.m_MixPlugins[plugin].pMixPlugin : nullptr); pszName.Format(_T("SFx MIDI Macro z=%d (SF%X: %s)"), param, macroIndex, sndFile.m_MidiCfg.GetParameteredMacroName(macroIndex, pPlugin).GetString()); } else Index: mptrack/MIDIMacroDialog.cpp =================================================================== --- mptrack/MIDIMacroDialog.cpp (revision 16687) +++ mptrack/MIDIMacroDialog.cpp (working copy) @@ -310,7 +310,7 @@ { CString s; m_EditSFx.GetWindowText(s); - mpt::String::WriteAutoBuf(m_MidiCfg.szMidiSFXExt[sfx]) = mpt::ToCharset(mpt::Charset::ASCII, s); + m_MidiCfg.szMidiSFXExt[sfx] = mpt::ToCharset(mpt::Charset::ASCII, s); int sfx_preset = m_MidiCfg.GetParameteredMacroType(sfx); m_CbnSFxPreset.SetCurSel(sfx_preset); @@ -330,7 +330,7 @@ { CString s; m_EditZxx.GetWindowText(s); - mpt::String::WriteAutoBuf(m_MidiCfg.szMidiZXXExt[zxx]) = mpt::ToCharset(mpt::Charset::ASCII, s); + m_MidiCfg.szMidiZXXExt[zxx] = mpt::ToCharset(mpt::Charset::ASCII, s); m_CbnZxxPreset.SetCurSel(m_MidiCfg.GetFixedMacroType()); } } @@ -442,7 +442,7 @@ } -bool CMidiMacroSetup::ValidateMacroString(CEdit &wnd, char *lastMacro, bool isParametric) +bool CMidiMacroSetup::ValidateMacroString(CEdit &wnd, const MIDIMacroConfig::Macro &lastMacro, bool isParametric) { CString macroStrT; wnd.GetWindowText(macroStrT); @@ -451,11 +451,11 @@ bool allowed = true, caseChange = false; for(char &c : macroStr) { - if(c == 'k' || c == 'K') // Previously, 'K' was used for MIDI channel + if(c == 'k' || c == 'K') // Previously, 'K' was used for MIDI channel { caseChange = true; c = 'c'; - } else if(c >= 'd' && c <= 'f') // abc have special meanings, but def can be fixed + } else if(c >= 'd' && c <= 'f') // abc have special meanings, but def can be fixed { caseChange = true; c = c - 'a' + 'A'; @@ -476,7 +476,7 @@ if(!allowed) { // Replace text and keep cursor position if we just typed in an invalid character - if(lastMacro != macroStr) + if(lastMacro != std::string_view{macroStr}) { int start, end; wnd.GetSel(start, end); Index: mptrack/MIDIMacroDialog.h =================================================================== --- mptrack/MIDIMacroDialog.h (revision 16687) +++ mptrack/MIDIMacroDialog.h (working copy) @@ -40,7 +40,7 @@ BOOL OnInitDialog() override; void DoDataExchange(CDataExchange* pDX) override; - bool ValidateMacroString(CEdit &wnd, char *lastMacro, bool isParametric); + bool ValidateMacroString(CEdit &wnd, const MIDIMacroConfig::Macro &lastMacro, bool isParametric); void UpdateMacroList(int macro=-1); void ToggleBoxes(UINT preset, UINT sfx); Index: mptrack/mod2midi.cpp =================================================================== --- mptrack/mod2midi.cpp (revision 16687) +++ mptrack/mod2midi.cpp (working copy) @@ -94,7 +94,9 @@ void SynchronizeMidiPitchWheelDepth(CHANNELINDEX trackerChn) { - const auto midiCh = GetMidiChannel(trackerChn); + if(trackerChn >= std::size(m_sndFile.m_PlayState.Chn)) + return; + const auto midiCh = GetMidiChannel(m_sndFile.m_PlayState.Chn[trackerChn], trackerChn); if(!m_overlappingInstruments && m_tempoTrack && m_tempoTrack->m_pitchWheelDepth[midiCh] != m_instr.midiPWD) WritePitchWheelDepth(static_cast(midiCh + MidiFirstChannel)); } @@ -306,7 +308,7 @@ return true; } - uint8 GetMidiChannel(CHANNELINDEX trackChannel) const override + uint8 GetMidiChannel(const ModChannel &chn, CHANNELINDEX trackChannel) const override { if(m_instr.nMidiChannel == MidiMappedChannel && trackChannel < std::size(m_sndFile.m_PlayState.Chn)) { @@ -316,7 +318,7 @@ midiCh++; return midiCh; } - return IMidiPlugin::GetMidiChannel(trackChannel); + return IMidiPlugin::GetMidiChannel(chn, trackChannel); } void MidiCommand(const ModInstrument &instr, uint16 note, uint16 vol, CHANNELINDEX trackChannel) override @@ -327,8 +329,8 @@ note = NOTE_KEYOFF; } SynchronizeMidiChannelState(); - if(trackChannel < MAX_CHANNELS) - m_lastModChannel[GetMidiChannel(trackChannel)] = trackChannel; + if(trackChannel < std::size(m_sndFile.m_PlayState.Chn)) + m_lastModChannel[GetMidiChannel(m_sndFile.m_PlayState.Chn[trackChannel], trackChannel)] = trackChannel; IMidiPlugin::MidiCommand(instr, note, vol, trackChannel); } Index: mptrack/Moddoc.cpp =================================================================== --- mptrack/Moddoc.cpp (revision 16687) +++ mptrack/Moddoc.cpp (working copy) @@ -1290,7 +1290,7 @@ m_SndFile.m_PlayState.Chn[nChn].dwFlags.set(muteType); if(m_SndFile.m_opl) m_SndFile.m_opl->NoteCut(nChn); // Kill VSTi notes on muted channel. - PLUGINDEX nPlug = m_SndFile.GetBestPlugin(nChn, PrioritiseInstrument, EvenIfMuted); + PLUGINDEX nPlug = m_SndFile.GetBestPlugin(m_SndFile.m_PlayState, nChn, PrioritiseInstrument, EvenIfMuted); if ((nPlug) && (nPlug<=MAX_MIXPLUGINS)) { IMixPlugin *pPlug = m_SndFile.m_MixPlugins[nPlug - 1].pMixPlugin; Index: mptrack/TrackerSettings.cpp =================================================================== --- mptrack/TrackerSettings.cpp (revision 16687) +++ mptrack/TrackerSettings.cpp (working copy) @@ -443,11 +443,11 @@ theApp.GetDefaultMidiMacro(macros); for(int isfx = 0; isfx < 16; isfx++) { - mpt::String::WriteAutoBuf(macros.szMidiSFXExt[isfx]) = conf.Read(U_("Zxx Macros"), MPT_UFORMAT("SF{}")(mpt::ufmt::HEX(isfx)), macros.szMidiSFXExt[isfx]); + macros.szMidiSFXExt[isfx] = conf.Read(U_("Zxx Macros"), MPT_UFORMAT("SF{}")(mpt::ufmt::HEX(isfx)), macros.szMidiSFXExt[isfx]); } for(int izxx = 0; izxx < 128; izxx++) { - mpt::String::WriteAutoBuf(macros.szMidiZXXExt[izxx]) = conf.Read(U_("Zxx Macros"), MPT_UFORMAT("Z{}")(mpt::ufmt::HEX0<2>(izxx | 0x80)), macros.szMidiZXXExt[izxx]); + macros.szMidiZXXExt[izxx] = conf.Read(U_("Zxx Macros"), MPT_UFORMAT("Z{}")(mpt::ufmt::HEX0<2>(izxx | 0x80)), macros.szMidiZXXExt[izxx]); } Index: soundlib/Fastmix.cpp =================================================================== --- soundlib/Fastmix.cpp (revision 16687) +++ soundlib/Fastmix.cpp (working copy) @@ -345,7 +345,7 @@ //Look for plugins associated with this implicit tracker channel. #ifndef NO_PLUGINS - PLUGINDEX nMixPlugin = GetBestPlugin(m_PlayState.ChnMix[nChn], PrioritiseInstrument, RespectMutes); + PLUGINDEX nMixPlugin = GetBestPlugin(m_PlayState, m_PlayState.ChnMix[nChn], PrioritiseInstrument, RespectMutes); if ((nMixPlugin > 0) && (nMixPlugin <= MAX_MIXPLUGINS) && m_MixPlugins[nMixPlugin - 1].pMixPlugin != nullptr) { Index: soundlib/Load_dbm.cpp =================================================================== --- soundlib/Load_dbm.cpp (revision 16687) +++ soundlib/Load_dbm.cpp (working copy) @@ -623,10 +623,10 @@ for(uint32 i = 0; i < 32; i++) { uint32 param = (i * 127u) / 32u; - mpt::String::WriteAutoBuf(m_MidiCfg.szMidiZXXExt[i ]) = MPT_AFORMAT("F0F080{}")(mpt::afmt::HEX0<2>(param)); - mpt::String::WriteAutoBuf(m_MidiCfg.szMidiZXXExt[i + 32]) = MPT_AFORMAT("F0F081{}")(mpt::afmt::HEX0<2>(param)); - mpt::String::WriteAutoBuf(m_MidiCfg.szMidiZXXExt[i + 64]) = MPT_AFORMAT("F0F082{}")(mpt::afmt::HEX0<2>(param)); - mpt::String::WriteAutoBuf(m_MidiCfg.szMidiZXXExt[i + 96]) = MPT_AFORMAT("F0F083{}")(mpt::afmt::HEX0<2>(param)); + m_MidiCfg.szMidiZXXExt[i ] = MPT_AFORMAT("F0F080{}")(mpt::afmt::HEX0<2>(param)); + m_MidiCfg.szMidiZXXExt[i + 32] = MPT_AFORMAT("F0F081{}")(mpt::afmt::HEX0<2>(param)); + m_MidiCfg.szMidiZXXExt[i + 64] = MPT_AFORMAT("F0F082{}")(mpt::afmt::HEX0<2>(param)); + m_MidiCfg.szMidiZXXExt[i + 96] = MPT_AFORMAT("F0F083{}")(mpt::afmt::HEX0<2>(param)); } } #endif // NO_PLUGINS Index: soundlib/Load_med.cpp =================================================================== --- soundlib/Load_med.cpp (revision 16687) +++ soundlib/Load_med.cpp (working copy) @@ -1042,7 +1042,7 @@ // Setup a program change macro for command 1C (even if MIDI plugin is disabled, as otherwise these commands may act as filter commands) m_MidiCfg.ClearZxxMacros(); - strcpy(m_MidiCfg.szMidiSFXExt[0], "Cc z"); + m_MidiCfg.szMidiSFXExt[0] = "Cc z"; file.Rewind(); PATTERNINDEX basePattern = 0; @@ -1216,8 +1216,8 @@ file.ReadStruct(dumpHeader); if(!file.Seek(dumpHeader.dataPointer) || !file.CanRead(dumpHeader.length)) continue; - auto ¯o = m_MidiCfg.szMidiZXXExt[dump]; - auto length = std::min(static_cast(dumpHeader.length), std::size(macro) / 2u); + MIDIMacroConfig::Macro::RawType macro; + auto length = std::min(static_cast(dumpHeader.length), macro.size() / 2u); for(size_t i = 0; i < length; i++) { const uint8 byte = file.ReadUint8(), high = byte >> 4, low = byte & 0x0F; @@ -1224,6 +1224,7 @@ macro[i * 2] = high + (high < 0x0A ? '0' : 'A' - 0x0A); macro[i * 2 + 1] = low + (low < 0x0A ? '0' : 'A' - 0x0A); } + m_MidiCfg.szMidiZXXExt[dump] = macro; } } } Index: soundlib/Load_mo3.cpp =================================================================== --- soundlib/Load_mo3.cpp (revision 16687) +++ soundlib/Load_mo3.cpp (working copy) @@ -907,16 +907,16 @@ for(uint32 i = 0; i < 16; i++) { if(fileHeader.sfxMacros[i]) - mpt::String::WriteAutoBuf(m_MidiCfg.szMidiSFXExt[i]) = MPT_AFORMAT("F0F0{}z")(mpt::afmt::HEX0<2>(fileHeader.sfxMacros[i] - 1)); + m_MidiCfg.szMidiSFXExt[i] = MPT_AFORMAT("F0F0{}z")(mpt::afmt::HEX0<2>(fileHeader.sfxMacros[i] - 1)); else - mpt::String::WriteAutoBuf(m_MidiCfg.szMidiSFXExt[i]) = ""; + m_MidiCfg.szMidiSFXExt[i] = ""; } for(uint32 i = 0; i < 128; i++) { if(fileHeader.fixedMacros[i][1]) - mpt::String::WriteAutoBuf(m_MidiCfg.szMidiZXXExt[i]) = MPT_AFORMAT("F0F0{}{}")(mpt::afmt::HEX0<2>(fileHeader.fixedMacros[i][1] - 1), mpt::afmt::HEX0<2>(fileHeader.fixedMacros[i][0].get())); + m_MidiCfg.szMidiZXXExt[i] = MPT_AFORMAT("F0F0{}{}")(mpt::afmt::HEX0<2>(fileHeader.fixedMacros[i][1] - 1), mpt::afmt::HEX0<2>(fileHeader.fixedMacros[i][0].get())); else - mpt::String::WriteAutoBuf(m_MidiCfg.szMidiZXXExt[i]) = ""; + m_MidiCfg.szMidiZXXExt[i] = ""; } } Index: soundlib/Load_symmod.cpp =================================================================== --- soundlib/Load_symmod.cpp (revision 16687) +++ soundlib/Load_symmod.cpp (working copy) @@ -955,11 +955,11 @@ const uint8 reso = static_cast(std::min(127, event.inst * 127 / 185)); if(type == 1) // lowpass filter - mpt::String::WriteAutoBuf(macro) = MPT_AFORMAT("F0F000{} F0F001{} F0F00200")(mpt::afmt::HEX0<2>(cutoff), mpt::afmt::HEX0<2>(reso)); + macro = MPT_AFORMAT("F0F000{} F0F001{} F0F00200")(mpt::afmt::HEX0<2>(cutoff), mpt::afmt::HEX0<2>(reso)); else if(type == 2) // highpass filter - mpt::String::WriteAutoBuf(macro) = MPT_AFORMAT("F0F000{} F0F001{} F0F00210")(mpt::afmt::HEX0<2>(cutoff), mpt::afmt::HEX0<2>(reso)); + macro = MPT_AFORMAT("F0F000{} F0F001{} F0F00210")(mpt::afmt::HEX0<2>(cutoff), mpt::afmt::HEX0<2>(reso)); else // no filter or unsupported filter type - mpt::String::WriteAutoBuf(macro) = "F0F0007F F0F00100"; + macro = "F0F0007F F0F00100"; return true; } else if(event.command == SymEvent::DSPEcho) { @@ -966,7 +966,7 @@ const uint8 type = (event.note < 5) ? event.note : 0; const uint8 length = (event.param < 128) ? event.param : 127; const uint8 feedback = (event.inst < 128) ? event.inst : 127; - mpt::String::WriteAutoBuf(macro) = MPT_AFORMAT("F0F080{} F0F081{} F0F082{}")(mpt::afmt::HEX0<2>(type), mpt::afmt::HEX0<2>(length), mpt::afmt::HEX0<2>(feedback)); + macro = MPT_AFORMAT("F0F080{} F0F081{} F0F082{}")(mpt::afmt::HEX0<2>(type), mpt::afmt::HEX0<2>(length), mpt::afmt::HEX0<2>(feedback)); return true; } else if(event.command == SymEvent::DSPDelay) { Index: soundlib/MIDIMacros.cpp =================================================================== --- soundlib/MIDIMacros.cpp (revision 16687) +++ soundlib/MIDIMacros.cpp (working copy) @@ -9,9 +9,8 @@ #include "stdafx.h" +#include "MIDIMacros.h" #include "../soundlib/MIDIEvents.h" -#include "MIDIMacros.h" -#include "../common/mptStringBuffer.h" #include "../common/misc_util.h" #ifdef MODPLUG_TRACKER @@ -60,7 +59,7 @@ bool found = true; for(uint32 j = 0; j < 128; j++) { - if(strncmp(macros[j], szMidiZXXExt[j], MACRO_LENGTH)) + if(macros[j] != szMidiZXXExt[j]) { found = false; break; @@ -77,17 +76,17 @@ { switch(macroType) { - case kSFxUnused: mpt::String::WriteAutoBuf(parameteredMacro) = ""; break; - case kSFxCutoff: mpt::String::WriteAutoBuf(parameteredMacro) = "F0F000z"; break; - case kSFxReso: mpt::String::WriteAutoBuf(parameteredMacro) = "F0F001z"; break; - case kSFxFltMode: mpt::String::WriteAutoBuf(parameteredMacro) = "F0F002z"; break; - case kSFxDryWet: mpt::String::WriteAutoBuf(parameteredMacro) = "F0F003z"; break; - case kSFxCC: mpt::String::WriteAutoBuf(parameteredMacro) = MPT_AFORMAT("Bc{}z")(mpt::afmt::HEX0<2>(subType & 0x7F)); break; - case kSFxPlugParam: mpt::String::WriteAutoBuf(parameteredMacro) = MPT_AFORMAT("F0F{}z")(mpt::afmt::HEX0<3>(std::min(subType, 0x17F) + 0x80)); break; - case kSFxChannelAT: mpt::String::WriteAutoBuf(parameteredMacro) = "Dcz"; break; - case kSFxPolyAT: mpt::String::WriteAutoBuf(parameteredMacro) = "Acnz"; break; - case kSFxPitch: mpt::String::WriteAutoBuf(parameteredMacro) = "Ec00z"; break; - case kSFxProgChange: mpt::String::WriteAutoBuf(parameteredMacro) = "Ccz"; break; + case kSFxUnused: parameteredMacro = ""; break; + case kSFxCutoff: parameteredMacro = "F0F000z"; break; + case kSFxReso: parameteredMacro = "F0F001z"; break; + case kSFxFltMode: parameteredMacro = "F0F002z"; break; + case kSFxDryWet: parameteredMacro = "F0F003z"; break; + case kSFxCC: parameteredMacro = MPT_AFORMAT("Bc{}z")(mpt::afmt::HEX0<2>(subType & 0x7F)); break; + case kSFxPlugParam: parameteredMacro = MPT_AFORMAT("F0F{}z")(mpt::afmt::HEX0<3>(std::min(subType, 0x17F) + 0x80)); break; + case kSFxChannelAT: parameteredMacro = "Dcz"; break; + case kSFxPolyAT: parameteredMacro = "Acnz"; break; + case kSFxPitch: parameteredMacro = "Ec00z"; break; + case kSFxProgChange: parameteredMacro = "Ccz"; break; case kSFxCustom: default: MPT_ASSERT_NOTREACHED(); @@ -100,7 +99,7 @@ { Macro parameteredMacro; CreateParameteredMacro(parameteredMacro, macroType, subType); - return mpt::String::ReadAutoBuf(parameteredMacro); + return parameteredMacro; } @@ -113,44 +112,44 @@ switch(macroType) { case kZxxUnused: - mpt::String::WriteAutoBuf(fixedMacros[i]) = ""; + fixedMacros[i] = ""; break; case kZxxReso4Bit: param = i * 8; if(i < 16) - mpt::String::WriteAutoBuf(fixedMacros[i]) = MPT_AFORMAT("F0F001{}")(mpt::afmt::HEX0<2>(param)); + fixedMacros[i] = MPT_AFORMAT("F0F001{}")(mpt::afmt::HEX0<2>(param)); else - mpt::String::WriteAutoBuf(fixedMacros[i]) = ""; + fixedMacros[i] = ""; break; case kZxxReso7Bit: - mpt::String::WriteAutoBuf(fixedMacros[i]) = MPT_AFORMAT("F0F001{}")(mpt::afmt::HEX0<2>(param)); + fixedMacros[i] = MPT_AFORMAT("F0F001{}")(mpt::afmt::HEX0<2>(param)); break; case kZxxCutoff: - mpt::String::WriteAutoBuf(fixedMacros[i]) = MPT_AFORMAT("F0F000{}")(mpt::afmt::HEX0<2>(param)); + fixedMacros[i] = MPT_AFORMAT("F0F000{}")(mpt::afmt::HEX0<2>(param)); break; case kZxxFltMode: - mpt::String::WriteAutoBuf(fixedMacros[i]) = MPT_AFORMAT("F0F002{}")(mpt::afmt::HEX0<2>(param)); + fixedMacros[i] = MPT_AFORMAT("F0F002{}")(mpt::afmt::HEX0<2>(param)); break; case kZxxResoFltMode: param = (i & 0x0F) * 8; if(i < 16) - mpt::String::WriteAutoBuf(fixedMacros[i]) = MPT_AFORMAT("F0F001{}")(mpt::afmt::HEX0<2>(param)); + fixedMacros[i] = MPT_AFORMAT("F0F001{}")(mpt::afmt::HEX0<2>(param)); else if(i < 32) - mpt::String::WriteAutoBuf(fixedMacros[i]) = MPT_AFORMAT("F0F002{}")(mpt::afmt::HEX0<2>(param)); + fixedMacros[i] = MPT_AFORMAT("F0F002{}")(mpt::afmt::HEX0<2>(param)); else - mpt::String::WriteAutoBuf(fixedMacros[i]) = ""; + fixedMacros[i] = ""; break; case kZxxChannelAT: - mpt::String::WriteAutoBuf(fixedMacros[i]) = MPT_AFORMAT("Dc{}")(mpt::afmt::HEX0<2>(param)); + fixedMacros[i] = MPT_AFORMAT("Dc{}")(mpt::afmt::HEX0<2>(param)); break; case kZxxPolyAT: - mpt::String::WriteAutoBuf(fixedMacros[i]) = MPT_AFORMAT("Acn{}")(mpt::afmt::HEX0<2>(param)); + fixedMacros[i] = MPT_AFORMAT("Acn{}")(mpt::afmt::HEX0<2>(param)); break; case kZxxPitch: - mpt::String::WriteAutoBuf(fixedMacros[i]) = MPT_AFORMAT("Ec00{}")(mpt::afmt::HEX0<2>(param)); + fixedMacros[i] = MPT_AFORMAT("Ec00{}")(mpt::afmt::HEX0<2>(param)); break; case kZxxProgChange: - mpt::String::WriteAutoBuf(fixedMacros[i]) = MPT_AFORMAT("Cc{}")(mpt::afmt::HEX0<2>(param)); + fixedMacros[i] = MPT_AFORMAT("Cc{}")(mpt::afmt::HEX0<2>(param)); break; case kZxxCustom: default: @@ -167,7 +166,7 @@ { for(auto left = begin(), right = other.begin(); left != end(); left++, right++) { - if(strncmp(*left, *right, MACRO_LENGTH)) + if(*left != *right) return false; } return true; @@ -344,11 +343,11 @@ MemsetZero(szMidiSFXExt); MemsetZero(szMidiZXXExt); - strcpy(szMidiGlb[MIDIOUT_START], "FF"); - strcpy(szMidiGlb[MIDIOUT_STOP], "FC"); - strcpy(szMidiGlb[MIDIOUT_NOTEON], "9c n v"); - strcpy(szMidiGlb[MIDIOUT_NOTEOFF], "9c n 0"); - strcpy(szMidiGlb[MIDIOUT_PROGRAM], "Cc p"); + szMidiGlb[MIDIOUT_START] = "FF"; + szMidiGlb[MIDIOUT_STOP] = "FC"; + szMidiGlb[MIDIOUT_NOTEON] = "9c n v"; + szMidiGlb[MIDIOUT_NOTEOFF] = "9c n 0"; + szMidiGlb[MIDIOUT_PROGRAM] = "Cc p"; // SF0: Z00-Z7F controls cutoff CreateParameteredMacro(0, kSFxCutoff); // Z80-Z8F controls resonance @@ -369,16 +368,15 @@ { for(auto ¯o : *this) { - macro[MACRO_LENGTH - 1] = '\0'; - std::fill(std::find(std::begin(macro), std::end(macro), '\0'), std::end(macro), '\0'); + macro.Sanitize(); } } // Helper function for UpgradeMacros() -void MIDIMacroConfig::UpgradeMacroString(Macro ¯o) const +void MIDIMacroConfig::Macro::UpgradeLegacyMacro() noexcept { - for(auto &c : macro) + for(auto &c : *this) { if(c >= 'a' && c <= 'f') // Both A-F and a-f were treated as hex constants { @@ -399,7 +397,7 @@ { for(auto ¯o : *this) { - UpgradeMacroString(macro); + macro.UpgradeLegacyMacro(); } } Index: soundlib/MIDIMacros.h =================================================================== --- soundlib/MIDIMacros.h (revision 16687) +++ soundlib/MIDIMacros.h (working copy) @@ -86,19 +86,93 @@ struct MIDIMacroConfigData { - typedef char Macro[MACRO_LENGTH]; + struct Macro + { + public: + using RawType = std::array; + constexpr auto begin() const noexcept { return m_data.begin(); } + constexpr auto cbegin() const noexcept { return m_data.cbegin(); } + constexpr auto end() const noexcept { return m_data.end(); } + constexpr auto cend() const noexcept { return m_data.cend(); } + constexpr auto data() const noexcept { return m_data.data(); } + constexpr auto size() const noexcept { return m_data.size(); } + private: + constexpr auto begin() noexcept { return m_data.begin(); } + constexpr auto end() noexcept { return m_data.end(); } + constexpr auto data() noexcept { return m_data.data(); } + + public: + Macro &operator=(const Macro &other) = default; + Macro &operator=(const RawType &other) noexcept + { + return (*this = std::string_view{other.data(), other.size()}); + } + Macro &operator=(const std::string_view &other) noexcept + { + const size_t copyLength = std::min({m_data.size() - 1u, other.size(), other.find('\0')}); + std::copy(other.begin(), other.begin() + copyLength, begin()); + std::fill(begin() + copyLength, end(), '\0'); + return *this; + } + + bool operator==(const Macro &other) const noexcept + { + return m_data == other.m_data; // Don't care about data past null-terminator as operator= and Sanitize() ensure there is no data behind it. + } + bool operator!=(const Macro &other) const noexcept + { + return !(*this == other); + } + + operator mpt::span() const noexcept + { + return {data(), length()}; + } + operator std::string_view() const noexcept + { + return {data(), length()}; + } + operator std::string() const + { + return {data(), length()}; + } + + size_t length() const noexcept + { + return static_cast(std::distance(begin(), std::find(begin(), end(), '\0'))); + } + + void clear() noexcept + { + m_data.fill('\0'); + } + + void Sanitize() noexcept + { + m_data.back() = '\0'; + std::fill(begin() + length(), end(), '\0'); + } + + void UpgradeLegacyMacro() noexcept; + + private: + RawType m_data; + }; + // encoding is ASCII - Macro szMidiGlb[9]; // Global MIDI macros - Macro szMidiSFXExt[16]; // Parametric MIDI macros - Macro szMidiZXXExt[128]; // Fixed MIDI macros + Macro szMidiGlb[9]; // Global MIDI macros + Macro szMidiSFXExt[NUM_MACROS]; // Parametric MIDI macros + Macro szMidiZXXExt[128]; // Fixed MIDI macros - Macro *begin() { return std::begin(szMidiGlb); } - const Macro *begin() const { return std::begin(szMidiGlb); } - Macro *end() { return std::end(szMidiZXXExt); } - const Macro *end() const { return std::end(szMidiZXXExt); } + Macro *begin() noexcept { return std::begin(szMidiGlb); } + const Macro *begin() const noexcept { return std::begin(szMidiGlb); } + Macro *end() noexcept { return std::end(szMidiZXXExt); } + const Macro *end() const noexcept { return std::end(szMidiZXXExt); } }; -MPT_BINARY_STRUCT(MIDIMacroConfigData, 4896) // this is directly written to files, so the size must be correct! +// This is directly written to files, so the size must be correct! +MPT_BINARY_STRUCT(MIDIMacroConfigData::Macro, 32) +MPT_BINARY_STRUCT(MIDIMacroConfigData, 4896) class MIDIMacroConfig : public MIDIMacroConfigData { Index: soundlib/ModInstrument.cpp =================================================================== --- soundlib/ModInstrument.cpp (revision 16687) +++ soundlib/ModInstrument.cpp (working copy) @@ -314,13 +314,9 @@ } -uint8 ModInstrument::GetMIDIChannel(const CSoundFile &sndFile, CHANNELINDEX chn) const +uint8 ModInstrument::GetMIDIChannel(const ModChannel &channel, CHANNELINDEX chn) const { - if(chn >= std::size(sndFile.m_PlayState.Chn)) - return 0; - // For mapped channels, return their pattern channel, modulo 16 (because there are only 16 MIDI channels) - const ModChannel &channel = sndFile.m_PlayState.Chn[chn]; if(nMidiChannel == MidiMappedChannel) return static_cast((channel.nMasterChn ? (channel.nMasterChn - 1u) : chn) % 16u); else if(HasValidMIDIChannel()) Index: soundlib/ModInstrument.h =================================================================== --- soundlib/ModInstrument.h (revision 16687) +++ soundlib/ModInstrument.h (working copy) @@ -21,7 +21,7 @@ OPENMPT_NAMESPACE_BEGIN -class CSoundFile; +struct ModChannel; // Instrument Nodes struct EnvelopeNode @@ -150,7 +150,7 @@ void SetResonance(uint8 resonance, bool enable) { nIFR = std::min(resonance, uint8(0x7F)) | (enable ? 0x80 : 0x00); } bool HasValidMIDIChannel() const { return (nMidiChannel >= 1 && nMidiChannel <= 17); } - uint8 GetMIDIChannel(const CSoundFile &sndFile, CHANNELINDEX chn) const; + uint8 GetMIDIChannel(const ModChannel &channel, CHANNELINDEX chn) const; void SetTuning(CTuning *pT) { Index: soundlib/plugins/PlugInterface.cpp =================================================================== --- soundlib/plugins/PlugInterface.cpp (revision 16687) +++ soundlib/plugins/PlugInterface.cpp (working copy) @@ -775,13 +775,19 @@ // Get the MIDI channel currently associated with a given tracker channel -uint8 IMidiPlugin::GetMidiChannel(CHANNELINDEX trackChannel) const +uint8 IMidiPlugin::GetMidiChannel(const ModChannel &chn, CHANNELINDEX trackChannel) const { - if(trackChannel >= std::size(m_SndFile.m_PlayState.Chn)) + if(auto ins = chn.pModInstrument; ins != nullptr) + return ins->GetMIDIChannel(chn, trackChannel); + else return 0; +} - if(auto ins = m_SndFile.m_PlayState.Chn[trackChannel].pModInstrument; ins != nullptr) - return ins->GetMIDIChannel(m_SndFile, trackChannel); + +uint8 IMidiPlugin::GetMidiChannel(CHANNELINDEX trackChannel) const +{ + if(trackChannel < std::size(m_SndFile.m_PlayState.Chn)) + return GetMidiChannel(m_SndFile.m_PlayState.Chn[trackChannel], trackChannel); else return 0; } Index: soundlib/plugins/PlugInterface.h =================================================================== --- soundlib/plugins/PlugInterface.h (revision 16687) +++ soundlib/plugins/PlugInterface.h (working copy) @@ -25,6 +25,7 @@ struct VSTPluginLib; struct SNDMIXPLUGIN; struct ModInstrument; +struct ModChannel; class CSoundFile; class CModDoc; class CAbstractVstEditor; @@ -275,9 +276,11 @@ bool IsNotePlaying(uint8 note, CHANNELINDEX trackerChn) override; // Get the MIDI channel currently associated with a given tracker channel - virtual uint8 GetMidiChannel(CHANNELINDEX trackChannel) const; + virtual uint8 GetMidiChannel(const ModChannel &chn, CHANNELINDEX trackChannel) const; protected: + uint8 GetMidiChannel(CHANNELINDEX trackChannel) const; + // Plugin wants to send MIDI to OpenMPT virtual void ReceiveMidi(uint32 midiCode); virtual void ReceiveSysex(mpt::const_byte_span sysex); Index: soundlib/Snd_fx.cpp =================================================================== --- soundlib/Snd_fx.cpp (revision 16687) +++ soundlib/Snd_fx.cpp (working copy) @@ -828,6 +828,13 @@ case CMD_PANBRELLO: Panbrello(chn, param); break; + + case CMD_MIDI: + case CMD_SMOOTHMIDI: + if(param < 0x80) + ProcessMIDIMacro(playState, nChn, false, m_MidiCfg.szMidiSFXExt[chn.nActiveMacro], chn.rowCommand.param, 0, true); + else + ProcessMIDIMacro(playState, nChn, false, m_MidiCfg.szMidiZXXExt[param & 0x7F], chn.rowCommand.param, 0, true); default: break; } @@ -2268,7 +2275,7 @@ IMixPlugin *pPlugin = nullptr; if(srcChn.HasMIDIOutput() && ModCommand::IsNote(srcChn.nNote)) // instro sends to a midi chan { - PLUGINDEX plugin = GetBestPlugin(nChn, PrioritiseInstrument, RespectMutes); + PLUGINDEX plugin = GetBestPlugin(m_PlayState, nChn, PrioritiseInstrument, RespectMutes); if(plugin > 0 && plugin <= MAX_MIXPLUGINS) { @@ -3371,7 +3378,7 @@ { SetFinetune(nChn, m_PlayState, cmd == CMD_FINETUNE_SMOOTH); #ifndef NO_PLUGINS - if(IMixPlugin *plugin = GetChannelInstrumentPlugin(nChn); plugin != nullptr) + if(IMixPlugin *plugin = GetChannelInstrumentPlugin(m_PlayState.Chn[nChn]); plugin != nullptr) plugin->MidiPitchBendRaw(chn.GetMIDIPitchBend(), nChn); #endif // NO_PLUGINS } @@ -3860,7 +3867,7 @@ if(pitchBend) { #ifndef NO_PLUGINS - IMixPlugin *plugin = GetChannelInstrumentPlugin(nChn); + IMixPlugin *plugin = GetChannelInstrumentPlugin(m_PlayState.Chn[nChn]); if(plugin != nullptr) { int8 pwd = 13; // Early OpenMPT legacy... Actually it's not *exactly* 13, but close enough... @@ -4811,23 +4818,104 @@ // Process a MIDI Macro. // Parameters: +// playState: The playback state to operate on. // nChn: Mod channel to apply macro on // isSmooth: If true, internal macros are interpolated between two rows -// macro: Actual MIDI Macro string -// param: Parameter for parametric macros (Z00 - Z7F) +// macro: MIDI Macro string to process +// param: Parameter for parametric macros (Zxx / \xx parameter) // plugin: Plugin to send MIDI message to (if not specified but needed, it is autodetected) -void CSoundFile::ProcessMIDIMacro(CHANNELINDEX nChn, bool isSmooth, const char *macro, uint8 param, PLUGINDEX plugin) +// localOnly: Do not execute macros that would modify state outside of playState (e.g. sending messages to plugins) +void CSoundFile::ProcessMIDIMacro(PlayState &playState, CHANNELINDEX nChn, bool isSmooth, const MIDIMacroConfigData::Macro ¯o, uint8 param, PLUGINDEX plugin, bool localOnly) { - ModChannel &chn = m_PlayState.Chn[nChn]; - const ModInstrument *pIns = GetNumInstruments() ? chn.pModInstrument : nullptr; + playState.m_MidiMacroScratchSpace.resize(macro.length() + 1); + auto out = mpt::as_span(playState.m_MidiMacroScratchSpace); - uint8 out[MACRO_LENGTH]; - uint32 outPos = 0; // output buffer position, which also equals the number of complete bytes + ParseMIDIMacro(playState, nChn, isSmooth, macro, out, param, plugin); + + // Macro string has been parsed and translated, now send the message(s)... + uint32 outSize = static_cast(out.size()); + uint32 sendPos = 0; + uint8 runningStatus = 0; + while(sendPos < out.size()) + { + uint32 sendLen = 0; + if(out[sendPos] == 0xF0) + { + // SysEx start + if((outSize - sendPos >= 4) && (out[sendPos + 1] == 0xF0 || out[sendPos + 1] == 0xF1)) + { + // Internal macro (normal (F0F0) or extended (F0F1)), 4 bytes long + sendLen = 4; + } else + { + // SysEx message, find end of message + for(uint32 i = sendPos + 1; i < outSize; i++) + { + if(out[i] == 0xF7) + { + // Found end of SysEx message + sendLen = i - sendPos + 1; + break; + } + } + if(sendLen == 0) + { + // Didn't find end, so "invent" end of SysEx message + out[outSize++] = 0xF7; + sendLen = outSize - sendPos; + } + } + } else if(!(out[sendPos] & 0x80)) + { + // Missing status byte? Try inserting running status + if(runningStatus != 0) + { + sendPos--; + out[sendPos] = runningStatus; + } else + { + // No running status to re-use; skip this byte + sendPos++; + } + continue; + } else + { + // Other MIDI messages + sendLen = std::min(static_cast(MIDIEvents::GetEventLength(out[sendPos])), outSize - sendPos); + } + + if(sendLen == 0) + break; + + if(out[sendPos] < 0xF0) + { + runningStatus = out[sendPos]; + } + const auto midiMsg = mpt::as_span(out.data() + sendPos, sendLen); + uint32 bytesSent = 0; + // Local-only messages are messages that can be processed directly in the replay routines without sending stuff to plugins. + bytesSent = SendMIDIData(playState, nChn, isSmooth, midiMsg, plugin, localOnly); + // If there's no error in the macro data (e.g. unrecognized internal MIDI macro), we have sendLen == bytesSent. + if(bytesSent > 0) + sendPos += bytesSent; + else + sendPos += sendLen; + } +} + + +void CSoundFile::ParseMIDIMacro(PlayState &playState, CHANNELINDEX nChn, bool isSmooth, const mpt::span macro, mpt::span &out, uint8 param, PLUGINDEX plugin) const +{ + ModChannel &chn = playState.Chn[nChn]; + const ModInstrument *pIns = chn.pModInstrument; + const uint8 lastZxxParam = chn.lastZxxParam; // always interpolate based on original value in case z appears multiple times in macro string uint8 updateZxxParam = 0xFF; // avoid updating lastZxxParam immediately if macro contains both internal and external MIDI message + bool firstNibble = true; - - for(uint32 pos = 0; pos < (MACRO_LENGTH - 1) && macro[pos]; pos++) + size_t outPos = 0; // output buffer position, which also equals the number of complete bytes + size_t pos = 0; + for(; pos < macro.size() && outPos < out.size(); pos++) { bool isNibble = false; // did we parse a nibble or a byte value? uint8 data = 0; // data that has just been parsed @@ -4837,8 +4925,7 @@ { isNibble = true; data = static_cast(macro[pos] - '0'); - } - else if(macro[pos] >= 'A' && macro[pos] <= 'F') + } else if(macro[pos] >= 'A' && macro[pos] <= 'F') { isNibble = true; data = static_cast(macro[pos] - 'A' + 0x0A); @@ -4848,12 +4935,12 @@ isNibble = true; data = 0xFF; #ifndef NO_PLUGINS - const PLUGINDEX plug = (plugin != 0) ? plugin : GetBestPlugin(nChn, PrioritiseChannel, EvenIfMuted); + const PLUGINDEX plug = (plugin != 0) ? plugin : GetBestPlugin(playState, nChn, PrioritiseChannel, EvenIfMuted); if(plug > 0 && plug <= MAX_MIXPLUGINS) { auto midiPlug = dynamic_cast(m_MixPlugins[plug - 1u].pMixPlugin); if(midiPlug) - data = midiPlug->GetMidiChannel(nChn); + data = midiPlug->GetMidiChannel(playState.Chn[nChn], nChn); } #endif // NO_PLUGINS if(data == 0xFF) @@ -4860,7 +4947,7 @@ { // Fallback if no plugin was found if(pIns) - data = pIns->GetMIDIChannel(*this, nChn); + data = pIns->GetMIDIChannel(playState.Chn[nChn], nChn); else data = 0; } @@ -4936,7 +5023,7 @@ { // Interpolation for external MIDI messages - interpolation for internal messages // is handled separately to allow for more than 7-bit granularity where it's possible - data = static_cast(CalculateSmoothParamChange(lastZxxParam, data)); + data = static_cast(CalculateSmoothParamChange(playState, lastZxxParam, data)); chn.lastZxxParam = data; updateZxxParam = 0x80; } else if(updateZxxParam == 0xFF) @@ -4946,13 +5033,13 @@ } else if(macro[pos] == 's') { // SysEx Checksum (not an original Impulse Tracker macro variable, but added for convenience) - uint32 startPos = outPos; + auto startPos = outPos; while(startPos > 0 && out[--startPos] != 0xF0); if(outPos - startPos < 5 || out[startPos] != 0xF0) { continue; } - for(uint32 p = startPos + 5; p != outPos; p++) + for(auto p = startPos + 5u; p != outPos; p++) { data += out[p]; } @@ -4977,7 +5064,7 @@ firstNibble = !firstNibble; } else // parsed a byte (variable) { - if(!firstNibble) // From MIDI.TXT: '9n' is exactly the same as '09 n' or '9 n' -- so finish current byte first + if(!firstNibble) // From MIDI.TXT: '9n' is exactly the same as '09 n' or '9 n' -- so finish current byte first { outPos++; } @@ -4993,83 +5080,19 @@ if(updateZxxParam < 0x80) chn.lastZxxParam = updateZxxParam; - // Macro string has been parsed and translated, now send the message(s)... - uint32 sendPos = 0; - uint8 runningStatus = 0; - while(sendPos < outPos) - { - uint32 sendLen = 0; - if(out[sendPos] == 0xF0) - { - // SysEx start - if((outPos - sendPos >= 4) && (out[sendPos + 1] == 0xF0 || out[sendPos + 1] == 0xF1)) - { - // Internal macro (normal (F0F0) or extended (F0F1)), 4 bytes long - sendLen = 4; - } else - { - // SysEx message, find end of message - for(uint32 i = sendPos + 1; i < outPos; i++) - { - if(out[i] == 0xF7) - { - // Found end of SysEx message - sendLen = i - sendPos + 1; - break; - } - } - if(sendLen == 0) - { - // Didn't find end, so "invent" end of SysEx message - out[outPos++] = 0xF7; - sendLen = outPos - sendPos; - } - } - } else if(!(out[sendPos] & 0x80)) - { - // Missing status byte? Try inserting running status - if(runningStatus != 0) - { - sendPos--; - out[sendPos] = runningStatus; - } else - { - // No running status to re-use; skip this byte - sendPos++; - } - continue; - } else - { - // Other MIDI messages - sendLen = std::min(static_cast(MIDIEvents::GetEventLength(out[sendPos])), outPos - sendPos); - } - - if(sendLen == 0) - break; - - if(out[sendPos] < 0xF0) - { - runningStatus = out[sendPos]; - } - uint32 bytesSent = SendMIDIData(nChn, isSmooth, out + sendPos, sendLen, plugin); - // If there's no error in the macro data (e.g. unrecognized internal MIDI macro), we have sendLen == bytesSent. - if(bytesSent > 0) - sendPos += bytesSent; - else - sendPos += sendLen; - } + out = out.first(outPos); } // Calculate smooth MIDI macro slide parameter for current tick. -float CSoundFile::CalculateSmoothParamChange(float currentValue, float param) const +float CSoundFile::CalculateSmoothParamChange(const PlayState &playState, float currentValue, float param) { - MPT_ASSERT(m_PlayState.TicksOnRow() > m_PlayState.m_nTickCount); - const uint32 ticksLeft = m_PlayState.TicksOnRow() - m_PlayState.m_nTickCount; + MPT_ASSERT(playState.TicksOnRow() > playState.m_nTickCount); + const uint32 ticksLeft = playState.TicksOnRow() - playState.m_nTickCount; if(ticksLeft > 1) { // Slide param - const float step = (param - currentValue) / (float)ticksLeft; + const float step = (param - currentValue) / static_cast(ticksLeft); return (currentValue + step); } else { @@ -5080,12 +5103,10 @@ // Process exactly one MIDI message parsed by ProcessMIDIMacro. Returns bytes sent on success, 0 on (parse) failure. -uint32 CSoundFile::SendMIDIData(CHANNELINDEX nChn, bool isSmooth, const unsigned char *macro, uint32 macroLen, PLUGINDEX plugin) +uint32 CSoundFile::SendMIDIData(PlayState &playState, CHANNELINDEX nChn, bool isSmooth, const mpt::span macro, PLUGINDEX plugin, bool localOnly) { - if(macroLen < 1) - { + if(macro.size() < 1) return 0; - } if(macro[0] == 0xFA || macro[0] == 0xFC || macro[0] == 0xFF) { @@ -5092,16 +5113,16 @@ // Start Song, Stop Song, MIDI Reset - both interpreted internally and sent to plugins for(CHANNELINDEX chn = 0; chn < GetNumChannels(); chn++) { - m_PlayState.Chn[chn].nCutOff = 0x7F; - m_PlayState.Chn[chn].nResonance = 0x00; + playState.Chn[chn].nCutOff = 0x7F; + playState.Chn[chn].nResonance = 0x00; } } - ModChannel &chn = m_PlayState.Chn[nChn]; + ModChannel &chn = playState.Chn[nChn]; if(macro[0] == 0xF0 && (macro[1] == 0xF0 || macro[1] == 0xF1)) { // Internal device. - if(macroLen < 4) + if(macro.size() < 4) { return 0; } @@ -5113,16 +5134,13 @@ { // F0.F0.00.xx: Set CutOff if(!isSmooth) - { chn.nCutOff = param; - } else - { - chn.nCutOff = mpt::saturate_round(CalculateSmoothParamChange(chn.nCutOff, param)); - } + else + chn.nCutOff = mpt::saturate_round(CalculateSmoothParamChange(playState, chn.nCutOff, param)); chn.nRestoreCutoffOnNewNote = 0; int cutoff = SetupChannelFilter(chn, !chn.dwFlags[CHN_FILTER]); - if(cutoff >= 0 && chn.dwFlags[CHN_ADLIB] && m_opl) + if(cutoff >= 0 && chn.dwFlags[CHN_ADLIB] && m_opl && !localOnly) { // Cutoff doubles as modulator intensity for FM instruments m_opl->Volume(nChn, static_cast(cutoff / 4), true); @@ -5133,12 +5151,9 @@ { // F0.F0.01.xx: Set Resonance if(!isSmooth) - { chn.nResonance = param; - } else - { - chn.nResonance = (uint8)CalculateSmoothParamChange((float)chn.nResonance, (float)param); - } + else + chn.nResonance = mpt::saturate_round(CalculateSmoothParamChange(playState, chn.nResonance, param)); chn.nRestoreResonanceOnNewNote = 0; SetupChannelFilter(chn, !chn.dwFlags[CHN_FILTER]); @@ -5157,38 +5172,32 @@ } else if(macroCode == 0x03 && !isExtended) { // F0.F0.03.xx: Set plug dry/wet - const PLUGINDEX plug = (plugin != 0) ? plugin : GetBestPlugin(nChn, PrioritiseChannel, EvenIfMuted); - if(plug > 0 && plug <= MAX_MIXPLUGINS && param < 0x80) + const PLUGINDEX plug = (plugin != 0) ? plugin : GetBestPlugin(playState, nChn, PrioritiseChannel, EvenIfMuted); + if(plug > 0 && plug <= MAX_MIXPLUGINS && param < 0x80 && !localOnly) { - const float newRatio = (0x7F - (param & 0x7F)) / 127.0f; + const float newRatio = (127 - param) / 127.0f; if(!isSmooth) - { m_MixPlugins[plug - 1].fDryRatio = newRatio; - } else - { - m_MixPlugins[plug - 1].fDryRatio = CalculateSmoothParamChange(m_MixPlugins[plug - 1].fDryRatio, newRatio); - } + else + m_MixPlugins[plug - 1].fDryRatio = CalculateSmoothParamChange(playState, m_MixPlugins[plug - 1].fDryRatio, newRatio); } return 4; - } else if((macroCode & 0x80) || isExtended) + } else if(((macroCode & 0x80) || isExtended)) { // F0.F0.{80|n}.xx / F0.F1.n.xx: Set VST effect parameter n to xx - const PLUGINDEX plug = (plugin != 0) ? plugin : GetBestPlugin(nChn, PrioritiseChannel, EvenIfMuted); - const uint32 plugParam = isExtended ? (0x80 + macroCode) : (macroCode & 0x7F); - if(plug > 0 && plug <= MAX_MIXPLUGINS) + const PLUGINDEX plug = (plugin != 0) ? plugin : GetBestPlugin(playState, nChn, PrioritiseChannel, EvenIfMuted); + if(plug > 0 && plug <= MAX_MIXPLUGINS && !localOnly) { IMixPlugin *pPlugin = m_MixPlugins[plug - 1].pMixPlugin; if(pPlugin && param < 0x80) { + const uint32 plugParam = isExtended ? (0x80 + macroCode) : (macroCode & 0x7F); const float fParam = param / 127.0f; if(!isSmooth) - { pPlugin->SetParameter(plugParam, fParam); - } else - { - pPlugin->SetParameter(plugParam, CalculateSmoothParamChange(pPlugin->GetParameter(plugParam), fParam)); - } + else + pPlugin->SetParameter(plugParam, CalculateSmoothParamChange(playState, pPlugin->GetParameter(plugParam), fParam)); } } @@ -5198,7 +5207,7 @@ // If we reach this point, the internal macro was invalid. - } else + } else if(!localOnly) { #ifndef NO_PLUGINS // Not an internal device. Pass on to appropriate plugin. @@ -5208,7 +5217,7 @@ PLUGINDEX plug = 0; if(!chn.dwFlags[CHN_NOFX]) { - plug = (plugin != 0) ? plugin : GetBestPlugin(nChn, PrioritiseChannel, EvenIfMuted); + plug = (plugin != 0) ? plugin : GetBestPlugin(playState, nChn, PrioritiseChannel, EvenIfMuted); } if(plug > 0 && plug <= MAX_MIXPLUGINS) @@ -5218,12 +5227,12 @@ { if(macro[0] == 0xF0) { - pPlugin->MidiSysexSend(mpt::as_span(mpt::byte_cast(macro), macroLen)); + pPlugin->MidiSysexSend(mpt::byte_cast(macro)); } else { - uint32 len = std::min(static_cast(MIDIEvents::GetEventLength(macro[0])), macroLen); + size_t len = std::min(static_cast(MIDIEvents::GetEventLength(macro[0])), macro.size()); uint32 curData = 0; - memcpy(&curData, macro, len); + memcpy(&curData, macro.data(), len); pPlugin->MidiSend(curData); } } @@ -5233,7 +5242,7 @@ MPT_UNREFERENCED_PARAMETER(plugin); #endif // NO_PLUGINS - return macroLen; + return static_cast(macro.size()); } return 0; @@ -6165,7 +6174,7 @@ } -PLUGINDEX CSoundFile::GetBestPlugin(CHANNELINDEX nChn, PluginPriority priority, PluginMutePriority respectMutes) const +PLUGINDEX CSoundFile::GetBestPlugin(const PlayState &playState, CHANNELINDEX nChn, PluginPriority priority, PluginMutePriority respectMutes) const { if (nChn >= MAX_CHANNELS) //Check valid channel number { @@ -6177,23 +6186,23 @@ switch (priority) { case ChannelOnly: - plugin = GetChannelPlugin(nChn, respectMutes); + plugin = GetChannelPlugin(playState, nChn, respectMutes); break; case InstrumentOnly: - plugin = GetActiveInstrumentPlugin(nChn, respectMutes); + plugin = GetActiveInstrumentPlugin(playState.Chn[nChn], respectMutes); break; case PrioritiseInstrument: - plugin = GetActiveInstrumentPlugin(nChn, respectMutes); + plugin = GetActiveInstrumentPlugin(playState.Chn[nChn], respectMutes); if(!plugin || plugin > MAX_MIXPLUGINS) { - plugin = GetChannelPlugin(nChn, respectMutes); + plugin = GetChannelPlugin(playState, nChn, respectMutes); } break; case PrioritiseChannel: - plugin = GetChannelPlugin(nChn, respectMutes); + plugin = GetChannelPlugin(playState, nChn, respectMutes); if(!plugin || plugin > MAX_MIXPLUGINS) { - plugin = GetActiveInstrumentPlugin(nChn, respectMutes); + plugin = GetActiveInstrumentPlugin(playState.Chn[nChn], respectMutes); } break; } @@ -6202,9 +6211,9 @@ } -PLUGINDEX CSoundFile::GetChannelPlugin(CHANNELINDEX nChn, PluginMutePriority respectMutes) const +PLUGINDEX CSoundFile::GetChannelPlugin(const PlayState &playState, CHANNELINDEX nChn, PluginMutePriority respectMutes) const { - const ModChannel &channel = m_PlayState.Chn[nChn]; + const ModChannel &channel = playState.Chn[nChn]; PLUGINDEX plugin; if((respectMutes == RespectMutes && channel.dwFlags[CHN_MUTE | CHN_SYNCMUTE]) || channel.dwFlags[CHN_NOFX]) @@ -6214,8 +6223,7 @@ { // If it looks like this is an NNA channel, we need to find the master channel. // This ensures we pick up the right ChnSettings. - // NB: nMasterChn == 0 means no master channel, so we need to -1 to get correct index. - if (nChn >= m_nChannels && channel.nMasterChn > 0) + if(channel.nMasterChn > 0) { nChn = channel.nMasterChn - 1; } @@ -6232,20 +6240,21 @@ } -PLUGINDEX CSoundFile::GetActiveInstrumentPlugin(CHANNELINDEX nChn, PluginMutePriority respectMutes) const +PLUGINDEX CSoundFile::GetActiveInstrumentPlugin(const ModChannel &chn, PluginMutePriority respectMutes) { // Unlike channel settings, pModInstrument is copied from the original chan to the NNA chan, // so we don't need to worry about finding the master chan. PLUGINDEX plug = 0; - if(m_PlayState.Chn[nChn].pModInstrument != nullptr) + if(chn.pModInstrument != nullptr) { - if(respectMutes == RespectMutes && m_PlayState.Chn[nChn].pModSample && m_PlayState.Chn[nChn].pModSample->uFlags[CHN_MUTE]) + // TODO this looks fishy. Shouldn't it check the mute status of the instrument itself?! + if(respectMutes == RespectMutes && chn.pModSample && chn.pModSample->uFlags[CHN_MUTE]) { plug = 0; } else { - plug = m_PlayState.Chn[nChn].pModInstrument->nMixPlug; + plug = chn.pModInstrument->nMixPlug; } } return plug; @@ -6255,10 +6264,10 @@ // Retrieve the plugin that is associated with the channel's current instrument. // No plugin is returned if the channel is muted or if the instrument doesn't have a MIDI channel set up, // As this is meant to be used with instrument plugins. -IMixPlugin *CSoundFile::GetChannelInstrumentPlugin(CHANNELINDEX chn) const +IMixPlugin *CSoundFile::GetChannelInstrumentPlugin(const ModChannel &chn) const { #ifndef NO_PLUGINS - if(m_PlayState.Chn[chn].dwFlags[CHN_MUTE | CHN_SYNCMUTE]) + if(chn.dwFlags[CHN_MUTE | CHN_SYNCMUTE]) { // Don't process portamento on muted channels. Note that this might have a side-effect // on other channels which trigger notes on the same MIDI channel of the same plugin, @@ -6266,9 +6275,9 @@ return nullptr; } - if(m_PlayState.Chn[chn].HasMIDIOutput()) + if(chn.HasMIDIOutput()) { - const ModInstrument *pIns = m_PlayState.Chn[chn].pModInstrument; + const ModInstrument *pIns = chn.pModInstrument; // Instrument sends to a MIDI channel if(pIns->nMixPlug != 0 && pIns->nMixPlug <= MAX_MIXPLUGINS) { Index: soundlib/Sndfile.h =================================================================== --- soundlib/Sndfile.h (revision 16687) +++ soundlib/Sndfile.h (working copy) @@ -583,10 +583,13 @@ CHANNELINDEX ChnMix[MAX_CHANNELS]; // Index of channels in Chn to be actually mixed ModChannel Chn[MAX_CHANNELS]; // Mixing channels... First m_nChannels channels are master channels (i.e. they are never NNA channels)! + std::vector m_MidiMacroScratchSpace; + public: PlayState() { std::fill(std::begin(Chn), std::end(Chn), ModChannel()); + m_MidiMacroScratchSpace.reserve(MACRO_LENGTH); } void ResetGlobalVolumeRamping() @@ -1115,9 +1118,10 @@ void GlobalVolSlide(ModCommand::PARAM param, uint8 &nOldGlobalVolSlide); void ProcessMacroOnChannel(CHANNELINDEX nChn); - void ProcessMIDIMacro(CHANNELINDEX nChn, bool isSmooth, const char *macro, uint8 param = 0, PLUGINDEX plugin = 0); - float CalculateSmoothParamChange(float currentValue, float param) const; - uint32 SendMIDIData(CHANNELINDEX nChn, bool isSmooth, const unsigned char *macro, uint32 macroLen, PLUGINDEX plugin); + void ProcessMIDIMacro(PlayState &playState, CHANNELINDEX nChn, bool isSmooth, const MIDIMacroConfigData::Macro ¯o, uint8 param = 0, PLUGINDEX plugin = 0, bool localOnly = false); + void ParseMIDIMacro(PlayState &playState, CHANNELINDEX nChn, bool isSmooth, const mpt::span macro, mpt::span &out, uint8 param = 0, PLUGINDEX plugin = 0) const; + static float CalculateSmoothParamChange(const PlayState &playState, float currentValue, float param); + uint32 SendMIDIData(PlayState &playState, CHANNELINDEX nChn, bool isSmooth, const mpt::span macro, PLUGINDEX plugin, bool localOnly); void SendMIDINote(CHANNELINDEX chn, uint16 note, uint16 volume); int SetupChannelFilter(ModChannel &chn, bool bReset, int envModifier = 256) const; @@ -1243,12 +1247,12 @@ void ProcessStereoSeparation(long countChunk); private: - PLUGINDEX GetChannelPlugin(CHANNELINDEX nChn, PluginMutePriority respectMutes) const; - PLUGINDEX GetActiveInstrumentPlugin(CHANNELINDEX, PluginMutePriority respectMutes) const; - IMixPlugin *GetChannelInstrumentPlugin(CHANNELINDEX chn) const; + PLUGINDEX GetChannelPlugin(const PlayState &playState, CHANNELINDEX nChn, PluginMutePriority respectMutes) const; + static PLUGINDEX GetActiveInstrumentPlugin(const ModChannel &chn, PluginMutePriority respectMutes); + IMixPlugin *GetChannelInstrumentPlugin(const ModChannel &chn) const; public: - PLUGINDEX GetBestPlugin(CHANNELINDEX nChn, PluginPriority priority, PluginMutePriority respectMutes) const; + PLUGINDEX GetBestPlugin(const PlayState &playState, CHANNELINDEX nChn, PluginPriority priority, PluginMutePriority respectMutes) const; }; Index: soundlib/Sndmix.cpp =================================================================== --- soundlib/Sndmix.cpp (revision 16687) +++ soundlib/Sndmix.cpp (working copy) @@ -1705,7 +1705,7 @@ // Process MIDI vibrato for plugins: #ifndef NO_PLUGINS - IMixPlugin *plugin = GetChannelInstrumentPlugin(nChn); + IMixPlugin *plugin = GetChannelInstrumentPlugin(m_PlayState.Chn[nChn]); if(plugin != nullptr) { // If the Pitch Wheel Depth is configured correctly (so it's the same as the plugin's PWD), @@ -1728,7 +1728,7 @@ { // Stop MIDI vibrato for plugins: #ifndef NO_PLUGINS - IMixPlugin *plugin = GetChannelInstrumentPlugin(nChn); + IMixPlugin *plugin = GetChannelInstrumentPlugin(m_PlayState.Chn[nChn]); if(plugin != nullptr) { plugin->MidiVibrato(0, 0, nChn); @@ -2528,15 +2528,15 @@ if(nChn < GetNumChannels()) { // TODO evaluate per-plugin macros here - //ProcessMIDIMacro(nChn, false, m_MidiCfg.szMidiGlb[MIDIOUT_PAN]); - //ProcessMIDIMacro(nChn, false, m_MidiCfg.szMidiGlb[MIDIOUT_VOLUME]); + //ProcessMIDIMacro(m_PlayState, nChn, false, m_MidiCfg.szMidiGlb[MIDIOUT_PAN]); + //ProcessMIDIMacro(m_PlayState, nChn, false, m_MidiCfg.szMidiGlb[MIDIOUT_VOLUME]); if((chn.rowCommand.command == CMD_MIDI && m_SongFlags[SONG_FIRSTTICK]) || chn.rowCommand.command == CMD_SMOOTHMIDI) { if(chn.rowCommand.param < 0x80) - ProcessMIDIMacro(nChn, (chn.rowCommand.command == CMD_SMOOTHMIDI), m_MidiCfg.szMidiSFXExt[chn.nActiveMacro], chn.rowCommand.param); + ProcessMIDIMacro(m_PlayState, nChn, (chn.rowCommand.command == CMD_SMOOTHMIDI), m_MidiCfg.szMidiSFXExt[chn.nActiveMacro], chn.rowCommand.param); else - ProcessMIDIMacro(nChn, (chn.rowCommand.command == CMD_SMOOTHMIDI), m_MidiCfg.szMidiZXXExt[(chn.rowCommand.param & 0x7F)], 0); + ProcessMIDIMacro(m_PlayState, nChn, (chn.rowCommand.command == CMD_SMOOTHMIDI), m_MidiCfg.szMidiZXXExt[(chn.rowCommand.param & 0x7F)], 0); } } } @@ -2562,7 +2562,7 @@ } // Check instrument plugins - const PLUGINDEX nPlugin = GetBestPlugin(nChn, PrioritiseInstrument, RespectMutes); + const PLUGINDEX nPlugin = GetBestPlugin(m_PlayState, nChn, PrioritiseInstrument, RespectMutes); IMixPlugin *pPlugin = nullptr; if(nPlugin > 0 && nPlugin <= MAX_MIXPLUGINS) {