MIDIMacroDialog.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503
  1. /*
  2. * MIDIMacroDialog.cpp
  3. * -------------------
  4. * Purpose: MIDI Macro Configuration Dialog
  5. * Notes : (currently none)
  6. * Authors: OpenMPT Devs
  7. * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
  8. */
  9. #include "stdafx.h"
  10. #include "../mptrack/Reporting.h"
  11. #include "../common/mptStringBuffer.h"
  12. #include "Mainfrm.h"
  13. #include "Mptrack.h"
  14. #include "resource.h"
  15. #include "MIDIMacroDialog.h"
  16. #include "../soundlib/MIDIEvents.h"
  17. #include "../soundlib/plugins/PlugInterface.h"
  18. OPENMPT_NAMESPACE_BEGIN
  19. BEGIN_MESSAGE_MAP(CMidiMacroSetup, CDialog)
  20. ON_COMMAND(IDC_BUTTON1, &CMidiMacroSetup::OnSetAsDefault)
  21. ON_COMMAND(IDC_BUTTON2, &CMidiMacroSetup::OnResetCfg)
  22. ON_COMMAND(IDC_BUTTON3, &CMidiMacroSetup::OnMacroHelp)
  23. ON_CBN_SELCHANGE(IDC_COMBO1, &CMidiMacroSetup::OnSFxChanged)
  24. ON_CBN_SELCHANGE(IDC_COMBO2, &CMidiMacroSetup::OnSFxPresetChanged)
  25. ON_CBN_SELCHANGE(IDC_COMBO3, &CMidiMacroSetup::OnZxxPresetChanged)
  26. ON_CBN_SELCHANGE(IDC_COMBO4, &CMidiMacroSetup::UpdateZxxSelection)
  27. ON_CBN_SELCHANGE(IDC_MACROPLUG, &CMidiMacroSetup::OnPlugChanged)
  28. ON_CBN_SELCHANGE(IDC_MACROPARAM,&CMidiMacroSetup::OnPlugParamChanged)
  29. ON_CBN_SELCHANGE(IDC_MACROCC, &CMidiMacroSetup::OnCCChanged)
  30. ON_EN_CHANGE(IDC_EDIT1, &CMidiMacroSetup::OnSFxEditChanged)
  31. ON_EN_CHANGE(IDC_EDIT2, &CMidiMacroSetup::OnZxxEditChanged)
  32. ON_COMMAND_RANGE(ID_PLUGSELECT, ID_PLUGSELECT + kSFxMacros - 1, &CMidiMacroSetup::OnViewAllParams)
  33. ON_COMMAND_RANGE(ID_PLUGSELECT + kSFxMacros, ID_PLUGSELECT + kSFxMacros + kSFxMacros - 1, &CMidiMacroSetup::OnSetSFx)
  34. END_MESSAGE_MAP()
  35. void CMidiMacroSetup::DoDataExchange(CDataExchange* pDX)
  36. {
  37. CDialog::DoDataExchange(pDX);
  38. DDX_Control(pDX, IDC_COMBO1, m_CbnSFx);
  39. DDX_Control(pDX, IDC_COMBO2, m_CbnSFxPreset);
  40. DDX_Control(pDX, IDC_COMBO3, m_CbnZxxPreset);
  41. DDX_Control(pDX, IDC_COMBO4, m_CbnZxx);
  42. DDX_Control(pDX, IDC_EDIT1, m_EditSFx);
  43. DDX_Control(pDX, IDC_EDIT2, m_EditZxx);
  44. DDX_Control(pDX, IDC_MACROPLUG, m_CbnMacroPlug);
  45. DDX_Control(pDX, IDC_MACROPARAM, m_CbnMacroParam);
  46. DDX_Control(pDX, IDC_MACROCC, m_CbnMacroCC);
  47. }
  48. BOOL CMidiMacroSetup::OnInitDialog()
  49. {
  50. CString s;
  51. CDialog::OnInitDialog();
  52. m_EditSFx.SetLimitText(kMacroLength - 1);
  53. m_EditZxx.SetLimitText(kMacroLength - 1);
  54. // Parametered macro selection
  55. for(int i = 0; i < 16; i++)
  56. {
  57. s.Format(_T("%d (SF%X)"), i, i);
  58. m_CbnSFx.AddString(s);
  59. }
  60. // Parametered macro presets
  61. m_CbnSFx.SetCurSel(0);
  62. for(int i = 0; i < kSFxMax; i++)
  63. {
  64. m_CbnSFxPreset.SetItemData(m_CbnSFxPreset.AddString(m_MidiCfg.GetParameteredMacroName(static_cast<ParameteredMacro>(i))), i);
  65. }
  66. OnSFxChanged();
  67. // MIDI CC selection box
  68. for (int cc = MIDIEvents::MIDICC_start; cc <= MIDIEvents::MIDICC_end; cc++)
  69. {
  70. s.Format(_T("CC %02d "), cc);
  71. s += mpt::ToCString(mpt::Charset::UTF8, MIDIEvents::MidiCCNames[cc]);
  72. m_CbnMacroCC.SetItemData(m_CbnMacroCC.AddString(s), cc);
  73. }
  74. // Z80...ZFF box
  75. for(int zxx = 0x80; zxx <= 0xFF; zxx++)
  76. {
  77. s.Format(_T("Z%02X"), zxx);
  78. m_CbnZxx.AddString(s);
  79. }
  80. // Fixed macro presets
  81. m_CbnZxx.SetCurSel(0);
  82. for(int i = 0; i < kZxxMax; i++)
  83. {
  84. m_CbnZxxPreset.SetItemData(m_CbnZxxPreset.AddString(m_MidiCfg.GetFixedMacroName(static_cast<FixedMacro>(i))), i);
  85. }
  86. m_CbnZxxPreset.SetCurSel(m_MidiCfg.GetFixedMacroType());
  87. UpdateDialog();
  88. auto ScalePixels = [&](auto x) { return Util::ScalePixels(x, m_hWnd); };
  89. int offsetx = ScalePixels(19), offsety = ScalePixels(30), separatorx = ScalePixels(4), separatory = ScalePixels(2);
  90. int height = ScalePixels(18), widthMacro = ScalePixels(30), widthVal = ScalePixels(179), widthType = ScalePixels(135), widthBtn = ScalePixels(70);
  91. for(UINT m = 0; m < kSFxMacros; m++)
  92. {
  93. m_EditMacro[m].Create(_T(""), WS_CHILD | WS_VISIBLE | WS_TABSTOP,
  94. CRect(offsetx, offsety + m * (separatory + height), offsetx + widthMacro, offsety + m * (separatory + height) + height), this, ID_PLUGSELECT + kSFxMacros + m);
  95. m_EditMacro[m].SetFont(GetFont());
  96. m_EditMacroType[m].Create(ES_READONLY | WS_CHILD| WS_VISIBLE | WS_TABSTOP | WS_BORDER,
  97. CRect(offsetx + separatorx + widthMacro, offsety + m * (separatory + height), offsetx + widthMacro + widthType, offsety + m * (separatory + height) + height), this, ID_PLUGSELECT + kSFxMacros + m);
  98. m_EditMacroType[m].SetFont(GetFont());
  99. m_EditMacroValue[m].Create(ES_CENTER | ES_READONLY | WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_BORDER,
  100. CRect(offsetx + separatorx + widthType + widthMacro, offsety + m * (separatory + height), offsetx + widthMacro + widthType + widthVal, offsety + m * (separatory + height) + height), this, ID_PLUGSELECT + kSFxMacros + m);
  101. m_EditMacroValue[m].SetFont(GetFont());
  102. m_BtnMacroShowAll[m].Create(_T("Show All..."), WS_CHILD | WS_TABSTOP | WS_VISIBLE,
  103. CRect(offsetx + separatorx + widthType + widthMacro + widthVal, offsety + m * (separatory + height), offsetx + widthMacro + widthType + widthVal + widthBtn, offsety + m * (separatory + height) + height), this, ID_PLUGSELECT + m);
  104. m_BtnMacroShowAll[m].SetFont(GetFont());
  105. }
  106. UpdateMacroList();
  107. #ifndef NO_PLUGINS
  108. for(PLUGINDEX i = 0; i < MAX_MIXPLUGINS; i++)
  109. {
  110. const SNDMIXPLUGIN &plugin = m_SndFile.m_MixPlugins[i];
  111. if(plugin.IsValidPlugin())
  112. {
  113. s.Format(_T("FX%d: "), i + 1);
  114. s += mpt::ToCString(plugin.GetName());
  115. m_CbnMacroPlug.SetItemData(m_CbnMacroPlug.AddString(s), i);
  116. }
  117. }
  118. m_CbnMacroPlug.SetCurSel(0);
  119. OnPlugChanged();
  120. #endif // NO_PLUGINS
  121. return FALSE;
  122. }
  123. // macro == -1 for updating all macros at once
  124. void CMidiMacroSetup::UpdateMacroList(int macro)
  125. {
  126. if(!m_EditMacro[0])
  127. {
  128. // GUI not yet initialized
  129. return;
  130. }
  131. int start, end;
  132. if(macro >= 0 && macro < kSFxMacros)
  133. {
  134. start = end = macro;
  135. } else
  136. {
  137. start = 0;
  138. end = kSFxMacros - 1;
  139. }
  140. CString s;
  141. const int selectedMacro = m_CbnSFx.GetCurSel();
  142. for(int m = start; m <= end; m++)
  143. {
  144. // SFx
  145. s.Format(_T("SF%X"), static_cast<unsigned int>(m));
  146. m_EditMacro[m].SetWindowText(s);
  147. // Macro value:
  148. m_EditMacroValue[m].SetWindowText(mpt::ToCString(mpt::Charset::ASCII, m_MidiCfg.SFx[m]));
  149. m_EditMacroValue[m].SetBackColor(m == selectedMacro ? RGB(200, 200, 225) : RGB(245, 245, 245));
  150. // Macro Type:
  151. const ParameteredMacro macroType = m_MidiCfg.GetParameteredMacroType(m);
  152. switch(macroType)
  153. {
  154. case kSFxPlugParam:
  155. s.Format(_T("Control Plugin Param %u"), static_cast<unsigned int>(m_MidiCfg.MacroToPlugParam(m)));
  156. break;
  157. default:
  158. s = m_MidiCfg.GetParameteredMacroName(m);
  159. break;
  160. }
  161. m_EditMacroType[m].SetWindowText(s);
  162. m_EditMacroType[m].SetBackColor(m == selectedMacro ? RGB(200,200,225) : RGB(245,245,245));
  163. // Param details button:
  164. m_BtnMacroShowAll[m].ShowWindow((macroType == kSFxPlugParam) ? SW_SHOW : SW_HIDE);
  165. }
  166. }
  167. void CMidiMacroSetup::UpdateDialog()
  168. {
  169. UINT sfx = m_CbnSFx.GetCurSel();
  170. UINT sfx_preset = static_cast<UINT>(m_CbnSFxPreset.GetItemData(m_CbnSFxPreset.GetCurSel()));
  171. if(sfx < m_MidiCfg.SFx.size())
  172. {
  173. ToggleBoxes(sfx_preset, sfx);
  174. m_EditSFx.SetWindowText(mpt::ToCString(mpt::Charset::ASCII, m_MidiCfg.SFx[sfx]));
  175. }
  176. UpdateZxxSelection();
  177. UpdateMacroList();
  178. }
  179. void CMidiMacroSetup::OnSetAsDefault()
  180. {
  181. theApp.SetDefaultMidiMacro(m_MidiCfg);
  182. }
  183. void CMidiMacroSetup::OnResetCfg()
  184. {
  185. theApp.GetDefaultMidiMacro(m_MidiCfg);
  186. m_CbnZxxPreset.SetCurSel(0);
  187. OnSFxChanged();
  188. }
  189. void CMidiMacroSetup::OnMacroHelp()
  190. {
  191. Reporting::Information(_T("Valid characters in macros:\n\n"
  192. "0-9, A-F - Raw hex data (4-Bit value)\n"
  193. "c - MIDI channel (4-Bit value)\n"
  194. "n - Note value\n\n"
  195. "v - Note velocity\n"
  196. "u - Computed note volume (including envelopes)\n\n"
  197. "x - Note panning\n"
  198. "y - Computed panning (including envelopes)\n\n"
  199. "a - High byte of bank select\n"
  200. "b - Low byte of bank select\n"
  201. "p - Program select\n\n"
  202. "h - Pattern channel\n"
  203. "m - Sample loop direction\n"
  204. "o - Last sample offset (Oxx / 9xx)\n"
  205. "s - SysEx checksum (Roland)\n\n"
  206. "z - Zxx parameter (00-7F)\n\n"
  207. "Macros can be up to 31 characters long and contain multiple MIDI messages. SysEx messages are automatically terminated if not specified by the user."),
  208. _T("OpenMPT MIDI Macro quick reference"));
  209. }
  210. void CMidiMacroSetup::OnSFxChanged()
  211. {
  212. UINT sfx = m_CbnSFx.GetCurSel();
  213. if (sfx < 16)
  214. {
  215. int preset = m_MidiCfg.GetParameteredMacroType(sfx);
  216. m_CbnSFxPreset.SetCurSel(preset);
  217. }
  218. UpdateDialog();
  219. }
  220. void CMidiMacroSetup::OnSFxPresetChanged()
  221. {
  222. UINT sfx = m_CbnSFx.GetCurSel();
  223. ParameteredMacro sfx_preset = static_cast<ParameteredMacro>(m_CbnSFxPreset.GetItemData(m_CbnSFxPreset.GetCurSel()));
  224. if (sfx < kSFxMacros)
  225. {
  226. if(sfx_preset != kSFxCustom)
  227. {
  228. m_MidiCfg.CreateParameteredMacro(sfx, sfx_preset);
  229. }
  230. UpdateDialog();
  231. }
  232. }
  233. void CMidiMacroSetup::OnZxxPresetChanged()
  234. {
  235. FixedMacro zxxPreset = static_cast<FixedMacro>(m_CbnZxxPreset.GetItemData(m_CbnZxxPreset.GetCurSel()));
  236. if (zxxPreset != kZxxCustom)
  237. {
  238. m_MidiCfg.CreateFixedMacro(zxxPreset);
  239. UpdateDialog();
  240. }
  241. }
  242. void CMidiMacroSetup::UpdateZxxSelection()
  243. {
  244. UINT zxx = m_CbnZxx.GetCurSel();
  245. if(zxx < m_MidiCfg.Zxx.size())
  246. {
  247. m_EditZxx.SetWindowText(mpt::ToCString(mpt::Charset::ASCII, m_MidiCfg.Zxx[zxx]));
  248. }
  249. }
  250. void CMidiMacroSetup::OnSFxEditChanged()
  251. {
  252. UINT sfx = m_CbnSFx.GetCurSel();
  253. if(sfx < m_MidiCfg.SFx.size())
  254. {
  255. if(ValidateMacroString(m_EditSFx, m_MidiCfg.SFx[sfx], true))
  256. {
  257. CString s;
  258. m_EditSFx.GetWindowText(s);
  259. m_MidiCfg.SFx[sfx] = mpt::ToCharset(mpt::Charset::ASCII, s);
  260. int sfx_preset = m_MidiCfg.GetParameteredMacroType(sfx);
  261. m_CbnSFxPreset.SetCurSel(sfx_preset);
  262. ToggleBoxes(sfx_preset, sfx);
  263. UpdateMacroList(sfx);
  264. }
  265. }
  266. }
  267. void CMidiMacroSetup::OnZxxEditChanged()
  268. {
  269. UINT zxx = m_CbnZxx.GetCurSel();
  270. if(zxx < m_MidiCfg.Zxx.size())
  271. {
  272. if(ValidateMacroString(m_EditZxx, m_MidiCfg.Zxx[zxx], false))
  273. {
  274. CString s;
  275. m_EditZxx.GetWindowText(s);
  276. m_MidiCfg.Zxx[zxx] = mpt::ToCharset(mpt::Charset::ASCII, s);
  277. m_CbnZxxPreset.SetCurSel(m_MidiCfg.GetFixedMacroType());
  278. }
  279. }
  280. }
  281. void CMidiMacroSetup::OnSetSFx(UINT id)
  282. {
  283. m_CbnSFx.SetCurSel(id - (ID_PLUGSELECT + kSFxMacros));
  284. OnSFxChanged();
  285. }
  286. void CMidiMacroSetup::OnViewAllParams(UINT id)
  287. {
  288. #ifndef NO_PLUGINS
  289. CString message, plugName;
  290. int sfx = id - ID_PLUGSELECT;
  291. PlugParamIndex param = m_MidiCfg.MacroToPlugParam(sfx);
  292. message.Format(_T("These are the parameters that can be controlled by macro SF%X:\n\n"), sfx);
  293. for(PLUGINDEX plug = 0; plug < MAX_MIXPLUGINS; plug++)
  294. {
  295. IMixPlugin *pVstPlugin = m_SndFile.m_MixPlugins[plug].pMixPlugin;
  296. if(pVstPlugin && param < pVstPlugin->GetNumParameters())
  297. {
  298. plugName = mpt::ToCString(m_SndFile.m_MixPlugins[plug].GetName());
  299. message.AppendFormat(_T("FX%d: "), plug + 1);
  300. message += plugName + _T("\t") + pVstPlugin->GetFormattedParamName(param) + _T("\n");
  301. }
  302. }
  303. Reporting::Notification(message, _T("Macro -> Parameters"));
  304. #endif // NO_PLUGINS
  305. }
  306. void CMidiMacroSetup::OnPlugChanged()
  307. {
  308. #ifndef NO_PLUGINS
  309. DWORD_PTR plug = m_CbnMacroPlug.GetItemData(m_CbnMacroPlug.GetCurSel());
  310. if(plug >= MAX_MIXPLUGINS)
  311. return;
  312. IMixPlugin *pVstPlugin = m_SndFile.m_MixPlugins[plug].pMixPlugin;
  313. if (pVstPlugin != nullptr)
  314. {
  315. m_CbnMacroParam.SetRedraw(FALSE);
  316. m_CbnMacroParam.Clear();
  317. m_CbnMacroParam.ResetContent();
  318. AddPluginParameternamesToCombobox(m_CbnMacroParam, *pVstPlugin);
  319. m_CbnMacroParam.SetRedraw(TRUE);
  320. int param = m_MidiCfg.MacroToPlugParam(m_CbnSFx.GetCurSel());
  321. m_CbnMacroParam.SetCurSel(param);
  322. }
  323. #endif // NO_PLUGINS
  324. }
  325. void CMidiMacroSetup::OnPlugParamChanged()
  326. {
  327. int param = static_cast<int>(m_CbnMacroParam.GetItemData(m_CbnMacroParam.GetCurSel()));
  328. if(param < 384)
  329. {
  330. const std::string macroText = m_MidiCfg.CreateParameteredMacro(kSFxPlugParam, param);
  331. m_EditSFx.SetWindowText(mpt::ToCString(mpt::Charset::ASCII, macroText));
  332. } else
  333. {
  334. Reporting::Notification("Only parameters 0 to 383 can be controlled using MIDI Macros. Use Parameter Control Events to automate higher parameters.");
  335. }
  336. }
  337. void CMidiMacroSetup::OnCCChanged()
  338. {
  339. int cc = static_cast<int>(m_CbnMacroCC.GetItemData(m_CbnMacroCC.GetCurSel()));
  340. const std::string macroText = m_MidiCfg.CreateParameteredMacro(kSFxCC, cc);
  341. m_EditSFx.SetWindowText(mpt::ToCString(mpt::Charset::ASCII, macroText));
  342. }
  343. void CMidiMacroSetup::ToggleBoxes(UINT sfxPreset, UINT sfx)
  344. {
  345. if (sfxPreset == kSFxPlugParam)
  346. {
  347. m_CbnMacroCC.ShowWindow(FALSE);
  348. m_CbnMacroPlug.ShowWindow(TRUE);
  349. m_CbnMacroParam.ShowWindow(TRUE);
  350. m_CbnMacroPlug.EnableWindow(TRUE);
  351. m_CbnMacroParam.EnableWindow(TRUE);
  352. SetDlgItemText(IDC_GENMACROLABEL, _T("Plugin/Param"));
  353. m_CbnMacroParam.SetCurSel(m_MidiCfg.MacroToPlugParam(sfx));
  354. } else
  355. {
  356. m_CbnMacroPlug.EnableWindow(FALSE);
  357. m_CbnMacroParam.EnableWindow(FALSE);
  358. }
  359. if (sfxPreset == kSFxCC)
  360. {
  361. m_CbnMacroCC.EnableWindow(TRUE);
  362. m_CbnMacroCC.ShowWindow(TRUE);
  363. m_CbnMacroPlug.ShowWindow(FALSE);
  364. m_CbnMacroParam.ShowWindow(FALSE);
  365. SetDlgItemText(IDC_GENMACROLABEL, _T("MIDI CC"));
  366. m_CbnMacroCC.SetCurSel(m_MidiCfg.MacroToMidiCC(sfx));
  367. } else
  368. {
  369. m_CbnMacroCC.EnableWindow(FALSE);
  370. }
  371. }
  372. bool CMidiMacroSetup::ValidateMacroString(CEdit &wnd, const MIDIMacroConfig::Macro &prevMacro, bool isParametric)
  373. {
  374. CString macroStrT;
  375. wnd.GetWindowText(macroStrT);
  376. std::string macroStr = mpt::ToCharset(mpt::Charset::ASCII, macroStrT);
  377. bool allowed = true, caseChange = false;
  378. for(char &c : macroStr)
  379. {
  380. if(c == 'k' || c == 'K') // Previously, 'K' was used for MIDI channel
  381. {
  382. caseChange = true;
  383. c = 'c';
  384. } else if(c >= 'd' && c <= 'f') // abc have special meanings, but def can be fixed
  385. {
  386. caseChange = true;
  387. c = c - 'a' + 'A';
  388. } else if(c == 'M' || c == 'N' || c == 'O' || c == 'P' || c == 'S' || c == 'U' || c == 'V' || c == 'X' || c == 'Y' || c == 'Z')
  389. {
  390. caseChange = true;
  391. c = c - 'A' + 'a';
  392. } else if(!(
  393. (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'c') ||
  394. (c == 'h' || c == 'm' || c == 'n' || c == 'o' || c == 'p' || c == 's' ||c == 'u' || c == 'v' || c == 'x' || c == 'y' || c == ' ') ||
  395. (c == 'z' && isParametric)))
  396. {
  397. allowed = false;
  398. break;
  399. }
  400. }
  401. if(!allowed)
  402. {
  403. // Replace text and keep cursor position if we just typed in an invalid character
  404. if(prevMacro != std::string_view{macroStr})
  405. {
  406. int start, end;
  407. wnd.GetSel(start, end);
  408. wnd.SetWindowText(mpt::ToCString(mpt::Charset::ASCII, prevMacro));
  409. wnd.SetSel(start - 1, end - 1, true);
  410. MessageBeep(MB_OK);
  411. }
  412. return false;
  413. } else
  414. {
  415. if(caseChange)
  416. {
  417. // Replace text and keep cursor position if there was a case conversion
  418. int start, end;
  419. wnd.GetSel(start, end);
  420. wnd.SetWindowText(mpt::ToCString(mpt::Charset::ASCII, macroStr));
  421. wnd.SetSel(start, end, true);
  422. }
  423. return true;
  424. }
  425. }
  426. OPENMPT_NAMESPACE_END