mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-10 23:44:24 +00:00
Added a LogSmoothedValue class
This commit is contained in:
parent
6f2f9afb06
commit
94f1641402
5 changed files with 725 additions and 242 deletions
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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 <typename FloatType>
|
||||
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<FloatType> (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<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] *= 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<FloatType> (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
|
||||
285
modules/juce_audio_basics/utilities/juce_SmoothedValues.cpp
Normal file
285
modules/juce_audio_basics/utilities/juce_SmoothedValues.cpp
Normal file
|
|
@ -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 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
|
||||
438
modules/juce_audio_basics/utilities/juce_SmoothedValues.h
Normal file
438
modules/juce_audio_basics/utilities/juce_SmoothedValues.h
Normal file
|
|
@ -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 <template<typename> 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<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);
|
||||
}
|
||||
}
|
||||
|
||||
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:
|
||||
//==============================================================================
|
||||
FloatType getNextSmoothedValue() noexcept
|
||||
{
|
||||
return static_cast<SmoothedValueClass<FloatType>*> (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 <typename FloatType>
|
||||
class LinearSmoothedValue : public SmoothedValueBase<LinearSmoothedValue, FloatType>
|
||||
{
|
||||
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 <typename FloatType>
|
||||
class LogSmoothedValue : public SmoothedValueBase<LogSmoothedValue, FloatType>
|
||||
{
|
||||
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
|
||||
Loading…
Add table
Add a link
Reference in a new issue