AbstractVstEditor.cpp 28 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022
  1. /*
  2. * AbstractVstEditor.cpp
  3. * ---------------------
  4. * Purpose: Common plugin editor interface class. This code is shared between custom and default plugin user interfaces.
  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.h"
  11. #include "Mainfrm.h"
  12. #include "Clipboard.h"
  13. #include "../soundlib/Sndfile.h"
  14. #include "../soundlib/mod_specifications.h"
  15. #include "../soundlib/plugins/PlugInterface.h"
  16. #include "../soundlib/plugins/PluginManager.h"
  17. #include "Vstplug.h"
  18. #include "dlg_misc.h"
  19. #include "AbstractVstEditor.h"
  20. #include "../common/mptStringBuffer.h"
  21. #include "MIDIMacros.h"
  22. #include "VstPresets.h"
  23. #include "../common/FileReader.h"
  24. #include "InputHandler.h"
  25. #include "dlg_misc.h"
  26. #include <sstream>
  27. #include "Globals.h"
  28. OPENMPT_NAMESPACE_BEGIN
  29. #ifndef NO_PLUGINS
  30. CAbstractVstEditor::WindowSizeAdjuster::WindowSizeAdjuster(CWnd &wnd)
  31. : m_wnd(wnd)
  32. {
  33. MENUBARINFO mbi = { sizeof(mbi) };
  34. if(GetMenuBarInfo(m_wnd, OBJID_MENU, 0, &mbi))
  35. m_menuHeight = (mbi.rcBar.bottom - mbi.rcBar.top);
  36. }
  37. CAbstractVstEditor::WindowSizeAdjuster::~WindowSizeAdjuster()
  38. {
  39. // Extend window height by the menu size if it changed
  40. MENUBARINFO mbi = { sizeof(mbi) };
  41. if(GetMenuBarInfo(m_wnd, OBJID_MENU, 0, &mbi))
  42. {
  43. CRect windowRect;
  44. m_wnd.GetWindowRect(&windowRect);
  45. windowRect.bottom += (mbi.rcBar.bottom - mbi.rcBar.top) - m_menuHeight;
  46. m_wnd.SetWindowPos(nullptr, 0, 0,
  47. windowRect.Width(), windowRect.Height(),
  48. SWP_NOZORDER | SWP_NOMOVE | SWP_NOACTIVATE);
  49. }
  50. }
  51. #define PRESETS_PER_COLUMN 32
  52. #define PRESETS_PER_GROUP 128
  53. UINT CAbstractVstEditor::m_clipboardFormat = RegisterClipboardFormat(_T("VST Preset Data"));
  54. BEGIN_MESSAGE_MAP(CAbstractVstEditor, CDialog)
  55. ON_WM_CLOSE()
  56. ON_WM_INITMENU()
  57. ON_WM_MENUSELECT()
  58. ON_WM_ACTIVATE()
  59. ON_WM_DROPFILES()
  60. ON_WM_MOVE()
  61. ON_WM_NCLBUTTONDBLCLK()
  62. ON_COMMAND(ID_EDIT_COPY, &CAbstractVstEditor::OnCopyParameters)
  63. ON_COMMAND(ID_EDIT_PASTE, &CAbstractVstEditor::OnPasteParameters)
  64. ON_COMMAND(ID_PRESET_LOAD, &CAbstractVstEditor::OnLoadPreset)
  65. ON_COMMAND(ID_PLUG_BYPASS, &CAbstractVstEditor::OnBypassPlug)
  66. ON_COMMAND(ID_PLUG_RECORDAUTOMATION,&CAbstractVstEditor::OnRecordAutomation)
  67. ON_COMMAND(ID_PLUG_RECORD_MIDIOUT, &CAbstractVstEditor::OnRecordMIDIOut)
  68. ON_COMMAND(ID_PLUG_PASSKEYS, &CAbstractVstEditor::OnPassKeypressesToPlug)
  69. ON_COMMAND(ID_PRESET_SAVE, &CAbstractVstEditor::OnSavePreset)
  70. ON_COMMAND(ID_PRESET_RANDOM, &CAbstractVstEditor::OnRandomizePreset)
  71. ON_COMMAND(ID_RENAME_PLUGIN, &CAbstractVstEditor::OnRenamePlugin)
  72. ON_COMMAND(ID_PREVIOUSVSTPRESET, &CAbstractVstEditor::OnSetPreviousVSTPreset)
  73. ON_COMMAND(ID_NEXTVSTPRESET, &CAbstractVstEditor::OnSetNextVSTPreset)
  74. ON_COMMAND(ID_VSTPRESETBACKWARDJUMP,&CAbstractVstEditor::OnVSTPresetBackwardJump)
  75. ON_COMMAND(ID_VSTPRESETFORWARDJUMP, &CAbstractVstEditor::OnVSTPresetForwardJump)
  76. ON_COMMAND(ID_VSTPRESETNAME, &CAbstractVstEditor::OnVSTPresetRename)
  77. ON_COMMAND(ID_PLUGINTOINSTRUMENT, &CAbstractVstEditor::OnCreateInstrument)
  78. ON_COMMAND_RANGE(ID_PRESET_SET, ID_PRESET_SET + PRESETS_PER_GROUP, &CAbstractVstEditor::OnSetPreset)
  79. ON_MESSAGE(WM_MOD_MIDIMSG, &CAbstractVstEditor::OnMidiMsg)
  80. ON_MESSAGE(WM_MOD_KEYCOMMAND, &CAbstractVstEditor::OnCustomKeyMsg) //rewbs.customKeys
  81. ON_COMMAND_RANGE(ID_PLUGSELECT, ID_PLUGSELECT + MAX_MIXPLUGINS, &CAbstractVstEditor::OnToggleEditor) //rewbs.patPlugName
  82. ON_COMMAND_RANGE(ID_SELECTINST, ID_SELECTINST + MAX_INSTRUMENTS, &CAbstractVstEditor::OnSetInputInstrument) //rewbs.patPlugName
  83. ON_COMMAND_RANGE(ID_LEARN_MACRO_FROM_PLUGGUI, ID_LEARN_MACRO_FROM_PLUGGUI + kSFxMacros, &CAbstractVstEditor::PrepareToLearnMacro)
  84. END_MESSAGE_MAP()
  85. CAbstractVstEditor::CAbstractVstEditor(IMixPlugin &plugin)
  86. : m_VstPlugin(plugin)
  87. {
  88. m_Menu.LoadMenu(IDR_VSTMENU);
  89. m_nInstrument = GetBestInstrumentCandidate();
  90. }
  91. CAbstractVstEditor::~CAbstractVstEditor()
  92. {
  93. m_VstPlugin.m_pEditor = nullptr;
  94. }
  95. void CAbstractVstEditor::PostNcDestroy()
  96. {
  97. CDialog::PostNcDestroy();
  98. delete this;
  99. }
  100. void CAbstractVstEditor::OnNcLButtonDblClk(UINT nHitTest, CPoint point)
  101. {
  102. CDialog::OnNcLButtonDblClk(nHitTest, point);
  103. // Double click on title bar = reduce plugin window to non-client area
  104. if(nHitTest == HTCAPTION)
  105. {
  106. CRect rcWnd, rcClient;
  107. GetWindowRect(&rcWnd);
  108. if(!m_isMinimized)
  109. {
  110. // When minimizing, remove the client area
  111. GetClientRect(&rcClient);
  112. m_clientHeight = rcClient.Height();
  113. }
  114. m_isMinimized = !m_isMinimized;
  115. m_clientHeight = -m_clientHeight;
  116. int rcHeight = rcWnd.Height() + m_clientHeight;
  117. SetWindowPos(NULL, 0, 0,
  118. rcWnd.Width(), rcHeight,
  119. SWP_NOZORDER | SWP_NOMOVE);
  120. }
  121. }
  122. void CAbstractVstEditor::OnActivate(UINT nState, CWnd *pWndOther, BOOL bMinimized)
  123. {
  124. CDialog::OnActivate(nState, pWndOther, bMinimized);
  125. if(nState != WA_INACTIVE) CMainFrame::GetMainFrame()->SetMidiRecordWnd(GetSafeHwnd());
  126. }
  127. LRESULT CAbstractVstEditor::OnMidiMsg(WPARAM midiData, LPARAM sender)
  128. {
  129. CModDoc *modDoc = m_VstPlugin.GetModDoc();
  130. if(modDoc != nullptr && sender != reinterpret_cast<LPARAM>(&m_VstPlugin))
  131. {
  132. if(!CheckInstrument(m_nInstrument))
  133. m_nInstrument = GetBestInstrumentCandidate();
  134. modDoc->ProcessMIDI((uint32)midiData, m_nInstrument, &m_VstPlugin, kCtxVSTGUI);
  135. return 1;
  136. }
  137. return 0;
  138. }
  139. // Drop files from Windows
  140. void CAbstractVstEditor::OnDropFiles(HDROP hDropInfo)
  141. {
  142. const UINT nFiles = ::DragQueryFileW(hDropInfo, (UINT)-1, NULL, 0);
  143. CMainFrame::GetMainFrame()->SetForegroundWindow();
  144. for(UINT f = 0; f < nFiles; f++)
  145. {
  146. UINT size = ::DragQueryFile(hDropInfo, f, nullptr, 0) + 1;
  147. std::vector<TCHAR> fileName(size, _T('\0'));
  148. if(::DragQueryFile(hDropInfo, f, fileName.data(), size))
  149. {
  150. m_VstPlugin.LoadProgram(mpt::PathString::FromNative(fileName.data()));
  151. }
  152. }
  153. ::DragFinish(hDropInfo);
  154. }
  155. void CAbstractVstEditor::OnLoadPreset()
  156. {
  157. if(m_VstPlugin.LoadProgram())
  158. {
  159. UpdatePresetMenu(true);
  160. UpdatePresetField();
  161. }
  162. }
  163. void CAbstractVstEditor::OnSavePreset()
  164. {
  165. m_VstPlugin.SaveProgram();
  166. }
  167. void CAbstractVstEditor::OnCopyParameters()
  168. {
  169. if(CMainFrame::GetMainFrame() == nullptr) return;
  170. BeginWaitCursor();
  171. std::ostringstream f(std::ios::out | std::ios::binary);
  172. if(VSTPresets::SaveFile(f, m_VstPlugin, false))
  173. {
  174. const std::string data = f.str();
  175. Clipboard clipboard(m_clipboardFormat, data.length());
  176. if(auto dst = clipboard.As<char>())
  177. {
  178. memcpy(dst, data.data(), data.length());
  179. }
  180. }
  181. EndWaitCursor();
  182. }
  183. void CAbstractVstEditor::OnPasteParameters()
  184. {
  185. if(CMainFrame::GetMainFrame() == nullptr) return;
  186. BeginWaitCursor();
  187. Clipboard clipboard(m_clipboardFormat);
  188. if(auto data = clipboard.Get(); data.data())
  189. {
  190. FileReader file(data);
  191. VSTPresets::ErrorCode error = VSTPresets::LoadFile(file, m_VstPlugin);
  192. clipboard.Close();
  193. if(error == VSTPresets::noError)
  194. {
  195. const CSoundFile &sndFile = m_VstPlugin.GetSoundFile();
  196. CModDoc *pModDoc;
  197. if(sndFile.GetModSpecifications().supportsPlugins && (pModDoc = sndFile.GetpModDoc()) != nullptr)
  198. {
  199. pModDoc->SetModified();
  200. }
  201. UpdatePresetField();
  202. } else
  203. {
  204. Reporting::Error(VSTPresets::GetErrorMessage(error));
  205. }
  206. }
  207. EndWaitCursor();
  208. }
  209. void CAbstractVstEditor::OnRandomizePreset()
  210. {
  211. static double randomFactor = 10.0;
  212. CInputDlg dlg(this, _T("Input parameter randomization amount (0 = no change, 100 = completely random)"), 0.0, 100.0, randomFactor);
  213. if(dlg.DoModal() == IDOK)
  214. {
  215. randomFactor = dlg.resultAsDouble;
  216. PlugParamValue factor = PlugParamValue(randomFactor / 100.0);
  217. PlugParamIndex numParams = m_VstPlugin.GetNumParameters();
  218. for(PlugParamIndex p = 0; p < numParams; p++)
  219. {
  220. PlugParamValue val = m_VstPlugin.GetParameter(p);
  221. val += mpt::random(theApp.PRNG(), PlugParamValue(-1.0), PlugParamValue(1.0)) * factor;
  222. Limit(val, 0.0f, 1.0f);
  223. m_VstPlugin.SetParameter(p, val);
  224. }
  225. UpdateParamDisplays();
  226. }
  227. }
  228. void CAbstractVstEditor::OnRenamePlugin()
  229. {
  230. auto &sndFile = m_VstPlugin.GetSoundFile();
  231. auto &plugin = sndFile.m_MixPlugins[m_VstPlugin.m_nSlot];
  232. CInputDlg dlg(this, _T("New name for this plugin instance:"), mpt::ToCString(plugin.GetName()), static_cast<int32>(std::size(plugin.Info.szName.buf)));
  233. if(dlg.DoModal() == IDOK)
  234. {
  235. if(dlg.resultAsString != mpt::ToCString(plugin.GetName()))
  236. {
  237. plugin.Info.szName = mpt::ToCharset(mpt::Charset::Locale, dlg.resultAsString);
  238. if(auto *modDoc = sndFile.GetpModDoc(); modDoc != nullptr)
  239. {
  240. if(sndFile.GetModSpecifications().supportsPlugins)
  241. modDoc->SetModified();
  242. modDoc->UpdateAllViews(nullptr, PluginHint(m_VstPlugin.m_nSlot + 1).Info().Names(), this);
  243. }
  244. SetTitle();
  245. }
  246. }
  247. }
  248. bool CAbstractVstEditor::OpenEditor(CWnd *)
  249. {
  250. ModifyStyleEx(0, WS_EX_ACCEPTFILES);
  251. RestoreWindowPos();
  252. SetTitle();
  253. SetupMenu();
  254. ShowWindow(SW_SHOW);
  255. return true;
  256. }
  257. void CAbstractVstEditor::DoClose()
  258. {
  259. StoreWindowPos();
  260. m_presetMenuGroup.clear();
  261. DestroyWindow();
  262. }
  263. void CAbstractVstEditor::SetupMenu(bool force)
  264. {
  265. WindowSizeAdjuster adjuster(*this);
  266. SetMenu(&m_Menu);
  267. UpdatePresetMenu(force);
  268. UpdateInputMenu();
  269. UpdateOutputMenu();
  270. UpdateMacroMenu();
  271. UpdateOptionsMenu();
  272. UpdatePresetField();
  273. }
  274. void CAbstractVstEditor::UpdatePresetField()
  275. {
  276. if(m_VstPlugin.GetNumPrograms() > 0)
  277. {
  278. if(m_Menu.GetMenuItemCount() < 5)
  279. {
  280. m_Menu.AppendMenu(MF_BYPOSITION, ID_VSTPRESETBACKWARDJUMP, _T("<<"));
  281. m_Menu.AppendMenu(MF_BYPOSITION, ID_PREVIOUSVSTPRESET, _T("<"));
  282. m_Menu.AppendMenu(MF_BYPOSITION, ID_NEXTVSTPRESET, _T(">"));
  283. m_Menu.AppendMenu(MF_BYPOSITION, ID_VSTPRESETFORWARDJUMP, _T(">>"));
  284. m_Menu.AppendMenu(MF_BYPOSITION|MF_DISABLED, ID_VSTPRESETNAME, _T(""));
  285. }
  286. CString programName = m_VstPlugin.GetFormattedProgramName(m_VstPlugin.GetCurrentProgram());
  287. programName.Replace(_T("&"), _T("&&"));
  288. m_Menu.ModifyMenu(8, MF_BYPOSITION, ID_VSTPRESETNAME, programName);
  289. }
  290. DrawMenuBar();
  291. }
  292. void CAbstractVstEditor::OnSetPreset(UINT nID)
  293. {
  294. SetPreset(nID - ID_PRESET_SET + m_currentPresetMenu * PRESETS_PER_GROUP);
  295. }
  296. void CAbstractVstEditor::OnSetPreviousVSTPreset()
  297. {
  298. SetPreset(m_VstPlugin.GetCurrentProgram() - 1);
  299. }
  300. void CAbstractVstEditor::OnSetNextVSTPreset()
  301. {
  302. SetPreset(m_VstPlugin.GetCurrentProgram() + 1);
  303. }
  304. void CAbstractVstEditor::OnVSTPresetBackwardJump()
  305. {
  306. SetPreset(std::max(0, m_VstPlugin.GetCurrentProgram() - 10));
  307. }
  308. void CAbstractVstEditor::OnVSTPresetForwardJump()
  309. {
  310. SetPreset(std::min(m_VstPlugin.GetCurrentProgram() + 10, m_VstPlugin.GetNumPrograms() - 1));
  311. }
  312. void CAbstractVstEditor::SetPreset(int32 preset)
  313. {
  314. if(preset >= 0 && preset < m_VstPlugin.GetNumPrograms())
  315. {
  316. m_VstPlugin.SetCurrentProgram(preset);
  317. WindowSizeAdjuster adjuster(*this);
  318. UpdatePresetField();
  319. if(m_VstPlugin.GetSoundFile().GetModSpecifications().supportsPlugins)
  320. {
  321. m_VstPlugin.GetModDoc()->SetModified();
  322. }
  323. }
  324. }
  325. void CAbstractVstEditor::OnVSTPresetRename()
  326. {
  327. auto currentName = m_VstPlugin.GetCurrentProgramName();
  328. CInputDlg dlg(this, _T("New program name:"), currentName);
  329. if(dlg.DoModal() == IDOK)
  330. {
  331. m_VstPlugin.SetCurrentProgramName(dlg.resultAsString);
  332. if(m_VstPlugin.GetCurrentProgramName() != currentName)
  333. {
  334. m_VstPlugin.SetModified();
  335. WindowSizeAdjuster adjuster(*this);
  336. UpdatePresetField();
  337. UpdatePresetMenu(true);
  338. }
  339. }
  340. }
  341. void CAbstractVstEditor::OnBypassPlug()
  342. {
  343. m_VstPlugin.ToggleBypass();
  344. if(m_VstPlugin.GetSoundFile().GetModSpecifications().supportsPlugins)
  345. {
  346. m_VstPlugin.GetModDoc()->SetModified();
  347. }
  348. SetTitle();
  349. }
  350. void CAbstractVstEditor::OnRecordAutomation()
  351. {
  352. m_VstPlugin.m_recordAutomation = !m_VstPlugin.m_recordAutomation;
  353. }
  354. void CAbstractVstEditor::OnRecordMIDIOut()
  355. {
  356. m_VstPlugin.m_recordMIDIOut = !m_VstPlugin.m_recordMIDIOut;
  357. }
  358. void CAbstractVstEditor::OnPassKeypressesToPlug()
  359. {
  360. m_VstPlugin.m_passKeypressesToPlug = !m_VstPlugin.m_passKeypressesToPlug;
  361. }
  362. BOOL CAbstractVstEditor::PreTranslateMessage(MSG *msg)
  363. {
  364. if(msg && HandleKeyMessage(*msg))
  365. return TRUE;
  366. return CDialog::PreTranslateMessage(msg);
  367. }
  368. bool CAbstractVstEditor::HandleKeyMessage(MSG &msg)
  369. {
  370. if(m_VstPlugin.m_passKeypressesToPlug)
  371. return false;
  372. if(msg.message != WM_SYSKEYUP && msg.message != WM_KEYUP && msg.message != WM_SYSKEYDOWN && msg.message != WM_KEYDOWN)
  373. return false;
  374. CInputHandler *ih = CMainFrame::GetInputHandler();
  375. if(ih->IsKeyPressHandledByTextBox(static_cast<DWORD>(msg.wParam), ::GetFocus()))
  376. return false;
  377. // Translate message manually
  378. UINT nChar = (UINT)msg.wParam;
  379. UINT nRepCnt = LOWORD(msg.lParam);
  380. UINT nFlags = HIWORD(msg.lParam);
  381. KeyEventType kT = ih->GetKeyEventType(nFlags);
  382. // If we successfully mapped to a command and plug does not listen for keypresses, no need to pass message on.
  383. if(ih->KeyEvent(kCtxVSTGUI, nChar, nRepCnt, nFlags, kT, this) != kcNull)
  384. return true;
  385. // Don't forward key repeats if plug does not listen for keypresses
  386. // (avoids system beeps on note hold)
  387. if(kT == kKeyEventRepeat)
  388. return true;
  389. return false;
  390. }
  391. void CAbstractVstEditor::UpdateView(UpdateHint hint)
  392. {
  393. if(!hint.GetType()[HINT_PLUGINNAMES | HINT_MIXPLUGINS])
  394. return;
  395. PLUGINDEX hintPlug = hint.ToType<PluginHint>().GetPlugin();
  396. if(hintPlug > 0 && (hintPlug - 1) != m_VstPlugin.GetSlot())
  397. return;
  398. SetTitle();
  399. }
  400. void CAbstractVstEditor::SetTitle()
  401. {
  402. if(m_VstPlugin.m_pMixStruct)
  403. {
  404. CString title = MPT_CFORMAT("FX {}: ")(mpt::cfmt::dec0<2>(m_VstPlugin.m_nSlot + 1));
  405. bool hasCustomName = (m_VstPlugin.m_pMixStruct->GetName() != U_("")) && (m_VstPlugin.m_pMixStruct->GetName() != m_VstPlugin.m_pMixStruct->GetLibraryName());
  406. if(hasCustomName)
  407. title += mpt::ToCString(m_VstPlugin.m_pMixStruct->GetName()) + _T(" (");
  408. title += mpt::ToCString(m_VstPlugin.m_pMixStruct->GetLibraryName());
  409. if(hasCustomName)
  410. title += _T(")");
  411. #ifdef MPT_WITH_VST
  412. const CVstPlugin *vstPlugin = dynamic_cast<CVstPlugin *>(&m_VstPlugin);
  413. if(vstPlugin != nullptr && vstPlugin->isBridged)
  414. title += MPT_CFORMAT(" ({} Bridged)")(m_VstPlugin.GetPluginFactory().GetDllArchNameUser());
  415. #endif // MPT_WITH_VST
  416. if(m_VstPlugin.IsBypassed())
  417. title += _T(" - Bypass");
  418. SetWindowText(title);
  419. }
  420. }
  421. LRESULT CAbstractVstEditor::OnCustomKeyMsg(WPARAM wParam, LPARAM /*lParam*/)
  422. {
  423. switch(wParam)
  424. {
  425. case kcVSTGUIPrevPreset: OnSetPreviousVSTPreset(); return wParam;
  426. case kcVSTGUIPrevPresetJump: OnVSTPresetBackwardJump(); return wParam;
  427. case kcVSTGUINextPreset: OnSetNextVSTPreset(); return wParam;
  428. case kcVSTGUINextPresetJump: OnVSTPresetForwardJump(); return wParam;
  429. case kcVSTGUIRandParams: OnRandomizePreset() ; return wParam;
  430. case kcVSTGUIToggleRecordParams: OnRecordAutomation(); return wParam;
  431. case kcVSTGUIToggleSendKeysToPlug: OnPassKeypressesToPlug(); return wParam;
  432. case kcVSTGUIBypassPlug: OnBypassPlug(); return wParam;
  433. }
  434. if (wParam >= kcVSTGUIStartNotes && wParam <= kcVSTGUIEndNotes)
  435. {
  436. if(ValidateCurrentInstrument())
  437. {
  438. CModDoc *pModDoc = m_VstPlugin.GetModDoc();
  439. const ModCommand::NOTE note = pModDoc->GetNoteWithBaseOctave(static_cast<int>(wParam - kcVSTGUIStartNotes), m_nInstrument);
  440. if(ModCommand::IsNote(note))
  441. {
  442. pModDoc->PlayNote(PlayNoteParam(note).Instrument(m_nInstrument), &m_noteChannel);
  443. }
  444. }
  445. return wParam;
  446. }
  447. if (wParam >= kcVSTGUIStartNoteStops && wParam <= kcVSTGUIEndNoteStops)
  448. {
  449. if(ValidateCurrentInstrument())
  450. {
  451. CModDoc *pModDoc = m_VstPlugin.GetModDoc();
  452. const ModCommand::NOTE note = pModDoc->GetNoteWithBaseOctave(static_cast<int>(wParam - kcVSTGUIStartNoteStops), m_nInstrument);
  453. if(ModCommand::IsNote(note))
  454. {
  455. pModDoc->NoteOff(note, false, m_nInstrument, m_noteChannel[note - NOTE_MIN]);
  456. }
  457. }
  458. return wParam;
  459. }
  460. return kcNull;
  461. }
  462. // When trying to play a note using this plugin, but no instrument is assigned to it,
  463. // the user is asked whether a new instrument should be added.
  464. bool CAbstractVstEditor::ValidateCurrentInstrument()
  465. {
  466. if(!CheckInstrument(m_nInstrument))
  467. m_nInstrument = GetBestInstrumentCandidate();
  468. //only show messagebox if plug is able to process notes.
  469. if(m_nInstrument == INSTRUMENTINDEX_INVALID)
  470. {
  471. if(m_VstPlugin.CanRecieveMidiEvents())
  472. {
  473. // We might need to steal the focus from the plugin bridge. This is going to work
  474. // as the plugin bridge will call AllowSetForegroundWindow on key messages.
  475. SetForegroundWindow();
  476. if(!m_VstPlugin.IsInstrument() || m_VstPlugin.GetSoundFile().GetModSpecifications().instrumentsMax == 0 ||
  477. Reporting::Confirm(_T("You need to assign an instrument to this plugin before you can play notes from here.\nCreate a new instrument and assign this plugin to the instrument?"), false, false, this) == cnfNo)
  478. {
  479. return false;
  480. } else
  481. {
  482. OnCreateInstrument();
  483. // Return true since we don't want to trigger the note for which the instrument has been validated yet.
  484. // Otherwise, the note might hang forever because the key-up event will go missing.
  485. return false;
  486. }
  487. } else
  488. {
  489. // Can't process notes
  490. return false;
  491. }
  492. }
  493. return true;
  494. }
  495. static int GetNumSubMenus(int32 numProgs) { return (numProgs + (PRESETS_PER_GROUP - 1)) / PRESETS_PER_GROUP; }
  496. void CAbstractVstEditor::OnMenuSelect(UINT nItemID, UINT nFlags, HMENU hSysMenu)
  497. {
  498. if(!(nFlags & MF_POPUP))
  499. {
  500. return;
  501. }
  502. if(hSysMenu == m_Menu.m_hMenu)
  503. {
  504. // Main menu
  505. switch(nItemID)
  506. {
  507. case 0:
  508. // Grey out paste menu item.
  509. m_Menu.EnableMenuItem(ID_EDIT_PASTE, MF_BYCOMMAND | (IsClipboardFormatAvailable(m_clipboardFormat) ? 0 : MF_GRAYED));
  510. break;
  511. case 1:
  512. // If there would be only one sub menu, we add presets directly to the factory menu
  513. {
  514. const int32 numProgs = m_VstPlugin.GetNumPrograms();
  515. if(GetNumSubMenus(numProgs) <= 1)
  516. {
  517. GeneratePresetMenu(0, m_PresetMenu);
  518. }
  519. }
  520. break;
  521. }
  522. } else if(hSysMenu == m_Menu.GetSubMenu(1)->m_hMenu)
  523. {
  524. // Preset menu
  525. m_currentPresetMenu = nItemID;
  526. GeneratePresetMenu(nItemID * PRESETS_PER_GROUP, *m_presetMenuGroup[nItemID]);
  527. }
  528. }
  529. void CAbstractVstEditor::UpdatePresetMenu(bool force)
  530. {
  531. const int32 numProgs = m_VstPlugin.GetNumPrograms();
  532. const int32 curProg = m_VstPlugin.GetCurrentProgram();
  533. if(m_PresetMenu.m_hMenu) // We rebuild the menu from scratch, so remove any exiting menus...
  534. {
  535. if(curProg == m_nCurProg && !force) // ... unless menu exists and is accurate, in which case we are done.
  536. return;
  537. m_presetMenuGroup.clear();
  538. // If there were no preset groups, delete the remaining content so that it can be refilled dynamically
  539. while(m_PresetMenu.GetMenuItemCount() > 0)
  540. m_PresetMenu.RemoveMenu(0, MF_BYPOSITION);
  541. } else
  542. {
  543. // Create Factory preset menu
  544. m_PresetMenu.CreatePopupMenu();
  545. m_Menu.InsertMenu(1, MF_BYPOSITION | MF_POPUP, reinterpret_cast<UINT_PTR>(m_PresetMenu.m_hMenu), _T("&Presets"));
  546. }
  547. m_Menu.EnableMenuItem(1, MF_BYPOSITION | (numProgs ? 0 : MF_GRAYED));
  548. const int numSubMenus = GetNumSubMenus(numProgs);
  549. if(numSubMenus > 1)
  550. {
  551. // Depending on the plugin and its number of presets, filling the sub menus can take quite a while (e.g. Synth1),
  552. // so we fill the menus only on demand (when they are opened), so that the editor GUI creation doesn't take forever.
  553. m_presetMenuGroup.resize(numSubMenus);
  554. for(int bank = 0, prog = 0; bank < numSubMenus; bank++, prog += PRESETS_PER_GROUP)
  555. {
  556. m_presetMenuGroup[bank] = std::make_unique<CMenu>();
  557. m_presetMenuGroup[bank]->CreatePopupMenu();
  558. CString label;
  559. label.Format(_T("Bank %d (%d-%d)"), bank + 1, prog + 1, std::min(prog + PRESETS_PER_GROUP, numProgs));
  560. m_PresetMenu.AppendMenu(MF_POPUP
  561. | (bank % 32 == 0 ? MF_MENUBREAK : 0)
  562. | (curProg >= prog && curProg < prog + PRESETS_PER_GROUP ? MF_CHECKED : MF_UNCHECKED),
  563. reinterpret_cast<UINT_PTR>(m_presetMenuGroup[bank]->m_hMenu), label);
  564. }
  565. }
  566. m_currentPresetMenu = 0;
  567. m_nCurProg = curProg;
  568. }
  569. void CAbstractVstEditor::GeneratePresetMenu(int32 offset, CMenu &parent)
  570. {
  571. const int32 numProgs = m_VstPlugin.GetNumPrograms();
  572. const int32 curProg = m_VstPlugin.GetCurrentProgram();
  573. const int32 endProg = std::min(offset + PRESETS_PER_GROUP, numProgs);
  574. if(parent.GetMenuItemCount() != 0)
  575. {
  576. // Already generated.
  577. return;
  578. }
  579. m_VstPlugin.CacheProgramNames(offset, endProg);
  580. for(int32 p = offset, row = 0, id = 0; p < endProg; p++, row++, id++)
  581. {
  582. CString programName = m_VstPlugin.GetFormattedProgramName(p);
  583. programName.Replace(_T("&"), _T("&&"));
  584. UINT splitMenuFlag = 0;
  585. if(row == PRESETS_PER_COLUMN)
  586. {
  587. // Advance to next menu column
  588. row = 0;
  589. splitMenuFlag = MF_MENUBARBREAK;
  590. }
  591. parent.AppendMenu(MF_STRING | (p == curProg ? MF_CHECKED : MF_UNCHECKED) | splitMenuFlag, ID_PRESET_SET + id, programName);
  592. }
  593. }
  594. void CAbstractVstEditor::UpdateInputMenu()
  595. {
  596. CMenu *pInfoMenu = m_Menu.GetSubMenu(2);
  597. pInfoMenu->DeleteMenu(0, MF_BYPOSITION);
  598. const CSoundFile &sndFile = m_VstPlugin.GetSoundFile();
  599. if(m_InputMenu.m_hMenu)
  600. {
  601. m_InputMenu.DestroyMenu();
  602. }
  603. if(!m_InputMenu.m_hMenu)
  604. {
  605. m_InputMenu.CreatePopupMenu();
  606. }
  607. std::vector<IMixPlugin *> inputPlugs;
  608. m_VstPlugin.GetInputPlugList(inputPlugs);
  609. for(auto plug : inputPlugs)
  610. {
  611. CString name = MPT_CFORMAT("FX{}: {}")(mpt::cfmt::dec0<2>(plug->m_nSlot + 1), mpt::ToCString(plug->m_pMixStruct->GetName()));
  612. m_InputMenu.AppendMenu(MF_STRING, ID_PLUGSELECT + plug->m_nSlot, name);
  613. }
  614. std::vector<CHANNELINDEX> inputChannels;
  615. m_VstPlugin.GetInputChannelList(inputChannels);
  616. bool addSeparator = !inputPlugs.empty();
  617. for(auto chn : inputChannels)
  618. {
  619. if(addSeparator)
  620. {
  621. m_InputMenu.AppendMenu(MF_SEPARATOR);
  622. addSeparator = false;
  623. }
  624. CString name = MPT_CFORMAT("Chn{}: {}")(mpt::cfmt::dec0<2>(chn + 1), mpt::ToCString(sndFile.GetCharsetInternal(), sndFile.ChnSettings[chn].szName));
  625. m_InputMenu.AppendMenu(MF_STRING, NULL, name);
  626. }
  627. std::vector<INSTRUMENTINDEX> inputInstruments;
  628. m_VstPlugin.GetInputInstrumentList(inputInstruments);
  629. addSeparator = !inputPlugs.empty() || !inputChannels.empty();
  630. for(auto ins : inputInstruments)
  631. {
  632. if(addSeparator)
  633. {
  634. m_InputMenu.AppendMenu(MF_SEPARATOR);
  635. addSeparator = false;
  636. }
  637. CString name = MPT_CFORMAT("Ins{}: {}")(mpt::cfmt::dec0<2>(ins), mpt::ToCString(sndFile.GetCharsetInternal(), sndFile.GetInstrumentName(ins)));
  638. m_InputMenu.AppendMenu(MF_STRING | ((ins == m_nInstrument) ? MF_CHECKED : 0), ID_SELECTINST + ins, name);
  639. }
  640. if(inputPlugs.empty() && inputChannels.empty() && inputInstruments.empty())
  641. {
  642. m_InputMenu.AppendMenu(MF_STRING | MF_GRAYED, NULL, _T("None"));
  643. }
  644. pInfoMenu->InsertMenu(0, MF_BYPOSITION | MF_POPUP, reinterpret_cast<UINT_PTR>(m_InputMenu.m_hMenu), _T("I&nputs"));
  645. }
  646. void CAbstractVstEditor::UpdateOutputMenu()
  647. {
  648. CMenu *pInfoMenu = m_Menu.GetSubMenu(2);
  649. pInfoMenu->DeleteMenu(1, MF_BYPOSITION);
  650. if(m_OutputMenu.m_hMenu)
  651. {
  652. m_OutputMenu.DestroyMenu();
  653. }
  654. if(!m_OutputMenu.m_hMenu)
  655. {
  656. m_OutputMenu.CreatePopupMenu();
  657. }
  658. std::vector<IMixPlugin *> outputPlugs;
  659. m_VstPlugin.GetOutputPlugList(outputPlugs);
  660. CString name;
  661. for(auto plug : outputPlugs)
  662. {
  663. if(plug != nullptr)
  664. {
  665. name.Format(_T("FX%02d: "), plug->m_nSlot + 1);
  666. name += mpt::ToCString(plug->m_pMixStruct->GetName());
  667. m_OutputMenu.AppendMenu(MF_STRING, ID_PLUGSELECT + plug->m_nSlot, name);
  668. } else
  669. {
  670. name = _T("Master Output");
  671. m_OutputMenu.AppendMenu(MF_STRING | MF_GRAYED, NULL, name);
  672. }
  673. }
  674. pInfoMenu->InsertMenu(1, MF_BYPOSITION | MF_POPUP, reinterpret_cast<UINT_PTR>(m_OutputMenu.m_hMenu), _T("Ou&tputs"));
  675. }
  676. void CAbstractVstEditor::UpdateMacroMenu()
  677. {
  678. CMenu *pInfoMenu = m_Menu.GetSubMenu(2);
  679. pInfoMenu->DeleteMenu(2, MF_BYPOSITION);
  680. if(m_MacroMenu.m_hMenu)
  681. {
  682. m_MacroMenu.DestroyMenu();
  683. }
  684. if(!m_MacroMenu.m_hMenu)
  685. {
  686. m_MacroMenu.CreatePopupMenu();
  687. }
  688. CString label, macroName;
  689. for(int nMacro = 0; nMacro < kSFxMacros; nMacro++)
  690. {
  691. int action = 0;
  692. UINT greyed = MF_GRAYED;
  693. const MIDIMacroConfig &midiCfg = m_VstPlugin.GetSoundFile().m_MidiCfg;
  694. const ParameteredMacro macroType = midiCfg.GetParameteredMacroType(nMacro);
  695. if(macroType == kSFxUnused)
  696. {
  697. macroName = _T("Unused. Learn Param...");
  698. action= ID_LEARN_MACRO_FROM_PLUGGUI + nMacro;
  699. greyed = 0;
  700. } else
  701. {
  702. macroName = midiCfg.GetParameteredMacroName(nMacro, &m_VstPlugin);
  703. if(macroType != kSFxPlugParam || macroName.Left(3) != _T("N/A"))
  704. {
  705. greyed = 0;
  706. }
  707. }
  708. label.Format(_T("SF%X: "), nMacro);
  709. label += macroName;
  710. m_MacroMenu.AppendMenu(MF_STRING | greyed, action, label);
  711. }
  712. pInfoMenu->InsertMenu(2, MF_BYPOSITION | MF_POPUP, reinterpret_cast<UINT_PTR>(m_MacroMenu.m_hMenu), _T("&Macros"));
  713. }
  714. void CAbstractVstEditor::UpdateOptionsMenu()
  715. {
  716. if(m_OptionsMenu.m_hMenu)
  717. m_OptionsMenu.DestroyMenu();
  718. CInputHandler *ih = CMainFrame::GetInputHandler();
  719. m_OptionsMenu.CreatePopupMenu();
  720. //Bypass
  721. m_OptionsMenu.AppendMenu(MF_STRING | (m_VstPlugin.IsBypassed() ? MF_CHECKED : 0),
  722. ID_PLUG_BYPASS, ih->GetKeyTextFromCommand(kcVSTGUIBypassPlug, _T("&Bypass Plugin")));
  723. //Record Params
  724. m_OptionsMenu.AppendMenu(MF_STRING | (m_VstPlugin.m_recordAutomation ? MF_CHECKED : 0),
  725. ID_PLUG_RECORDAUTOMATION, ih->GetKeyTextFromCommand(kcVSTGUIToggleRecordParams, _T("Record &Parameter Changes")));
  726. //Record MIDI Out
  727. m_OptionsMenu.AppendMenu(MF_STRING | (m_VstPlugin.m_recordMIDIOut ? MF_CHECKED : 0),
  728. ID_PLUG_RECORD_MIDIOUT, ih->GetKeyTextFromCommand(kcVSTGUIToggleRecordMIDIOut, _T("Record &MIDI Out to Pattern Editor")));
  729. //Pass on keypresses
  730. m_OptionsMenu.AppendMenu(MF_STRING | (m_VstPlugin.m_passKeypressesToPlug ? MF_CHECKED : 0),
  731. ID_PLUG_PASSKEYS, ih->GetKeyTextFromCommand(kcVSTGUIToggleSendKeysToPlug, _T("Pass &Keys to Plugin")));
  732. m_Menu.DeleteMenu(3, MF_BYPOSITION);
  733. m_Menu.InsertMenu(3, MF_BYPOSITION | MF_POPUP, reinterpret_cast<UINT_PTR>(m_OptionsMenu.m_hMenu), _T("&Options"));
  734. }
  735. void CAbstractVstEditor::OnToggleEditor(UINT nID)
  736. {
  737. CModDoc *pModDoc = m_VstPlugin.GetModDoc();
  738. if(pModDoc)
  739. {
  740. pModDoc->TogglePluginEditor(nID - ID_PLUGSELECT);
  741. }
  742. }
  743. void CAbstractVstEditor::OnInitMenu(CMenu* /*pMenu*/)
  744. {
  745. SetupMenu();
  746. }
  747. bool CAbstractVstEditor::CheckInstrument(INSTRUMENTINDEX ins) const
  748. {
  749. const CSoundFile &sndFile = m_VstPlugin.GetSoundFile();
  750. if(ins != INSTRUMENTINDEX_INVALID && ins < MAX_INSTRUMENTS && sndFile.Instruments[ins] != nullptr)
  751. {
  752. return (sndFile.Instruments[ins]->nMixPlug) == (m_VstPlugin.m_nSlot + 1);
  753. }
  754. return false;
  755. }
  756. INSTRUMENTINDEX CAbstractVstEditor::GetBestInstrumentCandidate() const
  757. {
  758. // First try current instrument:
  759. const CModDoc *modDoc = m_VstPlugin.GetModDoc();
  760. POSITION pos = modDoc->GetFirstViewPosition();
  761. while(pos != NULL)
  762. {
  763. CModControlView *pView = dynamic_cast<CModControlView *>(modDoc->GetNextView(pos));
  764. if(pView != nullptr && pView->GetDocument() == modDoc)
  765. {
  766. INSTRUMENTINDEX ins = static_cast<INSTRUMENTINDEX>(pView->GetInstrumentChange());
  767. if(CheckInstrument(ins))
  768. return ins;
  769. }
  770. }
  771. // Just take the first instrument that points to this plug..
  772. return modDoc->HasInstrumentForPlugin(m_VstPlugin.m_nSlot);
  773. }
  774. void CAbstractVstEditor::OnSetInputInstrument(UINT nID)
  775. {
  776. m_nInstrument = static_cast<INSTRUMENTINDEX>(nID - ID_SELECTINST);
  777. }
  778. void CAbstractVstEditor::OnCreateInstrument()
  779. {
  780. if(m_VstPlugin.GetModDoc() != nullptr)
  781. {
  782. INSTRUMENTINDEX instr = m_VstPlugin.GetModDoc()->InsertInstrumentForPlugin(m_VstPlugin.GetSlot());
  783. if(instr != INSTRUMENTINDEX_INVALID) m_nInstrument = instr;
  784. }
  785. }
  786. void CAbstractVstEditor::PrepareToLearnMacro(UINT nID)
  787. {
  788. m_nLearnMacro = (nID-ID_LEARN_MACRO_FROM_PLUGGUI);
  789. //Now we wait for a param to be touched. We'll get the message from the VST Plug Manager.
  790. //Then pModDoc->LearnMacro(macro, param) is called
  791. }
  792. void CAbstractVstEditor::SetLearnMacro(int inMacro)
  793. {
  794. if (inMacro < kSFxMacros)
  795. {
  796. m_nLearnMacro=inMacro;
  797. }
  798. }
  799. int CAbstractVstEditor::GetLearnMacro()
  800. {
  801. return m_nLearnMacro;
  802. }
  803. void CAbstractVstEditor::OnMove(int, int)
  804. {
  805. if(IsWindowVisible())
  806. {
  807. StoreWindowPos();
  808. }
  809. }
  810. void CAbstractVstEditor::StoreWindowPos()
  811. {
  812. if(m_hWnd)
  813. {
  814. WINDOWPLACEMENT wnd;
  815. wnd.length = sizeof(WINDOWPLACEMENT);
  816. GetWindowPlacement(&wnd);
  817. m_VstPlugin.SetEditorPos(wnd.rcNormalPosition.left, wnd.rcNormalPosition.top);
  818. }
  819. }
  820. void CAbstractVstEditor::RestoreWindowPos()
  821. {
  822. // Restore previous editor position
  823. int32 editorX, editorY;
  824. m_VstPlugin.GetEditorPos(editorX, editorY);
  825. if(editorX != int32_min && editorY != int32_min)
  826. {
  827. WINDOWPLACEMENT wnd;
  828. wnd.length = sizeof(wnd);
  829. GetWindowPlacement(&wnd);
  830. wnd.showCmd = SW_SHOWNOACTIVATE;
  831. CRect rect = wnd.rcNormalPosition;
  832. rect.MoveToXY(editorX, editorY);
  833. wnd.rcNormalPosition = rect;
  834. SetWindowPlacement(&wnd);
  835. }
  836. }
  837. #endif // NO_PLUGINS
  838. OPENMPT_NAMESPACE_END