From 94f16414025dd5f03e63416cb03565a64a35dcfd Mon Sep 17 00:00:00 2001 From: Tom Poole Date: Fri, 15 Feb 2019 14:00:36 +0000 Subject: [PATCH] Added a LogSmoothedValue class --- .../juce_audio_basics/juce_audio_basics.cpp | 1 + modules/juce_audio_basics/juce_audio_basics.h | 2 +- .../utilities/juce_LinearSmoothedValue.h | 241 ---------- .../utilities/juce_SmoothedValues.cpp | 285 ++++++++++++ .../utilities/juce_SmoothedValues.h | 438 ++++++++++++++++++ 5 files changed, 725 insertions(+), 242 deletions(-) delete mode 100644 modules/juce_audio_basics/utilities/juce_LinearSmoothedValue.h create mode 100644 modules/juce_audio_basics/utilities/juce_SmoothedValues.cpp create mode 100644 modules/juce_audio_basics/utilities/juce_SmoothedValues.h diff --git a/modules/juce_audio_basics/juce_audio_basics.cpp b/modules/juce_audio_basics/juce_audio_basics.cpp index acf498806c..3f2a661055 100644 --- a/modules/juce_audio_basics/juce_audio_basics.cpp +++ b/modules/juce_audio_basics/juce_audio_basics.cpp @@ -60,6 +60,7 @@ #include "utilities/juce_IIRFilter.cpp" #include "utilities/juce_LagrangeInterpolator.cpp" #include "utilities/juce_CatmullRomInterpolator.cpp" +#include "utilities/juce_SmoothedValues.cpp" #include "midi/juce_MidiBuffer.cpp" #include "midi/juce_MidiFile.cpp" #include "midi/juce_MidiKeyboardState.cpp" diff --git a/modules/juce_audio_basics/juce_audio_basics.h b/modules/juce_audio_basics/juce_audio_basics.h index c5a135d794..83fe62270d 100644 --- a/modules/juce_audio_basics/juce_audio_basics.h +++ b/modules/juce_audio_basics/juce_audio_basics.h @@ -89,7 +89,7 @@ #include "utilities/juce_IIRFilter.h" #include "utilities/juce_LagrangeInterpolator.h" #include "utilities/juce_CatmullRomInterpolator.h" -#include "utilities/juce_LinearSmoothedValue.h" +#include "utilities/juce_SmoothedValues.h" #include "utilities/juce_Reverb.h" #include "utilities/juce_ADSR.h" #include "midi/juce_MidiMessage.h" diff --git a/modules/juce_audio_basics/utilities/juce_LinearSmoothedValue.h b/modules/juce_audio_basics/utilities/juce_LinearSmoothedValue.h deleted file mode 100644 index 5eaa2e5203..0000000000 --- a/modules/juce_audio_basics/utilities/juce_LinearSmoothedValue.h +++ /dev/null @@ -1,241 +0,0 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2017 - ROLI Ltd. - - JUCE is an open source library subject to commercial or open-source - licensing. - - The code included in this file is provided under the terms of the ISC license - http://www.isc.org/downloads/software-support-policy/isc-license. Permission - To use, copy, modify, and/or distribute this software for any purpose with or - without fee is hereby granted provided that the above copyright notice and - this permission notice appear in all copies. - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -namespace juce -{ - -//============================================================================== -/** - Utility class for linearly smoothed values like volume etc. that should - not change abruptly but as a linear ramp to avoid audio glitches. - - @tags{Audio} -*/ -template -class LinearSmoothedValue -{ -public: - /** Constructor. */ - LinearSmoothedValue() = default; - - /** Constructor. */ - LinearSmoothedValue (FloatType initialValue) noexcept - : currentValue (initialValue), target (initialValue) - { - } - - //============================================================================== - /** Set a new sample rate and ramp length in seconds. - @param sampleRate The sampling rate - @param rampLengthInSeconds The duration of the ramp in seconds - */ - void reset (double sampleRate, double rampLengthInSeconds) noexcept - { - jassert (sampleRate > 0 && rampLengthInSeconds >= 0); - reset ((int) std::floor (rampLengthInSeconds * sampleRate)); - } - - /** Set a new ramp length directly in samples. - @param numSteps The number of samples over which the ramp should be active - */ - void reset (int numSteps) noexcept - { - stepsToTarget = numSteps; - setCurrentAndTargetValue (target); - } - - /** Set the next value to ramp towards. - @param newValue The new target value - */ - void setTargetValue (FloatType newValue) noexcept - { - if (target == newValue) - return; - - target = newValue; - - if (stepsToTarget <= 0) - { - setCurrentAndTargetValue (target); - return; - } - - countdown = stepsToTarget; - step = (target - currentValue) / static_cast (countdown); - } - - /** Sets the current value and the target value. - @param newValue the new value to take - */ - void setCurrentAndTargetValue (FloatType newValue) - { - target = currentValue = newValue; - countdown = 0; - } - - //============================================================================== - /** Compute the next value. - @returns Smoothed value - */ - FloatType getNextValue() noexcept - { - if (! isSmoothing()) - return target; - - --countdown; - currentValue += step; - return currentValue; - } - - /** Returns true if the current value is currently being interpolated. */ - bool isSmoothing() const noexcept { return countdown > 0; } - - /** Returns the current value of the ramp. */ - FloatType getCurrentValue() const noexcept { return currentValue; } - - /** Returns the target value towards which the smoothed value is currently moving. */ - FloatType getTargetValue() const noexcept { return target; } - - //============================================================================== - /** Applies a linear smoothed gain to a stream of samples - S[i] *= gain - @param samples Pointer to a raw array of samples - @param numSamples Length of array of samples - */ - void applyGain (FloatType* samples, int numSamples) noexcept - { - jassert(numSamples >= 0); - - if (isSmoothing()) - { - for (int i = 0; i < numSamples; i++) - samples[i] *= getNextValue(); - } - else - { - FloatVectorOperations::multiply (samples, target, numSamples); - } - } - - //============================================================================== - /** Computes output as linear smoothed gain applied to a stream of samples. - Sout[i] = Sin[i] * gain - @param samplesOut A pointer to a raw array of output samples - @param samplesIn A pointer to a raw array of input samples - @param numSamples The length of the array of samples - */ - void applyGain (FloatType* samplesOut, const FloatType* samplesIn, int numSamples) noexcept - { - jassert (numSamples >= 0); - - if (isSmoothing()) - { - for (int i = 0; i < numSamples; i++) - samplesOut[i] = samplesIn[i] * getNextValue(); - } - else - { - FloatVectorOperations::multiply (samplesOut, samplesIn, target, numSamples); - } - } - - //============================================================================== - /** Applies a linear smoothed gain to a buffer */ - void applyGain (AudioBuffer& buffer, int numSamples) noexcept - { - jassert (numSamples >= 0); - - if (isSmoothing()) - { - if (buffer.getNumChannels() == 1) - { - auto samples = buffer.getWritePointer(0); - - for (int i = 0; i < numSamples; ++i) - samples[i] *= getNextValue(); - } - else - { - for (int i = 0; i < numSamples; ++i) - { - auto gain = getNextValue(); - - for (int channel = 0; channel < buffer.getNumChannels(); channel++) - buffer.setSample (channel, i, buffer.getSample (channel, i) * gain); - } - } - } - else - { - buffer.applyGain (0, numSamples, target); - } - } - - //============================================================================== - /** Skip the next numSamples samples. - This is identical to calling getNextValue numSamples times. It returns - the new current value. - @see getNextValue - */ - FloatType skip (int numSamples) noexcept - { - if (numSamples >= countdown) - { - setCurrentAndTargetValue (target); - return target; - } - - currentValue += (step * static_cast (numSamples)); - countdown -= numSamples; - return currentValue; - } - - //============================================================================== - /** THIS FUNCTION IS DEPRECATED. - - Use `setTargetValue (float)` and `setCurrentAndTargetValue()` instead: - - lsv.setValue (x, false); -> lsv.setTargetValue (x); - lsv.setValue (x, true); -> lsv.setCurrentAndTargetValue (x); - - @param newValue The new target value - @param force If true, the value will be set immediately, bypassing the ramp - */ - JUCE_DEPRECATED_WITH_BODY (void setValue (FloatType newValue, bool force = false) noexcept, - { - if (force) - { - target = newValue; - setCurrentAndTargetValue (target); - return; - } - - setTargetValue (newValue); - }) - -private: - //============================================================================== - FloatType currentValue = 0, target = 0, step = 0; - int countdown = 0, stepsToTarget = 0; -}; - -} // namespace juce diff --git a/modules/juce_audio_basics/utilities/juce_SmoothedValues.cpp b/modules/juce_audio_basics/utilities/juce_SmoothedValues.cpp new file mode 100644 index 0000000000..52e41ea7b7 --- /dev/null +++ b/modules/juce_audio_basics/utilities/juce_SmoothedValues.cpp @@ -0,0 +1,285 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2018 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +#if JUCE_UNIT_TESTS + +template +class CommonSmoothedValueTests : public UnitTest +{ +public: + CommonSmoothedValueTests() + : UnitTest ("CommonSmoothedValueTests", "SmoothedValues") + {} + + void runTest() override + { + beginTest ("Initial state"); + { + SmoothedValueType lsv; + + auto value = lsv.getCurrentValue(); + expectEquals (lsv.getTargetValue(), value); + + lsv.getNextValue(); + expectEquals (lsv.getCurrentValue(), value); + expect (! lsv.isSmoothing()); + } + + beginTest ("Resetting"); + { + auto initialValue = -5.0f; + + SmoothedValueType lsv (-5.0f); + lsv.reset (3); + expectEquals (lsv.getCurrentValue(), initialValue); + + auto targetValue = initialValue + 1.0f; + lsv.setTargetValue (targetValue); + expectEquals (lsv.getTargetValue(), targetValue); + expectEquals (lsv.getCurrentValue(), initialValue); + expect (lsv.isSmoothing()); + + auto currentValue = lsv.getNextValue(); + expect (currentValue > initialValue); + expectEquals (lsv.getCurrentValue(), currentValue); + expectEquals (lsv.getTargetValue(), targetValue); + expect (lsv.isSmoothing()); + + lsv.reset (5); + + expectEquals (lsv.getCurrentValue(), targetValue); + expectEquals (lsv.getTargetValue(), targetValue); + expect (! lsv.isSmoothing()); + + lsv.getNextValue(); + expectEquals (lsv.getCurrentValue(), targetValue); + + lsv.setTargetValue (-15.0f); + lsv.getNextValue(); + + float newStart = -20.0f; + lsv.setCurrentAndTargetValue (newStart); + expectEquals (lsv.getNextValue(), newStart); + expectEquals (lsv.getTargetValue(), newStart); + expectEquals (lsv.getCurrentValue(), newStart); + expect (! lsv.isSmoothing()); + } + + beginTest ("Sample rate"); + { + SmoothedValueType lsvSamples { 3.0f }; + auto lsvTime = lsvSamples; + + auto numSamples = 12; + + lsvSamples.reset (numSamples); + lsvTime.reset (numSamples * 2, 1.0); + + for (int i = 0; i < numSamples; ++i) + { + lsvTime.skip (1); + expectWithinAbsoluteError (lsvSamples.getNextValue(), + lsvTime.getNextValue(), + 1.0e-7f); + } + } + + beginTest ("Block processing"); + { + SmoothedValueType lsv (1.0f); + + lsv.reset (12); + lsv.setTargetValue (2.0f); + + const auto numSamples = 15; + + AudioBuffer referenceData (1, numSamples); + + for (int i = 0; i < numSamples; ++i) + referenceData.setSample (0, i, lsv.getNextValue()); + + expect (referenceData.getSample (0, 0) > 0); + expect (referenceData.getSample (0, 10) < lsv.getTargetValue()); + expectWithinAbsoluteError (referenceData.getSample (0, 11), + lsv.getTargetValue(), + 1.0e-7f); + + auto getUnitData = [] (int numSamplesToGenerate) + { + AudioBuffer result (1, numSamplesToGenerate); + + for (int i = 0; i < numSamplesToGenerate; ++i) + result.setSample (0, i, 1.0f); + + return result; + }; + + auto compareData = [this](const AudioBuffer& test, + const AudioBuffer& reference) + { + for (int i = 0; i < test.getNumSamples(); ++i) + expectWithinAbsoluteError (test.getSample (0, i), + reference.getSample (0, i), + 1.0e-7f); + }; + + auto testData = getUnitData (numSamples); + lsv.setCurrentAndTargetValue (1.0f); + lsv.setTargetValue (2.0f); + lsv.applyGain (testData.getWritePointer (0), numSamples); + compareData (testData, referenceData); + + testData = getUnitData (numSamples); + AudioBuffer destData (1, numSamples); + lsv.setCurrentAndTargetValue (1.0f); + lsv.setTargetValue (2.0f); + lsv.applyGain (destData.getWritePointer (0), + testData.getReadPointer (0), + numSamples); + compareData (destData, referenceData); + compareData (testData, getUnitData (numSamples)); + + testData = getUnitData (numSamples); + lsv.setCurrentAndTargetValue (1.0f); + lsv.setTargetValue (2.0f); + lsv.applyGain (testData, numSamples); + compareData (testData, referenceData); + } + + beginTest ("Skip"); + { + SmoothedValueType lsv; + + lsv.reset (12); + lsv.setCurrentAndTargetValue (0.0f); + lsv.setTargetValue (1.0f); + + Array reference; + + for (int i = 0; i < 15; ++i) + reference.add (lsv.getNextValue()); + + lsv.setCurrentAndTargetValue (0.0f); + lsv.setTargetValue (1.0f); + + expectWithinAbsoluteError (lsv.skip (1), reference[0], 1.0e-7f); + expectWithinAbsoluteError (lsv.skip (1), reference[1], 1.0e-7f); + expectWithinAbsoluteError (lsv.skip (2), reference[3], 1.0e-7f); + lsv.skip (3); + expectWithinAbsoluteError (lsv.getCurrentValue(), reference[6], 1.0e-7f); + expectEquals (lsv.skip (300), lsv.getTargetValue()); + expectEquals (lsv.getCurrentValue(), lsv.getTargetValue()); + } + + beginTest ("Moving target"); + { + SmoothedValueType lsv; + + lsv.reset (12); + float initialValue = 0.0f; + lsv.setCurrentAndTargetValue (initialValue); + lsv.setTargetValue (1.0f); + + auto delta = lsv.getNextValue() - initialValue; + + lsv.skip (6); + + auto newInitialValue = lsv.getCurrentValue(); + lsv.setTargetValue (newInitialValue + 2.0f); + auto doubleDelta = lsv.getNextValue() - newInitialValue; + + expectWithinAbsoluteError (doubleDelta, delta * 2.0f, 1.0e-7f); + } + } +}; + +static CommonSmoothedValueTests> commonLinearSmoothedValueTests; +static CommonSmoothedValueTests> commonLogSmoothedValueTests; + +class LogSmoothedValueTests : public UnitTest +{ +public: + LogSmoothedValueTests() + : UnitTest ("LogSmoothedValueTests", "SmoothedValues") + {} + + void runTest() override + { + beginTest ("Curve"); + { + Array levels = { -0.12243, -1.21245, -12.2342, -22.4683, -30.0, -61.18753 }; + + for (auto level : levels) + { + Array> ranges = { Range (0.0, 1.0), + Range (-2.345, 0.0), + Range (-2.63, 3.56), + Range (3.3, -0.2) }; + + for (auto range : ranges) + { + LogSmoothedValue slowStart { range.getStart() } , fastStart { range.getEnd() }; + + auto numSamples = 12; + slowStart.reset (numSamples); + fastStart.reset (numSamples); + + slowStart.setLogParameters (level, true); + fastStart.setLogParameters (level, false); + + slowStart.setTargetValue (range.getEnd()); + fastStart.setTargetValue (range.getStart()); + + AudioBuffer results (2, numSamples + 1); + + results.setSample (0, 0, slowStart.getCurrentValue()); + results.setSample (1, 0, fastStart.getCurrentValue()); + + for (int i = 1; i < results.getNumSamples(); ++i) + { + results.setSample (0, i, slowStart.getNextValue()); + results.setSample (1, i, fastStart.getNextValue()); + } + + for (int i = 0; i < results.getNumSamples(); ++i) + expectWithinAbsoluteError (results.getSample (0, i), + results.getSample (1, results.getNumSamples() - (i + 1)), + 1.0e-7); + + auto expectedMidpoint = range.getStart() + (range.getLength() * Decibels::decibelsToGain (level)); + expectWithinAbsoluteError (results.getSample (0, numSamples / 2), + expectedMidpoint, + 1.0e-7); + } + } + } + } +}; + +static LogSmoothedValueTests logSmoothedValueTests; + +#endif + +} // namespace juce diff --git a/modules/juce_audio_basics/utilities/juce_SmoothedValues.h b/modules/juce_audio_basics/utilities/juce_SmoothedValues.h new file mode 100644 index 0000000000..cca0f72f6e --- /dev/null +++ b/modules/juce_audio_basics/utilities/juce_SmoothedValues.h @@ -0,0 +1,438 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +//============================================================================== +/** + A base class for the smoothed value classes. + + @tags{Audio} +*/ +template class SmoothedValueClass, typename FloatType> +class SmoothedValueBase +{ +public: + //============================================================================== + /** Constructor. */ + SmoothedValueBase() = default; + + //============================================================================== + /** Returns true if the current value is currently being interpolated. */ + bool isSmoothing() const noexcept { return countdown > 0; } + + /** Returns the current value of the ramp. */ + FloatType getCurrentValue() const noexcept { return currentValue; } + + //============================================================================== + /** Returns the target value towards which the smoothed value is currently moving. */ + FloatType getTargetValue() const noexcept { return target; } + + /** Sets the current value and the target value. + @param newValue the new value to take + */ + void setCurrentAndTargetValue (FloatType newValue) + { + target = currentValue = newValue; + countdown = 0; + } + + //============================================================================== + /** Applies a smoothed gain to a stream of samples + S[i] *= gain + @param samples Pointer to a raw array of samples + @param numSamples Length of array of samples + */ + void applyGain (FloatType* samples, int numSamples) noexcept + { + jassert (numSamples >= 0); + + if (isSmoothing()) + { + for (int i = 0; i < numSamples; ++i) + samples[i] *= getNextSmoothedValue(); + } + else + { + FloatVectorOperations::multiply (samples, target, numSamples); + } + } + + /** Computes output as a smoothed gain applied to a stream of samples. + Sout[i] = Sin[i] * gain + @param samplesOut A pointer to a raw array of output samples + @param samplesIn A pointer to a raw array of input samples + @param numSamples The length of the array of samples + */ + void applyGain (FloatType* samplesOut, const FloatType* samplesIn, int numSamples) noexcept + { + jassert (numSamples >= 0); + + if (isSmoothing()) + { + for (int i = 0; i < numSamples; ++i) + samplesOut[i] = samplesIn[i] * getNextSmoothedValue(); + } + else + { + FloatVectorOperations::multiply (samplesOut, samplesIn, target, numSamples); + } + } + + /** Applies a smoothed gain to a buffer */ + void applyGain (AudioBuffer& buffer, int numSamples) noexcept + { + jassert (numSamples >= 0); + + if (isSmoothing()) + { + if (buffer.getNumChannels() == 1) + { + auto* samples = buffer.getWritePointer (0); + + for (int i = 0; i < numSamples; ++i) + samples[i] *= getNextSmoothedValue(); + } + else + { + for (auto i = 0; i < numSamples; ++i) + { + auto gain = getNextSmoothedValue(); + + for (int channel = 0; channel < buffer.getNumChannels(); channel++) + buffer.setSample (channel, i, buffer.getSample (channel, i) * gain); + } + } + } + else + { + buffer.applyGain (0, numSamples, target); + } + } + +protected: + //============================================================================== + void reset (double sampleRate, double rampLengthInSeconds) noexcept + { + jassert (sampleRate > 0 && rampLengthInSeconds >= 0); + auto& derived = *(static_cast*> (this)); + derived.reset ((int) std::floor (rampLengthInSeconds * sampleRate)); + } + + //============================================================================== + FloatType currentValue = 0, target = 0; + int countdown = 0; + +private: + //============================================================================== + FloatType getNextSmoothedValue() noexcept + { + return static_cast*> (this)->getNextValue(); + } +}; + +//============================================================================== +/** + Utility class for linearly smoothed values like volume etc. that should + not change abruptly but as a linear ramp to avoid audio glitches. + + @tags{Audio} +*/ +template +class LinearSmoothedValue : public SmoothedValueBase +{ +public: + //============================================================================== + /** Constructor. */ + LinearSmoothedValue() = default; + + /** Constructor. */ + LinearSmoothedValue (FloatType initialValue) noexcept + { + // Visual Studio can't handle base class initialisation with CRTP + this->currentValue = initialValue; + this->target = initialValue; + } + + //============================================================================== + /** Reset to a new sample rate and ramp length. + @param sampleRate The sample rate + @param rampLengthInSeconds The duration of the ramp in seconds + */ + void reset (double sampleRate, double rampLengthInSeconds) noexcept + { + jassert (sampleRate > 0 && rampLengthInSeconds >= 0); + reset ((int) std::floor (rampLengthInSeconds * sampleRate)); + } + + /** Set a new ramp length directly in samples. + @param numSteps The number of samples over which the ramp should be active + */ + void reset (int numSteps) noexcept + { + stepsToTarget = numSteps; + this->setCurrentAndTargetValue (this->target); + } + + //============================================================================== + /** Set the next value to ramp towards. + @param newValue The new target value + */ + void setTargetValue (FloatType newValue) noexcept + { + if (newValue == this->target) + return; + + if (stepsToTarget <= 0) + { + this->setCurrentAndTargetValue (newValue); + return; + } + + this->target = newValue; + this->countdown = stepsToTarget; + step = (this->target - this->currentValue) / (FloatType) this->countdown; + } + + //============================================================================== + /** Compute the next value. + @returns Smoothed value + */ + FloatType getNextValue() noexcept + { + if (! this->isSmoothing()) + return this->target; + + --(this->countdown); + + this->currentValue = this->isSmoothing() ? this->currentValue + step + : this->target; + + return this->currentValue; + } + + //============================================================================== + /** Skip the next numSamples samples. + This is identical to calling getNextValue numSamples times. It returns + the new current value. + @see getNextValue + */ + FloatType skip (int numSamples) noexcept + { + if (numSamples >= this->countdown) + { + this->setCurrentAndTargetValue (this->target); + return this->target; + } + + this->currentValue += step * (FloatType) numSamples; + this->countdown -= numSamples; + return this->currentValue; + } + + //============================================================================== + /** THIS FUNCTION IS DEPRECATED. + + Use `setTargetValue (float)` and `setCurrentAndTargetValue()` instead: + + lsv.setValue (x, false); -> lsv.setTargetValue (x); + lsv.setValue (x, true); -> lsv.setCurrentAndTargetValue (x); + + @param newValue The new target value + @param force If true, the value will be set immediately, bypassing the ramp + */ + JUCE_DEPRECATED_WITH_BODY (void setValue (FloatType newValue, bool force = false) noexcept, + { + if (force) + { + this->setCurrentAndTargetValue (newValue); + return; + } + + setTargetValue (newValue); + }) + +private: + //============================================================================== + FloatType step = FloatType(); + int stepsToTarget = 0; +}; + +//============================================================================== +/** + Utility class for logarithmically smoothed values. + + Logarithmically smoothed values can be more relevant than linear ones for + specific cases such as algorithm change smoothing, using two of them in + opposite directions. + + @see LinearSmoothedValue + + @tags{Audio} +*/ +template +class LogSmoothedValue : public SmoothedValueBase +{ +public: + //============================================================================== + /** Constructor. */ + LogSmoothedValue() = default; + + /** Constructor. */ + LogSmoothedValue (FloatType initialValue) noexcept + { + // Visual Studio can't handle base class initialisation with CRTP + this->currentValue = initialValue; + this->target = initialValue; + } + + //============================================================================== + /** Sets the behaviour of the log ramp. + @param midPointAmplitudedB Sets the amplitude of the mid point in + decibels, with the target value at 0 dB + and the initial value at -inf dB + @param rateOfChangeShouldIncrease If true then the ramp starts shallow + and gets progressively steeper, if false + then the ramp is initially steep and + flattens out as you approach the target + value + */ + void setLogParameters (FloatType midPointAmplitudedB, bool rateOfChangeShouldIncrease) noexcept + { + jassert (midPointAmplitudedB < (FloatType) 0.0); + B = Decibels::decibelsToGain (midPointAmplitudedB); + + increasingRateOfChange = rateOfChangeShouldIncrease; + } + + //============================================================================== + /** Reset to a new sample rate and ramp length. + @param sampleRate The sample rate + @param rampLengthInSeconds The duration of the ramp in seconds + */ + void reset (double sampleRate, double rampLengthInSeconds) noexcept + { + jassert (sampleRate > 0 && rampLengthInSeconds >= 0); + reset ((int) std::floor (rampLengthInSeconds * sampleRate)); + } + + /** Set a new ramp length directly in samples. + @param numSteps The number of samples over which the ramp should be active + @param increasingRateOfChange If the log behaviour makes the ramp increase + slowly at the beginning, rather than at the end + */ + void reset (int numSteps) noexcept + { + stepsToTarget = numSteps; + + this->setCurrentAndTargetValue (this->target); + + updateRampParameters(); + } + + //============================================================================== + /** Set a new target value. + + @param newValue The new target value + @param force If true, the value will be set immediately, bypassing the ramp + */ + void setTargetValue (FloatType newValue) noexcept + { + if (newValue == this->target) + return; + + if (stepsToTarget <= 0) + { + this->setCurrentAndTargetValue (newValue); + return; + } + + this->target = newValue; + this->countdown = stepsToTarget; + source = this->currentValue; + + updateRampParameters(); + } + + //============================================================================== + /** Compute the next value. + @returns Smoothed value + */ + FloatType getNextValue() noexcept + { + if (! this->isSmoothing()) + return this->target; + + --(this->countdown); + + temp *= r; temp += d; + this->currentValue = jmap (temp, source, this->target); + + return this->currentValue; + } + + //============================================================================== + /** Skip the next numSamples samples. + + This is identical to calling getNextValue numSamples times. + @see getNextValue + */ + FloatType skip (int numSamples) noexcept + { + if (numSamples >= this->countdown) + { + this->setCurrentAndTargetValue (this->target); + return this->target; + } + + this->countdown -= numSamples; + + auto rN = std::pow (r, numSamples); + temp *= rN; + temp += d * (rN - (FloatType) 1) / (r - (FloatType) 1); + + this->currentValue = jmap (temp, source, this->target); + return this->currentValue; + } + +private: + //============================================================================== + void updateRampParameters() + { + auto D = increasingRateOfChange ? B : (FloatType) 1 - B; + auto base = ((FloatType) 1 / D) - (FloatType) 1; + r = std::pow (base, (FloatType) 2 / (FloatType) stepsToTarget); + auto rN = std::pow (r, (FloatType) stepsToTarget); + d = (r - (FloatType) 1) / (rN - (FloatType) 1); + temp = 0; + } + + //============================================================================== + bool increasingRateOfChange = true; + FloatType B = Decibels::decibelsToGain ((FloatType) -40); + + int stepsToTarget = 0; + FloatType temp = 0, source = 0, r = 0, d = 1; +}; + +} // namespace juce