/* * MIDIMacroDialog.cpp * ------------------- * Purpose: MIDI Macro Configuration Dialog * Notes : (currently none) * Authors: OpenMPT Devs * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. */ #include "stdafx.h" #include "../mptrack/Reporting.h" #include "../common/mptStringBuffer.h" #include "Mainfrm.h" #include "Mptrack.h" #include "resource.h" #include "MIDIMacroDialog.h" #include "../soundlib/MIDIEvents.h" #include "../soundlib/plugins/PlugInterface.h" OPENMPT_NAMESPACE_BEGIN BEGIN_MESSAGE_MAP(CMidiMacroSetup, CDialog) ON_COMMAND(IDC_BUTTON1, &CMidiMacroSetup::OnSetAsDefault) ON_COMMAND(IDC_BUTTON2, &CMidiMacroSetup::OnResetCfg) ON_COMMAND(IDC_BUTTON3, &CMidiMacroSetup::OnMacroHelp) ON_CBN_SELCHANGE(IDC_COMBO1, &CMidiMacroSetup::OnSFxChanged) ON_CBN_SELCHANGE(IDC_COMBO2, &CMidiMacroSetup::OnSFxPresetChanged) ON_CBN_SELCHANGE(IDC_COMBO3, &CMidiMacroSetup::OnZxxPresetChanged) ON_CBN_SELCHANGE(IDC_COMBO4, &CMidiMacroSetup::UpdateZxxSelection) ON_CBN_SELCHANGE(IDC_MACROPLUG, &CMidiMacroSetup::OnPlugChanged) ON_CBN_SELCHANGE(IDC_MACROPARAM,&CMidiMacroSetup::OnPlugParamChanged) 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 + kSFxMacros - 1, &CMidiMacroSetup::OnViewAllParams) ON_COMMAND_RANGE(ID_PLUGSELECT + kSFxMacros, ID_PLUGSELECT + kSFxMacros + kSFxMacros - 1, &CMidiMacroSetup::OnSetSFx) END_MESSAGE_MAP() void CMidiMacroSetup::DoDataExchange(CDataExchange* pDX) { CDialog::DoDataExchange(pDX); DDX_Control(pDX, IDC_COMBO1, m_CbnSFx); DDX_Control(pDX, IDC_COMBO2, m_CbnSFxPreset); DDX_Control(pDX, IDC_COMBO3, m_CbnZxxPreset); DDX_Control(pDX, IDC_COMBO4, m_CbnZxx); DDX_Control(pDX, IDC_EDIT1, m_EditSFx); DDX_Control(pDX, IDC_EDIT2, m_EditZxx); DDX_Control(pDX, IDC_MACROPLUG, m_CbnMacroPlug); DDX_Control(pDX, IDC_MACROPARAM, m_CbnMacroParam); DDX_Control(pDX, IDC_MACROCC, m_CbnMacroCC); } BOOL CMidiMacroSetup::OnInitDialog() { CString s; CDialog::OnInitDialog(); m_EditSFx.SetLimitText(kMacroLength - 1); m_EditZxx.SetLimitText(kMacroLength - 1); // Parametered macro selection for(int i = 0; i < 16; i++) { s.Format(_T("%d (SF%X)"), i, i); m_CbnSFx.AddString(s); } // Parametered macro presets m_CbnSFx.SetCurSel(0); for(int i = 0; i < kSFxMax; i++) { m_CbnSFxPreset.SetItemData(m_CbnSFxPreset.AddString(m_MidiCfg.GetParameteredMacroName(static_cast(i))), i); } OnSFxChanged(); // MIDI CC selection box for (int cc = MIDIEvents::MIDICC_start; cc <= MIDIEvents::MIDICC_end; cc++) { s.Format(_T("CC %02d "), cc); s += mpt::ToCString(mpt::Charset::UTF8, MIDIEvents::MidiCCNames[cc]); m_CbnMacroCC.SetItemData(m_CbnMacroCC.AddString(s), cc); } // Z80...ZFF box for(int zxx = 0x80; zxx <= 0xFF; zxx++) { s.Format(_T("Z%02X"), zxx); m_CbnZxx.AddString(s); } // Fixed macro presets m_CbnZxx.SetCurSel(0); for(int i = 0; i < kZxxMax; i++) { m_CbnZxxPreset.SetItemData(m_CbnZxxPreset.AddString(m_MidiCfg.GetFixedMacroName(static_cast(i))), i); } m_CbnZxxPreset.SetCurSel(m_MidiCfg.GetFixedMacroType()); UpdateDialog(); auto ScalePixels = [&](auto x) { return Util::ScalePixels(x, m_hWnd); }; 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 < 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 + 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 + 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 + kSFxMacros + m); m_EditMacroValue[m].SetFont(GetFont()); m_BtnMacroShowAll[m].Create(_T("Show All..."), WS_CHILD | WS_TABSTOP | WS_VISIBLE, CRect(offsetx + separatorx + widthType + widthMacro + widthVal, offsety + m * (separatory + height), offsetx + widthMacro + widthType + widthVal + widthBtn, offsety + m * (separatory + height) + height), this, ID_PLUGSELECT + m); m_BtnMacroShowAll[m].SetFont(GetFont()); } UpdateMacroList(); #ifndef NO_PLUGINS for(PLUGINDEX i = 0; i < MAX_MIXPLUGINS; i++) { const SNDMIXPLUGIN &plugin = m_SndFile.m_MixPlugins[i]; if(plugin.IsValidPlugin()) { s.Format(_T("FX%d: "), i + 1); s += mpt::ToCString(plugin.GetName()); m_CbnMacroPlug.SetItemData(m_CbnMacroPlug.AddString(s), i); } } m_CbnMacroPlug.SetCurSel(0); OnPlugChanged(); #endif // NO_PLUGINS return FALSE; } // macro == -1 for updating all macros at once void CMidiMacroSetup::UpdateMacroList(int macro) { if(!m_EditMacro[0]) { // GUI not yet initialized return; } int start, end; if(macro >= 0 && macro < kSFxMacros) { start = end = macro; } else { start = 0; end = kSFxMacros - 1; } CString s; const int selectedMacro = m_CbnSFx.GetCurSel(); for(int m = start; m <= end; m++) { // SFx s.Format(_T("SF%X"), static_cast(m)); m_EditMacro[m].SetWindowText(s); // Macro value: 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: const ParameteredMacro macroType = m_MidiCfg.GetParameteredMacroType(m); switch(macroType) { case kSFxPlugParam: s.Format(_T("Control Plugin Param %u"), static_cast(m_MidiCfg.MacroToPlugParam(m))); break; default: s = m_MidiCfg.GetParameteredMacroName(m); break; } m_EditMacroType[m].SetWindowText(s); m_EditMacroType[m].SetBackColor(m == selectedMacro ? RGB(200,200,225) : RGB(245,245,245)); // Param details button: m_BtnMacroShowAll[m].ShowWindow((macroType == kSFxPlugParam) ? SW_SHOW : SW_HIDE); } } void CMidiMacroSetup::UpdateDialog() { UINT sfx = m_CbnSFx.GetCurSel(); UINT sfx_preset = static_cast(m_CbnSFxPreset.GetItemData(m_CbnSFxPreset.GetCurSel())); if(sfx < m_MidiCfg.SFx.size()) { ToggleBoxes(sfx_preset, sfx); m_EditSFx.SetWindowText(mpt::ToCString(mpt::Charset::ASCII, m_MidiCfg.SFx[sfx])); } UpdateZxxSelection(); UpdateMacroList(); } void CMidiMacroSetup::OnSetAsDefault() { theApp.SetDefaultMidiMacro(m_MidiCfg); } void CMidiMacroSetup::OnResetCfg() { theApp.GetDefaultMidiMacro(m_MidiCfg); m_CbnZxxPreset.SetCurSel(0); OnSFxChanged(); } void CMidiMacroSetup::OnMacroHelp() { Reporting::Information(_T("Valid characters in macros:\n\n" "0-9, A-F - Raw hex data (4-Bit value)\n" "c - MIDI channel (4-Bit value)\n" "n - Note value\n\n" "v - Note velocity\n" "u - Computed note volume (including envelopes)\n\n" "x - Note panning\n" "y - Computed panning (including envelopes)\n\n" "a - High byte of bank select\n" "b - Low byte of bank select\n" "p - Program select\n\n" "h - Pattern channel\n" "m - Sample loop direction\n" "o - Last sample offset (Oxx / 9xx)\n" "s - SysEx checksum (Roland)\n\n" "z - Zxx parameter (00-7F)\n\n" "Macros can be up to 31 characters long and contain multiple MIDI messages. SysEx messages are automatically terminated if not specified by the user."), _T("OpenMPT MIDI Macro quick reference")); } void CMidiMacroSetup::OnSFxChanged() { UINT sfx = m_CbnSFx.GetCurSel(); if (sfx < 16) { int preset = m_MidiCfg.GetParameteredMacroType(sfx); m_CbnSFxPreset.SetCurSel(preset); } UpdateDialog(); } void CMidiMacroSetup::OnSFxPresetChanged() { UINT sfx = m_CbnSFx.GetCurSel(); ParameteredMacro sfx_preset = static_cast(m_CbnSFxPreset.GetItemData(m_CbnSFxPreset.GetCurSel())); if (sfx < kSFxMacros) { if(sfx_preset != kSFxCustom) { m_MidiCfg.CreateParameteredMacro(sfx, sfx_preset); } UpdateDialog(); } } void CMidiMacroSetup::OnZxxPresetChanged() { FixedMacro zxxPreset = static_cast(m_CbnZxxPreset.GetItemData(m_CbnZxxPreset.GetCurSel())); if (zxxPreset != kZxxCustom) { m_MidiCfg.CreateFixedMacro(zxxPreset); UpdateDialog(); } } void CMidiMacroSetup::UpdateZxxSelection() { UINT zxx = m_CbnZxx.GetCurSel(); if(zxx < m_MidiCfg.Zxx.size()) { m_EditZxx.SetWindowText(mpt::ToCString(mpt::Charset::ASCII, m_MidiCfg.Zxx[zxx])); } } void CMidiMacroSetup::OnSFxEditChanged() { UINT sfx = m_CbnSFx.GetCurSel(); if(sfx < m_MidiCfg.SFx.size()) { if(ValidateMacroString(m_EditSFx, m_MidiCfg.SFx[sfx], true)) { CString s; m_EditSFx.GetWindowText(s); m_MidiCfg.SFx[sfx] = mpt::ToCharset(mpt::Charset::ASCII, s); int sfx_preset = m_MidiCfg.GetParameteredMacroType(sfx); m_CbnSFxPreset.SetCurSel(sfx_preset); ToggleBoxes(sfx_preset, sfx); UpdateMacroList(sfx); } } } void CMidiMacroSetup::OnZxxEditChanged() { UINT zxx = m_CbnZxx.GetCurSel(); if(zxx < m_MidiCfg.Zxx.size()) { if(ValidateMacroString(m_EditZxx, m_MidiCfg.Zxx[zxx], false)) { CString s; m_EditZxx.GetWindowText(s); m_MidiCfg.Zxx[zxx] = mpt::ToCharset(mpt::Charset::ASCII, s); m_CbnZxxPreset.SetCurSel(m_MidiCfg.GetFixedMacroType()); } } } void CMidiMacroSetup::OnSetSFx(UINT id) { m_CbnSFx.SetCurSel(id - (ID_PLUGSELECT + kSFxMacros)); OnSFxChanged(); } void CMidiMacroSetup::OnViewAllParams(UINT id) { #ifndef NO_PLUGINS CString message, plugName; int sfx = id - ID_PLUGSELECT; PlugParamIndex param = m_MidiCfg.MacroToPlugParam(sfx); message.Format(_T("These are the parameters that can be controlled by macro SF%X:\n\n"), sfx); for(PLUGINDEX plug = 0; plug < MAX_MIXPLUGINS; plug++) { IMixPlugin *pVstPlugin = m_SndFile.m_MixPlugins[plug].pMixPlugin; if(pVstPlugin && param < pVstPlugin->GetNumParameters()) { plugName = mpt::ToCString(m_SndFile.m_MixPlugins[plug].GetName()); message.AppendFormat(_T("FX%d: "), plug + 1); message += plugName + _T("\t") + pVstPlugin->GetFormattedParamName(param) + _T("\n"); } } Reporting::Notification(message, _T("Macro -> Parameters")); #endif // NO_PLUGINS } void CMidiMacroSetup::OnPlugChanged() { #ifndef NO_PLUGINS DWORD_PTR plug = m_CbnMacroPlug.GetItemData(m_CbnMacroPlug.GetCurSel()); if(plug >= MAX_MIXPLUGINS) return; IMixPlugin *pVstPlugin = m_SndFile.m_MixPlugins[plug].pMixPlugin; if (pVstPlugin != nullptr) { m_CbnMacroParam.SetRedraw(FALSE); m_CbnMacroParam.Clear(); m_CbnMacroParam.ResetContent(); AddPluginParameternamesToCombobox(m_CbnMacroParam, *pVstPlugin); m_CbnMacroParam.SetRedraw(TRUE); int param = m_MidiCfg.MacroToPlugParam(m_CbnSFx.GetCurSel()); m_CbnMacroParam.SetCurSel(param); } #endif // NO_PLUGINS } void CMidiMacroSetup::OnPlugParamChanged() { int param = static_cast(m_CbnMacroParam.GetItemData(m_CbnMacroParam.GetCurSel())); if(param < 384) { const std::string macroText = m_MidiCfg.CreateParameteredMacro(kSFxPlugParam, param); m_EditSFx.SetWindowText(mpt::ToCString(mpt::Charset::ASCII, macroText)); } else { Reporting::Notification("Only parameters 0 to 383 can be controlled using MIDI Macros. Use Parameter Control Events to automate higher parameters."); } } void CMidiMacroSetup::OnCCChanged() { int cc = static_cast(m_CbnMacroCC.GetItemData(m_CbnMacroCC.GetCurSel())); const std::string macroText = m_MidiCfg.CreateParameteredMacro(kSFxCC, cc); m_EditSFx.SetWindowText(mpt::ToCString(mpt::Charset::ASCII, macroText)); } void CMidiMacroSetup::ToggleBoxes(UINT sfxPreset, UINT sfx) { if (sfxPreset == kSFxPlugParam) { m_CbnMacroCC.ShowWindow(FALSE); m_CbnMacroPlug.ShowWindow(TRUE); m_CbnMacroParam.ShowWindow(TRUE); m_CbnMacroPlug.EnableWindow(TRUE); m_CbnMacroParam.EnableWindow(TRUE); SetDlgItemText(IDC_GENMACROLABEL, _T("Plugin/Param")); m_CbnMacroParam.SetCurSel(m_MidiCfg.MacroToPlugParam(sfx)); } else { m_CbnMacroPlug.EnableWindow(FALSE); m_CbnMacroParam.EnableWindow(FALSE); } if (sfxPreset == kSFxCC) { m_CbnMacroCC.EnableWindow(TRUE); m_CbnMacroCC.ShowWindow(TRUE); m_CbnMacroPlug.ShowWindow(FALSE); m_CbnMacroParam.ShowWindow(FALSE); SetDlgItemText(IDC_GENMACROLABEL, _T("MIDI CC")); m_CbnMacroCC.SetCurSel(m_MidiCfg.MacroToMidiCC(sfx)); } else { m_CbnMacroCC.EnableWindow(FALSE); } } bool CMidiMacroSetup::ValidateMacroString(CEdit &wnd, const MIDIMacroConfig::Macro &prevMacro, bool isParametric) { CString macroStrT; wnd.GetWindowText(macroStrT); std::string macroStr = mpt::ToCharset(mpt::Charset::ASCII, macroStrT); bool allowed = true, caseChange = false; for(char &c : macroStr) { 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 { caseChange = true; c = c - 'a' + 'A'; } else if(c == 'M' || c == 'N' || c == 'O' || c == 'P' || c == 'S' || c == 'U' || c == 'V' || c == 'X' || c == 'Y' || c == 'Z') { caseChange = true; c = c - 'A' + 'a'; } else if(!( (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'c') || (c == 'h' || c == 'm' || c == 'n' || c == 'o' || c == 'p' || c == 's' ||c == 'u' || c == 'v' || c == 'x' || c == 'y' || c == ' ') || (c == 'z' && isParametric))) { allowed = false; break; } } if(!allowed) { // Replace text and keep cursor position if we just typed in an invalid character if(prevMacro != std::string_view{macroStr}) { int start, end; wnd.GetSel(start, end); wnd.SetWindowText(mpt::ToCString(mpt::Charset::ASCII, prevMacro)); wnd.SetSel(start - 1, end - 1, true); MessageBeep(MB_OK); } return false; } else { if(caseChange) { // Replace text and keep cursor position if there was a case conversion int start, end; wnd.GetSel(start, end); wnd.SetWindowText(mpt::ToCString(mpt::Charset::ASCII, macroStr)); wnd.SetSel(start, end, true); } return true; } } OPENMPT_NAMESPACE_END