SelectPluginDialog.cpp 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868
  1. /*
  2. * SelectPluginDialog.cpp
  3. * ----------------------
  4. * Purpose: Dialog for adding plugins to a song.
  5. * Notes : (currently none)
  6. * Authors: Olivier Lapicque
  7. * OpenMPT Devs
  8. * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
  9. */
  10. #include "stdafx.h"
  11. #ifndef NO_PLUGINS
  12. #include "Mptrack.h"
  13. #include "Mainfrm.h"
  14. #include "InputHandler.h"
  15. #include "ImageLists.h"
  16. #include "Moddoc.h"
  17. #include "../common/mptStringBuffer.h"
  18. #include "FileDialog.h"
  19. #include "../soundlib/plugins/PluginManager.h"
  20. #include "../soundlib/plugins/PlugInterface.h"
  21. #include "SelectPluginDialog.h"
  22. #include "../pluginBridge/BridgeWrapper.h"
  23. #include "FolderScanner.h"
  24. OPENMPT_NAMESPACE_BEGIN
  25. /////////////////////////////////////////////////////////////////////////////////
  26. // Plugin selection dialog
  27. BEGIN_MESSAGE_MAP(CSelectPluginDlg, ResizableDialog)
  28. ON_NOTIFY(TVN_SELCHANGED, IDC_TREE1, &CSelectPluginDlg::OnSelChanged)
  29. ON_NOTIFY(NM_DBLCLK, IDC_TREE1, &CSelectPluginDlg::OnSelDblClk)
  30. ON_COMMAND(IDC_BUTTON1, &CSelectPluginDlg::OnAddPlugin)
  31. ON_COMMAND(IDC_BUTTON3, &CSelectPluginDlg::OnScanFolder)
  32. ON_COMMAND(IDC_BUTTON2, &CSelectPluginDlg::OnRemovePlugin)
  33. ON_COMMAND(IDC_CHECK1, &CSelectPluginDlg::OnSetBridge)
  34. ON_COMMAND(IDC_CHECK2, &CSelectPluginDlg::OnSetBridge)
  35. ON_COMMAND(IDC_CHECK3, &CSelectPluginDlg::OnSetBridge)
  36. ON_EN_CHANGE(IDC_NAMEFILTER, &CSelectPluginDlg::OnNameFilterChanged)
  37. ON_EN_CHANGE(IDC_PLUGINTAGS, &CSelectPluginDlg::OnPluginTagsChanged)
  38. END_MESSAGE_MAP()
  39. void CSelectPluginDlg::DoDataExchange(CDataExchange* pDX)
  40. {
  41. ResizableDialog::DoDataExchange(pDX);
  42. DDX_Control(pDX, IDC_TREE1, m_treePlugins);
  43. DDX_Control(pDX, IDC_CHECK1, m_chkBridge);
  44. DDX_Control(pDX, IDC_CHECK2, m_chkShare);
  45. DDX_Control(pDX, IDC_CHECK3, m_chkLegacyBridge);
  46. }
  47. CSelectPluginDlg::CSelectPluginDlg(CModDoc *pModDoc, PLUGINDEX pluginSlot, CWnd *parent)
  48. : ResizableDialog(IDD_SELECTMIXPLUGIN, parent)
  49. , m_pModDoc(pModDoc)
  50. , m_nPlugSlot(pluginSlot)
  51. {
  52. if(m_pModDoc && 0 <= m_nPlugSlot && m_nPlugSlot < MAX_MIXPLUGINS)
  53. {
  54. m_pPlugin = &(pModDoc->GetSoundFile().m_MixPlugins[m_nPlugSlot]);
  55. }
  56. CMainFrame::GetInputHandler()->Bypass(true);
  57. }
  58. CSelectPluginDlg::~CSelectPluginDlg()
  59. {
  60. CMainFrame::GetInputHandler()->Bypass(false);
  61. }
  62. BOOL CSelectPluginDlg::OnInitDialog()
  63. {
  64. DWORD dwRemove = TVS_EDITLABELS|TVS_SINGLEEXPAND;
  65. DWORD dwAdd = TVS_HASLINES | TVS_LINESATROOT | TVS_HASBUTTONS | TVS_SHOWSELALWAYS;
  66. ResizableDialog::OnInitDialog();
  67. m_treePlugins.ModifyStyle(dwRemove, dwAdd);
  68. m_treePlugins.SetImageList(&CMainFrame::GetMainFrame()->m_MiscIcons, TVSIL_NORMAL);
  69. if (m_pPlugin)
  70. {
  71. CString targetSlot = MPT_CFORMAT("&Put in FX{}")(mpt::cfmt::dec0<2>(m_nPlugSlot + 1));
  72. SetDlgItemText(IDOK, targetSlot);
  73. ::EnableWindow(::GetDlgItem(m_hWnd, IDOK), TRUE);
  74. } else
  75. {
  76. ::EnableWindow(::GetDlgItem(m_hWnd, IDOK), FALSE);
  77. }
  78. const int dpiX = Util::GetDPIx(m_hWnd);
  79. const int dpiY = Util::GetDPIy(m_hWnd);
  80. CRect rect
  81. (
  82. CPoint(MulDiv(TrackerSettings::Instance().gnPlugWindowX, dpiX, 96), MulDiv(TrackerSettings::Instance().gnPlugWindowY, dpiY, 96)),
  83. CSize(MulDiv(TrackerSettings::Instance().gnPlugWindowWidth, dpiX, 96), MulDiv(TrackerSettings::Instance().gnPlugWindowHeight, dpiY, 96))
  84. );
  85. ::MapWindowPoints(GetParent()->m_hWnd, HWND_DESKTOP, (CPoint *)&rect, 2);
  86. WINDOWPLACEMENT wnd;
  87. wnd.length = sizeof(wnd);
  88. GetWindowPlacement(&wnd);
  89. wnd.showCmd = SW_SHOW;
  90. wnd.rcNormalPosition = rect;
  91. SetWindowPlacement(&wnd);
  92. UpdatePluginsList();
  93. OnSelChanged(NULL, NULL);
  94. return TRUE;
  95. }
  96. void CSelectPluginDlg::OnOK()
  97. {
  98. if(m_pPlugin == nullptr)
  99. {
  100. ResizableDialog::OnOK();
  101. return;
  102. }
  103. bool changed = false;
  104. CVstPluginManager *pManager = theApp.GetPluginManager();
  105. VSTPluginLib *pNewPlug = GetSelectedPlugin();
  106. VSTPluginLib *pFactory = nullptr;
  107. IMixPlugin *pCurrentPlugin = nullptr;
  108. if(m_pPlugin)
  109. pCurrentPlugin = m_pPlugin->pMixPlugin;
  110. if((pManager) && (pManager->IsValidPlugin(pNewPlug)))
  111. pFactory = pNewPlug;
  112. if (pFactory)
  113. {
  114. // Plugin selected
  115. if ((!pCurrentPlugin) || &pCurrentPlugin->GetPluginFactory() != pFactory)
  116. {
  117. CriticalSection cs;
  118. // Destroy old plugin, if there was one.
  119. const auto oldOutput = m_pPlugin->GetOutputPlugin();
  120. m_pPlugin->Destroy();
  121. // Initialize plugin info
  122. MemsetZero(m_pPlugin->Info);
  123. if(oldOutput != PLUGINDEX_INVALID)
  124. m_pPlugin->SetOutputPlugin(oldOutput);
  125. m_pPlugin->Info.dwPluginId1 = pFactory->pluginId1;
  126. m_pPlugin->Info.dwPluginId2 = pFactory->pluginId2;
  127. m_pPlugin->editorX = m_pPlugin->editorY = int32_min;
  128. m_pPlugin->SetAutoSuspend(TrackerSettings::Instance().enableAutoSuspend);
  129. #ifdef MPT_WITH_VST
  130. if(m_pPlugin->Info.dwPluginId1 == Vst::kEffectMagic)
  131. {
  132. switch(m_pPlugin->Info.dwPluginId2)
  133. {
  134. // Enable drymix by default for these known plugins
  135. case Vst::FourCC("Scop"):
  136. m_pPlugin->SetWetMix();
  137. break;
  138. }
  139. }
  140. #endif // MPT_WITH_VST
  141. m_pPlugin->Info.szName = pFactory->libraryName.ToLocale();
  142. m_pPlugin->Info.szLibraryName = pFactory->libraryName.ToUTF8();
  143. cs.Leave();
  144. // Now, create the new plugin
  145. if(pManager && m_pModDoc)
  146. {
  147. pManager->CreateMixPlugin(*m_pPlugin, m_pModDoc->GetSoundFile());
  148. if (m_pPlugin->pMixPlugin)
  149. {
  150. IMixPlugin *p = m_pPlugin->pMixPlugin;
  151. const CString name = p->GetDefaultEffectName();
  152. if(!name.IsEmpty())
  153. {
  154. m_pPlugin->Info.szName = mpt::ToCharset(mpt::Charset::Locale, name);
  155. }
  156. // Check if plugin slot is already assigned to any instrument, and if not, create one.
  157. if(p->IsInstrument() && m_pModDoc->HasInstrumentForPlugin(m_nPlugSlot) == INSTRUMENTINDEX_INVALID)
  158. {
  159. m_pModDoc->InsertInstrumentForPlugin(m_nPlugSlot);
  160. }
  161. } else
  162. {
  163. MemsetZero(m_pPlugin->Info);
  164. }
  165. }
  166. changed = true;
  167. }
  168. } else if(m_pPlugin->IsValidPlugin())
  169. {
  170. // No effect
  171. if(m_pModDoc)
  172. changed = m_pModDoc->RemovePlugin(m_nPlugSlot);
  173. }
  174. //remember window size:
  175. SaveWindowPos();
  176. if(changed)
  177. {
  178. if(m_pPlugin->Info.dwPluginId2)
  179. TrackerSettings::Instance().gnPlugWindowLast = m_pPlugin->Info.dwPluginId2;
  180. if(m_pModDoc)
  181. {
  182. m_pModDoc->UpdateAllViews(nullptr, PluginHint(static_cast<PLUGINDEX>(m_nPlugSlot + 1)).Info().Names());
  183. }
  184. ResizableDialog::OnOK();
  185. } else
  186. {
  187. ResizableDialog::OnCancel();
  188. }
  189. }
  190. void CSelectPluginDlg::OnCancel()
  191. {
  192. //remember window size:
  193. SaveWindowPos();
  194. ResizableDialog::OnCancel();
  195. }
  196. VSTPluginLib* CSelectPluginDlg::GetSelectedPlugin()
  197. {
  198. HTREEITEM item = m_treePlugins.GetSelectedItem();
  199. if(item)
  200. return reinterpret_cast<VSTPluginLib *>(m_treePlugins.GetItemData(item));
  201. else
  202. return nullptr;
  203. }
  204. void CSelectPluginDlg::SaveWindowPos() const
  205. {
  206. WINDOWPLACEMENT wnd;
  207. wnd.length = sizeof(WINDOWPLACEMENT);
  208. GetWindowPlacement(&wnd);
  209. CRect rect = wnd.rcNormalPosition;
  210. ::MapWindowPoints(HWND_DESKTOP, GetParent()->m_hWnd, (CPoint *)&rect, 2);
  211. const int dpiX = Util::GetDPIx(m_hWnd);
  212. const int dpiY = Util::GetDPIy(m_hWnd);
  213. TrackerSettings::Instance().gnPlugWindowX = MulDiv(rect.left, 96, dpiX);
  214. TrackerSettings::Instance().gnPlugWindowY = MulDiv(rect.top, 96, dpiY);
  215. TrackerSettings::Instance().gnPlugWindowWidth = MulDiv(rect.Width(), 96, dpiY);
  216. TrackerSettings::Instance().gnPlugWindowHeight = MulDiv(rect.Height(), 96, dpiX);
  217. }
  218. BOOL CSelectPluginDlg::PreTranslateMessage(MSG *pMsg)
  219. {
  220. // Use up/down keys to navigate in tree view, even if search field is focussed.
  221. if(pMsg != nullptr && pMsg->message == WM_KEYDOWN && (pMsg->wParam == VK_UP || pMsg->wParam == VK_DOWN) && GetFocus() != &m_treePlugins)
  222. {
  223. HTREEITEM selItem = m_treePlugins.GetSelectedItem();
  224. if(selItem == nullptr)
  225. {
  226. selItem = m_treePlugins.GetRootItem();
  227. }
  228. while((selItem = m_treePlugins.GetNextItem(selItem, pMsg->wParam == VK_UP ? TVGN_PREVIOUSVISIBLE : TVGN_NEXTVISIBLE)) != nullptr)
  229. {
  230. int nImage, nSelectedImage;
  231. m_treePlugins.GetItemImage(selItem, nImage, nSelectedImage);
  232. if(nImage != IMAGE_FOLDER)
  233. {
  234. m_treePlugins.SelectItem(selItem);
  235. m_treePlugins.EnsureVisible(selItem);
  236. return TRUE;
  237. }
  238. }
  239. return TRUE;
  240. }
  241. return ResizableDialog::PreTranslateMessage(pMsg);
  242. }
  243. void CSelectPluginDlg::OnNameFilterChanged()
  244. {
  245. // Update name filter text
  246. m_nameFilter = mpt::ToLowerCase(GetWindowTextUnicode(*GetDlgItem(IDC_NAMEFILTER)));
  247. UpdatePluginsList();
  248. }
  249. void CSelectPluginDlg::UpdatePluginsList(const VSTPluginLib *forceSelect)
  250. {
  251. CVstPluginManager *pManager = theApp.GetPluginManager();
  252. m_treePlugins.SetRedraw(FALSE);
  253. m_treePlugins.DeleteAllItems();
  254. static constexpr struct
  255. {
  256. VSTPluginLib::PluginCategory category;
  257. const TCHAR *description;
  258. } categories[] =
  259. {
  260. { VSTPluginLib::catEffect, _T("Audio Effects") },
  261. { VSTPluginLib::catGenerator, _T("Tone Generators") },
  262. { VSTPluginLib::catRestoration, _T("Audio Restauration") },
  263. { VSTPluginLib::catSurroundFx, _T("Surround Effects") },
  264. { VSTPluginLib::catRoomFx, _T("Room Effects") },
  265. { VSTPluginLib::catSpacializer, _T("Spacializers") },
  266. { VSTPluginLib::catMastering, _T("Mastering Plugins") },
  267. { VSTPluginLib::catAnalysis, _T("Analysis Plugins") },
  268. { VSTPluginLib::catOfflineProcess, _T("Offline Processing") },
  269. { VSTPluginLib::catShell, _T("Shell Plugins") },
  270. { VSTPluginLib::catUnknown, _T("Unsorted") },
  271. { VSTPluginLib::catDMO, _T("DirectX Media Audio Effects") },
  272. { VSTPluginLib::catSynth, _T("Instrument Plugins") },
  273. { VSTPluginLib::catHidden, _T("Legacy Plugins") },
  274. };
  275. const HTREEITEM noPlug = AddTreeItem(_T("No plugin (empty slot)"), IMAGE_NOPLUGIN, false);
  276. HTREEITEM currentPlug = noPlug;
  277. std::bitset<VSTPluginLib::numCategories> categoryUsed;
  278. HTREEITEM categoryFolders[VSTPluginLib::numCategories];
  279. for(const auto &cat : categories)
  280. {
  281. categoryFolders[cat.category] = AddTreeItem(cat.description, IMAGE_FOLDER, false);
  282. }
  283. enum PlugMatchQuality
  284. {
  285. kNoMatch,
  286. kSameIdAsLast,
  287. kSameIdAsLastWithPlatformMatch,
  288. kSameIdAsCurrent,
  289. kFoundCurrentPlugin,
  290. };
  291. PlugMatchQuality foundPlugin = kNoMatch;
  292. const int32 lastPluginID = TrackerSettings::Instance().gnPlugWindowLast;
  293. const bool nameFilterActive = !m_nameFilter.empty();
  294. const auto currentTags = mpt::String::Split<mpt::ustring>(m_nameFilter, U_(" "));
  295. if(pManager)
  296. {
  297. bool first = true;
  298. for(auto p : *pManager)
  299. {
  300. MPT_ASSERT(p);
  301. const VSTPluginLib &plug = *p;
  302. if(plug.category == VSTPluginLib::catHidden && (m_pPlugin == nullptr || m_pPlugin->pMixPlugin == nullptr || &m_pPlugin->pMixPlugin->GetPluginFactory() != p))
  303. continue;
  304. if(nameFilterActive)
  305. {
  306. // Apply name filter
  307. bool matches = false;
  308. // Search in plugin names
  309. {
  310. mpt::ustring displayName = mpt::ToLowerCase(plug.libraryName.ToUnicode());
  311. if(displayName.find(m_nameFilter, 0) != displayName.npos)
  312. {
  313. matches = true;
  314. }
  315. }
  316. // Search in plugin tags
  317. if(!matches)
  318. {
  319. mpt::ustring tags = mpt::ToLowerCase(plug.tags);
  320. for(const auto &tag : currentTags)
  321. {
  322. if(!tag.empty() && tags.find(tag, 0) != tags.npos)
  323. {
  324. matches = true;
  325. break;
  326. }
  327. }
  328. }
  329. // Search in plugin vendors
  330. if(!matches)
  331. {
  332. mpt::ustring vendor = mpt::ToLowerCase(mpt::ToUnicode(plug.vendor));
  333. if(vendor.find(m_nameFilter, 0) != vendor.npos)
  334. {
  335. matches = true;
  336. }
  337. }
  338. if(!matches) continue;
  339. }
  340. CString title = plug.libraryName.ToCString();
  341. #ifdef MPT_WITH_VST
  342. if(!plug.IsNativeFromCache())
  343. {
  344. title += MPT_CFORMAT(" ({})")(plug.GetDllArchNameUser());
  345. }
  346. #endif // MPT_WITH_VST
  347. HTREEITEM h = AddTreeItem(title, plug.isInstrument ? IMAGE_PLUGININSTRUMENT : IMAGE_EFFECTPLUGIN, true, categoryFolders[plug.category], reinterpret_cast<LPARAM>(&plug));
  348. categoryUsed[plug.category] = true;
  349. if(nameFilterActive)
  350. {
  351. // If filter is active, expand nodes.
  352. m_treePlugins.EnsureVisible(h);
  353. if(first)
  354. {
  355. first = false;
  356. m_treePlugins.SelectItem(h);
  357. }
  358. }
  359. if(forceSelect != nullptr && &plug == forceSelect)
  360. {
  361. // Forced selection (e.g. just after add plugin)
  362. currentPlug = h;
  363. foundPlugin = kFoundCurrentPlugin;
  364. }
  365. if(m_pPlugin && foundPlugin < kFoundCurrentPlugin)
  366. {
  367. //Which plugin should be selected?
  368. if(m_pPlugin->pMixPlugin)
  369. {
  370. // Current slot's plugin
  371. IMixPlugin *pPlugin = m_pPlugin->pMixPlugin;
  372. if (&pPlugin->GetPluginFactory() == &plug)
  373. {
  374. currentPlug = h;
  375. foundPlugin = kFoundCurrentPlugin;
  376. }
  377. } else if(m_pPlugin->Info.dwPluginId1 != 0 || m_pPlugin->Info.dwPluginId2 != 0)
  378. {
  379. // Plugin with matching ID to current slot's plug
  380. if(plug.pluginId1 == m_pPlugin->Info.dwPluginId1
  381. && plug.pluginId2 == m_pPlugin->Info.dwPluginId2)
  382. {
  383. currentPlug = h;
  384. foundPlugin = kSameIdAsCurrent;
  385. }
  386. } else if(plug.pluginId2 == lastPluginID && foundPlugin < kSameIdAsLastWithPlatformMatch)
  387. {
  388. // Previously selected plugin
  389. #ifdef MPT_WITH_VST
  390. foundPlugin = plug.IsNativeFromCache() ? kSameIdAsLastWithPlatformMatch : kSameIdAsLast;
  391. #else // !MPT_WITH_VST
  392. foundPlugin = kSameIdAsLastWithPlatformMatch;
  393. #endif // MPT_WITH_VST
  394. currentPlug = h;
  395. }
  396. }
  397. }
  398. }
  399. // Remove empty categories
  400. for(size_t i = 0; i < std::size(categoryFolders); i++)
  401. {
  402. if(!categoryUsed[i])
  403. {
  404. m_treePlugins.DeleteItem(categoryFolders[i]);
  405. }
  406. }
  407. m_treePlugins.SetRedraw(TRUE);
  408. if(!nameFilterActive || currentPlug != noPlug)
  409. {
  410. m_treePlugins.SelectItem(currentPlug);
  411. }
  412. m_treePlugins.SetItemState(currentPlug, TVIS_BOLD, TVIS_BOLD);
  413. m_treePlugins.EnsureVisible(currentPlug);
  414. }
  415. HTREEITEM CSelectPluginDlg::AddTreeItem(const TCHAR *title, int image, bool sort, HTREEITEM hParent, LPARAM lParam)
  416. {
  417. return m_treePlugins.InsertItem(
  418. TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_PARAM | TVIF_TEXT,
  419. title,
  420. image, image,
  421. 0, 0,
  422. lParam,
  423. hParent,
  424. (sort ? TVI_SORT : TVI_LAST));
  425. }
  426. void CSelectPluginDlg::OnSelDblClk(NMHDR *, LRESULT *result)
  427. {
  428. if(m_pPlugin == nullptr) return;
  429. HTREEITEM hSel = m_treePlugins.GetSelectedItem();
  430. int nImage, nSelectedImage;
  431. m_treePlugins.GetItemImage(hSel, nImage, nSelectedImage);
  432. if ((hSel) && (nImage != IMAGE_FOLDER)) OnOK();
  433. if (result) *result = 0;
  434. }
  435. void CSelectPluginDlg::OnSelChanged(NMHDR *, LRESULT *result)
  436. {
  437. CVstPluginManager *pManager = theApp.GetPluginManager();
  438. VSTPluginLib *pPlug = GetSelectedPlugin();
  439. bool showBoxes = false;
  440. BOOL enableTagsTextBox = FALSE;
  441. BOOL enableRemoveButton = FALSE;
  442. if (pManager != nullptr && pManager->IsValidPlugin(pPlug))
  443. {
  444. if(pPlug->vendor.IsEmpty())
  445. SetDlgItemText(IDC_VENDOR, _T(""));
  446. else
  447. SetDlgItemText(IDC_VENDOR, _T("Vendor: ") + pPlug->vendor);
  448. if(pPlug->dllPath.empty())
  449. SetDlgItemText(IDC_TEXT_CURRENT_VSTPLUG, _T("Built-in plugin"));
  450. else
  451. SetDlgItemText(IDC_TEXT_CURRENT_VSTPLUG, pPlug->dllPath.ToCString());
  452. SetDlgItemText(IDC_PLUGINTAGS, mpt::ToCString(pPlug->tags));
  453. enableRemoveButton = pPlug->isBuiltIn ? FALSE : TRUE;
  454. #ifdef MPT_WITH_VST
  455. if(pPlug->pluginId1 == Vst::kEffectMagic && !pPlug->isBuiltIn)
  456. {
  457. bool isBridgeAvailable =
  458. ((pPlug->GetDllArch() == PluginArch_x86) && IsComponentAvailable(pluginBridge_x86))
  459. ||
  460. ((pPlug->GetDllArch() == PluginArch_x86) && IsComponentAvailable(pluginBridgeLegacy_x86))
  461. ||
  462. ((pPlug->GetDllArch() == PluginArch_amd64) && IsComponentAvailable(pluginBridge_amd64))
  463. ||
  464. ((pPlug->GetDllArch() == PluginArch_amd64) && IsComponentAvailable(pluginBridgeLegacy_amd64))
  465. #if defined(MPT_WITH_WINDOWS10)
  466. ||
  467. ((pPlug->GetDllArch() == PluginArch_arm) && IsComponentAvailable(pluginBridge_arm))
  468. ||
  469. ((pPlug->GetDllArch() == PluginArch_arm) && IsComponentAvailable(pluginBridgeLegacy_arm))
  470. ||
  471. ((pPlug->GetDllArch() == PluginArch_arm64) && IsComponentAvailable(pluginBridge_arm64))
  472. ||
  473. ((pPlug->GetDllArch() == PluginArch_arm64) && IsComponentAvailable(pluginBridgeLegacy_arm64))
  474. #endif // MPT_WITH_WINDOWS10
  475. ;
  476. if(TrackerSettings::Instance().bridgeAllPlugins || !isBridgeAvailable)
  477. {
  478. m_chkBridge.EnableWindow(FALSE);
  479. m_chkBridge.SetCheck(isBridgeAvailable ? BST_CHECKED : BST_UNCHECKED);
  480. } else
  481. {
  482. bool native = pPlug->IsNative();
  483. m_chkBridge.EnableWindow(native ? TRUE : FALSE);
  484. m_chkBridge.SetCheck((pPlug->useBridge || !native) ? BST_CHECKED : BST_UNCHECKED);
  485. }
  486. m_chkShare.SetCheck(pPlug->shareBridgeInstance ? BST_CHECKED : BST_UNCHECKED);
  487. m_chkShare.EnableWindow(m_chkBridge.GetCheck() != BST_UNCHECKED);
  488. m_chkLegacyBridge.SetCheck((!pPlug->modernBridge) ? BST_CHECKED : BST_UNCHECKED);
  489. m_chkLegacyBridge.EnableWindow(m_chkBridge.GetCheck() != BST_UNCHECKED);
  490. showBoxes = true;
  491. }
  492. enableTagsTextBox = TRUE;
  493. #endif // MPT_WITH_VST
  494. } else
  495. {
  496. SetDlgItemText(IDC_VENDOR, _T(""));
  497. SetDlgItemText(IDC_TEXT_CURRENT_VSTPLUG, _T(""));
  498. SetDlgItemText(IDC_PLUGINTAGS, _T(""));
  499. }
  500. GetDlgItem(IDC_PLUGINTAGS)->EnableWindow(enableTagsTextBox);
  501. GetDlgItem(IDC_BUTTON2)->EnableWindow(enableRemoveButton);
  502. if(!showBoxes)
  503. {
  504. m_chkBridge.EnableWindow(FALSE);
  505. m_chkShare.EnableWindow(FALSE);
  506. m_chkLegacyBridge.EnableWindow(FALSE);
  507. m_chkBridge.SetCheck(BST_UNCHECKED);
  508. m_chkShare.SetCheck(BST_UNCHECKED);
  509. m_chkLegacyBridge.SetCheck(BST_UNCHECKED);
  510. }
  511. if (result) *result = 0;
  512. }
  513. #ifdef MPT_WITH_VST
  514. namespace
  515. {
  516. // TODO: Keep these lists up-to-date.
  517. constexpr struct
  518. {
  519. int32 id1;
  520. int32 id2;
  521. const char *name;
  522. const char *problem;
  523. } ProblematicPlugins[] =
  524. {
  525. {Vst::kEffectMagic, Vst::FourCC("mdaC"), "MDA Degrade", "* Old versions of this plugin can crash OpenMPT.\nEnsure that you have the latest version of this plugin."},
  526. {Vst::kEffectMagic, Vst::FourCC("fV2s"), "Farbrausch V2", "* This plugin can cause OpenMPT to freeze if being used in a combination with various other plugins.\nIt is recommended to use V2 only through the Plugin Bridge."},
  527. {Vst::kEffectMagic, Vst::FourCC("frV2"), "Farbrausch V2", "* This plugin can cause OpenMPT to freeze if being used in a combination with various other plugins.\nIt is recommended to use V2 only through the Plugin Bridge."},
  528. {Vst::kEffectMagic, Vst::FourCC("MMID"), "MIDI Input Output", "* The MIDI Input / Output plugin is now built right into OpenMPT and should not be loaded from an external file."},
  529. };
  530. // Plugins that should always be bridged or require a specific bridge mode.
  531. constexpr struct
  532. {
  533. int32 id1;
  534. int32 id2;
  535. bool useBridge;
  536. bool shareInstance;
  537. bool modernBridge;
  538. } ForceBridgePlugins[] =
  539. {
  540. {Vst::kEffectMagic, Vst::FourCC("fV2s"), true, false, false}, // V2 freezes on shutdown if there's more than one instance per process
  541. {Vst::kEffectMagic, Vst::FourCC("frV2"), true, false, false}, // ditto
  542. {Vst::kEffectMagic, Vst::FourCC("SKV3"), false, true, false}, // SideKick v3 always has to run in a shared instance
  543. {Vst::kEffectMagic, Vst::FourCC("YWS!"), false, true, false}, // You Wa Shock ! always has to run in a shared instance
  544. {Vst::kEffectMagic, Vst::FourCC("S1Vs"), mpt::arch_bits == 64, true, false}, // Synth1 64-bit has an issue with pointers using the high 32 bits, hence must use the legacy bridge without high-entropy heap
  545. };
  546. } // namespace
  547. #endif // MPT_WITH_VST
  548. bool CSelectPluginDlg::VerifyPlugin(VSTPluginLib *plug, CWnd *parent)
  549. {
  550. #ifdef MPT_WITH_VST
  551. for(const auto &p : ProblematicPlugins)
  552. {
  553. if(p.id2 == plug->pluginId2 && p.id1 == plug->pluginId1)
  554. {
  555. std::string s = MPT_AFORMAT("WARNING: This plugin has been identified as {},\nwhich is known to have the following problem with OpenMPT:\n\n{}\n\nWould you still like to add this plugin to the library?")(p.name, p.problem);
  556. if(Reporting::Confirm(s, false, false, parent) == cnfNo)
  557. {
  558. return false;
  559. }
  560. break;
  561. }
  562. }
  563. for(const auto &p : ForceBridgePlugins)
  564. {
  565. if(p.id2 == plug->pluginId2 && p.id1 == plug->pluginId1)
  566. {
  567. plug->useBridge = p.useBridge;
  568. plug->shareBridgeInstance = p.shareInstance;
  569. if(!p.modernBridge)
  570. plug->modernBridge = false;
  571. plug->WriteToCache();
  572. break;
  573. }
  574. }
  575. #else // !MPT_WITH_VST
  576. MPT_UNREFERENCED_PARAMETER(plug);
  577. MPT_UNREFERENCED_PARAMETER(parent);
  578. #endif // MPT_WITH_VST
  579. return true;
  580. }
  581. void CSelectPluginDlg::OnAddPlugin()
  582. {
  583. FileDialog dlg = OpenFileDialog()
  584. .AllowMultiSelect()
  585. .DefaultExtension("dll")
  586. .ExtensionFilter("VST Plugins|*.dll;*.vst3||")
  587. .WorkingDirectory(TrackerSettings::Instance().PathPlugins.GetWorkingDir());
  588. if(!dlg.Show(this)) return;
  589. TrackerSettings::Instance().PathPlugins.SetWorkingDir(dlg.GetWorkingDirectory());
  590. CVstPluginManager *plugManager = theApp.GetPluginManager();
  591. if(!plugManager)
  592. return;
  593. VSTPluginLib *plugLib = nullptr;
  594. bool update = false;
  595. for(const auto &file : dlg.GetFilenames())
  596. {
  597. VSTPluginLib *lib = plugManager->AddPlugin(file, TrackerSettings::Instance().BrokenPluginsWorkaroundVSTMaskAllCrashes, mpt::ustring(), false);
  598. if(lib != nullptr)
  599. {
  600. update = true;
  601. if(!VerifyPlugin(lib, this))
  602. {
  603. plugManager->RemovePlugin(lib);
  604. } else
  605. {
  606. plugLib = lib;
  607. // If this plugin was missing anywhere, try loading it
  608. ReloadMissingPlugins(lib);
  609. }
  610. }
  611. }
  612. if(update)
  613. {
  614. // Force selection to last added plug.
  615. UpdatePluginsList(plugLib);
  616. } else
  617. {
  618. Reporting::Error("No valid VST Plugin was selected.");
  619. }
  620. }
  621. void CSelectPluginDlg::OnScanFolder()
  622. {
  623. BrowseForFolder dlg(TrackerSettings::Instance().PathPlugins.GetWorkingDir(), _T("Select a folder that should be scanned for VST plugins (including sub-folders)"));
  624. if(!dlg.Show(this)) return;
  625. TrackerSettings::Instance().PathPlugins.SetWorkingDir(dlg.GetDirectory());
  626. VSTPluginLib *plugLib = ScanPlugins(dlg.GetDirectory(), this);
  627. UpdatePluginsList(plugLib);
  628. // If any of the plugins was missing anywhere, try loading it
  629. for(auto p : *theApp.GetPluginManager())
  630. {
  631. ReloadMissingPlugins(p);
  632. }
  633. }
  634. VSTPluginLib *CSelectPluginDlg::ScanPlugins(const mpt::PathString &path, CWnd *parent)
  635. {
  636. CVstPluginManager *pManager = theApp.GetPluginManager();
  637. VSTPluginLib *plugLib = nullptr;
  638. bool update = false;
  639. CDialog pluginScanDlg;
  640. pluginScanDlg.Create(IDD_SCANPLUGINS, parent);
  641. pluginScanDlg.CenterWindow(parent);
  642. pluginScanDlg.ModifyStyle(0, WS_SYSMENU, WS_SYSMENU);
  643. pluginScanDlg.ShowWindow(SW_SHOW);
  644. FolderScanner scan(path, FolderScanner::kOnlyFiles | FolderScanner::kFindInSubDirectories);
  645. bool maskCrashes = TrackerSettings::Instance().BrokenPluginsWorkaroundVSTMaskAllCrashes;
  646. mpt::PathString fileName;
  647. int files = 0;
  648. while(scan.Next(fileName) && pluginScanDlg.IsWindowVisible())
  649. {
  650. if(!mpt::PathString::CompareNoCase(fileName.GetFileExt(), P_(".dll")))
  651. {
  652. CWnd *text = pluginScanDlg.GetDlgItem(IDC_SCANTEXT);
  653. CString scanStr = _T("Scanning Plugin...\n") + fileName.ToCString();
  654. text->SetWindowText(scanStr);
  655. MSG msg;
  656. while(::PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
  657. {
  658. ::TranslateMessage(&msg);
  659. ::DispatchMessage(&msg);
  660. }
  661. VSTPluginLib *lib = pManager->AddPlugin(fileName, maskCrashes, mpt::ustring(), false);
  662. if(lib)
  663. {
  664. update = true;
  665. if(!VerifyPlugin(lib, parent))
  666. {
  667. pManager->RemovePlugin(lib);
  668. } else
  669. {
  670. plugLib = lib;
  671. files++;
  672. }
  673. }
  674. }
  675. }
  676. if(update)
  677. {
  678. // Force selection to last added plug.
  679. Reporting::Information(MPT_AFORMAT("Found {} plugin{}.")(files, files == 1 ? "" : "s").c_str(), parent);
  680. return plugLib;
  681. } else
  682. {
  683. Reporting::Error("Could not find any valid VST plugins.");
  684. return nullptr;
  685. }
  686. }
  687. // After adding new plugins, check if they were missing in any open songs.
  688. void CSelectPluginDlg::ReloadMissingPlugins(const VSTPluginLib *lib) const
  689. {
  690. CVstPluginManager *plugManager = theApp.GetPluginManager();
  691. auto docs = theApp.GetOpenDocuments();
  692. for(auto &modDoc : docs)
  693. {
  694. CSoundFile &sndFile = modDoc->GetSoundFile();
  695. bool updateDoc = false;
  696. for(auto &plugin : sndFile.m_MixPlugins)
  697. {
  698. if(plugin.pMixPlugin == nullptr
  699. && plugin.Info.dwPluginId1 == lib->pluginId1
  700. && plugin.Info.dwPluginId2 == lib->pluginId2)
  701. {
  702. updateDoc = true;
  703. plugManager->CreateMixPlugin(plugin, sndFile);
  704. if(plugin.pMixPlugin)
  705. {
  706. plugin.pMixPlugin->RestoreAllParameters(plugin.defaultProgram);
  707. }
  708. }
  709. }
  710. if(updateDoc)
  711. {
  712. modDoc->UpdateAllViews(nullptr, PluginHint().Info().Names());
  713. CMainFrame::GetMainFrame()->UpdateTree(modDoc, PluginHint().Info().Names());
  714. }
  715. }
  716. }
  717. void CSelectPluginDlg::OnRemovePlugin()
  718. {
  719. const HTREEITEM pluginToDelete = m_treePlugins.GetSelectedItem();
  720. VSTPluginLib *plugin = GetSelectedPlugin();
  721. CVstPluginManager *plugManager = theApp.GetPluginManager();
  722. if(plugManager && plugin)
  723. {
  724. if(plugManager->RemovePlugin(plugin))
  725. {
  726. m_treePlugins.DeleteItem(pluginToDelete);
  727. }
  728. }
  729. }
  730. void CSelectPluginDlg::OnSetBridge()
  731. {
  732. VSTPluginLib *plug = GetSelectedPlugin();
  733. if(plug)
  734. {
  735. if(m_chkBridge.IsWindowEnabled())
  736. {
  737. // Only update this setting if the current setting isn't an enforced setting (e.g. because plugin isn't native).
  738. // This has the advantage that plugins don't get force-checked when switching between 32-bit and 64-bit versions of OpenMPT.
  739. plug->useBridge = m_chkBridge.GetCheck() != BST_UNCHECKED;
  740. }
  741. m_chkShare.EnableWindow(m_chkBridge.GetCheck() != BST_UNCHECKED);
  742. m_chkLegacyBridge.EnableWindow(m_chkBridge.GetCheck() != BST_UNCHECKED);
  743. plug->shareBridgeInstance = m_chkShare.GetCheck() != BST_UNCHECKED;
  744. plug->modernBridge = m_chkLegacyBridge.GetCheck() == BST_UNCHECKED;
  745. plug->WriteToCache();
  746. }
  747. }
  748. void CSelectPluginDlg::OnPluginTagsChanged()
  749. {
  750. VSTPluginLib *plug = GetSelectedPlugin();
  751. if (plug)
  752. {
  753. plug->tags = GetWindowTextUnicode(*GetDlgItem(IDC_PLUGINTAGS));
  754. }
  755. }
  756. OPENMPT_NAMESPACE_END
  757. #endif // NO_PLUGINS