1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-10 23:44:24 +00:00

Added a new templated SmoothedValue class

This commit is contained in:
Tom Poole 2019-02-18 23:43:28 +00:00
parent 5bdd3ea8c6
commit 4751e9d41a
20 changed files with 743 additions and 486 deletions

View file

@ -851,7 +851,7 @@ private:
} }
//============================================================================== //==============================================================================
LinearSmoothedValue<double> level, timbre, frequency; SmoothedValue<double> level, timbre, frequency;
double phase = 0.0; double phase = 0.0;
double phaseDelta = 0.0; double phaseDelta = 0.0;

View file

@ -128,7 +128,7 @@ public:
virtual double renderWaveShape (const double currentPhase) = 0; virtual double renderWaveShape (const double currentPhase) = 0;
private: private:
LinearSmoothedValue<double> amplitude, phaseIncrement; SmoothedValue<double> amplitude, phaseIncrement;
double frequency = 0.0; double frequency = 0.0;
double phasePos = 0.0; double phasePos = 0.0;

View file

@ -498,7 +498,7 @@ private:
{ {
ScopedNoDenormals noDenormals; ScopedNoDenormals noDenormals;
// Input volume applied with a LinearSmoothedValue // Input volume applied with a SmoothedValue
inputVolume.process (context); inputVolume.process (context);
// Pre-highpass filtering, very useful for distortion audio effects // Pre-highpass filtering, very useful for distortion audio effects
@ -543,7 +543,7 @@ private:
convolution.process (context); convolution.process (context);
context.isBypassed = wasBypassed; context.isBypassed = wasBypassed;
// Output volume applied with a LinearSmoothedValue // Output volume applied with a SmoothedValue
outputVolume.process (context); outputVolume.process (context);
} }

View file

@ -446,10 +446,10 @@ private:
} }
std::shared_ptr<const MPESamplerSound> samplerSound; std::shared_ptr<const MPESamplerSound> samplerSound;
LinearSmoothedValue<double> level { 0 }; SmoothedValue<double> level { 0 };
LinearSmoothedValue<double> frequency { 0 }; SmoothedValue<double> frequency { 0 };
LinearSmoothedValue<double> loopBegin; SmoothedValue<double> loopBegin;
LinearSmoothedValue<double> loopEnd; SmoothedValue<double> loopEnd;
double currentSamplePos { 0 }; double currentSamplePos { 0 };
double tailOff { 0 }; double tailOff { 0 };
Direction currentDirection { Direction::forward }; Direction currentDirection { Direction::forward };

View file

@ -60,7 +60,7 @@
#include "utilities/juce_IIRFilter.cpp" #include "utilities/juce_IIRFilter.cpp"
#include "utilities/juce_LagrangeInterpolator.cpp" #include "utilities/juce_LagrangeInterpolator.cpp"
#include "utilities/juce_CatmullRomInterpolator.cpp" #include "utilities/juce_CatmullRomInterpolator.cpp"
#include "utilities/juce_SmoothedValues.cpp" #include "utilities/juce_SmoothedValue.cpp"
#include "midi/juce_MidiBuffer.cpp" #include "midi/juce_MidiBuffer.cpp"
#include "midi/juce_MidiFile.cpp" #include "midi/juce_MidiFile.cpp"
#include "midi/juce_MidiKeyboardState.cpp" #include "midi/juce_MidiKeyboardState.cpp"

View file

@ -89,7 +89,7 @@
#include "utilities/juce_IIRFilter.h" #include "utilities/juce_IIRFilter.h"
#include "utilities/juce_LagrangeInterpolator.h" #include "utilities/juce_LagrangeInterpolator.h"
#include "utilities/juce_CatmullRomInterpolator.h" #include "utilities/juce_CatmullRomInterpolator.h"
#include "utilities/juce_SmoothedValues.h" #include "utilities/juce_SmoothedValue.h"
#include "utilities/juce_Reverb.h" #include "utilities/juce_Reverb.h"
#include "utilities/juce_ADSR.h" #include "utilities/juce_ADSR.h"
#include "midi/juce_MidiMessage.h" #include "midi/juce_MidiMessage.h"

View file

