272 lines
6.4 KiB
C++
272 lines
6.4 KiB
C++
|
/*
|
||
|
* 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
|