Initial community commit
This commit is contained in:
parent
537bcbc862
commit
fc06254474
16440 changed files with 4239995 additions and 2 deletions
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
File diff suppressed because it is too large
Load diff
|
@ -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
|
||||
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
|
@ -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
|
||||
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
Loading…
Add table
Add a link
Reference in a new issue