#pragma once #include #include #include "Parameters.h" #include "ModulatedDelay.h" #include "MultitapDelay.h" #include "RandomBuffer.h" #include "Lp1.h" #include "Hp1.h" #include "DelayLine.h" #include "AllpassDiffuser.h" #include #include "ReverbChannel.h" #include "Utils.h" namespace ReverbHallRoom { enum class ChannelLR { Left, Right }; class ReverbChannel { private: static const int TotalLineCount = 12; double paramsScaled[Parameter::COUNT] = { 0.0 }; int samplerate; ModulatedDelay preDelay; MultitapDelay multitap; AllpassDiffuser diffuser; DelayLine lines[TotalLineCount]; RandomBuffer rand; Hp1 highPass; Lp1 lowPass; int delayLineSeed; int postDiffusionSeed; // Used the the main process loop int lineCount; bool lowCutEnabled; bool highCutEnabled; bool multitapEnabled; bool diffuserEnabled; float inputMix; float dryOut; float earlyOut; float lineOut; float crossSeed; ChannelLR channelLr; public: ReverbChannel(int samplerate, ChannelLR leftOrRight) { this->channelLr = leftOrRight; crossSeed = 0.0; lineCount = 8; diffuser.SetInterpolationEnabled(true); highPass.SetCutoffHz(20); lowPass.SetCutoffHz(20000); SetSamplerate(samplerate); } int GetSamplerate() { return samplerate; } void SetSamplerate(int samplerate) { this->samplerate = samplerate; highPass.SetSamplerate(samplerate); lowPass.SetSamplerate(samplerate); diffuser.SetSamplerate(samplerate); for (int i = 0; i < TotalLineCount; i++) lines[i].SetSamplerate(samplerate); ReapplyAllParams(); ClearBuffers(); UpdateLines(); } void ReapplyAllParams() { for (int i = 0; i < Parameter::COUNT; i++) SetParameter(i, paramsScaled[i]); } void SetParameter(int para, double scaledValue) { paramsScaled[para] = scaledValue; switch (para) { case Parameter::Interpolation: for (int i = 0; i < TotalLineCount; i++) lines[i].SetInterpolationEnabled(scaledValue >= 0.5); break; case Parameter::LowCutEnabled: lowCutEnabled = scaledValue >= 0.5; if (lowCutEnabled) highPass.ClearBuffers(); break; case Parameter::HighCutEnabled: highCutEnabled = scaledValue >= 0.5; if (highCutEnabled) lowPass.ClearBuffers(); break; case Parameter::InputMix: inputMix = scaledValue; break; case Parameter::LowCut: highPass.SetCutoffHz(scaledValue); break; case Parameter::HighCut: lowPass.SetCutoffHz(scaledValue); break; case Parameter::DryOut: dryOut = scaledValue <= -30 ? 0.0 : Utils::DB2Gainf(scaledValue); break; case Parameter::EarlyOut: earlyOut = scaledValue <= -30 ? 0.0 : Utils::DB2Gainf(scaledValue); break; case Parameter::LateOut: lineOut = scaledValue <= -30 ? 0.0 : Utils::DB2Gainf(scaledValue); break; case Parameter::TapEnabled: { auto newVal = scaledValue >= 0.5; if (newVal != multitapEnabled) multitap.ClearBuffers(); multitapEnabled = newVal; break; } case Parameter::TapCount: multitap.SetTapCount((int)scaledValue); break; case Parameter::TapDecay: multitap.SetTapDecay(scaledValue); break; case Parameter::TapPredelay: preDelay.SampleDelay = (int)Ms2Samples(scaledValue); break; case Parameter::TapLength: multitap.SetTapLength((int)Ms2Samples(scaledValue)); break; case Parameter::EarlyDiffuseEnabled: { auto newVal = scaledValue >= 0.5; if (newVal != diffuserEnabled) diffuser.ClearBuffers(); diffuserEnabled = newVal; break; } case Parameter::EarlyDiffuseCount: diffuser.Stages = (int)scaledValue; break; case Parameter::EarlyDiffuseDelay: diffuser.SetDelay((int)Ms2Samples(scaledValue)); break; case Parameter::EarlyDiffuseModAmount: diffuser.SetModulationEnabled(scaledValue > 0.5); diffuser.SetModAmount(Ms2Samples(scaledValue)); break; case Parameter::EarlyDiffuseFeedback: diffuser.SetFeedback(scaledValue); break; case Parameter::EarlyDiffuseModRate: diffuser.SetModRate(scaledValue); break; case Parameter::LateMode: for (int i = 0; i < TotalLineCount; i++) lines[i].TapPostDiffuser = scaledValue >= 0.5; break; case Parameter::LateLineCount: lineCount = (int)scaledValue; break; case Parameter::LateDiffuseEnabled: for (int i = 0; i < TotalLineCount; i++) { auto newVal = scaledValue >= 0.5; if (newVal != lines[i].DiffuserEnabled) lines[i].ClearDiffuserBuffer(); lines[i].DiffuserEnabled = newVal; } break; case Parameter::LateDiffuseCount: for (int i = 0; i < TotalLineCount; i++) lines[i].SetDiffuserStages((int)scaledValue); break; case Parameter::LateLineSize: UpdateLines(); break; case Parameter::LateLineModAmount: UpdateLines(); break; case Parameter::LateDiffuseDelay: for (int i = 0; i < TotalLineCount; i++) lines[i].SetDiffuserDelay((int)Ms2Samples(scaledValue)); break; case Parameter::LateDiffuseModAmount: UpdateLines(); break; case Parameter::LateLineDecay: UpdateLines(); break; case Parameter::LateLineModRate: UpdateLines(); break; case Parameter::LateDiffuseFeedback: for (int i = 0; i < TotalLineCount; i++) lines[i].SetDiffuserFeedback(scaledValue); break; case Parameter::LateDiffuseModRate: UpdateLines(); break; case Parameter::EqLowShelfEnabled: for (int i = 0; i < TotalLineCount; i++) lines[i].LowShelfEnabled = scaledValue >= 0.5; break; case Parameter::EqHighShelfEnabled: for (int i = 0; i < TotalLineCount; i++) lines[i].HighShelfEnabled = scaledValue >= 0.5; break; case Parameter::EqLowpassEnabled: for (int i = 0; i < TotalLineCount; i++) lines[i].CutoffEnabled = scaledValue >= 0.5; break; case Parameter::EqLowFreq: for (int i = 0; i < TotalLineCount; i++) lines[i].SetLowShelfFrequency(scaledValue); break; case Parameter::EqHighFreq: for (int i = 0; i < TotalLineCount; i++) lines[i].SetHighShelfFrequency(scaledValue); break; case Parameter::EqCutoff: for (int i = 0; i < TotalLineCount; i++) lines[i].SetCutoffFrequency(scaledValue); break; case Parameter::EqLowGain: for (int i = 0; i < TotalLineCount; i++) lines[i].SetLowShelfGain(scaledValue); break; case Parameter::EqHighGain: for (int i = 0; i < TotalLineCount; i++) lines[i].SetHighShelfGain(scaledValue); break; case Parameter::EqCrossSeed: crossSeed = channelLr == ChannelLR::Right ? 0.5 * scaledValue : 1 - 0.5 * scaledValue; multitap.SetCrossSeed(crossSeed); diffuser.SetCrossSeed(crossSeed); UpdateLines(); UpdatePostDiffusion(); break; case Parameter::SeedTap: multitap.SetSeed((int)scaledValue); break; case Parameter::SeedDiffusion: diffuser.SetSeed((int)scaledValue); break; case Parameter::SeedDelay: delayLineSeed = (int)scaledValue; UpdateLines(); break; case Parameter::SeedPostDiffusion: postDiffusionSeed = (int)scaledValue; UpdatePostDiffusion(); break; } } void Process(float* input, float* output, int bufSize) { float tempBuffer[BUFFER_SIZE]; float earlyOutBuffer[BUFFER_SIZE]; float lineOutBuffer[BUFFER_SIZE]; float lineSumBuffer[BUFFER_SIZE]; Utils::Copy(tempBuffer, input, bufSize); if (lowCutEnabled) highPass.Process(tempBuffer, tempBuffer, bufSize); if (highCutEnabled) lowPass.Process(tempBuffer, tempBuffer, bufSize); // completely zero if no input present // Previously, the very small values were causing some really strange CPU spikes for (int i = 0; i < bufSize; i++) { auto n = tempBuffer[i]; if (n * n < 0.000000001) tempBuffer[i] = 0; } preDelay.Process(tempBuffer, tempBuffer, bufSize); if (multitapEnabled) multitap.Process(tempBuffer, tempBuffer, bufSize); if (diffuserEnabled) diffuser.Process(tempBuffer, tempBuffer, bufSize); Utils::Copy(earlyOutBuffer, tempBuffer, bufSize); Utils::ZeroBuffer(lineSumBuffer, bufSize); for (int i = 0; i < lineCount; i++) { lines[i].Process(tempBuffer, lineOutBuffer, bufSize); Utils::Mix(lineSumBuffer, lineOutBuffer, 1.0f, bufSize); } auto perLineGain = GetPerLineGain(); Utils::Gain(lineSumBuffer, perLineGain, bufSize); for (int i = 0; i < bufSize; i++) { output[i] = dryOut * input[i] + earlyOut * earlyOutBuffer[i] + lineOut * lineSumBuffer[i]; } } void ClearBuffers() { lowPass.ClearBuffers(); highPass.ClearBuffers(); preDelay.ClearBuffers(); multitap.ClearBuffers(); diffuser.ClearBuffers(); for (int i = 0; i < TotalLineCount; i++) lines[i].ClearBuffers(); } private: float GetPerLineGain() { return 1.0 / std::sqrt(lineCount); } void UpdateLines() { auto lineDelaySamples = (int)Ms2Samples(paramsScaled[Parameter::LateLineSize]); auto lineDecayMillis = paramsScaled[Parameter::LateLineDecay] * 1000; auto lineDecaySamples = Ms2Samples(lineDecayMillis); auto lineModAmount = Ms2Samples(paramsScaled[Parameter::LateLineModAmount]); auto lineModRate = paramsScaled[Parameter::LateLineModRate]; auto lateDiffusionModAmount = Ms2Samples(paramsScaled[Parameter::LateDiffuseModAmount]); auto lateDiffusionModRate = paramsScaled[Parameter::LateDiffuseModRate]; auto delayLineSeeds = RandomBuffer::Generate(delayLineSeed, TotalLineCount * 3, crossSeed); for (int i = 0; i < TotalLineCount; i++) { auto modAmount = lineModAmount * (0.7 + 0.3 * delayLineSeeds[i]); auto modRate = lineModRate * (0.7 + 0.3 * delayLineSeeds[TotalLineCount + i]) / samplerate; auto delaySamples = (0.5 + 1.0 * delayLineSeeds[TotalLineCount * 2 + i]) * lineDelaySamples; if (delaySamples < modAmount + 2) // when the delay is set really short, and the modulation is very high delaySamples = modAmount + 2; // the mod could actually take the delay time negative, prevent that! -- provide 2 extra sample as margin of safety auto dbAfter1Iteration = delaySamples / lineDecaySamples * (-60); // lineDecay is the time it takes to reach T60 auto gainAfter1Iteration = Utils::DB2Gainf(dbAfter1Iteration); lines[i].SetDelay((int)delaySamples); lines[i].SetFeedback(gainAfter1Iteration); lines[i].SetLineModAmount(modAmount); lines[i].SetLineModRate(modRate); lines[i].SetDiffuserModAmount(lateDiffusionModAmount); lines[i].SetDiffuserModRate(lateDiffusionModRate); } } void UpdatePostDiffusion() { for (int i = 0; i < TotalLineCount; i++) lines[i].SetDiffuserSeed((postDiffusionSeed) * (i + 1), crossSeed); } float Ms2Samples(float value) { return value / 1000.0f * samplerate; } }; }