/**************************************************************************
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 "Utils.h"
/*!
* DADSR : ADSR with a delay stage
* Derived from juce::ADSR
*
*/
class DADSR
{
public:
//==============================================================================
DADSR()
{
setSampleRate(44100.0);
setParameters({});
}
//==============================================================================
/** Holds the parameters being used by an ADSR object. */
struct Parameters
{
/** Delay time in seconds. */
float delay = 0.0f;
/** Attack time in seconds. */
float attack = 0.005f;
/** Decay time in seconds. */
float decay = 0.0f;
/** Sustain level. */
float sustain = 1.0f;
/** Release time in seconds. */
float release = 0.005f;
};
/** Sets the parameters that will be used by an ADSR object.
You must have called setSampleRate() with the correct sample rate before
this otherwise the values may be incorrect!
@see getParameters
*/
void setParameters(const Parameters& newParameters)
{
currentParameters = newParameters;
sustainLevel = newParameters.sustain;
calculateRates(newParameters);
if (currentState != State::idle)
checkCurrentState();
}
/** Returns the parameters currently being used by an ADSR object.
@see setParameters
*/
const Parameters& getParameters() const { return currentParameters; }
/** Returns true if the envelope is in its attack, decay, sustain or release stage. */
bool isActive() const noexcept { return currentState != State::idle; }
bool inRelease() const noexcept { return currentState == State::release; }
//==============================================================================
/** Sets the sample rate that will be used for the envelope.
This must be called before the getNextSample() or setParameters() methods.
*/
void setSampleRate(double sampleRate)
{
jassert(sampleRate > 0.0);
sr = sampleRate;
}
//==============================================================================
/** Resets the envelope to an idle state. */
void reset()
{
envelopeVal = 0.0f;
delayCurrent = 0.0f;
currentState = State::idle;
calculateRates(currentParameters);
}
/** Starts the attack phase of the envelope. */
void noteOn()
{
if (delayTime > 0.0f) { delayCurrent = 0.0f; currentState = State::delay; }
else if (attackRate > 0.0f) currentState = State::attack;
else if (decayRate > 0.0f) { currentState = State::decay; envelopeVal = 1.0f; } // PATCH
else currentState = State::sustain;
releaseStartLevel = sustainLevel;
}
/** Starts the release phase of the envelope. */
void noteOff()
{
if (currentState != State::idle)
{
{
// PATCH : we always re-compute
if (currentState != State::sustain) {
releaseStartLevel = envelopeVal; // PATCH
releaseRate = static_cast<float> (envelopeVal / (currentParameters.release * sr));
}
}
if (releaseRate > 0.0f) {
currentState = State::release;
}
else {
reset();
}
}
}
//==============================================================================
/** Returns the next sample value for an ADSR object.
@see applyEnvelopeToBuffer
*/
float getNextSample()
{
if (currentState == State::idle)
return 0.0f;
if (currentState == State::delay)
{
delayCurrent++;
if (delayCurrent >= delayTime) {
currentState = State::attack;
}
}
else if (currentState == State::attack)
{
envelopeVal += attackRate;
if (envelopeVal >= attackEndLevel)
{
envelopeVal = attackEndLevel;
if (decayRate > 0.0f)
currentState = State::decay;
else
currentState = State::sustain;
}
}
else if (currentState == State::decay)
{
envelopeVal -= decayRate;
if (envelopeVal <= sustainLevel)
{
envelopeVal = sustainLevel;
currentState = State::sustain;
}
}
else if (currentState == State::sustain)
{
envelopeVal = sustainLevel;
}
else if (currentState == State::release)
{
envelopeVal -= releaseRate;
if (envelopeVal <= 0.0f)
reset();
}
return envelopeVal;
}
/** This method will conveniently apply the next numSamples number of envelope values
to an AudioBuffer.
@see getNextSample
*/
template<typename FloatType>
void applyEnvelopeToBuffer(AudioBuffer<FloatType>& buffer, int startSample, int numSamples)
{
jassert(startSample + numSamples <= buffer.getNumSamples());
auto numChannels = buffer.getNumChannels();
while (--numSamples >= 0)
{
auto env = getNextSample();
for (int i = 0; i < numChannels; ++i)
buffer.getWritePointer(i)[startSample] *= env;
++startSample;
}
}
private:
//==============================================================================
void calculateRates(const Parameters& parameters)
{
// need to call setSampleRate() first!
jassert(sr > 0.0);
delayTime = static_cast<float> (parameters.delay*sr);
attackEndLevel = (parameters.decay > 0.0f ? 1.0f : sustainLevel);
attackRate = (parameters.attack > 0.0f ? static_cast<float> (attackEndLevel / (parameters.attack * sr)) : -1.0f);
decayRate = (parameters.decay > 0.0f ? static_cast<float> ((1.0f - sustainLevel) / (parameters.decay * sr)) : -1.0f);
releaseRate = (parameters.release > 0.0f ? static_cast<float> (releaseStartLevel / (parameters.release * sr)) : -1.0f); // PATCH
}
void checkCurrentState()
{
if (currentState == State::delay && delayTime <= 0.0f) currentState = State::attack;
if (currentState == State::attack && attackRate <= 0.0f) currentState = decayRate > 0.0f ? State::decay : State::sustain;
else if (currentState == State::decay && decayRate <= 0.0f) currentState = State::sustain;
else if (currentState == State::release && releaseRate <= 0.0f) reset();
}
//==============================================================================
enum class State { idle, delay, attack, decay, sustain, release };
State currentState = State::idle;
Parameters currentParameters;
double sr = 0.0;
float envelopeVal = 0.0f;
float delayTime = 0.0f;
float delayCurrent = 0.0f;
float sustainLevel = 0.0f;
float attackRate = 0.0f, decayRate = 0.0f, releaseRate = 0.0f;
float releaseStartLevel = 0.0f; // PATCH
float attackEndLevel = 1.0f; // PATCH
};