@ -305,7 +305,7 @@ private:
CombFilter comb [numChannels][numCombs]; CombFilter comb [numChannels][numCombs];
AllPassFilter allPass [numChannels][numAllPasses]; AllPassFilter allPass [numChannels][numAllPasses];
LinearSmoothedValue<float> damping, feedback, dryGain, wetGain1, wetGain2; SmoothedValue<float> damping, feedback, dryGain, wetGain1, wetGain2;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Reverb) JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Reverb)
}; };

View file

@ -0,0 +1,296 @@
/*
==============================================================================
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 SmoothedValueType>
class CommonSmoothedValueTests : public UnitTest
{
public:
CommonSmoothedValueTests()
: UnitTest ("CommonSmoothedValueTests", "SmoothedValues")
{}
void runTest() override
{
beginTest ("Initial state");
{
SmoothedValueType sv;
auto value = sv.getCurrentValue();
expectEquals (sv.getTargetValue(), value);
sv.getNextValue();
expectEquals (sv.getCurrentValue(), value);
expect (! sv.isSmoothing());
}
beginTest ("Resetting");
{
auto initialValue = 15.0f;
SmoothedValueType sv (initialValue);
sv.reset (3);
expectEquals (sv.getCurrentValue(), initialValue);
auto targetValue = initialValue + 1.0f;
sv.setTargetValue (targetValue);
expectEquals (sv.getTargetValue(), targetValue);
expectEquals (sv.getCurrentValue(), initialValue);
expect (sv.isSmoothing());
auto currentValue = sv.getNextValue();
expect (currentValue > initialValue);
expectEquals (sv.getCurrentValue(), currentValue);
expectEquals (sv.getTargetValue(), targetValue);
expect (sv.isSmoothing());
sv.reset (5);
expectEquals (sv.getCurrentValue(), targetValue);
expectEquals (sv.getTargetValue(), targetValue);
expect (! sv.isSmoothing());
sv.getNextValue();
expectEquals (sv.getCurrentValue(), targetValue);
sv.setTargetValue (1.5f);
sv.getNextValue();
float newStart = 0.2f;
sv.setCurrentAndTargetValue (newStart);
expectEquals (sv.getNextValue(), newStart);
expectEquals (sv.getTargetValue(), newStart);
expectEquals (sv.getCurrentValue(), newStart);
expect (! sv.isSmoothing());
}
beginTest ("Sample rate");
{
SmoothedValueType svSamples { 3.0f };
auto svTime = svSamples;
auto numSamples = 12;
svSamples.reset (numSamples);
svTime.reset (numSamples * 2, 1.0);
for (int i = 0; i < numSamples; ++i)
{
svTime.skip (1);
expectWithinAbsoluteError (svSamples.getNextValue(),
svTime.getNextValue(),
1.0e-7f);
}
}
beginTest ("Block processing");
{
SmoothedValueType sv (1.0f);
sv.reset (12);
sv.setTargetValue (2.0f);
const auto numSamples = 15;
AudioBuffer<float> referenceData (1, numSamples);
for (int i = 0; i < numSamples; ++i)
referenceData.setSample (0, i, sv.getNextValue());
expect (referenceData.getSample (0, 0) > 0);
expect (referenceData.getSample (0, 10) < sv.getTargetValue());
expectWithinAbsoluteError (referenceData.getSample (0, 11),
sv.getTargetValue(),
1.0e-7f);
auto getUnitData = [] (int numSamplesToGenerate)
{
AudioBuffer<float> result (1, numSamplesToGenerate);
for (int i = 0; i < numSamplesToGenerate; ++i)
result.setSample (0, i, 1.0f);
return result;
};
auto compareData = [this](const AudioBuffer<float>& test,
const AudioBuffer<float>& 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);
sv.setCurrentAndTargetValue (1.0f);
sv.setTargetValue (2.0f);
sv.applyGain (testData.getWritePointer (0), numSamples);
compareData (testData, referenceData);
testData = getUnitData (numSamples);
AudioBuffer<float> destData (1, numSamples);
sv.setCurrentAndTargetValue (1.0f);
sv.setTargetValue (2.0f);
sv.applyGain (destData.getWritePointer (0),
testData.getReadPointer (0),
numSamples);
compareData (destData, referenceData);
compareData (testData, getUnitData (numSamples));
testData = getUnitData (numSamples);
sv.setCurrentAndTargetValue (1.0f);
sv.setTargetValue (2.0f);
sv.applyGain (testData, numSamples);
compareData (testData, referenceData);
}
beginTest ("Skip");
{
SmoothedValueType sv;
sv.reset (12);
sv.setCurrentAndTargetValue (1.0f);
sv.setTargetValue (2.0f);
Array<float> reference;
for (int i = 0; i < 15; ++i)
reference.add (sv.getNextValue());
sv.setCurrentAndTargetValue (1.0f);
sv.setTargetValue (2.0f);
expectWithinAbsoluteError (sv.skip (1), reference[0], 1.0e-6f);
expectWithinAbsoluteError (sv.skip (1), reference[1], 1.0e-6f);
expectWithinAbsoluteError (sv.skip (2), reference[3], 1.0e-6f);
sv.skip (3);
expectWithinAbsoluteError (sv.getCurrentValue(), reference[6], 1.0e-6f);
expectEquals (sv.skip (300), sv.getTargetValue());
expectEquals (sv.getCurrentValue(), sv.getTargetValue());
}
beginTest ("Negative");
{
SmoothedValueType sv;
auto start = -1.0f, end = -2.0f;
auto numValues = 12;
sv.reset (numValues);
sv.setCurrentAndTargetValue (start);
sv.setTargetValue (end);
auto val = sv.skip (3);
expect (val < start && val > end);
auto nextVal = sv.getNextValue();
expect (nextVal < val);
auto endVal = sv.skip (500);
expectEquals (endVal, end);
expectEquals (sv.getNextValue(), end);
expectEquals (sv.getCurrentValue(), end);
sv.setCurrentAndTargetValue (start);
sv.reset (numValues);
sv.setTargetValue (end);
SmoothedValueType positiveSv { -start };
positiveSv.reset (numValues);
positiveSv.setTargetValue (-end);
for (int i = 0; i < numValues + 2; ++i)
expectEquals (sv.getNextValue(), -positiveSv.getNextValue());
}
}
};
static CommonSmoothedValueTests <SmoothedValue<float, ValueSmoothingTypes::Linear>> commonLinearSmoothedValueTests;
static CommonSmoothedValueTests <SmoothedValue<float, ValueSmoothingTypes::Multiplicative>> commonMultiplicativeSmoothedValueTests;
class SmoothedValueTests : public UnitTest
{
public:
SmoothedValueTests()
: UnitTest ("SmoothedValueTests", "SmoothedValues")
{}
void runTest() override
{
beginTest ("Linear moving target");
{
SmoothedValue<float, ValueSmoothingTypes::Linear> sv;
sv.reset (12);
float initialValue = 0.0f;
sv.setCurrentAndTargetValue (initialValue);
sv.setTargetValue (1.0f);
auto delta = sv.getNextValue() - initialValue;
sv.skip (6);
auto newInitialValue = sv.getCurrentValue();
sv.setTargetValue (newInitialValue + 2.0f);
auto doubleDelta = sv.getNextValue() - newInitialValue;
expectWithinAbsoluteError (doubleDelta, delta * 2.0f, 1.0e-7f);
}
beginTest ("Multiplicative curve");
{
SmoothedValue<double, ValueSmoothingTypes::Multiplicative> sv;
auto numSamples = 12;
AudioBuffer<double> values (2, numSamples + 1);
sv.reset (numSamples);
sv.setCurrentAndTargetValue (1.0);
sv.setTargetValue (2.0f);
values.setSample (0, 0, sv.getCurrentValue());
for (int i = 1; i < values.getNumSamples(); ++i)
values.setSample (0, i, sv.getNextValue());
sv.setTargetValue (1.0f);
values.setSample (1, values.getNumSamples() - 1, sv.getCurrentValue());
for (int i = values.getNumSamples() - 2; i >= 0 ; --i)
values.setSample (1, i, sv.getNextValue());
for (int i = 0; i < values.getNumSamples(); ++i)
expectWithinAbsoluteError (values.getSample (0, i), values.getSample (1, i), 1.0e-9);
}
}
};
static SmoothedValueTests smoothedValueTests;
#endif
} // namespace juce

View file

@ -27,16 +27,39 @@ namespace juce
/** /**
A base class for the smoothed value classes. A base class for the smoothed value classes.
This class is used to provide common functionality to the SmoothedValue and
dsp::LogRampedValue classes.
@tags{Audio} @tags{Audio}
*/ */
template <template<typename> class SmoothedValueClass, typename FloatType> template <typename SmoothedValueType>
class SmoothedValueBase class SmoothedValueBase
{ {
private:
//==============================================================================
template <typename T> struct FloatTypeHelper;
template <template <typename> class SmoothedValueClass, typename FloatType>
struct FloatTypeHelper <SmoothedValueClass <FloatType>>
{
using Type = FloatType;
};
template <template <typename, typename> class SmoothedValueClass, typename FloatType, typename SmoothingType>
struct FloatTypeHelper <SmoothedValueClass <FloatType, SmoothingType>>
{
using Type = FloatType;
};
public: public:
using FloatType = typename FloatTypeHelper<SmoothedValueType>::Type;
//============================================================================== //==============================================================================
/** Constructor. */ /** Constructor. */
SmoothedValueBase() = default; SmoothedValueBase() = default;
virtual ~SmoothedValueBase() {}
//============================================================================== //==============================================================================
/** Returns true if the current value is currently being interpolated. */ /** Returns true if the current value is currently being interpolated. */
bool isSmoothing() const noexcept { return countdown > 0; } bool isSmoothing() const noexcept { return countdown > 0; }
@ -130,48 +153,77 @@ public:
} }
} }
protected:
//==============================================================================
void reset (double sampleRate, double rampLengthInSeconds) noexcept
{
jassert (sampleRate > 0 && rampLengthInSeconds >= 0);
auto& derived = *(static_cast<SmoothedValueClass<FloatType>*> (this));
derived.reset ((int) std::floor (rampLengthInSeconds * sampleRate));
}
//==============================================================================
FloatType currentValue = 0, target = 0;
int countdown = 0;
private: private:
//============================================================================== //==============================================================================
FloatType getNextSmoothedValue() noexcept FloatType getNextSmoothedValue() noexcept
{ {
return static_cast<SmoothedValueClass<FloatType>*> (this)->getNextValue(); return static_cast <SmoothedValueType*> (this)->getNextValue();
} }
protected:
//==============================================================================
FloatType currentValue = 0;
FloatType target = currentValue;
int countdown = 0;
}; };
//============================================================================== //==============================================================================
/** /**
Utility class for linearly smoothed values like volume etc. that should A namespace containing a set of types used for specifying the smoothing
not change abruptly but as a linear ramp to avoid audio glitches. behaviour of the SmoothedValue class.
For example:
@code
SmoothedValue<float, ValueSmoothingTypes::Multiplicative> frequency (1.0f);
@endcode
*/
namespace ValueSmoothingTypes
{
/** Used to indicate a linear smoothing between values. */
struct Linear {};
/** Used to indicate a smoothing between multiplicative values. */
struct Multiplicative {};
}
//==============================================================================
/**
A utility class for values that need smoothing, like volume, that should not
change abruptly to avoid audio glitches.
To smooth values spread across an exponential range, where the increments
between the current and target value are multiplicative (like frequencies),
you should pass the multiplicative smoothing type as a template paramater:
@code
SmoothedValue<float, ValueSmoothingTypes::Multiplicative> yourSmoothedValue;
@endcode
Note that when you are using multiplicative smoothing you cannot ever reach a
target value of zero!
@tags{Audio} @tags{Audio}
*/ */
template <typename FloatType> template <typename FloatType, typename SmoothingType = ValueSmoothingTypes::Linear>
class LinearSmoothedValue : public SmoothedValueBase<LinearSmoothedValue, FloatType> class SmoothedValue : public SmoothedValueBase <SmoothedValue <FloatType, SmoothingType>>
{ {
public: public:
//============================================================================== //==============================================================================
/** Constructor. */ /** Constructor. */
LinearSmoothedValue() = default; SmoothedValue() noexcept
: SmoothedValue ((FloatType) (std::is_same<SmoothingType, ValueSmoothingTypes::Linear>::value ? 0 : 1))
{
}
/** Constructor. */ /** Constructor. */
LinearSmoothedValue (FloatType initialValue) noexcept SmoothedValue (FloatType initialValue) noexcept
{ {
// Multiplicative smoothed values cannot ever reach 0!
jassert (! (std::is_same<SmoothingType, ValueSmoothingTypes::Multiplicative>::value && initialValue == 0));
// Visual Studio can't handle base class initialisation with CRTP // Visual Studio can't handle base class initialisation with CRTP
this->currentValue = initialValue; this->currentValue = initialValue;
this->target = initialValue; this->target = this->currentValue;
} }
//============================================================================== //==============================================================================
@ -209,9 +261,13 @@ public:
return; return;
} }
// Multiplicative smoothed values cannot ever reach 0!
jassert (! (std::is_same<SmoothingType, ValueSmoothingTypes::Multiplicative>::value && newValue == 0));
this->target = newValue; this->target = newValue;
this->countdown = stepsToTarget; this->countdown = stepsToTarget;
step = (this->target - this->currentValue) / (FloatType) this->countdown;
setStepSize();
} }
//============================================================================== //==============================================================================
@ -225,8 +281,10 @@ public:
--(this->countdown); --(this->countdown);
this->currentValue = this->isSmoothing() ? this->currentValue + step if (this->isSmoothing())
: this->target; setNextValue();
else
this->currentValue = this->target;
return this->currentValue; return this->currentValue;
} }
@ -245,7 +303,8 @@ public:
return this->target; return this->target;
} }
this->currentValue += step * (FloatType) numSamples; skipCurrentValue (numSamples);
this->countdown -= numSamples; this->countdown -= numSamples;
return this->currentValue; return this->currentValue;
} }
@ -273,166 +332,58 @@ public:
}) })
private: private:
//==============================================================================
template <typename T>
using LinearVoid = typename std::enable_if <std::is_same <T, ValueSmoothingTypes::Linear>::value, void>::type;
template <typename T>
using MultiplicativeVoid = typename std::enable_if <std::is_same <T, ValueSmoothingTypes::Multiplicative>::value, void>::type;
//==============================================================================
template <typename T = SmoothingType>
LinearVoid<T> setStepSize() noexcept
{
step = (this->target - this->currentValue) / (FloatType) this->countdown;
}
template <typename T = SmoothingType>
MultiplicativeVoid<T> setStepSize()
{
step = std::exp ((std::log (std::abs (this->target)) - std::log (std::abs (this->currentValue))) / this->countdown);
}
//==============================================================================
template <typename T = SmoothingType>
LinearVoid<T> setNextValue() noexcept
{
this->currentValue += step;
}
template <typename T = SmoothingType>
MultiplicativeVoid<T> setNextValue() noexcept
{
this->currentValue *= step;
}
//==============================================================================
template <typename T = SmoothingType>
LinearVoid<T> skipCurrentValue (int numSamples) noexcept
{
this->currentValue += step * (FloatType) numSamples;
}
template <typename T = SmoothingType>
MultiplicativeVoid<T> skipCurrentValue (int numSamples)
{
this->currentValue *= (FloatType) std::pow (step, numSamples);
}
//============================================================================== //==============================================================================
FloatType step = FloatType(); FloatType step = FloatType();
int stepsToTarget = 0; 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 <typename FloatType> template <typename FloatType>
class LogSmoothedValue : public SmoothedValueBase<LogSmoothedValue, FloatType> using LinearSmoothedValue = SmoothedValue <FloatType, ValueSmoothingTypes::Linear>;
{
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 = (FloatType) 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 } // namespace juce

