Index: mptrack/AbstractVstEditor.cpp =================================================================== --- mptrack/AbstractVstEditor.cpp (revision 16701) +++ mptrack/AbstractVstEditor.cpp (working copy) @@ -91,7 +91,7 @@ ON_MESSAGE(WM_MOD_KEYCOMMAND, &CAbstractVstEditor::OnCustomKeyMsg) //rewbs.customKeys ON_COMMAND_RANGE(ID_PLUGSELECT, ID_PLUGSELECT + MAX_MIXPLUGINS, &CAbstractVstEditor::OnToggleEditor) //rewbs.patPlugName ON_COMMAND_RANGE(ID_SELECTINST, ID_SELECTINST + MAX_INSTRUMENTS, &CAbstractVstEditor::OnSetInputInstrument) //rewbs.patPlugName - ON_COMMAND_RANGE(ID_LEARN_MACRO_FROM_PLUGGUI, ID_LEARN_MACRO_FROM_PLUGGUI + NUM_MACROS, &CAbstractVstEditor::PrepareToLearnMacro) + ON_COMMAND_RANGE(ID_LEARN_MACRO_FROM_PLUGGUI, ID_LEARN_MACRO_FROM_PLUGGUI + kSFxMacros, &CAbstractVstEditor::PrepareToLearnMacro) END_MESSAGE_MAP() @@ -829,7 +829,7 @@ } CString label, macroName; - for(int nMacro = 0; nMacro < NUM_MACROS; nMacro++) + for(int nMacro = 0; nMacro < kSFxMacros; nMacro++) { int action = 0; UINT greyed = MF_GRAYED; @@ -965,7 +965,7 @@ void CAbstractVstEditor::SetLearnMacro(int inMacro) { - if (inMacro < NUM_MACROS) + if (inMacro < kSFxMacros) { m_nLearnMacro=inMacro; } Index: mptrack/EffectInfo.cpp =================================================================== --- mptrack/EffectInfo.cpp (revision 16701) +++ 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 16701) +++ mptrack/MIDIMacroDialog.cpp (working copy) @@ -35,8 +35,8 @@ ON_CBN_SELCHANGE(IDC_MACROCC, &CMidiMacroSetup::OnCCChanged) ON_EN_CHANGE(IDC_EDIT1, &CMidiMacroSetup::OnSFxEditChanged) ON_EN_CHANGE(IDC_EDIT2, &CMidiMacroSetup::OnZxxEditChanged) - ON_COMMAND_RANGE(ID_PLUGSELECT, ID_PLUGSELECT + NUM_MACROS - 1, &CMidiMacroSetup::OnViewAllParams) - ON_COMMAND_RANGE(ID_PLUGSELECT + NUM_MACROS, ID_PLUGSELECT + NUM_MACROS + NUM_MACROS - 1, &CMidiMacroSetup::OnSetSFx) + ON_COMMAND_RANGE(ID_PLUGSELECT, ID_PLUGSELECT + kSFxMacros - 1, &CMidiMacroSetup::OnViewAllParams) + ON_COMMAND_RANGE(ID_PLUGSELECT + kSFxMacros, ID_PLUGSELECT + kSFxMacros + kSFxMacros - 1, &CMidiMacroSetup::OnSetSFx) END_MESSAGE_MAP() @@ -59,8 +59,8 @@ { CString s; CDialog::OnInitDialog(); - m_EditSFx.SetLimitText(MACRO_LENGTH - 1); - m_EditZxx.SetLimitText(MACRO_LENGTH - 1); + m_EditSFx.SetLimitText(kMacroLength - 1); + m_EditZxx.SetLimitText(kMacroLength - 1); // Parametered macro selection for(int i = 0; i < 16; i++) @@ -106,18 +106,18 @@ int offsetx = ScalePixels(19), offsety = ScalePixels(30), separatorx = ScalePixels(4), separatory = ScalePixels(2); int height = ScalePixels(18), widthMacro = ScalePixels(30), widthVal = ScalePixels(179), widthType = ScalePixels(135), widthBtn = ScalePixels(70); - for(UINT m = 0; m < NUM_MACROS; m++) + for(UINT m = 0; m < kSFxMacros; m++) { m_EditMacro[m].Create(_T(""), WS_CHILD | WS_VISIBLE | WS_TABSTOP, - CRect(offsetx, offsety + m * (separatory + height), offsetx + widthMacro, offsety + m * (separatory + height) + height), this, ID_PLUGSELECT + NUM_MACROS + m); + CRect(offsetx, offsety + m * (separatory + height), offsetx + widthMacro, offsety + m * (separatory + height) + height), this, ID_PLUGSELECT + kSFxMacros + m); m_EditMacro[m].SetFont(GetFont()); m_EditMacroType[m].Create(ES_READONLY | WS_CHILD| WS_VISIBLE | WS_TABSTOP | WS_BORDER, - CRect(offsetx + separatorx + widthMacro, offsety + m * (separatory + height), offsetx + widthMacro + widthType, offsety + m * (separatory + height) + height), this, ID_PLUGSELECT + NUM_MACROS + m); + CRect(offsetx + separatorx + widthMacro, offsety + m * (separatory + height), offsetx + widthMacro + widthType, offsety + m * (separatory + height) + height), this, ID_PLUGSELECT + kSFxMacros + m); m_EditMacroType[m].SetFont(GetFont()); m_EditMacroValue[m].Create(ES_CENTER | ES_READONLY | WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_BORDER, - CRect(offsetx + separatorx + widthType + widthMacro, offsety + m * (separatory + height), offsetx + widthMacro + widthType + widthVal, offsety + m * (separatory + height) + height), this, ID_PLUGSELECT + NUM_MACROS + m); + CRect(offsetx + separatorx + widthType + widthMacro, offsety + m * (separatory + height), offsetx + widthMacro + widthType + widthVal, offsety + m * (separatory + height) + height), this, ID_PLUGSELECT + kSFxMacros + m); m_EditMacroValue[m].SetFont(GetFont()); m_BtnMacroShowAll[m].Create(_T("Show All..."), WS_CHILD | WS_TABSTOP | WS_VISIBLE, @@ -156,13 +156,13 @@ int start, end; - if(macro >= 0 && macro < 16) + if(macro >= 0 && macro < kSFxMacros) { start = end = macro; } else { start = 0; - end = NUM_MACROS - 1; + end = kSFxMacros - 1; } CString s; @@ -175,7 +175,7 @@ m_EditMacro[m].SetWindowText(s); // Macro value: - m_EditMacroValue[m].SetWindowText(mpt::ToCString(mpt::Charset::ASCII, m_MidiCfg.szMidiSFXExt[m])); + m_EditMacroValue[m].SetWindowText(mpt::ToCString(mpt::Charset::ASCII, m_MidiCfg.SFx[m])); m_EditMacroValue[m].SetBackColor(m == selectedMacro ? RGB(200, 200, 225) : RGB(245, 245, 245)); // Macro Type: @@ -203,10 +203,10 @@ { UINT sfx = m_CbnSFx.GetCurSel(); UINT sfx_preset = static_cast(m_CbnSFxPreset.GetItemData(m_CbnSFxPreset.GetCurSel())); - if(sfx < std::size(m_MidiCfg.szMidiSFXExt)) + if(sfx < m_MidiCfg.SFx.size()) { ToggleBoxes(sfx_preset, sfx); - m_EditSFx.SetWindowText(mpt::ToCString(mpt::Charset::ASCII, m_MidiCfg.szMidiSFXExt[sfx])); + m_EditSFx.SetWindowText(mpt::ToCString(mpt::Charset::ASCII, m_MidiCfg.SFx[sfx])); } UpdateZxxSelection(); @@ -268,7 +268,7 @@ UINT sfx = m_CbnSFx.GetCurSel(); ParameteredMacro sfx_preset = static_cast(m_CbnSFxPreset.GetItemData(m_CbnSFxPreset.GetCurSel())); - if (sfx < std::size(m_MidiCfg.szMidiSFXExt)) + if (sfx < kSFxMacros) { if(sfx_preset != kSFxCustom) { @@ -294,9 +294,9 @@ void CMidiMacroSetup::UpdateZxxSelection() { UINT zxx = m_CbnZxx.GetCurSel(); - if(zxx < std::size(m_MidiCfg.szMidiZXXExt)) + if(zxx < m_MidiCfg.Zxx.size()) { - m_EditZxx.SetWindowText(mpt::ToCString(mpt::Charset::ASCII, m_MidiCfg.szMidiZXXExt[zxx])); + m_EditZxx.SetWindowText(mpt::ToCString(mpt::Charset::ASCII, m_MidiCfg.Zxx[zxx])); } } @@ -304,13 +304,13 @@ void CMidiMacroSetup::OnSFxEditChanged() { UINT sfx = m_CbnSFx.GetCurSel(); - if (sfx < std::size(m_MidiCfg.szMidiSFXExt)) + if(sfx < m_MidiCfg.SFx.size()) { - if(ValidateMacroString(m_EditSFx, m_MidiCfg.szMidiSFXExt[sfx], true)) + if(ValidateMacroString(m_EditSFx, m_MidiCfg.SFx[sfx], true)) { CString s; m_EditSFx.GetWindowText(s); - mpt::String::WriteAutoBuf(m_MidiCfg.szMidiSFXExt[sfx]) = mpt::ToCharset(mpt::Charset::ASCII, s); + m_MidiCfg.SFx[sfx] = mpt::ToCharset(mpt::Charset::ASCII, s); int sfx_preset = m_MidiCfg.GetParameteredMacroType(sfx); m_CbnSFxPreset.SetCurSel(sfx_preset); @@ -324,13 +324,13 @@ void CMidiMacroSetup::OnZxxEditChanged() { UINT zxx = m_CbnZxx.GetCurSel(); - if (zxx < std::size(m_MidiCfg.szMidiZXXExt)) + if(zxx < m_MidiCfg.Zxx.size()) { - if(ValidateMacroString(m_EditZxx, m_MidiCfg.szMidiZXXExt[zxx], false)) + if(ValidateMacroString(m_EditZxx, m_MidiCfg.Zxx[zxx], false)) { CString s; m_EditZxx.GetWindowText(s); - mpt::String::WriteAutoBuf(m_MidiCfg.szMidiZXXExt[zxx]) = mpt::ToCharset(mpt::Charset::ASCII, s); + m_MidiCfg.Zxx[zxx] = mpt::ToCharset(mpt::Charset::ASCII, s); m_CbnZxxPreset.SetCurSel(m_MidiCfg.GetFixedMacroType()); } } @@ -338,7 +338,7 @@ void CMidiMacroSetup::OnSetSFx(UINT id) { - m_CbnSFx.SetCurSel(id - (ID_PLUGSELECT + NUM_MACROS)); + m_CbnSFx.SetCurSel(id - (ID_PLUGSELECT + kSFxMacros)); OnSFxChanged(); } @@ -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 16701) +++ mptrack/MIDIMacroDialog.h (working copy) @@ -24,8 +24,8 @@ protected: CComboBox m_CbnSFx, m_CbnSFxPreset, m_CbnZxx, m_CbnZxxPreset, m_CbnMacroPlug, m_CbnMacroParam, m_CbnMacroCC; CEdit m_EditSFx, m_EditZxx; - CColourEdit m_EditMacroValue[NUM_MACROS], m_EditMacroType[NUM_MACROS]; - CButton m_EditMacro[NUM_MACROS], m_BtnMacroShowAll[NUM_MACROS]; + CColourEdit m_EditMacroValue[kSFxMacros], m_EditMacroType[kSFxMacros]; + CButton m_EditMacro[kSFxMacros], m_BtnMacroShowAll[kSFxMacros]; CSoundFile &m_SndFile; @@ -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 16701) +++ 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 16701) +++ 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; @@ -2864,22 +2864,18 @@ void CModDoc::LearnMacro(int macroToSet, PlugParamIndex paramToUse) { - if (macroToSet < 0 || macroToSet > NUM_MACROS) + if(macroToSet < 0 || macroToSet > kSFxMacros) { return; } // If macro already exists for this param, inform user and return - for (int checkMacro = 0; checkMacro < NUM_MACROS; checkMacro++) + if(auto macro = m_SndFile.m_MidiCfg.FindMacroForParam(paramToUse); macro >= 0) { - if (m_SndFile.m_MidiCfg.GetParameteredMacroType(checkMacro) == kSFxPlugParam - && m_SndFile.m_MidiCfg.MacroToPlugParam(checkMacro) == paramToUse) - { - CString message; - message.Format(_T("Parameter %i can already be controlled with macro %X."), static_cast(paramToUse), checkMacro); - Reporting::Information(message, _T("Macro exists for this parameter")); - return; - } + CString message; + message.Format(_T("Parameter %i can already be controlled with macro %X."), static_cast(paramToUse), macro); + Reporting::Information(message, _T("Macro exists for this parameter")); + return; } // Set new macro Index: mptrack/MPTHacks.cpp =================================================================== --- mptrack/MPTHacks.cpp (revision 16701) +++ mptrack/MPTHacks.cpp (working copy) @@ -460,7 +460,7 @@ { for(const auto ¯o : m_SndFile.m_MidiCfg) { - for(const auto c : macro) + for(const auto c : macro.Span()) { if(c == 's') { Index: mptrack/TrackerSettings.cpp =================================================================== --- mptrack/TrackerSettings.cpp (revision 16701) +++ mptrack/TrackerSettings.cpp (working copy) @@ -441,13 +441,13 @@ // Zxx Macros MIDIMacroConfig macros; theApp.GetDefaultMidiMacro(macros); - for(int isfx = 0; isfx < 16; isfx++) + for(int i = 0; i < kSFxMacros; i++) { - mpt::String::WriteAutoBuf(macros.szMidiSFXExt[isfx]) = conf.Read(U_("Zxx Macros"), MPT_UFORMAT("SF{}")(mpt::ufmt::HEX(isfx)), macros.szMidiSFXExt[isfx]); + macros.SFx[i] = conf.Read(U_("Zxx Macros"), MPT_UFORMAT("SF{}")(mpt::ufmt::HEX(i)), macros.SFx[i]); } - for(int izxx = 0; izxx < 128; izxx++) + for(int i = 0; i < kZxxMacros; i++) { - mpt::String::WriteAutoBuf(macros.szMidiZXXExt[izxx]) = conf.Read(U_("Zxx Macros"), MPT_UFORMAT("Z{}")(mpt::ufmt::HEX0<2>(izxx | 0x80)), macros.szMidiZXXExt[izxx]); + macros.Zxx[i] = conf.Read(U_("Zxx Macros"), MPT_UFORMAT("Z{}")(mpt::ufmt::HEX0<2>(i | 0x80)), macros.Zxx[i]); } @@ -1342,13 +1342,13 @@ // Save default macro configuration MIDIMacroConfig macros; theApp.GetDefaultMidiMacro(macros); - for(int isfx = 0; isfx < 16; isfx++) + for(int isfx = 0; isfx < kSFxMacros; isfx++) { - conf.Write(U_("Zxx Macros"), MPT_UFORMAT("SF{}")(mpt::ufmt::HEX(isfx)), macros.szMidiSFXExt[isfx]); + conf.Write(U_("Zxx Macros"), MPT_UFORMAT("SF{}")(mpt::ufmt::HEX(isfx)), macros.SFx[isfx]); } - for(int izxx = 0; izxx < 128; izxx++) + for(int izxx = 0; izxx < kZxxMacros; izxx++) { - conf.Write(U_("Zxx Macros"), MPT_UFORMAT("Z{}")(mpt::ufmt::HEX0<2>(izxx | 0x80)), macros.szMidiZXXExt[izxx]); + conf.Write(U_("Zxx Macros"), MPT_UFORMAT("Z{}")(mpt::ufmt::HEX0<2>(izxx | 0x80)), macros.Zxx[izxx]); } // MRU list Index: soundlib/Fastmix.cpp =================================================================== --- soundlib/Fastmix.cpp (revision 16701) +++ 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 16701) +++ 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.Zxx[i ] = MPT_AFORMAT("F0F080{}")(mpt::afmt::HEX0<2>(param)); + m_MidiCfg.Zxx[i + 32] = MPT_AFORMAT("F0F081{}")(mpt::afmt::HEX0<2>(param)); + m_MidiCfg.Zxx[i + 64] = MPT_AFORMAT("F0F082{}")(mpt::afmt::HEX0<2>(param)); + m_MidiCfg.Zxx[i + 96] = MPT_AFORMAT("F0F083{}")(mpt::afmt::HEX0<2>(param)); } } #endif // NO_PLUGINS Index: soundlib/Load_med.cpp =================================================================== --- soundlib/Load_med.cpp (revision 16701) +++ 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.SFx[0] = "Cc z"; file.Rewind(); PATTERNINDEX basePattern = 0; @@ -1202,7 +1202,7 @@ // Read MIDI messages if(expData.midiDumpOffset && file.Seek(expData.midiDumpOffset) && file.CanRead(8)) { - uint16 numDumps = std::min(file.ReadUint16BE(), static_cast(std::size(m_MidiCfg.szMidiZXXExt))); + uint16 numDumps = std::min(file.ReadUint16BE(), static_cast(m_MidiCfg.Zxx.size())); file.Skip(6); if(file.CanRead(numDumps * 4)) { @@ -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.Zxx[dump] = macro; } } } Index: soundlib/Load_mo3.cpp =================================================================== --- soundlib/Load_mo3.cpp (revision 16701) +++ 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.SFx[i] = MPT_AFORMAT("F0F0{}z")(mpt::afmt::HEX0<2>(fileHeader.sfxMacros[i] - 1)); else - mpt::String::WriteAutoBuf(m_MidiCfg.szMidiSFXExt[i]) = ""; + m_MidiCfg.SFx[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.Zxx[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.Zxx[i] = ""; } } Index: soundlib/Load_symmod.cpp =================================================================== --- soundlib/Load_symmod.cpp (revision 16701) +++ 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) { @@ -1642,10 +1642,10 @@ { m.command = CMD_MIDI; m.param = macroMap[event]; - } else if(macroMap.size() < std::size(m_MidiCfg.szMidiZXXExt)) + } else if(macroMap.size() < m_MidiCfg.Zxx.size()) { uint8 param = static_cast(macroMap.size()); - if(ConvertDSP(event, m_MidiCfg.szMidiZXXExt[param], *this)) + if(ConvertDSP(event, m_MidiCfg.Zxx[param], *this)) { m.command = CMD_MIDI; m.param = macroMap[event] = 0x80 | param; Index: soundlib/MIDIMacros.cpp =================================================================== --- soundlib/MIDIMacros.cpp (revision 16701) +++ soundlib/MIDIMacros.cpp (working copy) @@ -9,10 +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 #include "Sndfile.h" @@ -23,7 +21,7 @@ ParameteredMacro MIDIMacroConfig::GetParameteredMacroType(uint32 macroIndex) const { - const std::string macro = GetSafeMacro(szMidiSFXExt[macroIndex]); + const std::string macro = SFx[macroIndex].NormalizedString(); for(uint32 i = 0; i < kSFxMax; i++) { @@ -30,17 +28,18 @@ ParameteredMacro sfx = static_cast(i); if(sfx != kSFxCustom) { - if(macro.compare(CreateParameteredMacro(sfx)) == 0) return sfx; + if(macro == CreateParameteredMacro(sfx)) + return sfx; } } // Special macros with additional "parameter": - if (macro.compare(CreateParameteredMacro(kSFxCC, MIDIEvents::MIDICC_start)) >= 0 && macro.compare(CreateParameteredMacro(kSFxCC, MIDIEvents::MIDICC_end)) <= 0 && macro.size() == 5) + if(macro.size() == 5 && macro.compare(CreateParameteredMacro(kSFxCC, MIDIEvents::MIDICC_start)) >= 0 && macro.compare(CreateParameteredMacro(kSFxCC, MIDIEvents::MIDICC_end)) <= 0) return kSFxCC; - if (macro.compare(CreateParameteredMacro(kSFxPlugParam, 0)) >= 0 && macro.compare(CreateParameteredMacro(kSFxPlugParam, 0x17F)) <= 0 && macro.size() == 7) + if(macro.size() == 7 && macro.compare(CreateParameteredMacro(kSFxPlugParam, 0)) >= 0 && macro.compare(CreateParameteredMacro(kSFxPlugParam, 0x17F)) <= 0) return kSFxPlugParam; - return kSFxCustom; // custom / unknown + return kSFxCustom; // custom / unknown } @@ -54,13 +53,13 @@ if(zxx != kZxxCustom) { // Prepare macro pattern to compare - Macro macros[128]; - CreateFixedMacro(macros, zxx); + decltype(Zxx) fixedMacro; + CreateFixedMacro(fixedMacro, zxx); bool found = true; - for(uint32 j = 0; j < 128; j++) + for(uint32 j = 0; j < kZxxMacros; j++) { - if(strncmp(macros[j], szMidiZXXExt[j], MACRO_LENGTH)) + if(fixedMacro[j] != Zxx[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(); @@ -98,59 +97,59 @@ std::string MIDIMacroConfig::CreateParameteredMacro(ParameteredMacro macroType, int subType) const { - Macro parameteredMacro; + Macro parameteredMacro{}; CreateParameteredMacro(parameteredMacro, macroType, subType); - return mpt::String::ReadAutoBuf(parameteredMacro); + return parameteredMacro; } // Create Zxx (Z80 - ZFF) from preset -void MIDIMacroConfig::CreateFixedMacro(Macro (&fixedMacros)[128], FixedMacro macroType) const +void MIDIMacroConfig::CreateFixedMacro(std::array fixedMacros, FixedMacro macroType) const { - for(uint32 i = 0; i < 128; i++) + for(uint32 i = 0; i < kZxxMacros; i++) { uint32 param = i; 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; @@ -262,7 +261,7 @@ int MIDIMacroConfig::MacroToPlugParam(uint32 macroIndex) const { - const std::string macro = GetSafeMacro(szMidiSFXExt[macroIndex]); + const std::string macro = SFx[macroIndex].NormalizedString(); int code = 0; const char *param = macro.c_str(); @@ -281,7 +280,7 @@ int MIDIMacroConfig::MacroToMidiCC(uint32 macroIndex) const { - const std::string macro = GetSafeMacro(szMidiSFXExt[macroIndex]); + const std::string macro = SFx[macroIndex].NormalizedString(); int code = 0; const char *param = macro.c_str(); @@ -297,7 +296,7 @@ int MIDIMacroConfig::FindMacroForParam(PlugParamIndex param) const { - for(int macroIndex = 0; macroIndex < NUM_MACROS; macroIndex++) + for(int macroIndex = 0; macroIndex < kSFxMacros; macroIndex++) { if(GetParameteredMacroType(macroIndex) == kSFxPlugParam && MacroToPlugParam(macroIndex) == param) { @@ -314,26 +313,7 @@ // i.e. the configuration that is assumed when loading a file that has no macros embedded. bool MIDIMacroConfig::IsMacroDefaultSetupUsed() const { - const MIDIMacroConfig defaultConfig; - - // TODO - Global macros (currently not checked because they are not editable) - - // SF0: Z00-Z7F controls cutoff, all other parametered macros are unused - for(uint32 i = 0; i < NUM_MACROS; i++) - { - if(GetParameteredMacroType(i) != defaultConfig.GetParameteredMacroType(i)) - { - return false; - } - } - - // Z80-Z8F controls resonance - if(GetFixedMacroType() != defaultConfig.GetFixedMacroType()) - { - return false; - } - - return true; + return *this == MIDIMacroConfig{}; } @@ -340,15 +320,13 @@ // Reset MIDI macro config to default values. void MIDIMacroConfig::Reset() { - MemsetZero(szMidiGlb); - MemsetZero(szMidiSFXExt); - MemsetZero(szMidiZXXExt); + std::fill(begin(), end(), Macro{}); - 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"); + Global[MIDIOUT_START] = "FF"; + Global[MIDIOUT_STOP] = "FC"; + Global[MIDIOUT_NOTEON] = "9c n v"; + Global[MIDIOUT_NOTEOFF] = "9c n 0"; + Global[MIDIOUT_PROGRAM] = "Cc p"; // SF0: Z00-Z7F controls cutoff CreateParameteredMacro(0, kSFxCutoff); // Z80-Z8F controls resonance @@ -359,8 +337,8 @@ // Clear all Zxx macros so that they do nothing. void MIDIMacroConfig::ClearZxxMacros() { - MemsetZero(szMidiSFXExt); - MemsetZero(szMidiZXXExt); + std::fill(SFx.begin(), SFx.end(), Macro{}); + std::fill(Zxx.begin(), Zxx.end(), Macro{}); } @@ -369,50 +347,25 @@ { for(auto ¯o : *this) { - macro[MACRO_LENGTH - 1] = '\0'; - std::fill(std::find(std::begin(macro), std::end(macro), '\0'), std::end(macro), '\0'); - for(auto &c : macro) - { - if(c && (c < 32 || c >= 127)) - c = ' '; - } + macro.Sanitize(); } } -// Helper function for UpgradeMacros() -void MIDIMacroConfig::UpgradeMacroString(Macro ¯o) const -{ - for(auto &c : macro) - { - if(c >= 'a' && c <= 'f') // Both A-F and a-f were treated as hex constants - { - c = c - 'a' + 'A'; - } else if(c == 'K' || c == 'k') // Channel was K or k - { - c = 'c'; - } else if(c == 'X' || c == 'x' || c == 'Y' || c == 'y') // Those were pointless - { - c = 'z'; - } - } -} - - // Fix old-format (not conforming to IT's MIDI macro definitions) MIDI config strings. void MIDIMacroConfig::UpgradeMacros() { for(auto ¯o : *this) { - UpgradeMacroString(macro); + macro.UpgradeLegacyMacro(); } } // Normalize by removing blanks and other unwanted characters from macro strings for internal usage. -std::string MIDIMacroConfig::GetSafeMacro(const Macro ¯o) const +std::string MIDIMacroConfig::Macro::NormalizedString() const { - std::string sanitizedMacro = macro; + std::string sanitizedMacro = *this; std::string::size_type pos; while((pos = sanitizedMacro.find_first_not_of("0123456789ABCDEFabchmnopsuvxyz")) != std::string::npos) @@ -424,4 +377,35 @@ } +void MIDIMacroConfig::Macro::Sanitize() noexcept +{ + m_data.back() = '\0'; + const auto length = Length(); + std::fill(m_data.begin() + length, m_data.end(), '\0'); + for(size_t i = 0; i < length; i++) + { + if(m_data[i] < 32 || m_data[i] >= 127) + m_data[i] = ' '; + } +} + + +void MIDIMacroConfig::Macro::UpgradeLegacyMacro() noexcept +{ + for(auto &c : m_data) + { + if(c >= 'a' && c <= 'f') // Both A-F and a-f were treated as hex constants + { + c = c - 'a' + 'A'; + } else if(c == 'K' || c == 'k') // Channel was K or k + { + c = 'c'; + } else if(c == 'X' || c == 'x' || c == 'Y' || c == 'y') // Those were pointless + { + c = 'z'; + } + } +} + + OPENMPT_NAMESPACE_END Index: soundlib/MIDIMacros.h =================================================================== --- soundlib/MIDIMacros.h (revision 16701) +++ soundlib/MIDIMacros.h (working copy) @@ -18,8 +18,10 @@ enum { - NUM_MACROS = 16, // number of parametered macros - MACRO_LENGTH = 32, // max number of chars per macro + kGlobalMacros = 9, // Number of global macros + kSFxMacros = 16, // Number of parametered macros + kZxxMacros = 128, // Number of fixed macros + kMacroLength = 32, // Max number of chars per macro }; OPENMPT_NAMESPACE_END @@ -70,7 +72,7 @@ // Global macro types -enum +enum GlobalMacro { MIDIOUT_START = 0, MIDIOUT_STOP, @@ -86,19 +88,80 @@ struct MIDIMacroConfigData { - typedef char Macro[MACRO_LENGTH]; - // encoding is ASCII - Macro szMidiGlb[9]; // Global MIDI macros - Macro szMidiSFXExt[16]; // Parametric MIDI macros - Macro szMidiZXXExt[128]; // Fixed MIDI macros + struct Macro + { + public: + using RawType = std::array; - 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 &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, m_data.begin()); + m_data[copyLength] = '\0'; + Sanitize(); + 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); + } + + mpt::span Span() const noexcept + { + return {m_data.data(), Length()}; + } + operator std::string_view() const noexcept + { + return {m_data.data(), Length()}; + } + operator std::string() const + { + return {m_data.data(), Length()}; + } + + MPT_CONSTEXPR20_FUN size_t Length() const noexcept + { + return static_cast(std::distance(m_data.begin(), std::find(m_data.begin(), m_data.end(), '\0'))); + } + + MPT_CONSTEXPR20_FUN void Clear() noexcept + { + m_data.fill('\0'); + } + + // Remove blanks and other unwanted characters from macro strings for internal usage. + std::string NormalizedString() const; + + void Sanitize() noexcept; + void UpgradeLegacyMacro() noexcept; + + private: + RawType m_data; + }; + + std::array Global; + std::array SFx; + std::array Zxx; + + constexpr Macro *begin() noexcept {return Global.data(); } + constexpr const Macro *begin() const noexcept { return Global.data(); } + constexpr Macro *end() noexcept { return Zxx.data() + Zxx.size(); } + constexpr const Macro *end() const noexcept { return Zxx.data() + Zxx.size(); } }; -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 { @@ -117,16 +180,17 @@ public: void CreateParameteredMacro(uint32 macroIndex, ParameteredMacro macroType, int subType = 0) { - CreateParameteredMacro(szMidiSFXExt[macroIndex], macroType, subType); + if(macroIndex < std::size(SFx)) + CreateParameteredMacro(SFx[macroIndex], macroType, subType); } std::string CreateParameteredMacro(ParameteredMacro macroType, int subType = 0) const; protected: - void CreateFixedMacro(Macro (&fixedMacros)[128], FixedMacro macroType) const; + void CreateFixedMacro(std::array fixedMacros, FixedMacro macroType) const; public: void CreateFixedMacro(FixedMacro macroType) { - CreateFixedMacro(szMidiZXXExt, macroType); + CreateFixedMacro(Zxx, macroType); } #ifdef MODPLUG_TRACKER @@ -162,15 +226,6 @@ // Fix old-format (not conforming to IT's MIDI macro definitions) MIDI config strings. void UpgradeMacros(); - -protected: - - // Helper function for FixMacroFormat() - void UpgradeMacroString(Macro ¯o) const; - - // Remove blanks and other unwanted characters from macro strings for internal usage. - std::string GetSafeMacro(const Macro ¯o) const; - }; static_assert(sizeof(MIDIMacroConfig) == sizeof(MIDIMacroConfigData)); // this is directly written to files, so the size must be correct! Index: soundlib/ModInstrument.cpp =================================================================== --- soundlib/ModInstrument.cpp (revision 16701) +++ 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 16701) +++ 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/PluginStructs.h =================================================================== --- soundlib/plugins/PluginStructs.h (revision 16701) +++ soundlib/plugins/PluginStructs.h (working copy) @@ -22,8 +22,8 @@ //////////////////////////////////////////////////////////////////// // Mix Plugins -typedef int32 PlugParamIndex; -typedef float PlugParamValue; +using PlugParamIndex = int32; +using PlugParamValue = float; struct SNDMIXPLUGINSTATE; struct SNDMIXPLUGIN; Index: soundlib/plugins/PlugInterface.cpp =================================================================== --- soundlib/plugins/PlugInterface.cpp (revision 16701) +++ 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; } @@ -884,7 +890,7 @@ uint8 high = static_cast(midiBank >> 7); uint8 low = static_cast(midiBank & 0x7F); - //m_SndFile.ProcessMIDIMacro(trackChannel, false, m_SndFile.m_MidiCfg.szMidiGlb[MIDIOUT_BANKSEL], 0, m_nSlot + 1); + //m_SndFile.ProcessMIDIMacro(trackChannel, false, m_SndFile.m_MidiCfg.Global[MIDIOUT_BANKSEL], 0, m_nSlot + 1); MidiSend(MIDIEvents::CC(MIDIEvents::MIDICC_BankSelect_Coarse, midiCh, high)); MidiSend(MIDIEvents::CC(MIDIEvents::MIDICC_BankSelect_Fine, midiCh, low)); @@ -897,7 +903,7 @@ if(progChanged || (midiProg < 0x80 && bankChanged)) { channel.currentProgram = midiProg; - //m_SndFile.ProcessMIDIMacro(trackChannel, false, m_SndFile.m_MidiCfg.szMidiGlb[MIDIOUT_PROGRAM], 0, m_nSlot + 1); + //m_SndFile.ProcessMIDIMacro(trackChannel, false, m_SndFile.m_MidiCfg.Global[MIDIOUT_PROGRAM], 0, m_nSlot + 1); MidiSend(MIDIEvents::ProgramChange(midiCh, midiProg)); } Index: soundlib/plugins/PlugInterface.h =================================================================== --- soundlib/plugins/PlugInterface.h (revision 16701) +++ 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 16701) +++ soundlib/Snd_fx.cpp (working copy) @@ -64,10 +64,6 @@ uint8 vol = 0xFF; }; -#ifndef NO_PLUGINS - typedef std::map, uint16> PlugParamMap; - PlugParamMap plugParams; -#endif std::vector chnSettings; double elapsedTime; static constexpr uint32 IGNORE_CHANNEL = uint32_max; @@ -81,9 +77,8 @@ void Reset() { -#ifndef NO_PLUGINS - plugParams.clear(); -#endif + if(state->m_midiMacroEvaluationResults) + state->m_midiMacroEvaluationResults.emplace(); elapsedTime = 0.0; state->m_lTotalSampleCount = 0; state->m_nMusicSpeed = sndFile.m_nDefaultSpeed; @@ -295,6 +290,9 @@ } } + if(adjustMode & eAdjust) + playState.m_midiMacroEvaluationResults.emplace(); + // If samples are being synced, force them to resync if tick duration changes uint32 oldTickDuration = 0; bool breakToRow = false; @@ -469,9 +467,9 @@ if(p->IsPcNote()) { #ifndef NO_PLUGINS - if((adjustMode & eAdjust) && p->instr > 0 && p->instr <= MAX_MIXPLUGINS) + if(playState.m_midiMacroEvaluationResults && p->instr > 0 && p->instr <= MAX_MIXPLUGINS) { - memory.plugParams[std::make_pair(p->instr, p->GetValueVolCol())] = p->GetValueEffectCol(); + playState.m_midiMacroEvaluationResults->pluginParameter[{static_cast(p->instr - 1), p->GetValueVolCol()}] = p->GetValueEffectCol() / PlugParamValue(ModCommand::maxColumnValue); } #endif // NO_PLUGINS chn.rowCommand.Clear(); @@ -828,6 +826,13 @@ case CMD_PANBRELLO: Panbrello(chn, param); break; + + case CMD_MIDI: + case CMD_SMOOTHMIDI: + if(param < 0x80) + ProcessMIDIMacro(playState, nChn, false, m_MidiCfg.SFx[chn.nActiveMacro], chn.rowCommand.param, 0); + else + ProcessMIDIMacro(playState, nChn, false, m_MidiCfg.Zxx[param & 0x7F], chn.rowCommand.param, 0); default: break; } @@ -1186,6 +1191,8 @@ { if(retval.targetReached || target.mode == GetLengthTarget::NoTarget) { + const auto midiMacroEvaluationResults = std::move(playState.m_midiMacroEvaluationResults); + playState.m_midiMacroEvaluationResults.reset(); // Target found, or there is no target (i.e. play whole song)... m_PlayState = std::move(playState); m_PlayState.ResetGlobalVolumeRamping(); @@ -1215,11 +1222,11 @@ } #ifndef NO_PLUGINS - // If there were any PC events, update plugin parameters to their latest value. + // If there were any PC events or MIDI macros updating plugin parameters, update plugin parameters to their latest value. std::bitset plugSetProgram; - for(const auto ¶m : memory.plugParams) + for(const auto [plugParam, value] : midiMacroEvaluationResults->pluginParameter) { - PLUGINDEX plug = param.first.first - 1; + PLUGINDEX plug = plugParam.first; IMixPlugin *plugin = m_MixPlugins[plug].pMixPlugin; if(plugin != nullptr) { @@ -1229,7 +1236,7 @@ plugSetProgram.set(plug); plugin->BeginSetProgram(); } - plugin->SetParameter(param.first.second, param.second / PlugParamValue(ModCommand::maxColumnValue)); + plugin->SetParameter(plugParam.second, value); } } if(plugSetProgram.any()) @@ -1242,6 +1249,11 @@ } } } + // Do the same for dry/wet ratios + for(const auto [plug, dryWetRatio] : midiMacroEvaluationResults->pluginDryWetRatio) + { + m_MixPlugins[plug].fDryRatio = dryWetRatio; + } #endif // NO_PLUGINS } else if(adjustMode != eAdjustOnSuccess) { @@ -2268,7 +2280,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 +3383,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 +3872,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 +4823,96 @@ // 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) +void CSoundFile::ProcessMIDIMacro(PlayState &playState, CHANNELINDEX nChn, bool isSmooth, const MIDIMacroConfigData::Macro ¯o, uint8 param, PLUGINDEX plugin) { - 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.Span(), 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 = out.subspan(sendPos, sendLen); + SendMIDIData(playState, nChn, isSmooth, midiMsg, plugin); + 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 + for(size_t pos = 0; 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 +4922,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 +4932,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 +4944,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 +5020,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 +5030,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 +5061,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 +5077,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,31 +5100,28 @@ // 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) +void CSoundFile::SendMIDIData(PlayState &playState, CHANNELINDEX nChn, bool isSmooth, const mpt::span macro, PLUGINDEX plugin) { - if(macroLen < 1) - { - return 0; - } + if(macro.size() < 1) + return; + // Don't do anything that modifies state outside of the playState itself. + const bool localOnly = playState.m_midiMacroEvaluationResults.has_value(); + if(macro[0] == 0xFA || macro[0] == 0xFC || macro[0] == 0xFF) { // 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]; - if(macro[0] == 0xF0 && (macro[1] == 0xF0 || macro[1] == 0xF1)) + ModChannel &chn = playState.Chn[nChn]; + if(macro.size() == 4 && macro[0] == 0xF0 && (macro[1] == 0xF0 || macro[1] == 0xF1)) { // Internal device. - if(macroLen < 4) - { - return 0; - } const bool isExtended = (macro[1] == 0xF1); const uint8 macroCode = macro[2]; const uint8 param = macro[3]; @@ -5113,36 +5130,26 @@ { // 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); } - - return 4; } else if(macroCode == 0x01 && !isExtended && param < 0x80) { // 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]); - - return 4; } else if(macroCode == 0x02 && !isExtended) { // F0.F0.02.xx: Set filter mode (high nibble determines filter mode) @@ -5151,54 +5158,45 @@ chn.nFilterMode = static_cast(param >> 4); SetupChannelFilter(chn, !chn.dwFlags[CHN_FILTER]); } - - return 4; #ifndef NO_PLUGINS } else if(macroCode == 0x03 && !isExtended) { // F0.F0.03.xx: Set plug dry/wet - const PLUGINDEX plug = (plugin != 0) ? plugin : GetBestPlugin(nChn, PrioritiseChannel, EvenIfMuted); + PLUGINDEX plug = (plugin != 0) ? plugin : GetBestPlugin(playState, nChn, PrioritiseChannel, EvenIfMuted); if(plug > 0 && plug <= MAX_MIXPLUGINS && param < 0x80) { - const float newRatio = (0x7F - (param & 0x7F)) / 127.0f; - if(!isSmooth) - { - m_MixPlugins[plug - 1].fDryRatio = newRatio; - } else - { - m_MixPlugins[plug - 1].fDryRatio = CalculateSmoothParamChange(m_MixPlugins[plug - 1].fDryRatio, newRatio); - } + plug--; + const float newRatio = (127 - param) / 127.0f; + if(localOnly) + playState.m_midiMacroEvaluationResults->pluginDryWetRatio[plug] = newRatio; + else if(!isSmooth) + m_MixPlugins[plug].fDryRatio = newRatio; + else + m_MixPlugins[plug].fDryRatio = CalculateSmoothParamChange(playState, m_MixPlugins[plug].fDryRatio, newRatio); } - - return 4; } 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); + PLUGINDEX plug = (plugin != 0) ? plugin : GetBestPlugin(playState, nChn, PrioritiseChannel, EvenIfMuted); if(plug > 0 && plug <= MAX_MIXPLUGINS) { - IMixPlugin *pPlugin = m_MixPlugins[plug - 1].pMixPlugin; + plug--; + IMixPlugin *pPlugin = m_MixPlugins[plug].pMixPlugin; if(pPlugin && param < 0x80) { - const float fParam = param / 127.0f; - if(!isSmooth) - { - pPlugin->SetParameter(plugParam, fParam); - } else - { - pPlugin->SetParameter(plugParam, CalculateSmoothParamChange(pPlugin->GetParameter(plugParam), fParam)); - } + const PlugParamIndex plugParam = isExtended ? (0x80 + macroCode) : (macroCode & 0x7F); + const PlugParamValue value = param / 127.0f; + if(localOnly) + playState.m_midiMacroEvaluationResults->pluginParameter[{plug, plugParam}] = value; + else if(!isSmooth) + pPlugin->SetParameter(plugParam, value); + else + pPlugin->SetParameter(plugParam, CalculateSmoothParamChange(playState, pPlugin->GetParameter(plugParam), value)); } } - - return 4; #endif // NO_PLUGINS } - - // 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 +5206,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 +5216,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); } } @@ -5232,11 +5230,7 @@ #else MPT_UNREFERENCED_PARAMETER(plugin); #endif // NO_PLUGINS - - return macroLen; } - - return 0; } @@ -6165,7 +6159,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 +6171,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 +6196,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 +6208,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 +6225,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 +6249,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 +6260,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.cpp =================================================================== --- soundlib/Sndfile.cpp (revision 16701) +++ soundlib/Sndfile.cpp (working copy) @@ -70,6 +70,13 @@ } +CSoundFile::PlayState::PlayState() +{ + std::fill(std::begin(Chn), std::end(Chn), ModChannel{}); + m_midiMacroScratchSpace.reserve(kMacroLength); // Note: If macros ever become variable-length, the scratch space needs to be at least one byte longer than the longest macro in the file for end-of-SysEx insertion! +} + + ////////////////////////////////////////////////////////// // CSoundFile Index: soundlib/Sndfile.h =================================================================== --- soundlib/Sndfile.h (revision 16701) +++ soundlib/Sndfile.h (working copy) @@ -583,12 +583,18 @@ 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)! - public: - PlayState() + struct MIDIMacroEvaluationResults { - std::fill(std::begin(Chn), std::end(Chn), ModChannel()); - } + std::map pluginDryWetRatio; + std::map, PlugParamValue> pluginParameter; + }; + std::vector m_midiMacroScratchSpace; + std::optional m_midiMacroEvaluationResults; + + public: + PlayState(); + void ResetGlobalVolumeRamping() { m_lHighResRampingGlobalVolume = m_nGlobalVolume << VOLUMERAMPPRECISION; @@ -1115,9 +1121,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); + 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); + void SendMIDIData(PlayState &playState, CHANNELINDEX nChn, bool isSmooth, const mpt::span macro, PLUGINDEX plugin); void SendMIDINote(CHANNELINDEX chn, uint16 note, uint16 volume); int SetupChannelFilter(ModChannel &chn, bool bReset, int envModifier = 256) const; @@ -1243,12 +1250,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 16701) +++ 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.Global[MIDIOUT_PAN]); + //ProcessMIDIMacro(m_PlayState, nChn, false, m_MidiCfg.Global[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.SFx[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.Zxx[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) { @@ -2623,7 +2623,7 @@ if(ModCommand::IsNote(note)) realNote = pIns->NoteMap[note - NOTE_MIN]; // Experimental VST panning - //ProcessMIDIMacro(nChn, false, m_MidiCfg.szMidiGlb[MIDIOUT_PAN], 0, nPlugin); + //ProcessMIDIMacro(nChn, false, m_MidiCfg.Global[MIDIOUT_PAN], 0, nPlugin); SendMIDINote(nChn, realNote, static_cast(velocity)); }