mirror of
https://github.com/WinampDesktop/winamp.git
synced 2024-12-13 13:01:00 -05:00
307 lines
7.5 KiB
C++
307 lines
7.5 KiB
C++
|
/*
|
||
|
* 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
|