winamp/Src/external_dependencies/openmpt-trunk/mptrack/SelectPluginDialog.cpp
2024-09-24 14:54:57 +02:00

869 lines
25 KiB
C++

/*
* 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<PLUGINDEX>(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<VSTPluginLib *>(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<VSTPluginLib::numCategories> 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<mpt::ustring>(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<LPARAM>(&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