aboutsummaryrefslogtreecommitdiff
path: root/Src/external_dependencies/openmpt-trunk/soundlib/plugins
diff options
context:
space:
mode:
authorJean-Francois Mauguit <jfmauguit@mac.com>2024-09-24 09:03:25 -0400
committerGitHub <noreply@github.com>2024-09-24 09:03:25 -0400
commitbab614c421ed7ae329d26bf028c4a3b1d2450f5a (patch)
tree12f17f78986871dd2cfb0a56e5e93b545c1ae0d0 /Src/external_dependencies/openmpt-trunk/soundlib/plugins
parent4bde6044fddf053f31795b9eaccdd2a5a527d21f (diff)
parent20d28e80a5c861a9d5f449ea911ab75b4f37ad0d (diff)
downloadwinamp-bab614c421ed7ae329d26bf028c4a3b1d2450f5a.tar.gz
Merge pull request #5 from WinampDesktop/community
Merge to main
Diffstat (limited to 'Src/external_dependencies/openmpt-trunk/soundlib/plugins')
-rw-r--r--Src/external_dependencies/openmpt-trunk/soundlib/plugins/DigiBoosterEcho.cpp235
-rw-r--r--Src/external_dependencies/openmpt-trunk/soundlib/plugins/DigiBoosterEcho.h126
-rw-r--r--Src/external_dependencies/openmpt-trunk/soundlib/plugins/LFOPlugin.cpp521
-rw-r--r--Src/external_dependencies/openmpt-trunk/soundlib/plugins/LFOPlugin.h156
-rw-r--r--Src/external_dependencies/openmpt-trunk/soundlib/plugins/OpCodes.h103
-rw-r--r--Src/external_dependencies/openmpt-trunk/soundlib/plugins/PlugInterface.cpp1065
-rw-r--r--Src/external_dependencies/openmpt-trunk/soundlib/plugins/PlugInterface.h301
-rw-r--r--Src/external_dependencies/openmpt-trunk/soundlib/plugins/PluginManager.cpp816
-rw-r--r--Src/external_dependencies/openmpt-trunk/soundlib/plugins/PluginManager.h190
-rw-r--r--Src/external_dependencies/openmpt-trunk/soundlib/plugins/PluginMixBuffer.h137
-rw-r--r--Src/external_dependencies/openmpt-trunk/soundlib/plugins/PluginStructs.h141
-rw-r--r--Src/external_dependencies/openmpt-trunk/soundlib/plugins/SymMODEcho.cpp271
-rw-r--r--Src/external_dependencies/openmpt-trunk/soundlib/plugins/SymMODEcho.h131
-rw-r--r--Src/external_dependencies/openmpt-trunk/soundlib/plugins/dmo/Chorus.cpp306
-rw-r--r--Src/external_dependencies/openmpt-trunk/soundlib/plugins/dmo/Chorus.h122
-rw-r--r--Src/external_dependencies/openmpt-trunk/soundlib/plugins/dmo/Compressor.cpp238
-rw-r--r--Src/external_dependencies/openmpt-trunk/soundlib/plugins/dmo/Compressor.h109
-rw-r--r--Src/external_dependencies/openmpt-trunk/soundlib/plugins/dmo/DMOPlugin.cpp431
-rw-r--r--Src/external_dependencies/openmpt-trunk/soundlib/plugins/dmo/DMOPlugin.h100
-rw-r--r--Src/external_dependencies/openmpt-trunk/soundlib/plugins/dmo/DMOUtils.cpp59
-rw-r--r--Src/external_dependencies/openmpt-trunk/soundlib/plugins/dmo/DMOUtils.h26
-rw-r--r--Src/external_dependencies/openmpt-trunk/soundlib/plugins/dmo/Distortion.cpp216
-rw-r--r--Src/external_dependencies/openmpt-trunk/soundlib/plugins/dmo/Distortion.h97
-rw-r--r--Src/external_dependencies/openmpt-trunk/soundlib/plugins/dmo/Echo.cpp207
-rw-r--r--Src/external_dependencies/openmpt-trunk/soundlib/plugins/dmo/Echo.h99
-rw-r--r--Src/external_dependencies/openmpt-trunk/soundlib/plugins/dmo/Flanger.cpp158
-rw-r--r--Src/external_dependencies/openmpt-trunk/soundlib/plugins/dmo/Flanger.h72
-rw-r--r--Src/external_dependencies/openmpt-trunk/soundlib/plugins/dmo/Gargle.cpp202
-rw-r--r--Src/external_dependencies/openmpt-trunk/soundlib/plugins/dmo/Gargle.h90
-rw-r--r--Src/external_dependencies/openmpt-trunk/soundlib/plugins/dmo/I3DL2Reverb.cpp645
-rw-r--r--Src/external_dependencies/openmpt-trunk/soundlib/plugins/dmo/I3DL2Reverb.h169
-rw-r--r--Src/external_dependencies/openmpt-trunk/soundlib/plugins/dmo/ParamEq.cpp201
-rw-r--r--Src/external_dependencies/openmpt-trunk/soundlib/plugins/dmo/ParamEq.h98
-rw-r--r--Src/external_dependencies/openmpt-trunk/soundlib/plugins/dmo/WavesReverb.cpp261
-rw-r--r--Src/external_dependencies/openmpt-trunk/soundlib/plugins/dmo/WavesReverb.h107
35 files changed, 8206 insertions, 0 deletions
diff --git a/Src/external_dependencies/openmpt-trunk/soundlib/plugins/DigiBoosterEcho.cpp b/Src/external_dependencies/openmpt-trunk/soundlib/plugins/DigiBoosterEcho.cpp
new file mode 100644
index 00000000..2ef25522
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/soundlib/plugins/DigiBoosterEcho.cpp
@@ -0,0 +1,235 @@
+/*
+ * DigiBoosterEcho.cpp
+ * -------------------
+ * Purpose: Implementation of the DigiBooster Pro Echo DSP
+ * Notes : (currently none)
+ * Authors: OpenMPT Devs, based on original code by Grzegorz Kraszewski (BSD 2-clause)
+ * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
+ */
+
+
+#include "stdafx.h"
+
+#ifndef NO_PLUGINS
+#include "../Sndfile.h"
+#include "DigiBoosterEcho.h"
+
+OPENMPT_NAMESPACE_BEGIN
+
+IMixPlugin* DigiBoosterEcho::Create(VSTPluginLib &factory, CSoundFile &sndFile, SNDMIXPLUGIN *mixStruct)
+{
+ return new (std::nothrow) DigiBoosterEcho(factory, sndFile, mixStruct);
+}
+
+
+DigiBoosterEcho::DigiBoosterEcho(VSTPluginLib &factory, CSoundFile &sndFile, SNDMIXPLUGIN *mixStruct)
+ : IMixPlugin(factory, sndFile, mixStruct)
+ , m_sampleRate(sndFile.GetSampleRate())
+ , m_chunk(PluginChunk::Default())
+{
+ m_mixBuffer.Initialize(2, 2);
+ InsertIntoFactoryList();
+}
+
+
+void DigiBoosterEcho::Process(float *pOutL, float *pOutR, uint32 numFrames)
+{
+ if(!m_bufferSize)
+ return;
+ const float *srcL = m_mixBuffer.GetInputBuffer(0), *srcR = m_mixBuffer.GetInputBuffer(1);
+ float *outL = m_mixBuffer.GetOutputBuffer(0), *outR = m_mixBuffer.GetOutputBuffer(1);
+
+ for(uint32 i = numFrames; i != 0; i--)
+ {
+ int readPos = m_writePos - m_delayTime;
+ if(readPos < 0)
+ readPos += m_bufferSize;
+
+ float l = *srcL++, r = *srcR++;
+ float lDelay = m_delayLine[readPos * 2], rDelay = m_delayLine[readPos * 2 + 1];
+
+ // Calculate the delay
+ float al = l * m_NCrossNBack;
+ al += r * m_PCrossNBack;
+ al += lDelay * m_NCrossPBack;
+ al += rDelay * m_PCrossPBack;
+
+ float ar = r * m_NCrossNBack;
+ ar += l * m_PCrossNBack;
+ ar += rDelay * m_NCrossPBack;
+ ar += lDelay * m_PCrossPBack;
+
+ // Prevent denormals
+ if(std::abs(al) < 1e-24f)
+ al = 0.0f;
+ if(std::abs(ar) < 1e-24f)
+ ar = 0.0f;
+
+ m_delayLine[m_writePos * 2] = al;
+ m_delayLine[m_writePos * 2 + 1] = ar;
+ m_writePos++;
+ if(m_writePos == m_bufferSize)
+ m_writePos = 0;
+
+ // Output samples now
+ *outL++ = (l * m_NMix + lDelay * m_PMix);
+ *outR++ = (r * m_NMix + rDelay * m_PMix);
+ }
+
+ ProcessMixOps(pOutL, pOutR, m_mixBuffer.GetOutputBuffer(0), m_mixBuffer.GetOutputBuffer(1), numFrames);
+}
+
+
+void DigiBoosterEcho::SaveAllParameters()
+{
+ m_pMixStruct->defaultProgram = -1;
+ try
+ {
+ m_pMixStruct->pluginData.resize(sizeof(m_chunk));
+ memcpy(m_pMixStruct->pluginData.data(), &m_chunk, sizeof(m_chunk));
+ } catch(mpt::out_of_memory e)
+ {
+ mpt::delete_out_of_memory(e);
+ m_pMixStruct->pluginData.clear();
+ }
+}
+
+
+void DigiBoosterEcho::RestoreAllParameters(int32 program)
+{
+ if(m_pMixStruct->pluginData.size() == sizeof(m_chunk) && !memcmp(m_pMixStruct->pluginData.data(), "Echo", 4))
+ {
+ memcpy(&m_chunk, m_pMixStruct->pluginData.data(), sizeof(m_chunk));
+ } else
+ {
+ IMixPlugin::RestoreAllParameters(program);
+ }
+ RecalculateEchoParams();
+}
+
+
+PlugParamValue DigiBoosterEcho::GetParameter(PlugParamIndex index)
+{
+ if(index < kEchoNumParameters)
+ {
+ return m_chunk.param[index] / 255.0f;
+ }
+ return 0;
+}
+
+
+void DigiBoosterEcho::SetParameter(PlugParamIndex index, PlugParamValue value)
+{
+ if(index < kEchoNumParameters)
+ {
+ m_chunk.param[index] = mpt::saturate_round<uint8>(mpt::safe_clamp(value, 0.0f, 1.0f) * 255.0f);
+ RecalculateEchoParams();
+ }
+}
+
+
+void DigiBoosterEcho::Resume()
+{
+ m_isResumed = true;
+ m_sampleRate = m_SndFile.GetSampleRate();
+ RecalculateEchoParams();
+ PositionChanged();
+}
+
+
+void DigiBoosterEcho::PositionChanged()
+{
+ m_bufferSize = (m_sampleRate >> 1) + (m_sampleRate >> 6);
+ try
+ {
+ m_delayLine.assign(m_bufferSize * 2, 0);
+ } catch(mpt::out_of_memory e)
+ {
+ mpt::delete_out_of_memory(e);
+ m_bufferSize = 0;
+ }
+ m_writePos = 0;
+}
+
+
+#ifdef MODPLUG_TRACKER
+
+CString DigiBoosterEcho::GetParamName(PlugParamIndex param)
+{
+ switch(param)
+ {
+ case kEchoDelay: return _T("Delay");
+ case kEchoFeedback: return _T("Feedback");
+ case kEchoMix: return _T("Wet / Dry Ratio");
+ case kEchoCross: return _T("Cross Echo");
+ }
+ return CString();
+}
+
+
+CString DigiBoosterEcho::GetParamLabel(PlugParamIndex param)
+{
+ if(param == kEchoDelay)
+ return _T("ms");
+ return CString();
+}
+
+
+CString DigiBoosterEcho::GetParamDisplay(PlugParamIndex param)
+{
+ CString s;
+ if(param == kEchoMix)
+ {
+ int wet = (m_chunk.param[kEchoMix] * 100) / 255;
+ s.Format(_T("%d%% / %d%%"), wet, 100 - wet);
+ } else if(param < kEchoNumParameters)
+ {
+ int val = m_chunk.param[param];
+ if(param == kEchoDelay)
+ {
+ if(val == 0)
+ val = 167;
+ val *= 2;
+ }
+ s.Format(_T("%d"), val);
+ }
+ return s;
+}
+
+#endif // MODPLUG_TRACKER
+
+
+IMixPlugin::ChunkData DigiBoosterEcho::GetChunk(bool)
+{
+ auto data = reinterpret_cast<const std::byte *>(&m_chunk);
+ return ChunkData(data, sizeof(m_chunk));
+}
+
+
+void DigiBoosterEcho::SetChunk(const ChunkData &chunk, bool)
+{
+ auto data = chunk.data();
+ if(chunk.size() == sizeof(chunk) && !memcmp(data, "Echo", 4))
+ {
+ memcpy(&m_chunk, data, chunk.size());
+ RecalculateEchoParams();
+ }
+}
+
+
+void DigiBoosterEcho::RecalculateEchoParams()
+{
+ // The fallback value when the delay is 0 was determined experimentally from DBPro 2.21 output.
+ // The C implementation of libdigibooster3 has no specific handling of this value and thus produces a delay with maximum length.
+ m_delayTime = ((m_chunk.param[kEchoDelay] ? m_chunk.param[kEchoDelay] : 167u) * m_sampleRate + 250u) / 500u;
+ m_PMix = (m_chunk.param[kEchoMix]) * (1.0f / 256.0f);
+ m_NMix = (256 - m_chunk.param[kEchoMix]) * (1.0f / 256.0f);
+ m_PCrossPBack = (m_chunk.param[kEchoCross] * m_chunk.param[kEchoFeedback]) * (1.0f / 65536.0f);
+ m_PCrossNBack = (m_chunk.param[kEchoCross] * (256 - m_chunk.param[kEchoFeedback])) * (1.0f / 65536.0f);
+ m_NCrossPBack = ((m_chunk.param[kEchoCross] - 256) * m_chunk.param[kEchoFeedback]) * (1.0f / 65536.0f);
+ m_NCrossNBack = ((m_chunk.param[kEchoCross] - 256) * (m_chunk.param[kEchoFeedback] - 256)) * (1.0f / 65536.0f);
+}
+
+OPENMPT_NAMESPACE_END
+
+#endif // NO_PLUGINS
diff --git a/Src/external_dependencies/openmpt-trunk/soundlib/plugins/DigiBoosterEcho.h b/Src/external_dependencies/openmpt-trunk/soundlib/plugins/DigiBoosterEcho.h
new file mode 100644
index 00000000..8d0de4d8
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/soundlib/plugins/DigiBoosterEcho.h
@@ -0,0 +1,126 @@
+/*
+ * DigiBoosterEcho.h
+ * -----------------
+ * Purpose: Implementation of the DigiBooster Pro Echo DSP
+ * Notes : (currently none)
+ * Authors: OpenMPT Devs
+ * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
+ */
+
+
+#ifndef NO_PLUGINS
+
+#include "PlugInterface.h"
+
+OPENMPT_NAMESPACE_BEGIN
+
+class DigiBoosterEcho final : public IMixPlugin
+{
+public:
+ enum Parameters
+ {
+ kEchoDelay = 0,
+ kEchoFeedback,
+ kEchoMix,
+ kEchoCross,
+ kEchoNumParameters
+ };
+
+ // Our settings chunk for file I/O, as it will be written to files
+ struct PluginChunk
+ {
+ char id[4];
+ uint8 param[kEchoNumParameters];
+
+ static PluginChunk Create(uint8 delay, uint8 feedback, uint8 mix, uint8 cross)
+ {
+ static_assert(sizeof(PluginChunk) == 8);
+ PluginChunk result;
+ memcpy(result.id, "Echo", 4);
+ result.param[kEchoDelay] = delay;
+ result.param[kEchoFeedback] = feedback;
+ result.param[kEchoMix] = mix;
+ result.param[kEchoCross] = cross;
+ return result;
+ }
+ static PluginChunk Default()
+ {
+ return Create(80, 150, 80, 255);
+ }
+ };
+
+protected:
+ std::vector<float> m_delayLine; // Echo delay line
+ uint32 m_bufferSize = 0; // Delay line length in frames
+ uint32 m_writePos = 0; // Current write position in the delay line
+ uint32 m_delayTime = 0; // In frames
+ uint32 m_sampleRate = 0;
+
+ // Echo calculation coefficients
+ float m_PMix, m_NMix;
+ float m_PCrossPBack, m_PCrossNBack;
+ float m_NCrossPBack, m_NCrossNBack;
+
+ // Settings chunk for file I/O
+ PluginChunk m_chunk;
+
+public:
+ static IMixPlugin* Create(VSTPluginLib &factory, CSoundFile &sndFile, SNDMIXPLUGIN *mixStruct);
+ DigiBoosterEcho(VSTPluginLib &factory, CSoundFile &sndFile, SNDMIXPLUGIN *mixStruct);
+
+ void Release() override { delete this; }
+ void SaveAllParameters() override;
+ void RestoreAllParameters(int32 program) override;
+ int32 GetUID() const override { int32le id; memcpy(&id, "Echo", 4); return id; }
+ int32 GetVersion() const override { return 0; }
+ void Idle() override { }
+ uint32 GetLatency() const override { return 0; }
+
+ void Process(float *pOutL, float *pOutR, uint32 numFrames) override;
+
+ float RenderSilence(uint32) override { return 0.0f; }
+
+ int32 GetNumPrograms() const override { return 0; }
+ int32 GetCurrentProgram() override { return 0; }
+ void SetCurrentProgram(int32) override { }
+
+ PlugParamIndex GetNumParameters() const override { return kEchoNumParameters; }
+ PlugParamValue GetParameter(PlugParamIndex index) override;
+ void SetParameter(PlugParamIndex index, PlugParamValue value) override;
+
+ void Resume() override;
+ void Suspend() override { m_isResumed = false; }
+ void PositionChanged() override;
+
+ bool IsInstrument() const override { return false; }
+ bool CanRecieveMidiEvents() override { return false; }
+ bool ShouldProcessSilence() override { return true; }
+
+#ifdef MODPLUG_TRACKER
+ CString GetDefaultEffectName() override { return _T("Echo"); }
+
+ CString GetParamName(PlugParamIndex param) override;
+ CString GetParamLabel(PlugParamIndex) override;
+ CString GetParamDisplay(PlugParamIndex param) override;
+
+ CString GetCurrentProgramName() override { return CString(); }
+ void SetCurrentProgramName(const CString &) override { }
+ CString GetProgramName(int32) override { return CString(); }
+
+ bool HasEditor() const override { return false; }
+#endif
+
+ int GetNumInputChannels() const override { return 2; }
+ int GetNumOutputChannels() const override { return 2; }
+
+ bool ProgramsAreChunks() const override { return true; }
+ ChunkData GetChunk(bool) override;
+ void SetChunk(const ChunkData &chunk, bool) override;
+
+protected:
+ void RecalculateEchoParams();
+};
+
+OPENMPT_NAMESPACE_END
+
+#endif // NO_PLUGINS
diff --git a/Src/external_dependencies/openmpt-trunk/soundlib/plugins/LFOPlugin.cpp b/Src/external_dependencies/openmpt-trunk/soundlib/plugins/LFOPlugin.cpp
new file mode 100644
index 00000000..52ca4484
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/soundlib/plugins/LFOPlugin.cpp
@@ -0,0 +1,521 @@
+/*
+ * LFOPlugin.cpp
+ * -------------
+ * Purpose: Plugin for automating other plugins' parameters
+ * Notes : (currently none)
+ * Authors: OpenMPT Devs
+ * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
+ */
+
+
+#include "stdafx.h"
+
+#ifndef NO_PLUGINS
+#include "LFOPlugin.h"
+#include "../Sndfile.h"
+#include "../../common/FileReader.h"
+#ifdef MODPLUG_TRACKER
+#include "../../mptrack/plugins/LFOPluginEditor.h"
+#endif // MODPLUG_TRACKER
+#include "mpt/base/numbers.hpp"
+
+OPENMPT_NAMESPACE_BEGIN
+
+IMixPlugin* LFOPlugin::Create(VSTPluginLib &factory, CSoundFile &sndFile, SNDMIXPLUGIN *mixStruct)
+{
+ return new (std::nothrow) LFOPlugin(factory, sndFile, mixStruct);
+}
+
+
+LFOPlugin::LFOPlugin(VSTPluginLib &factory, CSoundFile &sndFile, SNDMIXPLUGIN *mixStruct)
+ : IMixPlugin(factory, sndFile, mixStruct)
+ , m_PRNG(mpt::make_prng<mpt::fast_prng>(mpt::global_prng()))
+{
+ RecalculateFrequency();
+ RecalculateIncrement();
+
+ m_mixBuffer.Initialize(2, 2);
+ InsertIntoFactoryList();
+}
+
+
+// Processing (we do not process audio, just send out parameters)
+void LFOPlugin::Process(float *pOutL, float *pOutR, uint32 numFrames)
+{
+ if(!m_bypassed)
+ {
+ ResetSilence();
+ if(m_tempoSync)
+ {
+ double tempo = m_SndFile.GetCurrentBPM();
+ if(tempo != m_tempo)
+ {
+ m_tempo = tempo;
+ RecalculateIncrement();
+ }
+ }
+
+ if(m_oneshot)
+ {
+ LimitMax(m_phase, 1.0);
+ } else
+ {
+ int intPhase = static_cast<int>(m_phase);
+ if(intPhase > 0 && (m_waveForm == kSHNoise || m_waveForm == kSmoothNoise))
+ {
+ // Phase wrap-around happened
+ NextRandom();
+ }
+ m_phase -= intPhase;
+ }
+
+ double value = 0;
+ switch(m_waveForm)
+ {
+ case kSine:
+ value = std::sin(m_phase * (2.0 * mpt::numbers::pi));
+ break;
+ case kTriangle:
+ value = 1.0 - 4.0 * std::abs(m_phase - 0.5);
+ break;
+ case kSaw:
+ value = 2.0 * m_phase - 1.0;
+ break;
+ case kSquare:
+ value = m_phase < 0.5 ? -1.0 : 1.0;
+ break;
+ case kSHNoise:
+ value = m_random;
+ break;
+ case kSmoothNoise:
+ value = m_phase * m_phase * m_phase * (m_phase * (m_phase * 6 - 15) + 10); // Smootherstep
+ value = m_nextRandom * value + m_random * (1.0 - value);
+ break;
+ default:
+ break;
+ }
+ if(m_polarity)
+ value = -value;
+ // Transform value from -1...+1 to 0...1 range and apply offset/amplitude
+ value = value * m_amplitude + m_offset;
+ Limit(value, 0.0, 1.0);
+
+ IMixPlugin *plugin = GetOutputPlugin();
+ if(plugin != nullptr)
+ {
+ if(m_outputToCC)
+ {
+ plugin->MidiSend(MIDIEvents::CC(static_cast<MIDIEvents::MidiCC>(m_outputParam & 0x7F), static_cast<uint8>((m_outputParam >> 8) & 0x0F), mpt::saturate_round<uint8>(value * 127.0f)));
+ } else
+ {
+ plugin->SetParameter(m_outputParam, static_cast<PlugParamValue>(value));
+ }
+ }
+
+ m_phase += m_increment * numFrames;
+ }
+
+ ProcessMixOps(pOutL, pOutR, m_mixBuffer.GetInputBuffer(0), m_mixBuffer.GetInputBuffer(1), numFrames);
+}
+
+
+PlugParamValue LFOPlugin::GetParameter(PlugParamIndex index)
+{
+ switch(index)
+ {
+ case kAmplitude: return m_amplitude;
+ case kOffset: return m_offset;
+ case kFrequency: return m_frequency;
+ case kTempoSync: return m_tempoSync ? 1.0f : 0.0f;
+ case kWaveform: return WaveformToParam(m_waveForm);
+ case kPolarity: return m_polarity ? 1.0f : 0.0f;
+ case kBypassed: return m_bypassed ? 1.0f : 0.0f;
+ case kLoopMode: return m_oneshot ? 1.0f : 0.0f;
+ default: return 0;
+ }
+}
+
+
+void LFOPlugin::SetParameter(PlugParamIndex index, PlugParamValue value)
+{
+ ResetSilence();
+ value = mpt::safe_clamp(value, 0.0f, 1.0f);
+ switch(index)
+ {
+ case kAmplitude: m_amplitude = value; break;
+ case kOffset: m_offset = value; break;
+ case kFrequency:
+ m_frequency = value;
+ RecalculateFrequency();
+ break;
+ case kTempoSync:
+ m_tempoSync = (value >= 0.5f);
+ RecalculateFrequency();
+ break;
+ case kWaveform:
+ m_waveForm = ParamToWaveform(value);
+ break;
+ case kPolarity: m_polarity = (value >= 0.5f); break;
+ case kBypassed: m_bypassed = (value >= 0.5f); break;
+ case kLoopMode: m_oneshot = (value >= 0.5f); break;
+ case kCurrentPhase:
+ if(value == 0)
+ {
+ // Enforce next random value for random LFOs
+ NextRandom();
+ }
+ m_phase = value;
+ return;
+
+ default: return;
+ }
+
+#ifdef MODPLUG_TRACKER
+ if(GetEditor() != nullptr)
+ {
+ GetEditor()->PostMessage(WM_PARAM_UDPATE, GetSlot(), index);
+ }
+#endif
+}
+
+
+void LFOPlugin::Resume()
+{
+ m_isResumed = true;
+ RecalculateIncrement();
+ NextRandom();
+ PositionChanged();
+}
+
+
+void LFOPlugin::PositionChanged()
+{
+ // TODO Changing tempo (with tempo sync enabled), parameter automation over time and setting the LFO phase manually is not considered here.
+ m_phase = m_increment * m_SndFile.GetTotalSampleCount();
+ m_phase -= static_cast<int64>(m_phase);
+}
+
+
+bool LFOPlugin::MidiSend(uint32 midiCode)
+{
+ if(IMixPlugin *plugin = GetOutputPlugin())
+ return plugin->MidiSend(midiCode);
+ else
+ return true;
+}
+
+
+bool LFOPlugin::MidiSysexSend(mpt::const_byte_span sysex)
+{
+ if(IMixPlugin *plugin = GetOutputPlugin())
+ return plugin->MidiSysexSend(sysex);
+ else
+ return true;
+}
+
+
+void LFOPlugin::MidiCC(MIDIEvents::MidiCC nController, uint8 nParam, CHANNELINDEX trackChannel)
+{
+ if(IMixPlugin *plugin = GetOutputPlugin())
+ {
+ plugin->MidiCC(nController, nParam, trackChannel);
+ }
+}
+
+
+void LFOPlugin::MidiPitchBend(int32 increment, int8 pwd, CHANNELINDEX trackChannel)
+{
+ if(IMixPlugin *plugin = GetOutputPlugin())
+ {
+ plugin->MidiPitchBend(increment, pwd, trackChannel);
+ }
+}
+
+
+void LFOPlugin::MidiVibrato(int32 depth, int8 pwd, CHANNELINDEX trackChannel)
+{
+ if(IMixPlugin *plugin = GetOutputPlugin())
+ {
+ plugin->MidiVibrato(depth, pwd, trackChannel);
+ }
+}
+
+
+void LFOPlugin::MidiCommand(const ModInstrument &instr, uint16 note, uint16 vol, CHANNELINDEX trackChannel)
+{
+ if(ModCommand::IsNote(static_cast<ModCommand::NOTE>(note)) && vol > 0)
+ {
+ SetParameter(kCurrentPhase, 0);
+ }
+ if(IMixPlugin *plugin = GetOutputPlugin())
+ {
+ plugin->MidiCommand(instr, note, vol, trackChannel);
+ }
+}
+
+
+void LFOPlugin::HardAllNotesOff()
+{
+ if(IMixPlugin *plugin = GetOutputPlugin())
+ {
+ plugin->HardAllNotesOff();
+ }
+}
+
+
+bool LFOPlugin::IsNotePlaying(uint8 note, CHANNELINDEX trackerChn)
+{
+ if(IMixPlugin *plugin = GetOutputPlugin())
+ return plugin->IsNotePlaying(note, trackerChn);
+ else
+ return false;
+}
+
+
+void LFOPlugin::SaveAllParameters()
+{
+ auto chunk = GetChunk(false);
+ if(chunk.empty())
+ return;
+
+ m_pMixStruct->defaultProgram = -1;
+ m_pMixStruct->pluginData.assign(chunk.begin(), chunk.end());
+}
+
+
+void LFOPlugin::RestoreAllParameters(int32 /*program*/)
+{
+ SetChunk(mpt::as_span(m_pMixStruct->pluginData), false);
+}
+
+
+struct PluginData
+{
+ char magic[4];
+ uint32le version;
+ uint32le amplitude; // float
+ uint32le offset; // float
+ uint32le frequency; // float
+ uint32le waveForm;
+ uint32le outputParam;
+ uint8le tempoSync;
+ uint8le polarity;
+ uint8le bypassed;
+ uint8le outputToCC;
+ uint8le loopMode;
+};
+
+MPT_BINARY_STRUCT(PluginData, 33)
+
+
+IMixPlugin::ChunkData LFOPlugin::GetChunk(bool)
+{
+ PluginData chunk;
+ memcpy(chunk.magic, "LFO ", 4);
+ chunk.version = 0;
+ chunk.amplitude = IEEE754binary32LE(m_amplitude).GetInt32();
+ chunk.offset = IEEE754binary32LE(m_offset).GetInt32();
+ chunk.frequency = IEEE754binary32LE(m_frequency).GetInt32();
+ chunk.waveForm = m_waveForm;
+ chunk.outputParam = m_outputParam;
+ chunk.tempoSync = m_tempoSync ? 1 : 0;
+ chunk.polarity = m_polarity ? 1 : 0;
+ chunk.bypassed = m_bypassed ? 1 : 0;
+ chunk.outputToCC = m_outputToCC ? 1 : 0;
+ chunk.loopMode = m_oneshot ? 1 : 0;
+
+ m_chunkData.resize(sizeof(chunk));
+ memcpy(m_chunkData.data(), &chunk, sizeof(chunk));
+ return mpt::as_span(m_chunkData);
+}
+
+
+void LFOPlugin::SetChunk(const ChunkData &chunk, bool)
+{
+ FileReader file(chunk);
+ PluginData data;
+ if(file.ReadStructPartial(data, file.BytesLeft())
+ && !memcmp(data.magic, "LFO ", 4)
+ && data.version == 0)
+ {
+ const float amplitude = IEEE754binary32LE().SetInt32(data.amplitude);
+ m_amplitude = mpt::safe_clamp(amplitude, 0.0f, 1.0f);
+ const float offset = IEEE754binary32LE().SetInt32(data.offset);
+ m_offset = mpt::safe_clamp(offset, 0.0f, 1.0f);
+ const float frequency = IEEE754binary32LE().SetInt32(data.frequency);
+ m_frequency = mpt::safe_clamp(frequency, 0.0f, 1.0f);
+ if(data.waveForm < kNumWaveforms)
+ m_waveForm = static_cast<LFOWaveform>(data.waveForm.get());
+ m_outputParam = data.outputParam;
+ m_tempoSync = data.tempoSync != 0;
+ m_polarity = data.polarity != 0;
+ m_bypassed = data.bypassed != 0;
+ m_outputToCC = data.outputToCC != 0;
+ m_oneshot = data.loopMode != 0;
+ RecalculateFrequency();
+ }
+}
+
+
+#ifdef MODPLUG_TRACKER
+
+std::pair<PlugParamValue, PlugParamValue> LFOPlugin::GetParamUIRange(PlugParamIndex param)
+{
+ if(param == kWaveform)
+ return {0.0f, WaveformToParam(static_cast<LFOWaveform>(kNumWaveforms - 1))};
+ else
+ return {0.0f, 1.0f};
+}
+
+CString LFOPlugin::GetParamName(PlugParamIndex param)
+{
+ switch(param)
+ {
+ case kAmplitude: return _T("Amplitude");
+ case kOffset: return _T("Offset");
+ case kFrequency: return _T("Frequency");
+ case kTempoSync: return _T("Tempo Sync");
+ case kWaveform: return _T("Waveform");
+ case kPolarity: return _T("Polarity");
+ case kBypassed: return _T("Bypassed");
+ case kLoopMode: return _T("Loop Mode");
+ case kCurrentPhase: return _T("Set LFO Phase");
+ }
+ return CString();
+}
+
+
+CString LFOPlugin::GetParamLabel(PlugParamIndex param)
+{
+ if(param == kFrequency)
+ {
+ if(m_tempoSync && m_computedFrequency > 0.0 && m_computedFrequency < 1.0)
+ return _T("Beats Per Cycle");
+ else if(m_tempoSync)
+ return _T("Cycles Per Beat");
+ else
+ return _T("Hz");
+ }
+ return CString();
+}
+
+
+CString LFOPlugin::GetParamDisplay(PlugParamIndex param)
+{
+ CString s;
+ if(param == kPolarity)
+ {
+ return m_polarity ? _T("Inverted") : _T("Normal");
+ } else if(param == kTempoSync)
+ {
+ return m_tempoSync ? _T("Yes") : _T("No");
+ } else if(param == kBypassed)
+ {
+ return m_bypassed ? _T("Yes") : _T("No");
+ } else if(param == kWaveform)
+ {
+ static constexpr const TCHAR * const waveforms[] = { _T("Sine"), _T("Triangle"), _T("Saw"), _T("Square"), _T("Noise"), _T("Smoothed Noise") };
+ if(m_waveForm < static_cast<int>(std::size(waveforms)))
+ return waveforms[m_waveForm];
+ } else if(param == kLoopMode)
+ {
+ return m_oneshot ? _T("One-Shot") : _T("Looped");
+ } else if(param == kCurrentPhase)
+ {
+ return _T("Write-Only");
+ } else if(param < kLFONumParameters)
+ {
+ auto val = GetParameter(param);
+ if(param == kOffset)
+ val = 2.0f * val - 1.0f;
+ if(param == kFrequency)
+ {
+ val = static_cast<PlugParamValue>(m_computedFrequency);
+ if(m_tempoSync && val > 0.0f && val < 1.0f)
+ val = static_cast<PlugParamValue>(1.0 / m_computedFrequency);
+ }
+ s.Format(_T("%.3f"), val);
+ }
+ return s;
+}
+
+
+CAbstractVstEditor *LFOPlugin::OpenEditor()
+{
+ try
+ {
+ return new LFOPluginEditor(*this);
+ } catch(mpt::out_of_memory e)
+ {
+ mpt::delete_out_of_memory(e);
+ return nullptr;
+ }
+}
+
+#endif // MODPLUG_TRACKER
+
+
+void LFOPlugin::NextRandom()
+{
+ m_random = m_nextRandom;
+ m_nextRandom = mpt::random<int32>(m_PRNG) / static_cast<float>(int32_min);
+}
+
+
+void LFOPlugin::RecalculateFrequency()
+{
+ m_computedFrequency = 0.25 * std::pow(2.0, m_frequency * 8.0) - 0.25;
+ if(m_tempoSync)
+ {
+ if(m_computedFrequency > 0.00045)
+ {
+ double freqLog = std::log(m_computedFrequency) / mpt::numbers::ln2;
+ double freqFrac = freqLog - std::floor(freqLog);
+ freqLog -= freqFrac;
+
+ // Lock to powers of two and 1.5 times or 1.333333... times the powers of two
+ if(freqFrac < 0.20751874963942190927313052802609)
+ freqFrac = 0.0;
+ else if(freqFrac < 0.5)
+ freqFrac = 0.41503749927884381854626105605218;
+ else if(freqFrac < 0.79248125036057809072686947197391)
+ freqFrac = 0.58496250072115618145373894394782;
+ else
+ freqFrac = 1.0;
+
+ m_computedFrequency = std::pow(2.0, freqLog + freqFrac) * 0.5;
+ } else
+ {
+ m_computedFrequency = 0;
+ }
+ }
+ RecalculateIncrement();
+}
+
+
+void LFOPlugin::RecalculateIncrement()
+{
+ m_increment = m_computedFrequency / m_SndFile.GetSampleRate();
+ if(m_tempoSync)
+ {
+ m_increment *= m_tempo / 60.0;
+ }
+}
+
+
+IMixPlugin *LFOPlugin::GetOutputPlugin() const
+{
+ PLUGINDEX outPlug = m_pMixStruct->GetOutputPlugin();
+ if(outPlug > m_nSlot && outPlug < MAX_MIXPLUGINS)
+ return m_SndFile.m_MixPlugins[outPlug].pMixPlugin;
+ else
+ return nullptr;
+}
+
+
+OPENMPT_NAMESPACE_END
+
+#else
+MPT_MSVC_WORKAROUND_LNK4221(LFOPlugin)
+
+#endif // !NO_PLUGINS
diff --git a/Src/external_dependencies/openmpt-trunk/soundlib/plugins/LFOPlugin.h b/Src/external_dependencies/openmpt-trunk/soundlib/plugins/LFOPlugin.h
new file mode 100644
index 00000000..56b70e9a
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/soundlib/plugins/LFOPlugin.h
@@ -0,0 +1,156 @@
+/*
+ * LFOPlugin.h
+ * -----------
+ * Purpose: Plugin for automating other plugins' parameters
+ * Notes : (currently none)
+ * Authors: OpenMPT Devs
+ * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
+ */
+
+
+#pragma once
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#ifndef NO_PLUGINS
+
+#include "PlugInterface.h"
+#include "../../common/mptRandom.h"
+
+OPENMPT_NAMESPACE_BEGIN
+
+class LFOPlugin final : public IMixPlugin
+{
+ friend class LFOPluginEditor;
+
+protected:
+ enum Parameters
+ {
+ kAmplitude = 0,
+ kOffset,
+ kFrequency,
+ kTempoSync,
+ kWaveform,
+ kPolarity,
+ kBypassed,
+ kLoopMode,
+ kCurrentPhase,
+ kLFONumParameters
+ };
+
+ enum LFOWaveform
+ {
+ kSine = 0,
+ kTriangle,
+ kSaw,
+ kSquare,
+ kSHNoise,
+ kSmoothNoise,
+ kNumWaveforms
+ };
+
+ std::vector<std::byte> m_chunkData;
+
+ static constexpr PlugParamIndex INVALID_OUTPUT_PARAM = uint32_max;
+
+ // LFO parameters
+ float m_amplitude = 0.5f, m_offset = 0.5f, m_frequency = 0.290241f;
+ LFOWaveform m_waveForm = kSine;
+ PlugParamIndex m_outputParam = INVALID_OUTPUT_PARAM;
+ bool m_tempoSync = false, m_polarity = false, m_bypassed = false, m_outputToCC = false, m_oneshot = false;
+
+ // LFO state
+ double m_computedFrequency = 0.0;
+ double m_phase = 0.0, m_increment = 0.0;
+ double m_random = 0.0, m_nextRandom = 0.0;
+ double m_tempo = 0.0;
+
+ mpt::fast_prng m_PRNG;
+
+#ifdef MODPLUG_TRACKER
+ static constexpr int WM_PARAM_UDPATE = WM_USER + 500;
+#endif
+
+public:
+ static IMixPlugin* Create(VSTPluginLib &factory, CSoundFile &sndFile, SNDMIXPLUGIN *mixStruct);
+ LFOPlugin(VSTPluginLib &factory, CSoundFile &sndFile, SNDMIXPLUGIN *mixStruct);
+
+ void Release() override { delete this; }
+ int32 GetUID() const override { int32 id; memcpy(&id, "LFO ", 4); return id; }
+ int32 GetVersion() const override { return 0; }
+ void Idle() override { }
+ uint32 GetLatency() const override { return 0; }
+
+ void Process(float *pOutL, float *pOutR, uint32 numFrames) override;
+
+ float RenderSilence(uint32) override { return 0.0f; }
+
+ // MIDI event handling (mostly passing it through to the follow-up plugin)
+ bool MidiSend(uint32 midiCode) override;
+ bool MidiSysexSend(mpt::const_byte_span sysex) override;
+ void MidiCC(MIDIEvents::MidiCC nController, uint8 nParam, CHANNELINDEX trackChannel) override;
+ void MidiPitchBend(int32 increment, int8 pwd, CHANNELINDEX trackChannel) override;
+ void MidiVibrato(int32 depth, int8 pwd, CHANNELINDEX trackChannel) override;
+ void MidiCommand(const ModInstrument &instr, uint16 note, uint16 vol, CHANNELINDEX trackChannel) override;
+ void HardAllNotesOff() override;
+ bool IsNotePlaying(uint8 note, CHANNELINDEX trackerChn) override;
+
+ int32 GetNumPrograms() const override { return 0; }
+ int32 GetCurrentProgram() override { return 0; }
+ void SetCurrentProgram(int32) override { }
+
+ PlugParamIndex GetNumParameters() const override { return kLFONumParameters; }
+ PlugParamValue GetParameter(PlugParamIndex index) override;
+ void SetParameter(PlugParamIndex index, PlugParamValue value) override;
+
+ void Resume() override;
+ void Suspend() override { m_isResumed = false; }
+ void PositionChanged() override;
+
+ bool IsInstrument() const override { return false; }
+ bool CanRecieveMidiEvents() override { return false; }
+ bool ShouldProcessSilence() override { return true; }
+
+#ifdef MODPLUG_TRACKER
+ CString GetDefaultEffectName() override { return _T("LFO"); }
+
+ std::pair<PlugParamValue, PlugParamValue> GetParamUIRange(PlugParamIndex param) override;
+ CString GetParamName(PlugParamIndex param) override;
+ CString GetParamLabel(PlugParamIndex) override;
+ CString GetParamDisplay(PlugParamIndex param) override;
+
+ CString GetCurrentProgramName() override { return CString(); }
+ void SetCurrentProgramName(const CString &) override { }
+ CString GetProgramName(int32) override { return CString(); }
+
+ bool HasEditor() const override { return true; }
+protected:
+ CAbstractVstEditor *OpenEditor() override;
+#endif
+
+public:
+ int GetNumInputChannels() const override { return 2; }
+ int GetNumOutputChannels() const override { return 2; }
+
+ bool ProgramsAreChunks() const override { return true; }
+ // Save parameters for storing them in a module file
+ void SaveAllParameters() override;
+ // Restore parameters from module file
+ void RestoreAllParameters(int32 program) override;
+ ChunkData GetChunk(bool) override;
+ void SetChunk(const ChunkData &chunk, bool) override;
+
+protected:
+ void NextRandom();
+ void RecalculateFrequency();
+ void RecalculateIncrement();
+ IMixPlugin *GetOutputPlugin() const;
+
+public:
+ static LFOWaveform ParamToWaveform(float param) { return static_cast<LFOWaveform>(std::clamp(mpt::saturate_round<int>(param * 32.0f), 0, kNumWaveforms - 1)); }
+ static float WaveformToParam(LFOWaveform waveform) { return static_cast<int>(waveform) / 32.0f; }
+};
+
+OPENMPT_NAMESPACE_END
+
+#endif // NO_PLUGINS
diff --git a/Src/external_dependencies/openmpt-trunk/soundlib/plugins/OpCodes.h b/Src/external_dependencies/openmpt-trunk/soundlib/plugins/OpCodes.h
new file mode 100644
index 00000000..a8d303bb
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/soundlib/plugins/OpCodes.h
@@ -0,0 +1,103 @@
+/*
+ * OpCodes.h
+ * ---------
+ * Purpose: A human-readable list of VST opcodes, for error reporting purposes.
+ * Notes : (currently none)
+ * Authors: OpenMPT Devs
+ * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
+ */
+
+
+#pragma once
+
+#include "openmpt/all/BuildSettings.hpp"
+
+OPENMPT_NAMESPACE_BEGIN
+
+#ifdef MPT_WITH_VST
+inline constexpr const char *VstOpCodes[] =
+{
+ "effOpen",
+ "effClose",
+ "effSetProgram",
+ "effGetProgram",
+ "effSetProgramName",
+ "effGetProgramName",
+ "effGetParamLabel",
+ "effGetParamDisplay",
+ "effGetParamName",
+ "effGetVu",
+ "effSetSampleRate",
+ "effSetBlockSize",
+ "effMainsChanged",
+ "effEditGetRect",
+ "effEditOpen",
+ "effEditClose",
+ "effEditDraw",
+ "effEditMouse",
+ "effEditKey",
+ "effEditIdle",
+ "effEditTop",
+ "effEditSleep",
+ "effIdentify",
+ "effGetChunk",
+ "effSetChunk",
+ "effProcessEvents",
+ "effCanBeAutomated",
+ "effString2Parameter",
+ "effGetNumProgramCategories",
+ "effGetProgramNameIndexed",
+ "effCopyProgram",
+ "effConnectInput",
+ "effConnectOutput",
+ "effGetInputProperties",
+ "effGetOutputProperties",
+ "effGetPlugCategory",
+ "effGetCurrentPosition",
+ "effGetDestinationBuffer",
+ "effOfflineNotify",
+ "effOfflinePrepare",
+ "effOfflineRun",
+ "effProcessVarIo",
+ "effSetSpeakerArrangement",
+ "effSetBlockSizeAndSampleRate",
+ "effSetBypass",
+ "effGetEffectName",
+ "effGetErrorText",
+ "effGetVendorString",
+ "effGetProductString",
+ "effGetVendorVersion",
+ "effVendorSpecific",
+ "effCanDo",
+ "effGetTailSize",
+ "effIdle",
+ "effGetIcon",
+ "effSetViewPosition",
+ "effGetParameterProperties",
+ "effKeysRequired",
+ "effGetVstVersion",
+ "effEditKeyDown",
+ "effEditKeyUp",
+ "effSetEditKnobMode",
+ "effGetMidiProgramName",
+ "effGetCurrentMidiProgram",
+ "effGetMidiProgramCategory",
+ "effHasMidiProgramsChanged",
+ "effGetMidiKeyName",
+ "effBeginSetProgram",
+ "effEndSetProgram",
+ "effGetSpeakerArrangement",
+ "effShellGetNextPlugin",
+ "effStartProcess",
+ "effStopProcess",
+ "effSetTotalSampleToProcess",
+ "effSetPanLaw",
+ "effBeginLoadBank",
+ "effBeginLoadProgram",
+ "effSetProcessPrecision",
+ "effGetNumMidiInputChannels",
+ "effGetNumMidiOutputChannels"
+};
+#endif
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/soundlib/plugins/PlugInterface.cpp b/Src/external_dependencies/openmpt-trunk/soundlib/plugins/PlugInterface.cpp
new file mode 100644
index 00000000..7e524c7a
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/soundlib/plugins/PlugInterface.cpp
@@ -0,0 +1,1065 @@
+/*
+ * 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
diff --git a/Src/external_dependencies/openmpt-trunk/soundlib/plugins/PlugInterface.h b/Src/external_dependencies/openmpt-trunk/soundlib/plugins/PlugInterface.h
new file mode 100644
index 00000000..34cf3121
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/soundlib/plugins/PlugInterface.h
@@ -0,0 +1,301 @@
+/*
+ * PlugInterface.h
+ * ---------------
+ * Purpose: Interface class for plugin handling
+ * Notes : (currently none)
+ * Authors: OpenMPT Devs
+ * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
+ */
+
+
+#pragma once
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#ifndef NO_PLUGINS
+
+#include "../../soundlib/Snd_defs.h"
+#include "../../soundlib/MIDIEvents.h"
+#include "../../soundlib/Mixer.h"
+#include "PluginMixBuffer.h"
+#include "PluginStructs.h"
+
+OPENMPT_NAMESPACE_BEGIN
+
+struct VSTPluginLib;
+struct SNDMIXPLUGIN;
+struct ModInstrument;
+struct ModChannel;
+class CSoundFile;
+class CModDoc;
+class CAbstractVstEditor;
+
+struct SNDMIXPLUGINSTATE
+{
+ // dwFlags flags
+ enum PluginStateFlags
+ {
+ psfMixReady = 0x01, // Set when cleared
+ psfHasInput = 0x02, // Set when plugin has non-silent input
+ psfSilenceBypass = 0x04, // Bypass because of silence detection
+ };
+
+ mixsample_t *pMixBuffer = nullptr; // Stereo effect send buffer
+ uint32 dwFlags = 0; // PluginStateFlags
+ uint32 inputSilenceCount = 0; // How much silence has been processed? (for plugin auto-turnoff)
+ mixsample_t nVolDecayL = 0, nVolDecayR = 0; // End of sample click removal
+
+ void ResetSilence()
+ {
+ dwFlags |= psfHasInput;
+ dwFlags &= ~psfSilenceBypass;
+ inputSilenceCount = 0;
+ }
+};
+
+
+class IMixPlugin
+{
+ friend class CAbstractVstEditor;
+
+protected:
+ IMixPlugin *m_pNext = nullptr, *m_pPrev = nullptr;
+ VSTPluginLib &m_Factory;
+ CSoundFile &m_SndFile;
+ SNDMIXPLUGIN *m_pMixStruct;
+#ifdef MODPLUG_TRACKER
+ CAbstractVstEditor *m_pEditor = nullptr;
+#endif // MODPLUG_TRACKER
+
+public:
+ SNDMIXPLUGINSTATE m_MixState;
+ PluginMixBuffer<float, MIXBUFFERSIZE> m_mixBuffer; // Float buffers (input and output) for plugins
+
+protected:
+ mixsample_t m_MixBuffer[MIXBUFFERSIZE * 2 + 2]; // Stereo interleaved input (sample mixer renders here)
+
+ float m_fGain = 1.0f;
+ PLUGINDEX m_nSlot = 0;
+
+ bool m_isSongPlaying = false;
+ bool m_isResumed = false;
+
+public:
+ bool m_recordAutomation = false;
+ bool m_passKeypressesToPlug = false;
+ bool m_recordMIDIOut = false;
+
+protected:
+ virtual ~IMixPlugin();
+
+ // Insert plugin into list of loaded plugins.
+ void InsertIntoFactoryList();
+
+public:
+ // Non-virtual part of the interface
+ IMixPlugin(VSTPluginLib &factory, CSoundFile &sndFile, SNDMIXPLUGIN *mixStruct);
+ inline CSoundFile &GetSoundFile() { return m_SndFile; }
+ inline const CSoundFile &GetSoundFile() const { return m_SndFile; }
+
+#ifdef MODPLUG_TRACKER
+ CModDoc *GetModDoc();
+ const CModDoc *GetModDoc() const;
+
+ void SetSlot(PLUGINDEX slot);
+ inline PLUGINDEX GetSlot() const { return m_nSlot; }
+#endif // MODPLUG_TRACKER
+
+ inline VSTPluginLib &GetPluginFactory() const { return m_Factory; }
+ // Returns the next instance of the same plugin
+ inline IMixPlugin *GetNextInstance() const { return m_pNext; }
+
+ void SetDryRatio(float dryRatio);
+ bool IsBypassed() const;
+ void RecalculateGain();
+ // Query output latency from host (in seconds)
+ double GetOutputLatency() const;
+
+ // Destroy the plugin
+ virtual void Release() = 0;
+ virtual int32 GetUID() const = 0;
+ virtual int32 GetVersion() const = 0;
+ virtual void Idle() = 0;
+ // Plugin latency in samples
+ virtual uint32 GetLatency() const = 0;
+
+ virtual int32 GetNumPrograms() const = 0;
+ virtual int32 GetCurrentProgram() = 0;
+ virtual void SetCurrentProgram(int32 nIndex) = 0;
+
+ virtual PlugParamIndex GetNumParameters() const = 0;
+ virtual void SetParameter(PlugParamIndex paramindex, PlugParamValue paramvalue) = 0;
+ virtual PlugParamValue GetParameter(PlugParamIndex nIndex) = 0;
+
+ // Save parameters for storing them in a module file
+ virtual void SaveAllParameters();
+ // Restore parameters from module file
+ virtual void RestoreAllParameters(int32 program);
+ virtual void Process(float *pOutL, float *pOutR, uint32 numFrames) = 0;
+ void ProcessMixOps(float *pOutL, float *pOutR, float *leftPlugOutput, float *rightPlugOutput, uint32 numFrames);
+ // Render silence and return the highest resulting output level
+ virtual float RenderSilence(uint32 numSamples);
+
+ // MIDI event handling
+ virtual bool MidiSend(uint32 /*midiCode*/) { return true; }
+ virtual bool MidiSysexSend(mpt::const_byte_span /*sysex*/) { return true; }
+ virtual void MidiCC(MIDIEvents::MidiCC /*nController*/, uint8 /*nParam*/, CHANNELINDEX /*trackChannel*/) { }
+ virtual void MidiPitchBendRaw(int32 /*pitchbend*/, CHANNELINDEX /*trackChannel*/) {}
+ virtual void MidiPitchBend(int32 /*increment*/, int8 /*pwd*/, CHANNELINDEX /*trackChannel*/) { }
+ virtual void MidiVibrato(int32 /*depth*/, int8 /*pwd*/, CHANNELINDEX /*trackerChn*/) { }
+ virtual void MidiCommand(const ModInstrument &/*instr*/, uint16 /*note*/, uint16 /*vol*/, CHANNELINDEX /*trackChannel*/) { }
+ virtual void HardAllNotesOff() { }
+ virtual bool IsNotePlaying(uint8 /*note*/, CHANNELINDEX /*trackerChn*/) { return false; }
+
+ // Modify parameter by given amount. Only needs to be re-implemented if plugin architecture allows this to be performed atomically.
+ virtual void ModifyParameter(PlugParamIndex nIndex, PlugParamValue diff);
+ virtual void NotifySongPlaying(bool playing) { m_isSongPlaying = playing; }
+ virtual bool IsSongPlaying() const { return m_isSongPlaying; }
+ virtual bool IsResumed() const { return m_isResumed; }
+ virtual void Resume() = 0;
+ virtual void Suspend() = 0;
+ // Tell the plugin that there is a discontinuity between the previous and next render call (e.g. aftert jumping around in the module)
+ virtual void PositionChanged() = 0;
+ virtual void Bypass(bool = true);
+ bool ToggleBypass() { Bypass(!IsBypassed()); return IsBypassed(); }
+ virtual bool IsInstrument() const = 0;
+ virtual bool CanRecieveMidiEvents() = 0;
+ // If false is returned, mixing this plugin can be skipped if its input are currently completely silent.
+ virtual bool ShouldProcessSilence() = 0;
+ virtual void ResetSilence() { m_MixState.ResetSilence(); }
+
+ size_t GetOutputPlugList(std::vector<IMixPlugin *> &list);
+ size_t GetInputPlugList(std::vector<IMixPlugin *> &list);
+ size_t GetInputInstrumentList(std::vector<INSTRUMENTINDEX> &list);
+ size_t GetInputChannelList(std::vector<CHANNELINDEX> &list);
+
+#ifdef MODPLUG_TRACKER
+ bool SaveProgram();
+ bool LoadProgram(mpt::PathString fileName = mpt::PathString());
+
+ virtual CString GetDefaultEffectName() = 0;
+
+ // Cache a range of names, in case one-by-one retrieval would be slow (e.g. when using plugin bridge)
+ virtual void CacheProgramNames(int32 /*firstProg*/, int32 /*lastProg*/) { }
+ virtual void CacheParameterNames(int32 /*firstParam*/, int32 /*lastParam*/) { }
+
+ // Allowed value range for a parameter
+ virtual std::pair<PlugParamValue, PlugParamValue> GetParamUIRange(PlugParamIndex /*param*/) { return {0.0f, 1.0f}; }
+ // Scale allowed value range of a parameter to/from [0,1]
+ PlugParamValue GetScaledUIParam(PlugParamIndex param);
+ void SetScaledUIParam(PlugParamIndex param, PlugParamValue value);
+
+ virtual CString GetParamName(PlugParamIndex param) = 0;
+ virtual CString GetParamLabel(PlugParamIndex param) = 0;
+ virtual CString GetParamDisplay(PlugParamIndex param) = 0;
+ CString GetFormattedParamName(PlugParamIndex param);
+ CString GetFormattedParamValue(PlugParamIndex param);
+ virtual CString GetCurrentProgramName() = 0;
+ virtual void SetCurrentProgramName(const CString &name) = 0;
+ virtual CString GetProgramName(int32 program) = 0;
+ CString GetFormattedProgramName(int32 index);
+
+ virtual bool HasEditor() const = 0;
+protected:
+ virtual CAbstractVstEditor *OpenEditor();
+public:
+ // Get the plugin's editor window
+ CAbstractVstEditor *GetEditor() { return m_pEditor; }
+ const CAbstractVstEditor *GetEditor() const { return m_pEditor; }
+ void ToggleEditor();
+ void CloseEditor();
+ void SetEditorPos(int32 x, int32 y);
+ void GetEditorPos(int32 &x, int32 &y) const;
+
+ // Notify OpenMPT that a plugin parameter has changed and set document as modified
+ void AutomateParameter(PlugParamIndex param);
+ // Plugin state changed, set document as modified.
+ void SetModified();
+#endif
+
+ virtual int GetNumInputChannels() const = 0;
+ virtual int GetNumOutputChannels() const = 0;
+
+ using ChunkData = mpt::const_byte_span;
+ virtual bool ProgramsAreChunks() const { return false; }
+ virtual ChunkData GetChunk(bool /*isBank*/) { return ChunkData(); }
+ virtual void SetChunk(const ChunkData &/*chunk*/, bool /*isBank*/) { }
+
+ virtual void BeginSetProgram(int32 /*program*/ = -1) {}
+ virtual void EndSetProgram() {}
+ virtual void BeginGetProgram(int32 /*program*/ = -1) {}
+ virtual void EndGetProgram() {}
+};
+
+
+inline void IMixPlugin::ModifyParameter(PlugParamIndex nIndex, PlugParamValue diff)
+{
+ PlugParamValue val = GetParameter(nIndex) + diff;
+ Limit(val, PlugParamValue(0), PlugParamValue(1));
+ SetParameter(nIndex, val);
+}
+
+
+// IMidiPlugin: Default implementation of plugins with MIDI input
+
+class IMidiPlugin : public IMixPlugin
+{
+protected:
+ enum
+ {
+ // Pitch wheel constants
+ kPitchBendShift = 12, // Use lowest 12 bits for fractional part and vibrato flag => 16.11 fixed point precision
+ kPitchBendMask = (~1),
+ kVibratoFlag = 1,
+ };
+
+ struct PlugInstrChannel
+ {
+ int32 midiPitchBendPos = 0; // Current Pitch Wheel position, in 16.11 fixed point format. Lowest bit is used for indicating that vibrato was applied. Vibrato offset itself is not stored in this value.
+ uint16 currentProgram = uint16_max;
+ uint16 currentBank = uint16_max;
+ uint8 noteOnMap[128][MAX_CHANNELS];
+
+ void ResetProgram() { currentProgram = uint16_max; currentBank = uint16_max; }
+ };
+
+ std::array<PlugInstrChannel, 16> m_MidiCh; // MIDI channel state
+
+public:
+ IMidiPlugin(VSTPluginLib &factory, CSoundFile &sndFile, SNDMIXPLUGIN *mixStruct);
+
+ void MidiCC(MIDIEvents::MidiCC nController, uint8 nParam, CHANNELINDEX trackChannel) override;
+ void MidiPitchBendRaw(int32 pitchbend, CHANNELINDEX trackerChn) override;
+ void MidiPitchBend(int32 increment, int8 pwd, CHANNELINDEX trackerChn) override;
+ void MidiVibrato(int32 depth, int8 pwd, CHANNELINDEX trackerChn) override;
+ void MidiCommand(const ModInstrument &instr, uint16 note, uint16 vol, CHANNELINDEX trackChannel) override;
+ bool IsNotePlaying(uint8 note, CHANNELINDEX trackerChn) override;
+
+ // Get the MIDI channel currently associated with a given tracker channel
+ virtual uint8 GetMidiChannel(const ModChannel &chn, CHANNELINDEX trackChannel) const;
+
+protected:
+ uint8 GetMidiChannel(CHANNELINDEX trackChannel) const;
+
+ // Plugin wants to send MIDI to OpenMPT
+ virtual void ReceiveMidi(uint32 midiCode);
+ virtual void ReceiveSysex(mpt::const_byte_span sysex);
+
+ // Converts a 14-bit MIDI pitch bend position to our internal pitch bend position representation
+ static constexpr int32 EncodePitchBendParam(int32 position) { return (position << kPitchBendShift); }
+ // Converts the internal pitch bend position to a 14-bit MIDI pitch bend position
+ static constexpr int16 DecodePitchBendParam(int32 position) { return static_cast<int16>(position >> kPitchBendShift); }
+ // Apply Pitch Wheel Depth (PWD) to some MIDI pitch bend value.
+ static inline void ApplyPitchWheelDepth(int32 &value, int8 pwd);
+
+ void SendMidiPitchBend(uint8 midiCh, int32 newPitchBendPos);
+};
+
+OPENMPT_NAMESPACE_END
+
+#endif // NO_PLUGINS
+
diff --git a/Src/external_dependencies/openmpt-trunk/soundlib/plugins/PluginManager.cpp b/Src/external_dependencies/openmpt-trunk/soundlib/plugins/PluginManager.cpp
new file mode 100644
index 00000000..ab4e4abe
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/soundlib/plugins/PluginManager.cpp
@@ -0,0 +1,816 @@
+/*
+ * PluginManager.cpp
+ * -----------------
+ * Purpose: Implementation of the plugin manager, which keeps a list of known plugins and instantiates them.
+ * Notes : (currently none)
+ * Authors: OpenMPT Devs
+ * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
+ */
+
+
+#include "stdafx.h"
+
+#ifndef NO_PLUGINS
+
+#include "../../common/version.h"
+#include "PluginManager.h"
+#include "PlugInterface.h"
+
+#include "mpt/uuid/guid.hpp"
+#include "mpt/uuid/uuid.hpp"
+
+// Built-in plugins
+#include "DigiBoosterEcho.h"
+#include "LFOPlugin.h"
+#include "SymMODEcho.h"
+#include "dmo/DMOPlugin.h"
+#include "dmo/Chorus.h"
+#include "dmo/Compressor.h"
+#include "dmo/Distortion.h"
+#include "dmo/Echo.h"
+#include "dmo/Flanger.h"
+#include "dmo/Gargle.h"
+#include "dmo/I3DL2Reverb.h"
+#include "dmo/ParamEq.h"
+#include "dmo/WavesReverb.h"
+#ifdef MODPLUG_TRACKER
+#include "../../mptrack/plugins/MidiInOut.h"
+#endif // MODPLUG_TRACKER
+
+#include "../../common/mptStringBuffer.h"
+#include "../Sndfile.h"
+#include "../Loaders.h"
+
+#ifdef MPT_WITH_VST
+#include "../../mptrack/Vstplug.h"
+#include "../../pluginBridge/BridgeWrapper.h"
+#endif // MPT_WITH_VST
+
+#if defined(MPT_WITH_DMO)
+#include <winreg.h>
+#include <strmif.h>
+#include <tchar.h>
+#endif // MPT_WITH_DMO
+
+#ifdef MODPLUG_TRACKER
+#include "../../mptrack/Mptrack.h"
+#include "../../mptrack/TrackerSettings.h"
+#include "../../mptrack/AbstractVstEditor.h"
+#include "../../soundlib/AudioCriticalSection.h"
+#include "../mptrack/ExceptionHandler.h"
+#include "mpt/crc/crc.hpp"
+#endif // MODPLUG_TRACKER
+
+
+OPENMPT_NAMESPACE_BEGIN
+
+
+using namespace mpt::uuid_literals;
+
+
+#ifdef MPT_ALL_LOGGING
+#define VST_LOG
+#define DMO_LOG
+#endif
+
+#ifdef MODPLUG_TRACKER
+static constexpr const mpt::uchar *cacheSection = UL_("PluginCache");
+#endif // MODPLUG_TRACKER
+
+
+#ifdef MPT_WITH_VST
+
+
+uint8 VSTPluginLib::GetNativePluginArch()
+{
+ uint8 result = 0;
+ switch(mpt::OS::Windows::GetProcessArchitecture())
+ {
+ case mpt::OS::Windows::Architecture::x86:
+ result = PluginArch_x86;
+ break;
+ case mpt::OS::Windows::Architecture::amd64:
+ result = PluginArch_amd64;
+ break;
+ case mpt::OS::Windows::Architecture::arm:
+ result = PluginArch_arm;
+ break;
+ case mpt::OS::Windows::Architecture::arm64:
+ result = PluginArch_arm64;
+ break;
+ default:
+ result = 0;
+ break;
+ }
+ return result;
+}
+
+
+mpt::ustring VSTPluginLib::GetPluginArchName(uint8 arch)
+{
+ mpt::ustring result;
+ switch(arch)
+ {
+ case PluginArch_x86:
+ result = U_("x86");
+ break;
+ case PluginArch_amd64:
+ result = U_("amd64");
+ break;
+ case PluginArch_arm:
+ result = U_("arm");
+ break;
+ case PluginArch_arm64:
+ result = U_("arm64");
+ break;
+ default:
+ result = U_("");
+ break;
+ }
+ return result;
+}
+
+
+mpt::ustring VSTPluginLib::GetPluginArchNameUser(uint8 arch)
+{
+ mpt::ustring result;
+ #if defined(MPT_WITH_WINDOWS10)
+ switch(arch)
+ {
+ case PluginArch_x86:
+ result = U_("x86 (32bit)");
+ break;
+ case PluginArch_amd64:
+ result = U_("amd64 (64bit)");
+ break;
+ case PluginArch_arm:
+ result = U_("arm (32bit)");
+ break;
+ case PluginArch_arm64:
+ result = U_("arm64 (64bit)");
+ break;
+ default:
+ result = U_("");
+ break;
+ }
+ #else // !MPT_WITH_WINDOWS10
+ switch(arch)
+ {
+ case PluginArch_x86:
+ result = U_("32-Bit");
+ break;
+ case PluginArch_amd64:
+ result = U_("64-Bit");
+ break;
+ case PluginArch_arm:
+ result = U_("32-Bit");
+ break;
+ case PluginArch_arm64:
+ result = U_("64-Bit");
+ break;
+ default:
+ result = U_("");
+ break;
+ }
+ #endif // MPT_WITH_WINDOWS10
+ return result;
+}
+
+
+uint8 VSTPluginLib::GetDllArch(bool fromCache) const
+{
+ // Built-in plugins are always native.
+ if(dllPath.empty())
+ return GetNativePluginArch();
+#ifdef MPT_WITH_VST
+ if(!dllArch || !fromCache)
+ {
+ dllArch = static_cast<uint8>(BridgeWrapper::GetPluginBinaryType(dllPath));
+ }
+#else // !MPT_WITH_VST
+ MPT_UNREFERENCED_PARAMETER(fromCache);
+#endif // MPT_WITH_VST
+ return dllArch;
+}
+
+
+mpt::ustring VSTPluginLib::GetDllArchName(bool fromCache) const
+{
+ return GetPluginArchName(GetDllArch(fromCache));
+}
+
+
+mpt::ustring VSTPluginLib::GetDllArchNameUser(bool fromCache) const
+{
+ return GetPluginArchNameUser(GetDllArch(fromCache));
+}
+
+
+bool VSTPluginLib::IsNative(bool fromCache) const
+{
+ return GetDllArch(fromCache) == GetNativePluginArch();
+}
+
+
+bool VSTPluginLib::IsNativeFromCache() const
+{
+ return dllArch == GetNativePluginArch() || dllArch == 0;
+}
+
+
+#endif // MPT_WITH_VST
+
+
+// PluginCache format:
+// FullDllPath = <ID1><ID2><CRC32> (hex-encoded)
+// <ID1><ID2><CRC32>.Flags = Plugin Flags (see VSTPluginLib::DecodeCacheFlags).
+// <ID1><ID2><CRC32>.Vendor = Plugin Vendor String.
+
+#ifdef MODPLUG_TRACKER
+void VSTPluginLib::WriteToCache() const
+{
+ SettingsContainer &cacheFile = theApp.GetPluginCache();
+
+ const std::string crcName = dllPath.ToUTF8();
+ const mpt::crc32 crc(crcName);
+ const mpt::ustring IDs = mpt::ufmt::HEX0<8>(pluginId1) + mpt::ufmt::HEX0<8>(pluginId2) + mpt::ufmt::HEX0<8>(crc.result());
+
+ mpt::PathString writePath = dllPath;
+ if(theApp.IsPortableMode())
+ {
+ writePath = theApp.PathAbsoluteToInstallRelative(writePath);
+ }
+
+ cacheFile.Write<mpt::ustring>(cacheSection, writePath.ToUnicode(), IDs);
+ cacheFile.Write<CString>(cacheSection, IDs + U_(".Vendor"), vendor);
+ cacheFile.Write<int32>(cacheSection, IDs + U_(".Flags"), EncodeCacheFlags());
+}
+#endif // MODPLUG_TRACKER
+
+
+bool CreateMixPluginProc(SNDMIXPLUGIN &mixPlugin, CSoundFile &sndFile)
+{
+#ifdef MODPLUG_TRACKER
+ CVstPluginManager *that = theApp.GetPluginManager();
+ if(that)
+ {
+ return that->CreateMixPlugin(mixPlugin, sndFile);
+ }
+ return false;
+#else
+ if(!sndFile.m_PluginManager)
+ {
+ sndFile.m_PluginManager = std::make_unique<CVstPluginManager>();
+ }
+ return sndFile.m_PluginManager->CreateMixPlugin(mixPlugin, sndFile);
+#endif // MODPLUG_TRACKER
+}
+
+
+CVstPluginManager::CVstPluginManager()
+{
+#if defined(MPT_WITH_DMO)
+ HRESULT COMinit = CoInitializeEx(NULL, COINIT_MULTITHREADED);
+ if(COMinit == S_OK || COMinit == S_FALSE)
+ {
+ MustUnInitilizeCOM = true;
+ }
+#endif
+
+ // Hard-coded "plugins"
+ static constexpr struct
+ {
+ VSTPluginLib::CreateProc createProc;
+ const char *filename, *name;
+ uint32 pluginId1, pluginId2;
+ VSTPluginLib::PluginCategory category;
+ bool isInstrument, isOurs;
+ } BuiltInPlugins[] =
+ {
+ // DirectX Media Objects Emulation
+ { DMO::Chorus::Create, "{EFE6629C-81F7-4281-BD91-C9D604A95AF6}", "Chorus", kDmoMagic, 0xEFE6629C, VSTPluginLib::catDMO, false, false },
+ { DMO::Compressor::Create, "{EF011F79-4000-406D-87AF-BFFB3FC39D57}", "Compressor", kDmoMagic, 0xEF011F79, VSTPluginLib::catDMO, false, false },
+ { DMO::Distortion::Create, "{EF114C90-CD1D-484E-96E5-09CFAF912A21}", "Distortion", kDmoMagic, 0xEF114C90, VSTPluginLib::catDMO, false, false },
+ { DMO::Echo::Create, "{EF3E932C-D40B-4F51-8CCF-3F98F1B29D5D}", "Echo", kDmoMagic, 0xEF3E932C, VSTPluginLib::catDMO, false, false },
+ { DMO::Flanger::Create, "{EFCA3D92-DFD8-4672-A603-7420894BAD98}", "Flanger", kDmoMagic, 0xEFCA3D92, VSTPluginLib::catDMO, false, false },
+ { DMO::Gargle::Create, "{DAFD8210-5711-4B91-9FE3-F75B7AE279BF}", "Gargle", kDmoMagic, 0xDAFD8210, VSTPluginLib::catDMO, false, false },
+ { DMO::I3DL2Reverb::Create, "{EF985E71-D5C7-42D4-BA4D-2D073E2E96F4}", "I3DL2Reverb", kDmoMagic, 0xEF985E71, VSTPluginLib::catDMO, false, false },
+ { DMO::ParamEq::Create, "{120CED89-3BF4-4173-A132-3CB406CF3231}", "ParamEq", kDmoMagic, 0x120CED89, VSTPluginLib::catDMO, false, false },
+ { DMO::WavesReverb::Create, "{87FC0268-9A55-4360-95AA-004A1D9DE26C}", "WavesReverb", kDmoMagic, 0x87FC0268, VSTPluginLib::catDMO, false, false },
+ // First (inaccurate) Flanger implementation (will be chosen based on library name, shares ID1 and ID2 with regular Flanger)
+ { DMO::Flanger::CreateLegacy, "{EFCA3D92-DFD8-4672-A603-7420894BAD98}", "Flanger (Legacy)", kDmoMagic, 0xEFCA3D92, VSTPluginLib::catHidden, false, false },
+ // DigiBooster Pro Echo DSP
+ { DigiBoosterEcho::Create, "", "DigiBooster Pro Echo", MagicLE("DBM0"), MagicLE("Echo"), VSTPluginLib::catRoomFx, false, true },
+ // LFO
+ { LFOPlugin::Create, "", "LFO", MagicLE("OMPT"), MagicLE("LFO "), VSTPluginLib::catGenerator, false, true },
+ // SymMOD Echo
+ { SymMODEcho::Create, "", "SymMOD Echo", MagicLE("SymM"), MagicLE("Echo"), VSTPluginLib::catRoomFx, false, true },
+#ifdef MODPLUG_TRACKER
+ { MidiInOut::Create, "", "MIDI Input Output", PLUGMAGIC('V','s','t','P'), PLUGMAGIC('M','M','I','D'), VSTPluginLib::catSynth, true, true },
+#endif // MODPLUG_TRACKER
+ };
+
+ pluginList.reserve(std::size(BuiltInPlugins));
+ for(const auto &plugin : BuiltInPlugins)
+ {
+ VSTPluginLib *plug = new (std::nothrow) VSTPluginLib(plugin.createProc, true, mpt::PathString::FromUTF8(plugin.filename), mpt::PathString::FromUTF8(plugin.name));
+ if(plug != nullptr)
+ {
+ pluginList.push_back(plug);
+ plug->pluginId1 = plugin.pluginId1;
+ plug->pluginId2 = plugin.pluginId2;
+ plug->category = plugin.category;
+ plug->isInstrument = plugin.isInstrument;
+#ifdef MODPLUG_TRACKER
+ if(plugin.isOurs)
+ plug->vendor = _T("OpenMPT Project");
+#endif // MODPLUG_TRACKER
+ }
+ }
+
+#ifdef MODPLUG_TRACKER
+ // For security reasons, we do not load untrusted DMO plugins in libopenmpt.
+ EnumerateDirectXDMOs();
+#endif
+}
+
+
+CVstPluginManager::~CVstPluginManager()
+{
+ for(auto &plug : pluginList)
+ {
+ while(plug->pPluginsList != nullptr)
+ {
+ plug->pPluginsList->Release();
+ }
+ delete plug;
+ }
+#if defined(MPT_WITH_DMO)
+ if(MustUnInitilizeCOM)
+ {
+ CoUninitialize();
+ MustUnInitilizeCOM = false;
+ }
+#endif
+}
+
+
+bool CVstPluginManager::IsValidPlugin(const VSTPluginLib *pLib) const
+{
+ return mpt::contains(pluginList, pLib);
+}
+
+
+void CVstPluginManager::EnumerateDirectXDMOs()
+{
+#if defined(MPT_WITH_DMO)
+ static constexpr mpt::UUID knownDMOs[] =
+ {
+ "745057C7-F353-4F2D-A7EE-58434477730E"_uuid, // AEC (Acoustic echo cancellation, not usable)
+ "EFE6629C-81F7-4281-BD91-C9D604A95AF6"_uuid, // Chorus
+ "EF011F79-4000-406D-87AF-BFFB3FC39D57"_uuid, // Compressor
+ "EF114C90-CD1D-484E-96E5-09CFAF912A21"_uuid, // Distortion
+ "EF3E932C-D40B-4F51-8CCF-3F98F1B29D5D"_uuid, // Echo
+ "EFCA3D92-DFD8-4672-A603-7420894BAD98"_uuid, // Flanger
+ "DAFD8210-5711-4B91-9FE3-F75B7AE279BF"_uuid, // Gargle
+ "EF985E71-D5C7-42D4-BA4D-2D073E2E96F4"_uuid, // I3DL2Reverb
+ "120CED89-3BF4-4173-A132-3CB406CF3231"_uuid, // ParamEq
+ "87FC0268-9A55-4360-95AA-004A1D9DE26C"_uuid, // WavesReverb
+ "F447B69E-1884-4A7E-8055-346F74D6EDB3"_uuid, // Resampler DMO (not usable)
+ };
+
+ HKEY hkEnum;
+ TCHAR keyname[128];
+
+ LONG cr = RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("software\\classes\\DirectShow\\MediaObjects\\Categories\\f3602b3f-0592-48df-a4cd-674721e7ebeb"), 0, KEY_READ, &hkEnum);
+ DWORD index = 0;
+ while (cr == ERROR_SUCCESS)
+ {
+ if ((cr = RegEnumKey(hkEnum, index, keyname, mpt::saturate_cast<DWORD>(std::size(keyname)))) == ERROR_SUCCESS)
+ {
+ CLSID clsid;
+ mpt::winstring formattedKey = mpt::winstring(_T("{")) + mpt::winstring(keyname) + mpt::winstring(_T("}"));
+ if(mpt::VerifyStringToCLSID(formattedKey, clsid))
+ {
+ if(!mpt::contains(knownDMOs, clsid))
+ {
+ HKEY hksub;
+ formattedKey = mpt::winstring(_T("software\\classes\\DirectShow\\MediaObjects\\")) + mpt::winstring(keyname);
+ if (RegOpenKey(HKEY_LOCAL_MACHINE, formattedKey.c_str(), &hksub) == ERROR_SUCCESS)
+ {
+ TCHAR name[64];
+ DWORD datatype = REG_SZ;
+ DWORD datasize = sizeof(name);
+
+ if(ERROR_SUCCESS == RegQueryValueEx(hksub, nullptr, 0, &datatype, (LPBYTE)name, &datasize))
+ {
+ VSTPluginLib *plug = new (std::nothrow) VSTPluginLib(DMOPlugin::Create, true, mpt::PathString::FromNative(mpt::GUIDToString(clsid)), mpt::PathString::FromNative(ParseMaybeNullTerminatedStringFromBufferWithSizeInBytes<mpt::winstring>(name, datasize)));
+ if(plug != nullptr)
+ {
+ try
+ {
+ pluginList.push_back(plug);
+ plug->pluginId1 = kDmoMagic;
+ plug->pluginId2 = clsid.Data1;
+ plug->category = VSTPluginLib::catDMO;
+ } catch(mpt::out_of_memory e)
+ {
+ mpt::delete_out_of_memory(e);
+ delete plug;
+ }
+#ifdef DMO_LOG
+ MPT_LOG_GLOBAL(LogDebug, "DMO", MPT_UFORMAT("Found \"{}\" clsid={}\n")(plug->libraryName, plug->dllPath));
+#endif
+ }
+ }
+ RegCloseKey(hksub);
+ }
+ }
+ }
+ }
+ index++;
+ }
+ if (hkEnum) RegCloseKey(hkEnum);
+#endif // MPT_WITH_DMO
+}
+
+
+// Extract instrument and category information from plugin.
+#ifdef MPT_WITH_VST
+static void GetPluginInformation(bool maskCrashes, Vst::AEffect *effect, VSTPluginLib &library)
+{
+ unsigned long exception = 0;
+ library.category = static_cast<VSTPluginLib::PluginCategory>(CVstPlugin::DispatchSEH(maskCrashes, effect, Vst::effGetPlugCategory, 0, 0, nullptr, 0, exception));
+ library.isInstrument = ((effect->flags & Vst::effFlagsIsSynth) || !effect->numInputs);
+
+ if(library.isInstrument)
+ {
+ library.category = VSTPluginLib::catSynth;
+ } else if(library.category >= VSTPluginLib::numCategories)
+ {
+ library.category = VSTPluginLib::catUnknown;
+ }
+
+#ifdef MODPLUG_TRACKER
+ std::vector<char> s(256, 0);
+ CVstPlugin::DispatchSEH(maskCrashes, effect, Vst::effGetVendorString, 0, 0, s.data(), 0, exception);
+ library.vendor = mpt::ToCString(mpt::Charset::Locale, s.data());
+#endif // MODPLUG_TRACKER
+}
+#endif // MPT_WITH_VST
+
+
+#ifdef MPT_WITH_VST
+static bool TryLoadPlugin(bool maskCrashes, VSTPluginLib *plug, HINSTANCE hLib, unsigned long &exception)
+{
+ Vst::AEffect *pEffect = CVstPlugin::LoadPlugin(maskCrashes, *plug, hLib, CVstPlugin::BridgeMode::DetectRequiredBridgeMode);
+ if(!pEffect || pEffect->magic != Vst::kEffectMagic || !pEffect->dispatcher)
+ {
+ return false;
+ }
+
+ CVstPlugin::DispatchSEH(maskCrashes, pEffect, Vst::effOpen, 0, 0, 0, 0, exception);
+
+ plug->pluginId1 = pEffect->magic;
+ plug->pluginId2 = pEffect->uniqueID;
+
+ GetPluginInformation(maskCrashes, pEffect, *plug);
+
+#ifdef VST_LOG
+ intptr_t nver = CVstPlugin::DispatchSEH(maskCrashes, pEffect, Vst::effGetVstVersion, 0,0, nullptr, 0, exception);
+ if (!nver) nver = pEffect->version;
+ MPT_LOG_GLOBAL(LogDebug, "VST", MPT_UFORMAT("{}: v{}.0, {} in, {} out, {} programs, {} params, flags=0x{} realQ={} offQ={}")(
+ plug->libraryName, nver,
+ pEffect->numInputs, pEffect->numOutputs,
+ mpt::ufmt::dec0<2>(pEffect->numPrograms), mpt::ufmt::dec0<2>(pEffect->numParams),
+ mpt::ufmt::HEX0<4>(static_cast<int32>(pEffect->flags)), pEffect->realQualities, pEffect->offQualities));
+#endif // VST_LOG
+
+ CVstPlugin::DispatchSEH(maskCrashes, pEffect, Vst::effClose, 0, 0, 0, 0, exception);
+
+ return true;
+}
+#endif // !NO_NVST
+
+
+#ifdef MODPLUG_TRACKER
+// Add a plugin to the list of known plugins.
+VSTPluginLib *CVstPluginManager::AddPlugin(const mpt::PathString &dllPath, bool maskCrashes, const mpt::ustring &tags, bool fromCache, bool *fileFound)
+{
+ const mpt::PathString fileName = dllPath.GetFileName();
+
+ // Check if this is already a known plugin.
+ for(const auto &dupePlug : pluginList)
+ {
+ if(!dllPath.CompareNoCase(dllPath, dupePlug->dllPath)) return dupePlug;
+ }
+
+ if(fileFound != nullptr)
+ {
+ *fileFound = dllPath.IsFile();
+ }
+
+ // Look if the plugin info is stored in the PluginCache
+ if(fromCache)
+ {
+ SettingsContainer & cacheFile = theApp.GetPluginCache();
+ // First try finding the full path
+ mpt::ustring IDs = cacheFile.Read<mpt::ustring>(cacheSection, dllPath.ToUnicode(), U_(""));
+ if(IDs.length() < 16)
+ {
+ // If that didn't work out, find relative path
+ mpt::PathString relPath = theApp.PathAbsoluteToInstallRelative(dllPath);
+ IDs = cacheFile.Read<mpt::ustring>(cacheSection, relPath.ToUnicode(), U_(""));
+ }
+
+ if(IDs.length() >= 16)
+ {
+ VSTPluginLib *plug = new (std::nothrow) VSTPluginLib(nullptr, false, dllPath, fileName, tags);
+ if(plug == nullptr)
+ {
+ return nullptr;
+ }
+ pluginList.push_back(plug);
+
+ // Extract plugin IDs
+ for (int i = 0; i < 16; i++)
+ {
+ int32 n = IDs[i] - '0';
+ if (n > 9) n = IDs[i] + 10 - 'A';
+ n &= 0x0f;
+ if (i < 8)
+ {
+ plug->pluginId1 = (plug->pluginId1 << 4) | n;
+ } else
+ {
+ plug->pluginId2 = (plug->pluginId2 << 4) | n;
+ }
+ }
+
+ const mpt::ustring flagKey = IDs + U_(".Flags");
+ plug->DecodeCacheFlags(cacheFile.Read<int32>(cacheSection, flagKey, 0));
+ plug->vendor = cacheFile.Read<CString>(cacheSection, IDs + U_(".Vendor"), CString());
+
+#ifdef VST_LOG
+ MPT_LOG_GLOBAL(LogDebug, "VST", MPT_UFORMAT("Plugin \"{}\" found in PluginCache")(plug->libraryName));
+#endif // VST_LOG
+ return plug;
+ } else
+ {
+#ifdef VST_LOG
+ MPT_LOG_GLOBAL(LogDebug, "VST", MPT_UFORMAT("Plugin mismatch in PluginCache: \"{}\" [{}]")(dllPath, IDs));
+#endif // VST_LOG
+ }
+ }
+
+ // If this key contains a file name on program launch, a plugin previously crashed OpenMPT.
+ theApp.GetSettings().Write<mpt::PathString>(U_("VST Plugins"), U_("FailedPlugin"), dllPath, SettingWriteThrough);
+
+ bool validPlug = false;
+
+ VSTPluginLib *plug = new (std::nothrow) VSTPluginLib(nullptr, false, dllPath, fileName, tags);
+ if(plug == nullptr)
+ {
+ return nullptr;
+ }
+
+#ifdef MPT_WITH_VST
+ unsigned long exception = 0;
+ // Always scan plugins in a separate process
+ HINSTANCE hLib = NULL;
+ {
+#ifdef MODPLUG_TRACKER
+ ExceptionHandler::Context ectx{ MPT_UFORMAT("VST Plugin: {}")(plug->dllPath.ToUnicode()) };
+ ExceptionHandler::ContextSetter ectxguard{&ectx};
+#endif // MODPLUG_TRACKER
+
+ validPlug = TryLoadPlugin(maskCrashes, plug, hLib, exception);
+ }
+ if(hLib)
+ {
+ FreeLibrary(hLib);
+ }
+ if(exception != 0)
+ {
+ CVstPluginManager::ReportPlugException(MPT_UFORMAT("Exception {} while trying to load plugin \"{}\"!\n")(mpt::ufmt::HEX0<8>(exception), plug->libraryName));
+ }
+
+#endif // MPT_WITH_VST
+
+ // Now it should be safe to assume that this plugin loaded properly. :)
+ theApp.GetSettings().Remove(U_("VST Plugins"), U_("FailedPlugin"));
+
+ // If OK, write the information in PluginCache
+ if(validPlug)
+ {
+ pluginList.push_back(plug);
+ plug->WriteToCache();
+ } else
+ {
+ delete plug;
+ }
+
+ return (validPlug ? plug : nullptr);
+}
+
+
+// Remove a plugin from the list of known plugins and release any remaining instances of it.
+bool CVstPluginManager::RemovePlugin(VSTPluginLib *pFactory)
+{
+ for(const_iterator p = begin(); p != end(); p++)
+ {
+ VSTPluginLib *plug = *p;
+ if(plug == pFactory)
+ {
+ // Kill all instances of this plugin
+ CriticalSection cs;
+
+ while(plug->pPluginsList != nullptr)
+ {
+ plug->pPluginsList->Release();
+ }
+ pluginList.erase(p);
+ delete plug;
+ return true;
+ }
+ }
+ return false;
+}
+#endif // MODPLUG_TRACKER
+
+
+// Create an instance of a plugin.
+bool CVstPluginManager::CreateMixPlugin(SNDMIXPLUGIN &mixPlugin, CSoundFile &sndFile)
+{
+ VSTPluginLib *pFound = nullptr;
+
+ // Find plugin in library
+ enum PlugMatchQuality
+ {
+ kNoMatch,
+ kMatchName,
+ kMatchId,
+ kMatchNameAndId,
+ };
+
+ PlugMatchQuality match = kNoMatch; // "Match quality" of found plugin. Higher value = better match.
+#if MPT_OS_WINDOWS && !MPT_OS_WINDOWS_WINRT
+ const mpt::PathString libraryName = mpt::PathString::FromUnicode(mixPlugin.GetLibraryName());
+#else
+ const std::string libraryName = mpt::ToCharset(mpt::Charset::UTF8, mixPlugin.GetLibraryName());
+#endif
+ for(const auto &plug : pluginList)
+ {
+ const bool matchID = (plug->pluginId1 == mixPlugin.Info.dwPluginId1)
+ && (plug->pluginId2 == mixPlugin.Info.dwPluginId2);
+#if MPT_OS_WINDOWS && !MPT_OS_WINDOWS_WINRT
+ const bool matchName = !mpt::PathString::CompareNoCase(plug->libraryName, libraryName);
+#else
+ const bool matchName = !mpt::CompareNoCaseAscii(plug->libraryName.ToUTF8(), libraryName);
+#endif
+
+ if(matchID && matchName)
+ {
+ pFound = plug;
+#ifdef MPT_WITH_VST
+ if(plug->IsNative(false))
+ {
+ break;
+ }
+#endif // MPT_WITH_VST
+ // If the plugin isn't native, first check if a native version can be found.
+ match = kMatchNameAndId;
+ } else if(matchID && match < kMatchId)
+ {
+ pFound = plug;
+ match = kMatchId;
+ } else if(matchName && match < kMatchName)
+ {
+ pFound = plug;
+ match = kMatchName;
+ }
+ }
+
+ if(pFound != nullptr && pFound->Create != nullptr)
+ {
+ IMixPlugin *plugin = pFound->Create(*pFound, sndFile, &mixPlugin);
+ return plugin != nullptr;
+ }
+
+#ifdef MODPLUG_TRACKER
+ bool maskCrashes = TrackerSettings::Instance().BrokenPluginsWorkaroundVSTMaskAllCrashes;
+
+ if(!pFound && (mixPlugin.GetLibraryName() != U_("")))
+ {
+ // Try finding the plugin DLL in the plugin directory or plugin cache instead.
+ mpt::PathString fullPath = TrackerSettings::Instance().PathPlugins.GetDefaultDir();
+ if(fullPath.empty())
+ {
+ fullPath = theApp.GetInstallPath() + P_("Plugins\\");
+ }
+ fullPath += mpt::PathString::FromUnicode(mixPlugin.GetLibraryName()) + P_(".dll");
+
+ pFound = AddPlugin(fullPath, maskCrashes);
+ if(!pFound)
+ {
+ // Try plugin cache (search for library name)
+ SettingsContainer &cacheFile = theApp.GetPluginCache();
+ mpt::ustring IDs = cacheFile.Read<mpt::ustring>(cacheSection, mixPlugin.GetLibraryName(), U_(""));
+ if(IDs.length() >= 16)
+ {
+ fullPath = cacheFile.Read<mpt::PathString>(cacheSection, IDs, P_(""));
+ if(!fullPath.empty())
+ {
+ fullPath = theApp.PathInstallRelativeToAbsolute(fullPath);
+ if(fullPath.IsFile())
+ {
+ pFound = AddPlugin(fullPath, maskCrashes);
+ }
+ }
+ }
+ }
+ }
+
+#ifdef MPT_WITH_VST
+ if(pFound && mixPlugin.Info.dwPluginId1 == Vst::kEffectMagic)
+ {
+ Vst::AEffect *pEffect = nullptr;
+ HINSTANCE hLibrary = nullptr;
+ bool validPlugin = false;
+
+ pEffect = CVstPlugin::LoadPlugin(maskCrashes, *pFound, hLibrary, TrackerSettings::Instance().bridgeAllPlugins ? CVstPlugin::BridgeMode::ForceBridgeWithFallback : CVstPlugin::BridgeMode::Automatic);
+
+ if(pEffect != nullptr && pEffect->dispatcher != nullptr && pEffect->magic == Vst::kEffectMagic)
+ {
+ validPlugin = true;
+
+ GetPluginInformation(maskCrashes, pEffect, *pFound);
+
+ // Update cached information
+ pFound->WriteToCache();
+
+ CVstPlugin *pVstPlug = new (std::nothrow) CVstPlugin(maskCrashes, hLibrary, *pFound, mixPlugin, *pEffect, sndFile);
+ if(pVstPlug == nullptr)
+ {
+ validPlugin = false;
+ }
+ }
+
+ if(!validPlugin)
+ {
+ FreeLibrary(hLibrary);
+ CVstPluginManager::ReportPlugException(MPT_UFORMAT("Unable to create plugin \"{}\"!\n")(pFound->libraryName));
+ }
+ return validPlugin;
+ } else
+ {
+ // "plug not found" notification code MOVED to CSoundFile::Create
+#ifdef VST_LOG
+ MPT_LOG_GLOBAL(LogDebug, "VST", U_("Unknown plugin"));
+#endif
+ }
+#endif // MPT_WITH_VST
+
+#endif // MODPLUG_TRACKER
+ return false;
+}
+
+
+#ifdef MODPLUG_TRACKER
+void CVstPluginManager::OnIdle()
+{
+ for(auto &factory : pluginList)
+ {
+ // Note: bridged plugins won't receive these messages and generate their own idle messages.
+ IMixPlugin *p = factory->pPluginsList;
+ while (p)
+ {
+ //rewbs. VSTCompliance: A specific plug has requested indefinite periodic processing time.
+ p->Idle();
+ //We need to update all open editors
+ CAbstractVstEditor *editor = p->GetEditor();
+ if (editor && editor->m_hWnd)
+ {
+ editor->UpdateParamDisplays();
+ }
+ //end rewbs. VSTCompliance:
+
+ p = p->GetNextInstance();
+ }
+ }
+}
+
+
+void CVstPluginManager::ReportPlugException(const mpt::ustring &msg)
+{
+ Reporting::Notification(msg);
+#ifdef VST_LOG
+ MPT_LOG_GLOBAL(LogDebug, "VST", mpt::ToUnicode(msg));
+#endif
+}
+
+#endif // MODPLUG_TRACKER
+
+OPENMPT_NAMESPACE_END
+
+#endif // NO_PLUGINS
diff --git a/Src/external_dependencies/openmpt-trunk/soundlib/plugins/PluginManager.h b/Src/external_dependencies/openmpt-trunk/soundlib/plugins/PluginManager.h
new file mode 100644
index 00000000..84575ad6
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/soundlib/plugins/PluginManager.h
@@ -0,0 +1,190 @@
+/*
+ * PluginManager.h
+ * ---------------
+ * Purpose: Plugin management
+ * Notes : (currently none)
+ * Authors: OpenMPT Devs
+ * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
+ */
+
+
+#pragma once
+
+#include "openmpt/all/BuildSettings.hpp"
+
+OPENMPT_NAMESPACE_BEGIN
+
+constexpr int32 PLUGMAGIC(char a, char b, char c, char d) noexcept
+{
+ return static_cast<int32>((static_cast<uint32>(a) << 24) | (static_cast<uint32>(b) << 16) | (static_cast<uint32>(c) << 8) | (static_cast<uint32>(d) << 0));
+}
+
+//#define kBuzzMagic PLUGMAGIC('B', 'u', 'z', 'z')
+inline constexpr int32 kDmoMagic = PLUGMAGIC('D', 'X', 'M', 'O');
+
+class CSoundFile;
+class IMixPlugin;
+struct SNDMIXPLUGIN;
+enum PluginArch : int;
+
+struct VSTPluginLib
+{
+public:
+ enum PluginCategory : uint8
+ {
+ // Same plugin categories as defined in VST SDK
+ catUnknown = 0,
+ catEffect, // Simple Effect
+ catSynth, // VST Instrument (Synths, samplers,...)
+ catAnalysis, // Scope, Tuner, ...
+ catMastering, // Dynamics, ...
+ catSpacializer, // Panners, ...
+ catRoomFx, // Delays and Reverbs
+ catSurroundFx, // Dedicated surround processor
+ catRestoration, // Denoiser, ...
+ catOfflineProcess, // Offline Process
+ catShell, // Plug-in is container of other plug-ins
+ catGenerator, // Tone Generator, ...
+ // Custom categories
+ catDMO, // DirectX media object plugin
+ catHidden, // For internal plugins that should not be visible to the user (e.g. because they only exist for legacy reasons)
+
+ numCategories
+ };
+
+public:
+ using CreateProc = IMixPlugin *(*)(VSTPluginLib &factory, CSoundFile &sndFile, SNDMIXPLUGIN *mixStruct);
+
+ IMixPlugin *pPluginsList = nullptr; // Pointer to first plugin instance (this instance carries pointers to other instances)
+ CreateProc Create; // Factory to call for this plugin
+ mpt::PathString libraryName; // Display name
+ mpt::PathString dllPath; // Full path name
+#ifdef MODPLUG_TRACKER
+ mpt::ustring tags; // User tags
+ CString vendor;
+#endif // MODPLUG_TRACKER
+ int32 pluginId1 = 0; // Plugin type (kEffectMagic, kDmoMagic, ...)
+ int32 pluginId2 = 0; // Plugin unique ID
+ PluginCategory category = catUnknown;
+ const bool isBuiltIn : 1;
+ bool isInstrument : 1;
+ bool useBridge : 1, shareBridgeInstance : 1, modernBridge : 1;
+protected:
+ mutable uint8 dllArch = 0;
+
+public:
+ VSTPluginLib(CreateProc factoryProc, bool isBuiltIn, const mpt::PathString &dllPath, const mpt::PathString &libraryName
+#ifdef MODPLUG_TRACKER
+ , const mpt::ustring &tags = mpt::ustring(), const CString &vendor = CString()
+#endif // MODPLUG_TRACKER
+ )
+ : Create(factoryProc)
+ , libraryName(libraryName), dllPath(dllPath)
+#ifdef MODPLUG_TRACKER
+ , tags(tags)
+ , vendor(vendor)
+#endif // MODPLUG_TRACKER
+ , category(catUnknown)
+ , isBuiltIn(isBuiltIn), isInstrument(false)
+ , useBridge(false), shareBridgeInstance(true), modernBridge(true)
+ {
+ }
+
+#ifdef MPT_WITH_VST
+
+ // Get native phost process arch encoded as plugin arch
+ static uint8 GetNativePluginArch();
+ static mpt::ustring GetPluginArchName(uint8 arch);
+ static mpt::ustring GetPluginArchNameUser(uint8 arch);
+
+ // Check whether a plugin can be hosted inside OpenMPT or requires bridging
+ uint8 GetDllArch(bool fromCache = true) const;
+ mpt::ustring GetDllArchName(bool fromCache = true) const;
+ mpt::ustring GetDllArchNameUser(bool fromCache = true) const;
+ bool IsNative(bool fromCache = true) const;
+ // Check if a plugin is native, and if it is currently unknown, assume that it is native. Use this function only for performance reasons
+ // (e.g. if tons of unscanned plugins would slow down generation of the plugin selection dialog)
+ bool IsNativeFromCache() const;
+
+#endif // MPT_WITH_VST
+
+ void WriteToCache() const;
+
+ uint32 EncodeCacheFlags() const
+ {
+ // Format: 00000000.0000000M.AAAAAASB.CCCCCCCI
+ return (isInstrument ? 1 : 0)
+ | (category << 1)
+ | (useBridge ? 0x100 : 0)
+ | (shareBridgeInstance ? 0x200 : 0)
+ | ((dllArch / 8) << 10)
+ | (modernBridge ? 0x10000 : 0)
+ ;
+ }
+
+ void DecodeCacheFlags(uint32 flags)
+ {
+ category = static_cast<PluginCategory>((flags & 0xFF) >> 1);
+ if(category >= numCategories)
+ {
+ category = catUnknown;
+ }
+ if(flags & 1)
+ {
+ isInstrument = true;
+ category = catSynth;
+ }
+ useBridge = (flags & 0x100) != 0;
+ shareBridgeInstance = (flags & 0x200) != 0;
+ dllArch = ((flags >> 10) & 0x3F) * 8;
+ modernBridge = (flags & 0x10000) != 0;
+ }
+};
+
+
+class CVstPluginManager
+{
+#ifndef NO_PLUGINS
+protected:
+#if defined(MPT_WITH_DMO)
+ bool MustUnInitilizeCOM = false;
+#endif
+ std::vector<VSTPluginLib *> pluginList;
+
+public:
+ CVstPluginManager();
+ ~CVstPluginManager();
+
+ using iterator = std::vector<VSTPluginLib *>::iterator;
+ using const_iterator = std::vector<VSTPluginLib *>::const_iterator;
+
+ iterator begin() { return pluginList.begin(); }
+ const_iterator begin() const { return pluginList.begin(); }
+ iterator end() { return pluginList.end(); }
+ const_iterator end() const { return pluginList.end(); }
+ void reserve(size_t num) { pluginList.reserve(num); }
+ size_t size() const { return pluginList.size(); }
+
+ bool IsValidPlugin(const VSTPluginLib *pLib) const;
+ VSTPluginLib *AddPlugin(const mpt::PathString &dllPath, bool maskCrashes, const mpt::ustring &tags = mpt::ustring(), bool fromCache = true, bool *fileFound = nullptr);
+ bool RemovePlugin(VSTPluginLib *);
+ bool CreateMixPlugin(SNDMIXPLUGIN &, CSoundFile &);
+ void OnIdle();
+ static void ReportPlugException(const mpt::ustring &msg);
+
+protected:
+ void EnumerateDirectXDMOs();
+
+#else // NO_PLUGINS
+public:
+ const VSTPluginLib **begin() const { return nullptr; }
+ const VSTPluginLib **end() const { return nullptr; }
+ void reserve(size_t) { }
+ size_t size() const { return 0; }
+
+ void OnIdle() {}
+#endif // NO_PLUGINS
+};
+
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/soundlib/plugins/PluginMixBuffer.h b/Src/external_dependencies/openmpt-trunk/soundlib/plugins/PluginMixBuffer.h
new file mode 100644
index 00000000..55178778
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/soundlib/plugins/PluginMixBuffer.h
@@ -0,0 +1,137 @@
+/*
+ * PluginMixBuffer.h
+ * -----------------
+ * Purpose: Helper class for managing plugin audio input and output buffers.
+ * Notes : (currently none)
+ * Authors: OpenMPT Devs
+ * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
+ */
+
+
+#pragma once
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#include <algorithm>
+#include <array>
+
+#if defined(MPT_ENABLE_ARCH_INTRINSICS) || defined(MPT_WITH_VST)
+#include "mpt/base/aligned_array.hpp"
+#endif // MPT_ENABLE_ARCH_INTRINSICS || MPT_WITH_VST
+
+
+OPENMPT_NAMESPACE_BEGIN
+
+
+// At least this part of the code is ready for double-precision rendering... :>
+// buffer_t: Sample buffer type (float, double, ...)
+// bufferSize: Buffer size in samples
+template<typename buffer_t, uint32 bufferSize>
+class PluginMixBuffer
+{
+
+private:
+
+#if defined(MPT_ENABLE_ARCH_INTRINSICS) || defined(MPT_WITH_VST)
+ static constexpr std::align_val_t alignment = std::align_val_t{16};
+ static_assert(sizeof(mpt::aligned_array<buffer_t, bufferSize, alignment>) == sizeof(std::array<buffer_t, bufferSize>));
+ static_assert(alignof(mpt::aligned_array<buffer_t, bufferSize, alignment>) == static_cast<std::size_t>(alignment));
+#endif // MPT_ENABLE_ARCH_INTRINSICS || MPT_WITH_VST
+
+protected:
+
+#if defined(MPT_ENABLE_ARCH_INTRINSICS) || defined(MPT_WITH_VST)
+ std::vector<mpt::aligned_array<buffer_t, bufferSize, alignment>> inputs;
+ std::vector<mpt::aligned_array<buffer_t, bufferSize, alignment>> outputs;
+#else // !(MPT_ENABLE_ARCH_INTRINSICS || MPT_WITH_VST)
+ std::vector<std::array<buffer_t, bufferSize>> inputs;
+ std::vector<std::array<buffer_t, bufferSize>> outputs;
+#endif // MPT_ENABLE_ARCH_INTRINSICS || MPT_WITH_VST
+ std::vector<buffer_t*> inputsarray;
+ std::vector<buffer_t*> outputsarray;
+
+public:
+
+ // Allocate input and output buffers
+ bool Initialize(uint32 numInputs, uint32 numOutputs)
+ {
+ // Short cut - we do not need to recreate the buffers.
+ if(inputs.size() == numInputs && outputs.size() == numOutputs)
+ {
+ return true;
+ }
+
+ try
+ {
+ inputs.resize(numInputs);
+ outputs.resize(numOutputs);
+ inputsarray.resize(numInputs);
+ outputsarray.resize(numOutputs);
+ } catch(mpt::out_of_memory e)
+ {
+ mpt::delete_out_of_memory(e);
+ inputs.clear();
+ inputs.shrink_to_fit();
+ outputs.clear();
+ outputs.shrink_to_fit();
+ inputsarray.clear();
+ inputsarray.shrink_to_fit();
+ outputsarray.clear();
+ outputsarray.shrink_to_fit();
+ return false;
+ }
+
+ for(uint32 i = 0; i < numInputs; i++)
+ {
+ inputsarray[i] = inputs[i].data();
+ }
+
+ for(uint32 i = 0; i < numOutputs; i++)
+ {
+ outputsarray[i] = outputs[i].data();
+ }
+
+ return true;
+ }
+
+ // Silence all input buffers.
+ void ClearInputBuffers(uint32 numSamples)
+ {
+ MPT_ASSERT(numSamples <= bufferSize);
+ for(size_t i = 0; i < inputs.size(); i++)
+ {
+ std::fill(inputs[i].data(), inputs[i].data() + numSamples, buffer_t{0});
+ }
+ }
+
+ // Silence all output buffers.
+ void ClearOutputBuffers(uint32 numSamples)
+ {
+ MPT_ASSERT(numSamples <= bufferSize);
+ for(size_t i = 0; i < outputs.size(); i++)
+ {
+ std::fill(outputs[i].data(), outputs[i].data() + numSamples, buffer_t{0});
+ }
+ }
+
+ PluginMixBuffer()
+ {
+ Initialize(2, 0);
+ }
+
+ // Return pointer to a given input or output buffer
+ const buffer_t *GetInputBuffer(uint32 index) const { return inputs[index].data(); }
+ const buffer_t *GetOutputBuffer(uint32 index) const { return outputs[index].data(); }
+ buffer_t *GetInputBuffer(uint32 index) { return inputs[index].data(); }
+ buffer_t *GetOutputBuffer(uint32 index) { return outputs[index].data(); }
+
+ // Return pointer array to all input or output buffers
+ buffer_t **GetInputBufferArray() { return inputs.empty() ? nullptr : inputsarray.data(); }
+ buffer_t **GetOutputBufferArray() { return outputs.empty() ? nullptr : outputsarray.data(); }
+
+ bool Ok() const { return (inputs.size() + outputs.size()) > 0; }
+
+};
+
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/soundlib/plugins/PluginStructs.h b/Src/external_dependencies/openmpt-trunk/soundlib/plugins/PluginStructs.h
new file mode 100644
index 00000000..4dc58d30
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/soundlib/plugins/PluginStructs.h
@@ -0,0 +1,141 @@
+/*
+ * PluginStructs.h
+ * ---------------
+ * Purpose: Basic plugin structs for CSoundFile.
+ * Notes : (currently none)
+ * Authors: OpenMPT Devs
+ * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
+ */
+
+
+#pragma once
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#include "../Snd_defs.h"
+#ifndef NO_PLUGINS
+#include "openmpt/base/Endian.hpp"
+#endif // NO_PLUGINS
+
+OPENMPT_NAMESPACE_BEGIN
+
+////////////////////////////////////////////////////////////////////
+// Mix Plugins
+
+using PlugParamIndex = uint32;
+using PlugParamValue = float;
+
+struct SNDMIXPLUGINSTATE;
+struct SNDMIXPLUGIN;
+class IMixPlugin;
+class CSoundFile;
+
+#ifndef NO_PLUGINS
+
+struct SNDMIXPLUGININFO
+{
+ // dwInputRouting flags
+ enum RoutingFlags
+ {
+ irApplyToMaster = 0x01, // Apply to master mix
+ irBypass = 0x02, // Bypass effect
+ irWetMix = 0x04, // Wet Mix (dry added)
+ irExpandMix = 0x08, // [0%,100%] -> [-200%,200%]
+ irAutoSuspend = 0x10, // Plugin will automatically suspend on silence
+ };
+
+ int32le dwPluginId1; // Plugin type (kEffectMagic, kDmoMagic, kBuzzMagic)
+ int32le dwPluginId2; // Plugin unique ID
+ uint8le routingFlags; // See RoutingFlags
+ uint8le mixMode;
+ uint8le gain; // Divide by 10 to get real gain
+ uint8le reserved;
+ uint32le dwOutputRouting; // 0 = send to master 0x80 + x = send to plugin x
+ uint32le dwReserved[4]; // Reserved for routing info
+ mpt::modecharbuf<32, mpt::String::nullTerminated> szName; // User-chosen plugin display name - this is locale ANSI!
+ mpt::modecharbuf<64, mpt::String::nullTerminated> szLibraryName; // original DLL name - this is UTF-8!
+
+ // Should only be called from SNDMIXPLUGIN::SetBypass() and IMixPlugin::Bypass()
+ void SetBypass(bool bypass = true) { if(bypass) routingFlags |= irBypass; else routingFlags &= uint8(~irBypass); }
+};
+
+MPT_BINARY_STRUCT(SNDMIXPLUGININFO, 128) // this is directly written to files, so the size must be correct!
+
+
+struct SNDMIXPLUGIN
+{
+ IMixPlugin *pMixPlugin = nullptr;
+ std::vector<std::byte> pluginData;
+ SNDMIXPLUGININFO Info = {};
+ float fDryRatio = 0;
+ int32 defaultProgram = 0;
+ int32 editorX = 0, editorY = 0;
+
+#if defined(MPT_ENABLE_CHARSET_LOCALE)
+ const char * GetNameLocale() const
+ {
+ return Info.szName.buf;
+ }
+ mpt::ustring GetName() const
+ {
+ return mpt::ToUnicode(mpt::Charset::Locale, Info.szName);
+ }
+#endif // MPT_ENABLE_CHARSET_LOCALE
+ mpt::ustring GetLibraryName() const
+ {
+ return mpt::ToUnicode(mpt::Charset::UTF8, Info.szLibraryName);
+ }
+
+ // Check if a plugin is loaded into this slot (also returns true if the plugin in this slot has not been found)
+ bool IsValidPlugin() const { return (Info.dwPluginId1 | Info.dwPluginId2) != 0; }
+
+ // Input routing getters
+ uint8 GetGain() const
+ { return Info.gain; }
+ uint8 GetMixMode() const
+ { return Info.mixMode; }
+ bool IsMasterEffect() const
+ { return (Info.routingFlags & SNDMIXPLUGININFO::irApplyToMaster) != 0; }
+ bool IsWetMix() const
+ { return (Info.routingFlags & SNDMIXPLUGININFO::irWetMix) != 0; }
+ bool IsExpandedMix() const
+ { return (Info.routingFlags & SNDMIXPLUGININFO::irExpandMix) != 0; }
+ bool IsBypassed() const
+ { return (Info.routingFlags & SNDMIXPLUGININFO::irBypass) != 0; }
+ bool IsAutoSuspendable() const
+ { return (Info.routingFlags & SNDMIXPLUGININFO::irAutoSuspend) != 0; }
+
+ // Input routing setters
+ void SetGain(uint8 gain);
+ void SetMixMode(uint8 mixMode)
+ { Info.mixMode = mixMode; }
+ void SetMasterEffect(bool master = true)
+ { if(master) Info.routingFlags |= SNDMIXPLUGININFO::irApplyToMaster; else Info.routingFlags &= uint8(~SNDMIXPLUGININFO::irApplyToMaster); }
+ void SetWetMix(bool wetMix = true)
+ { if(wetMix) Info.routingFlags |= SNDMIXPLUGININFO::irWetMix; else Info.routingFlags &= uint8(~SNDMIXPLUGININFO::irWetMix); }
+ void SetExpandedMix(bool expanded = true)
+ { if(expanded) Info.routingFlags |= SNDMIXPLUGININFO::irExpandMix; else Info.routingFlags &= uint8(~SNDMIXPLUGININFO::irExpandMix); }
+ void SetBypass(bool bypass = true);
+ void SetAutoSuspend(bool suspend = true)
+ { if(suspend) Info.routingFlags |= SNDMIXPLUGININFO::irAutoSuspend; else Info.routingFlags &= uint8(~SNDMIXPLUGININFO::irAutoSuspend); }
+
+ // Output routing getters
+ bool IsOutputToMaster() const
+ { return Info.dwOutputRouting == 0; }
+ PLUGINDEX GetOutputPlugin() const
+ { return Info.dwOutputRouting >= 0x80 ? static_cast<PLUGINDEX>(Info.dwOutputRouting - 0x80) : PLUGINDEX_INVALID; }
+
+ // Output routing setters
+ void SetOutputToMaster()
+ { Info.dwOutputRouting = 0; }
+ void SetOutputPlugin(PLUGINDEX plugin)
+ { if(plugin < MAX_MIXPLUGINS) Info.dwOutputRouting = plugin + 0x80; else Info.dwOutputRouting = 0; }
+
+ void Destroy();
+};
+
+bool CreateMixPluginProc(SNDMIXPLUGIN &mixPlugin, CSoundFile &sndFile);
+
+#endif // NO_PLUGINS
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/soundlib/plugins/SymMODEcho.cpp b/Src/external_dependencies/openmpt-trunk/soundlib/plugins/SymMODEcho.cpp
new file mode 100644
index 00000000..8c2c9280
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/soundlib/plugins/SymMODEcho.cpp
@@ -0,0 +1,271 @@
+/*
+ * SymMODEcho.cpp
+ * --------------
+ * Purpose: Implementation of the SymMOD Echo DSP
+ * Notes : (currently none)
+ * Authors: OpenMPT Devs
+ * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
+ */
+
+
+#include "stdafx.h"
+
+#ifndef NO_PLUGINS
+#include "../Sndfile.h"
+#include "SymMODEcho.h"
+
+OPENMPT_NAMESPACE_BEGIN
+
+IMixPlugin *SymMODEcho::Create(VSTPluginLib &factory, CSoundFile &sndFile, SNDMIXPLUGIN *mixStruct)
+{
+ return new (std::nothrow) SymMODEcho(factory, sndFile, mixStruct);
+}
+
+
+SymMODEcho::SymMODEcho(VSTPluginLib &factory, CSoundFile &sndFile, SNDMIXPLUGIN *mixStruct)
+ : IMixPlugin(factory, sndFile, mixStruct)
+ , m_chunk(PluginChunk::Default())
+{
+ m_mixBuffer.Initialize(2, 2);
+ InsertIntoFactoryList();
+ RecalculateEchoParams();
+}
+
+
+void SymMODEcho::Process(float* pOutL, float* pOutR, uint32 numFrames)
+{
+ const float *srcL = m_mixBuffer.GetInputBuffer(0), *srcR = m_mixBuffer.GetInputBuffer(1);
+ float *outL = m_mixBuffer.GetOutputBuffer(0), *outR = m_mixBuffer.GetOutputBuffer(1);
+
+ const uint32 delayTime = m_SndFile.m_PlayState.m_nSamplesPerTick * m_chunk.param[kEchoDelay];
+ // SymMODs don't have a variable tempo so the tick duration should never change... but if someone loads a module into an MPTM file we have to account for this.
+ if(m_delayLine.size() < delayTime * 2)
+ m_delayLine.resize(delayTime * 2);
+
+ const auto dspType = GetDSPType();
+ if(dspType == DSPType::Off)
+ {
+ // Toggling the echo while it's running keeps its delay line untouched
+ std::copy(srcL, srcL + numFrames, outL);
+ std::copy(srcR, srcR + numFrames, outR);
+ } else
+ {
+ for(uint32 i = 0; i < numFrames; i++)
+ {
+ if(m_writePos >= delayTime)
+ m_writePos = 0;
+ int readPos = m_writePos - delayTime;
+ if(readPos < 0)
+ readPos += delayTime;
+
+ const float lDry = *srcL++, rDry = *srcR++;
+ const float lDelay = m_delayLine[readPos * 2], rDelay = m_delayLine[readPos * 2 + 1];
+
+ // Output samples
+ *outL++ = (lDry + lDelay);
+ *outR++ = (rDry + rDelay);
+
+ // Compute new delay line values
+ float lOut = 0.0f, rOut = 0.0f;
+ switch(dspType)
+ {
+ case DSPType::Off:
+ break;
+ case DSPType::Normal: // Normal
+ lOut = (lDelay + lDry) * m_feedback;
+ rOut = (rDelay + rDry) * m_feedback;
+ break;
+ case DSPType::Cross:
+ case DSPType::Cross2:
+ lOut = (rDelay + rDry) * m_feedback;
+ rOut = (lDelay + lDry) * m_feedback;
+ break;
+ case DSPType::Center:
+ lOut = (lDelay + (lDry + rDry) * 0.5f) * m_feedback;
+ rOut = lOut;
+ break;
+ case DSPType::NumTypes:
+ break;
+ }
+
+ // Prevent denormals
+ if(std::abs(lOut) < 1e-24f)
+ lOut = 0.0f;
+ if(std::abs(rOut) < 1e-24f)
+ rOut = 0.0f;
+
+ m_delayLine[m_writePos * 2 + 0] = lOut;
+ m_delayLine[m_writePos * 2 + 1] = rOut;
+ m_writePos++;
+ }
+ }
+
+ ProcessMixOps(pOutL, pOutR, m_mixBuffer.GetOutputBuffer(0), m_mixBuffer.GetOutputBuffer(1), numFrames);
+}
+
+
+void SymMODEcho::SaveAllParameters()
+{
+ m_pMixStruct->defaultProgram = -1;
+ try
+ {
+ const auto pluginData = mpt::as_raw_memory(m_chunk);
+ m_pMixStruct->pluginData.assign(pluginData.begin(), pluginData.end());
+ } catch(mpt::out_of_memory e)
+ {
+ mpt::delete_out_of_memory(e);
+ m_pMixStruct->pluginData.clear();
+ }
+}
+
+
+void SymMODEcho::RestoreAllParameters(int32 program)
+{
+ if(m_pMixStruct->pluginData.size() == sizeof(m_chunk) && !memcmp(m_pMixStruct->pluginData.data(), "Echo", 4))
+ {
+ std::copy(m_pMixStruct->pluginData.begin(), m_pMixStruct->pluginData.end(), mpt::as_raw_memory(m_chunk).begin());
+ } else
+ {
+ IMixPlugin::RestoreAllParameters(program);
+ }
+ RecalculateEchoParams();
+}
+
+
+PlugParamValue SymMODEcho::GetParameter(PlugParamIndex index)
+{
+ if(index < kEchoNumParameters)
+ {
+ return m_chunk.param[index] / 127.0f;
+ }
+ return 0;
+}
+
+
+void SymMODEcho::SetParameter(PlugParamIndex index, PlugParamValue value)
+{
+ if(index < kEchoNumParameters)
+ {
+ m_chunk.param[index] = mpt::saturate_round<uint8>(mpt::safe_clamp(value, 0.0f, 1.0f) * 127.0f);
+ RecalculateEchoParams();
+ }
+}
+
+
+void SymMODEcho::Resume()
+{
+ m_isResumed = true;
+ PositionChanged();
+}
+
+
+void SymMODEcho::PositionChanged()
+{
+ try
+ {
+ m_delayLine.assign(127 * 2 * m_SndFile.m_PlayState.m_nSamplesPerTick, 0.0f);
+ } catch(mpt::out_of_memory e)
+ {
+ mpt::delete_out_of_memory(e);
+ }
+ m_writePos = 0;
+}
+
+
+#ifdef MODPLUG_TRACKER
+
+std::pair<PlugParamValue, PlugParamValue> SymMODEcho::GetParamUIRange(PlugParamIndex param)
+{
+ if(param == kEchoType)
+ return {0.0f, (static_cast<uint8>(DSPType::NumTypes) - 1) / 127.0f};
+ else
+ return {0.0f, 1.0f};
+}
+
+CString SymMODEcho::GetParamName(PlugParamIndex param)
+{
+ switch (param)
+ {
+ case kEchoType: return _T("Type");
+ case kEchoDelay: return _T("Delay");
+ case kEchoFeedback: return _T("Feedback");
+ case kEchoNumParameters: break;
+ }
+ return {};
+}
+
+
+CString SymMODEcho::GetParamLabel(PlugParamIndex param)
+{
+ if(param == kEchoDelay)
+ return _T("Ticks");
+ if(param == kEchoFeedback)
+ return _T("%");
+ return {};
+}
+
+
+CString SymMODEcho::GetParamDisplay(PlugParamIndex param)
+{
+ switch(static_cast<Parameters>(param))
+ {
+ case kEchoType:
+ switch(GetDSPType())
+ {
+ case DSPType::Off: return _T("Off");
+ case DSPType::Normal: return _T("Normal");
+ case DSPType::Cross: return _T("Cross");
+ case DSPType::Cross2: return _T("Cross 2");
+ case DSPType::Center: return _T("Center");
+ case DSPType::NumTypes: break;
+ }
+ break;
+ case kEchoDelay:
+ return mpt::cfmt::val(m_chunk.param[kEchoDelay]);
+ case kEchoFeedback:
+ return mpt::cfmt::flt(m_feedback * 100.0f, 4);
+ case kEchoNumParameters:
+ break;
+ }
+ return {};
+}
+
+#endif // MODPLUG_TRACKER
+
+
+IMixPlugin::ChunkData SymMODEcho::GetChunk(bool)
+{
+ auto data = reinterpret_cast<const std::byte *>(&m_chunk);
+ return ChunkData(data, sizeof(m_chunk));
+}
+
+
+void SymMODEcho::SetChunk(const ChunkData& chunk, bool)
+{
+ auto data = chunk.data();
+ if(chunk.size() == sizeof(chunk) && !memcmp(data, "Echo", 4))
+ {
+ memcpy(&m_chunk, data, chunk.size());
+ RecalculateEchoParams();
+ }
+}
+
+
+void SymMODEcho::RecalculateEchoParams()
+{
+ if(m_chunk.param[kEchoType] >= static_cast<uint8>(DSPType::NumTypes))
+ m_chunk.param[kEchoType] = 0;
+ if(m_chunk.param[kEchoDelay] > 127)
+ m_chunk.param[kEchoDelay] = 127;
+ if(m_chunk.param[kEchoFeedback] > 127)
+ m_chunk.param[kEchoFeedback] = 127;
+
+ if(GetDSPType() == DSPType::Cross2)
+ m_feedback = 1.0f - std::pow(2.0f, -static_cast<float>(m_chunk.param[kEchoFeedback] + 1));
+ else
+ m_feedback = std::pow(2.0f, -static_cast<float>(m_chunk.param[kEchoFeedback]));
+}
+
+OPENMPT_NAMESPACE_END
+
+#endif // NO_PLUGINS
diff --git a/Src/external_dependencies/openmpt-trunk/soundlib/plugins/SymMODEcho.h b/Src/external_dependencies/openmpt-trunk/soundlib/plugins/SymMODEcho.h
new file mode 100644
index 00000000..4b54e0c8
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/soundlib/plugins/SymMODEcho.h
@@ -0,0 +1,131 @@
+/*
+ * SymMODEcho.h
+ * ------------
+ * Purpose: Implementation of the SymMOD Echo DSP
+ * Notes : (currently none)
+ * Authors: OpenMPT Devs
+ * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
+ */
+
+
+#ifndef NO_PLUGINS
+
+#include "PlugInterface.h"
+
+OPENMPT_NAMESPACE_BEGIN
+
+class SymMODEcho final : public IMixPlugin
+{
+public:
+ enum class DSPType : uint8
+ {
+ Off = 0,
+ Normal,
+ Cross,
+ Cross2,
+ Center,
+ NumTypes
+ };
+
+ enum Parameters
+ {
+ kEchoType = 0,
+ kEchoDelay,
+ kEchoFeedback,
+ kEchoNumParameters
+ };
+
+ // Our settings chunk for file I/O, as it will be written to files
+ struct PluginChunk
+ {
+ char id[4];
+ uint8 param[kEchoNumParameters];
+
+ static PluginChunk Create(uint8 type, uint8 delay, uint8 feedback)
+ {
+ static_assert(sizeof(PluginChunk) == 7);
+ PluginChunk result;
+ memcpy(result.id, "Echo", 4);
+ result.param[kEchoType] = type;
+ result.param[kEchoDelay] = delay;
+ result.param[kEchoFeedback] = feedback;
+ return result;
+ }
+ static PluginChunk Default()
+ {
+ return Create(0, 4, 1);
+ }
+ };
+
+ std::vector<float> m_delayLine;
+ uint32 m_writePos = 0; // Current write position in the delay line
+ float m_feedback = 0.5f;
+
+ // Settings chunk for file I/O
+ PluginChunk m_chunk;
+
+public:
+ static IMixPlugin* Create(VSTPluginLib& factory, CSoundFile& sndFile, SNDMIXPLUGIN* mixStruct);
+ SymMODEcho(VSTPluginLib& factory, CSoundFile& sndFile, SNDMIXPLUGIN* mixStruct);
+
+ void Release() override { delete this; }
+ void SaveAllParameters() override;
+ void RestoreAllParameters(int32 program) override;
+ int32 GetUID() const override { int32le id; memcpy(&id, "Echo", 4); return id; }
+ int32 GetVersion() const override { return 0; }
+ void Idle() override { }
+ uint32 GetLatency() const override { return 0; }
+
+ void Process(float* pOutL, float* pOutR, uint32 numFrames) override;
+
+ float RenderSilence(uint32) override { return 0.0f; }
+
+ int32 GetNumPrograms() const override { return 0; }
+ int32 GetCurrentProgram() override { return 0; }
+ void SetCurrentProgram(int32) override { }
+
+ PlugParamIndex GetNumParameters() const override { return kEchoNumParameters; }
+ PlugParamValue GetParameter(PlugParamIndex index) override;
+ void SetParameter(PlugParamIndex index, PlugParamValue value) override;
+
+ void Resume() override;
+ void Suspend() override { m_isResumed = false; }
+ void PositionChanged() override;
+
+ bool IsInstrument() const override { return false; }
+ bool CanRecieveMidiEvents() override { return false; }
+ bool ShouldProcessSilence() override { return true; }
+
+#ifdef MODPLUG_TRACKER
+ CString GetDefaultEffectName() override { return _T("Echo"); }
+
+ std::pair<PlugParamValue, PlugParamValue> GetParamUIRange(PlugParamIndex param) override;
+ CString GetParamName(PlugParamIndex param) override;
+ CString GetParamLabel(PlugParamIndex) override;
+ CString GetParamDisplay(PlugParamIndex param) override;
+
+ CString GetCurrentProgramName() override { return CString(); }
+ void SetCurrentProgramName(const CString&) override { }
+ CString GetProgramName(int32) override { return CString(); }
+
+ bool HasEditor() const override { return false; }
+#endif
+
+ int GetNumInputChannels() const override { return 2; }
+ int GetNumOutputChannels() const override { return 2; }
+
+ bool ProgramsAreChunks() const override { return true; }
+ ChunkData GetChunk(bool) override;
+ void SetChunk(const ChunkData& chunk, bool) override;
+
+protected:
+ DSPType GetDSPType() const { return static_cast<DSPType>(m_chunk.param[kEchoType]); }
+ void RecalculateEchoParams();
+};
+
+MPT_BINARY_STRUCT(SymMODEcho::PluginChunk, 7)
+
+
+OPENMPT_NAMESPACE_END
+
+#endif // NO_PLUGINS
diff --git a/Src/external_dependencies/openmpt-trunk/soundlib/plugins/dmo/Chorus.cpp b/Src/external_dependencies/openmpt-trunk/soundlib/plugins/dmo/Chorus.cpp
new file mode 100644
index 00000000..ba43b1fb
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/soundlib/plugins/dmo/Chorus.cpp
@@ -0,0 +1,306 @@
+/*
+ * Chorus.cpp
+ * ----------
+ * Purpose: Implementation of the DMO Chorus DSP (for non-Windows platforms)
+ * Notes : (currently none)
+ * Authors: OpenMPT Devs
+ * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
+ */
+
+
+#include "stdafx.h"
+
+#ifndef NO_PLUGINS
+#include "../../Sndfile.h"
+#include "Chorus.h"
+#include "mpt/base/numbers.hpp"
+#endif // !NO_PLUGINS
+
+OPENMPT_NAMESPACE_BEGIN
+
+#ifndef NO_PLUGINS
+
+namespace DMO
+{
+
+IMixPlugin* Chorus::Create(VSTPluginLib &factory, CSoundFile &sndFile, SNDMIXPLUGIN *mixStruct)
+{
+ return new (std::nothrow) Chorus(factory, sndFile, mixStruct);
+}
+
+
+Chorus::Chorus(VSTPluginLib &factory, CSoundFile &sndFile, SNDMIXPLUGIN *mixStruct, bool isFlanger)
+ : IMixPlugin(factory, sndFile, mixStruct)
+ , m_isFlanger(isFlanger)
+{
+ m_param[kChorusWetDryMix] = 0.5f;
+ m_param[kChorusDepth] = 0.1f;
+ m_param[kChorusFrequency] = 0.11f;
+ m_param[kChorusWaveShape] = 1.0f;
+ m_param[kChorusPhase] = 0.75f;
+ m_param[kChorusFeedback] = (25.0f + 99.0f) / 198.0f;
+ m_param[kChorusDelay] = 0.8f;
+
+ m_mixBuffer.Initialize(2, 2);
+ InsertIntoFactoryList();
+}
+
+
+// Integer part of buffer position
+int32 Chorus::GetBufferIntOffset(int32 fpOffset) const
+{
+ if(fpOffset < 0)
+ fpOffset += m_bufSize * 4096;
+ MPT_ASSERT(fpOffset >= 0);
+ return (fpOffset / 4096) % m_bufSize;
+}
+
+
+void Chorus::Process(float *pOutL, float *pOutR, uint32 numFrames)
+{
+ if(!m_bufSize || !m_mixBuffer.Ok())
+ return;
+
+ const float *in[2] = { m_mixBuffer.GetInputBuffer(0), m_mixBuffer.GetInputBuffer(1) };
+ float *out[2] = { m_mixBuffer.GetOutputBuffer(0), m_mixBuffer.GetOutputBuffer(1) };
+
+ const bool isTriangle = IsTriangle();
+ const float feedback = Feedback() / 100.0f;
+ const float wetDryMix = WetDryMix();
+ const uint32 phase = Phase();
+ const auto &bufferR = m_isFlanger ? m_bufferR : m_bufferL;
+
+ for(uint32 i = numFrames; i != 0; i--)
+ {
+ const float leftIn = *(in[0])++;
+ const float rightIn = *(in[1])++;
+
+ const int32 readOffset = GetBufferIntOffset(m_bufPos + m_delayOffset);
+ const int32 writeOffset = GetBufferIntOffset(m_bufPos);
+ if(m_isFlanger)
+ {
+ m_DryBufferL[m_dryWritePos] = leftIn;
+ m_DryBufferR[m_dryWritePos] = rightIn;
+ m_bufferL[writeOffset] = (m_bufferL[readOffset] * feedback) + leftIn;
+ m_bufferR[writeOffset] = (m_bufferR[readOffset] * feedback) + rightIn;
+ } else
+ {
+ m_bufferL[writeOffset] = (m_bufferL[readOffset] * feedback) + (leftIn + rightIn) * 0.5f;
+ }
+
+ float waveMin;
+ float waveMax;
+ if(isTriangle)
+ {
+ m_waveShapeMin += m_waveShapeVal;
+ m_waveShapeMax += m_waveShapeVal;
+ if(m_waveShapeMin > 1)
+ m_waveShapeMin -= 2;
+ if(m_waveShapeMax > 1)
+ m_waveShapeMax -= 2;
+ waveMin = std::abs(m_waveShapeMin) * 2 - 1;
+ waveMax = std::abs(m_waveShapeMax) * 2 - 1;
+ } else
+ {
+ m_waveShapeMin = m_waveShapeMax * m_waveShapeVal + m_waveShapeMin;
+ m_waveShapeMax = m_waveShapeMax - m_waveShapeMin * m_waveShapeVal;
+ waveMin = m_waveShapeMin;
+ waveMax = m_waveShapeMax;
+ }
+
+ const float leftDelayIn = m_isFlanger ? m_DryBufferL[(m_dryWritePos + 2) % 3] : leftIn;
+ const float rightDelayIn = m_isFlanger ? m_DryBufferR[(m_dryWritePos + 2) % 3] : rightIn;
+
+ float left1 = m_bufferL[GetBufferIntOffset(m_bufPos + m_delayL)];
+ float left2 = m_bufferL[GetBufferIntOffset(m_bufPos + m_delayL + 4096)];
+ float fracPos = (m_delayL & 0xFFF) * (1.0f / 4096.0f);
+ float leftOut = (left2 - left1) * fracPos + left1;
+ *(out[0])++ = leftDelayIn + (leftOut - leftDelayIn) * wetDryMix;
+
+ float right1 = bufferR[GetBufferIntOffset(m_bufPos + m_delayR)];
+ float right2 = bufferR[GetBufferIntOffset(m_bufPos + m_delayR + 4096)];
+ fracPos = (m_delayR & 0xFFF) * (1.0f / 4096.0f);
+ float rightOut = (right2 - right1) * fracPos + right1;
+ *(out[1])++ = rightDelayIn + (rightOut - rightDelayIn) * wetDryMix;
+
+ // Increment delay positions
+ if(m_dryWritePos <= 0)
+ m_dryWritePos += 3;
+ m_dryWritePos--;
+
+ m_delayL = m_delayOffset + (phase < 4 ? 1 : -1) * static_cast<int32>(waveMin * m_depthDelay);
+ m_delayR = m_delayOffset + (phase < 2 ? -1 : 1) * static_cast<int32>(((phase % 2u) ? waveMax : waveMin) * m_depthDelay);
+
+ if(m_bufPos <= 0)
+ m_bufPos += m_bufSize * 4096;
+ m_bufPos -= 4096;
+ }
+
+ ProcessMixOps(pOutL, pOutR, m_mixBuffer.GetOutputBuffer(0), m_mixBuffer.GetOutputBuffer(1), numFrames);
+}
+
+
+PlugParamValue Chorus::GetParameter(PlugParamIndex index)
+{
+ if(index < kChorusNumParameters)
+ {
+ return m_param[index];
+ }
+ return 0;
+}
+
+
+void Chorus::SetParameter(PlugParamIndex index, PlugParamValue value)
+{
+ if(index < kChorusNumParameters)
+ {
+ value = mpt::safe_clamp(value, 0.0f, 1.0f);
+ if(index == kChorusWaveShape)
+ {
+ value = mpt::round(value);
+ if(m_param[index] != value)
+ {
+ m_waveShapeMin = 0.0f;
+ m_waveShapeMax = 0.5f + value * 0.5f;
+ }
+ } else if(index == kChorusPhase)
+ {
+ value = mpt::round(value * 4.0f) / 4.0f;
+ }
+ m_param[index] = value;
+ RecalculateChorusParams();
+ }
+}
+
+
+void Chorus::Resume()
+{
+ PositionChanged();
+ RecalculateChorusParams();
+
+ m_isResumed = true;
+ m_waveShapeMin = 0.0f;
+ m_waveShapeMax = IsTriangle() ? 0.5f : 1.0f;
+ m_delayL = m_delayR = m_delayOffset;
+ m_bufPos = 0;
+ m_dryWritePos = 0;
+}
+
+
+void Chorus::PositionChanged()
+{
+ m_bufSize = Util::muldiv(m_SndFile.GetSampleRate(), 3840, 1000);
+ try
+ {
+ m_bufferL.assign(m_bufSize, 0.0f);
+ if(m_isFlanger)
+ m_bufferR.assign(m_bufSize, 0.0f);
+ m_DryBufferL.fill(0.0f);
+ m_DryBufferR.fill(0.0f);
+ } catch(mpt::out_of_memory e)
+ {
+ mpt::delete_out_of_memory(e);
+ m_bufSize = 0;
+ }
+}
+
+
+#ifdef MODPLUG_TRACKER
+
+CString Chorus::GetParamName(PlugParamIndex param)
+{
+ switch(param)
+ {
+ case kChorusWetDryMix: return _T("WetDryMix");
+ case kChorusDepth: return _T("Depth");
+ case kChorusFrequency: return _T("Frequency");
+ case kChorusWaveShape: return _T("WaveShape");
+ case kChorusPhase: return _T("Phase");
+ case kChorusFeedback: return _T("Feedback");
+ case kChorusDelay: return _T("Delay");
+ }
+ return CString();
+}
+
+
+CString Chorus::GetParamLabel(PlugParamIndex param)
+{
+ switch(param)
+ {
+ case kChorusWetDryMix:
+ case kChorusDepth:
+ case kChorusFeedback:
+ return _T("%");
+ case kChorusFrequency:
+ return _T("Hz");
+ case kChorusPhase:
+ return mpt::ToCString(MPT_UTF8("\xC2\xB0")); // U+00B0 DEGREE SIGN
+ case kChorusDelay:
+ return _T("ms");
+ }
+ return CString();
+}
+
+
+CString Chorus::GetParamDisplay(PlugParamIndex param)
+{
+ CString s;
+ float value = m_param[param];
+ switch(param)
+ {
+ case kChorusWetDryMix:
+ case kChorusDepth:
+ value *= 100.0f;
+ break;
+ case kChorusFrequency:
+ value = FrequencyInHertz();
+ break;
+ case kChorusWaveShape:
+ return (value < 1) ? _T("Triangle") : _T("Sine");
+ break;
+ case kChorusPhase:
+ switch(Phase())
+ {
+ case 0: return _T("-180");
+ case 1: return _T("-90");
+ case 2: return _T("0");
+ case 3: return _T("90");
+ case 4: return _T("180");
+ }
+ break;
+ case kChorusFeedback:
+ value = Feedback();
+ break;
+ case kChorusDelay:
+ value = Delay();
+ }
+ s.Format(_T("%.2f"), value);
+ return s;
+}
+
+#endif // MODPLUG_TRACKER
+
+
+void Chorus::RecalculateChorusParams()
+{
+ const float sampleRate = static_cast<float>(m_SndFile.GetSampleRate());
+
+ float delaySamples = Delay() * sampleRate / 1000.0f;
+ m_depthDelay = Depth() * delaySamples * 2048.0f;
+ m_delayOffset = mpt::saturate_round<int32>(4096.0f * (delaySamples + 2.0f));
+ m_frequency = FrequencyInHertz();
+ const float frequencySamples = m_frequency / sampleRate;
+ if(IsTriangle())
+ m_waveShapeVal = frequencySamples * 2.0f;
+ else
+ m_waveShapeVal = std::sin(frequencySamples * mpt::numbers::pi_v<float>) * 2.0f;
+}
+
+} // namespace DMO
+
+#else
+MPT_MSVC_WORKAROUND_LNK4221(Chorus)
+
+#endif // !NO_PLUGINS
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/soundlib/plugins/dmo/Chorus.h b/Src/external_dependencies/openmpt-trunk/soundlib/plugins/dmo/Chorus.h
new file mode 100644
index 00000000..62c1db6d
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/soundlib/plugins/dmo/Chorus.h
@@ -0,0 +1,122 @@
+/*
+ * Chorus.h
+ * --------
+ * Purpose: Implementation of the DMO Chorus DSP (for non-Windows platforms)
+ * Notes : (currently none)
+ * Authors: OpenMPT Devs
+ * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
+ */
+
+
+#pragma once
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#ifndef NO_PLUGINS
+
+#include "../PlugInterface.h"
+
+OPENMPT_NAMESPACE_BEGIN
+
+namespace DMO
+{
+
+class Chorus : public IMixPlugin
+{
+protected:
+ enum Parameters
+ {
+ kChorusWetDryMix = 0,
+ kChorusDepth,
+ kChorusFrequency,
+ kChorusWaveShape,
+ kChorusPhase,
+ kChorusFeedback,
+ kChorusDelay,
+ kChorusNumParameters
+ };
+
+ std::array<float, kChorusNumParameters> m_param;
+
+ // Calculated parameters
+ float m_waveShapeMin, m_waveShapeMax, m_waveShapeVal;
+ float m_depthDelay;
+ float m_frequency;
+ int32 m_delayOffset;
+ const bool m_isFlanger = false;
+
+ // State
+ std::vector<float> m_bufferL, m_bufferR; // Only m_bufferL is used in case of !m_isFlanger
+ std::array<float, 3> m_DryBufferL, m_DryBufferR;
+ int32 m_bufPos = 0, m_bufSize = 0;
+
+ int32 m_delayL = 0, m_delayR = 0;
+ int32 m_dryWritePos = 0;
+
+public:
+ static IMixPlugin* Create(VSTPluginLib &factory, CSoundFile &sndFile, SNDMIXPLUGIN *mixStruct);
+ Chorus(VSTPluginLib &factory, CSoundFile &sndFile, SNDMIXPLUGIN *mixStruct, bool stereoBuffers = false);
+
+ void Release() override { delete this; }
+ int32 GetUID() const override { return 0xEFE6629C; }
+ int32 GetVersion() const override { return 0; }
+ void Idle() override { }
+ uint32 GetLatency() const override { return 0; }
+
+ void Process(float *pOutL, float *pOutR, uint32 numFrames) override;
+
+ float RenderSilence(uint32) override { return 0.0f; }
+
+ int32 GetNumPrograms() const override { return 0; }
+ int32 GetCurrentProgram() override { return 0; }
+ void SetCurrentProgram(int32) override { }
+
+ PlugParamIndex GetNumParameters() const override { return kChorusNumParameters; }
+ PlugParamValue GetParameter(PlugParamIndex index) override;
+ void SetParameter(PlugParamIndex index, PlugParamValue value) override;
+
+ void Resume() override;
+ void Suspend() override { m_isResumed = false; }
+ void PositionChanged() override;
+ bool IsInstrument() const override { return false; }
+ bool CanRecieveMidiEvents() override { return false; }
+ bool ShouldProcessSilence() override { return true; }
+
+#ifdef MODPLUG_TRACKER
+ CString GetDefaultEffectName() override { return _T("Chorus"); }
+
+ CString GetParamName(PlugParamIndex param) override;
+ CString GetParamLabel(PlugParamIndex) override;
+ CString GetParamDisplay(PlugParamIndex param) override;
+
+ CString GetCurrentProgramName() override { return CString(); }
+ void SetCurrentProgramName(const CString &) override { }
+ CString GetProgramName(int32) override { return CString(); }
+
+ bool HasEditor() const override { return false; }
+#endif
+
+ void BeginSetProgram(int32) override { }
+ void EndSetProgram() override { }
+
+ int GetNumInputChannels() const override { return 2; }
+ int GetNumOutputChannels() const override { return 2; }
+
+protected:
+ int32 GetBufferIntOffset(int32 fpOffset) const;
+
+ virtual float WetDryMix() const { return m_param[kChorusWetDryMix]; }
+ virtual bool IsTriangle() const { return m_param[kChorusWaveShape] < 1; }
+ virtual float Depth() const { return m_param[kChorusDepth]; }
+ virtual float Feedback() const { return -99.0f + m_param[kChorusFeedback] * 198.0f; }
+ virtual float Delay() const { return m_param[kChorusDelay] * 20.0f; }
+ virtual float FrequencyInHertz() const { return m_param[kChorusFrequency] * 10.0f; }
+ virtual int Phase() const { return mpt::saturate_round<uint32>(m_param[kChorusPhase] * 4.0f); }
+ void RecalculateChorusParams();
+};
+
+} // namespace DMO
+
+OPENMPT_NAMESPACE_END
+
+#endif // !NO_PLUGINS
diff --git a/Src/external_dependencies/openmpt-trunk/soundlib/plugins/dmo/Compressor.cpp b/Src/external_dependencies/openmpt-trunk/soundlib/plugins/dmo/Compressor.cpp
new file mode 100644
index 00000000..97d5cbcf
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/soundlib/plugins/dmo/Compressor.cpp
@@ -0,0 +1,238 @@
+/*
+ * Compressor.cpp
+ * ---------------
+ * Purpose: Implementation of the DMO Compressor DSP (for non-Windows platforms)
+ * Notes : The original plugin's integer and floating point code paths only
+ * behave identically when feeding floating point numbers in range
+ * [-32768, +32768] rather than the usual [-1, +1] into the plugin.
+ * Authors: OpenMPT Devs
+ * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
+ */
+
+
+#include "stdafx.h"
+
+#ifndef NO_PLUGINS
+#include "../../Sndfile.h"
+#include "Compressor.h"
+#include "DMOUtils.h"
+#include "mpt/base/numbers.hpp"
+#endif // !NO_PLUGINS
+
+OPENMPT_NAMESPACE_BEGIN
+
+#ifndef NO_PLUGINS
+
+namespace DMO
+{
+
+
+IMixPlugin* Compressor::Create(VSTPluginLib &factory, CSoundFile &sndFile, SNDMIXPLUGIN *mixStruct)
+{
+ return new (std::nothrow) Compressor(factory, sndFile, mixStruct);
+}
+
+
+Compressor::Compressor(VSTPluginLib &factory, CSoundFile &sndFile, SNDMIXPLUGIN *mixStruct)
+ : IMixPlugin(factory, sndFile, mixStruct)
+{
+ m_param[kCompGain] = 0.5f;
+ m_param[kCompAttack] = 0.02f;
+ m_param[kCompRelease] = 150.0f / 2950.0f;
+ m_param[kCompThreshold] = 2.0f / 3.0f;
+ m_param[kCompRatio] = 0.02f;
+ m_param[kCompPredelay] = 1.0f;
+
+ m_mixBuffer.Initialize(2, 2);
+ InsertIntoFactoryList();
+}
+
+
+void Compressor::Process(float *pOutL, float *pOutR, uint32 numFrames)
+{
+ if(!m_bufSize || !m_mixBuffer.Ok())
+ return;
+
+ const float *in[2] = { m_mixBuffer.GetInputBuffer(0), m_mixBuffer.GetInputBuffer(1) };
+ float *out[2] = { m_mixBuffer.GetOutputBuffer(0), m_mixBuffer.GetOutputBuffer(1) };
+
+ for(uint32 i = numFrames; i != 0; i--)
+ {
+ float leftIn = *(in[0])++;
+ float rightIn = *(in[1])++;
+
+ m_buffer[m_bufPos * 2] = leftIn;
+ m_buffer[m_bufPos * 2 + 1] = rightIn;
+
+ leftIn = std::abs(leftIn);
+ rightIn = std::abs(rightIn);
+
+ float mono = (leftIn + rightIn) * (0.5f * 32768.0f * 32768.0f);
+ float monoLog = std::abs(logGain(mono, 31, 5)) * (1.0f / float(1u << 31));
+
+ float newPeak = monoLog + (m_peak - monoLog) * ((m_peak <= monoLog) ? m_attack : m_release);
+ m_peak = newPeak;
+
+ if(newPeak < m_threshold)
+ newPeak = m_threshold;
+
+ float compGain = (m_threshold - newPeak) * m_ratio + 0.9999999f;
+
+ // Computes 2 ^ (2 ^ (log2(x) - 26) - 1) (x = 0...2^31)
+ uint32 compGainInt = static_cast<uint32>(compGain * 2147483648.0f);
+ uint32 compGainPow = compGainInt << 5;
+ compGainInt >>= 26;
+ if(compGainInt) // compGainInt >= 2^26
+ {
+ compGainPow |= 0x80000000u;
+ compGainInt--;
+ }
+ compGainPow >>= (31 - compGainInt);
+
+ int32 readOffset = m_predelay + m_bufPos * 4096 + m_bufSize - 1;
+ readOffset /= 4096;
+ readOffset %= m_bufSize;
+
+ float outGain = (compGainPow * (1.0f / 2147483648.0f)) * m_gain;
+ *(out[0])++ = m_buffer[readOffset * 2] * outGain;
+ *(out[1])++ = m_buffer[readOffset * 2 + 1] * outGain;
+
+ if(m_bufPos-- == 0)
+ m_bufPos += m_bufSize;
+ }
+
+ ProcessMixOps(pOutL, pOutR, m_mixBuffer.GetOutputBuffer(0), m_mixBuffer.GetOutputBuffer(1), numFrames);
+}
+
+
+PlugParamValue Compressor::GetParameter(PlugParamIndex index)
+{
+ if(index < kCompNumParameters)
+ {
+ return m_param[index];
+ }
+ return 0;
+}
+
+
+void Compressor::SetParameter(PlugParamIndex index, PlugParamValue value)
+{
+ if(index < kCompNumParameters)
+ {
+ value = mpt::safe_clamp(value, 0.0f, 1.0f);
+ m_param[index] = value;
+ RecalculateCompressorParams();
+ }
+}
+
+
+void Compressor::Resume()
+{
+ m_isResumed = true;
+ PositionChanged();
+ RecalculateCompressorParams();
+}
+
+
+void Compressor::PositionChanged()
+{
+ m_bufSize = Util::muldiv(m_SndFile.GetSampleRate(), 200, 1000);
+ try
+ {
+ m_buffer.assign(m_bufSize * 2, 0.0f);
+ } catch(mpt::out_of_memory e)
+ {
+ mpt::delete_out_of_memory(e);
+ m_bufSize = 0;
+ }
+ m_bufPos = 0;
+ m_peak = 0.0f;
+}
+
+
+#ifdef MODPLUG_TRACKER
+
+CString Compressor::GetParamName(PlugParamIndex param)
+{
+ switch(param)
+ {
+ case kCompGain: return _T("Gain");
+ case kCompAttack: return _T("Attack");
+ case kCompRelease: return _T("Release");
+ case kCompThreshold: return _T("Threshold");
+ case kCompRatio: return _T("Ratio");
+ case kCompPredelay: return _T("Predelay");
+ }
+ return CString();
+}
+
+
+CString Compressor::GetParamLabel(PlugParamIndex param)
+{
+ switch(param)
+ {
+ case kCompGain:
+ case kCompThreshold:
+ return _T("dB");
+ case kCompAttack:
+ case kCompRelease:
+ case kCompPredelay:
+ return _T("ms");
+ }
+ return CString();
+}
+
+
+CString Compressor::GetParamDisplay(PlugParamIndex param)
+{
+ float value = m_param[param];
+ switch(param)
+ {
+ case kCompGain:
+ value = GainInDecibel();
+ break;
+ case kCompAttack:
+ value = AttackTime();
+ break;
+ case kCompRelease:
+ value = ReleaseTime();
+ break;
+ case kCompThreshold:
+ value = ThresholdInDecibel();
+ break;
+ case kCompRatio:
+ value = CompressorRatio();
+ break;
+ case kCompPredelay:
+ value = PreDelay();
+ break;
+ }
+ CString s;
+ s.Format(_T("%.2f"), value);
+ return s;
+}
+
+#endif // MODPLUG_TRACKER
+
+
+void Compressor::RecalculateCompressorParams()
+{
+ const float sampleRate = m_SndFile.GetSampleRate() / 1000.0f;
+ m_gain = std::pow(10.0f, GainInDecibel() / 20.0f);
+ m_attack = std::pow(10.0f, -1.0f / (AttackTime() * sampleRate));
+ m_release = std::pow(10.0f, -1.0f / (ReleaseTime() * sampleRate));
+ const float _2e31 = float(1u << 31);
+ const float _2e26 = float(1u << 26);
+ m_threshold = std::min((_2e31 - 1.0f), (std::log(std::pow(10.0f, ThresholdInDecibel() / 20.0f) * _2e31) * _2e26) / mpt::numbers::ln2_v<float> + _2e26) * (1.0f / _2e31);
+ m_ratio = 1.0f - (1.0f / CompressorRatio());
+ m_predelay = static_cast<int32>((PreDelay() * sampleRate) + 2.0f);
+}
+
+} // namespace DMO
+
+#else
+MPT_MSVC_WORKAROUND_LNK4221(Compressor)
+
+#endif // !NO_PLUGINS
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/soundlib/plugins/dmo/Compressor.h b/Src/external_dependencies/openmpt-trunk/soundlib/plugins/dmo/Compressor.h
new file mode 100644
index 00000000..7fc60f5a
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/soundlib/plugins/dmo/Compressor.h
@@ -0,0 +1,109 @@
+/*
+ * Compressor.h
+ * -------------
+ * Purpose: Implementation of the DMO Compressor DSP (for non-Windows platforms)
+ * Notes : (currently none)
+ * Authors: OpenMPT Devs
+ * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
+ */
+
+
+#ifndef NO_PLUGINS
+
+#include "../PlugInterface.h"
+
+OPENMPT_NAMESPACE_BEGIN
+
+namespace DMO
+{
+
+class Compressor final : public IMixPlugin
+{
+protected:
+ enum Parameters
+ {
+ kCompGain = 0,
+ kCompAttack,
+ kCompRelease,
+ kCompThreshold,
+ kCompRatio,
+ kCompPredelay,
+ kCompNumParameters
+ };
+
+ std::array<float, kCompNumParameters> m_param;
+
+ // Calculated parameters and coefficients
+ float m_gain;
+ float m_attack;
+ float m_release;
+ float m_threshold;
+ float m_ratio;
+ int32 m_predelay;
+
+ // State
+ std::vector<float> m_buffer;
+ int32 m_bufPos, m_bufSize;
+ float m_peak;
+
+public:
+ static IMixPlugin* Create(VSTPluginLib &factory, CSoundFile &sndFile, SNDMIXPLUGIN *mixStruct);
+ Compressor(VSTPluginLib &factory, CSoundFile &sndFile, SNDMIXPLUGIN *mixStruct);
+
+ void Release() override { delete this; }
+ int32 GetUID() const override { return 0xEF011F79; }
+ int32 GetVersion() const override { return 0; }
+ void Idle() override { }
+ uint32 GetLatency() const override { return 0; }
+
+ void Process(float *pOutL, float *pOutR, uint32 numFrames) override;
+
+ float RenderSilence(uint32) override { return 0.0f; }
+
+ int32 GetNumPrograms() const override { return 0; }
+ int32 GetCurrentProgram() override { return 0; }
+ void SetCurrentProgram(int32) override { }
+
+ PlugParamIndex GetNumParameters() const override { return kCompNumParameters; }
+ PlugParamValue GetParameter(PlugParamIndex index) override;
+ void SetParameter(PlugParamIndex index, PlugParamValue value) override;
+
+ void Resume() override;
+ void Suspend() override { m_isResumed = false; }
+ void PositionChanged() override;
+ bool IsInstrument() const override { return false; }
+ bool CanRecieveMidiEvents() override { return false; }
+ bool ShouldProcessSilence() override { return true; }
+
+#ifdef MODPLUG_TRACKER
+ CString GetDefaultEffectName() override { return _T("Compressor"); }
+
+ CString GetParamName(PlugParamIndex param) override;
+ CString GetParamLabel(PlugParamIndex) override;
+ CString GetParamDisplay(PlugParamIndex param) override;
+
+ CString GetCurrentProgramName() override { return CString(); }
+ void SetCurrentProgramName(const CString &) override { }
+ CString GetProgramName(int32) override { return CString(); }
+
+ bool HasEditor() const override { return false; }
+#endif
+
+ int GetNumInputChannels() const override { return 2; }
+ int GetNumOutputChannels() const override { return 2; }
+
+protected:
+ float GainInDecibel() const { return -60.0f + m_param[kCompGain] * 120.0f; }
+ float AttackTime() const { return 0.01f + m_param[kCompAttack] * 499.99f; }
+ float ReleaseTime() const { return 50.0f + m_param[kCompRelease] * 2950.0f; }
+ float ThresholdInDecibel() const { return -60.0f + m_param[kCompThreshold] * 60.0f; }
+ float CompressorRatio() const { return 1.0f + m_param[kCompRatio] * 99.0f; }
+ float PreDelay() const { return m_param[kCompPredelay] * 4.0f; }
+ void RecalculateCompressorParams();
+};
+
+} // namespace DMO
+
+OPENMPT_NAMESPACE_END
+
+#endif // !NO_PLUGINS
diff --git a/Src/external_dependencies/openmpt-trunk/soundlib/plugins/dmo/DMOPlugin.cpp b/Src/external_dependencies/openmpt-trunk/soundlib/plugins/dmo/DMOPlugin.cpp
new file mode 100644
index 00000000..bb7df181
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/soundlib/plugins/dmo/DMOPlugin.cpp
@@ -0,0 +1,431 @@
+/*
+ * DMOPlugin.h
+ * -----------
+ * Purpose: DirectX Media Object plugin handling / processing.
+ * Notes : Some default plugins only have the same output characteristics in the floating point code path (compared to integer PCM)
+ * if we feed them input in the range [-32768, +32768] rather than the more usual [-1, +1].
+ * Hence, OpenMPT uses this range for both the floating-point and integer path.
+ * Authors: OpenMPT Devs
+ * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
+ */
+
+
+#include "stdafx.h"
+
+#include "mpt/base/aligned_array.hpp"
+#if defined(MPT_WITH_DMO)
+#include "mpt/uuid/guid.hpp"
+#include "../../Sndfile.h"
+#include "DMOPlugin.h"
+#include "../PluginManager.h"
+#include <uuids.h>
+#include <medparam.h>
+#include <mmsystem.h>
+#endif // MPT_WITH_DMO
+
+OPENMPT_NAMESPACE_BEGIN
+
+
+#if defined(MPT_WITH_DMO)
+
+
+#ifdef MPT_ALL_LOGGING
+#define DMO_LOG
+#else
+#define DMO_LOG
+#endif
+
+
+IMixPlugin* DMOPlugin::Create(VSTPluginLib &factory, CSoundFile &sndFile, SNDMIXPLUGIN *mixStruct)
+{
+ CLSID clsid;
+ if(mpt::VerifyStringToCLSID(factory.dllPath.AsNative(), clsid))
+ {
+ IMediaObject *pMO = nullptr;
+ IMediaObjectInPlace *pMOIP = nullptr;
+ if ((CoCreateInstance(clsid, nullptr, CLSCTX_INPROC_SERVER, IID_IMediaObject, (VOID **)&pMO) == S_OK) && (pMO))
+ {
+ if (pMO->QueryInterface(IID_IMediaObjectInPlace, (void **)&pMOIP) != S_OK) pMOIP = nullptr;
+ } else pMO = nullptr;
+ if ((pMO) && (pMOIP))
+ {
+ DWORD dwInputs = 0, dwOutputs = 0;
+ pMO->GetStreamCount(&dwInputs, &dwOutputs);
+ if (dwInputs == 1 && dwOutputs == 1)
+ {
+ DMOPlugin *p = new (std::nothrow) DMOPlugin(factory, sndFile, mixStruct, pMO, pMOIP, clsid.Data1);
+ return p;
+ }
+#ifdef DMO_LOG
+ MPT_LOG_GLOBAL(LogDebug, "DMO", factory.libraryName.ToUnicode() + U_(": Unable to use this DMO"));
+#endif
+ }
+#ifdef DMO_LOG
+ else MPT_LOG_GLOBAL(LogDebug, "DMO", factory.libraryName.ToUnicode() + U_(": Failed to get IMediaObject & IMediaObjectInPlace interfaces"));
+#endif
+ if (pMO) pMO->Release();
+ if (pMOIP) pMOIP->Release();
+ }
+ return nullptr;
+}
+
+
+DMOPlugin::DMOPlugin(VSTPluginLib &factory, CSoundFile &sndFile, SNDMIXPLUGIN *mixStruct, IMediaObject *pMO, IMediaObjectInPlace *pMOIP, uint32 uid)
+ : IMixPlugin(factory, sndFile, mixStruct)
+ , m_pMediaObject(pMO)
+ , m_pMediaProcess(pMOIP)
+ , m_pParamInfo(nullptr)
+ , m_pMediaParams(nullptr)
+ , m_nSamplesPerSec(sndFile.GetSampleRate())
+ , m_uid(uid)
+{
+ if(FAILED(m_pMediaObject->QueryInterface(IID_IMediaParamInfo, (void **)&m_pParamInfo)))
+ m_pParamInfo = nullptr;
+ if (FAILED(m_pMediaObject->QueryInterface(IID_IMediaParams, (void **)&m_pMediaParams)))
+ m_pMediaParams = nullptr;
+ m_alignedBuffer.f32 = mpt::align_bytes<16, MIXBUFFERSIZE * 2>(m_interleavedBuffer.f32);
+ m_mixBuffer.Initialize(2, 2);
+ InsertIntoFactoryList();
+}
+
+
+DMOPlugin::~DMOPlugin()
+{
+ if(m_pMediaParams)
+ {
+ m_pMediaParams->Release();
+ m_pMediaParams = nullptr;
+ }
+ if(m_pParamInfo)
+ {
+ m_pParamInfo->Release();
+ m_pParamInfo = nullptr;
+ }
+ if(m_pMediaProcess)
+ {
+ m_pMediaProcess->Release();
+ m_pMediaProcess = nullptr;
+ }
+ if(m_pMediaObject)
+ {
+ m_pMediaObject->Release();
+ m_pMediaObject = nullptr;
+ }
+}
+
+
+uint32 DMOPlugin::GetLatency() const
+{
+ REFERENCE_TIME time; // Unit 100-nanoseconds
+ if(m_pMediaProcess->GetLatency(&time) == S_OK)
+ {
+ return static_cast<uint32>(time * m_nSamplesPerSec / (10 * 1000 * 1000));
+ }
+ return 0;
+}
+
+
+static constexpr float _f2si = 32768.0f;
+static constexpr float _si2f = 1.0f / 32768.0f;
+
+
+static void InterleaveStereo(const float * MPT_RESTRICT inputL, const float * MPT_RESTRICT inputR, float * MPT_RESTRICT output, uint32 numFrames)
+{
+ while(numFrames--)
+ {
+ *(output++) = *(inputL++) * _f2si;
+ *(output++) = *(inputR++) * _f2si;
+ }
+}
+
+
+static void DeinterleaveStereo(const float * MPT_RESTRICT input, float * MPT_RESTRICT outputL, float * MPT_RESTRICT outputR, uint32 numFrames)
+{
+ while(numFrames--)
+ {
+ *(outputL++) = *(input++) * _si2f;
+ *(outputR++) = *(input++) * _si2f;
+ }
+}
+
+
+// Interleave two float streams into one int16 stereo stream.
+static void InterleaveFloatToInt16(const float * MPT_RESTRICT inputL, const float * MPT_RESTRICT inputR, int16 * MPT_RESTRICT output, uint32 numFrames)
+{
+ while(numFrames--)
+ {
+ *(output++) = static_cast<int16>(Clamp(*(inputL++) * _f2si, static_cast<float>(int16_min), static_cast<float>(int16_max)));
+ *(output++) = static_cast<int16>(Clamp(*(inputR++) * _f2si, static_cast<float>(int16_min), static_cast<float>(int16_max)));
+ }
+}
+
+
+// Deinterleave an int16 stereo stream into two float streams.
+static void DeinterleaveInt16ToFloat(const int16 * MPT_RESTRICT input, float * MPT_RESTRICT outputL, float * MPT_RESTRICT outputR, uint32 numFrames)
+{
+ while(numFrames--)
+ {
+ *outputL++ += _si2f * static_cast<float>(*input++);
+ *outputR++ += _si2f * static_cast<float>(*input++);
+ }
+}
+
+
+void DMOPlugin::Process(float *pOutL, float *pOutR, uint32 numFrames)
+{
+ if(!numFrames || !m_mixBuffer.Ok())
+ return;
+ m_mixBuffer.ClearOutputBuffers(numFrames);
+ REFERENCE_TIME startTime = Util::muldiv(m_SndFile.GetTotalSampleCount(), 10000000, m_nSamplesPerSec);
+
+ if(m_useFloat)
+ {
+ InterleaveStereo(m_mixBuffer.GetInputBuffer(0), m_mixBuffer.GetInputBuffer(1), m_alignedBuffer.f32, numFrames);
+ m_pMediaProcess->Process(numFrames * 2 * sizeof(float), reinterpret_cast<BYTE *>(m_alignedBuffer.f32), startTime, DMO_INPLACE_NORMAL);
+ DeinterleaveStereo(m_alignedBuffer.f32, m_mixBuffer.GetOutputBuffer(0), m_mixBuffer.GetOutputBuffer(1), numFrames);
+ } else
+ {
+ InterleaveFloatToInt16(m_mixBuffer.GetInputBuffer(0), m_mixBuffer.GetInputBuffer(1), m_alignedBuffer.i16, numFrames);
+ m_pMediaProcess->Process(numFrames * 2 * sizeof(int16), reinterpret_cast<BYTE *>(m_alignedBuffer.i16), startTime, DMO_INPLACE_NORMAL);
+ DeinterleaveInt16ToFloat(m_alignedBuffer.i16, m_mixBuffer.GetOutputBuffer(0), m_mixBuffer.GetOutputBuffer(1), numFrames);
+ }
+
+ ProcessMixOps(pOutL, pOutR, m_mixBuffer.GetOutputBuffer(0), m_mixBuffer.GetOutputBuffer(1), numFrames);
+}
+
+
+PlugParamIndex DMOPlugin::GetNumParameters() const
+{
+ DWORD dwParamCount = 0;
+ m_pParamInfo->GetParamCount(&dwParamCount);
+ return dwParamCount;
+}
+
+
+PlugParamValue DMOPlugin::GetParameter(PlugParamIndex index)
+{
+ if(index < GetNumParameters() && m_pParamInfo != nullptr && m_pMediaParams != nullptr)
+ {
+ MP_PARAMINFO mpi;
+ MP_DATA md;
+
+ MemsetZero(mpi);
+ md = 0;
+ if (m_pParamInfo->GetParamInfo(index, &mpi) == S_OK
+ && m_pMediaParams->GetParam(index, &md) == S_OK)
+ {
+ float fValue, fMin, fMax, fDefault;
+
+ fValue = md;
+ fMin = mpi.mpdMinValue;
+ fMax = mpi.mpdMaxValue;
+ fDefault = mpi.mpdNeutralValue;
+ if (mpi.mpType == MPT_BOOL)
+ {
+ fMin = 0;
+ fMax = 1;
+ }
+ fValue -= fMin;
+ if (fMax > fMin) fValue /= (fMax - fMin);
+ return fValue;
+ }
+ }
+ return 0;
+}
+
+
+void DMOPlugin::SetParameter(PlugParamIndex index, PlugParamValue value)
+{
+ if(index < GetNumParameters() && m_pParamInfo != nullptr && m_pMediaParams != nullptr)
+ {
+ MP_PARAMINFO mpi;
+ MemsetZero(mpi);
+ if (m_pParamInfo->GetParamInfo(index, &mpi) == S_OK)
+ {
+ float fMin = mpi.mpdMinValue;
+ float fMax = mpi.mpdMaxValue;
+
+ if (mpi.mpType == MPT_BOOL)
+ {
+ fMin = 0;
+ fMax = 1;
+ value = (value > 0.5f) ? 1.0f : 0.0f;
+ }
+ if (fMax > fMin) value *= (fMax - fMin);
+ value += fMin;
+ value = mpt::safe_clamp(value, fMin, fMax);
+ if (mpi.mpType != MPT_FLOAT) value = mpt::round(value);
+ m_pMediaParams->SetParam(index, value);
+ }
+ }
+}
+
+
+void DMOPlugin::Resume()
+{
+ m_nSamplesPerSec = m_SndFile.GetSampleRate();
+ m_isResumed = true;
+
+ DMO_MEDIA_TYPE mt;
+ WAVEFORMATEX wfx;
+
+ mt.majortype = MEDIATYPE_Audio;
+ mt.subtype = MEDIASUBTYPE_PCM;
+ mt.bFixedSizeSamples = TRUE;
+ mt.bTemporalCompression = FALSE;
+ mt.formattype = FORMAT_WaveFormatEx;
+ mt.pUnk = nullptr;
+ mt.pbFormat = (LPBYTE)&wfx;
+ mt.cbFormat = sizeof(WAVEFORMATEX);
+ mt.lSampleSize = 2 * sizeof(float);
+ wfx.wFormatTag = 3; // WAVE_FORMAT_IEEE_FLOAT;
+ wfx.nChannels = 2;
+ wfx.nSamplesPerSec = m_nSamplesPerSec;
+ wfx.wBitsPerSample = sizeof(float) * 8;
+ wfx.nBlockAlign = wfx.nChannels * (wfx.wBitsPerSample / 8);
+ wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign;
+ wfx.cbSize = 0;
+
+ // First try 32-bit float (DirectX 9+)
+ m_useFloat = true;
+ if(FAILED(m_pMediaObject->SetInputType(0, &mt, 0))
+ || FAILED(m_pMediaObject->SetOutputType(0, &mt, 0)))
+ {
+ m_useFloat = false;
+ // Try again with 16-bit PCM
+ mt.lSampleSize = 2 * sizeof(int16);
+ wfx.wFormatTag = WAVE_FORMAT_PCM;
+ wfx.wBitsPerSample = sizeof(int16) * 8;
+ wfx.nBlockAlign = wfx.nChannels * (wfx.wBitsPerSample / 8);
+ wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign;
+ if(FAILED(m_pMediaObject->SetInputType(0, &mt, 0))
+ || FAILED(m_pMediaObject->SetOutputType(0, &mt, 0)))
+ {
+#ifdef DMO_LOG
+ MPT_LOG_GLOBAL(LogDebug, "DMO", U_("DMO: Failed to set I/O media type"));
+#endif
+ }
+ }
+}
+
+
+void DMOPlugin::PositionChanged()
+{
+ m_pMediaObject->Discontinuity(0);
+ m_pMediaObject->Flush();
+}
+
+
+void DMOPlugin::Suspend()
+{
+ m_isResumed = false;
+ m_pMediaObject->Flush();
+ m_pMediaObject->SetInputType(0, nullptr, DMO_SET_TYPEF_CLEAR);
+ m_pMediaObject->SetOutputType(0, nullptr, DMO_SET_TYPEF_CLEAR);
+}
+
+
+#ifdef MODPLUG_TRACKER
+
+CString DMOPlugin::GetParamName(PlugParamIndex param)
+{
+ if(param < GetNumParameters() && m_pParamInfo != nullptr)
+ {
+ MP_PARAMINFO mpi;
+ mpi.mpType = MPT_INT;
+ mpi.szUnitText[0] = 0;
+ mpi.szLabel[0] = 0;
+ if(m_pParamInfo->GetParamInfo(param, &mpi) == S_OK)
+ {
+ return mpt::ToCString(mpi.szLabel);
+ }
+ }
+ return CString();
+
+}
+
+
+CString DMOPlugin::GetParamLabel(PlugParamIndex param)
+{
+ if(param < GetNumParameters() && m_pParamInfo != nullptr)
+ {
+ MP_PARAMINFO mpi;
+ mpi.mpType = MPT_INT;
+ mpi.szUnitText[0] = 0;
+ mpi.szLabel[0] = 0;
+ if(m_pParamInfo->GetParamInfo(param, &mpi) == S_OK)
+ {
+ return mpt::ToCString(mpi.szUnitText);
+ }
+ }
+ return CString();
+}
+
+
+CString DMOPlugin::GetParamDisplay(PlugParamIndex param)
+{
+ if(param < GetNumParameters() && m_pParamInfo != nullptr && m_pMediaParams != nullptr)
+ {
+ MP_PARAMINFO mpi;
+ mpi.mpType = MPT_INT;
+ mpi.szUnitText[0] = 0;
+ mpi.szLabel[0] = 0;
+ if (m_pParamInfo->GetParamInfo(param, &mpi) == S_OK)
+ {
+ MP_DATA md;
+ if(m_pMediaParams->GetParam(param, &md) == S_OK)
+ {
+ switch(mpi.mpType)
+ {
+ case MPT_FLOAT:
+ {
+ CString s;
+ s.Format(_T("%.2f"), md);
+ return s;
+ }
+ break;
+
+ case MPT_BOOL:
+ return ((int)md) ? _T("Yes") : _T("No");
+ break;
+
+ case MPT_ENUM:
+ {
+ WCHAR *text = nullptr;
+ m_pParamInfo->GetParamText(param, &text);
+
+ const int nValue = mpt::saturate_round<int>(md * (mpi.mpdMaxValue - mpi.mpdMinValue));
+ // Always skip first two strings (param name, unit name)
+ for(int i = 0; i < nValue + 2; i++)
+ {
+ text += wcslen(text) + 1;
+ }
+ return mpt::ToCString(text);
+ }
+ break;
+
+ case MPT_INT:
+ default:
+ {
+ CString s;
+ s.Format(_T("%d"), mpt::saturate_round<int>(md));
+ return s;
+ }
+ break;
+ }
+ }
+ }
+ }
+ return CString();
+}
+
+#endif // MODPLUG_TRACKER
+
+#else // !MPT_WITH_DMO
+
+MPT_MSVC_WORKAROUND_LNK4221(DMOPlugin)
+
+#endif // MPT_WITH_DMO
+
+OPENMPT_NAMESPACE_END
+
diff --git a/Src/external_dependencies/openmpt-trunk/soundlib/plugins/dmo/DMOPlugin.h b/Src/external_dependencies/openmpt-trunk/soundlib/plugins/dmo/DMOPlugin.h
new file mode 100644
index 00000000..9dabc877
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/soundlib/plugins/dmo/DMOPlugin.h
@@ -0,0 +1,100 @@
+/*
+ * DMOPlugin.h
+ * -----------
+ * Purpose: DirectX Media Object plugin handling / processing.
+ * Notes : (currently none)
+ * Authors: OpenMPT Devs
+ * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
+ */
+
+
+#if defined(MPT_WITH_DMO)
+
+#include "../PlugInterface.h"
+#include <dmoreg.h>
+#include <strmif.h>
+
+typedef interface IMediaObject IMediaObject;
+typedef interface IMediaObjectInPlace IMediaObjectInPlace;
+typedef interface IMediaParamInfo IMediaParamInfo;
+typedef interface IMediaParams IMediaParams;
+
+OPENMPT_NAMESPACE_BEGIN
+
+class DMOPlugin final : public IMixPlugin
+{
+protected:
+ IMediaObject *m_pMediaObject;
+ IMediaObjectInPlace *m_pMediaProcess;
+ IMediaParamInfo *m_pParamInfo;
+ IMediaParams *m_pMediaParams;
+
+ uint32 m_nSamplesPerSec;
+ const uint32 m_uid;
+ union
+ {
+ int16 *i16;
+ float *f32;
+ } m_alignedBuffer;
+ union
+ {
+ int16 i16[MIXBUFFERSIZE * 2 + 16]; // 16-bit PCM Stereo interleaved
+ float f32[MIXBUFFERSIZE * 2 + 16]; // 32-bit Float Stereo interleaved
+ } m_interleavedBuffer;
+ bool m_useFloat;
+
+public:
+ static IMixPlugin* Create(VSTPluginLib &factory, CSoundFile &sndFile, SNDMIXPLUGIN *mixStruct);
+
+protected:
+ DMOPlugin(VSTPluginLib &factory, CSoundFile &sndFile, SNDMIXPLUGIN *mixStruct, IMediaObject *pMO, IMediaObjectInPlace *pMOIP, uint32 uid);
+ ~DMOPlugin();
+
+public:
+ void Release() override { delete this; }
+ int32 GetUID() const override { return m_uid; }
+ int32 GetVersion() const override { return 2; }
+ void Idle() override { }
+ uint32 GetLatency() const override;
+
+ void Process(float *pOutL, float *pOutR, uint32 numFrames) override;
+
+ int32 GetNumPrograms() const override { return 0; }
+ int32 GetCurrentProgram() override { return 0; }
+ void SetCurrentProgram(int32 /*nIndex*/) override { }
+
+ PlugParamIndex GetNumParameters() const override;
+ PlugParamValue GetParameter(PlugParamIndex index) override;
+ void SetParameter(PlugParamIndex index, PlugParamValue value) override;
+
+ void Resume() override;
+ void Suspend() override;
+ void PositionChanged() override;
+
+ bool IsInstrument() const override { return false; }
+ bool CanRecieveMidiEvents() override { return false; }
+ bool ShouldProcessSilence() override { return true; }
+
+#ifdef MODPLUG_TRACKER
+ CString GetDefaultEffectName() override { return CString(); }
+
+ CString GetParamName(PlugParamIndex param) override;
+ CString GetParamLabel(PlugParamIndex param) override;
+ CString GetParamDisplay(PlugParamIndex param) override;
+
+ // TODO we could simply add our own preset mechanism. But is it really useful for these plugins?
+ CString GetCurrentProgramName() override { return CString(); }
+ void SetCurrentProgramName(const CString &) override { }
+ CString GetProgramName(int32) override { return CString(); }
+
+ bool HasEditor() const override { return false; }
+#endif
+
+ int GetNumInputChannels() const override { return 2; }
+ int GetNumOutputChannels() const override { return 2; }
+};
+
+OPENMPT_NAMESPACE_END
+
+#endif // MPT_WITH_DMO
+
diff --git a/Src/external_dependencies/openmpt-trunk/soundlib/plugins/dmo/DMOUtils.cpp b/Src/external_dependencies/openmpt-trunk/soundlib/plugins/dmo/DMOUtils.cpp
new file mode 100644
index 00000000..2ad32f6b
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/soundlib/plugins/dmo/DMOUtils.cpp
@@ -0,0 +1,59 @@
+/*
+ * DMOUtils.cpp
+ * ------------
+ * Purpose: Utility functions shared by DMO plugins
+ * Notes : none
+ * Authors: OpenMPT Devs
+ * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
+ */
+
+
+#include "stdafx.h"
+
+#include "DMOUtils.h"
+
+#ifndef NO_PLUGINS
+#include "../../Sndfile.h"
+#endif // !NO_PLUGINS
+
+OPENMPT_NAMESPACE_BEGIN
+
+#ifndef NO_PLUGINS
+
+namespace DMO
+{
+
+// Computes (log2(x) + 1) * 2 ^ (shiftL - shiftR) (x = -2^31...2^31)
+float logGain(float x, int32 shiftL, int32 shiftR)
+{
+ uint32 intSample = static_cast<uint32>(static_cast<int64>(x));
+ const uint32 sign = intSample & 0x80000000;
+ if(sign)
+ intSample = (~intSample) + 1;
+
+ // Multiply until overflow (or edge shift factor is reached)
+ while(shiftL > 0 && intSample < 0x80000000)
+ {
+ intSample += intSample;
+ shiftL--;
+ }
+ // Unsign clipped sample
+ if(intSample >= 0x80000000)
+ {
+ intSample &= 0x7FFFFFFF;
+ shiftL++;
+ }
+ intSample = (shiftL << (31 - shiftR)) | (intSample >> shiftR);
+ if(sign)
+ intSample = ~intSample | sign;
+ return static_cast<float>(static_cast<int32>(intSample));
+}
+
+} // namespace DMO
+
+#else
+MPT_MSVC_WORKAROUND_LNK4221(Distortion)
+
+#endif // !NO_PLUGINS
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/soundlib/plugins/dmo/DMOUtils.h b/Src/external_dependencies/openmpt-trunk/soundlib/plugins/dmo/DMOUtils.h
new file mode 100644
index 00000000..2e17c007
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/soundlib/plugins/dmo/DMOUtils.h
@@ -0,0 +1,26 @@
+/*
+ * DMOUtils.h
+ * ----------
+ * Purpose: Utility functions shared by DMO plugins
+ * Notes : none
+ * Authors: OpenMPT Devs
+ * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
+ */
+
+
+OPENMPT_NAMESPACE_BEGIN
+
+#ifndef NO_PLUGINS
+
+namespace DMO
+{
+
+// Computes (log2(x) + 1) * 2 ^ (shiftL - shiftR) (x = -2^31...2^31)
+float logGain(float x, int32 shiftL, int32 shiftR);
+
+}
+
+#endif // !NO_PLUGINS
+
+OPENMPT_NAMESPACE_END
+
diff --git a/Src/external_dependencies/openmpt-trunk/soundlib/plugins/dmo/Distortion.cpp b/Src/external_dependencies/openmpt-trunk/soundlib/plugins/dmo/Distortion.cpp
new file mode 100644
index 00000000..cce38508
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/soundlib/plugins/dmo/Distortion.cpp
@@ -0,0 +1,216 @@
+/*
+ * Distortion.cpp
+ * --------------
+ * Purpose: Implementation of the DMO Distortion DSP (for non-Windows platforms)
+ * Notes : The original plugin's integer and floating point code paths only
+ * behave identically when feeding floating point numbers in range
+ * [-32768, +32768] rather than the usual [-1, +1] into the plugin.
+ * Authors: OpenMPT Devs
+ * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
+ */
+
+
+#include "stdafx.h"
+
+#ifndef NO_PLUGINS
+#include "../../Sndfile.h"
+#include "Distortion.h"
+#include "DMOUtils.h"
+#include "mpt/base/numbers.hpp"
+#endif // !NO_PLUGINS
+
+OPENMPT_NAMESPACE_BEGIN
+
+#ifndef NO_PLUGINS
+
+namespace DMO
+{
+
+IMixPlugin* Distortion::Create(VSTPluginLib &factory, CSoundFile &sndFile, SNDMIXPLUGIN *mixStruct)
+{
+ return new (std::nothrow) Distortion(factory, sndFile, mixStruct);
+}
+
+
+Distortion::Distortion(VSTPluginLib &factory, CSoundFile &sndFile, SNDMIXPLUGIN *mixStruct)
+ : IMixPlugin(factory, sndFile, mixStruct)
+{
+ m_param[kDistGain] = 0.7f;
+ m_param[kDistEdge] = 0.15f;
+ m_param[kDistPreLowpassCutoff] = 1.0f;
+ m_param[kDistPostEQCenterFrequency] = 0.291f;
+ m_param[kDistPostEQBandwidth] = 0.291f;
+
+ m_mixBuffer.Initialize(2, 2);
+ InsertIntoFactoryList();
+}
+
+
+void Distortion::Process(float *pOutL, float *pOutR, uint32 numFrames)
+{
+ if(!m_mixBuffer.Ok())
+ return;
+
+ const float *in[2] = { m_mixBuffer.GetInputBuffer(0), m_mixBuffer.GetInputBuffer(1) };
+ float *out[2] = { m_mixBuffer.GetOutputBuffer(0), m_mixBuffer.GetOutputBuffer(1) };
+
+ for(uint32 i = numFrames; i != 0; i--)
+ {
+ for(uint8 channel = 0; channel < 2; channel++)
+ {
+ float x = *(in[channel])++;
+
+ // Pre EQ
+ float z = x * m_preEQa0 + m_preEQz1[channel] * m_preEQb1;
+ m_preEQz1[channel] = z;
+
+ z *= 1073741824.0f; // 32768^2
+
+ // The actual distortion
+ z = logGain(z, m_edge, m_shift);
+
+ // Post EQ / Gain
+ z = (z * m_postEQa0) - m_postEQz1[channel] * m_postEQb1 - m_postEQz2[channel] * m_postEQb0;
+ m_postEQz1[channel] = z * m_postEQb0 + m_postEQz2[channel];
+ m_postEQz2[channel] = z;
+
+ z *= (1.0f / 1073741824.0f); // 32768^2
+ *(out[channel])++ = z;
+ }
+ }
+
+ ProcessMixOps(pOutL, pOutR, m_mixBuffer.GetOutputBuffer(0), m_mixBuffer.GetOutputBuffer(1), numFrames);
+}
+
+
+PlugParamValue Distortion::GetParameter(PlugParamIndex index)
+{
+ if(index < kDistNumParameters)
+ {
+ return m_param[index];
+ }
+ return 0;
+}
+
+
+void Distortion::SetParameter(PlugParamIndex index, PlugParamValue value)
+{
+ if(index < kDistNumParameters)
+ {
+ value = mpt::safe_clamp(value, 0.0f, 1.0f);
+ m_param[index] = value;
+ RecalculateDistortionParams();
+ }
+}
+
+
+void Distortion::Resume()
+{
+ m_isResumed = true;
+ RecalculateDistortionParams();
+ PositionChanged();
+}
+
+
+void Distortion::PositionChanged()
+{
+ // Reset filter state
+ m_preEQz1[0] = m_preEQz1[1] = 0;
+ m_postEQz1[0] = m_postEQz2[0] = 0;
+ m_postEQz1[1] = m_postEQz2[1] = 0;
+}
+
+
+#ifdef MODPLUG_TRACKER
+
+CString Distortion::GetParamName(PlugParamIndex param)
+{
+ switch(param)
+ {
+ case kDistGain: return _T("Gain");
+ case kDistEdge: return _T("Edge");
+ case kDistPreLowpassCutoff: return _T("PreLowpassCutoff");
+ case kDistPostEQCenterFrequency: return _T("PostEQCenterFrequency");
+ case kDistPostEQBandwidth: return _T("PostEQBandwidth");
+ }
+ return CString();
+}
+
+
+CString Distortion::GetParamLabel(PlugParamIndex param)
+{
+ switch(param)
+ {
+ case kDistGain:
+ return _T("dB");
+ case kDistPreLowpassCutoff:
+ case kDistPostEQCenterFrequency:
+ case kDistPostEQBandwidth:
+ return _T("Hz");
+ }
+ return CString();
+}
+
+
+CString Distortion::GetParamDisplay(PlugParamIndex param)
+{
+ float value = m_param[param];
+ switch(param)
+ {
+ case kDistGain:
+ value = GainInDecibel();
+ break;
+ case kDistEdge:
+ value *= 100.0f;
+ break;
+ case kDistPreLowpassCutoff:
+ case kDistPostEQCenterFrequency:
+ case kDistPostEQBandwidth:
+ value = FreqInHertz(value);
+ break;
+ }
+ CString s;
+ s.Format(_T("%.2f"), value);
+ return s;
+}
+
+#endif // MODPLUG_TRACKER
+
+
+void Distortion::RecalculateDistortionParams()
+{
+ // Pre-EQ
+ m_preEQb1 = std::sqrt((2.0f * std::cos(2.0f * mpt::numbers::pi_v<float> * std::min(FreqInHertz(m_param[kDistPreLowpassCutoff]) / m_SndFile.GetSampleRate(), 0.5f)) + 3.0f) / 5.0f);
+ m_preEQa0 = std::sqrt(1.0f - m_preEQb1 * m_preEQb1);
+
+ // Distortion
+ float edge = 2.0f + m_param[kDistEdge] * 29.0f;
+ m_edge = static_cast<uint8>(edge); // 2...31 shifted bits
+ m_shift = mpt::bit_width(m_edge);
+
+ static constexpr float LogNorm[32] =
+ {
+ 1.00f, 1.00f, 1.50f, 1.00f, 1.75f, 1.40f, 1.17f, 1.00f,
+ 1.88f, 1.76f, 1.50f, 1.36f, 1.25f, 1.15f, 1.07f, 1.00f,
+ 1.94f, 1.82f, 1.72f, 1.63f, 1.55f, 1.48f, 1.41f, 1.35f,
+ 1.29f, 1.24f, 1.19f, 1.15f, 1.11f, 1.07f, 1.03f, 1.00f,
+ };
+
+ // Post-EQ
+ const float gain = std::pow(10.0f, GainInDecibel() / 20.0f);
+ const float postFreq = 2.0f * mpt::numbers::pi_v<float> * std::min(FreqInHertz(m_param[kDistPostEQCenterFrequency]) / m_SndFile.GetSampleRate(), 0.5f);
+ const float postBw = 2.0f * mpt::numbers::pi_v<float> * std::min(FreqInHertz(m_param[kDistPostEQBandwidth]) / m_SndFile.GetSampleRate(), 0.5f);
+ const float t = std::tan(5.0e-1f * postBw);
+ m_postEQb1 = ((1.0f - t) / (1.0f + t));
+ m_postEQb0 = -std::cos(postFreq);
+ m_postEQa0 = gain * std::sqrt(1.0f - m_postEQb0 * m_postEQb0) * std::sqrt(1.0f - m_postEQb1 * m_postEQb1) * LogNorm[m_edge];
+}
+
+} // namespace DMO
+
+#else
+MPT_MSVC_WORKAROUND_LNK4221(Distortion)
+
+#endif // !NO_PLUGINS
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/soundlib/plugins/dmo/Distortion.h b/Src/external_dependencies/openmpt-trunk/soundlib/plugins/dmo/Distortion.h
new file mode 100644
index 00000000..3c88e8e7
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/soundlib/plugins/dmo/Distortion.h
@@ -0,0 +1,97 @@
+/*
+ * Distortion.h
+ * ------------
+ * Purpose: Implementation of the DMO Distortion DSP (for non-Windows platforms)
+ * Notes : (currently none)
+ * Authors: OpenMPT Devs
+ * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
+ */
+
+
+#ifndef NO_PLUGINS
+
+#include "../PlugInterface.h"
+
+OPENMPT_NAMESPACE_BEGIN
+
+namespace DMO
+{
+
+class Distortion final : public IMixPlugin
+{
+protected:
+ enum Parameters
+ {
+ kDistGain = 0,
+ kDistEdge,
+ kDistPreLowpassCutoff,
+ kDistPostEQCenterFrequency,
+ kDistPostEQBandwidth,
+ kDistNumParameters
+ };
+
+ std::array<float, kDistNumParameters> m_param;
+
+ // Pre-EQ coefficients
+ float m_preEQz1[2], m_preEQb1, m_preEQa0;
+ // Post-EQ coefficients
+ float m_postEQz1[2], m_postEQz2[2], m_postEQa0, m_postEQb0, m_postEQb1;
+ uint8 m_edge, m_shift;
+
+public:
+ static IMixPlugin* Create(VSTPluginLib &factory, CSoundFile &sndFile, SNDMIXPLUGIN *mixStruct);
+ Distortion(VSTPluginLib &factory, CSoundFile &sndFile, SNDMIXPLUGIN *mixStruct);
+
+ void Release() override { delete this; }
+ int32 GetUID() const override { return 0xEF114C90; }
+ int32 GetVersion() const override { return 0; }
+ void Idle() override { }
+ uint32 GetLatency() const override { return 0; }
+
+ void Process(float *pOutL, float *pOutR, uint32 numFrames) override;
+
+ float RenderSilence(uint32) override { return 0.0f; }
+
+ int32 GetNumPrograms() const override { return 0; }
+ int32 GetCurrentProgram() override { return 0; }
+ void SetCurrentProgram(int32) override { }
+
+ PlugParamIndex GetNumParameters() const override { return kDistNumParameters; }
+ PlugParamValue GetParameter(PlugParamIndex index) override;
+ void SetParameter(PlugParamIndex index, PlugParamValue value) override;
+
+ void Resume() override;
+ void Suspend() override { m_isResumed = false; }
+ void PositionChanged() override;
+ bool IsInstrument() const override { return false; }
+ bool CanRecieveMidiEvents() override { return false; }
+ bool ShouldProcessSilence() override { return true; }
+
+#ifdef MODPLUG_TRACKER
+ CString GetDefaultEffectName() override { return _T("Distortion"); }
+
+ CString GetParamName(PlugParamIndex param) override;
+ CString GetParamLabel(PlugParamIndex) override;
+ CString GetParamDisplay(PlugParamIndex param) override;
+
+ CString GetCurrentProgramName() override { return CString(); }
+ void SetCurrentProgramName(const CString &) override { }
+ CString GetProgramName(int32) override { return CString(); }
+
+ bool HasEditor() const override { return false; }
+#endif
+
+ int GetNumInputChannels() const override { return 2; }
+ int GetNumOutputChannels() const override { return 2; }
+
+protected:
+ static float FreqInHertz(float param) { return 100.0f + param * 7900.0f; }
+ float GainInDecibel() const { return -60.0f + m_param[kDistGain] * 60.0f; }
+ void RecalculateDistortionParams();
+};
+
+} // namespace DMO
+
+OPENMPT_NAMESPACE_END
+
+#endif // !NO_PLUGINS
diff --git a/Src/external_dependencies/openmpt-trunk/soundlib/plugins/dmo/Echo.cpp b/Src/external_dependencies/openmpt-trunk/soundlib/plugins/dmo/Echo.cpp
new file mode 100644
index 00000000..98251cca
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/soundlib/plugins/dmo/Echo.cpp
@@ -0,0 +1,207 @@
+/*
+ * Echo.cpp
+ * --------
+ * Purpose: Implementation of the DMO Echo DSP (for non-Windows platforms)
+ * Notes : (currently none)
+ * Authors: OpenMPT Devs
+ * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
+ */
+
+
+#include "stdafx.h"
+
+#ifndef NO_PLUGINS
+#include "../../Sndfile.h"
+#include "Echo.h"
+#endif // !NO_PLUGINS
+
+OPENMPT_NAMESPACE_BEGIN
+
+#ifndef NO_PLUGINS
+
+namespace DMO
+{
+
+IMixPlugin* Echo::Create(VSTPluginLib &factory, CSoundFile &sndFile, SNDMIXPLUGIN *mixStruct)
+{
+ return new (std::nothrow) Echo(factory, sndFile, mixStruct);
+}
+
+
+Echo::Echo(VSTPluginLib &factory, CSoundFile &sndFile, SNDMIXPLUGIN *mixStruct)
+ : IMixPlugin(factory, sndFile, mixStruct)
+ , m_bufferSize(0)
+ , m_writePos(0)
+ , m_sampleRate(sndFile.GetSampleRate())
+ , m_initialFeedback(0.0f)
+{
+ m_param[kEchoWetDry] = 0.5f;
+ m_param[kEchoFeedback] = 0.5f;
+ m_param[kEchoLeftDelay] = (500.0f - 1.0f) / 1999.0f;
+ m_param[kEchoRightDelay] = (500.0f - 1.0f) / 1999.0f;
+ m_param[kEchoPanDelay] = 0.0f;
+
+ m_mixBuffer.Initialize(2, 2);
+ InsertIntoFactoryList();
+}
+
+
+void Echo::Process(float *pOutL, float *pOutR, uint32 numFrames)
+{
+ if(!m_bufferSize || !m_mixBuffer.Ok())
+ return;
+ const float wetMix = m_param[kEchoWetDry], dryMix = 1 - wetMix;
+ const float *in[2] = { m_mixBuffer.GetInputBuffer(0), m_mixBuffer.GetInputBuffer(1) };
+ float *out[2] = { m_mixBuffer.GetOutputBuffer(0), m_mixBuffer.GetOutputBuffer(1) };
+
+ for(uint32 i = numFrames; i != 0; i--)
+ {
+ for(uint8 channel = 0; channel < 2; channel++)
+ {
+ const uint8 readChannel = (m_crossEcho ? (1 - channel) : channel);
+ int readPos = m_writePos - m_delayTime[readChannel];
+ if(readPos < 0)
+ readPos += m_bufferSize;
+
+ float chnInput = *(in[channel])++;
+ float chnDelay = m_delayLine[readPos * 2 + readChannel];
+
+ // Calculate the delay
+ float chnOutput = chnInput * m_initialFeedback;
+ chnOutput += chnDelay * m_param[kEchoFeedback];
+
+ // Prevent denormals
+ if(std::abs(chnOutput) < 1e-24f)
+ chnOutput = 0.0f;
+
+ m_delayLine[m_writePos * 2 + channel] = chnOutput;
+ // Output samples now
+ *(out[channel])++ = (chnInput * dryMix + chnDelay * wetMix);
+ }
+ m_writePos++;
+ if(m_writePos == m_bufferSize)
+ m_writePos = 0;
+ }
+
+ ProcessMixOps(pOutL, pOutR, m_mixBuffer.GetOutputBuffer(0), m_mixBuffer.GetOutputBuffer(1), numFrames);
+}
+
+
+PlugParamValue Echo::GetParameter(PlugParamIndex index)
+{
+ if(index < kEchoNumParameters)
+ {
+ return m_param[index];
+ }
+ return 0;
+}
+
+
+void Echo::SetParameter(PlugParamIndex index, PlugParamValue value)
+{
+ if(index < kEchoNumParameters)
+ {
+ value = mpt::safe_clamp(value, 0.0f, 1.0f);
+ if(index == kEchoPanDelay)
+ value = mpt::round(value);
+ m_param[index] = value;
+ RecalculateEchoParams();
+ }
+}
+
+
+void Echo::Resume()
+{
+ m_isResumed = true;
+ m_sampleRate = m_SndFile.GetSampleRate();
+ RecalculateEchoParams();
+ PositionChanged();
+}
+
+
+void Echo::PositionChanged()
+{
+ m_bufferSize = m_sampleRate * 2u;
+ try
+ {
+ m_delayLine.assign(m_bufferSize * 2, 0);
+ } catch(mpt::out_of_memory e)
+ {
+ mpt::delete_out_of_memory(e);
+ m_bufferSize = 0;
+ }
+ m_writePos = 0;
+}
+
+
+#ifdef MODPLUG_TRACKER
+
+CString Echo::GetParamName(PlugParamIndex param)
+{
+ switch(param)
+ {
+ case kEchoWetDry: return _T("WetDryMix");
+ case kEchoFeedback: return _T("Feedback");
+ case kEchoLeftDelay: return _T("LeftDelay");
+ case kEchoRightDelay: return _T("RightDelay");
+ case kEchoPanDelay: return _T("PanDelay");
+ }
+ return CString();
+}
+
+
+CString Echo::GetParamLabel(PlugParamIndex param)
+{
+ switch(param)
+ {
+ case kEchoFeedback:
+ return _T("%");
+ case kEchoLeftDelay:
+ case kEchoRightDelay:
+ return _T("ms");
+ default:
+ return CString{};
+ }
+}
+
+
+CString Echo::GetParamDisplay(PlugParamIndex param)
+{
+ CString s;
+ switch(param)
+ {
+ case kEchoWetDry:
+ s.Format(_T("%.1f : %.1f"), m_param[param] * 100.0f, 100.0f - m_param[param] * 100.0f);
+ break;
+ case kEchoFeedback:
+ s.Format(_T("%.2f"), m_param[param] * 100.0f);
+ break;
+ case kEchoLeftDelay:
+ case kEchoRightDelay:
+ s.Format(_T("%.2f"), 1.0f + m_param[param] * 1999.0f);
+ break;
+ case kEchoPanDelay:
+ s = (m_param[param] <= 0.5) ? _T("No") : _T("Yes");
+ }
+ return s;
+}
+
+#endif // MODPLUG_TRACKER
+
+
+void Echo::RecalculateEchoParams()
+{
+ m_initialFeedback = std::sqrt(1.0f - (m_param[kEchoFeedback] * m_param[kEchoFeedback]));
+ m_delayTime[0] = static_cast<uint32>((1.0f + m_param[kEchoLeftDelay] * 1999.0f) / 1000.0f * m_sampleRate);
+ m_delayTime[1] = static_cast<uint32>((1.0f + m_param[kEchoRightDelay] * 1999.0f) / 1000.0f * m_sampleRate);
+ m_crossEcho = (m_param[kEchoPanDelay]) > 0.5f;
+}
+
+} // namespace DMO
+
+#else
+MPT_MSVC_WORKAROUND_LNK4221(Echo)
+
+#endif // !NO_PLUGINS
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/soundlib/plugins/dmo/Echo.h b/Src/external_dependencies/openmpt-trunk/soundlib/plugins/dmo/Echo.h
new file mode 100644
index 00000000..b56d6c95
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/soundlib/plugins/dmo/Echo.h
@@ -0,0 +1,99 @@
+/*
+ * Echo.h
+ * ------
+ * Purpose: Implementation of the DMO Echo DSP (for non-Windows platforms)
+ * Notes : (currently none)
+ * Authors: OpenMPT Devs
+ * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
+ */
+
+
+#ifndef NO_PLUGINS
+
+#include "../PlugInterface.h"
+
+OPENMPT_NAMESPACE_BEGIN
+
+namespace DMO
+{
+
+class Echo final : public IMixPlugin
+{
+protected:
+ enum Parameters
+ {
+ kEchoWetDry = 0,
+ kEchoFeedback,
+ kEchoLeftDelay,
+ kEchoRightDelay,
+ kEchoPanDelay,
+ kEchoNumParameters
+ };
+
+ std::vector<float> m_delayLine; // Echo delay line
+ float m_param[kEchoNumParameters];
+ uint32 m_bufferSize; // Delay line length in frames
+ uint32 m_writePos; // Current write position in the delay line
+ uint32 m_delayTime[2]; // In frames
+ uint32 m_sampleRate;
+
+ // Echo calculation coefficients
+ float m_initialFeedback;
+ bool m_crossEcho;
+
+public:
+ static IMixPlugin* Create(VSTPluginLib &factory, CSoundFile &sndFile, SNDMIXPLUGIN *mixStruct);
+ Echo(VSTPluginLib &factory, CSoundFile &sndFile, SNDMIXPLUGIN *mixStruct);
+
+ void Release() override { delete this; }
+ int32 GetUID() const override { return 0xEF3E932C; }
+ int32 GetVersion() const override { return 0; }
+ void Idle() override { }
+ uint32 GetLatency() const override { return 0; }
+
+ void Process(float *pOutL, float *pOutR, uint32 numFrames)override;
+
+ float RenderSilence(uint32) override { return 0.0f; }
+
+ int32 GetNumPrograms() const override { return 0; }
+ int32 GetCurrentProgram() override { return 0; }
+ void SetCurrentProgram(int32) override { }
+
+ PlugParamIndex GetNumParameters() const override { return kEchoNumParameters; }
+ PlugParamValue GetParameter(PlugParamIndex index) override;
+ void SetParameter(PlugParamIndex index, PlugParamValue value) override;
+
+ void Resume() override;
+ void Suspend() override { m_isResumed = false; }
+ void PositionChanged() override;
+
+ bool IsInstrument() const override { return false; }
+ bool CanRecieveMidiEvents() override { return false; }
+ bool ShouldProcessSilence() override { return true; }
+
+#ifdef MODPLUG_TRACKER
+ CString GetDefaultEffectName() override { return _T("Echo"); }
+
+ CString GetParamName(PlugParamIndex param) override;
+ CString GetParamLabel(PlugParamIndex) override;
+ CString GetParamDisplay(PlugParamIndex param) override;
+
+ CString GetCurrentProgramName() override { return CString(); }
+ void SetCurrentProgramName(const CString &) override { }
+ CString GetProgramName(int32) override { return CString(); }
+
+ bool HasEditor() const override { return false; }
+#endif
+
+ int GetNumInputChannels() const override { return 2; }
+ int GetNumOutputChannels() const override { return 2; }
+
+protected:
+ void RecalculateEchoParams();
+};
+
+} // namespace DMO
+
+OPENMPT_NAMESPACE_END
+
+#endif // !NO_PLUGINS
diff --git a/Src/external_dependencies/openmpt-trunk/soundlib/plugins/dmo/Flanger.cpp b/Src/external_dependencies/openmpt-trunk/soundlib/plugins/dmo/Flanger.cpp
new file mode 100644
index 00000000..2da25110
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/soundlib/plugins/dmo/Flanger.cpp
@@ -0,0 +1,158 @@
+/*
+ * Flanger.cpp
+ * -----------
+ * Purpose: Implementation of the DMO Flanger DSP (for non-Windows platforms)
+ * Notes : (currently none)
+ * Authors: OpenMPT Devs
+ * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
+ */
+
+
+#include "stdafx.h"
+
+#ifndef NO_PLUGINS
+#include "../../Sndfile.h"
+#include "Flanger.h"
+#endif // !NO_PLUGINS
+
+OPENMPT_NAMESPACE_BEGIN
+
+#ifndef NO_PLUGINS
+
+namespace DMO
+{
+
+IMixPlugin* Flanger::Create(VSTPluginLib &factory, CSoundFile &sndFile, SNDMIXPLUGIN *mixStruct)
+{
+ return new (std::nothrow) Flanger(factory, sndFile, mixStruct, false);
+}
+
+IMixPlugin* Flanger::CreateLegacy(VSTPluginLib &factory, CSoundFile &sndFile, SNDMIXPLUGIN *mixStruct)
+{
+ return new(std::nothrow) Flanger(factory, sndFile, mixStruct, true);
+}
+
+
+Flanger::Flanger(VSTPluginLib &factory, CSoundFile &sndFile, SNDMIXPLUGIN *mixStruct, const bool legacy)
+ : Chorus(factory, sndFile, mixStruct, !legacy)
+{
+ m_param[kFlangerWetDryMix] = 0.5f;
+ m_param[kFlangerWaveShape] = 1.0f;
+ m_param[kFlangerFrequency] = 0.025f;
+ m_param[kFlangerDepth] = 1.0f;
+ m_param[kFlangerPhase] = 0.5f;
+ m_param[kFlangerFeedback] = (-50.0f + 99.0f) / 198.0f;
+ m_param[kFlangerDelay] = 0.5f;
+
+ // Already done in Chorus constructor
+ //m_mixBuffer.Initialize(2, 2);
+ //InsertIntoFactoryList();
+}
+
+
+void Flanger::SetParameter(PlugParamIndex index, PlugParamValue value)
+{
+ if(index < kFlangerNumParameters)
+ {
+ value = mpt::safe_clamp(value, 0.0f, 1.0f);
+ if(index == kFlangerWaveShape)
+ {
+ value = mpt::round(value);
+ if(m_param[index] != value)
+ {
+ m_waveShapeMin = 0.0f;
+ m_waveShapeMax = 0.5f + value * 0.5f;
+ }
+ } else if(index == kFlangerPhase)
+ {
+ value = mpt::round(value * 4.0f) / 4.0f;
+ }
+ m_param[index] = value;
+ RecalculateChorusParams();
+ }
+}
+
+
+#ifdef MODPLUG_TRACKER
+
+CString Flanger::GetParamName(PlugParamIndex param)
+{
+ switch(param)
+ {
+ case kFlangerWetDryMix: return _T("WetDryMix");
+ case kFlangerWaveShape: return _T("WaveShape");
+ case kFlangerFrequency: return _T("Frequency");
+ case kFlangerDepth: return _T("Depth");
+ case kFlangerPhase: return _T("Phase");
+ case kFlangerFeedback: return _T("Feedback");
+ case kFlangerDelay: return _T("Delay");
+ }
+ return CString();
+}
+
+
+CString Flanger::GetParamLabel(PlugParamIndex param)
+{
+ switch(param)
+ {
+ case kFlangerWetDryMix:
+ case kFlangerDepth:
+ case kFlangerFeedback:
+ return _T("%");
+ case kFlangerFrequency:
+ return _T("Hz");
+ case kFlangerPhase:
+ return mpt::ToCString(MPT_UTF8("\xC2\xB0")); // U+00B0 DEGREE SIGN
+ case kFlangerDelay:
+ return _T("ms");
+ }
+ return CString();
+}
+
+
+CString Flanger::GetParamDisplay(PlugParamIndex param)
+{
+ CString s;
+ float value = m_param[param];
+ switch(param)
+ {
+ case kFlangerWetDryMix:
+ case kFlangerDepth:
+ value *= 100.0f;
+ break;
+ case kFlangerFrequency:
+ value = FrequencyInHertz();
+ break;
+ case kFlangerWaveShape:
+ return (value < 1) ? _T("Triangle") : _T("Sine");
+ break;
+ case kFlangerPhase:
+ switch(Phase())
+ {
+ case 0: return _T("-180");
+ case 1: return _T("-90");
+ case 2: return _T("0");
+ case 3: return _T("90");
+ case 4: return _T("180");
+ }
+ break;
+ case kFlangerFeedback:
+ value = Feedback();
+ break;
+ case kFlangerDelay:
+ value = Delay();
+ }
+ s.Format(_T("%.2f"), value);
+ return s;
+}
+
+#endif // MODPLUG_TRACKER
+
+} // namespace DMO
+
+#else
+MPT_MSVC_WORKAROUND_LNK4221(Flanger)
+
+#endif // !NO_PLUGINS
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/soundlib/plugins/dmo/Flanger.h b/Src/external_dependencies/openmpt-trunk/soundlib/plugins/dmo/Flanger.h
new file mode 100644
index 00000000..1383028a
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/soundlib/plugins/dmo/Flanger.h
@@ -0,0 +1,72 @@
+/*
+ * Flanger.h
+ * ---------
+ * Purpose: Implementation of the DMO Flanger DSP (for non-Windows platforms)
+ * Notes : (currently none)
+ * Authors: OpenMPT Devs
+ * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
+ */
+
+
+#pragma once
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#ifndef NO_PLUGINS
+
+#include "Chorus.h"
+
+OPENMPT_NAMESPACE_BEGIN
+
+namespace DMO
+{
+
+class Flanger final : public Chorus
+{
+protected:
+ enum Parameters
+ {
+ kFlangerWetDryMix = 0,
+ kFlangerWaveShape,
+ kFlangerFrequency,
+ kFlangerDepth,
+ kFlangerPhase,
+ kFlangerFeedback,
+ kFlangerDelay,
+ kFlangerNumParameters
+ };
+
+public:
+ static IMixPlugin* Create(VSTPluginLib &factory, CSoundFile &sndFile, SNDMIXPLUGIN *mixStruct);
+ static IMixPlugin* CreateLegacy(VSTPluginLib& factory, CSoundFile& sndFile, SNDMIXPLUGIN* mixStruct);
+ Flanger(VSTPluginLib &factory, CSoundFile &sndFile, SNDMIXPLUGIN *mixStruct, const bool legacy);
+
+ void Release() override { delete this; }
+ int32 GetUID() const override { return 0xEFCA3D92; }
+
+ PlugParamIndex GetNumParameters() const override { return kFlangerNumParameters; }
+ void SetParameter(PlugParamIndex index, PlugParamValue value) override;
+
+#ifdef MODPLUG_TRACKER
+ CString GetDefaultEffectName() override { return _T("Flanger"); }
+
+ CString GetParamName(PlugParamIndex param) override;
+ CString GetParamLabel(PlugParamIndex) override;
+ CString GetParamDisplay(PlugParamIndex param) override;
+#endif
+
+protected:
+ float WetDryMix() const override { return m_param[kFlangerWetDryMix]; }
+ bool IsTriangle() const override { return m_param[kFlangerWaveShape] < 1; }
+ float Depth() const override { return m_param[kFlangerDepth]; }
+ float Feedback() const override { return -99.0f + m_param[kFlangerFeedback] * 198.0f; }
+ float Delay() const override { return m_param[kFlangerDelay] * 4.0f; }
+ float FrequencyInHertz() const override { return m_param[kFlangerFrequency] * 10.0f; }
+ int Phase() const override { return mpt::saturate_round<uint32>(m_param[kFlangerPhase] * 4.0f); }
+};
+
+} // namespace DMO
+
+OPENMPT_NAMESPACE_END
+
+#endif // !NO_PLUGINS
diff --git a/Src/external_dependencies/openmpt-trunk/soundlib/plugins/dmo/Gargle.cpp b/Src/external_dependencies/openmpt-trunk/soundlib/plugins/dmo/Gargle.cpp
new file mode 100644
index 00000000..91f5e145
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/soundlib/plugins/dmo/Gargle.cpp
@@ -0,0 +1,202 @@
+/*
+ * Gargle.cpp
+ * ----------
+ * Purpose: Implementation of the DMO Gargle DSP (for non-Windows platforms)
+ * Notes : (currently none)
+ * Authors: OpenMPT Devs
+ * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
+ */
+
+
+#include "stdafx.h"
+
+#ifndef NO_PLUGINS
+#include "../../Sndfile.h"
+#include "Gargle.h"
+#endif // !NO_PLUGINS
+
+OPENMPT_NAMESPACE_BEGIN
+
+#ifndef NO_PLUGINS
+
+namespace DMO
+{
+
+IMixPlugin* Gargle::Create(VSTPluginLib &factory, CSoundFile &sndFile, SNDMIXPLUGIN *mixStruct)
+{
+ return new (std::nothrow) Gargle(factory, sndFile, mixStruct);
+}
+
+
+Gargle::Gargle(VSTPluginLib &factory, CSoundFile &sndFile, SNDMIXPLUGIN *mixStruct)
+ : IMixPlugin(factory, sndFile, mixStruct)
+{
+ m_param[kGargleRate] = 0.02f;
+ m_param[kGargleWaveShape] = 0.0f;
+
+ m_mixBuffer.Initialize(2, 2);
+ InsertIntoFactoryList();
+}
+
+
+void Gargle::Process(float *pOutL, float *pOutR, uint32 numFrames)
+{
+ if(!m_mixBuffer.Ok())
+ return;
+
+ const float *inL = m_mixBuffer.GetInputBuffer(0), *inR = m_mixBuffer.GetInputBuffer(1);
+ float *outL = m_mixBuffer.GetOutputBuffer(0), *outR = m_mixBuffer.GetOutputBuffer(1);
+ const bool triangle = m_param[kGargleWaveShape] < 1.0f;
+
+ for(uint32 frame = numFrames; frame != 0;)
+ {
+ if(m_counter < m_periodHalf)
+ {
+ // First half of gargle period
+ const uint32 remain = std::min(frame, m_periodHalf - m_counter);
+ if(triangle)
+ {
+ const uint32 stop = m_counter + remain;
+ const float factor = 1.0f / m_periodHalf;
+ for(uint32 i = m_counter; i < stop; i++)
+ {
+ *outL++ = *inL++ * i * factor;
+ *outR++ = *inR++ * i * factor;
+ }
+ } else
+ {
+ for(uint32 i = 0; i < remain; i++)
+ {
+ *outL++ = *inL++;
+ *outR++ = *inR++;
+ }
+ }
+ frame -= remain;
+ m_counter += remain;
+ } else
+ {
+ // Second half of gargle period
+ const uint32 remain = std::min(frame, m_period - m_counter);
+ if(triangle)
+ {
+ const uint32 stop = m_period - m_counter - remain;
+ const float factor = 1.0f / m_periodHalf;
+ for(uint32 i = m_period - m_counter; i > stop; i--)
+ {
+ *outL++ = *inL++ * i * factor;
+ *outR++ = *inR++ * i * factor;
+ }
+ } else
+ {
+ for(uint32 i = 0; i < remain; i++)
+ {
+ *outL++ = 0;
+ *outR++ = 0;
+ }
+ inL += remain;
+ inR += remain;
+
+ }
+ frame -= remain;
+ m_counter += remain;
+ if(m_counter >= m_period) m_counter = 0;
+ }
+ }
+
+ ProcessMixOps(pOutL, pOutR, m_mixBuffer.GetOutputBuffer(0), m_mixBuffer.GetOutputBuffer(1), numFrames);
+}
+
+
+PlugParamValue Gargle::GetParameter(PlugParamIndex index)
+{
+ if(index < kGargleNumParameters)
+ {
+ return m_param[index];
+ }
+ return 0;
+}
+
+
+void Gargle::SetParameter(PlugParamIndex index, PlugParamValue value)
+{
+ if(index < kGargleNumParameters)
+ {
+ value = mpt::safe_clamp(value, 0.0f, 1.0f);
+ if(index == kGargleWaveShape)
+ value = mpt::round(value);
+ m_param[index] = value;
+ RecalculateGargleParams();
+ }
+}
+
+
+void Gargle::Resume()
+{
+ RecalculateGargleParams();
+ m_counter = 0;
+ m_isResumed = true;
+}
+
+
+#ifdef MODPLUG_TRACKER
+
+CString Gargle::GetParamName(PlugParamIndex param)
+{
+ switch(param)
+ {
+ case kGargleRate: return _T("Rate");
+ case kGargleWaveShape: return _T("WaveShape");
+ }
+ return CString();
+}
+
+
+CString Gargle::GetParamLabel(PlugParamIndex param)
+{
+ switch(param)
+ {
+ case kGargleRate: return _T("Hz");
+ }
+ return CString();
+}
+
+
+CString Gargle::GetParamDisplay(PlugParamIndex param)
+{
+ CString s;
+ switch(param)
+ {
+ case kGargleRate:
+ s.Format(_T("%u"), RateInHertz());
+ break;
+ case kGargleWaveShape:
+ return (m_param[param] < 0.5) ? _T("Triangle") : _T("Square");
+ }
+ return s;
+}
+
+#endif // MODPLUG_TRACKER
+
+
+uint32 Gargle::RateInHertz() const
+{
+ return static_cast<uint32>(mpt::round(std::clamp(m_param[kGargleRate], 0.0f, 1.0f) * 999.0f)) + 1;
+}
+
+
+void Gargle::RecalculateGargleParams()
+{
+ m_period = m_SndFile.GetSampleRate() / RateInHertz();
+ if(m_period < 2) m_period = 2;
+ m_periodHalf = m_period / 2;
+ LimitMax(m_counter, m_period);
+}
+
+} // namespace DMO
+
+#else
+MPT_MSVC_WORKAROUND_LNK4221(Gargle)
+
+#endif // !NO_PLUGINS
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/soundlib/plugins/dmo/Gargle.h b/Src/external_dependencies/openmpt-trunk/soundlib/plugins/dmo/Gargle.h
new file mode 100644
index 00000000..c4a721c5
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/soundlib/plugins/dmo/Gargle.h
@@ -0,0 +1,90 @@
+/*
+ * Gargle.h
+ * --------
+ * Purpose: Implementation of the DMO Gargle DSP (for non-Windows platforms)
+ * Notes : (currently none)
+ * Authors: OpenMPT Devs
+ * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
+ */
+
+
+#ifndef NO_PLUGINS
+
+#include "../PlugInterface.h"
+
+OPENMPT_NAMESPACE_BEGIN
+
+namespace DMO
+{
+
+class Gargle final : public IMixPlugin
+{
+protected:
+ enum Parameters
+ {
+ kGargleRate = 0,
+ kGargleWaveShape,
+ kGargleNumParameters
+ };
+
+ std::array<float, kGargleNumParameters> m_param;
+
+ uint32 m_period, m_periodHalf, m_counter; // In frames
+
+public:
+ static IMixPlugin* Create(VSTPluginLib &factory, CSoundFile &sndFile, SNDMIXPLUGIN *mixStruct);
+ Gargle(VSTPluginLib &factory, CSoundFile &sndFile, SNDMIXPLUGIN *mixStruct);
+
+ void Release() override { delete this; }
+ int32 GetUID() const override { return 0xDAFD8210; }
+ int32 GetVersion() const override { return 0; }
+ void Idle() override { }
+ uint32 GetLatency() const override { return 0; }
+
+ void Process(float *pOutL, float *pOutR, uint32 numFrames) override;
+
+ float RenderSilence(uint32) override { return 0.0f; }
+
+ int32 GetNumPrograms() const override { return 0; }
+ int32 GetCurrentProgram() override { return 0; }
+ void SetCurrentProgram(int32) override { }
+
+ PlugParamIndex GetNumParameters() const override { return kGargleNumParameters; }
+ PlugParamValue GetParameter(PlugParamIndex index) override;
+ void SetParameter(PlugParamIndex index, PlugParamValue value) override;
+
+ void Resume() override;
+ void Suspend() override { m_isResumed = false; }
+ void PositionChanged() override { m_counter = 0; }
+
+ bool IsInstrument() const override { return false; }
+ bool CanRecieveMidiEvents() override { return false; }
+ bool ShouldProcessSilence() override { return true; }
+
+#ifdef MODPLUG_TRACKER
+ CString GetDefaultEffectName() override { return _T("Gargle"); }
+
+ CString GetParamName(PlugParamIndex param) override;
+ CString GetParamLabel(PlugParamIndex) override;
+ CString GetParamDisplay(PlugParamIndex param) override;
+
+ CString GetCurrentProgramName() override { return CString(); }
+ void SetCurrentProgramName(const CString &) override { }
+ CString GetProgramName(int32) override { return CString(); }
+
+ bool HasEditor() const override { return false; }
+#endif
+
+ int GetNumInputChannels() const override { return 2; }
+ int GetNumOutputChannels() const override { return 2; }
+
+protected:
+ uint32 RateInHertz() const;
+ void RecalculateGargleParams();
+};
+
+} // namespace DMO
+
+OPENMPT_NAMESPACE_END
+
+#endif // !NO_PLUGINS
diff --git a/Src/external_dependencies/openmpt-trunk/soundlib/plugins/dmo/I3DL2Reverb.cpp b/Src/external_dependencies/openmpt-trunk/soundlib/plugins/dmo/I3DL2Reverb.cpp
new file mode 100644
index 00000000..63f5b9c1
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/soundlib/plugins/dmo/I3DL2Reverb.cpp
@@ -0,0 +1,645 @@
+/*
+ * I3DL2Reverb.cpp
+ * ---------------
+ * Purpose: Implementation of the DMO I3DL2Reverb DSP (for non-Windows platforms)
+ * Notes : (currently none)
+ * Authors: OpenMPT Devs
+ * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
+ */
+
+
+#include "stdafx.h"
+
+#ifndef NO_PLUGINS
+#include "../../Sndfile.h"
+#include "I3DL2Reverb.h"
+#ifdef MODPLUG_TRACKER
+#include "../../../sounddsp/Reverb.h"
+#endif // MODPLUG_TRACKER
+#include "mpt/base/numbers.hpp"
+#endif // !NO_PLUGINS
+
+OPENMPT_NAMESPACE_BEGIN
+
+#ifndef NO_PLUGINS
+
+namespace DMO
+{
+
+void I3DL2Reverb::DelayLine::Init(int32 ms, int32 padding, uint32 sampleRate, int32 delayTap)
+{
+ m_length = Util::muldiv(sampleRate, ms, 1000) + padding;
+ m_position = 0;
+ SetDelayTap(delayTap);
+ assign(m_length, 0.0f);
+}
+
+
+void I3DL2Reverb::DelayLine::SetDelayTap(int32 delayTap)
+{
+ if(m_length > 0)
+ m_delayPosition = (delayTap + m_position + m_length) % m_length;
+}
+
+
+void I3DL2Reverb::DelayLine::Advance()
+{
+ if(--m_position < 0)
+ m_position += m_length;
+ if(--m_delayPosition < 0)
+ m_delayPosition += m_length;
+}
+
+
+MPT_FORCEINLINE void I3DL2Reverb::DelayLine::Set(float value)
+{
+ at(m_position) = value;
+}
+
+
+float I3DL2Reverb::DelayLine::Get(int32 offset) const
+{
+ offset = (offset + m_position) % m_length;
+ if(offset < 0)
+ offset += m_length;
+ return at(offset);
+}
+
+
+MPT_FORCEINLINE float I3DL2Reverb::DelayLine::Get() const
+{
+ return at(m_delayPosition);
+}
+
+
+IMixPlugin* I3DL2Reverb::Create(VSTPluginLib &factory, CSoundFile &sndFile, SNDMIXPLUGIN *mixStruct)
+{
+ return new (std::nothrow) I3DL2Reverb(factory, sndFile, mixStruct);
+}
+
+
+I3DL2Reverb::I3DL2Reverb(VSTPluginLib &factory, CSoundFile &sndFile, SNDMIXPLUGIN *mixStruct)
+ : IMixPlugin(factory, sndFile, mixStruct)
+{
+ m_param[kI3DL2ReverbRoom] = 0.9f;
+ m_param[kI3DL2ReverbRoomHF] = 0.99f;
+ m_param[kI3DL2ReverbRoomRolloffFactor] = 0.0f;
+ m_param[kI3DL2ReverbDecayTime] = 0.07f;
+ m_param[kI3DL2ReverbDecayHFRatio] = 0.3842105f;
+ m_param[kI3DL2ReverbReflections] = 0.672545433f;
+ m_param[kI3DL2ReverbReflectionsDelay] = 0.233333333f;
+ m_param[kI3DL2ReverbReverb] = 0.85f;
+ m_param[kI3DL2ReverbReverbDelay] = 0.11f;
+ m_param[kI3DL2ReverbDiffusion] = 1.0f;
+ m_param[kI3DL2ReverbDensity] = 1.0f;
+ m_param[kI3DL2ReverbHFReference] = (5000.0f - 20.0f) / 19980.0f;
+ m_param[kI3DL2ReverbQuality] = 2.0f / 3.0f;
+
+ SetCurrentProgram(m_program);
+
+ m_mixBuffer.Initialize(2, 2);
+ InsertIntoFactoryList();
+}
+
+
+void I3DL2Reverb::Process(float *pOutL, float *pOutR, uint32 numFrames)
+{
+ if(m_recalcParams)
+ {
+ auto sampleRate = m_effectiveSampleRate;
+ RecalculateI3DL2ReverbParams();
+ // Resize and clear delay lines if quality has changed
+ if(sampleRate != m_effectiveSampleRate)
+ PositionChanged();
+ }
+
+ if(!m_ok || !m_mixBuffer.Ok())
+ return;
+
+ const float *in[2] = { m_mixBuffer.GetInputBuffer(0), m_mixBuffer.GetInputBuffer(1) };
+ float *out[2] = { m_mixBuffer.GetOutputBuffer(0), m_mixBuffer.GetOutputBuffer(1) };
+
+ uint32 frames = numFrames;
+ if(!(m_quality & kFullSampleRate) && m_remain && frames > 0)
+ {
+ // Remaining frame from previous render call
+ frames--;
+ *(out[0]++) = m_prevL;
+ *(out[1]++) = m_prevR;
+ in[0]++;
+ in[1]++;
+ m_remain = false;
+ }
+
+ while(frames > 0)
+ {
+ // Apply room filter and insert into early reflection delay lines
+ const float inL = *(in[0]++);
+ const float inRoomL = (m_filterHist[12] - inL) * m_roomFilter + inL;
+ m_filterHist[12] = inRoomL;
+ m_delayLines[15].Set(inRoomL);
+
+ const float inR = *(in[1]++);
+ const float inRoomR = (m_filterHist[13] - inR) * m_roomFilter + inR;
+ m_filterHist[13] = inRoomR;
+ m_delayLines[16].Set(inRoomR);
+
+ // Early reflections (left)
+ float earlyL = m_delayLines[15].Get(m_earlyTaps[0][1]) * 0.68f
+ - m_delayLines[15].Get(m_earlyTaps[0][2]) * 0.5f
+ - m_delayLines[15].Get(m_earlyTaps[0][3]) * 0.62f
+ - m_delayLines[15].Get(m_earlyTaps[0][4]) * 0.5f
+ - m_delayLines[15].Get(m_earlyTaps[0][5]) * 0.62f;
+ if(m_quality & kMoreDelayLines)
+ {
+ float earlyL2 = earlyL;
+ earlyL = m_delayLines[13].Get() + earlyL * 0.618034f;
+ m_delayLines[13].Set(earlyL2 - earlyL * 0.618034f);
+ }
+ const float earlyRefOutL = earlyL * m_ERLevel;
+ m_filterHist[15] = m_delayLines[15].Get(m_earlyTaps[0][0]) + m_filterHist[15];
+ m_filterHist[16] = m_delayLines[16].Get(m_earlyTaps[1][0]) + m_filterHist[16];
+
+ // Lots of slightly different copy-pasta ahead
+ float reverbL1, reverbL2, reverbL3, reverbR1, reverbR2, reverbR3;
+
+ reverbL1 = -m_filterHist[15] * 0.707f;
+ reverbL2 = m_filterHist[16] * 0.707f + reverbL1;
+ reverbR2 = reverbL1 - m_filterHist[16] * 0.707f;
+
+ m_filterHist[5] = (m_filterHist[5] - m_delayLines[5].Get()) * m_delayCoeffs[5][1] + m_delayLines[5].Get();
+ reverbL1 = m_filterHist[5] * m_delayCoeffs[5][0] + reverbL2 * m_diffusion;
+ m_delayLines[5].Set(reverbL2 - reverbL1 * m_diffusion);
+ reverbL2 = reverbL1;
+ reverbL3 = -0.15f * reverbL1;
+
+ m_filterHist[4] = (m_filterHist[4] - m_delayLines[4].Get()) * m_delayCoeffs[4][1] + m_delayLines[4].Get();
+ reverbL1 = m_filterHist[4] * m_delayCoeffs[4][0] + reverbL2 * m_diffusion;
+ m_delayLines[4].Set(reverbL2 - reverbL1 * m_diffusion);
+ reverbL2 = reverbL1;
+ reverbL3 -= reverbL1 * 0.2f;
+
+ if(m_quality & kMoreDelayLines)
+ {
+ m_filterHist[3] = (m_filterHist[3] - m_delayLines[3].Get()) * m_delayCoeffs[3][1] + m_delayLines[3].Get();
+ reverbL1 = m_filterHist[3] * m_delayCoeffs[3][0] + reverbL2 * m_diffusion;
+ m_delayLines[3].Set(reverbL2 - reverbL1 * m_diffusion);
+ reverbL2 = reverbL1;
+ reverbL3 += 0.35f * reverbL1;
+
+ m_filterHist[2] = (m_filterHist[2] - m_delayLines[2].Get()) * m_delayCoeffs[2][1] + m_delayLines[2].Get();
+ reverbL1 = m_filterHist[2] * m_delayCoeffs[2][0] + reverbL2 * m_diffusion;
+ m_delayLines[2].Set(reverbL2 - reverbL1 * m_diffusion);
+ reverbL2 = reverbL1;
+ reverbL3 -= reverbL1 * 0.38f;
+ }
+ m_delayLines[17].Set(reverbL2);
+
+ reverbL1 = m_delayLines[17].Get() * m_delayCoeffs[12][0];
+ m_filterHist[17] = (m_filterHist[17] - reverbL1) * m_delayCoeffs[12][1] + reverbL1;
+
+ m_filterHist[1] = (m_filterHist[1] - m_delayLines[1].Get()) * m_delayCoeffs[1][1] + m_delayLines[1].Get();
+ reverbL1 = m_filterHist[17] * m_diffusion + m_filterHist[1] * m_delayCoeffs[1][0];
+ m_delayLines[1].Set(m_filterHist[17] - reverbL1 * m_diffusion);
+ reverbL2 = reverbL1;
+ float reverbL4 = reverbL1 * 0.38f;
+
+ m_filterHist[0] = (m_filterHist[0] - m_delayLines[0].Get()) * m_delayCoeffs[0][1] + m_delayLines[0].Get();
+ reverbL1 = m_filterHist[0] * m_delayCoeffs[0][0] + reverbL2 * m_diffusion;
+ m_delayLines[0].Set(reverbL2 - reverbL1 * m_diffusion);
+ reverbL3 -= reverbL1 * 0.38f;
+ m_filterHist[15] = reverbL1;
+
+ // Early reflections (right)
+ float earlyR = m_delayLines[16].Get(m_earlyTaps[1][1]) * 0.707f
+ - m_delayLines[16].Get(m_earlyTaps[1][2]) * 0.6f
+ - m_delayLines[16].Get(m_earlyTaps[1][3]) * 0.5f
+ - m_delayLines[16].Get(m_earlyTaps[1][4]) * 0.6f
+ - m_delayLines[16].Get(m_earlyTaps[1][5]) * 0.5f;
+ if(m_quality & kMoreDelayLines)
+ {
+ float earlyR2 = earlyR;
+ earlyR = m_delayLines[14].Get() + earlyR * 0.618034f;
+ m_delayLines[14].Set(earlyR2 - earlyR * 0.618034f);
+ }
+ const float earlyRefOutR = earlyR * m_ERLevel;
+
+ m_filterHist[11] = (m_filterHist[11] - m_delayLines[11].Get()) * m_delayCoeffs[11][1] + m_delayLines[11].Get();
+ reverbR1 = m_filterHist[11] * m_delayCoeffs[11][0] + reverbR2 * m_diffusion;
+ m_delayLines[11].Set(reverbR2 - reverbR1 * m_diffusion);
+ reverbR2 = reverbR1;
+
+ m_filterHist[10] = (m_filterHist[10] - m_delayLines[10].Get()) * m_delayCoeffs[10][1] + m_delayLines[10].Get();
+ reverbR1 = m_filterHist[10] * m_delayCoeffs[10][0] + reverbR2 * m_diffusion;
+ m_delayLines[10].Set(reverbR2 - reverbR1 * m_diffusion);
+ reverbR3 = reverbL4 - reverbR2 * 0.15f - reverbR1 * 0.2f;
+ reverbR2 = reverbR1;
+
+ if(m_quality & kMoreDelayLines)
+ {
+ m_filterHist[9] = (m_filterHist[9] - m_delayLines[9].Get()) * m_delayCoeffs[9][1] + m_delayLines[9].Get();
+ reverbR1 = m_filterHist[9] * m_delayCoeffs[9][0] + reverbR2 * m_diffusion;
+ m_delayLines[9].Set(reverbR2 - reverbR1 * m_diffusion);
+ reverbR2 = reverbR1;
+ reverbR3 += reverbR1 * 0.35f;
+
+ m_filterHist[8] = (m_filterHist[8] - m_delayLines[8].Get()) * m_delayCoeffs[8][1] + m_delayLines[8].Get();
+ reverbR1 = m_filterHist[8] * m_delayCoeffs[8][0] + reverbR2 * m_diffusion;
+ m_delayLines[8].Set(reverbR2 - reverbR1 * m_diffusion);
+ reverbR2 = reverbR1;
+ reverbR3 -= reverbR1 * 0.38f;
+ }
+ m_delayLines[18].Set(reverbR2);
+
+ reverbR1 = m_delayLines[18].Get() * m_delayCoeffs[12][0];
+ m_filterHist[18] = (m_filterHist[18] - reverbR1) * m_delayCoeffs[12][1] + reverbR1;
+
+ m_filterHist[7] = (m_filterHist[7] - m_delayLines[7].Get()) * m_delayCoeffs[7][1] + m_delayLines[7].Get();
+ reverbR1 = m_filterHist[18] * m_diffusion + m_filterHist[7] * m_delayCoeffs[7][0];
+ m_delayLines[7].Set(m_filterHist[18] - reverbR1 * m_diffusion);
+ reverbR2 = reverbR1;
+
+ float lateRevOutL = (reverbL3 + reverbR1 * 0.38f) * m_ReverbLevelL;
+
+ m_filterHist[6] = (m_filterHist[6] - m_delayLines[6].Get()) * m_delayCoeffs[6][1] + m_delayLines[6].Get();
+ reverbR1 = m_filterHist[6] * m_delayCoeffs[6][0] + reverbR2 * m_diffusion;
+ m_delayLines[6].Set(reverbR2 - reverbR1 * m_diffusion);
+ m_filterHist[16] = reverbR1;
+
+ float lateRevOutR = (reverbR3 - reverbR1 * 0.38f) * m_ReverbLevelR;
+
+ float outL = earlyRefOutL + lateRevOutL;
+ float outR = earlyRefOutR + lateRevOutR;
+
+ for(auto &line : m_delayLines)
+ line.Advance();
+
+ if(!(m_quality & kFullSampleRate))
+ {
+ *(out[0]++) = (outL + m_prevL) * 0.5f;
+ *(out[1]++) = (outR + m_prevR) * 0.5f;
+ m_prevL = outL;
+ m_prevR = outR;
+ in[0]++;
+ in[1]++;
+ if(frames-- == 1)
+ {
+ m_remain = true;
+ break;
+ }
+ }
+ *(out[0]++) = outL;
+ *(out[1]++) = outR;
+ frames--;
+ }
+
+ ProcessMixOps(pOutL, pOutR, m_mixBuffer.GetOutputBuffer(0), m_mixBuffer.GetOutputBuffer(1), numFrames);
+}
+
+
+int32 I3DL2Reverb::GetNumPrograms() const
+{
+#ifdef MODPLUG_TRACKER
+ return NUM_REVERBTYPES;
+#else
+ return 0;
+#endif
+}
+
+void I3DL2Reverb::SetCurrentProgram(int32 program)
+{
+#ifdef MODPLUG_TRACKER
+ if(program < static_cast<int32>(NUM_REVERBTYPES))
+ {
+ m_program = program;
+ const auto &preset = *GetReverbPreset(m_program);
+ m_param[kI3DL2ReverbRoom] = (preset.lRoom + 10000) / 10000.0f;
+ m_param[kI3DL2ReverbRoomHF] = (preset.lRoomHF + 10000) / 10000.0f;
+ m_param[kI3DL2ReverbRoomRolloffFactor] = 0.0f;
+ m_param[kI3DL2ReverbDecayTime] = (preset.flDecayTime - 0.1f) / 19.9f;
+ m_param[kI3DL2ReverbDecayHFRatio] = (preset.flDecayHFRatio - 0.1f) / 1.9f;
+ m_param[kI3DL2ReverbReflections] = (preset.lReflections + 10000) / 11000.0f;
+ m_param[kI3DL2ReverbReflectionsDelay] = preset.flReflectionsDelay / 0.3f;
+ m_param[kI3DL2ReverbReverb] = (preset.lReverb + 10000) / 12000.0f;
+ m_param[kI3DL2ReverbReverbDelay] = preset.flReverbDelay / 0.1f;
+ m_param[kI3DL2ReverbDiffusion] = preset.flDiffusion / 100.0f;
+ m_param[kI3DL2ReverbDensity] = preset.flDensity / 100.0f;
+ m_param[kI3DL2ReverbHFReference] = (5000.0f - 20.0f) / 19980.0f;
+ RecalculateI3DL2ReverbParams();
+ }
+#else
+ MPT_UNUSED_VARIABLE(program);
+#endif
+}
+
+
+PlugParamValue I3DL2Reverb::GetParameter(PlugParamIndex index)
+{
+ if(index < kI3DL2ReverbNumParameters)
+ {
+ return m_param[index];
+ }
+ return 0;
+}
+
+
+void I3DL2Reverb::SetParameter(PlugParamIndex index, PlugParamValue value)
+{
+ if(index < kI3DL2ReverbNumParameters)
+ {
+ value = mpt::safe_clamp(value, 0.0f, 1.0f);
+ if(index == kI3DL2ReverbQuality)
+ value = mpt::round(value * 3.0f) / 3.0f;
+ m_param[index] = value;
+ m_recalcParams = true;
+ }
+}
+
+
+void I3DL2Reverb::Resume()
+{
+ RecalculateI3DL2ReverbParams();
+ PositionChanged();
+ m_isResumed = true;
+}
+
+
+void I3DL2Reverb::PositionChanged()
+{
+ MemsetZero(m_filterHist);
+ m_prevL = 0;
+ m_prevR = 0;
+ m_remain = false;
+
+ try
+ {
+ uint32 sampleRate = static_cast<uint32>(m_effectiveSampleRate);
+ m_delayLines[0].Init(67, 5, sampleRate, m_delayTaps[0]);
+ m_delayLines[1].Init(62, 5, sampleRate, m_delayTaps[1]);
+ m_delayLines[2].Init(53, 5, sampleRate, m_delayTaps[2]);
+ m_delayLines[3].Init(43, 5, sampleRate, m_delayTaps[3]);
+ m_delayLines[4].Init(32, 5, sampleRate, m_delayTaps[4]);
+ m_delayLines[5].Init(22, 5, sampleRate, m_delayTaps[5]);
+ m_delayLines[6].Init(75, 5, sampleRate, m_delayTaps[6]);
+ m_delayLines[7].Init(69, 5, sampleRate, m_delayTaps[7]);
+ m_delayLines[8].Init(60, 5, sampleRate, m_delayTaps[8]);
+ m_delayLines[9].Init(48, 5, sampleRate, m_delayTaps[9]);
+ m_delayLines[10].Init(36, 5, sampleRate, m_delayTaps[10]);
+ m_delayLines[11].Init(25, 5, sampleRate, m_delayTaps[11]);
+ m_delayLines[12].Init(0, 0, 0); // Dummy for array index consistency with both tap and coefficient arrays
+ m_delayLines[13].Init(3, 0, sampleRate, m_delayTaps[13]);
+ m_delayLines[14].Init(3, 0, sampleRate, m_delayTaps[14]);
+ m_delayLines[15].Init(407, 1, sampleRate);
+ m_delayLines[16].Init(400, 1, sampleRate);
+ m_delayLines[17].Init(10, 0, sampleRate, -1);
+ m_delayLines[18].Init(10, 0, sampleRate, -1);
+ m_ok = true;
+ } catch(mpt::out_of_memory e)
+ {
+ m_ok = false;
+ mpt::delete_out_of_memory(e);
+ }
+}
+
+
+#ifdef MODPLUG_TRACKER
+
+CString I3DL2Reverb::GetParamName(PlugParamIndex param)
+{
+ switch(param)
+ {
+ case kI3DL2ReverbRoom: return _T("Room");
+ case kI3DL2ReverbRoomHF: return _T("RoomHF");
+ case kI3DL2ReverbRoomRolloffFactor: return _T("RoomRolloffFactor");
+ case kI3DL2ReverbDecayTime: return _T("DecayTime");
+ case kI3DL2ReverbDecayHFRatio: return _T("DecayHFRatio");
+ case kI3DL2ReverbReflections: return _T("Reflections");
+ case kI3DL2ReverbReflectionsDelay: return _T("ReflectionsDelay");
+ case kI3DL2ReverbReverb: return _T("Reverb");
+ case kI3DL2ReverbReverbDelay: return _T("ReverbDelay");
+ case kI3DL2ReverbDiffusion: return _T("Diffusion");
+ case kI3DL2ReverbDensity: return _T("Density");
+ case kI3DL2ReverbHFReference: return _T("HFRefrence");
+ case kI3DL2ReverbQuality: return _T("Quality");
+ }
+ return CString();
+}
+
+
+CString I3DL2Reverb::GetParamLabel(PlugParamIndex param)
+{
+ switch(param)
+ {
+ case kI3DL2ReverbRoom:
+ case kI3DL2ReverbRoomHF:
+ case kI3DL2ReverbReflections:
+ case kI3DL2ReverbReverb:
+ return _T("dB");
+ case kI3DL2ReverbDecayTime:
+ case kI3DL2ReverbReflectionsDelay:
+ case kI3DL2ReverbReverbDelay:
+ return _T("s");
+ case kI3DL2ReverbDiffusion:
+ case kI3DL2ReverbDensity:
+ return _T("%");
+ case kI3DL2ReverbHFReference:
+ return _T("Hz");
+ }
+ return CString();
+}
+
+
+CString I3DL2Reverb::GetParamDisplay(PlugParamIndex param)
+{
+ static constexpr const TCHAR * const modes[] = { _T("LQ"), _T("LQ+"), _T("HQ"), _T("HQ+") };
+ float value = m_param[param];
+ switch(param)
+ {
+ case kI3DL2ReverbRoom: value = Room() * 0.01f; break;
+ case kI3DL2ReverbRoomHF: value = RoomHF() * 0.01f; break;
+ case kI3DL2ReverbRoomRolloffFactor: value = RoomRolloffFactor(); break;
+ case kI3DL2ReverbDecayTime: value = DecayTime(); break;
+ case kI3DL2ReverbDecayHFRatio: value = DecayHFRatio(); break;
+ case kI3DL2ReverbReflections: value = Reflections() * 0.01f; break;
+ case kI3DL2ReverbReflectionsDelay: value = ReflectionsDelay(); break;
+ case kI3DL2ReverbReverb: value = Reverb() * 0.01f; break;
+ case kI3DL2ReverbReverbDelay: value = ReverbDelay(); break;
+ case kI3DL2ReverbDiffusion: value = Diffusion(); break;
+ case kI3DL2ReverbDensity: value = Density(); break;
+ case kI3DL2ReverbHFReference: value = HFReference(); break;
+ case kI3DL2ReverbQuality: return modes[Quality() % 4u];
+ }
+ CString s;
+ s.Format(_T("%.2f"), value);
+ return s;
+}
+
+
+CString I3DL2Reverb::GetCurrentProgramName()
+{
+ return GetProgramName(m_program);
+}
+
+
+CString I3DL2Reverb::GetProgramName(int32 program)
+{
+ return mpt::ToCString(GetReverbPresetName(program));
+}
+
+#endif // MODPLUG_TRACKER
+
+
+void I3DL2Reverb::RecalculateI3DL2ReverbParams()
+{
+ m_quality = Quality();
+ m_effectiveSampleRate = static_cast<float>(m_SndFile.GetSampleRate() / ((m_quality & kFullSampleRate) ? 1u : 2u));
+
+ // Diffusion
+ m_diffusion = Diffusion() * (0.618034f / 100.0f);
+ // Early Reflection Level
+ m_ERLevel = std::min(std::pow(10.0f, (Room() + Reflections()) / (100.0f * 20.0f)), 1.0f) * 0.761f;
+
+ // Room Filter
+ float roomHF = std::pow(10.0f, RoomHF() / 100.0f / 10.0f);
+ if(roomHF == 1.0f)
+ {
+ m_roomFilter = 0.0f;
+ } else
+ {
+ float freq = std::cos(HFReference() * (2.0f * mpt::numbers::pi_v<float>) / m_effectiveSampleRate);
+ float roomFilter = (freq * (roomHF + roomHF) - 2.0f + std::sqrt(freq * (roomHF * roomHF * freq * 4.0f) + roomHF * 8.0f - roomHF * roomHF * 4.0f - roomHF * freq * 8.0f)) / (roomHF + roomHF - 2.0f);
+ m_roomFilter = Clamp(roomFilter, 0.0f, 1.0f);
+ }
+
+ SetDelayTaps();
+ SetDecayCoeffs();
+
+ m_recalcParams = false;
+}
+
+
+void I3DL2Reverb::SetDelayTaps()
+{
+ // Early reflections
+ static constexpr float delays[] =
+ {
+ 1.0000f, 1.0000f, 0.0000f, 0.1078f, 0.1768f, 0.2727f,
+ 0.3953f, 0.5386f, 0.6899f, 0.8306f, 0.9400f, 0.9800f,
+ };
+
+ const float sampleRate = m_effectiveSampleRate;
+ const float reflectionsDelay = ReflectionsDelay();
+ const float reverbDelay = std::max(ReverbDelay(), 5.0f / 1000.0f);
+ m_earlyTaps[0][0] = static_cast<int32>((reverbDelay + reflectionsDelay + 7.0f / 1000.0f) * sampleRate);
+ for(uint32 i = 1; i < 12; i++)
+ {
+ m_earlyTaps[i % 2u][i / 2u] = static_cast<int32>((reverbDelay * delays[i] + reflectionsDelay) * sampleRate);
+ }
+
+ // Late reflections
+ float density = std::min((Density() / 100.0f + 0.1f) * 0.9091f, 1.0f);
+ float delayL = density * 67.0f / 1000.0f * sampleRate;
+ float delayR = density * 75.0f / 1000.0f * sampleRate;
+ for(int i = 0, power = 0; i < 6; i++)
+ {
+ power += i;
+ float factor = std::pow(0.93f, static_cast<float>(power));
+ m_delayTaps[i + 0] = static_cast<int32>(delayL * factor);
+ m_delayTaps[i + 6] = static_cast<int32>(delayR * factor);
+ }
+ m_delayTaps[12] = static_cast<int32>(10.0f / 1000.0f * sampleRate);
+ // Early reflections (extra delay lines)
+ m_delayTaps[13] = static_cast<int32>(3.25f / 1000.0f * sampleRate);
+ m_delayTaps[14] = static_cast<int32>(3.53f / 1000.0f * sampleRate);
+
+ for(std::size_t d = 0; d < std::size(m_delayTaps); d++)
+ m_delayLines[d].SetDelayTap(m_delayTaps[d]);
+}
+
+
+void I3DL2Reverb::SetDecayCoeffs()
+{
+ float levelLtmp = 1.0f, levelRtmp = 1.0f;
+ float levelL = 0.0f, levelR = 0.0f;
+
+ levelLtmp *= CalcDecayCoeffs(5);
+ levelRtmp *= CalcDecayCoeffs(11);
+ levelL += levelLtmp * 0.0225f;
+ levelR += levelRtmp * 0.0225f;
+
+ levelLtmp *= CalcDecayCoeffs(4);
+ levelRtmp *= CalcDecayCoeffs(10);
+ levelL += levelLtmp * 0.04f;
+ levelR += levelRtmp * 0.04f;
+
+ if(m_quality & kMoreDelayLines)
+ {
+ levelLtmp *= CalcDecayCoeffs(3);
+ levelRtmp *= CalcDecayCoeffs(9);
+ levelL += levelLtmp * 0.1225f;
+ levelR += levelRtmp * 0.1225f;
+
+ levelLtmp *= CalcDecayCoeffs(2);
+ levelRtmp *= CalcDecayCoeffs(8);
+ levelL += levelLtmp * 0.1444f;
+ levelR += levelRtmp * 0.1444f;
+ }
+ CalcDecayCoeffs(12);
+ levelLtmp *= m_delayCoeffs[12][0] * m_delayCoeffs[12][0];
+ levelRtmp *= m_delayCoeffs[12][0] * m_delayCoeffs[12][0];
+
+ levelLtmp *= CalcDecayCoeffs(1);
+ levelRtmp *= CalcDecayCoeffs(7);
+ levelL += levelRtmp * 0.1444f;
+ levelR += levelLtmp * 0.1444f;
+
+ levelLtmp *= CalcDecayCoeffs(0);
+ levelRtmp *= CalcDecayCoeffs(6);
+ levelL += levelLtmp * 0.1444f;
+ levelR += levelRtmp * 0.1444f;
+
+ // Final Reverb Level
+ float level = std::min(std::pow(10.0f, (Room() + Reverb()) / (100.0f * 20.0f)), 1.0f);
+ float monoInv = 1.0f - ((levelLtmp + levelRtmp) * 0.5f);
+ m_ReverbLevelL = level * std::sqrt(monoInv / levelL);
+ m_ReverbLevelR = level * std::sqrt(monoInv / levelR);
+}
+
+
+float I3DL2Reverb::CalcDecayCoeffs(int32 index)
+{
+ float hfRef = (2.0f * mpt::numbers::pi_v<float>) / m_effectiveSampleRate * HFReference();
+ float decayHFRatio = DecayHFRatio();
+ if(decayHFRatio > 1.0f)
+ hfRef = mpt::numbers::pi_v<float>;
+
+ float c1 = std::pow(10.0f, ((m_delayTaps[index] / m_effectiveSampleRate) * -60.0f / DecayTime()) / 20.0f);
+ float c2 = 0.0f;
+
+ float c21 = (std::pow(c1, 2.0f - 2.0f / decayHFRatio) - 1.0f) / (1.0f - std::cos(hfRef));
+ if(c21 != 0 && std::isfinite(c21))
+ {
+ float c22 = -2.0f * c21 - 2.0f;
+ float c23sq = c22 * c22 - c21 * c21 * 4.0f;
+ float c23 = c23sq > 0.0f ? std::sqrt(c23sq) : 0.0f;
+ c2 = (c23 - c22) / (c21 + c21);
+ if(std::abs(c2) > 1.0f)
+ c2 = (-c22 - c23) / (c21 + c21);
+ c2 = mpt::sanitize_nan(c2);
+ }
+ m_delayCoeffs[index][0] = c1;
+ m_delayCoeffs[index][1] = c2;
+
+ c1 *= c1;
+ float diff2 = m_diffusion * m_diffusion;
+ return diff2 + c1 / (1.0f - diff2 * c1) * (1.0f - diff2) * (1.0f - diff2);
+}
+
+} // namespace DMO
+
+#else
+MPT_MSVC_WORKAROUND_LNK4221(I3DL2Reverb)
+
+#endif // !NO_PLUGINS
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/soundlib/plugins/dmo/I3DL2Reverb.h b/Src/external_dependencies/openmpt-trunk/soundlib/plugins/dmo/I3DL2Reverb.h
new file mode 100644
index 00000000..ee3ea690
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/soundlib/plugins/dmo/I3DL2Reverb.h
@@ -0,0 +1,169 @@
+/*
+ * I3DL2Reverb.h
+ * -------------
+ * Purpose: Implementation of the DMO I3DL2Reverb DSP (for non-Windows platforms)
+ * Notes : (currently none)
+ * Authors: OpenMPT Devs
+ * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
+ */
+
+
+#pragma once
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#ifndef NO_PLUGINS
+
+#include "../PlugInterface.h"
+
+OPENMPT_NAMESPACE_BEGIN
+
+namespace DMO
+{
+
+class I3DL2Reverb final : public IMixPlugin
+{
+protected:
+ enum Parameters
+ {
+ kI3DL2ReverbRoom = 0,
+ kI3DL2ReverbRoomHF,
+ kI3DL2ReverbRoomRolloffFactor, // Doesn't actually do anything :)
+ kI3DL2ReverbDecayTime,
+ kI3DL2ReverbDecayHFRatio,
+ kI3DL2ReverbReflections,
+ kI3DL2ReverbReflectionsDelay,
+ kI3DL2ReverbReverb,
+ kI3DL2ReverbReverbDelay,
+ kI3DL2ReverbDiffusion,
+ kI3DL2ReverbDensity,
+ kI3DL2ReverbHFReference,
+ kI3DL2ReverbQuality,
+ kI3DL2ReverbNumParameters
+ };
+
+ enum QualityFlags
+ {
+ kMoreDelayLines = 0x01,
+ kFullSampleRate = 0x02,
+ };
+
+ class DelayLine : private std::vector<float>
+ {
+ int32 m_length;
+ int32 m_position;
+ int32 m_delayPosition;
+
+ public:
+ void Init(int32 ms, int32 padding, uint32 sampleRate, int32 delayTap = 0);
+ void SetDelayTap(int32 delayTap);
+ void Advance();
+ void Set(float value);
+ float Get(int32 offset) const;
+ float Get() const;
+ };
+
+ std::array<float, kI3DL2ReverbNumParameters> m_param;
+ int32 m_program = 0;
+
+ // Calculated parameters
+ uint32 m_quality;
+ float m_effectiveSampleRate;
+ float m_diffusion;
+ float m_roomFilter;
+ float m_ERLevel;
+ float m_ReverbLevelL;
+ float m_ReverbLevelR;
+
+ int32 m_delayTaps[15]; // 6*L + 6*R + LR + Early L + Early R
+ int32 m_earlyTaps[2][6];
+ float m_delayCoeffs[13][2];
+
+ // State
+ DelayLine m_delayLines[19];
+ float m_filterHist[19];
+
+ // Remaining frame for downsampled reverb
+ float m_prevL;
+ float m_prevR;
+ bool m_remain = false;
+
+ bool m_ok = false, m_recalcParams = true;
+
+public:
+ static IMixPlugin* Create(VSTPluginLib &factory, CSoundFile &sndFile, SNDMIXPLUGIN *mixStruct);
+ I3DL2Reverb(VSTPluginLib &factory, CSoundFile &sndFile, SNDMIXPLUGIN *mixStruct);
+
+ void Release() override { delete this; }
+ int32 GetUID() const override { return 0xEF985E71; }
+ int32 GetVersion() const override { return 0; }
+ void Idle() override { }
+ uint32 GetLatency() const override { return 0; }
+
+ void Process(float *pOutL, float *pOutR, uint32 numFrames) override;
+
+ float RenderSilence(uint32) override { return 0.0f; }
+
+ int32 GetNumPrograms() const override;
+ int32 GetCurrentProgram() override { return m_program; }
+ // cppcheck-suppress virtualCallInConstructor
+ void SetCurrentProgram(int32) override;
+
+ PlugParamIndex GetNumParameters() const override { return kI3DL2ReverbNumParameters; }
+ PlugParamValue GetParameter(PlugParamIndex index) override;
+ void SetParameter(PlugParamIndex index, PlugParamValue value) override;
+
+ void Resume() override;
+ void Suspend() override { m_isResumed = false; }
+ void PositionChanged() override;
+ bool IsInstrument() const override { return false; }
+ bool CanRecieveMidiEvents() override { return false; }
+ bool ShouldProcessSilence() override { return true; }
+
+#ifdef MODPLUG_TRACKER
+ CString GetDefaultEffectName() override { return _T("I3DL2Reverb"); }
+
+ CString GetParamName(PlugParamIndex param) override;
+ CString GetParamLabel(PlugParamIndex) override;
+ CString GetParamDisplay(PlugParamIndex param) override;
+
+ CString GetCurrentProgramName() override;
+ void SetCurrentProgramName(const CString &) override { }
+ CString GetProgramName(int32 program) override;
+
+ bool HasEditor() const override { return false; }
+#endif
+
+ void BeginSetProgram(int32) override { }
+ void EndSetProgram() override { }
+
+ int GetNumInputChannels() const override { return 2; }
+ int GetNumOutputChannels() const override { return 2; }
+
+protected:
+ float Room() const { return -10000.0f + m_param[kI3DL2ReverbRoom] * 10000.0f; }
+ float RoomHF() const { return -10000.0f + m_param[kI3DL2ReverbRoomHF] * 10000.0f; }
+ float RoomRolloffFactor() const { return m_param[kI3DL2ReverbRoomRolloffFactor] * 10.0f; }
+ float DecayTime() const { return 0.1f + m_param[kI3DL2ReverbDecayTime] * 19.9f; }
+ float DecayHFRatio() const { return 0.1f + m_param[kI3DL2ReverbDecayHFRatio] * 1.9f; }
+ float Reflections() const { return -10000.0f + m_param[kI3DL2ReverbReflections] * 11000.0f; }
+ float ReflectionsDelay() const { return m_param[kI3DL2ReverbReflectionsDelay] * 0.3f; }
+ float Reverb() const { return -10000.0f + m_param[kI3DL2ReverbReverb] * 12000.0f; }
+ float ReverbDelay() const { return m_param[kI3DL2ReverbReverbDelay] * 0.1f; }
+ float Diffusion() const { return m_param[kI3DL2ReverbDiffusion] * 100.0f; }
+ float Density() const { return m_param[kI3DL2ReverbDensity] * 100.0f; }
+ float HFReference() const { return 20.0f + m_param[kI3DL2ReverbHFReference] * 19980.0f; }
+ uint32 Quality() const { return mpt::saturate_round<uint32>(m_param[kI3DL2ReverbQuality] * 3.0f); }
+
+ void RecalculateI3DL2ReverbParams();
+
+ void SetDelayTaps();
+ void SetDecayCoeffs();
+ float CalcDecayCoeffs(int32 index);
+};
+
+} // namespace DMO
+
+OPENMPT_NAMESPACE_END
+
+#endif // !NO_PLUGINS
diff --git a/Src/external_dependencies/openmpt-trunk/soundlib/plugins/dmo/ParamEq.cpp b/Src/external_dependencies/openmpt-trunk/soundlib/plugins/dmo/ParamEq.cpp
new file mode 100644
index 00000000..eb237d6f
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/soundlib/plugins/dmo/ParamEq.cpp
@@ -0,0 +1,201 @@
+/*
+ * ParamEq.cpp
+ * -----------
+ * Purpose: Implementation of the DMO Parametric Equalizer DSP (for non-Windows platforms)
+ * Notes : (currently none)
+ * Authors: OpenMPT Devs
+ * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
+ */
+
+
+#include "stdafx.h"
+
+#ifndef NO_PLUGINS
+#include "../../Sndfile.h"
+#include "ParamEq.h"
+#include "mpt/base/numbers.hpp"
+#endif // !NO_PLUGINS
+
+OPENMPT_NAMESPACE_BEGIN
+
+#ifndef NO_PLUGINS
+
+namespace DMO
+{
+
+IMixPlugin* ParamEq::Create(VSTPluginLib &factory, CSoundFile &sndFile, SNDMIXPLUGIN *mixStruct)
+{
+ return new (std::nothrow) ParamEq(factory, sndFile, mixStruct);
+}
+
+
+ParamEq::ParamEq(VSTPluginLib &factory, CSoundFile &sndFile, SNDMIXPLUGIN *mixStruct)
+ : IMixPlugin(factory, sndFile, mixStruct)
+ , m_maxFreqParam(1.0f)
+{
+ m_param[kEqCenter] = (8000.0f - 80.0f) / 15920.0f;
+ m_param[kEqBandwidth] = 0.314286f;
+ m_param[kEqGain] = 0.5f;
+
+ m_mixBuffer.Initialize(2, 2);
+ InsertIntoFactoryList();
+}
+
+
+void ParamEq::Process(float *pOutL, float *pOutR, uint32 numFrames)
+{
+ if(!m_mixBuffer.Ok())
+ return;
+
+ const float *in[2] = { m_mixBuffer.GetInputBuffer(0), m_mixBuffer.GetInputBuffer(1) };
+ float *out[2] = { m_mixBuffer.GetOutputBuffer(0), m_mixBuffer.GetOutputBuffer(1) };
+
+ if(m_param[kEqGain] == 0.5f)
+ {
+ memcpy(out[0], in[0], numFrames * sizeof(float));
+ memcpy(out[1], in[1], numFrames * sizeof(float));
+ } else
+ {
+ for(uint32 i = numFrames; i != 0; i--)
+ {
+ for(uint8 channel = 0; channel < 2; channel++)
+ {
+ float x = *(in[channel])++;
+ float y = b0DIVa0 * x + b1DIVa0 * x1[channel] + b2DIVa0 * x2[channel] - a1DIVa0 * y1[channel] - a2DIVa0 * y2[channel];
+
+ x2[channel] = x1[channel];
+ x1[channel] = x;
+ y2[channel] = y1[channel];
+ y1[channel] = y;
+
+ *(out[channel])++ = y;
+ }
+ }
+ }
+
+ ProcessMixOps(pOutL, pOutR, m_mixBuffer.GetOutputBuffer(0), m_mixBuffer.GetOutputBuffer(1), numFrames);
+}
+
+
+PlugParamValue ParamEq::GetParameter(PlugParamIndex index)
+{
+ if(index < kEqNumParameters)
+ {
+ return m_param[index];
+ }
+ return 0;
+}
+
+
+void ParamEq::SetParameter(PlugParamIndex index, PlugParamValue value)
+{
+ if(index < kEqNumParameters)
+ {
+ value = mpt::safe_clamp(value, 0.0f, 1.0f);
+ m_param[index] = value;
+ RecalculateEqParams();
+ }
+}
+
+
+void ParamEq::Resume()
+{
+ m_isResumed = true;
+ // Limit center frequency to a third of the sampling rate.
+ m_maxFreqParam = Clamp((m_SndFile.GetSampleRate() / 3.0f - 80.0f) / 15920.0f, 0.0f, 1.0f);
+ RecalculateEqParams();
+ PositionChanged();
+}
+
+
+void ParamEq::PositionChanged()
+{
+ // Reset filter state
+ x1[0] = x2[0] = 0;
+ x1[1] = x2[1] = 0;
+ y1[0] = y2[0] = 0;
+ y1[1] = y2[1] = 0;
+}
+
+
+#ifdef MODPLUG_TRACKER
+
+CString ParamEq::GetParamName(PlugParamIndex param)
+{
+ switch(param)
+ {
+ case kEqCenter: return _T("Center");
+ case kEqBandwidth: return _T("Bandwidth");
+ case kEqGain: return _T("Gain");
+ }
+ return CString();
+}
+
+
+CString ParamEq::GetParamLabel(PlugParamIndex param)
+{
+ switch(param)
+ {
+ case kEqCenter: return _T("Hz");
+ case kEqBandwidth: return _T("Semitones");
+ case kEqGain: return _T("dB");
+ }
+ return CString();
+}
+
+
+CString ParamEq::GetParamDisplay(PlugParamIndex param)
+{
+ float value = 0.0f;
+ switch(param)
+ {
+ case kEqCenter:
+ value = FreqInHertz();
+ break;
+ case kEqBandwidth:
+ value = BandwidthInSemitones();
+ break;
+ case kEqGain:
+ value = GainInDecibel();
+ break;
+ }
+ CString s;
+ s.Format(_T("%.2f"), value);
+ return s;
+}
+
+#endif // MODPLUG_TRACKER
+
+
+void ParamEq::RecalculateEqParams()
+{
+ LimitMax(m_param[kEqCenter], m_maxFreqParam);
+ const float freq = FreqInHertz() / m_SndFile.GetSampleRate();
+ const float a = std::pow(10.0f, GainInDecibel() / 40.0f);
+ const float w0 = 2.0f * mpt::numbers::pi_v<float> * freq;
+ const float sinW0 = std::sin(w0);
+ const float cosW0 = std::cos(w0);
+ const float alpha = sinW0 * std::sinh((BandwidthInSemitones() * (mpt::numbers::ln2_v<float> / 24.0f)) * w0 / sinW0);
+
+ const float b0 = 1.0f + alpha * a;
+ const float b1 = -2.0f * cosW0;
+ const float b2 = 1.0f - alpha * a;
+ const float a0 = 1.0f + alpha / a;
+ const float a1 = -2.0f * cosW0;
+ const float a2 = 1.0f - alpha / a;
+
+ b0DIVa0 = b0 / a0;
+ b1DIVa0 = b1 / a0;
+ b2DIVa0 = b2 / a0;
+ a1DIVa0 = a1 / a0;
+ a2DIVa0 = a2 / a0;
+}
+
+} // namespace DMO
+
+#else
+MPT_MSVC_WORKAROUND_LNK4221(ParamEq)
+
+#endif // !NO_PLUGINS
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/soundlib/plugins/dmo/ParamEq.h b/Src/external_dependencies/openmpt-trunk/soundlib/plugins/dmo/ParamEq.h
new file mode 100644
index 00000000..6ad3e4f3
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/soundlib/plugins/dmo/ParamEq.h
@@ -0,0 +1,98 @@
+/*
+ * ParamEq.h
+ * ---------
+ * Purpose: Implementation of the DMO Parametric Equalizer DSP (for non-Windows platforms)
+ * Notes : (currently none)
+ * Authors: OpenMPT Devs
+ * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
+ */
+
+
+#ifndef NO_PLUGINS
+
+#include "../PlugInterface.h"
+
+OPENMPT_NAMESPACE_BEGIN
+
+namespace DMO
+{
+
+class ParamEq final : public IMixPlugin
+{
+protected:
+ enum Parameters
+ {
+ kEqCenter = 0,
+ kEqBandwidth,
+ kEqGain,
+ kEqNumParameters
+ };
+
+ std::array<float, kEqNumParameters> m_param;
+
+ // Equalizer coefficients
+ float b0DIVa0, b1DIVa0, b2DIVa0, a1DIVa0, a2DIVa0;
+ // Equalizer memory
+ float x1[2], x2[2];
+ float y1[2], y2[2];
+ float m_maxFreqParam;
+
+public:
+ static IMixPlugin* Create(VSTPluginLib &factory, CSoundFile &sndFile, SNDMIXPLUGIN *mixStruct);
+ ParamEq(VSTPluginLib &factory, CSoundFile &sndFile, SNDMIXPLUGIN *mixStruct);
+
+ void Release() override { delete this; }
+ int32 GetUID() const override { return 0x120CED89; }
+ int32 GetVersion() const override { return 0; }
+ void Idle() override { }
+ uint32 GetLatency() const override { return 0; }
+
+ void Process(float *pOutL, float *pOutR, uint32 numFrames) override;
+
+ float RenderSilence(uint32) override { return 0.0f; }
+
+ int32 GetNumPrograms() const override { return 0; }
+ int32 GetCurrentProgram() override { return 0; }
+ void SetCurrentProgram(int32) override { }
+
+ PlugParamIndex GetNumParameters() const override { return kEqNumParameters; }
+ PlugParamValue GetParameter(PlugParamIndex index) override;
+ void SetParameter(PlugParamIndex index, PlugParamValue value) override;
+
+ void Resume() override;
+ void Suspend() override { m_isResumed = false; }
+ void PositionChanged() override;
+
+ bool IsInstrument() const override { return false; }
+ bool CanRecieveMidiEvents() override { return false; }
+ bool ShouldProcessSilence() override { return true; }
+
+#ifdef MODPLUG_TRACKER
+ CString GetDefaultEffectName() override { return _T("ParamEq"); }
+
+ CString GetParamName(PlugParamIndex param) override;
+ CString GetParamLabel(PlugParamIndex) override;
+ CString GetParamDisplay(PlugParamIndex param) override;
+
+ CString GetCurrentProgramName() override { return CString(); }
+ void SetCurrentProgramName(const CString &) override { }
+ CString GetProgramName(int32) override { return CString(); }
+
+ bool HasEditor() const override { return false; }
+#endif
+
+ int GetNumInputChannels() const override { return 2; }
+ int GetNumOutputChannels() const override { return 2; }
+
+protected:
+ float BandwidthInSemitones() const { return 1.0f + m_param[kEqBandwidth] * 35.0f; }
+ float FreqInHertz() const { return 80.0f + m_param[kEqCenter] * 15920.0f; }
+ float GainInDecibel() const { return (m_param[kEqGain] - 0.5f) * 30.0f; }
+ void RecalculateEqParams();
+};
+
+} // namespace DMO
+
+OPENMPT_NAMESPACE_END
+
+#endif // !NO_PLUGINS
diff --git a/Src/external_dependencies/openmpt-trunk/soundlib/plugins/dmo/WavesReverb.cpp b/Src/external_dependencies/openmpt-trunk/soundlib/plugins/dmo/WavesReverb.cpp
new file mode 100644
index 00000000..3f8757a2
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/soundlib/plugins/dmo/WavesReverb.cpp
@@ -0,0 +1,261 @@
+/*
+ * WavesReverb.cpp
+ * ---------------
+ * Purpose: Implementation of the DMO WavesReverb DSP (for non-Windows platforms)
+ * Notes : (currently none)
+ * Authors: OpenMPT Devs
+ * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
+ */
+
+
+#include "stdafx.h"
+
+#ifndef NO_PLUGINS
+#include "../../Sndfile.h"
+#include "WavesReverb.h"
+#endif // !NO_PLUGINS
+
+OPENMPT_NAMESPACE_BEGIN
+
+#ifndef NO_PLUGINS
+
+namespace DMO
+{
+
+IMixPlugin* WavesReverb::Create(VSTPluginLib &factory, CSoundFile &sndFile, SNDMIXPLUGIN *mixStruct)
+{
+ return new (std::nothrow) WavesReverb(factory, sndFile, mixStruct);
+}
+
+
+WavesReverb::WavesReverb(VSTPluginLib &factory, CSoundFile &sndFile, SNDMIXPLUGIN *mixStruct)
+ : IMixPlugin(factory, sndFile, mixStruct)
+{
+ m_param[kRvbInGain] = 1.0f;
+ m_param[kRvbReverbMix] = 1.0f;
+ m_param[kRvbReverbTime] = 1.0f / 3.0f;
+ m_param[kRvbHighFreqRTRatio] = 0.0f;
+
+ m_mixBuffer.Initialize(2, 2);
+ InsertIntoFactoryList();
+}
+
+
+void WavesReverb::Process(float *pOutL, float *pOutR, uint32 numFrames)
+{
+ if(!m_mixBuffer.Ok())
+ return;
+
+ const float *in[2] = { m_mixBuffer.GetInputBuffer(0), m_mixBuffer.GetInputBuffer(1) };
+ float *out[2] = { m_mixBuffer.GetOutputBuffer(0), m_mixBuffer.GetOutputBuffer(1) };
+
+ uint32 combPos = m_state.combPos, allpassPos = m_state.allpassPos;
+ uint32 delay0 = (m_delay[0] + combPos + 1) & 0xFFF;
+ uint32 delay1 = (m_delay[1] + combPos + 1) & 0xFFF;
+ uint32 delay2 = (m_delay[2] + combPos + 1) & 0xFFF;
+ uint32 delay3 = (m_delay[3] + combPos + 1) & 0xFFF;
+ uint32 delay4 = (m_delay[4] + allpassPos) & 0x3FF;
+ uint32 delay5 = (m_delay[5] + allpassPos) & 0x3FF;
+ float delay0old = m_state.comb[delay0][0];
+ float delay1old = m_state.comb[delay1][1];
+ float delay2old = m_state.comb[delay2][2];
+ float delay3old = m_state.comb[delay3][3];
+
+ for(uint32 i = numFrames; i != 0; i--)
+ {
+ const float leftIn = *(in[0])++ + 1e-30f; // Prevent denormals
+ const float rightIn = *(in[1])++ + 1e-30f; // Prevent denormals
+
+ // Advance buffer index for the four comb filters
+ delay0 = (delay0 - 1) & 0xFFF;
+ delay1 = (delay1 - 1) & 0xFFF;
+ delay2 = (delay2 - 1) & 0xFFF;
+ delay3 = (delay3 - 1) & 0xFFF;
+ float &delay0new = m_state.comb[delay0][0];
+ float &delay1new = m_state.comb[delay1][1];
+ float &delay2new = m_state.comb[delay2][2];
+ float &delay3new = m_state.comb[delay3][3];
+
+ float r1, r2;
+
+ r1 = delay1new * 0.61803401f + m_state.allpass1[delay4][0] * m_coeffs[0];
+ r2 = m_state.allpass1[delay4][1] * m_coeffs[0] - delay0new * 0.61803401f;
+ m_state.allpass1[allpassPos][0] = r2 * 0.61803401f + delay0new;
+ m_state.allpass1[allpassPos][1] = delay1new - r1 * 0.61803401f;
+ delay0new = r1;
+ delay1new = r2;
+
+ r1 = delay3new * 0.61803401f + m_state.allpass2[delay5][0] * m_coeffs[1];
+ r2 = m_state.allpass2[delay5][1] * m_coeffs[1] - delay2new * 0.61803401f;
+ m_state.allpass2[allpassPos][0] = r2 * 0.61803401f + delay2new;
+ m_state.allpass2[allpassPos][1] = delay3new - r1 * 0.61803401f;
+ delay2new = r1;
+ delay3new = r2;
+
+ *(out[0])++ = (leftIn * m_dryFactor) + delay0new + delay2new;
+ *(out[1])++ = (rightIn * m_dryFactor) + delay1new + delay3new;
+
+ const float leftWet = leftIn * m_wetFactor;
+ const float rightWet = rightIn * m_wetFactor;
+ m_state.comb[combPos][0] = (delay0new * m_coeffs[2]) + (delay0old * m_coeffs[3]) + leftWet;
+ m_state.comb[combPos][1] = (delay1new * m_coeffs[4]) + (delay1old * m_coeffs[5]) + rightWet;
+ m_state.comb[combPos][2] = (delay2new * m_coeffs[6]) + (delay2old * m_coeffs[7]) - rightWet;
+ m_state.comb[combPos][3] = (delay3new * m_coeffs[8]) + (delay3old * m_coeffs[9]) + leftWet;
+
+ delay0old = delay0new;
+ delay1old = delay1new;
+ delay2old = delay2new;
+ delay3old = delay3new;
+
+ // Advance buffer index
+ combPos = (combPos - 1) & 0xFFF;
+ allpassPos = (allpassPos - 1) & 0x3FF;
+ delay4 = (delay4 - 1) & 0x3FF;
+ delay5 = (delay5 - 1) & 0x3FF;
+ }
+ m_state.combPos = combPos;
+ m_state.allpassPos = allpassPos;
+
+ ProcessMixOps(pOutL, pOutR, m_mixBuffer.GetOutputBuffer(0), m_mixBuffer.GetOutputBuffer(1), numFrames);
+}
+
+
+PlugParamValue WavesReverb::GetParameter(PlugParamIndex index)
+{
+ if(index < kRvbNumParameters)
+ {
+ return m_param[index];
+ }
+ return 0;
+}
+
+
+void WavesReverb::SetParameter(PlugParamIndex index, PlugParamValue value)
+{
+ if(index < kRvbNumParameters)
+ {
+ value = mpt::safe_clamp(value, 0.0f, 1.0f);
+ m_param[index] = value;
+ RecalculateWavesReverbParams();
+ }
+}
+
+
+void WavesReverb::Resume()
+{
+ m_isResumed = true;
+ // Recalculate delays
+ uint32 delay0 = mpt::saturate_round<uint32>(m_SndFile.GetSampleRate() * 0.045f);
+ uint32 delay1 = mpt::saturate_round<uint32>(delay0 * 1.18920707f); // 2^0.25
+ uint32 delay2 = mpt::saturate_round<uint32>(delay1 * 1.18920707f);
+ uint32 delay3 = mpt::saturate_round<uint32>(delay2 * 1.18920707f);
+ uint32 delay4 = mpt::saturate_round<uint32>((delay0 + delay2) * 0.11546667f);
+ uint32 delay5 = mpt::saturate_round<uint32>((delay1 + delay3) * 0.11546667f);
+ // Comb delays
+ m_delay[0] = delay0 - delay4;
+ m_delay[1] = delay2 - delay4;
+ m_delay[2] = delay1 - delay5;
+ m_delay[3] = delay3 - delay5;
+ // Allpass delays
+ m_delay[4] = delay4;
+ m_delay[5] = delay5;
+
+ RecalculateWavesReverbParams();
+ PositionChanged();
+}
+
+
+void WavesReverb::PositionChanged()
+{
+ MemsetZero(m_state);
+}
+
+
+#ifdef MODPLUG_TRACKER
+
+CString WavesReverb::GetParamName(PlugParamIndex param)
+{
+ switch(param)
+ {
+ case kRvbInGain: return _T("InGain");
+ case kRvbReverbMix: return _T("ReverbMix");
+ case kRvbReverbTime: return _T("ReverbTime");
+ case kRvbHighFreqRTRatio: return _T("HighFreqRTRatio");
+ }
+ return CString();
+}
+
+
+CString WavesReverb::GetParamLabel(PlugParamIndex param)
+{
+ switch(param)
+ {
+ case kRvbInGain:
+ case kRvbReverbMix:
+ return _T("dB");
+ case kRvbReverbTime:
+ return _T("ms");
+ }
+ return CString();
+}
+
+
+CString WavesReverb::GetParamDisplay(PlugParamIndex param)
+{
+ float value = m_param[param];
+ switch(param)
+ {
+ case kRvbInGain:
+ case kRvbReverbMix:
+ value = GainInDecibel(value);
+ break;
+ case kRvbReverbTime:
+ value = ReverbTime();
+ break;
+ case kRvbHighFreqRTRatio:
+ value = HighFreqRTRatio();
+ break;
+ }
+ CString s;
+ s.Format(_T("%.2f"), value);
+ return s;
+}
+
+#endif // MODPLUG_TRACKER
+
+
+void WavesReverb::RecalculateWavesReverbParams()
+{
+ // Recalculate filters
+ const double ReverbTimeSmp = -3000.0 / (m_SndFile.GetSampleRate() * ReverbTime());
+ const double ReverbTimeSmpHF = ReverbTimeSmp * (1.0 / HighFreqRTRatio() - 1.0);
+
+ m_coeffs[0] = static_cast<float>(std::pow(10.0, m_delay[4] * ReverbTimeSmp));
+ m_coeffs[1] = static_cast<float>(std::pow(10.0, m_delay[5] * ReverbTimeSmp));
+
+ double sum = 0.0;
+ for(uint32 pair = 0; pair < 4; pair++)
+ {
+ double gain1 = std::pow(10.0, m_delay[pair] * ReverbTimeSmp);
+ double gain2 = (1.0 - std::pow(10.0, (m_delay[pair] + m_delay[4 + pair / 2]) * ReverbTimeSmpHF)) * 0.5;
+ double gain3 = gain1 * m_coeffs[pair / 2];
+ double gain4 = gain3 * (((gain3 + 1.0) * gain3 + 1.0) * gain3 + 1.0) + 1.0;
+ m_coeffs[2 + pair * 2] = static_cast<float>(gain1 * (1.0 - gain2));
+ m_coeffs[3 + pair * 2] = static_cast<float>(gain1 * gain2);
+ sum += gain4 * gain4;
+ }
+
+ double inGain = std::pow(10.0, GainInDecibel(m_param[kRvbInGain]) * 0.05);
+ double reverbMix = std::pow(10.0, GainInDecibel(m_param[kRvbReverbMix]) * 0.1);
+ m_dryFactor = static_cast<float>(std::sqrt(1.0 - reverbMix) * inGain);
+ m_wetFactor = static_cast<float>(std::sqrt(reverbMix) * (4.0 / std::sqrt(sum) * inGain));
+}
+
+} // namespace DMO
+
+#else
+MPT_MSVC_WORKAROUND_LNK4221(WavesReverb)
+
+#endif // !NO_PLUGINS
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/soundlib/plugins/dmo/WavesReverb.h b/Src/external_dependencies/openmpt-trunk/soundlib/plugins/dmo/WavesReverb.h
new file mode 100644
index 00000000..13e9af1e
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/soundlib/plugins/dmo/WavesReverb.h
@@ -0,0 +1,107 @@
+/*
+ * WavesReverb.h
+ * -------------
+ * Purpose: Implementation of the DMO WavesReverb DSP (for non-Windows platforms)
+ * Notes : (currently none)
+ * Authors: OpenMPT Devs
+ * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
+ */
+
+
+#ifndef NO_PLUGINS
+
+#include "../PlugInterface.h"
+
+OPENMPT_NAMESPACE_BEGIN
+
+namespace DMO
+{
+
+class WavesReverb final : public IMixPlugin
+{
+protected:
+ enum Parameters
+ {
+ kRvbInGain = 0,
+ kRvbReverbMix,
+ kRvbReverbTime,
+ kRvbHighFreqRTRatio,
+ kRvbNumParameters
+ };
+
+ std::array<float, kRvbNumParameters> m_param;
+
+ // Parameters and coefficients
+ float m_dryFactor;
+ float m_wetFactor;
+ std::array<float, 10> m_coeffs;
+ std::array<uint32, 6> m_delay;
+
+ // State
+ struct ReverbState
+ {
+ uint32 combPos, allpassPos;
+ float comb[4096][4];
+ float allpass1[1024][2];
+ float allpass2[1024][2];
+ } m_state;
+
+public:
+ static IMixPlugin* Create(VSTPluginLib &factory, CSoundFile &sndFile, SNDMIXPLUGIN *mixStruct);
+ WavesReverb(VSTPluginLib &factory, CSoundFile &sndFile, SNDMIXPLUGIN *mixStruct);
+
+ void Release() override { delete this; }
+ int32 GetUID() const override { return 0x87FC0268; }
+ int32 GetVersion() const override { return 0; }
+ void Idle() override { }
+ uint32 GetLatency() const override { return 0; }
+
+ void Process(float *pOutL, float *pOutR, uint32 numFrames) override;
+
+ float RenderSilence(uint32) override { return 0.0f; }
+
+ int32 GetNumPrograms() const override { return 0; }
+ int32 GetCurrentProgram() override { return 0; }
+ void SetCurrentProgram(int32) override { }
+
+ PlugParamIndex GetNumParameters() const override { return kRvbNumParameters; }
+ PlugParamValue GetParameter(PlugParamIndex index) override;
+ void SetParameter(PlugParamIndex index, PlugParamValue value) override;
+
+ void Resume() override;
+ void Suspend() override { m_isResumed = false; }
+ void PositionChanged() override;
+
+ bool IsInstrument() const override { return false; }
+ bool CanRecieveMidiEvents() override { return false; }
+ bool ShouldProcessSilence() override { return true; }
+
+#ifdef MODPLUG_TRACKER
+ CString GetDefaultEffectName() override { return _T("WavesReverb"); }
+
+ CString GetParamName(PlugParamIndex param) override;
+ CString GetParamLabel(PlugParamIndex) override;
+ CString GetParamDisplay(PlugParamIndex param) override;
+
+ CString GetCurrentProgramName() override { return CString(); }
+ void SetCurrentProgramName(const CString &) override { }
+ CString GetProgramName(int32) override { return CString(); }
+
+ bool HasEditor() const override { return false; }
+#endif
+
+ int GetNumInputChannels() const override { return 2; }
+ int GetNumOutputChannels() const override { return 2; }
+
+protected:
+ static float GainInDecibel(float param) { return -96.0f + param * 96.0f; }
+ float ReverbTime() const { return 0.001f + m_param[kRvbReverbTime] * 2999.999f; }
+ float HighFreqRTRatio() const { return 0.001f + m_param[kRvbHighFreqRTRatio] * 0.998f; }
+ void RecalculateWavesReverbParams();
+};
+
+} // namespace DMO
+
+OPENMPT_NAMESPACE_END
+
+#endif // !NO_PLUGINS