/* * view_gen.cpp * ------------ * Purpose: General tab, lower panel. * 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" #include "Mptrack.h" #include "Mainfrm.h" #include "InputHandler.h" #include "Childfrm.h" #include "Moddoc.h" #include "Globals.h" #include "Ctrl_gen.h" #include "View_gen.h" #include "../soundlib/plugins/PlugInterface.h" #include "EffectVis.h" #include "MoveFXSlotDialog.h" #include "ChannelManagerDlg.h" #include "SelectPluginDialog.h" #include "../soundlib/mod_specifications.h" #include "../common/mptStringBuffer.h" #include "AbstractVstEditor.h" // This is used for retrieving the correct background colour for the // frames on the general tab when using WinXP Luna or Vista/Win7 Aero. #include OPENMPT_NAMESPACE_BEGIN IMPLEMENT_SERIAL(CViewGlobals, CFormView, 0) BEGIN_MESSAGE_MAP(CViewGlobals, CFormView) //{{AFX_MSG_MAP(CViewGlobals) ON_WM_SIZE() ON_WM_HSCROLL() ON_WM_VSCROLL() ON_WM_DESTROY() ON_MESSAGE(WM_MOD_MDIACTIVATE, &CViewGlobals::OnMDIDeactivate) ON_MESSAGE(WM_MOD_MDIDEACTIVATE, &CViewGlobals::OnMDIDeactivate) ON_COMMAND(IDC_CHECK1, &CViewGlobals::OnMute1) ON_COMMAND(IDC_CHECK3, &CViewGlobals::OnMute2) ON_COMMAND(IDC_CHECK5, &CViewGlobals::OnMute3) ON_COMMAND(IDC_CHECK7, &CViewGlobals::OnMute4) ON_COMMAND(IDC_CHECK2, &CViewGlobals::OnSurround1) ON_COMMAND(IDC_CHECK4, &CViewGlobals::OnSurround2) ON_COMMAND(IDC_CHECK6, &CViewGlobals::OnSurround3) ON_COMMAND(IDC_CHECK8, &CViewGlobals::OnSurround4) ON_COMMAND(IDC_BUTTON9, &CViewGlobals::OnEditColor1) ON_COMMAND(IDC_BUTTON10, &CViewGlobals::OnEditColor2) ON_COMMAND(IDC_BUTTON11, &CViewGlobals::OnEditColor3) ON_COMMAND(IDC_BUTTON12, &CViewGlobals::OnEditColor4) ON_EN_UPDATE(IDC_EDIT1, &CViewGlobals::OnEditVol1) ON_EN_UPDATE(IDC_EDIT3, &CViewGlobals::OnEditVol2) ON_EN_UPDATE(IDC_EDIT5, &CViewGlobals::OnEditVol3) ON_EN_UPDATE(IDC_EDIT7, &CViewGlobals::OnEditVol4) ON_EN_UPDATE(IDC_EDIT2, &CViewGlobals::OnEditPan1) ON_EN_UPDATE(IDC_EDIT4, &CViewGlobals::OnEditPan2) ON_EN_UPDATE(IDC_EDIT6, &CViewGlobals::OnEditPan3) ON_EN_UPDATE(IDC_EDIT8, &CViewGlobals::OnEditPan4) ON_EN_UPDATE(IDC_EDIT9, &CViewGlobals::OnEditName1) ON_EN_UPDATE(IDC_EDIT10, &CViewGlobals::OnEditName2) ON_EN_UPDATE(IDC_EDIT11, &CViewGlobals::OnEditName3) ON_EN_UPDATE(IDC_EDIT12, &CViewGlobals::OnEditName4) ON_CBN_SELCHANGE(IDC_COMBO1, &CViewGlobals::OnFx1Changed) ON_CBN_SELCHANGE(IDC_COMBO2, &CViewGlobals::OnFx2Changed) ON_CBN_SELCHANGE(IDC_COMBO3, &CViewGlobals::OnFx3Changed) ON_CBN_SELCHANGE(IDC_COMBO4, &CViewGlobals::OnFx4Changed) // Plugins ON_COMMAND(IDC_CHECK9, &CViewGlobals::OnMixModeChanged) ON_COMMAND(IDC_CHECK10, &CViewGlobals::OnBypassChanged) ON_COMMAND(IDC_CHECK11, &CViewGlobals::OnDryMixChanged) ON_COMMAND(IDC_BUTTON1, &CViewGlobals::OnSelectPlugin) ON_COMMAND(IDC_DELPLUGIN, &CViewGlobals::OnRemovePlugin) ON_COMMAND(IDC_BUTTON2, &CViewGlobals::OnEditPlugin) ON_COMMAND(IDC_BUTTON4, &CViewGlobals::OnNextPlugin) ON_COMMAND(IDC_BUTTON5, &CViewGlobals::OnPrevPlugin) ON_COMMAND(IDC_MOVEFXSLOT, &CViewGlobals::OnMovePlugToSlot) ON_COMMAND(IDC_INSERTFXSLOT,&CViewGlobals::OnInsertSlot) ON_COMMAND(IDC_CLONEPLUG, &CViewGlobals::OnClonePlug) ON_COMMAND(IDC_BUTTON6, &CViewGlobals::OnLoadParam) ON_COMMAND(IDC_BUTTON8, &CViewGlobals::OnSaveParam) ON_EN_UPDATE(IDC_EDIT13, &CViewGlobals::OnPluginNameChanged) ON_EN_UPDATE(IDC_EDIT14, &CViewGlobals::OnSetParameter) ON_EN_SETFOCUS(IDC_EDIT14, &CViewGlobals::OnFocusParam) ON_EN_KILLFOCUS(IDC_EDIT14, &CViewGlobals::OnParamChanged) ON_CBN_SELCHANGE(IDC_COMBO5, &CViewGlobals::OnPluginChanged) ON_CBN_SELCHANGE(IDC_COMBO6, &CViewGlobals::OnParamChanged) ON_CBN_SETFOCUS(IDC_COMBO6, &CViewGlobals::OnFillParamCombo) ON_CBN_SELCHANGE(IDC_COMBO7, &CViewGlobals::OnOutputRoutingChanged) ON_CBN_SELCHANGE(IDC_COMBO8, &CViewGlobals::OnProgramChanged) ON_CBN_SETFOCUS(IDC_COMBO8, &CViewGlobals::OnFillProgramCombo) ON_COMMAND(IDC_CHECK12, &CViewGlobals::OnWetDryExpandChanged) ON_CBN_SELCHANGE(IDC_COMBO9, &CViewGlobals::OnSpecialMixProcessingChanged) ON_NOTIFY(TCN_SELCHANGE, IDC_TABCTRL1, &CViewGlobals::OnTabSelchange) ON_MESSAGE(WM_MOD_UNLOCKCONTROLS, &CViewGlobals::OnUnlockControls) ON_MESSAGE(WM_MOD_VIEWMSG, &CViewGlobals::OnModViewMsg) ON_MESSAGE(WM_MOD_MIDIMSG, &CViewGlobals::OnMidiMsg) ON_MESSAGE(WM_MOD_PLUGPARAMAUTOMATE, &CViewGlobals::OnParamAutomated) ON_MESSAGE(WM_MOD_PLUGINDRYWETRATIOCHANGED, &CViewGlobals::OnDryWetRatioChangedFromPlayer) ON_COMMAND(ID_EDIT_UNDO, &CViewGlobals::OnEditUndo) ON_COMMAND(ID_EDIT_REDO, &CViewGlobals::OnEditRedo) ON_UPDATE_COMMAND_UI(ID_EDIT_UNDO, &CViewGlobals::OnUpdateUndo) ON_UPDATE_COMMAND_UI(ID_EDIT_REDO, &CViewGlobals::OnUpdateRedo) ON_NOTIFY_EX_RANGE(TTN_NEEDTEXT, 0, 0xFFFF, &CViewGlobals::OnToolTipText) //}}AFX_MSG_MAP END_MESSAGE_MAP() void CViewGlobals::DoDataExchange(CDataExchange* pDX) { CFormView::DoDataExchange(pDX); //{{AFX_DATA_MAP(CViewGlobals) DDX_Control(pDX, IDC_TABCTRL1, m_TabCtrl); DDX_Control(pDX, IDC_COMBO1, m_CbnEffects[0]); DDX_Control(pDX, IDC_COMBO2, m_CbnEffects[1]); DDX_Control(pDX, IDC_COMBO3, m_CbnEffects[2]); DDX_Control(pDX, IDC_COMBO4, m_CbnEffects[3]); DDX_Control(pDX, IDC_COMBO5, m_CbnPlugin); DDX_Control(pDX, IDC_COMBO6, m_CbnParam); DDX_Control(pDX, IDC_COMBO7, m_CbnOutput); DDX_Control(pDX, IDC_COMBO8, m_CbnPreset); DDX_Control(pDX, IDC_COMBO9, m_CbnSpecialMixProcessing); DDX_Control(pDX, IDC_SPIN10, m_SpinMixGain); DDX_Control(pDX, IDC_SLIDER1, m_sbVolume[0]); DDX_Control(pDX, IDC_SLIDER2, m_sbPan[0]); DDX_Control(pDX, IDC_SLIDER3, m_sbVolume[1]); DDX_Control(pDX, IDC_SLIDER4, m_sbPan[1]); DDX_Control(pDX, IDC_SLIDER5, m_sbVolume[2]); DDX_Control(pDX, IDC_SLIDER6, m_sbPan[2]); DDX_Control(pDX, IDC_SLIDER7, m_sbVolume[3]); DDX_Control(pDX, IDC_SLIDER8, m_sbPan[3]); DDX_Control(pDX, IDC_SLIDER9, m_sbValue); DDX_Control(pDX, IDC_SLIDER10, m_sbDryRatio); DDX_Control(pDX, IDC_SPIN1, m_spinVolume[0]); DDX_Control(pDX, IDC_SPIN2, m_spinPan[0]); DDX_Control(pDX, IDC_SPIN3, m_spinVolume[1]); DDX_Control(pDX, IDC_SPIN4, m_spinPan[1]); DDX_Control(pDX, IDC_SPIN5, m_spinVolume[2]); DDX_Control(pDX, IDC_SPIN6, m_spinPan[2]); DDX_Control(pDX, IDC_SPIN7, m_spinVolume[3]); DDX_Control(pDX, IDC_SPIN8, m_spinPan[3]); DDX_Control(pDX, IDC_BUTTON1, m_BtnSelect); DDX_Control(pDX, IDC_BUTTON2, m_BtnEdit); //}}AFX_DATA_MAP } void CViewGlobals::OnInitialUpdate() { CChildFrame *pFrame = (CChildFrame *)GetParentFrame(); int nMapMode = MM_TEXT; SIZE sizeTotal, sizePage, sizeLine; m_nActiveTab = CHANNELINDEX(-1); m_nCurrentPlugin = 0; m_nCurrentParam = 0; CFormView::OnInitialUpdate(); EnableToolTips(); if (pFrame) { GENERALVIEWSTATE &generalState = pFrame->GetGeneralViewState(); if (generalState.initialized) { m_TabCtrl.SetCurSel(generalState.nTab); m_nActiveTab = generalState.nTab; m_nCurrentPlugin = generalState.nPlugin; m_nCurrentParam = generalState.nParam; } } GetDeviceScrollSizes(nMapMode, sizeTotal, sizePage, sizeLine); m_rcClient.SetRect(0, 0, sizeTotal.cx, sizeTotal.cy); RecalcLayout(); // Initializing scroll ranges for(int ichn = 0; ichn < CHANNELS_IN_TAB; ichn++) { // Color select m_channelColor[ichn].SubclassDlgItem(IDC_BUTTON9 + ichn, this); // Volume Slider m_sbVolume[ichn].SetRange(0, 64); m_sbVolume[ichn].SetTicFreq(8); // Pan Slider m_sbPan[ichn].SetRange(0, 64); m_sbPan[ichn].SetTicFreq(8); // Volume Spin m_spinVolume[ichn].SetRange(0, 64); // Pan Spin m_spinPan[ichn].SetRange(0, 256); } m_sbValue.SetPos(0); m_sbValue.SetRange(0, 100); m_sbValue.SetPos(0); m_sbValue.SetRange(0, 100); m_CbnSpecialMixProcessing.AddString(_T("Default")); m_CbnSpecialMixProcessing.AddString(_T("Wet Subtract")); m_CbnSpecialMixProcessing.AddString(_T("Dry Subtract")); m_CbnSpecialMixProcessing.AddString(_T("Mix Subtract")); m_CbnSpecialMixProcessing.AddString(_T("Middle Subtract")); m_CbnSpecialMixProcessing.AddString(_T("LR Balance")); m_SpinMixGain.SetRange(0, 80); m_SpinMixGain.SetPos(10); SetDlgItemText(IDC_EDIT16, _T("Gain: x1.0")); UpdateView(UpdateHint().ModType()); OnParamChanged(); m_nLockCount = 0; // Use tab background color rather than regular dialog background color (required for Aero/etc. where they are not the same color) EnableThemeDialogTexture(m_hWnd, ETDT_ENABLETAB); } void CViewGlobals::OnDestroy() { CChildFrame *pFrame = (CChildFrame *)GetParentFrame(); if (pFrame) { GENERALVIEWSTATE &generalState = pFrame->GetGeneralViewState(); generalState.initialized = true; generalState.nTab = m_nActiveTab; generalState.nPlugin = m_nCurrentPlugin; generalState.nParam = m_nCurrentParam; } CFormView::OnDestroy(); } LRESULT CViewGlobals::OnMDIDeactivate(WPARAM, LPARAM) { // Create new undo point if we switch to / from other window m_lastEdit = CHANNELINDEX_INVALID; return 0; } LRESULT CViewGlobals::OnMidiMsg(WPARAM midiData_, LPARAM) { uint32 midiData = static_cast(midiData_); // Handle MIDI messages assigned to shortcuts CInputHandler *ih = CMainFrame::GetInputHandler(); ih->HandleMIDIMessage(kCtxViewGeneral, midiData) != kcNull || ih->HandleMIDIMessage(kCtxAllContexts, midiData) != kcNull; return 1; } void CViewGlobals::RecalcLayout() { if (m_TabCtrl.m_hWnd != NULL) { CRect rect; GetClientRect(&rect); if (rect.right < m_rcClient.right) rect.right = m_rcClient.right; if (rect.bottom < m_rcClient.bottom) rect.bottom = m_rcClient.bottom; m_TabCtrl.SetWindowPos(&CWnd::wndBottom, 0,0, rect.right, rect.bottom, SWP_NOMOVE); } } int CViewGlobals::GetDlgItemIntEx(UINT nID) { CString s; GetDlgItemText(nID, s); if(s.GetLength() < 1 || s[0] < _T('0') || s[0] > _T('9')) return -1; return _ttoi(s); } void CViewGlobals::OnSize(UINT nType, int cx, int cy) { CFormView::OnSize(nType, cx, cy); if (((nType == SIZE_RESTORED) || (nType == SIZE_MAXIMIZED)) && (cx > 0) && (cy > 0) && (m_hWnd)) { RecalcLayout(); } } void CViewGlobals::OnUpdate(CView *pView, LPARAM lHint, CObject *pHint) { if (pView != this) UpdateView(UpdateHint::FromLPARAM(lHint), pHint); } void CViewGlobals::UpdateView(UpdateHint hint, CObject *pObject) { const CModDoc *pModDoc = GetDocument(); int nTabCount, nTabIndex; if (!pModDoc || pObject == this ) return; const CSoundFile &sndFile = pModDoc->GetSoundFile(); const GeneralHint genHint = hint.ToType(); const PluginHint plugHint = hint.ToType(); if (!genHint.GetType()[HINT_MODTYPE | HINT_MODCHANNELS] && !plugHint.GetType()[HINT_MIXPLUGINS | HINT_PLUGINNAMES | HINT_PLUGINPARAM]) { return; } FlagSet hintType = hint.GetType(); const bool updateAll = hintType[HINT_MODTYPE]; const auto updateChannel = genHint.GetChannel(); const int updateTab = (updateChannel < sndFile.GetNumChannels()) ? (updateChannel / CHANNELS_IN_TAB) : m_nActiveTab; if(genHint.GetType()[HINT_MODCHANNELS] && updateTab != m_nActiveTab) return; CString s; nTabCount = (sndFile.m_nChannels + (CHANNELS_IN_TAB - 1)) / CHANNELS_IN_TAB; if (nTabCount != m_TabCtrl.GetItemCount()) { UINT nOldSel = m_TabCtrl.GetCurSel(); if (!m_TabCtrl.GetItemCount()) nOldSel = m_nActiveTab; m_TabCtrl.SetRedraw(FALSE); m_TabCtrl.DeleteAllItems(); for (int iItem=0; iItem(MAX_BASECHANNELS)); s = MPT_CFORMAT("{} - {}")(iItem * CHANNELS_IN_TAB + 1, lastItem); TC_ITEM tci; tci.mask = TCIF_TEXT | TCIF_PARAM; tci.pszText = const_cast(s.GetString()); tci.lParam = iItem * CHANNELS_IN_TAB; m_TabCtrl.InsertItem(iItem, &tci); } if (nOldSel >= (UINT)nTabCount) nOldSel = 0; m_TabCtrl.SetRedraw(TRUE); m_TabCtrl.SetCurSel(nOldSel); InvalidateRect(NULL, FALSE); } nTabIndex = m_TabCtrl.GetCurSel(); if ((nTabIndex < 0) || (nTabIndex >= nTabCount)) return; // ??? if (m_nActiveTab != nTabIndex || genHint.GetType()[HINT_MODTYPE | HINT_MODCHANNELS]) { LockControls(); m_nActiveTab = static_cast(nTabIndex); for (CHANNELINDEX ichn = 0; ichn < CHANNELS_IN_TAB; ichn++) { const CHANNELINDEX nChn = m_nActiveTab * CHANNELS_IN_TAB + ichn; const BOOL bEnable = (nChn < sndFile.GetNumChannels()) ? TRUE : FALSE; if(nChn < MAX_BASECHANNELS) { const auto &chnSettings = sndFile.ChnSettings[nChn]; // Text if(bEnable) s = MPT_CFORMAT("Channel {}")(nChn + 1); else s = _T(""); SetDlgItemText(IDC_TEXT1 + ichn, s); // Channel color m_channelColor[ichn].SetColor(chnSettings.color); m_channelColor[ichn].EnableWindow(bEnable); // Mute CheckDlgButton(IDC_CHECK1 + ichn * 2, chnSettings.dwFlags[CHN_MUTE] ? TRUE : FALSE); // Surround CheckDlgButton(IDC_CHECK2 + ichn * 2, chnSettings.dwFlags[CHN_SURROUND] ? TRUE : FALSE); // Volume int vol = chnSettings.nVolume; m_sbVolume[ichn].SetPos(vol); m_sbVolume[ichn].Invalidate(FALSE); SetDlgItemInt(IDC_EDIT1+ichn*2, vol); // Pan int pan = chnSettings.nPan; m_sbPan[ichn].SetPos(pan/4); m_sbPan[ichn].Invalidate(FALSE); SetDlgItemInt(IDC_EDIT2+ichn*2, pan); // Channel name s = mpt::ToCString(sndFile.GetCharsetInternal(), chnSettings.szName); SetDlgItemText(IDC_EDIT9 + ichn, s); ((CEdit*)(GetDlgItem(IDC_EDIT9 + ichn)))->LimitText(MAX_CHANNELNAME - 1); } else { SetDlgItemText(IDC_TEXT1 + ichn, _T("")); SetDlgItemText(IDC_EDIT9 + ichn, _T("")); m_channelColor[ichn].EnableWindow(FALSE); } // Enable/Disable controls for this channel BOOL bIT = ((bEnable) && (sndFile.m_nType & (MOD_TYPE_IT|MOD_TYPE_MPT))); GetDlgItem(IDC_CHECK1 + ichn * 2)->EnableWindow(bEnable); GetDlgItem(IDC_CHECK2 + ichn * 2)->EnableWindow(bIT); m_sbVolume[ichn].EnableWindow(bIT); m_spinVolume[ichn].EnableWindow(bIT); m_sbPan[ichn].EnableWindow(bEnable && !(sndFile.GetType() & (MOD_TYPE_XM|MOD_TYPE_MOD))); m_spinPan[ichn].EnableWindow(bEnable && !(sndFile.GetType() & (MOD_TYPE_XM|MOD_TYPE_MOD))); GetDlgItem(IDC_EDIT1 + ichn * 2)->EnableWindow(bIT); // channel vol GetDlgItem(IDC_EDIT2 + ichn * 2)->EnableWindow(bEnable && !(sndFile.GetType() & (MOD_TYPE_XM|MOD_TYPE_MOD))); // channel pan GetDlgItem(IDC_EDIT9 + ichn)->EnableWindow(((bEnable) && (sndFile.m_nType & (MOD_TYPE_XM|MOD_TYPE_IT|MOD_TYPE_MPT)))); // channel name m_CbnEffects[ichn].EnableWindow(bEnable & (sndFile.GetModSpecifications().supportsPlugins ? TRUE : FALSE)); } UnlockControls(); } // Update plugin names if(genHint.GetType()[HINT_MODTYPE | HINT_MODCHANNELS] || plugHint.GetType()[HINT_PLUGINNAMES]) { PopulateChannelPlugins(plugHint.GetPlugin() ? plugHint.GetPlugin() - 1 : PLUGINDEX_INVALID); SetDlgItemText(IDC_EDIT13, mpt::ToCString(sndFile.m_MixPlugins[m_nCurrentPlugin].GetName())); } // Update plugin info const SNDMIXPLUGIN &plugin = sndFile.m_MixPlugins[m_nCurrentPlugin]; const bool updatePlug = (plugHint.GetPlugin() == 0 || plugHint.GetPlugin() == m_nCurrentPlugin + 1); const bool updateWholePluginView = updateAll || (plugHint.GetType()[HINT_MIXPLUGINS] && updatePlug); if(updateWholePluginView) { const PLUGINDEX plugIndex = (plugHint.GetPlugin() != 0) ? plugHint.GetPlugin() - 1 : PLUGINDEX_INVALID; m_CbnPlugin.SetRedraw(FALSE); if(plugIndex == PLUGINDEX_INVALID) m_CbnPlugin.ResetContent(); AddPluginNamesToCombobox(m_CbnPlugin, sndFile.m_MixPlugins, true, plugIndex); m_CbnPlugin.SetRedraw(TRUE); m_CbnPlugin.SetCurSel(m_nCurrentPlugin); if (m_nCurrentPlugin >= MAX_MIXPLUGINS) m_nCurrentPlugin = 0; SetDlgItemText(IDC_EDIT13, mpt::ToCString(plugin.GetName())); CheckDlgButton(IDC_CHECK9, plugin.IsMasterEffect() ? BST_CHECKED : BST_UNCHECKED); CheckDlgButton(IDC_CHECK10, plugin.IsBypassed() ? BST_CHECKED : BST_UNCHECKED); CheckDlgButton(IDC_CHECK11, plugin.IsWetMix() ? BST_CHECKED : BST_UNCHECKED); IMixPlugin *pPlugin = plugin.pMixPlugin; m_BtnEdit.EnableWindow((pPlugin != nullptr && (pPlugin->HasEditor() || pPlugin->GetNumParameters())) ? TRUE : FALSE); GetDlgItem(IDC_MOVEFXSLOT)->EnableWindow((pPlugin) ? TRUE : FALSE); GetDlgItem(IDC_INSERTFXSLOT)->EnableWindow((pPlugin) ? TRUE : FALSE); GetDlgItem(IDC_CLONEPLUG)->EnableWindow((pPlugin) ? TRUE : FALSE); UpdateDryWetDisplay(); if(pPlugin && pPlugin->IsInstrument()) { m_CbnSpecialMixProcessing.EnableWindow(FALSE); GetDlgItem(IDC_CHECK12)->EnableWindow(FALSE); } else { m_CbnSpecialMixProcessing.EnableWindow(TRUE); GetDlgItem(IDC_CHECK12)->EnableWindow(TRUE); m_CbnSpecialMixProcessing.SetCurSel(plugin.GetMixMode()); CheckDlgButton(IDC_CHECK12, plugin.IsExpandedMix() ? BST_CHECKED : BST_UNCHECKED); } int gain = plugin.GetGain(); if(gain == 0) gain = 10; float value = 0.1f * (float)gain; s.Format(_T("Gain: x%1.1f"), value); SetDlgItemText(IDC_EDIT16, s); m_SpinMixGain.SetPos(gain); if (pPlugin) { const PlugParamIndex nParams = pPlugin->GetNumParameters(); m_CbnParam.SetRedraw(FALSE); m_CbnParam.ResetContent(); if (m_nCurrentParam >= nParams) m_nCurrentParam = 0; if(nParams) { m_CbnParam.SetItemData(m_CbnParam.AddString(pPlugin->GetFormattedParamName(m_nCurrentParam)), m_nCurrentParam); } m_CbnParam.SetCurSel(0); m_CbnParam.SetRedraw(TRUE); OnParamChanged(); // Input / Output type int in = pPlugin->GetNumInputChannels(), out = pPlugin->GetNumOutputChannels(); if (in < 1) s = _T("No input"); else if (in == 1) s = _T("Mono-In"); else s = _T("Stereo-In"); s += _T(", "); if (out < 1) s += _T("No output"); else if (out == 1) s += _T("Mono-Out"); else s += _T("Stereo-Out"); // For now, only display the "current" preset. // This prevents the program from hanging when switching between plugin slots or // switching to the general tab and the first plugin in the list has a lot of presets. // Some plugins like Synth1 have so many presets that this *does* indeed make a difference, // even on fairly modern CPUs. The rest of the presets are just added when the combo box // gets the focus, i.e. just when they're needed. int32 currentProg = pPlugin->GetCurrentProgram(); FillPluginProgramBox(currentProg, currentProg); m_CbnPreset.SetCurSel(0); m_sbValue.EnableWindow(TRUE); m_sbDryRatio.EnableWindow(TRUE); GetDlgItem(IDC_EDIT14)->EnableWindow(TRUE); } else { s.Empty(); if (m_CbnParam.GetCount() > 0) m_CbnParam.ResetContent(); m_nCurrentParam = 0; m_CbnPreset.SetRedraw(FALSE); m_CbnPreset.ResetContent(); m_CbnPreset.SetItemData(m_CbnPreset.AddString(_T("none")), 0); m_CbnPreset.SetRedraw(TRUE); m_CbnPreset.SetCurSel(0); m_sbValue.EnableWindow(FALSE); m_sbDryRatio.EnableWindow(FALSE); GetDlgItem(IDC_EDIT14)->EnableWindow(FALSE); } SetDlgItemText(IDC_TEXT6, s); } if(updateWholePluginView || plugHint.GetPlugin() > m_nCurrentPlugin) { int insertAt = 1; m_CbnOutput.SetRedraw(FALSE); if(updateWholePluginView) { m_CbnOutput.ResetContent(); m_CbnOutput.SetItemData(m_CbnOutput.AddString(_T("Default")), 0); for(PLUGINDEX i = m_nCurrentPlugin + 1; i < MAX_MIXPLUGINS; i++) { if(!sndFile.m_MixPlugins[i].IsValidPlugin()) { m_CbnOutput.SetItemData(m_CbnOutput.AddString(_T("New Plugin...")), 1); insertAt = 2; break; } } } else { const DWORD_PTR changedPlugin = 0x80 + (plugHint.GetPlugin() - 1); const int items = m_CbnOutput.GetCount(); for(insertAt = 1; insertAt < items; insertAt++) { DWORD_PTR thisPlugin = m_CbnOutput.GetItemData(insertAt); if(thisPlugin == changedPlugin) m_CbnOutput.DeleteString(insertAt); if(thisPlugin >= changedPlugin) break; } } int outputSel = plugin.IsOutputToMaster() ? 0 : -1; for(PLUGINDEX iOut = m_nCurrentPlugin + 1; iOut < MAX_MIXPLUGINS; iOut++) { if(!updateWholePluginView && (iOut + 1) != plugHint.GetPlugin()) continue; const SNDMIXPLUGIN &outPlug = sndFile.m_MixPlugins[iOut]; if(outPlug.IsValidPlugin()) { const auto name = outPlug.GetName(), libName = outPlug.GetLibraryName(); s.Format(_T("FX%d: "), iOut + 1); s += mpt::ToCString(name.empty() ? libName : name); if(!name.empty() && libName != name) { s += _T(" ("); s += mpt::ToCString(libName); s += _T(")"); } insertAt = m_CbnOutput.InsertString(insertAt, s); m_CbnOutput.SetItemData(insertAt, 0x80 + iOut); if(!plugin.IsOutputToMaster() && (plugin.GetOutputPlugin() == iOut)) { outputSel = insertAt; } insertAt++; } } m_CbnOutput.SetRedraw(TRUE); if(outputSel >= 0) m_CbnOutput.SetCurSel(outputSel); } if(plugHint.GetType()[HINT_PLUGINPARAM] && updatePlug) { OnParamChanged(); } m_CbnPlugin.Invalidate(FALSE); m_CbnParam.Invalidate(FALSE); m_CbnPreset.Invalidate(FALSE); m_CbnSpecialMixProcessing.Invalidate(FALSE); m_CbnOutput.Invalidate(FALSE); } void CViewGlobals::PopulateChannelPlugins(PLUGINDEX plugin) { // Channel effect lists const CSoundFile &sndFile = GetDocument()->GetSoundFile(); CString s; for(CHANNELINDEX ichn = 0; ichn < CHANNELS_IN_TAB; ichn++) { const CHANNELINDEX nChn = m_nActiveTab * CHANNELS_IN_TAB + ichn; auto &comboBox = m_CbnEffects[ichn]; if(nChn < MAX_BASECHANNELS) { comboBox.SetRedraw(FALSE); int insertAt = 1; if(plugin == PLUGINDEX_INVALID) { comboBox.ResetContent(); comboBox.SetItemData(comboBox.AddString(_T("No plugin")), 0); } else { const int items = comboBox.GetCount(); for(insertAt = 1; insertAt < items; insertAt++) { auto thisPlugin = static_cast(comboBox.GetItemData(insertAt)); if(thisPlugin == (plugin + 1)) comboBox.DeleteString(insertAt); if(thisPlugin >= (plugin + 1)) break; } } int fxsel = 0; for(PLUGINDEX ifx = 0; ifx < MAX_MIXPLUGINS; ifx++) { if(plugin != PLUGINDEX_INVALID && ifx != plugin) continue; if(sndFile.m_MixPlugins[ifx].IsValidPlugin() || (sndFile.m_MixPlugins[ifx].GetName() != U_("")) || (sndFile.ChnSettings[nChn].nMixPlugin == ifx + 1)) { s = MPT_CFORMAT("FX{}: ")(ifx + 1); s += mpt::ToCString(sndFile.m_MixPlugins[ifx].GetName()); insertAt = comboBox.InsertString(insertAt, s); comboBox.SetItemData(insertAt, ifx + 1); if(sndFile.ChnSettings[nChn].nMixPlugin == ifx + 1) fxsel = insertAt; insertAt++; } } comboBox.SetRedraw(TRUE); if(plugin == PLUGINDEX_INVALID || fxsel > 0) comboBox.SetCurSel(fxsel); comboBox.Invalidate(FALSE); } } } IMixPlugin *CViewGlobals::GetCurrentPlugin() const { if(GetDocument() == nullptr || m_nCurrentPlugin >= MAX_MIXPLUGINS) { return nullptr; } return GetDocument()->GetSoundFile().m_MixPlugins[m_nCurrentPlugin].pMixPlugin; } void CViewGlobals::OnTabSelchange(NMHDR*, LRESULT* pResult) { UpdateView(GeneralHint().Channels()); if (pResult) *pResult = 0; } void CViewGlobals::OnUpdateUndo(CCmdUI *pCmdUI) { CModDoc *pModDoc = GetDocument(); if((pCmdUI) && (pModDoc)) { pCmdUI->Enable(pModDoc->GetPatternUndo().CanUndoChannelSettings()); pCmdUI->SetText(CMainFrame::GetInputHandler()->GetKeyTextFromCommand(kcEditUndo, _T("Undo ") + pModDoc->GetPatternUndo().GetUndoName())); } } void CViewGlobals::OnUpdateRedo(CCmdUI *pCmdUI) { CModDoc *pModDoc = GetDocument(); if((pCmdUI) && (pModDoc)) { pCmdUI->Enable(pModDoc->GetPatternUndo().CanRedoChannelSettings()); pCmdUI->SetText(CMainFrame::GetInputHandler()->GetKeyTextFromCommand(kcEditRedo, _T("Redo ") + pModDoc->GetPatternUndo().GetRedoName())); } } void CViewGlobals::OnEditUndo() { UndoRedo(true); } void CViewGlobals::OnEditRedo() { UndoRedo(false); } void CViewGlobals::UndoRedo(bool undo) { CModDoc *pModDoc = GetDocument(); if(!pModDoc) return; if(undo && pModDoc->GetPatternUndo().CanUndoChannelSettings()) pModDoc->GetPatternUndo().Undo(); else if(!undo && pModDoc->GetPatternUndo().CanRedoChannelSettings()) pModDoc->GetPatternUndo().Redo(); } void CViewGlobals::PrepareUndo(CHANNELINDEX chnMod4) { if(m_lastEdit != chnMod4) { // Backup old channel settings through pattern undo. m_lastEdit = chnMod4; const CHANNELINDEX chn = static_cast(m_nActiveTab * CHANNELS_IN_TAB) + chnMod4; GetDocument()->GetPatternUndo().PrepareChannelUndo(chn, 1, "Channel Settings"); } } void CViewGlobals::OnEditColor(const CHANNELINDEX chnMod4) { auto *modDoc = GetDocument(); auto &sndFile = modDoc->GetSoundFile(); const CHANNELINDEX chn = static_cast(m_nActiveTab * CHANNELS_IN_TAB) + chnMod4; if(auto color = m_channelColor[chnMod4].PickColor(sndFile, chn); color.has_value()) { PrepareUndo(chnMod4); sndFile.ChnSettings[chn].color = *color; if(modDoc->SupportsChannelColors()) modDoc->SetModified(); modDoc->UpdateAllViews(nullptr, GeneralHint(chn).Channels()); } } void CViewGlobals::OnEditColor1() { OnEditColor(0); } void CViewGlobals::OnEditColor2() { OnEditColor(1); } void CViewGlobals::OnEditColor3() { OnEditColor(2); } void CViewGlobals::OnEditColor4() { OnEditColor(3); } void CViewGlobals::OnMute(const CHANNELINDEX chnMod4, const UINT itemID) { CModDoc *pModDoc = GetDocument(); if (pModDoc) { const bool b = (IsDlgButtonChecked(itemID) != FALSE); const CHANNELINDEX nChn = (CHANNELINDEX)(m_nActiveTab * CHANNELS_IN_TAB) + chnMod4; pModDoc->MuteChannel(nChn, b); pModDoc->UpdateAllViews(this, GeneralHint(nChn).Channels()); } } void CViewGlobals::OnMute1() {OnMute(0, IDC_CHECK1);} void CViewGlobals::OnMute2() {OnMute(1, IDC_CHECK3);} void CViewGlobals::OnMute3() {OnMute(2, IDC_CHECK5);} void CViewGlobals::OnMute4() {OnMute(3, IDC_CHECK7);} void CViewGlobals::OnSurround(const CHANNELINDEX chnMod4, const UINT itemID) { CModDoc *pModDoc = GetDocument(); if (pModDoc) { PrepareUndo(chnMod4); const bool b = (IsDlgButtonChecked(itemID) != FALSE); const CHANNELINDEX nChn = (CHANNELINDEX)(m_nActiveTab * CHANNELS_IN_TAB) + chnMod4; pModDoc->SurroundChannel(nChn, b); pModDoc->UpdateAllViews(nullptr, GeneralHint(nChn).Channels()); } } void CViewGlobals::OnSurround1() {OnSurround(0, IDC_CHECK2);} void CViewGlobals::OnSurround2() {OnSurround(1, IDC_CHECK4);} void CViewGlobals::OnSurround3() {OnSurround(2, IDC_CHECK6);} void CViewGlobals::OnSurround4() {OnSurround(3, IDC_CHECK8);} void CViewGlobals::OnEditVol(const CHANNELINDEX chnMod4, const UINT itemID) { CModDoc *pModDoc = GetDocument(); const CHANNELINDEX nChn = (CHANNELINDEX)(m_nActiveTab * CHANNELS_IN_TAB) + chnMod4; const int vol = GetDlgItemIntEx(itemID); if ((pModDoc) && (vol >= 0) && (vol <= 64) && (!m_nLockCount)) { PrepareUndo(chnMod4); if (pModDoc->SetChannelGlobalVolume(nChn, static_cast(vol))) { m_sbVolume[chnMod4].SetPos(vol); pModDoc->UpdateAllViews(this, GeneralHint(nChn).Channels()); } } } void CViewGlobals::OnEditVol1() {OnEditVol(0, IDC_EDIT1);} void CViewGlobals::OnEditVol2() {OnEditVol(1, IDC_EDIT3);} void CViewGlobals::OnEditVol3() {OnEditVol(2, IDC_EDIT5);} void CViewGlobals::OnEditVol4() {OnEditVol(3, IDC_EDIT7);} void CViewGlobals::OnEditPan(const CHANNELINDEX chnMod4, const UINT itemID) { CModDoc *pModDoc = GetDocument(); const CHANNELINDEX nChn = (CHANNELINDEX)(m_nActiveTab * CHANNELS_IN_TAB) + chnMod4; const int pan = GetDlgItemIntEx(itemID); if ((pModDoc) && (pan >= 0) && (pan <= 256) && (!m_nLockCount)) { PrepareUndo(chnMod4); if (pModDoc->SetChannelDefaultPan(nChn, static_cast(pan))) { m_sbPan[chnMod4].SetPos(pan / 4); pModDoc->UpdateAllViews(this, GeneralHint(nChn).Channels()); // Surround is forced off when changing pan, so uncheck the checkbox. CheckDlgButton(IDC_CHECK2 + chnMod4 * 2, BST_UNCHECKED); } } } void CViewGlobals::OnEditPan1() {OnEditPan(0, IDC_EDIT2);} void CViewGlobals::OnEditPan2() {OnEditPan(1, IDC_EDIT4);} void CViewGlobals::OnEditPan3() {OnEditPan(2, IDC_EDIT6);} void CViewGlobals::OnEditPan4() {OnEditPan(3, IDC_EDIT8);} void CViewGlobals::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar) { CModDoc *pModDoc; CHANNELINDEX nChn; CFormView::OnHScroll(nSBCode, nPos, pScrollBar); pModDoc = GetDocument(); nChn = (CHANNELINDEX)(m_nActiveTab * CHANNELS_IN_TAB); if ((pModDoc) && (!IsLocked()) && (nChn < MAX_BASECHANNELS)) { BOOL bUpdate = FALSE; short int pos; LockControls(); const CHANNELINDEX nLoopLimit = std::min(static_cast(CHANNELS_IN_TAB), static_cast(pModDoc->GetSoundFile().GetNumChannels() - nChn)); for (CHANNELINDEX iCh = 0; iCh < nLoopLimit; iCh++) { if(pScrollBar == (CScrollBar *) &m_sbVolume[iCh]) { // Volume sliders pos = (short int)m_sbVolume[iCh].GetPos(); if ((pos >= 0) && (pos <= 64)) { PrepareUndo(iCh); if (pModDoc->SetChannelGlobalVolume(nChn + iCh, pos)) { SetDlgItemInt(IDC_EDIT1 + iCh * 2, pos); bUpdate = TRUE; } } } else if(pScrollBar == (CScrollBar *) &m_sbPan[iCh]) { // Pan sliders pos = (short int)m_sbPan[iCh].GetPos(); if(pos >= 0 && pos <= 64 && (static_cast(pos) != pModDoc->GetSoundFile().ChnSettings[nChn+iCh].nPan / 4u)) { PrepareUndo(iCh); if (pModDoc->SetChannelDefaultPan(nChn + iCh, pos * 4)) { SetDlgItemInt(IDC_EDIT2 + iCh * 2, pos * 4); CheckDlgButton(IDC_CHECK2 + iCh * 2, BST_UNCHECKED); bUpdate = TRUE; } } } } if ((pScrollBar) && (pScrollBar->m_hWnd == m_sbDryRatio.m_hWnd)) { int n = 100 - m_sbDryRatio.GetPos(); if ((n >= 0) && (n <= 100) && (m_nCurrentPlugin < MAX_MIXPLUGINS)) { SNDMIXPLUGIN &plugin = pModDoc->GetSoundFile().m_MixPlugins[m_nCurrentPlugin]; if(plugin.pMixPlugin) { plugin.fDryRatio = static_cast(n) / 100.0f; SetPluginModified(); } UpdateDryWetDisplay(); } } if (bUpdate) pModDoc->UpdateAllViews(this, GeneralHint(nChn).Channels()); UnlockControls(); if ((pScrollBar) && (pScrollBar->m_hWnd == m_sbValue.m_hWnd)) { int n = (short int)m_sbValue.GetPos(); if ((n >= 0) && (n <= 100) && (m_nCurrentPlugin < MAX_MIXPLUGINS)) { IMixPlugin *pPlugin = GetCurrentPlugin(); if(pPlugin != nullptr) { const PlugParamIndex nParams = pPlugin->GetNumParameters(); if(m_nCurrentParam < nParams) { if (nSBCode == SB_THUMBPOSITION || nSBCode == SB_THUMBTRACK || nSBCode == SB_ENDSCROLL) { pPlugin->SetScaledUIParam(m_nCurrentParam, 0.01f * n); OnParamChanged(); SetPluginModified(); } } } } } } } void CViewGlobals::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar) { CModDoc *pModDoc = GetDocument(); if((m_nCurrentPlugin >= MAX_MIXPLUGINS) || (!pModDoc)) return; CSoundFile &sndFile = pModDoc->GetSoundFile(); TCHAR s[32]; if(nSBCode != SB_ENDSCROLL && pScrollBar && pScrollBar == (CScrollBar*)&m_SpinMixGain) { SNDMIXPLUGIN &plugin = sndFile.m_MixPlugins[m_nCurrentPlugin]; if(plugin.pMixPlugin) { uint8 gain = (uint8)nPos; if(gain == 0) gain = 1; plugin.SetGain(gain); float fValue = 0.1f * (float)gain; _stprintf(s, _T("Gain: x%1.1f"), fValue); CEdit *gainEdit = (CEdit *)GetDlgItem(IDC_EDIT16); gainEdit->SetWindowText(s); SetPluginModified(); } } CFormView::OnVScroll(nSBCode, nPos, pScrollBar); } void CViewGlobals::OnEditName(const CHANNELINDEX chnMod4, const UINT itemID) { CModDoc *pModDoc = GetDocument(); if ((pModDoc) && (!m_nLockCount)) { CSoundFile &sndFile = pModDoc->GetSoundFile(); const CHANNELINDEX nChn = m_nActiveTab * CHANNELS_IN_TAB + chnMod4; CString tmp; GetDlgItemText(itemID, tmp); const std::string s = mpt::ToCharset(sndFile.GetCharsetInternal(), tmp); if ((sndFile.GetType() & (MOD_TYPE_XM|MOD_TYPE_IT|MOD_TYPE_MPT)) && (nChn < sndFile.GetNumChannels()) && (s != sndFile.ChnSettings[nChn].szName)) { PrepareUndo(chnMod4); sndFile.ChnSettings[nChn].szName = s; pModDoc->SetModified(); pModDoc->UpdateAllViews(this, GeneralHint(nChn).Channels()); } } } void CViewGlobals::OnEditName1() {OnEditName(0, IDC_EDIT9);} void CViewGlobals::OnEditName2() {OnEditName(1, IDC_EDIT10);} void CViewGlobals::OnEditName3() {OnEditName(2, IDC_EDIT11);} void CViewGlobals::OnEditName4() {OnEditName(3, IDC_EDIT12);} void CViewGlobals::OnFxChanged(const CHANNELINDEX chnMod4) { CModDoc *pModDoc = GetDocument(); if (pModDoc) { CSoundFile &sndFile = pModDoc->GetSoundFile(); CHANNELINDEX nChn = m_nActiveTab * CHANNELS_IN_TAB + chnMod4; int nfx = static_cast(m_CbnEffects[chnMod4].GetItemData(m_CbnEffects[chnMod4].GetCurSel())); if ((nfx >= 0) && (nfx <= MAX_MIXPLUGINS) && (nChn < sndFile.GetNumChannels()) && (sndFile.ChnSettings[nChn].nMixPlugin != (UINT)nfx)) { PrepareUndo(chnMod4); sndFile.ChnSettings[nChn].nMixPlugin = (PLUGINDEX)nfx; if(sndFile.GetModSpecifications().supportsPlugins) pModDoc->SetModified(); pModDoc->UpdateAllViews(this, GeneralHint(nChn).Channels()); } } } void CViewGlobals::OnFx1Changed() {OnFxChanged(0);} void CViewGlobals::OnFx2Changed() {OnFxChanged(1);} void CViewGlobals::OnFx3Changed() {OnFxChanged(2);} void CViewGlobals::OnFx4Changed() {OnFxChanged(3);} void CViewGlobals::OnPluginNameChanged() { CModDoc *pModDoc = GetDocument(); if ((pModDoc) && (m_nCurrentPlugin < MAX_MIXPLUGINS)) { CSoundFile &sndFile = pModDoc->GetSoundFile(); SNDMIXPLUGIN &plugin = sndFile.m_MixPlugins[m_nCurrentPlugin]; CString s; GetDlgItemText(IDC_EDIT13, s); if (s != mpt::ToCString(plugin.GetName())) { plugin.Info.szName = mpt::ToCharset(mpt::Charset::Locale, s); if(sndFile.GetModSpecifications().supportsPlugins) pModDoc->SetModified(); pModDoc->UpdateAllViews(this, PluginHint(m_nCurrentPlugin + 1).Info().Names(), this); IMixPlugin *pPlugin = plugin.pMixPlugin; if(pPlugin != nullptr && pPlugin->GetEditor() != nullptr) { pPlugin->GetEditor()->SetTitle(); } // Update channel plugin assignments PopulateChannelPlugins(m_nCurrentPlugin); m_CbnPlugin.SetRedraw(FALSE); AddPluginNamesToCombobox(m_CbnPlugin, sndFile.m_MixPlugins, true, m_nCurrentPlugin); m_CbnPlugin.SetCurSel(m_nCurrentPlugin); m_CbnPlugin.Invalidate(FALSE); m_CbnPlugin.SetRedraw(TRUE); } } } void CViewGlobals::OnPrevPlugin() { CModDoc *pModDoc = GetDocument(); if ((m_nCurrentPlugin > 0) && (pModDoc)) { m_nCurrentPlugin--; UpdateView(PluginHint(m_nCurrentPlugin + 1).Info()); } } void CViewGlobals::OnNextPlugin() { CModDoc *pModDoc = GetDocument(); if ((m_nCurrentPlugin < MAX_MIXPLUGINS-1) && (pModDoc)) { m_nCurrentPlugin++; UpdateView(PluginHint(m_nCurrentPlugin + 1).Info()); } } void CViewGlobals::OnPluginChanged() { CModDoc *pModDoc = GetDocument(); int nPlugin = m_CbnPlugin.GetCurSel(); if ((pModDoc) && (nPlugin >= 0) && (nPlugin < MAX_MIXPLUGINS)) { m_nCurrentPlugin = (PLUGINDEX)nPlugin; UpdateView(PluginHint(m_nCurrentPlugin + 1).Info()); } m_CbnPreset.SetCurSel(0); } void CViewGlobals::OnSelectPlugin() { #ifndef NO_PLUGINS CModDoc *pModDoc = GetDocument(); if ((pModDoc) && (m_nCurrentPlugin < MAX_MIXPLUGINS)) { CSelectPluginDlg dlg(pModDoc, m_nCurrentPlugin, this); if (dlg.DoModal() == IDOK) { if(pModDoc->GetSoundFile().GetModSpecifications().supportsPlugins) pModDoc->SetModified(); } OnPluginChanged(); OnParamChanged(); } #endif // NO_PLUGINS } void CViewGlobals::OnRemovePlugin() { #ifndef NO_PLUGINS CModDoc *pModDoc = GetDocument(); if(pModDoc && m_nCurrentPlugin < MAX_MIXPLUGINS && Reporting::Confirm(MPT_UFORMAT("Remove plugin FX{}: {}?")(m_nCurrentPlugin + 1, pModDoc->GetSoundFile().m_MixPlugins[m_nCurrentPlugin].GetName()), false, true) == cnfYes) { if(pModDoc->RemovePlugin(m_nCurrentPlugin)) { OnPluginChanged(); OnParamChanged(); } } #endif // NO_PLUGINS } LRESULT CViewGlobals::OnParamAutomated(WPARAM plugin, LPARAM param) { if(plugin == m_nCurrentPlugin && static_cast(param) == m_nCurrentParam) { OnParamChanged(); } return 0; } LRESULT CViewGlobals::OnDryWetRatioChangedFromPlayer(WPARAM plugin, LPARAM) { if(plugin == m_nCurrentPlugin) { UpdateDryWetDisplay(); } return 0; } void CViewGlobals::OnParamChanged() { PlugParamIndex cursel = static_cast(m_CbnParam.GetItemData(m_CbnParam.GetCurSel())); IMixPlugin *pPlugin = GetCurrentPlugin(); if(pPlugin != nullptr && cursel != static_cast(CB_ERR)) { const PlugParamIndex nParams = pPlugin->GetNumParameters(); if(cursel < nParams) m_nCurrentParam = cursel; if(m_nCurrentParam < nParams) { const auto value = pPlugin->GetScaledUIParam(m_nCurrentParam); int intValue = mpt::saturate_round(value * 100.0f); LockControls(); if(GetFocus() != GetDlgItem(IDC_EDIT14)) { CString s = pPlugin->GetFormattedParamValue(m_nCurrentParam).Trim(); if(s.IsEmpty()) { s.Format(_T("%f"), value); } SetDlgItemText(IDC_EDIT14, s); } m_sbValue.SetPos(intValue); UnlockControls(); return; } } SetDlgItemText(IDC_EDIT14, _T("")); m_sbValue.SetPos(0); } // When focussing the parameter value, show its real value to edit void CViewGlobals::OnFocusParam() { IMixPlugin *pPlugin = GetCurrentPlugin(); if(pPlugin != nullptr) { const PlugParamIndex nParams = pPlugin->GetNumParameters(); if(m_nCurrentParam < nParams) { TCHAR s[32]; float fValue = pPlugin->GetScaledUIParam(m_nCurrentParam); _stprintf(s, _T("%f"), fValue); LockControls(); SetDlgItemText(IDC_EDIT14, s); UnlockControls(); } } } void CViewGlobals::SetPluginModified() { CModDoc *pModDoc = GetDocument(); if(pModDoc->GetSoundFile().GetModSpecifications().supportsPlugins) pModDoc->SetModified(); pModDoc->UpdateAllViews(this, PluginHint(m_nCurrentPlugin + 1).Info()); } void CViewGlobals::OnProgramChanged() { int32 curProg = static_cast(m_CbnPreset.GetItemData(m_CbnPreset.GetCurSel())); IMixPlugin *pPlugin = GetCurrentPlugin(); if(pPlugin != nullptr) { const int32 numProgs = pPlugin->GetNumPrograms(); if(curProg <= numProgs) { pPlugin->SetCurrentProgram(curProg); // Update parameter display OnParamChanged(); SetPluginModified(); } } } void CViewGlobals::OnLoadParam() { IMixPlugin *pPlugin = GetCurrentPlugin(); if(pPlugin != nullptr && pPlugin->LoadProgram()) { int32 currentProg = pPlugin->GetCurrentProgram(); FillPluginProgramBox(currentProg, currentProg); m_CbnPreset.SetCurSel(0); SetPluginModified(); } } void CViewGlobals::OnSaveParam() { IMixPlugin *pPlugin = GetCurrentPlugin(); if(pPlugin != nullptr) { pPlugin->SaveProgram(); } } void CViewGlobals::OnSetParameter() { if(m_nCurrentPlugin >= MAX_MIXPLUGINS || IsLocked()) return; IMixPlugin *pPlugin = GetCurrentPlugin(); if(pPlugin != nullptr) { const PlugParamIndex nParams = pPlugin->GetNumParameters(); TCHAR s[32]; GetDlgItemText(IDC_EDIT14, s, mpt::saturate_cast(std::size(s))); if ((m_nCurrentParam < nParams) && (s[0])) { float fValue = (float)_tstof(s); pPlugin->SetScaledUIParam(m_nCurrentParam, fValue); OnParamChanged(); SetPluginModified(); } } } void CViewGlobals::UpdateDryWetDisplay() { SNDMIXPLUGIN &plugin = GetDocument()->GetSoundFile().m_MixPlugins[m_nCurrentPlugin]; float wetRatio = 1.0f - plugin.fDryRatio, dryRatio = plugin.fDryRatio; m_sbDryRatio.SetPos(mpt::saturate_round(wetRatio * 100)); if(plugin.IsExpandedMix()) { wetRatio = 2.0f * wetRatio - 1.0f; dryRatio = -wetRatio; } int wetInt = mpt::saturate_round(wetRatio * 100), dryInt = mpt::saturate_round(dryRatio * 100); SetDlgItemText(IDC_STATIC8, MPT_TFORMAT("{}% wet, {}% dry")(wetInt, dryInt).c_str()); } void CViewGlobals::OnMixModeChanged() { CModDoc *pModDoc = GetDocument(); if ((m_nCurrentPlugin >= MAX_MIXPLUGINS) || (!pModDoc)) return; pModDoc->GetSoundFile().m_MixPlugins[m_nCurrentPlugin].SetMasterEffect(IsDlgButtonChecked(IDC_CHECK9) != BST_UNCHECKED); SetPluginModified(); } void CViewGlobals::OnBypassChanged() { CModDoc *pModDoc = GetDocument(); if ((m_nCurrentPlugin >= MAX_MIXPLUGINS) || (!pModDoc)) return; pModDoc->GetSoundFile().m_MixPlugins[m_nCurrentPlugin].SetBypass(IsDlgButtonChecked(IDC_CHECK10) != BST_UNCHECKED); SetPluginModified(); } void CViewGlobals::OnWetDryExpandChanged() { CModDoc *pModDoc = GetDocument(); if ((m_nCurrentPlugin >= MAX_MIXPLUGINS) || (!pModDoc)) return; pModDoc->GetSoundFile().m_MixPlugins[m_nCurrentPlugin].SetExpandedMix(IsDlgButtonChecked(IDC_CHECK12) != BST_UNCHECKED); UpdateDryWetDisplay(); SetPluginModified(); } void CViewGlobals::OnSpecialMixProcessingChanged() { CModDoc *pModDoc = GetDocument(); if ((m_nCurrentPlugin >= MAX_MIXPLUGINS) || (!pModDoc)) return; pModDoc->GetSoundFile().m_MixPlugins[m_nCurrentPlugin].SetMixMode((uint8)m_CbnSpecialMixProcessing.GetCurSel()); SetPluginModified(); } void CViewGlobals::OnDryMixChanged() { CModDoc *pModDoc = GetDocument(); if ((m_nCurrentPlugin >= MAX_MIXPLUGINS) || (!pModDoc)) return; pModDoc->GetSoundFile().m_MixPlugins[m_nCurrentPlugin].SetWetMix(IsDlgButtonChecked(IDC_CHECK11) != BST_UNCHECKED); SetPluginModified(); } void CViewGlobals::OnEditPlugin() { CModDoc *pModDoc = GetDocument(); if ((m_nCurrentPlugin >= MAX_MIXPLUGINS) || (!pModDoc)) return; pModDoc->TogglePluginEditor(m_nCurrentPlugin, CMainFrame::GetInputHandler()->ShiftPressed()); return; } void CViewGlobals::OnOutputRoutingChanged() { CModDoc *pModDoc = GetDocument(); int nroute; if ((m_nCurrentPlugin >= MAX_MIXPLUGINS) || (!pModDoc)) return; CSoundFile &sndFile = pModDoc->GetSoundFile(); SNDMIXPLUGIN &plugin = sndFile.m_MixPlugins[m_nCurrentPlugin]; nroute = static_cast(m_CbnOutput.GetItemData(m_CbnOutput.GetCurSel())); if(nroute == 1) { // Add new plugin for(PLUGINDEX i = m_nCurrentPlugin + 1; i < MAX_MIXPLUGINS; i++) { if(!sndFile.m_MixPlugins[i].IsValidPlugin()) { CSelectPluginDlg dlg(pModDoc, i, this); if(dlg.DoModal() != IDOK) return; plugin.SetOutputPlugin(i); SetPluginModified(); nroute = 0x80 + i; m_nCurrentPlugin = i; m_CbnPlugin.SetCurSel(i); OnPluginChanged(); break; } } if(nroute == 1) { Reporting::Error("All following plugin slots are used.", "Unable to add new plugin"); return; } } if(!nroute) plugin.SetOutputToMaster(); else plugin.SetOutputPlugin(static_cast(nroute - 0x80)); SetPluginModified(); } LRESULT CViewGlobals::OnModViewMsg(WPARAM wParam, LPARAM /*lParam*/) { switch(wParam) { case VIEWMSG_SETFOCUS: case VIEWMSG_SETACTIVE: GetParentFrame()->SetActiveView(this); SetFocus(); return 0; default: return 0; } } void CViewGlobals::OnMovePlugToSlot() { if(GetCurrentPlugin() == nullptr) { return; } // If any plugin routes its output to the current plugin, we shouldn't try to move it before that plugin... PLUGINDEX defaultIndex = 0; CSoundFile &sndFile = GetDocument()->GetSoundFile(); for(PLUGINDEX i = 0; i < m_nCurrentPlugin; i++) { if(sndFile.m_MixPlugins[i].GetOutputPlugin() == m_nCurrentPlugin) { defaultIndex = i + 1; } } std::vector emptySlots; BuildEmptySlotList(emptySlots); CMoveFXSlotDialog dlg(this, m_nCurrentPlugin, emptySlots, defaultIndex, false, !sndFile.m_MixPlugins[m_nCurrentPlugin].IsOutputToMaster()); if(dlg.DoModal() == IDOK) { size_t toIndex = dlg.GetSlotIndex(); do { const SNDMIXPLUGIN &curPlugin = sndFile.m_MixPlugins[m_nCurrentPlugin]; SNDMIXPLUGIN &newPlugin = sndFile.m_MixPlugins[emptySlots[toIndex]]; const PLUGINDEX nextPlugin = curPlugin.GetOutputPlugin(); MovePlug(m_nCurrentPlugin, emptySlots[toIndex]); if(nextPlugin == PLUGINDEX_INVALID || toIndex == emptySlots.size() - 1) { break; } m_nCurrentPlugin = nextPlugin; if(dlg.DoMoveChain()) { toIndex++; newPlugin.SetOutputPlugin(emptySlots[toIndex]); } } while(dlg.DoMoveChain()); m_CbnPlugin.SetCurSel(dlg.GetSlot()); OnPluginChanged(); GetDocument()->UpdateAllViews(nullptr, PluginHint().Names().Info()); } } // Functor for adjusting plug indexes in modcommands. Adjusts all instrument column values in // range [m_nInstrMin, m_nInstrMax] by m_nDiff. struct PlugIndexModifier { PlugIndexModifier(PLUGINDEX nMin, PLUGINDEX nMax, int nDiff) : m_nDiff(nDiff), m_nInstrMin(nMin), m_nInstrMax(nMax) {} void operator()(ModCommand& m) { if (m.IsInstrPlug() && m.instr >= m_nInstrMin && m.instr <= m_nInstrMax) m.instr = (ModCommand::INSTR)((int)m.instr + m_nDiff); } int m_nDiff; ModCommand::INSTR m_nInstrMin; ModCommand::INSTR m_nInstrMax; }; bool CViewGlobals::MovePlug(PLUGINDEX src, PLUGINDEX dest, bool bAdjustPat) { if (src == dest) return false; CModDoc *pModDoc = GetDocument(); CSoundFile &sndFile = pModDoc->GetSoundFile(); BeginWaitCursor(); CriticalSection cs; // Move plug data sndFile.m_MixPlugins[dest] = std::move(sndFile.m_MixPlugins[src]); sndFile.m_MixPlugins[src] = SNDMIXPLUGIN(); // Prevent plug from pointing backwards. if(!sndFile.m_MixPlugins[dest].IsOutputToMaster()) { PLUGINDEX nOutput = sndFile.m_MixPlugins[dest].GetOutputPlugin(); if (nOutput <= dest && nOutput != PLUGINDEX_INVALID) { sndFile.m_MixPlugins[dest].SetOutputToMaster(); } } // Update current plug IMixPlugin *pPlugin = sndFile.m_MixPlugins[dest].pMixPlugin; if(pPlugin != nullptr) { pPlugin->SetSlot(dest); if(pPlugin->GetEditor() != nullptr) { pPlugin->GetEditor()->SetTitle(); } } // Update all other plugs' outputs for (PLUGINDEX nPlug = 0; nPlug < src; nPlug++) { if(!sndFile.m_MixPlugins[nPlug].IsOutputToMaster()) { if(sndFile.m_MixPlugins[nPlug].GetOutputPlugin() == src) { sndFile.m_MixPlugins[nPlug].SetOutputPlugin(dest); } } } // Update channels for (CHANNELINDEX nChn = 0; nChn < sndFile.GetNumChannels(); nChn++) { if (sndFile.ChnSettings[nChn].nMixPlugin == src + 1u) { sndFile.ChnSettings[nChn].nMixPlugin = dest + 1u; } } // Update instruments for (INSTRUMENTINDEX nIns = 1; nIns <= sndFile.GetNumInstruments(); nIns++) { if (sndFile.Instruments[nIns] && (sndFile.Instruments[nIns]->nMixPlug == src + 1)) { sndFile.Instruments[nIns]->nMixPlug = dest + 1u; } } // Update MODCOMMANDs so that they won't be referring to old indexes (e.g. with NOTE_PC). if (bAdjustPat && sndFile.GetModSpecifications().HasNote(NOTE_PC)) sndFile.Patterns.ForEachModCommand(PlugIndexModifier(src + 1, src + 1, int(dest) - int(src))); cs.Leave(); SetPluginModified(); EndWaitCursor(); return true; } void CViewGlobals::BuildEmptySlotList(std::vector &emptySlots) { const CSoundFile &sndFile = GetDocument()->GetSoundFile(); emptySlots.clear(); for(PLUGINDEX nSlot = 0; nSlot < MAX_MIXPLUGINS; nSlot++) { if(sndFile.m_MixPlugins[nSlot].pMixPlugin == nullptr) { emptySlots.push_back(nSlot); } } return; } void CViewGlobals::OnInsertSlot() { CString prompt; CSoundFile &sndFile = GetDocument()->GetSoundFile(); prompt.Format(_T("Insert empty slot before slot FX%d?"), m_nCurrentPlugin + 1); // If last plugin slot is occupied, move it so that the plugin is not lost. // This could certainly be improved... bool moveLastPlug = false; if(sndFile.m_MixPlugins[MAX_MIXPLUGINS - 1].pMixPlugin) { if(sndFile.m_MixPlugins[MAX_MIXPLUGINS - 2].pMixPlugin == nullptr) { moveLastPlug = true; } else { prompt += _T("\nWarning: plugin data in last slot will be lost."); } } if(Reporting::Confirm(prompt) == cnfYes) { // Delete last plug... if(sndFile.m_MixPlugins[MAX_MIXPLUGINS - 1].pMixPlugin) { if(moveLastPlug) { MovePlug(MAX_MIXPLUGINS - 1, MAX_MIXPLUGINS - 2, true); } else { sndFile.m_MixPlugins[MAX_MIXPLUGINS - 1].Destroy(); MemsetZero(sndFile.m_MixPlugins[MAX_MIXPLUGINS - 1].Info); } } // Update MODCOMMANDs so that they won't be referring to old indexes (e.g. with NOTE_PC). if(sndFile.GetModSpecifications().HasNote(NOTE_PC)) sndFile.Patterns.ForEachModCommand(PlugIndexModifier(m_nCurrentPlugin + 1, MAX_MIXPLUGINS - 1, 1)); for(PLUGINDEX nSlot = MAX_MIXPLUGINS - 1; nSlot > m_nCurrentPlugin; nSlot--) { if(sndFile.m_MixPlugins[nSlot-1].pMixPlugin) { MovePlug(nSlot - 1, nSlot, NoPatternAdjust); } } m_CbnPlugin.SetCurSel(m_nCurrentPlugin + 1); OnPluginChanged(); GetDocument()->UpdateAllViews(nullptr, PluginHint().Names().Info()); SetPluginModified(); } } void CViewGlobals::OnClonePlug() { if(GetCurrentPlugin() == nullptr) { return; } CSoundFile &sndFile = GetDocument()->GetSoundFile(); std::vector emptySlots; BuildEmptySlotList(emptySlots); CMoveFXSlotDialog dlg(this, m_nCurrentPlugin, emptySlots, 0, true, !sndFile.m_MixPlugins[m_nCurrentPlugin].IsOutputToMaster()); if(dlg.DoModal() == IDOK) { size_t toIndex = dlg.GetSlotIndex(); do { const SNDMIXPLUGIN &curPlugin = sndFile.m_MixPlugins[m_nCurrentPlugin]; SNDMIXPLUGIN &newPlugin = sndFile.m_MixPlugins[emptySlots[toIndex]]; GetDocument()->ClonePlugin(newPlugin, curPlugin); IMixPlugin *mixPlug = newPlugin.pMixPlugin; if(mixPlug != nullptr && mixPlug->IsInstrument() && GetDocument()->HasInstrumentForPlugin(emptySlots[toIndex]) == INSTRUMENTINDEX_INVALID) { GetDocument()->InsertInstrumentForPlugin(emptySlots[toIndex]); } if(curPlugin.IsOutputToMaster() || toIndex == emptySlots.size() - 1) { break; } m_nCurrentPlugin = curPlugin.GetOutputPlugin(); if(dlg.DoMoveChain()) { toIndex++; newPlugin.SetOutputPlugin(emptySlots[toIndex]); } } while(dlg.DoMoveChain()); m_CbnPlugin.SetCurSel(dlg.GetSlot()); OnPluginChanged(); PopulateChannelPlugins(); GetDocument()->UpdateAllViews(this, PluginHint().Names(), this); SetPluginModified(); } } // The plugin param box is only filled when it gets the focus (done here). void CViewGlobals::OnFillParamCombo() { // no need to fill it again. if(m_CbnParam.GetCount() > 1) return; IMixPlugin *pPlugin = GetCurrentPlugin(); if(pPlugin == nullptr) return; const PlugParamIndex nParams = pPlugin->GetNumParameters(); m_CbnParam.SetRedraw(FALSE); m_CbnParam.ResetContent(); AddPluginParameternamesToCombobox(m_CbnParam, *pPlugin); if (m_nCurrentParam >= nParams) m_nCurrentParam = 0; m_CbnParam.SetCurSel(m_nCurrentParam); m_CbnParam.SetRedraw(TRUE); m_CbnParam.Invalidate(FALSE); } // The preset box is only filled when it gets the focus (done here). void CViewGlobals::OnFillProgramCombo() { // no need to fill it again. if(m_CbnPreset.GetCount() > 1) return; IMixPlugin *pPlugin = GetCurrentPlugin(); if(pPlugin == nullptr) return; FillPluginProgramBox(0, pPlugin->GetNumPrograms() - 1); m_CbnPreset.SetCurSel(pPlugin->GetCurrentProgram()); } void CViewGlobals::FillPluginProgramBox(int32 firstProg, int32 lastProg) { IMixPlugin *pPlugin = GetCurrentPlugin(); m_CbnPreset.SetRedraw(FALSE); m_CbnPreset.ResetContent(); pPlugin->CacheProgramNames(firstProg, lastProg + 1); for (int32 i = firstProg; i <= lastProg; i++) { m_CbnPreset.SetItemData(m_CbnPreset.AddString(pPlugin->GetFormattedProgramName(i)), i); } m_CbnPreset.SetRedraw(TRUE); m_CbnPreset.Invalidate(FALSE); } BOOL CViewGlobals::OnToolTipText(UINT, NMHDR *pNMHDR, LRESULT *pResult) { auto pTTT = reinterpret_cast(pNMHDR); UINT_PTR id = pNMHDR->idFrom; if(pTTT->uFlags & TTF_IDISHWND) { // idFrom is actually the HWND of the tool id = static_cast(::GetDlgCtrlID(reinterpret_cast(id))); } mpt::tstring text; const auto &chnSettings = GetDocument()->GetSoundFile().ChnSettings; switch(id) { case IDC_EDIT1: case IDC_EDIT3: case IDC_EDIT5: case IDC_EDIT7: text = CModDoc::LinearToDecibels(chnSettings[m_nActiveTab * CHANNELS_IN_TAB + (id - IDC_EDIT1) / 2].nVolume, 64.0); break; case IDC_SLIDER1: case IDC_SLIDER3: case IDC_SLIDER5: case IDC_SLIDER7: text = CModDoc::LinearToDecibels(chnSettings[m_nActiveTab * CHANNELS_IN_TAB + (id - IDC_SLIDER1) / 2].nVolume, 64.0); break; case IDC_EDIT2: case IDC_EDIT4: case IDC_EDIT6: case IDC_EDIT8: text = CModDoc::PanningToString(chnSettings[m_nActiveTab * CHANNELS_IN_TAB + (id - IDC_EDIT2) / 2].nPan, 128); break; case IDC_SLIDER2: case IDC_SLIDER4: case IDC_SLIDER6: case IDC_SLIDER8: text = CModDoc::PanningToString(chnSettings[m_nActiveTab * CHANNELS_IN_TAB + (id - IDC_SLIDER2) / 2].nPan, 128); break; case IDC_EDIT16: { const auto gain = GetDocument()->GetSoundFile().m_MixPlugins[m_nCurrentPlugin].GetGain(); text = CModDoc::LinearToDecibels(gain ? gain : 10, 10.0); } break; case IDC_BUTTON5: text = _T("Previous Plugin"); break; case IDC_BUTTON4: text = _T("Next Plugin"); break; default: return FALSE; } mpt::String::WriteWinBuf(pTTT->szText) = text; *pResult = 0; // bring the tooltip window above other popup windows ::SetWindowPos(pNMHDR->hwndFrom, HWND_TOP, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOMOVE | SWP_NOOWNERZORDER); return TRUE; // message was handled } OPENMPT_NAMESPACE_END