mirror of
https://github.com/WinampDesktop/winamp.git
synced 2024-12-18 19:05:52 +01:00
1023 lines
28 KiB
C++
1023 lines
28 KiB
C++
/*
|
|
* AbstractVstEditor.cpp
|
|
* ---------------------
|
|
* Purpose: Common plugin editor interface class. This code is shared between custom and default plugin user interfaces.
|
|
* Notes : (currently none)
|
|
* Authors: OpenMPT Devs
|
|
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
|
|
*/
|
|
|
|
|
|
#include "stdafx.h"
|
|
#include "Mptrack.h"
|
|
#include "Mainfrm.h"
|
|
#include "Clipboard.h"
|
|
#include "../soundlib/Sndfile.h"
|
|
#include "../soundlib/mod_specifications.h"
|
|
#include "../soundlib/plugins/PlugInterface.h"
|
|
#include "../soundlib/plugins/PluginManager.h"
|
|
#include "Vstplug.h"
|
|
#include "dlg_misc.h"
|
|
#include "AbstractVstEditor.h"
|
|
#include "../common/mptStringBuffer.h"
|
|
#include "MIDIMacros.h"
|
|
#include "VstPresets.h"
|
|
#include "../common/FileReader.h"
|
|
#include "InputHandler.h"
|
|
#include "dlg_misc.h"
|
|
#include <sstream>
|
|
#include "Globals.h"
|
|
|
|
|
|
OPENMPT_NAMESPACE_BEGIN
|
|
|
|
|
|
#ifndef NO_PLUGINS
|
|
|
|
CAbstractVstEditor::WindowSizeAdjuster::WindowSizeAdjuster(CWnd &wnd)
|
|
: m_wnd(wnd)
|
|
{
|
|
MENUBARINFO mbi = { sizeof(mbi) };
|
|
if(GetMenuBarInfo(m_wnd, OBJID_MENU, 0, &mbi))
|
|
m_menuHeight = (mbi.rcBar.bottom - mbi.rcBar.top);
|
|
}
|
|
|
|
CAbstractVstEditor::WindowSizeAdjuster::~WindowSizeAdjuster()
|
|
{
|
|
// Extend window height by the menu size if it changed
|
|
MENUBARINFO mbi = { sizeof(mbi) };
|
|
if(GetMenuBarInfo(m_wnd, OBJID_MENU, 0, &mbi))
|
|
{
|
|
CRect windowRect;
|
|
m_wnd.GetWindowRect(&windowRect);
|
|
windowRect.bottom += (mbi.rcBar.bottom - mbi.rcBar.top) - m_menuHeight;
|
|
m_wnd.SetWindowPos(nullptr, 0, 0,
|
|
windowRect.Width(), windowRect.Height(),
|
|
SWP_NOZORDER | SWP_NOMOVE | SWP_NOACTIVATE);
|
|
}
|
|
}
|
|
|
|
#define PRESETS_PER_COLUMN 32
|
|
#define PRESETS_PER_GROUP 128
|
|
|
|
UINT CAbstractVstEditor::m_clipboardFormat = RegisterClipboardFormat(_T("VST Preset Data"));
|
|
|
|
BEGIN_MESSAGE_MAP(CAbstractVstEditor, CDialog)
|
|
ON_WM_CLOSE()
|
|
ON_WM_INITMENU()
|
|
ON_WM_MENUSELECT()
|
|
ON_WM_ACTIVATE()
|
|
ON_WM_DROPFILES()
|
|
ON_WM_MOVE()
|
|
ON_WM_NCLBUTTONDBLCLK()
|
|
ON_COMMAND(ID_EDIT_COPY, &CAbstractVstEditor::OnCopyParameters)
|
|
ON_COMMAND(ID_EDIT_PASTE, &CAbstractVstEditor::OnPasteParameters)
|
|
ON_COMMAND(ID_PRESET_LOAD, &CAbstractVstEditor::OnLoadPreset)
|
|
ON_COMMAND(ID_PLUG_BYPASS, &CAbstractVstEditor::OnBypassPlug)
|
|
ON_COMMAND(ID_PLUG_RECORDAUTOMATION,&CAbstractVstEditor::OnRecordAutomation)
|
|
ON_COMMAND(ID_PLUG_RECORD_MIDIOUT, &CAbstractVstEditor::OnRecordMIDIOut)
|
|
ON_COMMAND(ID_PLUG_PASSKEYS, &CAbstractVstEditor::OnPassKeypressesToPlug)
|
|
ON_COMMAND(ID_PRESET_SAVE, &CAbstractVstEditor::OnSavePreset)
|
|
ON_COMMAND(ID_PRESET_RANDOM, &CAbstractVstEditor::OnRandomizePreset)
|
|
ON_COMMAND(ID_RENAME_PLUGIN, &CAbstractVstEditor::OnRenamePlugin)
|
|
ON_COMMAND(ID_PREVIOUSVSTPRESET, &CAbstractVstEditor::OnSetPreviousVSTPreset)
|
|
ON_COMMAND(ID_NEXTVSTPRESET, &CAbstractVstEditor::OnSetNextVSTPreset)
|
|
ON_COMMAND(ID_VSTPRESETBACKWARDJUMP,&CAbstractVstEditor::OnVSTPresetBackwardJump)
|
|
ON_COMMAND(ID_VSTPRESETFORWARDJUMP, &CAbstractVstEditor::OnVSTPresetForwardJump)
|
|
ON_COMMAND(ID_VSTPRESETNAME, &CAbstractVstEditor::OnVSTPresetRename)
|
|
ON_COMMAND(ID_PLUGINTOINSTRUMENT, &CAbstractVstEditor::OnCreateInstrument)
|
|
ON_COMMAND_RANGE(ID_PRESET_SET, ID_PRESET_SET + PRESETS_PER_GROUP, &CAbstractVstEditor::OnSetPreset)
|
|
ON_MESSAGE(WM_MOD_MIDIMSG, &CAbstractVstEditor::OnMidiMsg)
|
|
ON_MESSAGE(WM_MOD_KEYCOMMAND, &CAbstractVstEditor::OnCustomKeyMsg) //rewbs.customKeys
|
|
ON_COMMAND_RANGE(ID_PLUGSELECT, ID_PLUGSELECT + MAX_MIXPLUGINS, &CAbstractVstEditor::OnToggleEditor) //rewbs.patPlugName
|
|
ON_COMMAND_RANGE(ID_SELECTINST, ID_SELECTINST + MAX_INSTRUMENTS, &CAbstractVstEditor::OnSetInputInstrument) //rewbs.patPlugName
|
|
ON_COMMAND_RANGE(ID_LEARN_MACRO_FROM_PLUGGUI, ID_LEARN_MACRO_FROM_PLUGGUI + kSFxMacros, &CAbstractVstEditor::PrepareToLearnMacro)
|
|
END_MESSAGE_MAP()
|
|
|
|
|
|
CAbstractVstEditor::CAbstractVstEditor(IMixPlugin &plugin)
|
|
: m_VstPlugin(plugin)
|
|
{
|
|
m_Menu.LoadMenu(IDR_VSTMENU);
|
|
m_nInstrument = GetBestInstrumentCandidate();
|
|
}
|
|
|
|
|
|
CAbstractVstEditor::~CAbstractVstEditor()
|
|
{
|
|
m_VstPlugin.m_pEditor = nullptr;
|
|
}
|
|
|
|
|
|
void CAbstractVstEditor::PostNcDestroy()
|
|
{
|
|
CDialog::PostNcDestroy();
|
|
delete this;
|
|
}
|
|
|
|
|
|
void CAbstractVstEditor::OnNcLButtonDblClk(UINT nHitTest, CPoint point)
|
|
{
|
|
CDialog::OnNcLButtonDblClk(nHitTest, point);
|
|
// Double click on title bar = reduce plugin window to non-client area
|
|
if(nHitTest == HTCAPTION)
|
|
{
|
|
CRect rcWnd, rcClient;
|
|
GetWindowRect(&rcWnd);
|
|
if(!m_isMinimized)
|
|
{
|
|
// When minimizing, remove the client area
|
|
GetClientRect(&rcClient);
|
|
m_clientHeight = rcClient.Height();
|
|
}
|
|
m_isMinimized = !m_isMinimized;
|
|
m_clientHeight = -m_clientHeight;
|
|
int rcHeight = rcWnd.Height() + m_clientHeight;
|
|
|
|
SetWindowPos(NULL, 0, 0,
|
|
rcWnd.Width(), rcHeight,
|
|
SWP_NOZORDER | SWP_NOMOVE);
|
|
}
|
|
}
|
|
|
|
|
|
void CAbstractVstEditor::OnActivate(UINT nState, CWnd *pWndOther, BOOL bMinimized)
|
|
{
|
|
CDialog::OnActivate(nState, pWndOther, bMinimized);
|
|
if(nState != WA_INACTIVE) CMainFrame::GetMainFrame()->SetMidiRecordWnd(GetSafeHwnd());
|
|
}
|
|
|
|
|
|
LRESULT CAbstractVstEditor::OnMidiMsg(WPARAM midiData, LPARAM sender)
|
|
{
|
|
CModDoc *modDoc = m_VstPlugin.GetModDoc();
|
|
if(modDoc != nullptr && sender != reinterpret_cast<LPARAM>(&m_VstPlugin))
|
|
{
|
|
if(!CheckInstrument(m_nInstrument))
|
|
m_nInstrument = GetBestInstrumentCandidate();
|
|
modDoc->ProcessMIDI((uint32)midiData, m_nInstrument, &m_VstPlugin, kCtxVSTGUI);
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
// Drop files from Windows
|
|
void CAbstractVstEditor::OnDropFiles(HDROP hDropInfo)
|
|
{
|
|
const UINT nFiles = ::DragQueryFileW(hDropInfo, (UINT)-1, NULL, 0);
|
|
CMainFrame::GetMainFrame()->SetForegroundWindow();
|
|
for(UINT f = 0; f < nFiles; f++)
|
|
{
|
|
UINT size = ::DragQueryFile(hDropInfo, f, nullptr, 0) + 1;
|
|
std::vector<TCHAR> fileName(size, _T('\0'));
|
|
if(::DragQueryFile(hDropInfo, f, fileName.data(), size))
|
|
{
|
|
m_VstPlugin.LoadProgram(mpt::PathString::FromNative(fileName.data()));
|
|
}
|
|
}
|
|
::DragFinish(hDropInfo);
|
|
}
|
|
|
|
|
|
void CAbstractVstEditor::OnLoadPreset()
|
|
{
|
|
if(m_VstPlugin.LoadProgram())
|
|
{
|
|
UpdatePresetMenu(true);
|
|
UpdatePresetField();
|
|
}
|
|
}
|
|
|
|
|
|
void CAbstractVstEditor::OnSavePreset()
|
|
{
|
|
m_VstPlugin.SaveProgram();
|
|
}
|
|
|
|
|
|
void CAbstractVstEditor::OnCopyParameters()
|
|
{
|
|
if(CMainFrame::GetMainFrame() == nullptr) return;
|
|
|
|
BeginWaitCursor();
|
|
std::ostringstream f(std::ios::out | std::ios::binary);
|
|
if(VSTPresets::SaveFile(f, m_VstPlugin, false))
|
|
{
|
|
const std::string data = f.str();
|
|
Clipboard clipboard(m_clipboardFormat, data.length());
|
|
if(auto dst = clipboard.As<char>())
|
|
{
|
|
memcpy(dst, data.data(), data.length());
|
|
}
|
|
}
|
|
EndWaitCursor();
|
|
}
|
|
|
|
|
|
void CAbstractVstEditor::OnPasteParameters()
|
|
{
|
|
if(CMainFrame::GetMainFrame() == nullptr) return;
|
|
|
|
BeginWaitCursor();
|
|
Clipboard clipboard(m_clipboardFormat);
|
|
if(auto data = clipboard.Get(); data.data())
|
|
{
|
|
FileReader file(data);
|
|
VSTPresets::ErrorCode error = VSTPresets::LoadFile(file, m_VstPlugin);
|
|
clipboard.Close();
|
|
|
|
if(error == VSTPresets::noError)
|
|
{
|
|
const CSoundFile &sndFile = m_VstPlugin.GetSoundFile();
|
|
CModDoc *pModDoc;
|
|
if(sndFile.GetModSpecifications().supportsPlugins && (pModDoc = sndFile.GetpModDoc()) != nullptr)
|
|
{
|
|
pModDoc->SetModified();
|
|
}
|
|
UpdatePresetField();
|
|
} else
|
|
{
|
|
Reporting::Error(VSTPresets::GetErrorMessage(error));
|
|
}
|
|
}
|
|
EndWaitCursor();
|
|
}
|
|
|
|
|
|
void CAbstractVstEditor::OnRandomizePreset()
|
|
{
|
|
static double randomFactor = 10.0;
|
|
CInputDlg dlg(this, _T("Input parameter randomization amount (0 = no change, 100 = completely random)"), 0.0, 100.0, randomFactor);
|
|
if(dlg.DoModal() == IDOK)
|
|
{
|
|
randomFactor = dlg.resultAsDouble;
|
|
PlugParamValue factor = PlugParamValue(randomFactor / 100.0);
|
|
PlugParamIndex numParams = m_VstPlugin.GetNumParameters();
|
|
for(PlugParamIndex p = 0; p < numParams; p++)
|
|
{
|
|
PlugParamValue val = m_VstPlugin.GetParameter(p);
|
|
val += mpt::random(theApp.PRNG(), PlugParamValue(-1.0), PlugParamValue(1.0)) * factor;
|
|
Limit(val, 0.0f, 1.0f);
|
|
m_VstPlugin.SetParameter(p, val);
|
|
}
|
|
UpdateParamDisplays();
|
|
}
|
|
}
|
|
|
|
|
|
void CAbstractVstEditor::OnRenamePlugin()
|
|
{
|
|
auto &sndFile = m_VstPlugin.GetSoundFile();
|
|
auto &plugin = sndFile.m_MixPlugins[m_VstPlugin.m_nSlot];
|
|
|
|
CInputDlg dlg(this, _T("New name for this plugin instance:"), mpt::ToCString(plugin.GetName()), static_cast<int32>(std::size(plugin.Info.szName.buf)));
|
|
if(dlg.DoModal() == IDOK)
|
|
{
|
|
if(dlg.resultAsString != mpt::ToCString(plugin.GetName()))
|
|
{
|
|
plugin.Info.szName = mpt::ToCharset(mpt::Charset::Locale, dlg.resultAsString);
|
|
if(auto *modDoc = sndFile.GetpModDoc(); modDoc != nullptr)
|
|
{
|
|
if(sndFile.GetModSpecifications().supportsPlugins)
|
|
modDoc->SetModified();
|
|
modDoc->UpdateAllViews(nullptr, PluginHint(m_VstPlugin.m_nSlot + 1).Info().Names(), this);
|
|
}
|
|
SetTitle();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
bool CAbstractVstEditor::OpenEditor(CWnd *)
|
|
{
|
|
ModifyStyleEx(0, WS_EX_ACCEPTFILES);
|
|
RestoreWindowPos();
|
|
SetTitle();
|
|
SetupMenu();
|
|
ShowWindow(SW_SHOW);
|
|
return true;
|
|
}
|
|
|
|
|
|
void CAbstractVstEditor::DoClose()
|
|
{
|
|
StoreWindowPos();
|
|
m_presetMenuGroup.clear();
|
|
DestroyWindow();
|
|
}
|
|
|
|
|
|
void CAbstractVstEditor::SetupMenu(bool force)
|
|
{
|
|
WindowSizeAdjuster adjuster(*this);
|
|
SetMenu(&m_Menu);
|
|
UpdatePresetMenu(force);
|
|
UpdateInputMenu();
|
|
UpdateOutputMenu();
|
|
UpdateMacroMenu();
|
|
UpdateOptionsMenu();
|
|
UpdatePresetField();
|
|
}
|
|
|
|
|
|
void CAbstractVstEditor::UpdatePresetField()
|
|
{
|
|
if(m_VstPlugin.GetNumPrograms() > 0)
|
|
{
|
|
if(m_Menu.GetMenuItemCount() < 5)
|
|
{
|
|
m_Menu.AppendMenu(MF_BYPOSITION, ID_VSTPRESETBACKWARDJUMP, _T("<<"));
|
|
m_Menu.AppendMenu(MF_BYPOSITION, ID_PREVIOUSVSTPRESET, _T("<"));
|
|
m_Menu.AppendMenu(MF_BYPOSITION, ID_NEXTVSTPRESET, _T(">"));
|
|
m_Menu.AppendMenu(MF_BYPOSITION, ID_VSTPRESETFORWARDJUMP, _T(">>"));
|
|
m_Menu.AppendMenu(MF_BYPOSITION|MF_DISABLED, ID_VSTPRESETNAME, _T(""));
|
|
}
|
|
|
|
CString programName = m_VstPlugin.GetFormattedProgramName(m_VstPlugin.GetCurrentProgram());
|
|
programName.Replace(_T("&"), _T("&&"));
|
|
m_Menu.ModifyMenu(8, MF_BYPOSITION, ID_VSTPRESETNAME, programName);
|
|
}
|
|
|
|
DrawMenuBar();
|
|
|
|
}
|
|
|
|
|
|
void CAbstractVstEditor::OnSetPreset(UINT nID)
|
|
{
|
|
SetPreset(nID - ID_PRESET_SET + m_currentPresetMenu * PRESETS_PER_GROUP);
|
|
}
|
|
|
|
|
|
void CAbstractVstEditor::OnSetPreviousVSTPreset()
|
|
{
|
|
SetPreset(m_VstPlugin.GetCurrentProgram() - 1);
|
|
}
|
|
|
|
|
|
void CAbstractVstEditor::OnSetNextVSTPreset()
|
|
{
|
|
SetPreset(m_VstPlugin.GetCurrentProgram() + 1);
|
|
}
|
|
|
|
|
|
void CAbstractVstEditor::OnVSTPresetBackwardJump()
|
|
{
|
|
SetPreset(std::max(0, m_VstPlugin.GetCurrentProgram() - 10));
|
|
}
|
|
|
|
|
|
void CAbstractVstEditor::OnVSTPresetForwardJump()
|
|
{
|
|
SetPreset(std::min(m_VstPlugin.GetCurrentProgram() + 10, m_VstPlugin.GetNumPrograms() - 1));
|
|
}
|
|
|
|
|
|
void CAbstractVstEditor::SetPreset(int32 preset)
|
|
{
|
|
if(preset >= 0 && preset < m_VstPlugin.GetNumPrograms())
|
|
{
|
|
m_VstPlugin.SetCurrentProgram(preset);
|
|
WindowSizeAdjuster adjuster(*this);
|
|
UpdatePresetField();
|
|
|
|
if(m_VstPlugin.GetSoundFile().GetModSpecifications().supportsPlugins)
|
|
{
|
|
m_VstPlugin.GetModDoc()->SetModified();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void CAbstractVstEditor::OnVSTPresetRename()
|
|
{
|
|
auto currentName = m_VstPlugin.GetCurrentProgramName();
|
|
CInputDlg dlg(this, _T("New program name:"), currentName);
|
|
if(dlg.DoModal() == IDOK)
|
|
{
|
|
m_VstPlugin.SetCurrentProgramName(dlg.resultAsString);
|
|
if(m_VstPlugin.GetCurrentProgramName() != currentName)
|
|
{
|
|
m_VstPlugin.SetModified();
|
|
WindowSizeAdjuster adjuster(*this);
|
|
UpdatePresetField();
|
|
UpdatePresetMenu(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void CAbstractVstEditor::OnBypassPlug()
|
|
{
|
|
m_VstPlugin.ToggleBypass();
|
|
if(m_VstPlugin.GetSoundFile().GetModSpecifications().supportsPlugins)
|
|
{
|
|
m_VstPlugin.GetModDoc()->SetModified();
|
|
}
|
|
SetTitle();
|
|
}
|
|
|
|
|
|
void CAbstractVstEditor::OnRecordAutomation()
|
|
{
|
|
m_VstPlugin.m_recordAutomation = !m_VstPlugin.m_recordAutomation;
|
|
}
|
|
|
|
|
|
void CAbstractVstEditor::OnRecordMIDIOut()
|
|
{
|
|
m_VstPlugin.m_recordMIDIOut = !m_VstPlugin.m_recordMIDIOut;
|
|
}
|
|
|
|
|
|
void CAbstractVstEditor::OnPassKeypressesToPlug()
|
|
{
|
|
m_VstPlugin.m_passKeypressesToPlug = !m_VstPlugin.m_passKeypressesToPlug;
|
|
}
|
|
|
|
|
|
BOOL CAbstractVstEditor::PreTranslateMessage(MSG *msg)
|
|
{
|
|
if(msg && HandleKeyMessage(*msg))
|
|
return TRUE;
|
|
|
|
return CDialog::PreTranslateMessage(msg);
|
|
}
|
|
|
|
|
|
bool CAbstractVstEditor::HandleKeyMessage(MSG &msg)
|
|
{
|
|
if(m_VstPlugin.m_passKeypressesToPlug)
|
|
return false;
|
|
if(msg.message != WM_SYSKEYUP && msg.message != WM_KEYUP && msg.message != WM_SYSKEYDOWN && msg.message != WM_KEYDOWN)
|
|
return false;
|
|
|
|
CInputHandler *ih = CMainFrame::GetInputHandler();
|
|
if(ih->IsKeyPressHandledByTextBox(static_cast<DWORD>(msg.wParam), ::GetFocus()))
|
|
return false;
|
|
|
|
// Translate message manually
|
|
UINT nChar = (UINT)msg.wParam;
|
|
UINT nRepCnt = LOWORD(msg.lParam);
|
|
UINT nFlags = HIWORD(msg.lParam);
|
|
KeyEventType kT = ih->GetKeyEventType(nFlags);
|
|
|
|
// If we successfully mapped to a command and plug does not listen for keypresses, no need to pass message on.
|
|
if(ih->KeyEvent(kCtxVSTGUI, nChar, nRepCnt, nFlags, kT, this) != kcNull)
|
|
return true;
|
|
|
|
// Don't forward key repeats if plug does not listen for keypresses
|
|
// (avoids system beeps on note hold)
|
|
if(kT == kKeyEventRepeat)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
void CAbstractVstEditor::UpdateView(UpdateHint hint)
|
|
{
|
|
if(!hint.GetType()[HINT_PLUGINNAMES | HINT_MIXPLUGINS])
|
|
return;
|
|
|
|
PLUGINDEX hintPlug = hint.ToType<PluginHint>().GetPlugin();
|
|
if(hintPlug > 0 && (hintPlug - 1) != m_VstPlugin.GetSlot())
|
|
return;
|
|
|
|
SetTitle();
|
|
}
|
|
|
|
|
|
void CAbstractVstEditor::SetTitle()
|
|
{
|
|
if(m_VstPlugin.m_pMixStruct)
|
|
{
|
|
CString title = MPT_CFORMAT("FX {}: ")(mpt::cfmt::dec0<2>(m_VstPlugin.m_nSlot + 1));
|
|
|
|
bool hasCustomName = (m_VstPlugin.m_pMixStruct->GetName() != U_("")) && (m_VstPlugin.m_pMixStruct->GetName() != m_VstPlugin.m_pMixStruct->GetLibraryName());
|
|
if(hasCustomName)
|
|
title += mpt::ToCString(m_VstPlugin.m_pMixStruct->GetName()) + _T(" (");
|
|
title += mpt::ToCString(m_VstPlugin.m_pMixStruct->GetLibraryName());
|
|
if(hasCustomName)
|
|
title += _T(")");
|
|
|
|
#ifdef MPT_WITH_VST
|
|
const CVstPlugin *vstPlugin = dynamic_cast<CVstPlugin *>(&m_VstPlugin);
|
|
if(vstPlugin != nullptr && vstPlugin->isBridged)
|
|
title += MPT_CFORMAT(" ({} Bridged)")(m_VstPlugin.GetPluginFactory().GetDllArchNameUser());
|
|
#endif // MPT_WITH_VST
|
|
|
|
if(m_VstPlugin.IsBypassed())
|
|
title += _T(" - Bypass");
|
|
|
|
SetWindowText(title);
|
|
}
|
|
}
|
|
|
|
|
|
LRESULT CAbstractVstEditor::OnCustomKeyMsg(WPARAM wParam, LPARAM /*lParam*/)
|
|
{
|
|
switch(wParam)
|
|
{
|
|
case kcVSTGUIPrevPreset: OnSetPreviousVSTPreset(); return wParam;
|
|
case kcVSTGUIPrevPresetJump: OnVSTPresetBackwardJump(); return wParam;
|
|
case kcVSTGUINextPreset: OnSetNextVSTPreset(); return wParam;
|
|
case kcVSTGUINextPresetJump: OnVSTPresetForwardJump(); return wParam;
|
|
case kcVSTGUIRandParams: OnRandomizePreset() ; return wParam;
|
|
case kcVSTGUIToggleRecordParams: OnRecordAutomation(); return wParam;
|
|
case kcVSTGUIToggleSendKeysToPlug: OnPassKeypressesToPlug(); return wParam;
|
|
case kcVSTGUIBypassPlug: OnBypassPlug(); return wParam;
|
|
}
|
|
if (wParam >= kcVSTGUIStartNotes && wParam <= kcVSTGUIEndNotes)
|
|
{
|
|
if(ValidateCurrentInstrument())
|
|
{
|
|
CModDoc *pModDoc = m_VstPlugin.GetModDoc();
|
|
const ModCommand::NOTE note = pModDoc->GetNoteWithBaseOctave(static_cast<int>(wParam - kcVSTGUIStartNotes), m_nInstrument);
|
|
if(ModCommand::IsNote(note))
|
|
{
|
|
pModDoc->PlayNote(PlayNoteParam(note).Instrument(m_nInstrument), &m_noteChannel);
|
|
}
|
|
}
|
|
return wParam;
|
|
}
|
|
if (wParam >= kcVSTGUIStartNoteStops && wParam <= kcVSTGUIEndNoteStops)
|
|
{
|
|
if(ValidateCurrentInstrument())
|
|
{
|
|
CModDoc *pModDoc = m_VstPlugin.GetModDoc();
|
|
const ModCommand::NOTE note = pModDoc->GetNoteWithBaseOctave(static_cast<int>(wParam - kcVSTGUIStartNoteStops), m_nInstrument);
|
|
if(ModCommand::IsNote(note))
|
|
{
|
|
pModDoc->NoteOff(note, false, m_nInstrument, m_noteChannel[note - NOTE_MIN]);
|
|
}
|
|
}
|
|
return wParam;
|
|
}
|
|
|
|
return kcNull;
|
|
}
|
|
|
|
|
|
// When trying to play a note using this plugin, but no instrument is assigned to it,
|
|
// the user is asked whether a new instrument should be added.
|
|
bool CAbstractVstEditor::ValidateCurrentInstrument()
|
|
{
|
|
if(!CheckInstrument(m_nInstrument))
|
|
m_nInstrument = GetBestInstrumentCandidate();
|
|
|
|
//only show messagebox if plug is able to process notes.
|
|
if(m_nInstrument == INSTRUMENTINDEX_INVALID)
|
|
{
|
|
if(m_VstPlugin.CanRecieveMidiEvents())
|
|
{
|
|
// We might need to steal the focus from the plugin bridge. This is going to work
|
|
// as the plugin bridge will call AllowSetForegroundWindow on key messages.
|
|
SetForegroundWindow();
|
|
if(!m_VstPlugin.IsInstrument() || m_VstPlugin.GetSoundFile().GetModSpecifications().instrumentsMax == 0 ||
|
|
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)
|
|
{
|
|
return false;
|
|
} else
|
|
{
|
|
OnCreateInstrument();
|
|
// Return true since we don't want to trigger the note for which the instrument has been validated yet.
|
|
// Otherwise, the note might hang forever because the key-up event will go missing.
|
|
return false;
|
|
}
|
|
} else
|
|
{
|
|
// Can't process notes
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
static int GetNumSubMenus(int32 numProgs) { return (numProgs + (PRESETS_PER_GROUP - 1)) / PRESETS_PER_GROUP; }
|
|
|
|
|
|
void CAbstractVstEditor::OnMenuSelect(UINT nItemID, UINT nFlags, HMENU hSysMenu)
|
|
{
|
|
if(!(nFlags & MF_POPUP))
|
|
{
|
|
return;
|
|
}
|
|
if(hSysMenu == m_Menu.m_hMenu)
|
|
{
|
|
// Main menu
|
|
switch(nItemID)
|
|
{
|
|
case 0:
|
|
// Grey out paste menu item.
|
|
m_Menu.EnableMenuItem(ID_EDIT_PASTE, MF_BYCOMMAND | (IsClipboardFormatAvailable(m_clipboardFormat) ? 0 : MF_GRAYED));
|
|
break;
|
|
case 1:
|
|
// If there would be only one sub menu, we add presets directly to the factory menu
|
|
{
|
|
const int32 numProgs = m_VstPlugin.GetNumPrograms();
|
|
if(GetNumSubMenus(numProgs) <= 1)
|
|
{
|
|
GeneratePresetMenu(0, m_PresetMenu);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
} else if(hSysMenu == m_Menu.GetSubMenu(1)->m_hMenu)
|
|
{
|
|
// Preset menu
|
|
m_currentPresetMenu = nItemID;
|
|
GeneratePresetMenu(nItemID * PRESETS_PER_GROUP, *m_presetMenuGroup[nItemID]);
|
|
}
|
|
}
|
|
|
|
|
|
void CAbstractVstEditor::UpdatePresetMenu(bool force)
|
|
{
|
|
const int32 numProgs = m_VstPlugin.GetNumPrograms();
|
|
const int32 curProg = m_VstPlugin.GetCurrentProgram();
|
|
|
|
if(m_PresetMenu.m_hMenu) // We rebuild the menu from scratch, so remove any exiting menus...
|
|
{
|
|
if(curProg == m_nCurProg && !force) // ... unless menu exists and is accurate, in which case we are done.
|
|
return;
|
|
|
|
m_presetMenuGroup.clear();
|
|
|
|
// If there were no preset groups, delete the remaining content so that it can be refilled dynamically
|
|
while(m_PresetMenu.GetMenuItemCount() > 0)
|
|
m_PresetMenu.RemoveMenu(0, MF_BYPOSITION);
|
|
} else
|
|
{
|
|
// Create Factory preset menu
|
|
m_PresetMenu.CreatePopupMenu();
|
|
m_Menu.InsertMenu(1, MF_BYPOSITION | MF_POPUP, reinterpret_cast<UINT_PTR>(m_PresetMenu.m_hMenu), _T("&Presets"));
|
|
}
|
|
|
|
m_Menu.EnableMenuItem(1, MF_BYPOSITION | (numProgs ? 0 : MF_GRAYED));
|
|
|
|
const int numSubMenus = GetNumSubMenus(numProgs);
|
|
|
|
if(numSubMenus > 1)
|
|
{
|
|
// Depending on the plugin and its number of presets, filling the sub menus can take quite a while (e.g. Synth1),
|
|
// so we fill the menus only on demand (when they are opened), so that the editor GUI creation doesn't take forever.
|
|
m_presetMenuGroup.resize(numSubMenus);
|
|
for(int bank = 0, prog = 0; bank < numSubMenus; bank++, prog += PRESETS_PER_GROUP)
|
|
{
|
|
m_presetMenuGroup[bank] = std::make_unique<CMenu>();
|
|
m_presetMenuGroup[bank]->CreatePopupMenu();
|
|
|
|
CString label;
|
|
label.Format(_T("Bank %d (%d-%d)"), bank + 1, prog + 1, std::min(prog + PRESETS_PER_GROUP, numProgs));
|
|
m_PresetMenu.AppendMenu(MF_POPUP
|
|
| (bank % 32 == 0 ? MF_MENUBREAK : 0)
|
|
| (curProg >= prog && curProg < prog + PRESETS_PER_GROUP ? MF_CHECKED : MF_UNCHECKED),
|
|
reinterpret_cast<UINT_PTR>(m_presetMenuGroup[bank]->m_hMenu), label);
|
|
}
|
|
}
|
|
|
|
m_currentPresetMenu = 0;
|
|
m_nCurProg = curProg;
|
|
}
|
|
|
|
|
|
void CAbstractVstEditor::GeneratePresetMenu(int32 offset, CMenu &parent)
|
|
{
|
|
const int32 numProgs = m_VstPlugin.GetNumPrograms();
|
|
const int32 curProg = m_VstPlugin.GetCurrentProgram();
|
|
const int32 endProg = std::min(offset + PRESETS_PER_GROUP, numProgs);
|
|
|
|
if(parent.GetMenuItemCount() != 0)
|
|
{
|
|
// Already generated.
|
|
return;
|
|
}
|
|
|
|
m_VstPlugin.CacheProgramNames(offset, endProg);
|
|
for(int32 p = offset, row = 0, id = 0; p < endProg; p++, row++, id++)
|
|
{
|
|
CString programName = m_VstPlugin.GetFormattedProgramName(p);
|
|
programName.Replace(_T("&"), _T("&&"));
|
|
UINT splitMenuFlag = 0;
|
|
|
|
if(row == PRESETS_PER_COLUMN)
|
|
{
|
|
// Advance to next menu column
|
|
row = 0;
|
|
splitMenuFlag = MF_MENUBARBREAK;
|
|
}
|
|
|
|
parent.AppendMenu(MF_STRING | (p == curProg ? MF_CHECKED : MF_UNCHECKED) | splitMenuFlag, ID_PRESET_SET + id, programName);
|
|
}
|
|
}
|
|
|
|
|
|
void CAbstractVstEditor::UpdateInputMenu()
|
|
{
|
|
CMenu *pInfoMenu = m_Menu.GetSubMenu(2);
|
|
pInfoMenu->DeleteMenu(0, MF_BYPOSITION);
|
|
|
|
const CSoundFile &sndFile = m_VstPlugin.GetSoundFile();
|
|
|
|
if(m_InputMenu.m_hMenu)
|
|
{
|
|
m_InputMenu.DestroyMenu();
|
|
}
|
|
if(!m_InputMenu.m_hMenu)
|
|
{
|
|
m_InputMenu.CreatePopupMenu();
|
|
}
|
|
|
|
std::vector<IMixPlugin *> inputPlugs;
|
|
m_VstPlugin.GetInputPlugList(inputPlugs);
|
|
for(auto plug : inputPlugs)
|
|
{
|
|
CString name = MPT_CFORMAT("FX{}: {}")(mpt::cfmt::dec0<2>(plug->m_nSlot + 1), mpt::ToCString(plug->m_pMixStruct->GetName()));
|
|
m_InputMenu.AppendMenu(MF_STRING, ID_PLUGSELECT + plug->m_nSlot, name);
|
|
}
|
|
|
|
std::vector<CHANNELINDEX> inputChannels;
|
|
m_VstPlugin.GetInputChannelList(inputChannels);
|
|
bool addSeparator = !inputPlugs.empty();
|
|
for(auto chn : inputChannels)
|
|
{
|
|
if(addSeparator)
|
|
{
|
|
m_InputMenu.AppendMenu(MF_SEPARATOR);
|
|
addSeparator = false;
|
|
}
|
|
CString name = MPT_CFORMAT("Chn{}: {}")(mpt::cfmt::dec0<2>(chn + 1), mpt::ToCString(sndFile.GetCharsetInternal(), sndFile.ChnSettings[chn].szName));
|
|
m_InputMenu.AppendMenu(MF_STRING, NULL, name);
|
|
}
|
|
|
|
std::vector<INSTRUMENTINDEX> inputInstruments;
|
|
m_VstPlugin.GetInputInstrumentList(inputInstruments);
|
|
addSeparator = !inputPlugs.empty() || !inputChannels.empty();
|
|
for(auto ins : inputInstruments)
|
|
{
|
|
if(addSeparator)
|
|
{
|
|
m_InputMenu.AppendMenu(MF_SEPARATOR);
|
|
addSeparator = false;
|
|
}
|
|
CString name = MPT_CFORMAT("Ins{}: {}")(mpt::cfmt::dec0<2>(ins), mpt::ToCString(sndFile.GetCharsetInternal(), sndFile.GetInstrumentName(ins)));
|
|
m_InputMenu.AppendMenu(MF_STRING | ((ins == m_nInstrument) ? MF_CHECKED : 0), ID_SELECTINST + ins, name);
|
|
}
|
|
|
|
if(inputPlugs.empty() && inputChannels.empty() && inputInstruments.empty())
|
|
{
|
|
m_InputMenu.AppendMenu(MF_STRING | MF_GRAYED, NULL, _T("None"));
|
|
}
|
|
|
|
pInfoMenu->InsertMenu(0, MF_BYPOSITION | MF_POPUP, reinterpret_cast<UINT_PTR>(m_InputMenu.m_hMenu), _T("I&nputs"));
|
|
}
|
|
|
|
|
|
void CAbstractVstEditor::UpdateOutputMenu()
|
|
{
|
|
CMenu *pInfoMenu = m_Menu.GetSubMenu(2);
|
|
pInfoMenu->DeleteMenu(1, MF_BYPOSITION);
|
|
|
|
if(m_OutputMenu.m_hMenu)
|
|
{
|
|
m_OutputMenu.DestroyMenu();
|
|
}
|
|
if(!m_OutputMenu.m_hMenu)
|
|
{
|
|
m_OutputMenu.CreatePopupMenu();
|
|
}
|
|
|
|
std::vector<IMixPlugin *> outputPlugs;
|
|
m_VstPlugin.GetOutputPlugList(outputPlugs);
|
|
CString name;
|
|
|
|
for(auto plug : outputPlugs)
|
|
{
|
|
if(plug != nullptr)
|
|
{
|
|
name.Format(_T("FX%02d: "), plug->m_nSlot + 1);
|
|
name += mpt::ToCString(plug->m_pMixStruct->GetName());
|
|
m_OutputMenu.AppendMenu(MF_STRING, ID_PLUGSELECT + plug->m_nSlot, name);
|
|
} else
|
|
{
|
|
name = _T("Master Output");
|
|
m_OutputMenu.AppendMenu(MF_STRING | MF_GRAYED, NULL, name);
|
|
}
|
|
}
|
|
pInfoMenu->InsertMenu(1, MF_BYPOSITION | MF_POPUP, reinterpret_cast<UINT_PTR>(m_OutputMenu.m_hMenu), _T("Ou&tputs"));
|
|
}
|
|
|
|
|
|
void CAbstractVstEditor::UpdateMacroMenu()
|
|
{
|
|
CMenu *pInfoMenu = m_Menu.GetSubMenu(2);
|
|
pInfoMenu->DeleteMenu(2, MF_BYPOSITION);
|
|
|
|
if(m_MacroMenu.m_hMenu)
|
|
{
|
|
m_MacroMenu.DestroyMenu();
|
|
}
|
|
if(!m_MacroMenu.m_hMenu)
|
|
{
|
|
m_MacroMenu.CreatePopupMenu();
|
|
}
|
|
|
|
CString label, macroName;
|
|
for(int nMacro = 0; nMacro < kSFxMacros; nMacro++)
|
|
{
|
|
int action = 0;
|
|
UINT greyed = MF_GRAYED;
|
|
|
|
const MIDIMacroConfig &midiCfg = m_VstPlugin.GetSoundFile().m_MidiCfg;
|
|
|
|
const ParameteredMacro macroType = midiCfg.GetParameteredMacroType(nMacro);
|
|
|
|
if(macroType == kSFxUnused)
|
|
{
|
|
macroName = _T("Unused. Learn Param...");
|
|
action= ID_LEARN_MACRO_FROM_PLUGGUI + nMacro;
|
|
greyed = 0;
|
|
} else
|
|
{
|
|
macroName = midiCfg.GetParameteredMacroName(nMacro, &m_VstPlugin);
|
|
if(macroType != kSFxPlugParam || macroName.Left(3) != _T("N/A"))
|
|
{
|
|
greyed = 0;
|
|
}
|
|
}
|
|
|
|
label.Format(_T("SF%X: "), nMacro);
|
|
label += macroName;
|
|
m_MacroMenu.AppendMenu(MF_STRING | greyed, action, label);
|
|
}
|
|
|
|
pInfoMenu->InsertMenu(2, MF_BYPOSITION | MF_POPUP, reinterpret_cast<UINT_PTR>(m_MacroMenu.m_hMenu), _T("&Macros"));
|
|
}
|
|
|
|
void CAbstractVstEditor::UpdateOptionsMenu()
|
|
{
|
|
|
|
if(m_OptionsMenu.m_hMenu)
|
|
m_OptionsMenu.DestroyMenu();
|
|
|
|
CInputHandler *ih = CMainFrame::GetInputHandler();
|
|
|
|
m_OptionsMenu.CreatePopupMenu();
|
|
|
|
//Bypass
|
|
m_OptionsMenu.AppendMenu(MF_STRING | (m_VstPlugin.IsBypassed() ? MF_CHECKED : 0),
|
|
ID_PLUG_BYPASS, ih->GetKeyTextFromCommand(kcVSTGUIBypassPlug, _T("&Bypass Plugin")));
|
|
//Record Params
|
|
m_OptionsMenu.AppendMenu(MF_STRING | (m_VstPlugin.m_recordAutomation ? MF_CHECKED : 0),
|
|
ID_PLUG_RECORDAUTOMATION, ih->GetKeyTextFromCommand(kcVSTGUIToggleRecordParams, _T("Record &Parameter Changes")));
|
|
//Record MIDI Out
|
|
m_OptionsMenu.AppendMenu(MF_STRING | (m_VstPlugin.m_recordMIDIOut ? MF_CHECKED : 0),
|
|
ID_PLUG_RECORD_MIDIOUT, ih->GetKeyTextFromCommand(kcVSTGUIToggleRecordMIDIOut, _T("Record &MIDI Out to Pattern Editor")));
|
|
//Pass on keypresses
|
|
m_OptionsMenu.AppendMenu(MF_STRING | (m_VstPlugin.m_passKeypressesToPlug ? MF_CHECKED : 0),
|
|
ID_PLUG_PASSKEYS, ih->GetKeyTextFromCommand(kcVSTGUIToggleSendKeysToPlug, _T("Pass &Keys to Plugin")));
|
|
|
|
|
|
m_Menu.DeleteMenu(3, MF_BYPOSITION);
|
|
m_Menu.InsertMenu(3, MF_BYPOSITION | MF_POPUP, reinterpret_cast<UINT_PTR>(m_OptionsMenu.m_hMenu), _T("&Options"));
|
|
|
|
}
|
|
|
|
|
|
void CAbstractVstEditor::OnToggleEditor(UINT nID)
|
|
{
|
|
CModDoc *pModDoc = m_VstPlugin.GetModDoc();
|
|
|
|
if(pModDoc)
|
|
{
|
|
pModDoc->TogglePluginEditor(nID - ID_PLUGSELECT);
|
|
}
|
|
}
|
|
|
|
|
|
void CAbstractVstEditor::OnInitMenu(CMenu* /*pMenu*/)
|
|
{
|
|
SetupMenu();
|
|
}
|
|
|
|
|
|
bool CAbstractVstEditor::CheckInstrument(INSTRUMENTINDEX ins) const
|
|
{
|
|
const CSoundFile &sndFile = m_VstPlugin.GetSoundFile();
|
|
|
|
if(ins != INSTRUMENTINDEX_INVALID && ins < MAX_INSTRUMENTS && sndFile.Instruments[ins] != nullptr)
|
|
{
|
|
return (sndFile.Instruments[ins]->nMixPlug) == (m_VstPlugin.m_nSlot + 1);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
INSTRUMENTINDEX CAbstractVstEditor::GetBestInstrumentCandidate() const
|
|
{
|
|
// First try current instrument:
|
|
const CModDoc *modDoc = m_VstPlugin.GetModDoc();
|
|
POSITION pos = modDoc->GetFirstViewPosition();
|
|
while(pos != NULL)
|
|
{
|
|
CModControlView *pView = dynamic_cast<CModControlView *>(modDoc->GetNextView(pos));
|
|
if(pView != nullptr && pView->GetDocument() == modDoc)
|
|
{
|
|
INSTRUMENTINDEX ins = static_cast<INSTRUMENTINDEX>(pView->GetInstrumentChange());
|
|
if(CheckInstrument(ins))
|
|
return ins;
|
|
}
|
|
}
|
|
|
|
// Just take the first instrument that points to this plug..
|
|
return modDoc->HasInstrumentForPlugin(m_VstPlugin.m_nSlot);
|
|
}
|
|
|
|
|
|
void CAbstractVstEditor::OnSetInputInstrument(UINT nID)
|
|
{
|
|
m_nInstrument = static_cast<INSTRUMENTINDEX>(nID - ID_SELECTINST);
|
|
}
|
|
|
|
|
|
void CAbstractVstEditor::OnCreateInstrument()
|
|
{
|
|
if(m_VstPlugin.GetModDoc() != nullptr)
|
|
{
|
|
INSTRUMENTINDEX instr = m_VstPlugin.GetModDoc()->InsertInstrumentForPlugin(m_VstPlugin.GetSlot());
|
|
if(instr != INSTRUMENTINDEX_INVALID) m_nInstrument = instr;
|
|
}
|
|
}
|
|
|
|
|
|
void CAbstractVstEditor::PrepareToLearnMacro(UINT nID)
|
|
{
|
|
m_nLearnMacro = (nID-ID_LEARN_MACRO_FROM_PLUGGUI);
|
|
//Now we wait for a param to be touched. We'll get the message from the VST Plug Manager.
|
|
//Then pModDoc->LearnMacro(macro, param) is called
|
|
}
|
|
|
|
void CAbstractVstEditor::SetLearnMacro(int inMacro)
|
|
{
|
|
if (inMacro < kSFxMacros)
|
|
{
|
|
m_nLearnMacro=inMacro;
|
|
}
|
|
}
|
|
|
|
int CAbstractVstEditor::GetLearnMacro()
|
|
{
|
|
return m_nLearnMacro;
|
|
}
|
|
|
|
|
|
void CAbstractVstEditor::OnMove(int, int)
|
|
{
|
|
if(IsWindowVisible())
|
|
{
|
|
StoreWindowPos();
|
|
}
|
|
}
|
|
|
|
|
|
void CAbstractVstEditor::StoreWindowPos()
|
|
{
|
|
if(m_hWnd)
|
|
{
|
|
WINDOWPLACEMENT wnd;
|
|
wnd.length = sizeof(WINDOWPLACEMENT);
|
|
GetWindowPlacement(&wnd);
|
|
m_VstPlugin.SetEditorPos(wnd.rcNormalPosition.left, wnd.rcNormalPosition.top);
|
|
}
|
|
}
|
|
|
|
|
|
void CAbstractVstEditor::RestoreWindowPos()
|
|
{
|
|
// Restore previous editor position
|
|
int32 editorX, editorY;
|
|
m_VstPlugin.GetEditorPos(editorX, editorY);
|
|
|
|
if(editorX != int32_min && editorY != int32_min)
|
|
{
|
|
WINDOWPLACEMENT wnd;
|
|
wnd.length = sizeof(wnd);
|
|
GetWindowPlacement(&wnd);
|
|
wnd.showCmd = SW_SHOWNOACTIVATE;
|
|
CRect rect = wnd.rcNormalPosition;
|
|
rect.MoveToXY(editorX, editorY);
|
|
wnd.rcNormalPosition = rect;
|
|
SetWindowPlacement(&wnd);
|
|
}
|
|
}
|
|
|
|
|
|
#endif // NO_PLUGINS
|
|
|
|
|
|
OPENMPT_NAMESPACE_END
|