123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065 |
- /*
- * PlugInterface.cpp
- * -----------------
- * Purpose: Default plugin interface implementation
- * 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 "../Sndfile.h"
- #include "PlugInterface.h"
- #include "PluginManager.h"
- #include "../../common/FileReader.h"
- #ifdef MODPLUG_TRACKER
- #include "../../mptrack/Moddoc.h"
- #include "../../mptrack/Mainfrm.h"
- #include "../../mptrack/InputHandler.h"
- #include "../../mptrack/AbstractVstEditor.h"
- #include "../../mptrack/DefaultVstEditor.h"
- // LoadProgram/SaveProgram
- #include "../../mptrack/FileDialog.h"
- #include "../../mptrack/VstPresets.h"
- #include "../../common/mptFileIO.h"
- #include "../mod_specifications.h"
- #endif // MODPLUG_TRACKER
- #include "mpt/base/aligned_array.hpp"
- #include "mpt/io/base.hpp"
- #include "mpt/io/io.hpp"
- #include "mpt/io/io_span.hpp"
- #include <cmath>
- #ifndef NO_PLUGINS
- OPENMPT_NAMESPACE_BEGIN
- #ifdef MODPLUG_TRACKER
- CModDoc *IMixPlugin::GetModDoc() { return m_SndFile.GetpModDoc(); }
- const CModDoc *IMixPlugin::GetModDoc() const { return m_SndFile.GetpModDoc(); }
- #endif // MODPLUG_TRACKER
- IMixPlugin::IMixPlugin(VSTPluginLib &factory, CSoundFile &sndFile, SNDMIXPLUGIN *mixStruct)
- : m_Factory(factory)
- , m_SndFile(sndFile)
- , m_pMixStruct(mixStruct)
- {
- m_SndFile.m_loadedPlugins++;
- m_MixState.pMixBuffer = mpt::align_bytes<8, MIXBUFFERSIZE * 2>(m_MixBuffer);
- while(m_pMixStruct != &(m_SndFile.m_MixPlugins[m_nSlot]) && m_nSlot < MAX_MIXPLUGINS - 1)
- {
- m_nSlot++;
- }
- }
- IMixPlugin::~IMixPlugin()
- {
- #ifdef MODPLUG_TRACKER
- CloseEditor();
- CriticalSection cs;
- #endif // MODPLUG_TRACKER
- // First thing to do, if we don't want to hang in a loop
- if (m_Factory.pPluginsList == this) m_Factory.pPluginsList = m_pNext;
- if (m_pMixStruct)
- {
- m_pMixStruct->pMixPlugin = nullptr;
- m_pMixStruct = nullptr;
- }
- if (m_pNext) m_pNext->m_pPrev = m_pPrev;
- if (m_pPrev) m_pPrev->m_pNext = m_pNext;
- m_pPrev = nullptr;
- m_pNext = nullptr;
- m_SndFile.m_loadedPlugins--;
- }
- void IMixPlugin::InsertIntoFactoryList()
- {
- m_pMixStruct->pMixPlugin = this;
- m_pNext = m_Factory.pPluginsList;
- if(m_Factory.pPluginsList)
- {
- m_Factory.pPluginsList->m_pPrev = this;
- }
- m_Factory.pPluginsList = this;
- }
- #ifdef MODPLUG_TRACKER
- void IMixPlugin::SetSlot(PLUGINDEX slot)
- {
- m_nSlot = slot;
- m_pMixStruct = &m_SndFile.m_MixPlugins[slot];
- }
- PlugParamValue IMixPlugin::GetScaledUIParam(PlugParamIndex param)
- {
- const auto [paramMin, paramMax] = GetParamUIRange(param);
- return (std::clamp(GetParameter(param), paramMin, paramMax) - paramMin) / (paramMax - paramMin);
- }
- void IMixPlugin::SetScaledUIParam(PlugParamIndex param, PlugParamValue value)
- {
- const auto [paramMin, paramMax] = GetParamUIRange(param);
- const auto scaledVal = paramMin + std::clamp(value, 0.0f, 1.0f) * (paramMax - paramMin);
- SetParameter(param, scaledVal);
- }
- CString IMixPlugin::GetFormattedParamName(PlugParamIndex param)
- {
- CString paramName = GetParamName(param);
- CString name;
- if(paramName.IsEmpty())
- {
- name = MPT_CFORMAT("{}: Parameter {}")(mpt::cfmt::dec0<2>(param), mpt::cfmt::dec0<2>(param));
- } else
- {
- name = MPT_CFORMAT("{}: {}")(mpt::cfmt::dec0<2>(param), paramName);
- }
- return name;
- }
- // Get a parameter's current value, represented by the plugin.
- CString IMixPlugin::GetFormattedParamValue(PlugParamIndex param)
- {
- CString paramDisplay = GetParamDisplay(param);
- CString paramUnits = GetParamLabel(param);
- paramDisplay.Trim();
- paramUnits.Trim();
- paramDisplay += _T(" ") + paramUnits;
- return paramDisplay;
- }
- CString IMixPlugin::GetFormattedProgramName(int32 index)
- {
- CString rawname = GetProgramName(index);
-
- // Let's start counting at 1 for the program name (as most MIDI hardware / software does)
- index++;
- CString formattedName;
- if(rawname[0] >= 0 && rawname[0] < _T(' '))
- formattedName = MPT_CFORMAT("{} - Program {}")(mpt::cfmt::dec0<2>(index), index);
- else
- formattedName = MPT_CFORMAT("{} - {}")(mpt::cfmt::dec0<2>(index), rawname);
- return formattedName;
- }
- void IMixPlugin::SetEditorPos(int32 x, int32 y)
- {
- m_pMixStruct->editorX = x;
- m_pMixStruct->editorY = y;
- }
- void IMixPlugin::GetEditorPos(int32 &x, int32 &y) const
- {
- x = m_pMixStruct->editorX;
- y = m_pMixStruct->editorY;
- }
- #endif // MODPLUG_TRACKER
- bool IMixPlugin::IsBypassed() const
- {
- return m_pMixStruct != nullptr && m_pMixStruct->IsBypassed();
- }
- void IMixPlugin::RecalculateGain()
- {
- float gain = 0.1f * static_cast<float>(m_pMixStruct ? m_pMixStruct->GetGain() : 10);
- if(gain < 0.1f) gain = 1.0f;
- if(IsInstrument())
- {
- gain /= m_SndFile.GetPlayConfig().getVSTiAttenuation();
- gain = static_cast<float>(gain * (m_SndFile.m_nVSTiVolume / m_SndFile.GetPlayConfig().getNormalVSTiVol()));
- }
- m_fGain = gain;
- }
- void IMixPlugin::SetDryRatio(float dryRatio)
- {
- m_pMixStruct->fDryRatio = std::clamp(dryRatio, 0.0f, 1.0f);
- #ifdef MODPLUG_TRACKER
- m_SndFile.m_pluginDryWetRatioChanged.set(m_nSlot);
- #endif // MODPLUG_TRACKER
- }
- void IMixPlugin::Bypass(bool bypass)
- {
- m_pMixStruct->Info.SetBypass(bypass);
- #ifdef MODPLUG_TRACKER
- if(m_SndFile.GetpModDoc())
- m_SndFile.GetpModDoc()->UpdateAllViews(nullptr, PluginHint(m_nSlot + 1).Info(), nullptr);
- #endif // MODPLUG_TRACKER
- }
- double IMixPlugin::GetOutputLatency() const
- {
- if(GetSoundFile().IsRenderingToDisc())
- return 0;
- else
- return GetSoundFile().m_TimingInfo.OutputLatency;
- }
- void IMixPlugin::ProcessMixOps(float * MPT_RESTRICT pOutL, float * MPT_RESTRICT pOutR, float * MPT_RESTRICT leftPlugOutput, float * MPT_RESTRICT rightPlugOutput, uint32 numFrames)
- {
- /* float *leftPlugOutput;
- float *rightPlugOutput;
- if(m_Effect.numOutputs == 1)
- {
- // If there was just the one plugin output we copy it into our 2 outputs
- leftPlugOutput = rightPlugOutput = mixBuffer.GetOutputBuffer(0);
- } else if(m_Effect.numOutputs > 1)
- {
- // Otherwise we actually only cater for two outputs max (outputs > 2 have been mixed together already).
- leftPlugOutput = mixBuffer.GetOutputBuffer(0);
- rightPlugOutput = mixBuffer.GetOutputBuffer(1);
- } else
- {
- return;
- }*/
- // -> mixop == 0 : normal processing
- // -> mixop == 1 : MIX += DRY - WET * wetRatio
- // -> mixop == 2 : MIX += WET - DRY * dryRatio
- // -> mixop == 3 : MIX -= WET - DRY * wetRatio
- // -> mixop == 4 : MIX -= middle - WET * wetRatio + middle - DRY
- // -> mixop == 5 : MIX_L += wetRatio * (WET_L - DRY_L) + dryRatio * (DRY_R - WET_R)
- // MIX_R += dryRatio * (WET_L - DRY_L) + wetRatio * (DRY_R - WET_R)
- MPT_ASSERT(m_pMixStruct != nullptr);
- int mixop;
- if(IsInstrument())
- {
- // Force normal mix mode for instruments
- mixop = 0;
- } else
- {
- mixop = m_pMixStruct->GetMixMode();
- }
- float wetRatio = 1 - m_pMixStruct->fDryRatio;
- float dryRatio = IsInstrument() ? 1 : m_pMixStruct->fDryRatio; // Always mix full dry if this is an instrument
- // Wet / Dry range expansion [0,1] -> [-1,1]
- if(GetNumInputChannels() > 0 && m_pMixStruct->IsExpandedMix())
- {
- wetRatio = 2.0f * wetRatio - 1.0f;
- dryRatio = -wetRatio;
- }
- wetRatio *= m_fGain;
- dryRatio *= m_fGain;
- float * MPT_RESTRICT plugInputL = m_mixBuffer.GetInputBuffer(0);
- float * MPT_RESTRICT plugInputR = m_mixBuffer.GetInputBuffer(1);
- // Mix operation
- switch(mixop)
- {
- // Default mix
- case 0:
- for(uint32 i = 0; i < numFrames; i++)
- {
- //rewbs.wetratio - added the factors. [20040123]
- pOutL[i] += leftPlugOutput[i] * wetRatio + plugInputL[i] * dryRatio;
- pOutR[i] += rightPlugOutput[i] * wetRatio + plugInputR[i] * dryRatio;
- }
- break;
- // Wet subtract
- case 1:
- for(uint32 i = 0; i < numFrames; i++)
- {
- pOutL[i] += plugInputL[i] - leftPlugOutput[i] * wetRatio;
- pOutR[i] += plugInputR[i] - rightPlugOutput[i] * wetRatio;
- }
- break;
- // Dry subtract
- case 2:
- for(uint32 i = 0; i < numFrames; i++)
- {
- pOutL[i] += leftPlugOutput[i] - plugInputL[i] * dryRatio;
- pOutR[i] += rightPlugOutput[i] - plugInputR[i] * dryRatio;
- }
- break;
- // Mix subtract
- case 3:
- for(uint32 i = 0; i < numFrames; i++)
- {
- pOutL[i] -= leftPlugOutput[i] - plugInputL[i] * wetRatio;
- pOutR[i] -= rightPlugOutput[i] - plugInputR[i] * wetRatio;
- }
- break;
- // Middle subtract
- case 4:
- for(uint32 i = 0; i < numFrames; i++)
- {
- float middle = (pOutL[i] + plugInputL[i] + pOutR[i] + plugInputR[i]) / 2.0f;
- pOutL[i] -= middle - leftPlugOutput[i] * wetRatio + middle - plugInputL[i];
- pOutR[i] -= middle - rightPlugOutput[i] * wetRatio + middle - plugInputR[i];
- }
- break;
- // Left / Right balance
- case 5:
- if(m_pMixStruct->IsExpandedMix())
- {
- wetRatio /= 2.0f;
- dryRatio /= 2.0f;
- }
- for(uint32 i = 0; i < numFrames; i++)
- {
- pOutL[i] += wetRatio * (leftPlugOutput[i] - plugInputL[i]) + dryRatio * (plugInputR[i] - rightPlugOutput[i]);
- pOutR[i] += dryRatio * (leftPlugOutput[i] - plugInputL[i]) + wetRatio * (plugInputR[i] - rightPlugOutput[i]);
- }
- break;
- }
- // If dry mix is ticked, we add the unprocessed buffer,
- // except if this is an instrument since then it has already been done:
- if(m_pMixStruct->IsWetMix() && !IsInstrument())
- {
- for(uint32 i = 0; i < numFrames; i++)
- {
- pOutL[i] += plugInputL[i];
- pOutR[i] += plugInputR[i];
- }
- }
- }
- // Render some silence and return maximum level returned by the plugin.
- float IMixPlugin::RenderSilence(uint32 numFrames)
- {
- // The JUCE framework doesn't like processing while being suspended.
- const bool wasSuspended = !IsResumed();
- if(wasSuspended)
- {
- Resume();
- }
- float out[2][MIXBUFFERSIZE]; // scratch buffers
- float maxVal = 0.0f;
- m_mixBuffer.ClearInputBuffers(MIXBUFFERSIZE);
- while(numFrames > 0)
- {
- uint32 renderSamples = numFrames;
- LimitMax(renderSamples, mpt::saturate_cast<uint32>(std::size(out[0])));
- MemsetZero(out);
- Process(out[0], out[1], renderSamples);
- for(size_t i = 0; i < renderSamples; i++)
- {
- maxVal = std::max(maxVal, std::fabs(out[0][i]));
- maxVal = std::max(maxVal, std::fabs(out[1][i]));
- }
- numFrames -= renderSamples;
- }
- if(wasSuspended)
- {
- Suspend();
- }
- return maxVal;
- }
- // Get list of plugins to which output is sent. A nullptr indicates master output.
- size_t IMixPlugin::GetOutputPlugList(std::vector<IMixPlugin *> &list)
- {
- // At the moment we know there will only be 1 output.
- // Returning nullptr means plugin outputs directly to master.
- list.clear();
- IMixPlugin *outputPlug = nullptr;
- if(!m_pMixStruct->IsOutputToMaster())
- {
- PLUGINDEX nOutput = m_pMixStruct->GetOutputPlugin();
- if(nOutput > m_nSlot && nOutput != PLUGINDEX_INVALID)
- {
- outputPlug = m_SndFile.m_MixPlugins[nOutput].pMixPlugin;
- }
- }
- list.push_back(outputPlug);
- return 1;
- }
- // Get a list of plugins that send data to this plugin.
- size_t IMixPlugin::GetInputPlugList(std::vector<IMixPlugin *> &list)
- {
- std::vector<IMixPlugin *> candidatePlugOutputs;
- list.clear();
- for(PLUGINDEX plug = 0; plug < MAX_MIXPLUGINS; plug++)
- {
- IMixPlugin *candidatePlug = m_SndFile.m_MixPlugins[plug].pMixPlugin;
- if(candidatePlug)
- {
- candidatePlug->GetOutputPlugList(candidatePlugOutputs);
- for(auto &outPlug : candidatePlugOutputs)
- {
- if(outPlug == this)
- {
- list.push_back(candidatePlug);
- break;
- }
- }
- }
- }
- return list.size();
- }
- // Get a list of instruments that send data to this plugin.
- size_t IMixPlugin::GetInputInstrumentList(std::vector<INSTRUMENTINDEX> &list)
- {
- list.clear();
- const PLUGINDEX nThisMixPlug = m_nSlot + 1; //m_nSlot is position in mixplug array.
- for(INSTRUMENTINDEX ins = 0; ins <= m_SndFile.GetNumInstruments(); ins++)
- {
- if(m_SndFile.Instruments[ins] != nullptr && m_SndFile.Instruments[ins]->nMixPlug == nThisMixPlug)
- {
- list.push_back(ins);
- }
- }
- return list.size();
- }
- size_t IMixPlugin::GetInputChannelList(std::vector<CHANNELINDEX> &list)
- {
- list.clear();
- PLUGINDEX nThisMixPlug = m_nSlot + 1; //m_nSlot is position in mixplug array.
- const CHANNELINDEX chnCount = m_SndFile.GetNumChannels();
- for(CHANNELINDEX nChn=0; nChn<chnCount; nChn++)
- {
- if(m_SndFile.ChnSettings[nChn].nMixPlugin == nThisMixPlug)
- {
- list.push_back(nChn);
- }
- }
- return list.size();
- }
- void IMixPlugin::SaveAllParameters()
- {
- if (m_pMixStruct == nullptr)
- {
- return;
- }
- m_pMixStruct->defaultProgram = -1;
-
- // Default implementation: Save all parameter values
- PlugParamIndex numParams = std::min(GetNumParameters(), static_cast<PlugParamIndex>((std::numeric_limits<uint32>::max() - sizeof(uint32)) / sizeof(IEEE754binary32LE)));
- uint32 nLen = numParams * sizeof(IEEE754binary32LE);
- if (!nLen) return;
- nLen += sizeof(uint32);
- try
- {
- m_pMixStruct->pluginData.resize(nLen);
- auto memFile = std::make_pair(mpt::as_span(m_pMixStruct->pluginData), mpt::IO::Offset(0));
- mpt::IO::WriteIntLE<uint32>(memFile, 0); // Plugin data type
- BeginGetProgram();
- for(PlugParamIndex i = 0; i < numParams; i++)
- {
- mpt::IO::Write(memFile, IEEE754binary32LE(GetParameter(i)));
- }
- EndGetProgram();
- } catch(mpt::out_of_memory e)
- {
- m_pMixStruct->pluginData.clear();
- mpt::delete_out_of_memory(e);
- }
- }
- void IMixPlugin::RestoreAllParameters(int32 /*program*/)
- {
- if(m_pMixStruct != nullptr && m_pMixStruct->pluginData.size() >= sizeof(uint32))
- {
- FileReader memFile(mpt::as_span(m_pMixStruct->pluginData));
- uint32 type = memFile.ReadUint32LE();
- if(type == 0)
- {
- const uint32 numParams = GetNumParameters();
- if((m_pMixStruct->pluginData.size() - sizeof(uint32)) >= (numParams * sizeof(IEEE754binary32LE)))
- {
- BeginSetProgram();
- for(uint32 i = 0; i < numParams; i++)
- {
- const auto value = memFile.ReadFloatLE();
- SetParameter(i, std::isfinite(value) ? value : 0.0f);
- }
- EndSetProgram();
- }
- }
- }
- }
- #ifdef MODPLUG_TRACKER
- void IMixPlugin::ToggleEditor()
- {
- // We only really need this mutex for bridged plugins, as we may be processing window messages (in the same thread) while the editor opens.
- // The user could press the toggle button while the editor is loading and thus close the editor while still being initialized.
- // Note that this does not protect against closing the module while the editor is still loading.
- static bool initializing = false;
- if(initializing)
- return;
- initializing = true;
- if (m_pEditor)
- {
- CloseEditor();
- } else
- {
- m_pEditor = OpenEditor();
- if (m_pEditor)
- m_pEditor->OpenEditor(CMainFrame::GetMainFrame());
- }
- initializing = false;
- }
- // Provide default plugin editor
- CAbstractVstEditor *IMixPlugin::OpenEditor()
- {
- try
- {
- return new CDefaultVstEditor(*this);
- } catch(mpt::out_of_memory e)
- {
- mpt::delete_out_of_memory(e);
- return nullptr;
- }
- }
- void IMixPlugin::CloseEditor()
- {
- if(m_pEditor)
- {
- if (m_pEditor->m_hWnd) m_pEditor->DoClose();
- delete m_pEditor;
- m_pEditor = nullptr;
- }
- }
- // Automate a parameter from the plugin GUI (both custom and default plugin GUI)
- void IMixPlugin::AutomateParameter(PlugParamIndex param)
- {
- CModDoc *modDoc = GetModDoc();
- if(modDoc == nullptr)
- {
- return;
- }
- // TODO: Check if any params are actually automatable, and if there are but this one isn't, chicken out
- if(m_recordAutomation)
- {
- // Record parameter change
- modDoc->RecordParamChange(GetSlot(), param);
- }
- modDoc->SendNotifyMessageToAllViews(WM_MOD_PLUGPARAMAUTOMATE, m_nSlot, param);
- if(auto *vstEditor = GetEditor(); vstEditor && vstEditor->m_hWnd)
- {
- // Mark track modified if GUI is open and format supports plugins
- SetModified();
- // Do not use InputHandler in case we are coming from a bridged plugin editor
- if((GetAsyncKeyState(VK_SHIFT) & 0x8000) && TrackerSettings::Instance().midiMappingInPluginEditor)
- {
- // Shift pressed -> Open MIDI mapping dialog
- CMainFrame::GetMainFrame()->PostMessage(WM_MOD_MIDIMAPPING, m_nSlot, param);
- }
- // Learn macro
- int macroToLearn = vstEditor->GetLearnMacro();
- if (macroToLearn > -1)
- {
- modDoc->LearnMacro(macroToLearn, param);
- vstEditor->SetLearnMacro(-1);
- }
- }
- }
- void IMixPlugin::SetModified()
- {
- CModDoc *modDoc = GetModDoc();
- if(modDoc != nullptr && m_SndFile.GetModSpecifications().supportsPlugins)
- {
- modDoc->SetModified();
- }
- }
- bool IMixPlugin::SaveProgram()
- {
- mpt::PathString defaultDir = TrackerSettings::Instance().PathPluginPresets.GetWorkingDir();
- const bool useDefaultDir = !defaultDir.empty();
- if(!useDefaultDir && m_Factory.dllPath.IsFile())
- {
- defaultDir = m_Factory.dllPath.GetPath();
- }
- CString progName = m_Factory.libraryName.ToCString() + _T(" - ") + GetCurrentProgramName();
- SanitizeFilename(progName);
- FileDialog dlg = SaveFileDialog()
- .DefaultExtension("fxb")
- .DefaultFilename(progName)
- .ExtensionFilter("VST Plugin Programs (*.fxp)|*.fxp|"
- "VST Plugin Banks (*.fxb)|*.fxb||")
- .WorkingDirectory(defaultDir);
- if(!dlg.Show(m_pEditor)) return false;
- if(useDefaultDir)
- {
- TrackerSettings::Instance().PathPluginPresets.SetWorkingDir(dlg.GetWorkingDirectory());
- }
- const bool isBank = (dlg.GetExtension() == P_("fxb"));
- try
- {
- mpt::SafeOutputFile sf(dlg.GetFirstFile(), std::ios::binary, mpt::FlushModeFromBool(TrackerSettings::Instance().MiscFlushFileBuffersOnSave));
- mpt::ofstream &f = sf;
- f.exceptions(f.exceptions() | std::ios::badbit | std::ios::failbit);
- if(f.good() && VSTPresets::SaveFile(f, *this, isBank))
- return true;
- } catch(const std::exception &)
- {
-
- }
- Reporting::Error("Error saving preset.", m_pEditor);
- return false;
- }
- bool IMixPlugin::LoadProgram(mpt::PathString fileName)
- {
- mpt::PathString defaultDir = TrackerSettings::Instance().PathPluginPresets.GetWorkingDir();
- bool useDefaultDir = !defaultDir.empty();
- if(!useDefaultDir && m_Factory.dllPath.IsFile())
- {
- defaultDir = m_Factory.dllPath.GetPath();
- }
- if(fileName.empty())
- {
- FileDialog dlg = OpenFileDialog()
- .DefaultExtension("fxp")
- .ExtensionFilter("VST Plugin Programs and Banks (*.fxp,*.fxb)|*.fxp;*.fxb|"
- "VST Plugin Programs (*.fxp)|*.fxp|"
- "VST Plugin Banks (*.fxb)|*.fxb|"
- "All Files|*.*||")
- .WorkingDirectory(defaultDir);
- if(!dlg.Show(m_pEditor)) return false;
- if(useDefaultDir)
- {
- TrackerSettings::Instance().PathPluginPresets.SetWorkingDir(dlg.GetWorkingDirectory());
- }
- fileName = dlg.GetFirstFile();
- }
- const char *errorStr = nullptr;
- InputFile f(fileName, SettingCacheCompleteFileBeforeLoading());
- if(f.IsValid())
- {
- FileReader file = GetFileReader(f);
- errorStr = VSTPresets::GetErrorMessage(VSTPresets::LoadFile(file, *this));
- } else
- {
- errorStr = "Can't open file.";
- }
- if(errorStr == nullptr)
- {
- if(GetModDoc() != nullptr && GetSoundFile().GetModSpecifications().supportsPlugins)
- {
- GetModDoc()->SetModified();
- }
- return true;
- } else
- {
- Reporting::Error(errorStr, m_pEditor);
- return false;
- }
- }
- #endif // MODPLUG_TRACKER
- ////////////////////////////////////////////////////////////////////
- // IMidiPlugin: Default implementation of plugins with MIDI input //
- ////////////////////////////////////////////////////////////////////
- IMidiPlugin::IMidiPlugin(VSTPluginLib &factory, CSoundFile &sndFile, SNDMIXPLUGIN *mixStruct)
- : IMixPlugin(factory, sndFile, mixStruct)
- , m_MidiCh{{}}
- {
- for(auto &chn : m_MidiCh)
- {
- chn.midiPitchBendPos = EncodePitchBendParam(MIDIEvents::pitchBendCentre); // centre pitch bend on all channels
- chn.ResetProgram();
- }
- }
- void IMidiPlugin::ApplyPitchWheelDepth(int32 &value, int8 pwd)
- {
- if(pwd != 0)
- {
- value = (value * ((MIDIEvents::pitchBendMax - MIDIEvents::pitchBendCentre + 1) / 64)) / pwd;
- } else
- {
- value = 0;
- }
- }
- // Get the MIDI channel currently associated with a given tracker channel
- uint8 IMidiPlugin::GetMidiChannel(const ModChannel &chn, CHANNELINDEX trackChannel) const
- {
- if(auto ins = chn.pModInstrument; ins != nullptr)
- return ins->GetMIDIChannel(chn, trackChannel);
- else
- return 0;
- }
- uint8 IMidiPlugin::GetMidiChannel(CHANNELINDEX trackChannel) const
- {
- if(trackChannel < std::size(m_SndFile.m_PlayState.Chn))
- return GetMidiChannel(m_SndFile.m_PlayState.Chn[trackChannel], trackChannel);
- else
- return 0;
- }
- void IMidiPlugin::MidiCC(MIDIEvents::MidiCC nController, uint8 nParam, CHANNELINDEX trackChannel)
- {
- //Error checking
- LimitMax(nController, MIDIEvents::MIDICC_end);
- LimitMax(nParam, uint8(127));
- auto midiCh = GetMidiChannel(trackChannel);
- if(m_SndFile.m_playBehaviour[kMIDICCBugEmulation])
- MidiSend(MIDIEvents::Event(MIDIEvents::evControllerChange, midiCh, nParam, static_cast<uint8>(nController))); // param and controller are swapped (old broken implementation)
- else
- MidiSend(MIDIEvents::CC(nController, midiCh, nParam));
- }
- // Set MIDI pitch for given MIDI channel to the specified raw 14-bit position
- void IMidiPlugin::MidiPitchBendRaw(int32 pitchbend, CHANNELINDEX trackerChn)
- {
- SendMidiPitchBend(GetMidiChannel(trackerChn), EncodePitchBendParam(Clamp(pitchbend, MIDIEvents::pitchBendMin, MIDIEvents::pitchBendMax)));
- }
- // Bend MIDI pitch for given MIDI channel using fine tracker param (one unit = 1/64th of a note step)
- void IMidiPlugin::MidiPitchBend(int32 increment, int8 pwd, CHANNELINDEX trackerChn)
- {
- auto midiCh = GetMidiChannel(trackerChn);
- if(m_SndFile.m_playBehaviour[kOldMIDIPitchBends])
- {
- // OpenMPT Legacy: Old pitch slides never were really accurate, but setting the PWD to 13 in plugins would give the closest results.
- increment = (increment * 0x800 * 13) / (0xFF * pwd);
- increment = EncodePitchBendParam(increment);
- } else
- {
- increment = EncodePitchBendParam(increment);
- ApplyPitchWheelDepth(increment, pwd);
- }
- int32 newPitchBendPos = (increment + m_MidiCh[midiCh].midiPitchBendPos) & kPitchBendMask;
- Limit(newPitchBendPos, EncodePitchBendParam(MIDIEvents::pitchBendMin), EncodePitchBendParam(MIDIEvents::pitchBendMax));
- SendMidiPitchBend(midiCh, newPitchBendPos);
- }
- // Set MIDI pitch for given MIDI channel using fixed point pitch bend value (converted back to 0-16383 MIDI range)
- void IMidiPlugin::SendMidiPitchBend(uint8 midiCh, int32 newPitchBendPos)
- {
- MPT_ASSERT(EncodePitchBendParam(MIDIEvents::pitchBendMin) <= newPitchBendPos && newPitchBendPos <= EncodePitchBendParam(MIDIEvents::pitchBendMax));
- m_MidiCh[midiCh].midiPitchBendPos = newPitchBendPos;
- MidiSend(MIDIEvents::PitchBend(midiCh, DecodePitchBendParam(newPitchBendPos)));
- }
- // Apply vibrato effect through pitch wheel commands on a given MIDI channel.
- void IMidiPlugin::MidiVibrato(int32 depth, int8 pwd, CHANNELINDEX trackerChn)
- {
- auto midiCh = GetMidiChannel(trackerChn);
- depth = EncodePitchBendParam(depth);
- if(depth != 0 || (m_MidiCh[midiCh].midiPitchBendPos & kVibratoFlag))
- {
- ApplyPitchWheelDepth(depth, pwd);
- // Temporarily add vibrato offset to current pitch
- int32 newPitchBendPos = (depth + m_MidiCh[midiCh].midiPitchBendPos) & kPitchBendMask;
- Limit(newPitchBendPos, EncodePitchBendParam(MIDIEvents::pitchBendMin), EncodePitchBendParam(MIDIEvents::pitchBendMax));
- MidiSend(MIDIEvents::PitchBend(midiCh, DecodePitchBendParam(newPitchBendPos)));
- }
- // Update vibrato status
- if(depth != 0)
- m_MidiCh[midiCh].midiPitchBendPos |= kVibratoFlag;
- else
- m_MidiCh[midiCh].midiPitchBendPos &= ~kVibratoFlag;
- }
- void IMidiPlugin::MidiCommand(const ModInstrument &instr, uint16 note, uint16 vol, CHANNELINDEX trackChannel)
- {
- if(trackChannel >= MAX_CHANNELS)
- return;
- auto midiCh = GetMidiChannel(trackChannel);
- PlugInstrChannel &channel = m_MidiCh[midiCh];
- uint16 midiBank = instr.wMidiBank - 1;
- uint8 midiProg = instr.nMidiProgram - 1;
- bool bankChanged = (channel.currentBank != midiBank) && (midiBank < 0x4000);
- bool progChanged = (channel.currentProgram != midiProg) && (midiProg < 0x80);
- //get vol in [0,128[
- uint8 volume = static_cast<uint8>(std::min((vol + 1u) / 2u, 127u));
- // Bank change
- if(bankChanged)
- {
- uint8 high = static_cast<uint8>(midiBank >> 7);
- uint8 low = static_cast<uint8>(midiBank & 0x7F);
- //m_SndFile.ProcessMIDIMacro(trackChannel, false, m_SndFile.m_MidiCfg.Global[MIDIOUT_BANKSEL], 0, m_nSlot + 1);
- MidiSend(MIDIEvents::CC(MIDIEvents::MIDICC_BankSelect_Coarse, midiCh, high));
- MidiSend(MIDIEvents::CC(MIDIEvents::MIDICC_BankSelect_Fine, midiCh, low));
- channel.currentBank = midiBank;
- }
- // Program change
- // According to the MIDI specs, a bank change alone doesn't have to change the active program - it will only change the bank of subsequent program changes.
- // Thus we send program changes also if only the bank has changed.
- if(progChanged || (midiProg < 0x80 && bankChanged))
- {
- channel.currentProgram = midiProg;
- //m_SndFile.ProcessMIDIMacro(trackChannel, false, m_SndFile.m_MidiCfg.Global[MIDIOUT_PROGRAM], 0, m_nSlot + 1);
- MidiSend(MIDIEvents::ProgramChange(midiCh, midiProg));
- }
- // Specific Note Off
- if(note > NOTE_MAX_SPECIAL)
- {
- uint8 i = static_cast<uint8>(note - NOTE_MAX_SPECIAL - NOTE_MIN);
- if(channel.noteOnMap[i][trackChannel])
- {
- channel.noteOnMap[i][trackChannel]--;
- MidiSend(MIDIEvents::NoteOff(midiCh, i, 0));
- }
- }
- // "Hard core" All Sounds Off on this midi and tracker channel
- // This one doesn't check the note mask - just one note off per note.
- // Also less likely to cause a VST event buffer overflow.
- else if(note == NOTE_NOTECUT) // ^^
- {
- MidiSend(MIDIEvents::CC(MIDIEvents::MIDICC_AllNotesOff, midiCh, 0));
- MidiSend(MIDIEvents::CC(MIDIEvents::MIDICC_AllSoundOff, midiCh, 0));
- // Turn off all notes
- for(uint8 i = 0; i < std::size(channel.noteOnMap); i++)
- {
- channel.noteOnMap[i][trackChannel] = 0;
- MidiSend(MIDIEvents::NoteOff(midiCh, i, volume));
- }
- }
- // All "active" notes off on this midi and tracker channel
- // using note mask.
- else if(note == NOTE_KEYOFF || note == NOTE_FADE) // ==, ~~
- {
- for(uint8 i = 0; i < std::size(channel.noteOnMap); i++)
- {
- // Some VSTis need a note off for each instance of a note on, e.g. fabfilter.
- while(channel.noteOnMap[i][trackChannel])
- {
- MidiSend(MIDIEvents::NoteOff(midiCh, i, volume));
- channel.noteOnMap[i][trackChannel]--;
- }
- }
- }
- // Note On
- else if(note >= NOTE_MIN && note < NOTE_MIN + mpt::array_size<decltype(channel.noteOnMap)>::size)
- {
- note -= NOTE_MIN;
- // Reset pitch bend on each new note, tracker style.
- // This is done if the pitch wheel has been moved or there was a vibrato on the previous row (in which case the "vstVibratoFlag" bit of the pitch bend memory is set)
- auto newPitchBendPos = EncodePitchBendParam(Clamp(m_SndFile.m_PlayState.Chn[trackChannel].GetMIDIPitchBend(), MIDIEvents::pitchBendMin, MIDIEvents::pitchBendMax));
- if(m_MidiCh[midiCh].midiPitchBendPos != newPitchBendPos)
- {
- SendMidiPitchBend(midiCh, newPitchBendPos);
- }
- // count instances of active notes.
- // This is to send a note off for each instance of a note, for plugs like Fabfilter.
- // Problem: if a note dies out naturally and we never send a note off, this counter
- // will block at max until note off. Is this a problem?
- // Safe to assume we won't need more than 255 note offs max on a given note?
- if(channel.noteOnMap[note][trackChannel] < uint8_max)
- {
- channel.noteOnMap[note][trackChannel]++;
- }
- MidiSend(MIDIEvents::NoteOn(midiCh, static_cast<uint8>(note), volume));
- }
- }
- bool IMidiPlugin::IsNotePlaying(uint8 note, CHANNELINDEX trackerChn)
- {
- if(!ModCommand::IsNote(note) || trackerChn >= std::size(m_MidiCh[GetMidiChannel(trackerChn)].noteOnMap[note]))
- return false;
- note -= NOTE_MIN;
- return (m_MidiCh[GetMidiChannel(trackerChn)].noteOnMap[note][trackerChn] != 0);
- }
- void IMidiPlugin::ReceiveMidi(uint32 midiCode)
- {
- ResetSilence();
- // I think we should only route events to plugins that are explicitely specified as output plugins of the current plugin.
- // This should probably use GetOutputPlugList here if we ever get to support multiple output plugins.
- PLUGINDEX receiver;
- if(m_pMixStruct != nullptr && (receiver = m_pMixStruct->GetOutputPlugin()) != PLUGINDEX_INVALID)
- {
- IMixPlugin *plugin = m_SndFile.m_MixPlugins[receiver].pMixPlugin;
- // Add all events to the plugin's queue.
- plugin->MidiSend(midiCode);
- }
- #ifdef MODPLUG_TRACKER
- if(m_recordMIDIOut)
- {
- // Spam MIDI data to all views
- ::PostMessage(CMainFrame::GetMainFrame()->GetMidiRecordWnd(), WM_MOD_MIDIMSG, midiCode, reinterpret_cast<LPARAM>(this));
- }
- #endif // MODPLUG_TRACKER
- }
- void IMidiPlugin::ReceiveSysex(mpt::const_byte_span sysex)
- {
- ResetSilence();
- // I think we should only route events to plugins that are explicitely specified as output plugins of the current plugin.
- // This should probably use GetOutputPlugList here if we ever get to support multiple output plugins.
- PLUGINDEX receiver;
- if(m_pMixStruct != nullptr && (receiver = m_pMixStruct->GetOutputPlugin()) != PLUGINDEX_INVALID)
- {
- IMixPlugin *plugin = m_SndFile.m_MixPlugins[receiver].pMixPlugin;
- // Add all events to the plugin's queue.
- plugin->MidiSysexSend(sysex);
- }
- }
- // SNDMIXPLUGIN functions
- void SNDMIXPLUGIN::SetGain(uint8 gain)
- {
- Info.gain = gain;
- if(pMixPlugin != nullptr) pMixPlugin->RecalculateGain();
- }
- void SNDMIXPLUGIN::SetBypass(bool bypass)
- {
- if(pMixPlugin != nullptr)
- pMixPlugin->Bypass(bypass);
- else
- Info.SetBypass(bypass);
- }
- void SNDMIXPLUGIN::Destroy()
- {
- if(pMixPlugin)
- {
- pMixPlugin->Release();
- pMixPlugin = nullptr;
- }
- pluginData.clear();
- pluginData.shrink_to_fit();
- }
- OPENMPT_NAMESPACE_END
- #endif // NO_PLUGINS
|