/* * 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(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 SymMODEcho::GetParamUIRange(PlugParamIndex param) { if(param == kEchoType) return {0.0f, (static_cast(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(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(&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(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(m_chunk.param[kEchoFeedback] + 1)); else m_feedback = std::pow(2.0f, -static_cast(m_chunk.param[kEchoFeedback])); } OPENMPT_NAMESPACE_END #endif // NO_PLUGINS