/**************************************************************************
Copyright (C) 2019 Arnaud Champenois arthelion@free.fr
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
**************************************************************************/
#pragma once
#include "WaveGenerator.h"
#include "Utils.h"
#include "AudioParameters.h"
/*!
* Simple stereo Chorus
*
*/
class Chorus {
public:
/*!
* Constructor with linked synth parameters
*
* \param param the parameters to link to
*/
Chorus(const SynthParameters¶m) :
mP(param),
mLeftLine(1.0), mRightLine(1.0) {
}
/*!
* Destructor
*
*/
~Chorus() {}
/*!
* Init buffers from sample rate
*
* \param sampleRate The sample rate
*/
void init(double sampleRate) {
mTriGeneratorL.init(sampleRate);
mTriGeneratorR.init(sampleRate);
mLeftLine.init(sampleRate);
mRightLine.init(sampleRate);
mTriGeneratorR.setPhase(0.25); // In quadrature
mDepth = 0.0;
mFreqL = 0.0;
mFreqR = 0.0;
mMix = 1.0;
mInvMix = 0.0;
mDelay = ChorusDefaultDelay;
auto coeffs = dsp::IIR::Coefficients< double >::makeHighPass(sampleRate,80.0);
mHiPass.coefficients = coeffs;
updateInternalValues();
}
/*!
* Applies chorus to a stereo sample
*
* \param outL In/out left sample
* \param outR In/Out right sample
*/
void next(double&outL, double&outR) {
const ScopedLock slock(lock);
mLeftLine.push(outL);
mRightLine.push(outR);
double lfoValueL = mDepth * mTriGeneratorL.next(mFreqL);
double lfoValueR = mDepth * mTriGeneratorR.next(mFreqR);
double signalValue = 0.5*(mLeftLine.next(1.0 + lfoValueL) - mRightLine.next(1.0 + lfoValueR));
signalValue = mHiPass.processSample(signalValue);
outL = mMix * signalValue + mInvMix * outL;
outR = -mMix * signalValue + mInvMix * outR;
}
/*!
* Is Chorus active
*
* \return true if chorus applies (ie mix is wet)
*/
bool isActive() const { return mMix != 0.0; }
bool updateImmediateValue() {
bool hasChanged = false;
Moloss::setIfChanged(mMix, *mP.mChorusMix, hasChanged);
mInvMix = 1.0 - mMix;
return hasChanged;
}
/*!
* Update internal state values from synth parameters
*/
bool updateInternalValues() {
const ScopedLock slock(lock);
updateImmediateValue();
double depth = float(*mP.mChorusDepth);
double freqL = float(*mP.mChorusFreq);
if (*mP.mChorusSync) {
freqL = Moloss::getSyncFreq(*mP.mMetronomTempo, mP.mFrequency->convertTo0to1(freqL), true);
}
double minFreq = ChorusMinFreq + mP.mChorusDepth->convertTo0to1(depth)*0.2;
if (freqL < minFreq)
freqL = minFreq;
double freqR = freqL * ChorusFreqFactor;
// This is the max quantity that actual delay may vary relatively
// to the real-time cursor (fill-in), so to ensure there is no overflow
// we delay from that quantity, and add an arbitrary dephasing to
// the other channel (here ChorusDefaultDelay which is 4 ms)
bool delayParamHasChanged = false;
Moloss::setIfChanged(mDepth, depth, delayParamHasChanged);
Moloss::setIfChanged(mFreqR, freqR, delayParamHasChanged);
Moloss::setIfChanged(mFreqL, freqL, delayParamHasChanged);
if (delayParamHasChanged)
{
double maxDeltaSec = 0.5*mDepth / mFreqR;
mRightLine.setDelay(maxDeltaSec,false);
mLeftLine.setDelay(maxDeltaSec + ChorusDefaultDelay, false);
}
return (delayParamHasChanged);
}
private:
TriangleAliased mTriGeneratorL;
TriangleAliased mTriGeneratorR;
DelayLine mLeftLine;
DelayLine mRightLine;
// Current values
double mDepth = 0.0;
double mFreqL = 0.0;
double mFreqR = 0.0;
double mMix = 1.0;
double mInvMix = 0.0;
double mDelay = ChorusDefaultDelay;
juce::dsp::IIR::Filter<double> mHiPass;
// Parameters linked
const SynthParameters & mP;
CriticalSection lock;
};