View file

@ -1,285 +0,0 @@
/*
==============================================================================
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 SmoothedValueType>
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<float> 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<float> result (1, numSamplesToGenerate);
for (int i = 0; i < numSamplesToGenerate; ++i)
result.setSample (0, i, 1.0f);
return result;
};
auto compareData = [this](const AudioBuffer<float>& test,
const AudioBuffer<float>& 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<float> 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<float> 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<LinearSmoothedValue<float>> commonLinearSmoothedValueTests;
static CommonSmoothedValueTests<LogSmoothedValue <float>> commonLogSmoothedValueTests;
class LogSmoothedValueTests : public UnitTest
{
public:
LogSmoothedValueTests()
: UnitTest ("LogSmoothedValueTests", "SmoothedValues")
{}
void runTest() override
{
beginTest ("Curve");
{
Array<double> levels = { -0.12243, -1.21245, -12.2342, -22.4683, -30.0, -61.18753 };
for (auto level : levels)
{
Array<Range<double>> ranges = { Range<double> (0.0, 1.0),
Range<double> (-2.345, 0.0),
Range<double> (-2.63, 3.56),
Range<double> (3.3, -0.2) };
for (auto range : ranges)
{
LogSmoothedValue<double> 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<double> 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

View file

@ -486,7 +486,8 @@ public:
} }
/** Multiplies all channels of the AudioBlock by a smoothly changing value and stores them . */ /** Multiplies all channels of the AudioBlock by a smoothly changing value and stores them . */
AudioBlock& multiply (LinearSmoothedValue<SampleType>& value) noexcept template <typename SmoothingType>
AudioBlock& multiply (SmoothedValue<SampleType, SmoothingType>& value) noexcept
{ {
if (! value.isSmoothing()) if (! value.isSmoothing())
{ {
@ -507,7 +508,8 @@ public:
} }
/** Multiplies all channels of the source by a smoothly changing value and stores them in the receiver. */ /** Multiplies all channels of the source by a smoothly changing value and stores them in the receiver. */
AudioBlock& multiply (AudioBlock src, LinearSmoothedValue<SampleType>& value) noexcept template <typename SmoothingType>
AudioBlock& multiply (AudioBlock src, SmoothedValue<SampleType, SmoothingType>& value) noexcept
{ {
jassert (numChannels == src.numChannels); jassert (numChannels == src.numChannels);
@ -632,7 +634,8 @@ public:
forcedinline AudioBlock& operator-= (AudioBlock src) noexcept { return subtract (src); } forcedinline AudioBlock& operator-= (AudioBlock src) noexcept { return subtract (src); }
forcedinline AudioBlock& JUCE_VECTOR_CALLTYPE operator*= (SampleType src) noexcept { return multiply (src); } forcedinline AudioBlock& JUCE_VECTOR_CALLTYPE operator*= (SampleType src) noexcept { return multiply (src); }
forcedinline AudioBlock& operator*= (AudioBlock src) noexcept { return multiply (src); } forcedinline AudioBlock& operator*= (AudioBlock src) noexcept { return multiply (src); }
forcedinline AudioBlock& operator*= (LinearSmoothedValue<SampleType>& value) noexcept { return multiply (value); } template <typename SmoothingType>
forcedinline AudioBlock& operator*= (SmoothedValue<SampleType, SmoothingType>& value) noexcept { return multiply (value); }
//============================================================================== //==============================================================================
// This class can only be used with floating point types // This class can only be used with floating point types

View file

@ -1046,7 +1046,7 @@ private:
OwnedArray<ConvolutionEngine> engines; // the 4 convolution engines being used OwnedArray<ConvolutionEngine> engines; // the 4 convolution engines being used
AudioBuffer<float> interpolationBuffer; // a buffer to do the interpolation between the convolution engines 0-1 and 2-3 AudioBuffer<float> interpolationBuffer; // a buffer to do the interpolation between the convolution engines 0-1 and 2-3
LogSmoothedValue<float> changeVolumes[4]; // the volumes for each convolution engine during interpolation LogRampedValue<float> changeVolumes[4]; // the volumes for each convolution engine during interpolation
bool mustInterpolate = false; // tells if the convolution engines outputs must be currently interpolated bool mustInterpolate = false; // tells if the convolution engines outputs must be currently interpolated

View file

@ -162,7 +162,7 @@ private:
double sampleRate; double sampleRate;
bool currentIsBypassed = false; bool currentIsBypassed = false;
bool isActive = false; bool isActive = false;
LinearSmoothedValue<float> volumeDry[2], volumeWet[2]; SmoothedValue<float> volumeDry[2], volumeWet[2];
AudioBlock<float> dryBuffer; AudioBlock<float> dryBuffer;
HeapBlock<char> dryBufferStorage; HeapBlock<char> dryBufferStorage;

View file

@ -251,6 +251,7 @@ namespace juce
#include "maths/juce_Polynomial.h" #include "maths/juce_Polynomial.h"
#include "maths/juce_FastMathApproximations.h" #include "maths/juce_FastMathApproximations.h"
#include "maths/juce_LookupTable.h" #include "maths/juce_LookupTable.h"
#include "maths/juce_LogRampedValue.h"
#include "containers/juce_AudioBlock.h" #include "containers/juce_AudioBlock.h"
#include "processors/juce_ProcessContext.h" #include "processors/juce_ProcessContext.h"
#include "processors/juce_ProcessorWrapper.h" #include "processors/juce_ProcessorWrapper.h"

View file

@ -0,0 +1,101 @@
/*
==============================================================================
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.
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
27th April 2017).
End User License Agreement: www.juce.com/juce-5-licence
Privacy Policy: www.juce.com/juce-5-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
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
{
namespace dsp
{
#if JUCE_UNIT_TESTS
static CommonSmoothedValueTests <LogSmoothedValue <float>> commonLogRampedValueTests;
class LogSmoothedValueTests : public UnitTest
{
public:
LogSmoothedValueTests()
: UnitTest ("LogSmoothedValueTests", "SmoothedValues")
{}
void runTest() override
{
beginTest ("Curve");
{
Array<double> levels = { -0.12243, -1.21245, -12.2342, -22.4683, -30.0, -61.18753 };
for (auto level : levels)
{
Array<Range<double>> ranges = { Range<double> (0.0, 1.0),
Range<double> (-2.345, 0.0),
Range<double> (-2.63, 3.56),
Range<double> (3.3, -0.2) };
for (auto range : ranges)
{
LogSmoothedValue<double> 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<double> 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 dsp
} // namespace juce

View file

@ -0,0 +1,190 @@
/*
==============================================================================
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.
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
27th April 2017).
End User License Agreement: www.juce.com/juce-5-licence
Privacy Policy: www.juce.com/juce-5-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
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
{
namespace dsp
{
//==============================================================================
/**
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 SmoothedValue
@tags{Audio}
*/
template <typename FloatType>
class LogRampedValue : public SmoothedValueBase <LogRampedValue <FloatType>>
{
public:
//==============================================================================
/** Constructor. */
LogRampedValue() = default;
/** Constructor. */
LogRampedValue (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 = (FloatType) 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 dsp
} // namespace juce

View file

@ -144,7 +144,7 @@ public:
private: private:
//============================================================================== //==============================================================================
LinearSmoothedValue<FloatType> bias; SmoothedValue<FloatType> bias;
double sampleRate = 0, rampDurationSeconds = 0; double sampleRate = 0, rampDurationSeconds = 0;
void updateRamp() noexcept void updateRamp() noexcept

View file

@ -139,7 +139,7 @@ public:
private: private:
//============================================================================== //==============================================================================
LinearSmoothedValue<FloatType> gain; SmoothedValue<FloatType> gain;
double sampleRate = 0, rampDurationSeconds = 0; double sampleRate = 0, rampDurationSeconds = 0;
}; };

View file

@ -118,7 +118,7 @@ private:
std::vector<std::array<Type, numStates>> state; std::vector<std::array<Type, numStates>> state;
std::array<Type, numStates> A; std::array<Type, numStates> A;
LinearSmoothedValue<Type> cutoffTransformSmoother, scaledResonanceSmoother; SmoothedValue<Type> cutoffTransformSmoother, scaledResonanceSmoother;
Type cutoffTransformValue, scaledResonanceValue; Type cutoffTransformValue, scaledResonanceValue;
LookupTableTransform<Type> saturationLUT { [] (Type x) { return std::tanh (x); }, Type (-5), Type (5), 128 }; LookupTableTransform<Type> saturationLUT { [] (Type x) { return std::tanh (x); }, Type (-5), Type (5), 128 };

View file

@ -243,7 +243,7 @@ private:
std::function<NumericType (NumericType)> generator; std::function<NumericType (NumericType)> generator;
std::unique_ptr<LookupTableTransform<NumericType>> lookupTable; std::unique_ptr<LookupTableTransform<NumericType>> lookupTable;
Array<NumericType> rampBuffer; Array<NumericType> rampBuffer;
LinearSmoothedValue<NumericType> frequency { static_cast<NumericType> (440.0) }; SmoothedValue<NumericType> frequency { static_cast<NumericType> (440.0) };
NumericType sampleRate = 48000.0; NumericType sampleRate = 48000.0;
Phase<NumericType> phase; Phase<NumericType> phase;
}; };