333 lines
14 KiB
C
333 lines
14 KiB
C
|
/*
|
||
|
==============================================================================
|
||
|
|
||
|
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
|
||
|
{
|
||
|
|
||
|
/**
|
||
|
Class for efficiently approximating expensive arithmetic operations.
|
||
|
|
||
|
The approximation is based on linear interpolation between pre-calculated values.
|
||
|
The approximated function should be passed as a callable object to the constructor
|
||
|
along with the number of data points to be pre-calculated. The accuracy of the
|
||
|
approximation can be increased by using more points at the cost of a larger memory
|
||
|
footprint.
|
||
|
|
||
|
Consider using LookupTableTransform as an easy-to-use alternative.
|
||
|
|
||
|
Example:
|
||
|
|
||
|
LookupTable<float> lut ([] (size_t i) { return std::sqrt ((float) i); }, 64);
|
||
|
auto outValue = lut[17];
|
||
|
|
||
|
@see LookupTableTransform
|
||
|
|
||
|
@tags{DSP}
|
||
|
*/
|
||
|
template <typename FloatType>
|
||
|
class LookupTable
|
||
|
{
|
||
|
public:
|
||
|
/** Creates an uninitialised LookupTable object.
|
||
|
|
||
|
You need to call initialise() before using the object. Prefer using the
|
||
|
non-default constructor instead.
|
||
|
|
||
|
@see initialise
|
||
|
*/
|
||
|
LookupTable();
|
||
|
|
||
|
/** Creates and initialises a LookupTable object.
|
||
|
|
||
|
@param functionToApproximate The function to be approximated. This should be a
|
||
|
mapping from the integer range [0, numPointsToUse - 1].
|
||
|
@param numPointsToUse The number of pre-calculated values stored.
|
||
|
*/
|
||
|
LookupTable (const std::function<FloatType (size_t)>& functionToApproximate, size_t numPointsToUse);
|
||
|
|
||
|
/** Initialises or changes the parameters of a LookupTable object.
|
||
|
|
||
|
This function can be used to change what function is approximated by an already
|
||
|
constructed LookupTable along with the number of data points used. If the function
|
||
|
to be approximated won't ever change, prefer using the non-default constructor.
|
||
|
|
||
|
@param functionToApproximate The function to be approximated. This should be a
|
||
|
mapping from the integer range [0, numPointsToUse - 1].
|
||
|
@param numPointsToUse The number of pre-calculated values stored.
|
||
|
*/
|
||
|
void initialise (const std::function<FloatType (size_t)>& functionToApproximate, size_t numPointsToUse);
|
||
|
|
||
|
//==============================================================================
|
||
|
/** Calculates the approximated value for the given index without range checking.
|
||
|
|
||
|
Use this if you can guarantee that the index is non-negative and less than numPoints.
|
||
|
Otherwise use get().
|
||
|
|
||
|
@param index The approximation is calculated for this non-integer index.
|
||
|
@return The approximated value at the given index.
|
||
|
|
||
|
@see get, operator[]
|
||
|
*/
|
||
|
FloatType getUnchecked (FloatType index) const noexcept
|
||
|
{
|
||
|
jassert (isInitialised()); // Use the non-default constructor or call initialise() before first use
|
||
|
jassert (isPositiveAndBelow (index, FloatType (getNumPoints())));
|
||
|
|
||
|
auto i = truncatePositiveToUnsignedInt (index);
|
||
|
auto f = index - FloatType (i);
|
||
|
jassert (isPositiveAndBelow (f, FloatType (1)));
|
||
|
|
||
|
auto x0 = data.getUnchecked (static_cast<int> (i));
|
||
|
auto x1 = data.getUnchecked (static_cast<int> (i + 1));
|
||
|
|
||
|
return jmap (f, x0, x1);
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
/** Calculates the approximated value for the given index with range checking.
|
||
|
|
||
|
This can be called with any input indices. If the provided index is out-of-range
|
||
|
either the bottom or the top element of the LookupTable is returned.
|
||
|
|
||
|
If the index is guaranteed to be in range use the faster getUnchecked() instead.
|
||
|
|
||
|
@param index The approximation is calculated for this non-integer index.
|
||
|
@return The approximated value at the given index.
|
||
|
|
||
|
@see getUnchecked, operator[]
|
||
|
*/
|
||
|
FloatType get (FloatType index) const noexcept
|
||
|
{
|
||
|
if (index >= getNumPoints())
|
||
|
index = static_cast<FloatType> (getGuardIndex());
|
||
|
else if (index < 0)
|
||
|
index = {};
|
||
|
|
||
|
return getUnchecked (index);
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
/** @see getUnchecked */
|
||
|
FloatType operator[] (FloatType index) const noexcept { return getUnchecked (index); }
|
||
|
|
||
|
/** Returns the size of the LookupTable, i.e., the number of pre-calculated data points. */
|
||
|
size_t getNumPoints() const noexcept { return static_cast<size_t> (data.size()) - 1; }
|
||
|
|
||
|
/** Returns true if the LookupTable is initialised and ready to be used. */
|
||
|
bool isInitialised() const noexcept { return data.size() > 1; }
|
||
|
|
||
|
private:
|
||
|
//==============================================================================
|
||
|
Array<FloatType> data;
|
||
|
|
||
|
void prepare() noexcept;
|
||
|
static size_t getRequiredBufferSize (size_t numPointsToUse) noexcept { return numPointsToUse + 1; }
|
||
|
size_t getGuardIndex() const noexcept { return getRequiredBufferSize (getNumPoints()) - 1; }
|
||
|
|
||
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LookupTable)
|
||
|
};
|
||
|
|
||
|
|
||
|
//==============================================================================
|
||
|
/** Class for approximating expensive arithmetic operations.
|
||
|
|
||
|
Once initialised, this class can be used just like the function it approximates
|
||
|
via operator().
|
||
|
|
||
|
Example:
|
||
|
|
||
|
LookupTableTransform<float> tanhApprox ([] (float x) { return std::tanh (x); }, -5.0f, 5.0f, 64);
|
||
|
auto outValue = tanhApprox (4.2f);
|
||
|
|
||
|
Note : if you try to call the function with an input outside the provided
|
||
|
range, it will return either the first or the last recorded LookupTable value.
|
||
|
|
||
|
@see LookupTable
|
||
|
|
||
|
@tags{DSP}
|
||
|
*/
|
||
|
template <typename FloatType>
|
||
|
class LookupTableTransform
|
||
|
{
|
||
|
public:
|
||
|
//==============================================================================
|
||
|
/** Creates an uninitialised LookupTableTransform object.
|
||
|
|
||
|
You need to call initialise() before using the object. Prefer using the
|
||
|
non-default constructor instead.
|
||
|
|
||
|
@see initialise
|
||
|
*/
|
||
|
LookupTableTransform()
|
||
|
{}
|
||
|
|
||
|
//==============================================================================
|
||
|
/** Creates and initialises a LookupTableTransform object.
|
||
|
|
||
|
@param functionToApproximate The function to be approximated. This should be a
|
||
|
mapping from a FloatType to FloatType.
|
||
|
@param minInputValueToUse The lowest input value used. The approximation will
|
||
|
fail for values lower than this.
|
||
|
@param maxInputValueToUse The highest input value used. The approximation will
|
||
|
fail for values higher than this.
|
||
|
@param numPoints The number of pre-calculated values stored.
|
||
|
*/
|
||
|
LookupTableTransform (const std::function<FloatType (FloatType)>& functionToApproximate,
|
||
|
FloatType minInputValueToUse,
|
||
|
FloatType maxInputValueToUse,
|
||
|
size_t numPoints)
|
||
|
{
|
||
|
initialise (functionToApproximate, minInputValueToUse, maxInputValueToUse, numPoints);
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
/** Initialises or changes the parameters of a LookupTableTransform object.
|
||
|
|
||
|
@param functionToApproximate The function to be approximated. This should be a
|
||
|
mapping from a FloatType to FloatType.
|
||
|
@param minInputValueToUse The lowest input value used. The approximation will
|
||
|
fail for values lower than this.
|
||
|
@param maxInputValueToUse The highest input value used. The approximation will
|
||
|
fail for values higher than this.
|
||
|
@param numPoints The number of pre-calculated values stored.
|
||
|
*/
|
||
|
void initialise (const std::function<FloatType (FloatType)>& functionToApproximate,
|
||
|
FloatType minInputValueToUse,
|
||
|
FloatType maxInputValueToUse,
|
||
|
size_t numPoints);
|
||
|
|
||
|
//==============================================================================
|
||
|
/** Calculates the approximated value for the given input value without range checking.
|
||
|
|
||
|
Use this if you can guarantee that the input value is within the range specified
|
||
|
in the constructor or initialise(), otherwise use processSample().
|
||
|
|
||
|
@param value The approximation is calculated for this input value.
|
||
|
@return The approximated value for the provided input value.
|
||
|
|
||
|
@see processSample, operator(), operator[]
|
||
|
*/
|
||
|
FloatType processSampleUnchecked (FloatType value) const noexcept
|
||
|
{
|
||
|
jassert (value >= minInputValue && value <= maxInputValue);
|
||
|
return lookupTable[scaler * value + offset];
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
/** Calculates the approximated value for the given input value with range checking.
|
||
|
|
||
|
This can be called with any input values. Out-of-range input values will be
|
||
|
clipped to the specified input range.
|
||
|
|
||
|
If the index is guaranteed to be in range use the faster processSampleUnchecked()
|
||
|
instead.
|
||
|
|
||
|
@param value The approximation is calculated for this input value.
|
||
|
@return The approximated value for the provided input value.
|
||
|
|
||
|
@see processSampleUnchecked, operator(), operator[]
|
||
|
*/
|
||
|
FloatType processSample (FloatType value) const noexcept
|
||
|
{
|
||
|
auto index = scaler * jlimit (minInputValue, maxInputValue, value) + offset;
|
||
|
jassert (isPositiveAndBelow (index, FloatType (lookupTable.getNumPoints())));
|
||
|
|
||
|
return lookupTable[index];
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
/** @see processSampleUnchecked */
|
||
|
FloatType operator[] (FloatType index) const noexcept { return processSampleUnchecked (index); }
|
||
|
|
||
|
/** @see processSample */
|
||
|
FloatType operator() (FloatType index) const noexcept { return processSample (index); }
|
||
|
|
||
|
//==============================================================================
|
||
|
/** Processes an array of input values without range checking
|
||
|
@see process
|
||
|
*/
|
||
|
void processUnchecked (const FloatType* input, FloatType* output, size_t numSamples) const noexcept
|
||
|
{
|
||
|
for (size_t i = 0; i < numSamples; ++i)
|
||
|
output[i] = processSampleUnchecked (input[i]);
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
/** Processes an array of input values with range checking
|
||
|
@see processUnchecked
|
||
|
*/
|
||
|
void process (const FloatType* input, FloatType* output, size_t numSamples) const noexcept
|
||
|
{
|
||
|
for (size_t i = 0; i < numSamples; ++i)
|
||
|
output[i] = processSample (input[i]);
|
||
|
}
|
||
|
|
||
|
//==============================================================================
|
||
|
/** Calculates the maximum relative error of the approximation for the specified
|
||
|
parameter set.
|
||
|
|
||
|
The closer the returned value is to zero the more accurate the approximation
|
||
|
is.
|
||
|
|
||
|
This function compares the approximated output of this class to the function
|
||
|
it approximates at a range of points and returns the maximum relative error.
|
||
|
This can be used to determine if the approximation is suitable for the given
|
||
|
problem. The accuracy of the approximation can generally be improved by
|
||
|
increasing numPoints.
|
||
|
|
||
|
@param functionToApproximate The approximated function. This should be a
|
||
|
mapping from a FloatType to FloatType.
|
||
|
@param minInputValue The lowest input value used.
|
||
|
@param maxInputValue The highest input value used.
|
||
|
@param numPoints The number of pre-calculated values stored.
|
||
|
@param numTestPoints The number of input values used for error
|
||
|
calculation. Higher numbers can increase the
|
||
|
accuracy of the error calculation. If it's zero
|
||
|
then 100 * numPoints will be used.
|
||
|
*/
|
||
|
static double calculateMaxRelativeError (const std::function<FloatType (FloatType)>& functionToApproximate,
|
||
|
FloatType minInputValue,
|
||
|
FloatType maxInputValue,
|
||
|
size_t numPoints,
|
||
|
size_t numTestPoints = 0);
|
||
|
private:
|
||
|
//==============================================================================
|
||
|
static double calculateRelativeDifference (double, double) noexcept;
|
||
|
|
||
|
//==============================================================================
|
||
|
LookupTable<FloatType> lookupTable;
|
||
|
|
||
|
FloatType minInputValue, maxInputValue;
|
||
|
FloatType scaler, offset;
|
||
|
|
||
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LookupTableTransform)
|
||
|
};
|
||
|
|
||
|
} // namespace dsp
|
||
|
} // namespace juce
|