236 lines
5.7 KiB
C++
236 lines
5.7 KiB
C++
|
/*
|
||
|
* 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
|