diff options
author | Jean-Francois Mauguit <jfmauguit@mac.com> | 2024-09-24 09:03:25 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-09-24 09:03:25 -0400 |
commit | bab614c421ed7ae329d26bf028c4a3b1d2450f5a (patch) | |
tree | 12f17f78986871dd2cfb0a56e5e93b545c1ae0d0 /Src/external_dependencies/openmpt-trunk/soundlib/plugins | |
parent | 4bde6044fddf053f31795b9eaccdd2a5a527d21f (diff) | |
parent | 20d28e80a5c861a9d5f449ea911ab75b4f37ad0d (diff) | |
download | winamp-bab614c421ed7ae329d26bf028c4a3b1d2450f5a.tar.gz |
Merge pull request #5 from WinampDesktop/community
Merge to main
Diffstat (limited to 'Src/external_dependencies/openmpt-trunk/soundlib/plugins')
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 |