/**************************************************************************
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 "../JuceLibraryCode/JuceHeader.h"
#include "LFOModulated.h"
#include "Utils.h"
#include "DADSR.h"
/*!
* A mother class for all time variant components driven by a LFO, an ADSR enveloppe,
* and which can be modulated by controllers : pitch bend, mod wheel & aftertouch
*
*/
class TimeVariant : public LFOModulated {
public:
TimeVariant(const TimeVariantParameters&adsrParam) :
mAttackRamp(1.0),
LFOModulated(adsrParam),
mPADSR(adsrParam){}
virtual ~TimeVariant();
virtual void init(double sampleRate) override;
virtual void noteOn(int note) override;
virtual void noteOff(double velocity) override;
virtual bool isActive() const;
/*!
* Computes an ADSR sample between [0,1.0] for frequency filter
* \return A new ADSR sample
*/
double nextFreqADSR(bool applyBypass = true) {
if (mIsADSRBypass) {
// We must keep an adsr to avoid plops.
return applyBypass ? mBypassADSR.getNextSample() : 1.0;
}
else {
double ramp = mAttackRamp.getNextValue();
if (mADSRDepthValue > 0)
return ((1.0 - mADSRDepthValue) + mADSRDepthValue * mADSR.getNextSample())*ramp;
else
return ((1.0 + mADSRDepthValue) - mADSRDepthValue * (1.0 - mADSR.getNextSample()))*ramp;
}
}
/*!
* Computes an ADSR sample between [0,1.0] for volume envelope
* \return A new ADSR sample
*/
double nextADSR(bool applyBypass = true) {
if (mIsADSRBypass) {
// We must keep an adsr to avoid plops.
return applyBypass ? mBypassADSR.getNextSample() : 1.0;
}
else {
double byPass = mBypassADSR.getNextSample();
double ramp = mAttackRamp.getNextValue();
if (mADSRDepthValue > 0)
return ((1.0 - mADSRDepthValue)*byPass + mADSRDepthValue * mADSR.getNextSample())*ramp;
else {
if (mADSR.isActive()) {
auto value = ((1.0 + mADSRDepthValue)*byPass - mADSRDepthValue * (1.0 - mADSR.getNextSample()))*ramp;
// Now swith on ramp to smooth the end towards 0 (avoid plop)
if (!mADSR.isActive()) {
mAttackRamp.setCurrentAndTargetValue(value);
mAttackRamp.reset(mSampleRate, ADSRMinAttackRelease);
mAttackRamp.setTargetValue(0.0);
}
return value;
}
else
return mAttackRamp.getNextValue();
}
}
}
/*!
* Computes an ADSR value centered on the sustain level (that is,
* sustain level is always 0, and attack start/release are negative.
* If release time is 0, the end remains at sustain level (i.e. 0).
* Used for pitch envelope
*
* \return an ADSR sample between -1 and 1
*/
double nextCenteredADSR() {
double adsrDelta;
if (!mIsADSRBypass) {
if (mADSR.isActive())
adsrDelta =mADSRDepthValue * (mADSR.getNextSample() - mADSR.getParameters().sustain);
else {
if (mADSR.getParameters().release > 0)
adsrDelta = (-mADSRDepthValue * mADSR.getParameters().sustain);
else
adsrDelta = 0.0f;
}
}
else
adsrDelta = 0.0;
return adsrDelta;
}
bool isADSRBypass() const {
return mIsADSRBypass;
}
virtual void updateInternalValues(int controller = 0) override;
protected:
double mSampleRate = 44100.0;
private:
// Parameters
const TimeVariantParameters& mPADSR;
// Current state (also stored in mADSR)
DADSR mADSR;
bool mIsADSRBypass = true;
double mADSRDepthValue = 1.0;
// Smoothing envelope when adsr is bypassed to avoid plops
DADSR mBypassADSR;
// Attack smoothing
juce::SmoothedValue<double> mAttackRamp;
};