/* * SelectPluginDialog.cpp * ---------------------- * Purpose: Dialog for adding plugins to a song. * Notes : (currently none) * Authors: Olivier Lapicque * OpenMPT Devs * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. */ #include "stdafx.h" #ifndef NO_PLUGINS #include "Mptrack.h" #include "Mainfrm.h" #include "InputHandler.h" #include "ImageLists.h" #include "Moddoc.h" #include "../common/mptStringBuffer.h" #include "FileDialog.h" #include "../soundlib/plugins/PluginManager.h" #include "../soundlib/plugins/PlugInterface.h" #include "SelectPluginDialog.h" #include "../pluginBridge/BridgeWrapper.h" #include "FolderScanner.h" OPENMPT_NAMESPACE_BEGIN ///////////////////////////////////////////////////////////////////////////////// // Plugin selection dialog BEGIN_MESSAGE_MAP(CSelectPluginDlg, ResizableDialog) ON_NOTIFY(TVN_SELCHANGED, IDC_TREE1, &CSelectPluginDlg::OnSelChanged) ON_NOTIFY(NM_DBLCLK, IDC_TREE1, &CSelectPluginDlg::OnSelDblClk) ON_COMMAND(IDC_BUTTON1, &CSelectPluginDlg::OnAddPlugin) ON_COMMAND(IDC_BUTTON3, &CSelectPluginDlg::OnScanFolder) ON_COMMAND(IDC_BUTTON2, &CSelectPluginDlg::OnRemovePlugin) ON_COMMAND(IDC_CHECK1, &CSelectPluginDlg::OnSetBridge) ON_COMMAND(IDC_CHECK2, &CSelectPluginDlg::OnSetBridge) ON_COMMAND(IDC_CHECK3, &CSelectPluginDlg::OnSetBridge) ON_EN_CHANGE(IDC_NAMEFILTER, &CSelectPluginDlg::OnNameFilterChanged) ON_EN_CHANGE(IDC_PLUGINTAGS, &CSelectPluginDlg::OnPluginTagsChanged) END_MESSAGE_MAP() void CSelectPluginDlg::DoDataExchange(CDataExchange* pDX) { ResizableDialog::DoDataExchange(pDX); DDX_Control(pDX, IDC_TREE1, m_treePlugins); DDX_Control(pDX, IDC_CHECK1, m_chkBridge); DDX_Control(pDX, IDC_CHECK2, m_chkShare); DDX_Control(pDX, IDC_CHECK3, m_chkLegacyBridge); } CSelectPluginDlg::CSelectPluginDlg(CModDoc *pModDoc, PLUGINDEX pluginSlot, CWnd *parent) : ResizableDialog(IDD_SELECTMIXPLUGIN, parent) , m_pModDoc(pModDoc) , m_nPlugSlot(pluginSlot) { if(m_pModDoc && 0 <= m_nPlugSlot && m_nPlugSlot < MAX_MIXPLUGINS) { m_pPlugin = &(pModDoc->GetSoundFile().m_MixPlugins[m_nPlugSlot]); } CMainFrame::GetInputHandler()->Bypass(true); } CSelectPluginDlg::~CSelectPluginDlg() { CMainFrame::GetInputHandler()->Bypass(false); } BOOL CSelectPluginDlg::OnInitDialog() { DWORD dwRemove = TVS_EDITLABELS|TVS_SINGLEEXPAND; DWORD dwAdd = TVS_HASLINES | TVS_LINESATROOT | TVS_HASBUTTONS | TVS_SHOWSELALWAYS; ResizableDialog::OnInitDialog(); m_treePlugins.ModifyStyle(dwRemove, dwAdd); m_treePlugins.SetImageList(&CMainFrame::GetMainFrame()->m_MiscIcons, TVSIL_NORMAL); if (m_pPlugin) { CString targetSlot = MPT_CFORMAT("&Put in FX{}")(mpt::cfmt::dec0<2>(m_nPlugSlot + 1)); SetDlgItemText(IDOK, targetSlot); ::EnableWindow(::GetDlgItem(m_hWnd, IDOK), TRUE); } else { ::EnableWindow(::GetDlgItem(m_hWnd, IDOK), FALSE); } const int dpiX = Util::GetDPIx(m_hWnd); const int dpiY = Util::GetDPIy(m_hWnd); CRect rect ( CPoint(MulDiv(TrackerSettings::Instance().gnPlugWindowX, dpiX, 96), MulDiv(TrackerSettings::Instance().gnPlugWindowY, dpiY, 96)), CSize(MulDiv(TrackerSettings::Instance().gnPlugWindowWidth, dpiX, 96), MulDiv(TrackerSettings::Instance().gnPlugWindowHeight, dpiY, 96)) ); ::MapWindowPoints(GetParent()->m_hWnd, HWND_DESKTOP, (CPoint *)&rect, 2); WINDOWPLACEMENT wnd; wnd.length = sizeof(wnd); GetWindowPlacement(&wnd); wnd.showCmd = SW_SHOW; wnd.rcNormalPosition = rect; SetWindowPlacement(&wnd); UpdatePluginsList(); OnSelChanged(NULL, NULL); return TRUE; } void CSelectPluginDlg::OnOK() { if(m_pPlugin == nullptr) { ResizableDialog::OnOK(); return; } bool changed = false; CVstPluginManager *pManager = theApp.GetPluginManager(); VSTPluginLib *pNewPlug = GetSelectedPlugin(); VSTPluginLib *pFactory = nullptr; IMixPlugin *pCurrentPlugin = nullptr; if(m_pPlugin) pCurrentPlugin = m_pPlugin->pMixPlugin; if((pManager) && (pManager->IsValidPlugin(pNewPlug))) pFactory = pNewPlug; if (pFactory) { // Plugin selected if ((!pCurrentPlugin) || &pCurrentPlugin->GetPluginFactory() != pFactory) { CriticalSection cs; // Destroy old plugin, if there was one. const auto oldOutput = m_pPlugin->GetOutputPlugin(); m_pPlugin->Destroy(); // Initialize plugin info MemsetZero(m_pPlugin->Info); if(oldOutput != PLUGINDEX_INVALID) m_pPlugin->SetOutputPlugin(oldOutput); m_pPlugin->Info.dwPluginId1 = pFactory->pluginId1; m_pPlugin->Info.dwPluginId2 = pFactory->pluginId2; m_pPlugin->editorX = m_pPlugin->editorY = int32_min; m_pPlugin->SetAutoSuspend(TrackerSettings::Instance().enableAutoSuspend); #ifdef MPT_WITH_VST if(m_pPlugin->Info.dwPluginId1 == Vst::kEffectMagic) { switch(m_pPlugin->Info.dwPluginId2) { // Enable drymix by default for these known plugins case Vst::FourCC("Scop"): m_pPlugin->SetWetMix(); break; } } #endif // MPT_WITH_VST m_pPlugin->Info.szName = pFactory->libraryName.ToLocale(); m_pPlugin->Info.szLibraryName = pFactory->libraryName.ToUTF8(); cs.Leave(); // Now, create the new plugin if(pManager && m_pModDoc) { pManager->CreateMixPlugin(*m_pPlugin, m_pModDoc->GetSoundFile()); if (m_pPlugin->pMixPlugin) { IMixPlugin *p = m_pPlugin->pMixPlugin; const CString name = p->GetDefaultEffectName(); if(!name.IsEmpty()) { m_pPlugin->Info.szName = mpt::ToCharset(mpt::Charset::Locale, name); } // Check if plugin slot is already assigned to any instrument, and if not, create one. if(p->IsInstrument() && m_pModDoc->HasInstrumentForPlugin(m_nPlugSlot) == INSTRUMENTINDEX_INVALID) { m_pModDoc->InsertInstrumentForPlugin(m_nPlugSlot); } } else { MemsetZero(m_pPlugin->Info); } } changed = true; } } else if(m_pPlugin->IsValidPlugin()) { // No effect if(m_pModDoc) changed = m_pModDoc->RemovePlugin(m_nPlugSlot); } //remember window size: SaveWindowPos(); if(changed) { if(m_pPlugin->Info.dwPluginId2) TrackerSettings::Instance().gnPlugWindowLast = m_pPlugin->Info.dwPluginId2; if(m_pModDoc) { m_pModDoc->UpdateAllViews(nullptr, PluginHint(static_cast(m_nPlugSlot + 1)).Info().Names()); } ResizableDialog::OnOK(); } else { ResizableDialog::OnCancel(); } } void CSelectPluginDlg::OnCancel() { //remember window size: SaveWindowPos(); ResizableDialog::OnCancel(); } VSTPluginLib* CSelectPluginDlg::GetSelectedPlugin() { HTREEITEM item = m_treePlugins.GetSelectedItem(); if(item) return reinterpret_cast(m_treePlugins.GetItemData(item)); else return nullptr; } void CSelectPluginDlg::SaveWindowPos() const { WINDOWPLACEMENT wnd; wnd.length = sizeof(WINDOWPLACEMENT); GetWindowPlacement(&wnd); CRect rect = wnd.rcNormalPosition; ::MapWindowPoints(HWND_DESKTOP, GetParent()->m_hWnd, (CPoint *)&rect, 2); const int dpiX = Util::GetDPIx(m_hWnd); const int dpiY = Util::GetDPIy(m_hWnd); TrackerSettings::Instance().gnPlugWindowX = MulDiv(rect.left, 96, dpiX); TrackerSettings::Instance().gnPlugWindowY = MulDiv(rect.top, 96, dpiY); TrackerSettings::Instance().gnPlugWindowWidth = MulDiv(rect.Width(), 96, dpiY); TrackerSettings::Instance().gnPlugWindowHeight = MulDiv(rect.Height(), 96, dpiX); } BOOL CSelectPluginDlg::PreTranslateMessage(MSG *pMsg) { // Use up/down keys to navigate in tree view, even if search field is focussed. if(pMsg != nullptr && pMsg->message == WM_KEYDOWN && (pMsg->wParam == VK_UP || pMsg->wParam == VK_DOWN) && GetFocus() != &m_treePlugins) { HTREEITEM selItem = m_treePlugins.GetSelectedItem(); if(selItem == nullptr) { selItem = m_treePlugins.GetRootItem(); } while((selItem = m_treePlugins.GetNextItem(selItem, pMsg->wParam == VK_UP ? TVGN_PREVIOUSVISIBLE : TVGN_NEXTVISIBLE)) != nullptr) { int nImage, nSelectedImage; m_treePlugins.GetItemImage(selItem, nImage, nSelectedImage); if(nImage != IMAGE_FOLDER) { m_treePlugins.SelectItem(selItem); m_treePlugins.EnsureVisible(selItem); return TRUE; } } return TRUE; } return ResizableDialog::PreTranslateMessage(pMsg); } void CSelectPluginDlg::OnNameFilterChanged() { // Update name filter text m_nameFilter = mpt::ToLowerCase(GetWindowTextUnicode(*GetDlgItem(IDC_NAMEFILTER))); UpdatePluginsList(); } void CSelectPluginDlg::UpdatePluginsList(const VSTPluginLib *forceSelect) { CVstPluginManager *pManager = theApp.GetPluginManager(); m_treePlugins.SetRedraw(FALSE); m_treePlugins.DeleteAllItems(); static constexpr struct { VSTPluginLib::PluginCategory category; const TCHAR *description; } categories[] = { { VSTPluginLib::catEffect, _T("Audio Effects") }, { VSTPluginLib::catGenerator, _T("Tone Generators") }, { VSTPluginLib::catRestoration, _T("Audio Restauration") }, { VSTPluginLib::catSurroundFx, _T("Surround Effects") }, { VSTPluginLib::catRoomFx, _T("Room Effects") }, { VSTPluginLib::catSpacializer, _T("Spacializers") }, { VSTPluginLib::catMastering, _T("Mastering Plugins") }, { VSTPluginLib::catAnalysis, _T("Analysis Plugins") }, { VSTPluginLib::catOfflineProcess, _T("Offline Processing") }, { VSTPluginLib::catShell, _T("Shell Plugins") }, { VSTPluginLib::catUnknown, _T("Unsorted") }, { VSTPluginLib::catDMO, _T("DirectX Media Audio Effects") }, { VSTPluginLib::catSynth, _T("Instrument Plugins") }, { VSTPluginLib::catHidden, _T("Legacy Plugins") }, }; const HTREEITEM noPlug = AddTreeItem(_T("No plugin (empty slot)"), IMAGE_NOPLUGIN, false); HTREEITEM currentPlug = noPlug; std::bitset categoryUsed; HTREEITEM categoryFolders[VSTPluginLib::numCategories]; for(const auto &cat : categories) { categoryFolders[cat.category] = AddTreeItem(cat.description, IMAGE_FOLDER, false); } enum PlugMatchQuality { kNoMatch, kSameIdAsLast, kSameIdAsLastWithPlatformMatch, kSameIdAsCurrent, kFoundCurrentPlugin, }; PlugMatchQuality foundPlugin = kNoMatch; const int32 lastPluginID = TrackerSettings::Instance().gnPlugWindowLast; const bool nameFilterActive = !m_nameFilter.empty(); const auto currentTags = mpt::String::Split(m_nameFilter, U_(" ")); if(pManager) { bool first = true; for(auto p : *pManager) { MPT_ASSERT(p); const VSTPluginLib &plug = *p; if(plug.category == VSTPluginLib::catHidden && (m_pPlugin == nullptr || m_pPlugin->pMixPlugin == nullptr || &m_pPlugin->pMixPlugin->GetPluginFactory() != p)) continue; if(nameFilterActive) { // Apply name filter bool matches = false; // Search in plugin names { mpt::ustring displayName = mpt::ToLowerCase(plug.libraryName.ToUnicode()); if(displayName.find(m_nameFilter, 0) != displayName.npos) { matches = true; } } // Search in plugin tags if(!matches) { mpt::ustring tags = mpt::ToLowerCase(plug.tags); for(const auto &tag : currentTags) { if(!tag.empty() && tags.find(tag, 0) != tags.npos) { matches = true; break; } } } // Search in plugin vendors if(!matches) { mpt::ustring vendor = mpt::ToLowerCase(mpt::ToUnicode(plug.vendor)); if(vendor.find(m_nameFilter, 0) != vendor.npos) { matches = true; } } if(!matches) continue; } CString title = plug.libraryName.ToCString(); #ifdef MPT_WITH_VST if(!plug.IsNativeFromCache()) { title += MPT_CFORMAT(" ({})")(plug.GetDllArchNameUser()); } #endif // MPT_WITH_VST HTREEITEM h = AddTreeItem(title, plug.isInstrument ? IMAGE_PLUGININSTRUMENT : IMAGE_EFFECTPLUGIN, true, categoryFolders[plug.category], reinterpret_cast(&plug)); categoryUsed[plug.category] = true; if(nameFilterActive) { // If filter is active, expand nodes. m_treePlugins.EnsureVisible(h); if(first) { first = false; m_treePlugins.SelectItem(h); } } if(forceSelect != nullptr && &plug == forceSelect) { // Forced selection (e.g. just after add plugin) currentPlug = h; foundPlugin = kFoundCurrentPlugin; } if(m_pPlugin && foundPlugin < kFoundCurrentPlugin) { //Which plugin should be selected? if(m_pPlugin->pMixPlugin) { // Current slot's plugin IMixPlugin *pPlugin = m_pPlugin->pMixPlugin; if (&pPlugin->GetPluginFactory() == &plug) { currentPlug = h; foundPlugin = kFoundCurrentPlugin; } } else if(m_pPlugin->Info.dwPluginId1 != 0 || m_pPlugin->Info.dwPluginId2 != 0) { // Plugin with matching ID to current slot's plug if(plug.pluginId1 == m_pPlugin->Info.dwPluginId1 && plug.pluginId2 == m_pPlugin->Info.dwPluginId2) { currentPlug = h; foundPlugin = kSameIdAsCurrent; } } else if(plug.pluginId2 == lastPluginID && foundPlugin < kSameIdAsLastWithPlatformMatch) { // Previously selected plugin #ifdef MPT_WITH_VST foundPlugin = plug.IsNativeFromCache() ? kSameIdAsLastWithPlatformMatch : kSameIdAsLast; #else // !MPT_WITH_VST foundPlugin = kSameIdAsLastWithPlatformMatch; #endif // MPT_WITH_VST currentPlug = h; } } } } // Remove empty categories for(size_t i = 0; i < std::size(categoryFolders); i++) { if(!categoryUsed[i]) { m_treePlugins.DeleteItem(categoryFolders[i]); } } m_treePlugins.SetRedraw(TRUE); if(!nameFilterActive || currentPlug != noPlug) { m_treePlugins.SelectItem(currentPlug); } m_treePlugins.SetItemState(currentPlug, TVIS_BOLD, TVIS_BOLD); m_treePlugins.EnsureVisible(currentPlug); } HTREEITEM CSelectPluginDlg::AddTreeItem(const TCHAR *title, int image, bool sort, HTREEITEM hParent, LPARAM lParam) { return m_treePlugins.InsertItem( TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_PARAM | TVIF_TEXT, title, image, image, 0, 0, lParam, hParent, (sort ? TVI_SORT : TVI_LAST)); } void CSelectPluginDlg::OnSelDblClk(NMHDR *, LRESULT *result) { if(m_pPlugin == nullptr) return; HTREEITEM hSel = m_treePlugins.GetSelectedItem(); int nImage, nSelectedImage; m_treePlugins.GetItemImage(hSel, nImage, nSelectedImage); if ((hSel) && (nImage != IMAGE_FOLDER)) OnOK(); if (result) *result = 0; } void CSelectPluginDlg::OnSelChanged(NMHDR *, LRESULT *result) { CVstPluginManager *pManager = theApp.GetPluginManager(); VSTPluginLib *pPlug = GetSelectedPlugin(); bool showBoxes = false; BOOL enableTagsTextBox = FALSE; BOOL enableRemoveButton = FALSE; if (pManager != nullptr && pManager->IsValidPlugin(pPlug)) { if(pPlug->vendor.IsEmpty()) SetDlgItemText(IDC_VENDOR, _T("")); else SetDlgItemText(IDC_VENDOR, _T("Vendor: ") + pPlug->vendor); if(pPlug->dllPath.empty()) SetDlgItemText(IDC_TEXT_CURRENT_VSTPLUG, _T("Built-in plugin")); else SetDlgItemText(IDC_TEXT_CURRENT_VSTPLUG, pPlug->dllPath.ToCString()); SetDlgItemText(IDC_PLUGINTAGS, mpt::ToCString(pPlug->tags)); enableRemoveButton = pPlug->isBuiltIn ? FALSE : TRUE; #ifdef MPT_WITH_VST if(pPlug->pluginId1 == Vst::kEffectMagic && !pPlug->isBuiltIn) { bool isBridgeAvailable = ((pPlug->GetDllArch() == PluginArch_x86) && IsComponentAvailable(pluginBridge_x86)) || ((pPlug->GetDllArch() == PluginArch_x86) && IsComponentAvailable(pluginBridgeLegacy_x86)) || ((pPlug->GetDllArch() == PluginArch_amd64) && IsComponentAvailable(pluginBridge_amd64)) || ((pPlug->GetDllArch() == PluginArch_amd64) && IsComponentAvailable(pluginBridgeLegacy_amd64)) #if defined(MPT_WITH_WINDOWS10) || ((pPlug->GetDllArch() == PluginArch_arm) && IsComponentAvailable(pluginBridge_arm)) || ((pPlug->GetDllArch() == PluginArch_arm) && IsComponentAvailable(pluginBridgeLegacy_arm)) || ((pPlug->GetDllArch() == PluginArch_arm64) && IsComponentAvailable(pluginBridge_arm64)) || ((pPlug->GetDllArch() == PluginArch_arm64) && IsComponentAvailable(pluginBridgeLegacy_arm64)) #endif // MPT_WITH_WINDOWS10 ; if(TrackerSettings::Instance().bridgeAllPlugins || !isBridgeAvailable) { m_chkBridge.EnableWindow(FALSE); m_chkBridge.SetCheck(isBridgeAvailable ? BST_CHECKED : BST_UNCHECKED); } else { bool native = pPlug->IsNative(); m_chkBridge.EnableWindow(native ? TRUE : FALSE); m_chkBridge.SetCheck((pPlug->useBridge || !native) ? BST_CHECKED : BST_UNCHECKED); } m_chkShare.SetCheck(pPlug->shareBridgeInstance ? BST_CHECKED : BST_UNCHECKED); m_chkShare.EnableWindow(m_chkBridge.GetCheck() != BST_UNCHECKED); m_chkLegacyBridge.SetCheck((!pPlug->modernBridge) ? BST_CHECKED : BST_UNCHECKED); m_chkLegacyBridge.EnableWindow(m_chkBridge.GetCheck() != BST_UNCHECKED); showBoxes = true; } enableTagsTextBox = TRUE; #endif // MPT_WITH_VST } else { SetDlgItemText(IDC_VENDOR, _T("")); SetDlgItemText(IDC_TEXT_CURRENT_VSTPLUG, _T("")); SetDlgItemText(IDC_PLUGINTAGS, _T("")); } GetDlgItem(IDC_PLUGINTAGS)->EnableWindow(enableTagsTextBox); GetDlgItem(IDC_BUTTON2)->EnableWindow(enableRemoveButton); if(!showBoxes) { m_chkBridge.EnableWindow(FALSE); m_chkShare.EnableWindow(FALSE); m_chkLegacyBridge.EnableWindow(FALSE); m_chkBridge.SetCheck(BST_UNCHECKED); m_chkShare.SetCheck(BST_UNCHECKED); m_chkLegacyBridge.SetCheck(BST_UNCHECKED); } if (result) *result = 0; } #ifdef MPT_WITH_VST namespace { // TODO: Keep these lists up-to-date. constexpr struct { int32 id1; int32 id2; const char *name; const char *problem; } ProblematicPlugins[] = { {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."}, {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."}, {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."}, {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."}, }; // Plugins that should always be bridged or require a specific bridge mode. constexpr struct { int32 id1; int32 id2; bool useBridge; bool shareInstance; bool modernBridge; } ForceBridgePlugins[] = { {Vst::kEffectMagic, Vst::FourCC("fV2s"), true, false, false}, // V2 freezes on shutdown if there's more than one instance per process {Vst::kEffectMagic, Vst::FourCC("frV2"), true, false, false}, // ditto {Vst::kEffectMagic, Vst::FourCC("SKV3"), false, true, false}, // SideKick v3 always has to run in a shared instance {Vst::kEffectMagic, Vst::FourCC("YWS!"), false, true, false}, // You Wa Shock ! always has to run in a shared instance {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 }; } // namespace #endif // MPT_WITH_VST bool CSelectPluginDlg::VerifyPlugin(VSTPluginLib *plug, CWnd *parent) { #ifdef MPT_WITH_VST for(const auto &p : ProblematicPlugins) { if(p.id2 == plug->pluginId2 && p.id1 == plug->pluginId1) { 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); if(Reporting::Confirm(s, false, false, parent) == cnfNo) { return false; } break; } } for(const auto &p : ForceBridgePlugins) { if(p.id2 == plug->pluginId2 && p.id1 == plug->pluginId1) { plug->useBridge = p.useBridge; plug->shareBridgeInstance = p.shareInstance; if(!p.modernBridge) plug->modernBridge = false; plug->WriteToCache(); break; } } #else // !MPT_WITH_VST MPT_UNREFERENCED_PARAMETER(plug); MPT_UNREFERENCED_PARAMETER(parent); #endif // MPT_WITH_VST return true; } void CSelectPluginDlg::OnAddPlugin() { FileDialog dlg = OpenFileDialog() .AllowMultiSelect() .DefaultExtension("dll") .ExtensionFilter("VST Plugins|*.dll;*.vst3||") .WorkingDirectory(TrackerSettings::Instance().PathPlugins.GetWorkingDir()); if(!dlg.Show(this)) return; TrackerSettings::Instance().PathPlugins.SetWorkingDir(dlg.GetWorkingDirectory()); CVstPluginManager *plugManager = theApp.GetPluginManager(); if(!plugManager) return; VSTPluginLib *plugLib = nullptr; bool update = false; for(const auto &file : dlg.GetFilenames()) { VSTPluginLib *lib = plugManager->AddPlugin(file, TrackerSettings::Instance().BrokenPluginsWorkaroundVSTMaskAllCrashes, mpt::ustring(), false); if(lib != nullptr) { update = true; if(!VerifyPlugin(lib, this)) { plugManager->RemovePlugin(lib); } else { plugLib = lib; // If this plugin was missing anywhere, try loading it ReloadMissingPlugins(lib); } } } if(update) { // Force selection to last added plug. UpdatePluginsList(plugLib); } else { Reporting::Error("No valid VST Plugin was selected."); } } void CSelectPluginDlg::OnScanFolder() { BrowseForFolder dlg(TrackerSettings::Instance().PathPlugins.GetWorkingDir(), _T("Select a folder that should be scanned for VST plugins (including sub-folders)")); if(!dlg.Show(this)) return; TrackerSettings::Instance().PathPlugins.SetWorkingDir(dlg.GetDirectory()); VSTPluginLib *plugLib = ScanPlugins(dlg.GetDirectory(), this); UpdatePluginsList(plugLib); // If any of the plugins was missing anywhere, try loading it for(auto p : *theApp.GetPluginManager()) { ReloadMissingPlugins(p); } } VSTPluginLib *CSelectPluginDlg::ScanPlugins(const mpt::PathString &path, CWnd *parent) { CVstPluginManager *pManager = theApp.GetPluginManager(); VSTPluginLib *plugLib = nullptr; bool update = false; CDialog pluginScanDlg; pluginScanDlg.Create(IDD_SCANPLUGINS, parent); pluginScanDlg.CenterWindow(parent); pluginScanDlg.ModifyStyle(0, WS_SYSMENU, WS_SYSMENU); pluginScanDlg.ShowWindow(SW_SHOW); FolderScanner scan(path, FolderScanner::kOnlyFiles | FolderScanner::kFindInSubDirectories); bool maskCrashes = TrackerSettings::Instance().BrokenPluginsWorkaroundVSTMaskAllCrashes; mpt::PathString fileName; int files = 0; while(scan.Next(fileName) && pluginScanDlg.IsWindowVisible()) { if(!mpt::PathString::CompareNoCase(fileName.GetFileExt(), P_(".dll"))) { CWnd *text = pluginScanDlg.GetDlgItem(IDC_SCANTEXT); CString scanStr = _T("Scanning Plugin...\n") + fileName.ToCString(); text->SetWindowText(scanStr); MSG msg; while(::PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { ::TranslateMessage(&msg); ::DispatchMessage(&msg); } VSTPluginLib *lib = pManager->AddPlugin(fileName, maskCrashes, mpt::ustring(), false); if(lib) { update = true; if(!VerifyPlugin(lib, parent)) { pManager->RemovePlugin(lib); } else { plugLib = lib; files++; } } } } if(update) { // Force selection to last added plug. Reporting::Information(MPT_AFORMAT("Found {} plugin{}.")(files, files == 1 ? "" : "s").c_str(), parent); return plugLib; } else { Reporting::Error("Could not find any valid VST plugins."); return nullptr; } } // After adding new plugins, check if they were missing in any open songs. void CSelectPluginDlg::ReloadMissingPlugins(const VSTPluginLib *lib) const { CVstPluginManager *plugManager = theApp.GetPluginManager(); auto docs = theApp.GetOpenDocuments(); for(auto &modDoc : docs) { CSoundFile &sndFile = modDoc->GetSoundFile(); bool updateDoc = false; for(auto &plugin : sndFile.m_MixPlugins) { if(plugin.pMixPlugin == nullptr && plugin.Info.dwPluginId1 == lib->pluginId1 && plugin.Info.dwPluginId2 == lib->pluginId2) { updateDoc = true; plugManager->CreateMixPlugin(plugin, sndFile); if(plugin.pMixPlugin) { plugin.pMixPlugin->RestoreAllParameters(plugin.defaultProgram); } } } if(updateDoc) { modDoc->UpdateAllViews(nullptr, PluginHint().Info().Names()); CMainFrame::GetMainFrame()->UpdateTree(modDoc, PluginHint().Info().Names()); } } } void CSelectPluginDlg::OnRemovePlugin() { const HTREEITEM pluginToDelete = m_treePlugins.GetSelectedItem(); VSTPluginLib *plugin = GetSelectedPlugin(); CVstPluginManager *plugManager = theApp.GetPluginManager(); if(plugManager && plugin) { if(plugManager->RemovePlugin(plugin)) { m_treePlugins.DeleteItem(pluginToDelete); } } } void CSelectPluginDlg::OnSetBridge() { VSTPluginLib *plug = GetSelectedPlugin(); if(plug) { if(m_chkBridge.IsWindowEnabled()) { // Only update this setting if the current setting isn't an enforced setting (e.g. because plugin isn't native). // This has the advantage that plugins don't get force-checked when switching between 32-bit and 64-bit versions of OpenMPT. plug->useBridge = m_chkBridge.GetCheck() != BST_UNCHECKED; } m_chkShare.EnableWindow(m_chkBridge.GetCheck() != BST_UNCHECKED); m_chkLegacyBridge.EnableWindow(m_chkBridge.GetCheck() != BST_UNCHECKED); plug->shareBridgeInstance = m_chkShare.GetCheck() != BST_UNCHECKED; plug->modernBridge = m_chkLegacyBridge.GetCheck() == BST_UNCHECKED; plug->WriteToCache(); } } void CSelectPluginDlg::OnPluginTagsChanged() { VSTPluginLib *plug = GetSelectedPlugin(); if (plug) { plug->tags = GetWindowTextUnicode(*GetDlgItem(IDC_PLUGINTAGS)); } } OPENMPT_NAMESPACE_END #endif // NO_PLUGINS