Loading...
Searching...
No Matches
juce_SmoothedValue.h
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2022 - Raw Material Software Limited
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
{
//==============================================================================
template <typename SmoothedValueType>
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:
using FloatType = typename FloatTypeHelper<SmoothedValueType>::Type;
//==============================================================================
SmoothedValueBase() = default;
//==============================================================================
bool isSmoothing() const noexcept { return countdown > 0; }
FloatType getCurrentValue() const noexcept { return currentValue; }
//==============================================================================
FloatType getTargetValue() const noexcept { return target; }
void setCurrentAndTargetValue (FloatType newValue)
{
target = currentValue = newValue;
countdown = 0;
}
//==============================================================================
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);
}
}
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);
}
}
void applyGain (AudioBuffer<FloatType>& 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);
}
}
private:
//==============================================================================
FloatType getNextSmoothedValue() noexcept
{
return static_cast <SmoothedValueType*> (this)->getNextValue();
}
protected:
//==============================================================================
FloatType currentValue = 0;
FloatType target = currentValue;
int countdown = 0;
};
//==============================================================================
namespace ValueSmoothingTypes
{
struct Linear {};
struct Multiplicative {};
}
//==============================================================================
template <typename FloatType, typename SmoothingType = ValueSmoothingTypes::Linear>
class SmoothedValue : public SmoothedValueBase <SmoothedValue <FloatType, SmoothingType>>
{
public:
//==============================================================================
SmoothedValue() noexcept
: SmoothedValue ((FloatType) (std::is_same_v<SmoothingType, ValueSmoothingTypes::Linear> ? 0 : 1))
{
}
SmoothedValue (FloatType initialValue) noexcept
{
// Multiplicative smoothed values cannot ever reach 0!
jassert (! (std::is_same_v<SmoothingType, ValueSmoothingTypes::Multiplicative>
&& approximatelyEqual (initialValue, (FloatType) 0)));
// Visual Studio can't handle base class initialisation with CRTP
this->currentValue = initialValue;
this->target = this->currentValue;
}
//==============================================================================
void reset (double sampleRate, double rampLengthInSeconds) noexcept
{
jassert (sampleRate > 0 && rampLengthInSeconds >= 0);
reset ((int) std::floor (rampLengthInSeconds * sampleRate));
}
void reset (int numSteps) noexcept
{
stepsToTarget = numSteps;
this->setCurrentAndTargetValue (this->target);
}
//==============================================================================
void setTargetValue (FloatType newValue) noexcept
{
if (approximatelyEqual (newValue, this->target))
return;
if (stepsToTarget <= 0)
{
this->setCurrentAndTargetValue (newValue);
return;
}
// Multiplicative smoothed values cannot ever reach 0!
jassert (! (std::is_same_v<SmoothingType, ValueSmoothingTypes::Multiplicative>
&& approximatelyEqual (newValue, (FloatType) 0)));
this->target = newValue;
this->countdown = stepsToTarget;
setStepSize();
}
//==============================================================================
FloatType getNextValue() noexcept
{
if (! this->isSmoothing())
return this->target;
--(this->countdown);
if (this->isSmoothing())
setNextValue();
else
this->currentValue = this->target;
return this->currentValue;
}
//==============================================================================
FloatType skip (int numSamples) noexcept
{
if (numSamples >= this->countdown)
{
this->setCurrentAndTargetValue (this->target);
return this->target;
}
skipCurrentValue (numSamples);
this->countdown -= numSamples;
return this->currentValue;
}
//==============================================================================
#ifndef DOXYGEN
[[deprecated ("Use setTargetValue and setCurrentAndTargetValue instead.")]]
void setValue (FloatType newValue, bool force = false) noexcept
{
if (force)
{
this->setCurrentAndTargetValue (newValue);
return;
}
setTargetValue (newValue);
}
#endif
private:
//==============================================================================
template <typename T = SmoothingType>
void setStepSize() noexcept
{
if constexpr (std::is_same_v<T, ValueSmoothingTypes::Linear>)
{
step = (this->target - this->currentValue) / (FloatType) this->countdown;
}
else if constexpr (std::is_same_v<T, ValueSmoothingTypes::Multiplicative>)
{
step = std::exp ((std::log (std::abs (this->target)) - std::log (std::abs (this->currentValue))) / (FloatType) this->countdown);
}
}
//==============================================================================
template <typename T = SmoothingType>
void setNextValue() noexcept
{
if constexpr (std::is_same_v<T, ValueSmoothingTypes::Linear>)
{
this->currentValue += step;
}
else if constexpr (std::is_same_v<T, ValueSmoothingTypes::Multiplicative>)
{
this->currentValue *= step;
}
}
//==============================================================================
template <typename T = SmoothingType>
void skipCurrentValue (int numSamples) noexcept
{
if constexpr (std::is_same_v<T, ValueSmoothingTypes::Linear>)
{
this->currentValue += step * (FloatType) numSamples;
}
else if constexpr (std::is_same_v<T, ValueSmoothingTypes::Multiplicative>)
{
this->currentValue *= (FloatType) std::pow (step, numSamples);
}
}
//==============================================================================
FloatType step = FloatType();
int stepsToTarget = 0;
};
template <typename FloatType>
using LinearSmoothedValue = SmoothedValue <FloatType, ValueSmoothingTypes::Linear>;
//==============================================================================
//==============================================================================
#if JUCE_UNIT_TESTS
template <class SmoothedValueType>
class CommonSmoothedValueTests : public UnitTest
{
public:
CommonSmoothedValueTests()
: UnitTest ("CommonSmoothedValueTests", UnitTestCategories::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(),
2.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),
2.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 numValues = 12;
sv.reset (numValues);
std::vector<std::pair<float, float>> ranges = { { -1.0f, -2.0f },
{ -100.0f, -3.0f } };
for (auto range : ranges)
{
auto start = range.first, end = range.second;
sv.setCurrentAndTargetValue (start);
sv.setTargetValue (end);
auto val = sv.skip (numValues / 2);
if (end > start)
expect (val > start && val < end);
else
expect (val < start && val > end);
auto nextVal = sv.getNextValue();
expect (end > start ? (nextVal > val) : (nextVal < val));
auto endVal = sv.skip (500);
expectEquals (endVal, end);
expectEquals (sv.getNextValue(), end);
expectEquals (sv.getCurrentValue(), end);
sv.setCurrentAndTargetValue (start);
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());
}
}
}
};
#endif
} // namespace juce
juce::AudioBuffer
Definition juce_AudioSampleBuffer.h:34
juce::SmoothedValueBase::isSmoothing
bool isSmoothing() const noexcept
Definition juce_SmoothedValue.h:63
juce::SmoothedValueBase::SmoothedValueBase
SmoothedValueBase()=default
juce::SmoothedValueBase::applyGain
void applyGain(FloatType *samples, int numSamples) noexcept
Definition juce_SmoothedValue.h:87
juce::SmoothedValueBase::applyGain
void applyGain(AudioBuffer< FloatType > &buffer, int numSamples) noexcept
Definition juce_SmoothedValue.h:124
juce::SmoothedValueBase::setCurrentAndTargetValue
void setCurrentAndTargetValue(FloatType newValue)
Definition juce_SmoothedValue.h:75
juce::SmoothedValueBase::getTargetValue
FloatType getTargetValue() const noexcept
Definition juce_SmoothedValue.h:70
juce::SmoothedValueBase::getCurrentValue
FloatType getCurrentValue() const noexcept
Definition juce_SmoothedValue.h:66
juce::SmoothedValueBase::applyGain
void applyGain(FloatType *samplesOut, const FloatType *samplesIn, int numSamples) noexcept
Definition juce_SmoothedValue.h:108
juce::SmoothedValue::skip
FloatType skip(int numSamples) noexcept
Definition juce_SmoothedValue.h:318
juce::SmoothedValue::getNextValue
FloatType getNextValue() noexcept
Definition juce_SmoothedValue.h:297
juce::SmoothedValue::setValue
void setValue(FloatType newValue, bool force=false) noexcept
Definition juce_SmoothedValue.h:343
juce::SmoothedValue::SmoothedValue
SmoothedValue(FloatType initialValue) noexcept
Definition juce_SmoothedValue.h:237
juce::SmoothedValue::reset
void reset(double sampleRate, double rampLengthInSeconds) noexcept
Definition juce_SmoothedValue.h:253
juce::SmoothedValue::SmoothedValue
SmoothedValue() noexcept
Definition juce_SmoothedValue.h:231
juce::SmoothedValue::reset
void reset(int numSteps) noexcept
Definition juce_SmoothedValue.h:262
juce::SmoothedValue::setTargetValue
void setTargetValue(FloatType newValue) noexcept
Definition juce_SmoothedValue.h:272
juce::UnitTest
Definition juce_UnitTest.h:70
juce::ValueSmoothingTypes
Definition juce_SmoothedValue.h:179
juce::ValueSmoothingTypes::Linear
Definition juce_SmoothedValue.h:185
juce::ValueSmoothingTypes::Multiplicative
Definition juce_SmoothedValue.h:192