/**************************************************************************
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; // To force reset
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);
outL = mMix * mLeftLine.next(1.0 + lfoValueL) + mInvMix * outL;
outR = mMix * mRightLine.next(1.0 + lfoValueR) + mInvMix * outR;
}
/*!
* Update internal state values from synth parameters
*
*/
void updateInternalValues() {
const ScopedLock slock(lock);
double prevDepth = mDepth;
double prevFreq = mFreqR;
mMix = float(*mP.mChorusMix);
mInvMix = 1.0 - mMix;
mDepth = float(*mP.mChorusDepth);
mFreqL = float(*mP.mChorusFreq);
if (*mP.mChorusSync) {
mFreqL *= (*mP.mMetronomTempo) / 60.0;
}
if (mFreqL < 0.1)
mFreqL = 0.1;
mFreqR = mFreqL * 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 ChorusDeltaDelay which is 4 ms)
if (mDepth != prevDepth || prevFreq != mFreqR)
{
double maxDeltaSec = 0.5*mDepth / mFreqR;
mRightLine.setDelay(maxDeltaSec);
mLeftLine.setDelay(maxDeltaSec + ChorusDefaultDelay);
}
}
/*!
* Is Chorus active
*
* \return true if chorus applies (ie mix is wet)
*/
bool isActive() const { return mMix != 0.0; }
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;
// Parameters linked
const SynthParameters & mP;
CriticalSection lock;
};