MIDIMappingDialog.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506
  1. /*
  2. * MIDIMappingDialog.cpp
  3. * ---------------------
  4. * Purpose: Implementation of OpenMPT's MIDI mapping dialog, for mapping incoming MIDI messages to plugin parameters.
  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 "Mainfrm.h"
  11. #include "Moddoc.h"
  12. #include "MIDIMappingDialog.h"
  13. #include "InputHandler.h"
  14. #include "../soundlib/MIDIEvents.h"
  15. #include "../soundlib/mod_specifications.h"
  16. #include "../soundlib/plugins/PlugInterface.h"
  17. #include "../common/mptStringBuffer.h"
  18. #ifndef NO_PLUGINS
  19. OPENMPT_NAMESPACE_BEGIN
  20. CMIDIMappingDialog::CMIDIMappingDialog(CWnd *pParent, CSoundFile &rSndfile)
  21. : CDialog(IDD_MIDIPARAMCONTROL, pParent)
  22. , m_sndFile(rSndfile)
  23. , m_rMIDIMapper(m_sndFile.GetMIDIMapper())
  24. {
  25. CMainFrame::GetInputHandler()->Bypass(true);
  26. oldMIDIRecondWnd = CMainFrame::GetMainFrame()->GetMidiRecordWnd();
  27. }
  28. CMIDIMappingDialog::~CMIDIMappingDialog()
  29. {
  30. CMainFrame::GetMainFrame()->SetMidiRecordWnd(oldMIDIRecondWnd);
  31. CMainFrame::GetInputHandler()->Bypass(false);
  32. }
  33. void CMIDIMappingDialog::DoDataExchange(CDataExchange* pDX)
  34. {
  35. CDialog::DoDataExchange(pDX);
  36. DDX_Control(pDX, IDC_COMBO_CONTROLLER, m_ControllerCBox);
  37. DDX_Control(pDX, IDC_COMBO_PLUGIN, m_PluginCBox);
  38. DDX_Control(pDX, IDC_COMBO_PARAM, m_PlugParamCBox);
  39. DDX_Control(pDX, IDC_LIST1, m_List);
  40. DDX_Control(pDX, IDC_COMBO_CHANNEL, m_ChannelCBox);
  41. DDX_Control(pDX, IDC_COMBO_EVENT, m_EventCBox);
  42. DDX_Control(pDX, IDC_SPINMOVEMAPPING, m_SpinMoveMapping);
  43. }
  44. BEGIN_MESSAGE_MAP(CMIDIMappingDialog, CDialog)
  45. ON_NOTIFY(LVN_ITEMCHANGED, IDC_LIST1, &CMIDIMappingDialog::OnSelectionChanged)
  46. ON_BN_CLICKED(IDC_CHECKACTIVE, &CMIDIMappingDialog::OnBnClickedCheckactive)
  47. ON_BN_CLICKED(IDC_CHECKCAPTURE, &CMIDIMappingDialog::OnBnClickedCheckCapture)
  48. ON_CBN_SELCHANGE(IDC_COMBO_CONTROLLER, &CMIDIMappingDialog::OnCbnSelchangeComboController)
  49. ON_CBN_SELCHANGE(IDC_COMBO_CHANNEL, &CMIDIMappingDialog::OnCbnSelchangeComboChannel)
  50. ON_CBN_SELCHANGE(IDC_COMBO_PLUGIN, &CMIDIMappingDialog::OnCbnSelchangeComboPlugin)
  51. ON_CBN_SELCHANGE(IDC_COMBO_PARAM, &CMIDIMappingDialog::OnCbnSelchangeComboParam)
  52. ON_CBN_SELCHANGE(IDC_COMBO_EVENT, &CMIDIMappingDialog::OnCbnSelchangeComboEvent)
  53. ON_BN_CLICKED(IDC_BUTTON_ADD, &CMIDIMappingDialog::OnBnClickedButtonAdd)
  54. ON_BN_CLICKED(IDC_BUTTON_REPLACE, &CMIDIMappingDialog::OnBnClickedButtonReplace)
  55. ON_BN_CLICKED(IDC_BUTTON_REMOVE, &CMIDIMappingDialog::OnBnClickedButtonRemove)
  56. ON_MESSAGE(WM_MOD_MIDIMSG, &CMIDIMappingDialog::OnMidiMsg)
  57. ON_NOTIFY(UDN_DELTAPOS, IDC_SPINMOVEMAPPING, &CMIDIMappingDialog::OnDeltaposSpinmovemapping)
  58. ON_BN_CLICKED(IDC_CHECK_PATRECORD, &CMIDIMappingDialog::OnBnClickedCheckPatRecord)
  59. ON_NOTIFY_EX(TTN_NEEDTEXT, 0, &CMIDIMappingDialog::OnToolTipNotify)
  60. END_MESSAGE_MAP()
  61. LRESULT CMIDIMappingDialog::OnMidiMsg(WPARAM dwMidiDataParam, LPARAM)
  62. {
  63. uint32 midiData = static_cast<uint32>(dwMidiDataParam);
  64. if(IsDlgButtonChecked(IDC_CHECK_MIDILEARN))
  65. {
  66. for(int i = 0; i < m_EventCBox.GetCount(); i++)
  67. {
  68. if(static_cast<MIDIEvents::EventType>(m_EventCBox.GetItemData(i)) == MIDIEvents::GetTypeFromEvent(midiData))
  69. {
  70. m_ChannelCBox.SetCurSel(1 + MIDIEvents::GetChannelFromEvent(midiData));
  71. m_EventCBox.SetCurSel(i);
  72. if(MIDIEvents::GetTypeFromEvent(midiData) == MIDIEvents::evControllerChange)
  73. {
  74. uint8 cc = MIDIEvents::GetDataByte1FromEvent(midiData);
  75. if(m_lastCC >= 32 || cc != m_lastCC + 32)
  76. {
  77. // Ignore second CC message of 14-bit CC.
  78. m_ControllerCBox.SetCurSel(cc);
  79. }
  80. m_lastCC = cc;
  81. }
  82. OnCbnSelchangeComboChannel();
  83. OnCbnSelchangeComboEvent();
  84. OnCbnSelchangeComboController();
  85. break;
  86. }
  87. }
  88. }
  89. return 1;
  90. }
  91. BOOL CMIDIMappingDialog::OnInitDialog()
  92. {
  93. CDialog::OnInitDialog();
  94. // Add events
  95. m_EventCBox.SetItemData(m_EventCBox.AddString(_T("Controller Change")), MIDIEvents::evControllerChange);
  96. m_EventCBox.SetItemData(m_EventCBox.AddString(_T("Polyphonic Aftertouch")), MIDIEvents::evPolyAftertouch);
  97. m_EventCBox.SetItemData(m_EventCBox.AddString(_T("Channel Aftertouch")), MIDIEvents::evChannelAftertouch);
  98. // Add controller names
  99. CString s;
  100. for(uint8 i = MIDIEvents::MIDICC_start; i <= MIDIEvents::MIDICC_end; i++)
  101. {
  102. s.Format(_T("%3u "), i);
  103. s += mpt::ToCString(mpt::Charset::UTF8, MIDIEvents::MidiCCNames[i]);
  104. m_ControllerCBox.AddString(s);
  105. }
  106. // Add plugin names
  107. AddPluginNamesToCombobox(m_PluginCBox, m_sndFile.m_MixPlugins);
  108. // Initialize mapping table
  109. static constexpr CListCtrlEx::Header headers[] =
  110. {
  111. { _T("Channel"), 58, LVCFMT_LEFT },
  112. { _T("Event / Controller"), 176, LVCFMT_LEFT },
  113. { _T("Plugin"), 120, LVCFMT_LEFT },
  114. { _T("Parameter"), 120, LVCFMT_LEFT },
  115. { _T("Capture"), 40, LVCFMT_LEFT },
  116. { _T("Pattern Record"), 40, LVCFMT_LEFT }
  117. };
  118. m_List.SetHeaders(headers);
  119. m_List.SetExtendedStyle(m_List.GetExtendedStyle() | LVS_EX_CHECKBOXES | LVS_EX_FULLROWSELECT);
  120. // Add directives to list
  121. for(size_t i = 0; i < m_rMIDIMapper.GetCount(); i++)
  122. {
  123. InsertItem(m_rMIDIMapper.GetDirective(i), int(i));
  124. }
  125. if(m_rMIDIMapper.GetCount() > 0 && m_Setting.IsDefault())
  126. {
  127. SelectItem(0);
  128. OnSelectionChanged();
  129. } else
  130. {
  131. UpdateDialog();
  132. }
  133. GetDlgItem(IDC_CHECK_PATRECORD)->EnableWindow((m_sndFile.GetType() == MOD_TYPE_MPT) ? TRUE : FALSE);
  134. CMainFrame::GetMainFrame()->SetMidiRecordWnd(GetSafeHwnd());
  135. CheckDlgButton(IDC_CHECK_MIDILEARN, BST_CHECKED);
  136. EnableToolTips(TRUE);
  137. return TRUE; // return TRUE unless you set the focus to a control
  138. }
  139. int CMIDIMappingDialog::InsertItem(const CMIDIMappingDirective &m, int insertAt)
  140. {
  141. CString s;
  142. if(m.GetAnyChannel())
  143. s = _T("Any");
  144. else
  145. s.Format(_T("Ch %u"), m.GetChannel());
  146. insertAt = m_List.InsertItem(insertAt, s);
  147. if(insertAt == -1)
  148. return -1;
  149. m_List.SetCheck(insertAt, m.IsActive() ? TRUE : FALSE);
  150. switch(m.GetEvent())
  151. {
  152. case MIDIEvents::evControllerChange:
  153. s.Format(_T("CC %u: "), m.GetController());
  154. if(m.GetController() <= MIDIEvents::MIDICC_end) s += mpt::ToCString(mpt::Charset::UTF8, MIDIEvents::MidiCCNames[m.GetController()]);
  155. break;
  156. case MIDIEvents::evPolyAftertouch:
  157. s = _T("Polyphonic Aftertouch"); break;
  158. case MIDIEvents::evChannelAftertouch:
  159. s = _T("Channel Aftertouch"); break;
  160. default:
  161. s.Format(_T("0x%02X"), m.GetEvent()); break;
  162. }
  163. m_List.SetItemText(insertAt, 1, s);
  164. const PLUGINDEX plugindex = m.GetPlugIndex();
  165. if(plugindex > 0 && plugindex < MAX_MIXPLUGINS)
  166. {
  167. const SNDMIXPLUGIN &plug = m_sndFile.m_MixPlugins[plugindex - 1];
  168. s.Format(_T("FX%u: "), plugindex);
  169. s += mpt::ToCString(plug.GetName());
  170. m_List.SetItemText(insertAt, 2, s);
  171. if(plug.pMixPlugin != nullptr)
  172. s = plug.pMixPlugin->GetFormattedParamName(m.GetParamIndex());
  173. else
  174. s.Empty();
  175. m_List.SetItemText(insertAt, 3, s);
  176. }
  177. m_List.SetItemText(insertAt, 4, m.GetCaptureMIDI() ? _T("Capt") : _T(""));
  178. m_List.SetItemText(insertAt, 5, m.GetAllowPatternEdit() ? _T("Rec") : _T(""));
  179. return insertAt;
  180. }
  181. void CMIDIMappingDialog::SelectItem(int i)
  182. {
  183. m_List.SetItemState(i, LVIS_SELECTED, LVIS_SELECTED);
  184. m_List.SetSelectionMark(i);
  185. }
  186. void CMIDIMappingDialog::UpdateDialog(int selItem)
  187. {
  188. CheckDlgButton(IDC_CHECKACTIVE, m_Setting.IsActive() ? BST_CHECKED : BST_UNCHECKED);
  189. CheckDlgButton(IDC_CHECKCAPTURE, m_Setting.GetCaptureMIDI() ? BST_CHECKED : BST_UNCHECKED);
  190. CheckDlgButton(IDC_CHECK_PATRECORD, m_Setting.GetAllowPatternEdit() ? BST_CHECKED : BST_UNCHECKED);
  191. m_ChannelCBox.SetCurSel(m_Setting.GetChannel());
  192. m_EventCBox.SetCurSel(-1);
  193. for(int i = 0; i < m_EventCBox.GetCount(); i++)
  194. {
  195. if(m_EventCBox.GetItemData(i) == m_Setting.GetEvent())
  196. {
  197. m_EventCBox.SetCurSel(i);
  198. break;
  199. }
  200. }
  201. m_ControllerCBox.SetCurSel(m_Setting.GetController());
  202. m_PluginCBox.SetCurSel(m_Setting.GetPlugIndex() - 1);
  203. m_PlugParamCBox.SetCurSel(m_Setting.GetParamIndex());
  204. UpdateEvent();
  205. UpdateParameters();
  206. bool enableMover = selItem >= 0;
  207. if(enableMover)
  208. {
  209. const bool previousEqual = (selItem > 0 && m_rMIDIMapper.AreOrderEqual(selItem - 1, selItem));
  210. const bool nextEqual = (selItem + 1 < m_List.GetItemCount() && m_rMIDIMapper.AreOrderEqual(selItem, selItem + 1));
  211. enableMover = previousEqual || nextEqual;
  212. }
  213. m_SpinMoveMapping.EnableWindow(enableMover);
  214. }
  215. void CMIDIMappingDialog::UpdateEvent()
  216. {
  217. m_ControllerCBox.EnableWindow(m_Setting.GetEvent() == MIDIEvents::evControllerChange ? TRUE : FALSE);
  218. if(m_Setting.GetEvent() != MIDIEvents::evControllerChange)
  219. m_ControllerCBox.SetCurSel(0);
  220. }
  221. void CMIDIMappingDialog::UpdateParameters()
  222. {
  223. m_PlugParamCBox.SetRedraw(FALSE);
  224. m_PlugParamCBox.ResetContent();
  225. AddPluginParameternamesToCombobox(m_PlugParamCBox, m_sndFile.m_MixPlugins[m_Setting.GetPlugIndex() - 1]);
  226. m_PlugParamCBox.SetCurSel(m_Setting.GetParamIndex());
  227. m_PlugParamCBox.SetRedraw(TRUE);
  228. m_PlugParamCBox.Invalidate();
  229. }
  230. void CMIDIMappingDialog::OnSelectionChanged(NMHDR *pNMHDR, LRESULT * /*pResult*/)
  231. {
  232. int i;
  233. if(pNMHDR != nullptr)
  234. {
  235. NMLISTVIEW *nmlv = (NMLISTVIEW *)pNMHDR;
  236. if(((nmlv->uOldState ^ nmlv->uNewState) & INDEXTOSTATEIMAGEMASK(3)) != 0 && nmlv->uOldState != 0)
  237. {
  238. // Check box status changed
  239. CMIDIMappingDirective m = m_rMIDIMapper.GetDirective(nmlv->iItem);
  240. m.SetActive(nmlv->uNewState == INDEXTOSTATEIMAGEMASK(2));
  241. m_rMIDIMapper.SetDirective(nmlv->iItem, m);
  242. SetModified();
  243. if(nmlv->iItem == m_List.GetSelectionMark())
  244. CheckDlgButton(IDC_CHECKACTIVE, nmlv->uNewState == INDEXTOSTATEIMAGEMASK(2) ? BST_CHECKED : BST_UNCHECKED);
  245. }
  246. if(nmlv->uNewState & LVIS_SELECTED)
  247. i = nmlv->iItem;
  248. else
  249. return;
  250. } else
  251. {
  252. i = m_List.GetSelectionMark();
  253. }
  254. if(i < 0 || (size_t)i >= m_rMIDIMapper.GetCount()) return;
  255. m_Setting = m_rMIDIMapper.GetDirective(i);
  256. UpdateDialog(i);
  257. }
  258. void CMIDIMappingDialog::OnBnClickedCheckactive()
  259. {
  260. m_Setting.SetActive(IsDlgButtonChecked(IDC_CHECKACTIVE) == BST_CHECKED);
  261. }
  262. void CMIDIMappingDialog::OnBnClickedCheckCapture()
  263. {
  264. m_Setting.SetCaptureMIDI(IsDlgButtonChecked(IDC_CHECKCAPTURE) == BST_CHECKED);
  265. }
  266. void CMIDIMappingDialog::OnBnClickedCheckPatRecord()
  267. {
  268. m_Setting.SetAllowPatternEdit(IsDlgButtonChecked(IDC_CHECK_PATRECORD) == BST_CHECKED);
  269. }
  270. void CMIDIMappingDialog::OnCbnSelchangeComboController()
  271. {
  272. m_Setting.SetController(m_ControllerCBox.GetCurSel());
  273. }
  274. void CMIDIMappingDialog::OnCbnSelchangeComboChannel()
  275. {
  276. m_Setting.SetChannel(m_ChannelCBox.GetCurSel());
  277. }
  278. void CMIDIMappingDialog::OnCbnSelchangeComboPlugin()
  279. {
  280. int i = m_PluginCBox.GetCurSel();
  281. if(i < 0 || i >= MAX_MIXPLUGINS) return;
  282. m_Setting.SetPlugIndex(i+1);
  283. UpdateParameters();
  284. }
  285. void CMIDIMappingDialog::OnCbnSelchangeComboParam()
  286. {
  287. m_Setting.SetParamIndex(m_PlugParamCBox.GetCurSel());
  288. }
  289. void CMIDIMappingDialog::OnCbnSelchangeComboEvent()
  290. {
  291. uint8 eventType = static_cast<uint8>(m_EventCBox.GetItemData(m_EventCBox.GetCurSel()));
  292. m_Setting.SetEvent(eventType);
  293. UpdateEvent();
  294. }
  295. void CMIDIMappingDialog::OnBnClickedButtonAdd()
  296. {
  297. if(m_sndFile.GetModSpecifications().MIDIMappingDirectivesMax <= m_rMIDIMapper.GetCount())
  298. {
  299. Reporting::Information("Maximum amount of MIDI Mapping directives reached.");
  300. } else
  301. {
  302. const size_t i = m_rMIDIMapper.AddDirective(m_Setting);
  303. SetModified();
  304. SelectItem(InsertItem(m_Setting, static_cast<int>(i)));
  305. OnSelectionChanged();
  306. }
  307. }
  308. void CMIDIMappingDialog::OnBnClickedButtonReplace()
  309. {
  310. const int i = m_List.GetSelectionMark();
  311. if(i >= 0 && (size_t)i < m_rMIDIMapper.GetCount())
  312. {
  313. const size_t newIndex = m_rMIDIMapper.SetDirective(i, m_Setting);
  314. SetModified();
  315. m_List.DeleteItem(i);
  316. SelectItem(InsertItem(m_Setting, static_cast<int>(newIndex)));
  317. OnSelectionChanged();
  318. }
  319. }
  320. void CMIDIMappingDialog::OnBnClickedButtonRemove()
  321. {
  322. int i = m_List.GetSelectionMark();
  323. if(i >= 0 && (size_t)i < m_rMIDIMapper.GetCount())
  324. {
  325. m_rMIDIMapper.RemoveDirective(i);
  326. SetModified();
  327. m_List.DeleteItem(i);
  328. if(m_List.GetItemCount() > 0)
  329. {
  330. if(i < m_List.GetItemCount())
  331. SelectItem(i);
  332. else
  333. SelectItem(i - 1);
  334. }
  335. i = m_List.GetSelectionMark();
  336. if(i >= 0 && (size_t)i < m_rMIDIMapper.GetCount())
  337. m_Setting = m_rMIDIMapper.GetDirective(i);
  338. OnSelectionChanged();
  339. }
  340. }
  341. void CMIDIMappingDialog::OnDeltaposSpinmovemapping(NMHDR *pNMHDR, LRESULT *pResult)
  342. {
  343. const int index = m_List.GetSelectionMark();
  344. if(index < 0 || index >= m_List.GetItemCount()) return;
  345. LPNMUPDOWN pNMUpDown = reinterpret_cast<LPNMUPDOWN>(pNMHDR);
  346. int newIndex = -1;
  347. if(pNMUpDown->iDelta < 0) //Up
  348. {
  349. if(index - 1 >= 0 && m_rMIDIMapper.AreOrderEqual(index-1, index))
  350. {
  351. newIndex = index - 1;
  352. }
  353. } else //Down
  354. {
  355. if(index + 1 < m_List.GetItemCount() && m_rMIDIMapper.AreOrderEqual(index, index+1))
  356. {
  357. newIndex = index + 1;
  358. }
  359. }
  360. if(newIndex != -1)
  361. {
  362. m_rMIDIMapper.Swap(size_t(newIndex), size_t(index));
  363. m_List.DeleteItem(index);
  364. InsertItem(m_rMIDIMapper.GetDirective(newIndex), newIndex);
  365. SelectItem(newIndex);
  366. }
  367. *pResult = 0;
  368. }
  369. BOOL CMIDIMappingDialog::OnToolTipNotify(UINT, NMHDR * pNMHDR, LRESULT *)
  370. {
  371. TOOLTIPTEXT *pTTT = (TOOLTIPTEXT*)pNMHDR;
  372. const TCHAR *text = _T("");
  373. UINT_PTR nID = pNMHDR->idFrom;
  374. if(pTTT->uFlags & TTF_IDISHWND)
  375. {
  376. // idFrom is actually the HWND of the tool
  377. nID = ::GetDlgCtrlID((HWND)nID);
  378. }
  379. switch(nID)
  380. {
  381. case IDC_CHECKCAPTURE:
  382. text = _T("The event is not passed to any further MIDI mappings or recording facilities.");
  383. break;
  384. case IDC_CHECKACTIVE:
  385. text = _T("The MIDI mapping is enabled and can be processed.");
  386. break;
  387. case IDC_CHECK_PATRECORD:
  388. text = _T("Parameter changes are recorded into patterns as Parameter Control events.");
  389. break;
  390. case IDC_CHECK_MIDILEARN:
  391. text = _T("Listens to incoming MIDI data to automatically fill in the appropriate data.");
  392. break;
  393. case IDC_SPINMOVEMAPPING:
  394. text = _T("Change the processing order of the current selected MIDI mapping.");
  395. break;
  396. case IDC_COMBO_CHANNEL:
  397. text = _T("The MIDI channel to listen on for this event.");
  398. break;
  399. case IDC_COMBO_EVENT:
  400. text = _T("The MIDI event to listen for.");
  401. break;
  402. case IDC_COMBO_CONTROLLER:
  403. text = _T("The MIDI controler to listen for.");
  404. break;
  405. }
  406. mpt::String::WriteWinBuf(pTTT->szText) = mpt::winstring(text);
  407. return TRUE;
  408. }
  409. void CMIDIMappingDialog::SetModified()
  410. {
  411. if(m_sndFile.GetpModDoc() != nullptr)
  412. m_sndFile.GetpModDoc()->SetModified();
  413. }
  414. OPENMPT_NAMESPACE_END
  415. #endif // NO_PLUGINS