/* * 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(waveMin * m_depthDelay); m_delayR = m_delayOffset + (phase < 2 ? -1 : 1) * static_cast(((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(m_SndFile.GetSampleRate()); float delaySamples = Delay() * sampleRate / 1000.0f; m_depthDelay = Depth() * delaySamples * 2048.0f; m_delayOffset = mpt::saturate_round(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) * 2.0f; } } // namespace DMO #else MPT_MSVC_WORKAROUND_LNK4221(Chorus) #endif // !NO_PLUGINS OPENMPT_NAMESPACE_END