upgrade to JUCE 5.4.3. Remove (probably) unused JUCE modules. Remove VST2 target (it's been end-of-life'd by Steinberg and by JUCE)
This commit is contained in:
255
modules/juce_audio_basics/utilities/juce_ADSR.h
Normal file
255
modules/juce_audio_basics/utilities/juce_ADSR.h
Normal file
@ -0,0 +1,255 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
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 very simple ADSR envelope class.
|
||||
|
||||
To use it, call setSampleRate() with the current sample rate and give it some parameters
|
||||
with setParameters() then call getNextSample() to get the envelope value to be applied
|
||||
to each audio sample or applyEnvelopeToBuffer() to apply the envelope to a whole buffer.
|
||||
*/
|
||||
class ADSR
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
ADSR()
|
||||
{
|
||||
setSampleRate (44100.0);
|
||||
setParameters ({});
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
/** Holds the parameters being used by an ADSR object. */
|
||||
struct Parameters
|
||||
{
|
||||
/** Attack time in seconds. */
|
||||
float attack = 0.1f;
|
||||
|
||||
/** Decay time in seconds. */
|
||||
float decay = 0.1f;
|
||||
|
||||
/** Sustain level. */
|
||||
float sustain = 1.0f;
|
||||
|
||||
/** Release time in seconds. */
|
||||
float release = 0.1f;
|
||||
};
|
||||
|
||||
/** Sets the parameters that will be used by an ADSR object.
|
||||
|
||||
You must have called setSampleRate() with the correct sample rate before
|
||||
this otherwise the values may be incorrect!
|
||||
|
||||
@see getParameters
|
||||
*/
|
||||
void setParameters (const Parameters& newParameters)
|
||||
{
|
||||
currentParameters = newParameters;
|
||||
|
||||
sustainLevel = newParameters.sustain;
|
||||
calculateRates (newParameters);
|
||||
|
||||
if (currentState != State::idle)
|
||||
checkCurrentState();
|
||||
}
|
||||
|
||||
/** Returns the parameters currently being used by an ADSR object.
|
||||
|
||||
@see setParameters
|
||||
*/
|
||||
const Parameters& getParameters() const { return currentParameters; }
|
||||
|
||||
/** Returns true if the envelope is in its attack, decay, sustain or release stage. */
|
||||
bool isActive() const noexcept { return currentState != State::idle; }
|
||||
|
||||
//==============================================================================
|
||||
/** Sets the sample rate that will be used for the envelope.
|
||||
|
||||
This must be called before the getNextSample() or setParameters() methods.
|
||||
*/
|
||||
void setSampleRate (double sampleRate)
|
||||
{
|
||||
jassert (sampleRate > 0.0);
|
||||
sr = sampleRate;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
/** Resets the envelope to an idle state. */
|
||||
void reset()
|
||||
{
|
||||
envelopeVal = 0.0f;
|
||||
currentState = State::idle;
|
||||
|
||||
if (resetReleaseRate)
|
||||
{
|
||||
releaseRate = static_cast<float> (sustainLevel / (currentParameters.release * sr));
|
||||
resetReleaseRate = false;
|
||||
}
|
||||
}
|
||||
|
||||
/** Starts the attack phase of the envelope. */
|
||||
void noteOn()
|
||||
{
|
||||
if (attackRate > 0.0f)
|
||||
{
|
||||
currentState = State::attack;
|
||||
}
|
||||
else if (decayRate > 0.0f)
|
||||
{
|
||||
envelopeVal = 1.0f;
|
||||
currentState = State::decay;
|
||||
}
|
||||
else
|
||||
{
|
||||
currentState = State::sustain;
|
||||
}
|
||||
}
|
||||
|
||||
/** Starts the release phase of the envelope. */
|
||||
void noteOff()
|
||||
{
|
||||
if (currentState != State::idle)
|
||||
{
|
||||
if (releaseRate > 0.0f)
|
||||
{
|
||||
if (currentState != State::sustain)
|
||||
{
|
||||
releaseRate = static_cast<float> (envelopeVal / (currentParameters.release * sr));
|
||||
resetReleaseRate = true;
|
||||
}
|
||||
|
||||
currentState = State::release;
|
||||
}
|
||||
else
|
||||
{
|
||||
reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the next sample value for an ADSR object.
|
||||
|
||||
@see applyEnvelopeToBuffer
|
||||
*/
|
||||
float getNextSample()
|
||||
{
|
||||
if (currentState == State::idle)
|
||||
return 0.0f;
|
||||
|
||||
if (currentState == State::attack)
|
||||
{
|
||||
envelopeVal += attackRate;
|
||||
|
||||
if (envelopeVal >= 1.0f)
|
||||
{
|
||||
envelopeVal = 1.0f;
|
||||
|
||||
if (decayRate > 0.0f)
|
||||
currentState = State::decay;
|
||||
else
|
||||
currentState = State::sustain;
|
||||
}
|
||||
}
|
||||
else if (currentState == State::decay)
|
||||
{
|
||||
envelopeVal -= decayRate;
|
||||
|
||||
if (envelopeVal <= sustainLevel)
|
||||
{
|
||||
envelopeVal = sustainLevel;
|
||||
currentState = State::sustain;
|
||||
}
|
||||
}
|
||||
else if (currentState == State::sustain)
|
||||
{
|
||||
envelopeVal = sustainLevel;
|
||||
}
|
||||
else if (currentState == State::release)
|
||||
{
|
||||
envelopeVal -= releaseRate;
|
||||
|
||||
if (envelopeVal <= 0.0f)
|
||||
reset();
|
||||
}
|
||||
|
||||
return envelopeVal;
|
||||
}
|
||||
|
||||
/** This method will conveniently apply the next numSamples number of envelope values
|
||||
to an AudioBuffer.
|
||||
|
||||
@see getNextSample
|
||||
*/
|
||||
template<typename FloatType>
|
||||
void applyEnvelopeToBuffer (AudioBuffer<FloatType>& buffer, int startSample, int numSamples)
|
||||
{
|
||||
jassert (startSample + numSamples <= buffer.getNumSamples());
|
||||
|
||||
auto numChannels = buffer.getNumChannels();
|
||||
|
||||
while (--numSamples >= 0)
|
||||
{
|
||||
auto env = getNextSample();
|
||||
|
||||
for (int i = 0; i < numChannels; ++i)
|
||||
buffer.getWritePointer (i)[startSample] *= env;
|
||||
|
||||
++startSample;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
void calculateRates (const Parameters& parameters)
|
||||
{
|
||||
// need to call setSampleRate() first!
|
||||
jassert (sr > 0.0);
|
||||
|
||||
attackRate = (parameters.attack > 0.0f ? static_cast<float> (1.0f / (parameters.attack * sr)) : -1.0f);
|
||||
decayRate = (parameters.decay > 0.0f ? static_cast<float> ((1.0f - sustainLevel) / (parameters.decay * sr)) : -1.0f);
|
||||
releaseRate = (parameters.release > 0.0f ? static_cast<float> (sustainLevel / (parameters.release * sr)) : -1.0f);
|
||||
}
|
||||
|
||||
void checkCurrentState()
|
||||
{
|
||||
if (currentState == State::attack && attackRate <= 0.0f) currentState = decayRate > 0.0f ? State::decay : State::sustain;
|
||||
else if (currentState == State::decay && decayRate <= 0.0f) currentState = State::sustain;
|
||||
else if (currentState == State::release && releaseRate <= 0.0f) reset();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
enum class State { idle, attack, decay, sustain, release };
|
||||
|
||||
State currentState = State::idle;
|
||||
Parameters currentParameters;
|
||||
|
||||
double sr = 0.0;
|
||||
float envelopeVal = 0.0f, sustainLevel = 0.0f, attackRate = 0.0f, decayRate = 0.0f, releaseRate = 0.0f;
|
||||
bool resetReleaseRate = false;
|
||||
};
|
||||
|
||||
} // namespace juce
|
@ -0,0 +1,75 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
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
|
||||
{
|
||||
|
||||
struct CatmullRomAlgorithm
|
||||
{
|
||||
static forcedinline float valueAtOffset (const float* const inputs, const float offset) noexcept
|
||||
{
|
||||
auto y0 = inputs[3];
|
||||
auto y1 = inputs[2];
|
||||
auto y2 = inputs[1];
|
||||
auto y3 = inputs[0];
|
||||
|
||||
auto halfY0 = 0.5f * y0;
|
||||
auto halfY3 = 0.5f * y3;
|
||||
|
||||
return y1 + offset * ((0.5f * y2 - halfY0)
|
||||
+ (offset * (((y0 + 2.0f * y2) - (halfY3 + 2.5f * y1))
|
||||
+ (offset * ((halfY3 + 1.5f * y1) - (halfY0 + 1.5f * y2))))));
|
||||
}
|
||||
};
|
||||
|
||||
CatmullRomInterpolator::CatmullRomInterpolator() noexcept { reset(); }
|
||||
CatmullRomInterpolator::~CatmullRomInterpolator() noexcept {}
|
||||
|
||||
void CatmullRomInterpolator::reset() noexcept
|
||||
{
|
||||
subSamplePos = 1.0;
|
||||
|
||||
for (auto& s : lastInputSamples)
|
||||
s = 0;
|
||||
}
|
||||
|
||||
int CatmullRomInterpolator::process (double actualRatio, const float* in, float* out, int numOut, int available, int wrap) noexcept
|
||||
{
|
||||
return interpolate<CatmullRomAlgorithm> (lastInputSamples, subSamplePos, actualRatio, in, out, numOut, available, wrap);
|
||||
}
|
||||
|
||||
int CatmullRomInterpolator::process (double actualRatio, const float* in, float* out, int numOut) noexcept
|
||||
{
|
||||
return interpolate<CatmullRomAlgorithm> (lastInputSamples, subSamplePos, actualRatio, in, out, numOut);
|
||||
}
|
||||
|
||||
int CatmullRomInterpolator::processAdding (double actualRatio, const float* in, float* out, int numOut, int available, int wrap, float gain) noexcept
|
||||
{
|
||||
return interpolateAdding<CatmullRomAlgorithm> (lastInputSamples, subSamplePos, actualRatio, in, out, numOut, available, wrap, gain);
|
||||
}
|
||||
|
||||
int CatmullRomInterpolator::processAdding (double actualRatio, const float* in, float* out, int numOut, float gain) noexcept
|
||||
{
|
||||
return interpolateAdding<CatmullRomAlgorithm> (lastInputSamples, subSamplePos, actualRatio, in, out, numOut, gain);
|
||||
}
|
||||
|
||||
} // namespace juce
|
@ -0,0 +1,143 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
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
|
||||
{
|
||||
|
||||
/**
|
||||
Interpolator for resampling a stream of floats using Catmull-Rom interpolation.
|
||||
|
||||
Note that the resampler is stateful, so when there's a break in the continuity
|
||||
of the input stream you're feeding it, you should call reset() before feeding
|
||||
it any new data. And like with any other stateful filter, if you're resampling
|
||||
multiple channels, make sure each one uses its own CatmullRomInterpolator
|
||||
object.
|
||||
|
||||
@see LagrangeInterpolator
|
||||
|
||||
@tags{Audio}
|
||||
*/
|
||||
class JUCE_API CatmullRomInterpolator
|
||||
{
|
||||
public:
|
||||
CatmullRomInterpolator() noexcept;
|
||||
~CatmullRomInterpolator() noexcept;
|
||||
|
||||
/** Resets the state of the interpolator.
|
||||
Call this when there's a break in the continuity of the input data stream.
|
||||
*/
|
||||
void reset() noexcept;
|
||||
|
||||
/** Resamples a stream of samples.
|
||||
|
||||
@param speedRatio the number of input samples to use for each output sample
|
||||
@param inputSamples the source data to read from. This must contain at
|
||||
least (speedRatio * numOutputSamplesToProduce) samples.
|
||||
@param outputSamples the buffer to write the results into
|
||||
@param numOutputSamplesToProduce the number of output samples that should be created
|
||||
|
||||
@returns the actual number of input samples that were used
|
||||
*/
|
||||
int process (double speedRatio,
|
||||
const float* inputSamples,
|
||||
float* outputSamples,
|
||||
int numOutputSamplesToProduce) noexcept;
|
||||
|
||||
/** Resamples a stream of samples.
|
||||
|
||||
@param speedRatio the number of input samples to use for each output sample
|
||||
@param inputSamples the source data to read from. This must contain at
|
||||
least (speedRatio * numOutputSamplesToProduce) samples.
|
||||
@param outputSamples the buffer to write the results into
|
||||
@param numOutputSamplesToProduce the number of output samples that should be created
|
||||
@param available the number of available input samples. If it needs more samples
|
||||
than available, it either wraps back for wrapAround samples, or
|
||||
it feeds zeroes
|
||||
@param wrapAround if the stream exceeds available samples, it wraps back for
|
||||
wrapAround samples. If wrapAround is set to 0, it will feed zeroes.
|
||||
|
||||
@returns the actual number of input samples that were used
|
||||
*/
|
||||
int process (double speedRatio,
|
||||
const float* inputSamples,
|
||||
float* outputSamples,
|
||||
int numOutputSamplesToProduce,
|
||||
int available,
|
||||
int wrapAround) noexcept;
|
||||
|
||||
/** Resamples a stream of samples, adding the results to the output data
|
||||
with a gain.
|
||||
|
||||
@param speedRatio the number of input samples to use for each output sample
|
||||
@param inputSamples the source data to read from. This must contain at
|
||||
least (speedRatio * numOutputSamplesToProduce) samples.
|
||||
@param outputSamples the buffer to write the results to - the result values will be added
|
||||
to any pre-existing data in this buffer after being multiplied by
|
||||
the gain factor
|
||||
@param numOutputSamplesToProduce the number of output samples that should be created
|
||||
@param gain a gain factor to multiply the resulting samples by before
|
||||
adding them to the destination buffer
|
||||
|
||||
@returns the actual number of input samples that were used
|
||||
*/
|
||||
int processAdding (double speedRatio,
|
||||
const float* inputSamples,
|
||||
float* outputSamples,
|
||||
int numOutputSamplesToProduce,
|
||||
float gain) noexcept;
|
||||
|
||||
/** Resamples a stream of samples, adding the results to the output data
|
||||
with a gain.
|
||||
|
||||
@param speedRatio the number of input samples to use for each output sample
|
||||
@param inputSamples the source data to read from. This must contain at
|
||||
least (speedRatio * numOutputSamplesToProduce) samples.
|
||||
@param outputSamples the buffer to write the results to - the result values will be added
|
||||
to any pre-existing data in this buffer after being multiplied by
|
||||
the gain factor
|
||||
@param numOutputSamplesToProduce the number of output samples that should be created
|
||||
@param available the number of available input samples. If it needs more samples
|
||||
than available, it either wraps back for wrapAround samples, or
|
||||
it feeds zeroes
|
||||
@param wrapAround if the stream exceeds available samples, it wraps back for
|
||||
wrapAround samples. If wrapAround is set to 0, it will feed zeroes.
|
||||
@param gain a gain factor to multiply the resulting samples by before
|
||||
adding them to the destination buffer
|
||||
|
||||
@returns the actual number of input samples that were used
|
||||
*/
|
||||
int processAdding (double speedRatio,
|
||||
const float* inputSamples,
|
||||
float* outputSamples,
|
||||
int numOutputSamplesToProduce,
|
||||
int available,
|
||||
int wrapAround,
|
||||
float gain) noexcept;
|
||||
|
||||
private:
|
||||
float lastInputSamples[5];
|
||||
double subSamplePos;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CatmullRomInterpolator)
|
||||
};
|
||||
|
||||
} // namespace juce
|
112
modules/juce_audio_basics/utilities/juce_Decibels.h
Normal file
112
modules/juce_audio_basics/utilities/juce_Decibels.h
Normal file
@ -0,0 +1,112 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
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
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
This class contains some helpful static methods for dealing with decibel values.
|
||||
|
||||
@tags{Audio}
|
||||
*/
|
||||
class Decibels
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Converts a dBFS value to its equivalent gain level.
|
||||
|
||||
A gain of 1.0 = 0 dB, and lower gains map onto negative decibel values. Any
|
||||
decibel value lower than minusInfinityDb will return a gain of 0.
|
||||
*/
|
||||
template <typename Type>
|
||||
static Type decibelsToGain (Type decibels,
|
||||
Type minusInfinityDb = Type (defaultMinusInfinitydB))
|
||||
{
|
||||
return decibels > minusInfinityDb ? std::pow (Type (10.0), decibels * Type (0.05))
|
||||
: Type();
|
||||
}
|
||||
|
||||
/** Converts a gain level into a dBFS value.
|
||||
|
||||
A gain of 1.0 = 0 dB, and lower gains map onto negative decibel values.
|
||||
If the gain is 0 (or negative), then the method will return the value
|
||||
provided as minusInfinityDb.
|
||||
*/
|
||||
template <typename Type>
|
||||
static Type gainToDecibels (Type gain,
|
||||
Type minusInfinityDb = Type (defaultMinusInfinitydB))
|
||||
{
|
||||
return gain > Type() ? jmax (minusInfinityDb, static_cast<Type> (std::log10 (gain)) * Type (20.0))
|
||||
: minusInfinityDb;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
/** Converts a decibel reading to a string.
|
||||
|
||||
By default the returned string will have the 'dB' suffix added, but this can be removed by
|
||||
setting the shouldIncludeSuffix argument to false. If a customMinusInfinityString argument
|
||||
is provided this will be returned if the value is lower than minusInfinityDb, otherwise
|
||||
the return value will be "-INF".
|
||||
*/
|
||||
template <typename Type>
|
||||
static String toString (Type decibels,
|
||||
int decimalPlaces = 2,
|
||||
Type minusInfinityDb = Type (defaultMinusInfinitydB),
|
||||
bool shouldIncludeSuffix = true,
|
||||
StringRef customMinusInfinityString = {})
|
||||
{
|
||||
String s;
|
||||
s.preallocateBytes (20);
|
||||
|
||||
if (decibels <= minusInfinityDb)
|
||||
{
|
||||
if (customMinusInfinityString.isEmpty())
|
||||
s << "-INF";
|
||||
else
|
||||
s << customMinusInfinityString;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (decibels >= Type())
|
||||
s << '+';
|
||||
|
||||
if (decimalPlaces <= 0)
|
||||
s << roundToInt (decibels);
|
||||
else
|
||||
s << String (decibels, decimalPlaces);
|
||||
}
|
||||
|
||||
if (shouldIncludeSuffix)
|
||||
s << " dB";
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
enum { defaultMinusInfinitydB = -100 };
|
||||
|
||||
Decibels() = delete; // This class can't be instantiated, it's just a holder for static methods..
|
||||
};
|
||||
|
||||
} // namespace juce
|
336
modules/juce_audio_basics/utilities/juce_IIRFilter.cpp
Normal file
336
modules/juce_audio_basics/utilities/juce_IIRFilter.cpp
Normal file
@ -0,0 +1,336 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
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
|
||||
{
|
||||
|
||||
IIRCoefficients::IIRCoefficients() noexcept
|
||||
{
|
||||
zeromem (coefficients, sizeof (coefficients));
|
||||
}
|
||||
|
||||
IIRCoefficients::~IIRCoefficients() noexcept {}
|
||||
|
||||
IIRCoefficients::IIRCoefficients (const IIRCoefficients& other) noexcept
|
||||
{
|
||||
memcpy (coefficients, other.coefficients, sizeof (coefficients));
|
||||
}
|
||||
|
||||
IIRCoefficients& IIRCoefficients::operator= (const IIRCoefficients& other) noexcept
|
||||
{
|
||||
memcpy (coefficients, other.coefficients, sizeof (coefficients));
|
||||
return *this;
|
||||
}
|
||||
|
||||
IIRCoefficients::IIRCoefficients (double c1, double c2, double c3,
|
||||
double c4, double c5, double c6) noexcept
|
||||
{
|
||||
auto a = 1.0 / c4;
|
||||
|
||||
coefficients[0] = (float) (c1 * a);
|
||||
coefficients[1] = (float) (c2 * a);
|
||||
coefficients[2] = (float) (c3 * a);
|
||||
coefficients[3] = (float) (c5 * a);
|
||||
coefficients[4] = (float) (c6 * a);
|
||||
}
|
||||
|
||||
IIRCoefficients IIRCoefficients::makeLowPass (double sampleRate,
|
||||
double frequency) noexcept
|
||||
{
|
||||
return makeLowPass (sampleRate, frequency, 1.0 / MathConstants<double>::sqrt2);
|
||||
}
|
||||
|
||||
IIRCoefficients IIRCoefficients::makeLowPass (double sampleRate,
|
||||
double frequency,
|
||||
double Q) noexcept
|
||||
{
|
||||
jassert (sampleRate > 0.0);
|
||||
jassert (frequency > 0.0 && frequency <= sampleRate * 0.5);
|
||||
jassert (Q > 0.0);
|
||||
|
||||
auto n = 1.0 / std::tan (MathConstants<double>::pi * frequency / sampleRate);
|
||||
auto nSquared = n * n;
|
||||
auto c1 = 1.0 / (1.0 + 1.0 / Q * n + nSquared);
|
||||
|
||||
return IIRCoefficients (c1,
|
||||
c1 * 2.0,
|
||||
c1,
|
||||
1.0,
|
||||
c1 * 2.0 * (1.0 - nSquared),
|
||||
c1 * (1.0 - 1.0 / Q * n + nSquared));
|
||||
}
|
||||
|
||||
IIRCoefficients IIRCoefficients::makeHighPass (double sampleRate,
|
||||
double frequency) noexcept
|
||||
{
|
||||
return makeHighPass (sampleRate, frequency, 1.0 / std::sqrt(2.0));
|
||||
}
|
||||
|
||||
IIRCoefficients IIRCoefficients::makeHighPass (double sampleRate,
|
||||
double frequency,
|
||||
double Q) noexcept
|
||||
{
|
||||
jassert (sampleRate > 0.0);
|
||||
jassert (frequency > 0.0 && frequency <= sampleRate * 0.5);
|
||||
jassert (Q > 0.0);
|
||||
|
||||
auto n = std::tan (MathConstants<double>::pi * frequency / sampleRate);
|
||||
auto nSquared = n * n;
|
||||
auto c1 = 1.0 / (1.0 + 1.0 / Q * n + nSquared);
|
||||
|
||||
return IIRCoefficients (c1,
|
||||
c1 * -2.0,
|
||||
c1,
|
||||
1.0,
|
||||
c1 * 2.0 * (nSquared - 1.0),
|
||||
c1 * (1.0 - 1.0 / Q * n + nSquared));
|
||||
}
|
||||
|
||||
IIRCoefficients IIRCoefficients::makeBandPass (double sampleRate,
|
||||
double frequency) noexcept
|
||||
{
|
||||
return makeBandPass (sampleRate, frequency, 1.0 / MathConstants<double>::sqrt2);
|
||||
}
|
||||
|
||||
IIRCoefficients IIRCoefficients::makeBandPass (double sampleRate,
|
||||
double frequency,
|
||||
double Q) noexcept
|
||||
{
|
||||
jassert (sampleRate > 0.0);
|
||||
jassert (frequency > 0.0 && frequency <= sampleRate * 0.5);
|
||||
jassert (Q > 0.0);
|
||||
|
||||
auto n = 1.0 / std::tan (MathConstants<double>::pi * frequency / sampleRate);
|
||||
auto nSquared = n * n;
|
||||
auto c1 = 1.0 / (1.0 + 1.0 / Q * n + nSquared);
|
||||
|
||||
return IIRCoefficients (c1 * n / Q,
|
||||
0.0,
|
||||
-c1 * n / Q,
|
||||
1.0,
|
||||
c1 * 2.0 * (1.0 - nSquared),
|
||||
c1 * (1.0 - 1.0 / Q * n + nSquared));
|
||||
}
|
||||
|
||||
IIRCoefficients IIRCoefficients::makeNotchFilter (double sampleRate,
|
||||
double frequency) noexcept
|
||||
{
|
||||
return makeNotchFilter (sampleRate, frequency, 1.0 / MathConstants<double>::sqrt2);
|
||||
}
|
||||
|
||||
IIRCoefficients IIRCoefficients::makeNotchFilter (double sampleRate,
|
||||
double frequency,
|
||||
double Q) noexcept
|
||||
{
|
||||
jassert (sampleRate > 0.0);
|
||||
jassert (frequency > 0.0 && frequency <= sampleRate * 0.5);
|
||||
jassert (Q > 0.0);
|
||||
|
||||
auto n = 1.0 / std::tan (MathConstants<double>::pi * frequency / sampleRate);
|
||||
auto nSquared = n * n;
|
||||
auto c1 = 1.0 / (1.0 + n / Q + nSquared);
|
||||
|
||||
return IIRCoefficients (c1 * (1.0 + nSquared),
|
||||
2.0 * c1 * (1.0 - nSquared),
|
||||
c1 * (1.0 + nSquared),
|
||||
1.0,
|
||||
c1 * 2.0 * (1.0 - nSquared),
|
||||
c1 * (1.0 - n / Q + nSquared));
|
||||
}
|
||||
|
||||
IIRCoefficients IIRCoefficients::makeAllPass (double sampleRate,
|
||||
double frequency) noexcept
|
||||
{
|
||||
return makeAllPass (sampleRate, frequency, 1.0 / MathConstants<double>::sqrt2);
|
||||
}
|
||||
|
||||
IIRCoefficients IIRCoefficients::makeAllPass (double sampleRate,
|
||||
double frequency,
|
||||
double Q) noexcept
|
||||
{
|
||||
jassert (sampleRate > 0.0);
|
||||
jassert (frequency > 0.0 && frequency <= sampleRate * 0.5);
|
||||
jassert (Q > 0.0);
|
||||
|
||||
auto n = 1.0 / std::tan (MathConstants<double>::pi * frequency / sampleRate);
|
||||
auto nSquared = n * n;
|
||||
auto c1 = 1.0 / (1.0 + 1.0 / Q * n + nSquared);
|
||||
|
||||
return IIRCoefficients (c1 * (1.0 - n / Q + nSquared),
|
||||
c1 * 2.0 * (1.0 - nSquared),
|
||||
1.0,
|
||||
1.0,
|
||||
c1 * 2.0 * (1.0 - nSquared),
|
||||
c1 * (1.0 - n / Q + nSquared));
|
||||
}
|
||||
|
||||
IIRCoefficients IIRCoefficients::makeLowShelf (double sampleRate,
|
||||
double cutOffFrequency,
|
||||
double Q,
|
||||
float gainFactor) noexcept
|
||||
{
|
||||
jassert (sampleRate > 0.0);
|
||||
jassert (cutOffFrequency > 0.0 && cutOffFrequency <= sampleRate * 0.5);
|
||||
jassert (Q > 0.0);
|
||||
|
||||
auto A = jmax (0.0f, std::sqrt (gainFactor));
|
||||
auto aminus1 = A - 1.0;
|
||||
auto aplus1 = A + 1.0;
|
||||
auto omega = (MathConstants<double>::twoPi * jmax (cutOffFrequency, 2.0)) / sampleRate;
|
||||
auto coso = std::cos (omega);
|
||||
auto beta = std::sin (omega) * std::sqrt (A) / Q;
|
||||
auto aminus1TimesCoso = aminus1 * coso;
|
||||
|
||||
return IIRCoefficients (A * (aplus1 - aminus1TimesCoso + beta),
|
||||
A * 2.0 * (aminus1 - aplus1 * coso),
|
||||
A * (aplus1 - aminus1TimesCoso - beta),
|
||||
aplus1 + aminus1TimesCoso + beta,
|
||||
-2.0 * (aminus1 + aplus1 * coso),
|
||||
aplus1 + aminus1TimesCoso - beta);
|
||||
}
|
||||
|
||||
IIRCoefficients IIRCoefficients::makeHighShelf (double sampleRate,
|
||||
double cutOffFrequency,
|
||||
double Q,
|
||||
float gainFactor) noexcept
|
||||
{
|
||||
jassert (sampleRate > 0.0);
|
||||
jassert (cutOffFrequency > 0.0 && cutOffFrequency <= sampleRate * 0.5);
|
||||
jassert (Q > 0.0);
|
||||
|
||||
auto A = jmax (0.0f, std::sqrt (gainFactor));
|
||||
auto aminus1 = A - 1.0;
|
||||
auto aplus1 = A + 1.0;
|
||||
auto omega = (MathConstants<double>::twoPi * jmax (cutOffFrequency, 2.0)) / sampleRate;
|
||||
auto coso = std::cos (omega);
|
||||
auto beta = std::sin (omega) * std::sqrt (A) / Q;
|
||||
auto aminus1TimesCoso = aminus1 * coso;
|
||||
|
||||
return IIRCoefficients (A * (aplus1 + aminus1TimesCoso + beta),
|
||||
A * -2.0 * (aminus1 + aplus1 * coso),
|
||||
A * (aplus1 + aminus1TimesCoso - beta),
|
||||
aplus1 - aminus1TimesCoso + beta,
|
||||
2.0 * (aminus1 - aplus1 * coso),
|
||||
aplus1 - aminus1TimesCoso - beta);
|
||||
}
|
||||
|
||||
IIRCoefficients IIRCoefficients::makePeakFilter (double sampleRate,
|
||||
double frequency,
|
||||
double Q,
|
||||
float gainFactor) noexcept
|
||||
{
|
||||
jassert (sampleRate > 0.0);
|
||||
jassert (frequency > 0.0 && frequency <= sampleRate * 0.5);
|
||||
jassert (Q > 0.0);
|
||||
|
||||
auto A = jmax (0.0f, std::sqrt (gainFactor));
|
||||
auto omega = (MathConstants<double>::twoPi * jmax (frequency, 2.0)) / sampleRate;
|
||||
auto alpha = 0.5 * std::sin (omega) / Q;
|
||||
auto c2 = -2.0 * std::cos (omega);
|
||||
auto alphaTimesA = alpha * A;
|
||||
auto alphaOverA = alpha / A;
|
||||
|
||||
return IIRCoefficients (1.0 + alphaTimesA,
|
||||
c2,
|
||||
1.0 - alphaTimesA,
|
||||
1.0 + alphaOverA,
|
||||
c2,
|
||||
1.0 - alphaOverA);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
IIRFilter::IIRFilter() noexcept
|
||||
{
|
||||
}
|
||||
|
||||
IIRFilter::IIRFilter (const IIRFilter& other) noexcept : active (other.active)
|
||||
{
|
||||
const SpinLock::ScopedLockType sl (other.processLock);
|
||||
coefficients = other.coefficients;
|
||||
}
|
||||
|
||||
IIRFilter::~IIRFilter() noexcept
|
||||
{
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void IIRFilter::makeInactive() noexcept
|
||||
{
|
||||
const SpinLock::ScopedLockType sl (processLock);
|
||||
active = false;
|
||||
}
|
||||
|
||||
void IIRFilter::setCoefficients (const IIRCoefficients& newCoefficients) noexcept
|
||||
{
|
||||
const SpinLock::ScopedLockType sl (processLock);
|
||||
coefficients = newCoefficients;
|
||||
active = true;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void IIRFilter::reset() noexcept
|
||||
{
|
||||
const SpinLock::ScopedLockType sl (processLock);
|
||||
v1 = v2 = 0.0;
|
||||
}
|
||||
|
||||
float IIRFilter::processSingleSampleRaw (float in) noexcept
|
||||
{
|
||||
auto out = coefficients.coefficients[0] * in + v1;
|
||||
|
||||
JUCE_SNAP_TO_ZERO (out);
|
||||
|
||||
v1 = coefficients.coefficients[1] * in - coefficients.coefficients[3] * out + v2;
|
||||
v2 = coefficients.coefficients[2] * in - coefficients.coefficients[4] * out;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
void IIRFilter::processSamples (float* const samples, const int numSamples) noexcept
|
||||
{
|
||||
const SpinLock::ScopedLockType sl (processLock);
|
||||
|
||||
if (active)
|
||||
{
|
||||
auto c0 = coefficients.coefficients[0];
|
||||
auto c1 = coefficients.coefficients[1];
|
||||
auto c2 = coefficients.coefficients[2];
|
||||
auto c3 = coefficients.coefficients[3];
|
||||
auto c4 = coefficients.coefficients[4];
|
||||
auto lv1 = v1, lv2 = v2;
|
||||
|
||||
for (int i = 0; i < numSamples; ++i)
|
||||
{
|
||||
auto in = samples[i];
|
||||
auto out = c0 * in + lv1;
|
||||
samples[i] = out;
|
||||
|
||||
lv1 = c1 * in - c3 * out + lv2;
|
||||
lv2 = c2 * in - c4 * out;
|
||||
}
|
||||
|
||||
JUCE_SNAP_TO_ZERO (lv1); v1 = lv1;
|
||||
JUCE_SNAP_TO_ZERO (lv2); v2 = lv2;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace juce
|
217
modules/juce_audio_basics/utilities/juce_IIRFilter.h
Normal file
217
modules/juce_audio_basics/utilities/juce_IIRFilter.h
Normal file
@ -0,0 +1,217 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
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
|
||||
{
|
||||
|
||||
class IIRFilter;
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A set of coefficients for use in an IIRFilter object.
|
||||
|
||||
@see IIRFilter
|
||||
|
||||
@tags{Audio}
|
||||
*/
|
||||
class JUCE_API IIRCoefficients
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a null set of coefficients (which will produce silence). */
|
||||
IIRCoefficients() noexcept;
|
||||
|
||||
/** Directly constructs an object from the raw coefficients.
|
||||
Most people will want to use the static methods instead of this, but
|
||||
the constructor is public to allow tinkerers to create their own custom
|
||||
filters!
|
||||
*/
|
||||
IIRCoefficients (double c1, double c2, double c3,
|
||||
double c4, double c5, double c6) noexcept;
|
||||
|
||||
/** Creates a copy of another filter. */
|
||||
IIRCoefficients (const IIRCoefficients&) noexcept;
|
||||
/** Creates a copy of another filter. */
|
||||
IIRCoefficients& operator= (const IIRCoefficients&) noexcept;
|
||||
/** Destructor. */
|
||||
~IIRCoefficients() noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the coefficients for a low-pass filter. */
|
||||
static IIRCoefficients makeLowPass (double sampleRate,
|
||||
double frequency) noexcept;
|
||||
|
||||
/** Returns the coefficients for a low-pass filter with variable Q. */
|
||||
static IIRCoefficients makeLowPass (double sampleRate,
|
||||
double frequency,
|
||||
double Q) noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the coefficients for a high-pass filter. */
|
||||
static IIRCoefficients makeHighPass (double sampleRate,
|
||||
double frequency) noexcept;
|
||||
|
||||
/** Returns the coefficients for a high-pass filter with variable Q. */
|
||||
static IIRCoefficients makeHighPass (double sampleRate,
|
||||
double frequency,
|
||||
double Q) noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the coefficients for a band-pass filter. */
|
||||
static IIRCoefficients makeBandPass (double sampleRate, double frequency) noexcept;
|
||||
|
||||
/** Returns the coefficients for a band-pass filter with variable Q. */
|
||||
static IIRCoefficients makeBandPass (double sampleRate,
|
||||
double frequency,
|
||||
double Q) noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the coefficients for a notch filter. */
|
||||
static IIRCoefficients makeNotchFilter (double sampleRate, double frequency) noexcept;
|
||||
|
||||
/** Returns the coefficients for a notch filter with variable Q. */
|
||||
static IIRCoefficients makeNotchFilter (double sampleRate,
|
||||
double frequency,
|
||||
double Q) noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the coefficients for an all-pass filter. */
|
||||
static IIRCoefficients makeAllPass (double sampleRate, double frequency) noexcept;
|
||||
|
||||
/** Returns the coefficients for an all-pass filter with variable Q. */
|
||||
static IIRCoefficients makeAllPass (double sampleRate,
|
||||
double frequency,
|
||||
double Q) noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the coefficients for a low-pass shelf filter with variable Q and gain.
|
||||
|
||||
The gain is a scale factor that the low frequencies are multiplied by, so values
|
||||
greater than 1.0 will boost the low frequencies, values less than 1.0 will
|
||||
attenuate them.
|
||||
*/
|
||||
static IIRCoefficients makeLowShelf (double sampleRate,
|
||||
double cutOffFrequency,
|
||||
double Q,
|
||||
float gainFactor) noexcept;
|
||||
|
||||
/** Returns the coefficients for a high-pass shelf filter with variable Q and gain.
|
||||
|
||||
The gain is a scale factor that the high frequencies are multiplied by, so values
|
||||
greater than 1.0 will boost the high frequencies, values less than 1.0 will
|
||||
attenuate them.
|
||||
*/
|
||||
static IIRCoefficients makeHighShelf (double sampleRate,
|
||||
double cutOffFrequency,
|
||||
double Q,
|
||||
float gainFactor) noexcept;
|
||||
|
||||
/** Returns the coefficients for a peak filter centred around a
|
||||
given frequency, with a variable Q and gain.
|
||||
|
||||
The gain is a scale factor that the centre frequencies are multiplied by, so
|
||||
values greater than 1.0 will boost the centre frequencies, values less than
|
||||
1.0 will attenuate them.
|
||||
*/
|
||||
static IIRCoefficients makePeakFilter (double sampleRate,
|
||||
double centreFrequency,
|
||||
double Q,
|
||||
float gainFactor) noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** The raw coefficients.
|
||||
You should leave these numbers alone unless you really know what you're doing.
|
||||
*/
|
||||
float coefficients[5];
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
An IIR filter that can perform low, high, or band-pass filtering on an
|
||||
audio signal.
|
||||
|
||||
@see IIRCoefficient, IIRFilterAudioSource
|
||||
|
||||
@tags{Audio}
|
||||
*/
|
||||
class JUCE_API IIRFilter
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a filter.
|
||||
|
||||
Initially the filter is inactive, so will have no effect on samples that
|
||||
you process with it. Use the setCoefficients() method to turn it into the
|
||||
type of filter needed.
|
||||
*/
|
||||
IIRFilter() noexcept;
|
||||
|
||||
/** Creates a copy of another filter. */
|
||||
IIRFilter (const IIRFilter&) noexcept;
|
||||
|
||||
/** Destructor. */
|
||||
~IIRFilter() noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Clears the filter so that any incoming data passes through unchanged. */
|
||||
void makeInactive() noexcept;
|
||||
|
||||
/** Applies a set of coefficients to this filter. */
|
||||
void setCoefficients (const IIRCoefficients& newCoefficients) noexcept;
|
||||
|
||||
/** Returns the coefficients that this filter is using. */
|
||||
IIRCoefficients getCoefficients() const noexcept { return coefficients; }
|
||||
|
||||
//==============================================================================
|
||||
/** Resets the filter's processing pipeline, ready to start a new stream of data.
|
||||
|
||||
Note that this clears the processing state, but the type of filter and
|
||||
its coefficients aren't changed. To put a filter into an inactive state, use
|
||||
the makeInactive() method.
|
||||
*/
|
||||
void reset() noexcept;
|
||||
|
||||
/** Performs the filter operation on the given set of samples. */
|
||||
void processSamples (float* samples, int numSamples) noexcept;
|
||||
|
||||
/** Processes a single sample, without any locking or checking.
|
||||
|
||||
Use this if you need fast processing of a single value, but be aware that
|
||||
this isn't thread-safe in the way that processSamples() is.
|
||||
*/
|
||||
float processSingleSampleRaw (float sample) noexcept;
|
||||
|
||||
protected:
|
||||
//==============================================================================
|
||||
SpinLock processLock;
|
||||
IIRCoefficients coefficients;
|
||||
float v1 = 0, v2 = 0;
|
||||
bool active = false;
|
||||
|
||||
// The exact meaning of an assignment operator would be ambiguous since the filters are
|
||||
// stateful. If you want to copy the coefficients, then just use setCoefficients().
|
||||
IIRFilter& operator= (const IIRFilter&) = delete;
|
||||
|
||||
JUCE_LEAK_DETECTOR (IIRFilter)
|
||||
};
|
||||
|
||||
} // namespace juce
|
@ -0,0 +1,467 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
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
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
static forcedinline void pushInterpolationSample (float* lastInputSamples, float newValue) noexcept
|
||||
{
|
||||
lastInputSamples[4] = lastInputSamples[3];
|
||||
lastInputSamples[3] = lastInputSamples[2];
|
||||
lastInputSamples[2] = lastInputSamples[1];
|
||||
lastInputSamples[1] = lastInputSamples[0];
|
||||
lastInputSamples[0] = newValue;
|
||||
}
|
||||
|
||||
static forcedinline void pushInterpolationSamples (float* lastInputSamples, const float* input, int numOut) noexcept
|
||||
{
|
||||
if (numOut >= 5)
|
||||
{
|
||||
for (int i = 0; i < 5; ++i)
|
||||
lastInputSamples[i] = input[--numOut];
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < numOut; ++i)
|
||||
pushInterpolationSample (lastInputSamples, input[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static forcedinline void pushInterpolationSamples (float* lastInputSamples, const float* input,
|
||||
int numOut, int available, int wrapAround) noexcept
|
||||
{
|
||||
if (numOut >= 5)
|
||||
{
|
||||
if (available >= 5)
|
||||
{
|
||||
for (int i = 0; i < 5; ++i)
|
||||
lastInputSamples[i] = input[--numOut];
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < available; ++i)
|
||||
lastInputSamples[i] = input[--numOut];
|
||||
|
||||
if (wrapAround > 0)
|
||||
{
|
||||
numOut -= wrapAround;
|
||||
|
||||
for (int i = available; i < 5; ++i)
|
||||
lastInputSamples[i] = input[--numOut];
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = available; i < 5; ++i)
|
||||
lastInputSamples[i] = 0.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (numOut > available)
|
||||
{
|
||||
for (int i = 0; i < available; ++i)
|
||||
pushInterpolationSample (lastInputSamples, input[i]);
|
||||
|
||||
if (wrapAround > 0)
|
||||
{
|
||||
for (int i = 0; i < numOut - available; ++i)
|
||||
pushInterpolationSample (lastInputSamples, input[i + available - wrapAround]);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < numOut - available; ++i)
|
||||
pushInterpolationSample (lastInputSamples, 0);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < numOut; ++i)
|
||||
pushInterpolationSample (lastInputSamples, input[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename InterpolatorType>
|
||||
static int interpolate (float* lastInputSamples, double& subSamplePos, double actualRatio,
|
||||
const float* in, float* out, int numOut) noexcept
|
||||
{
|
||||
auto pos = subSamplePos;
|
||||
|
||||
if (actualRatio == 1.0 && pos == 1.0)
|
||||
{
|
||||
memcpy (out, in, (size_t) numOut * sizeof (float));
|
||||
pushInterpolationSamples (lastInputSamples, in, numOut);
|
||||
return numOut;
|
||||
}
|
||||
|
||||
int numUsed = 0;
|
||||
|
||||
while (numOut > 0)
|
||||
{
|
||||
while (pos >= 1.0)
|
||||
{
|
||||
pushInterpolationSample (lastInputSamples, in[numUsed++]);
|
||||
pos -= 1.0;
|
||||
}
|
||||
|
||||
*out++ = InterpolatorType::valueAtOffset (lastInputSamples, (float) pos);
|
||||
pos += actualRatio;
|
||||
--numOut;
|
||||
}
|
||||
|
||||
subSamplePos = pos;
|
||||
return numUsed;
|
||||
}
|
||||
|
||||
template <typename InterpolatorType>
|
||||
static int interpolate (float* lastInputSamples, double& subSamplePos, double actualRatio,
|
||||
const float* in, float* out, int numOut, int available, int wrap) noexcept
|
||||
{
|
||||
if (actualRatio == 1.0)
|
||||
{
|
||||
if (available >= numOut)
|
||||
{
|
||||
memcpy (out, in, (size_t) numOut * sizeof (float));
|
||||
pushInterpolationSamples (lastInputSamples, in, numOut, available, wrap);
|
||||
}
|
||||
else
|
||||
{
|
||||
memcpy (out, in, (size_t) available * sizeof (float));
|
||||
pushInterpolationSamples (lastInputSamples, in, numOut, available, wrap);
|
||||
|
||||
if (wrap > 0)
|
||||
{
|
||||
memcpy (out + available, in + available - wrap, (size_t) (numOut - available) * sizeof (float));
|
||||
pushInterpolationSamples (lastInputSamples, in, numOut, available, wrap);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < numOut - available; ++i)
|
||||
pushInterpolationSample (lastInputSamples, 0);
|
||||
}
|
||||
}
|
||||
|
||||
return numOut;
|
||||
}
|
||||
|
||||
auto originalIn = in;
|
||||
auto pos = subSamplePos;
|
||||
bool exceeded = false;
|
||||
|
||||
if (actualRatio < 1.0)
|
||||
{
|
||||
for (int i = numOut; --i >= 0;)
|
||||
{
|
||||
if (pos >= 1.0)
|
||||
{
|
||||
if (exceeded)
|
||||
{
|
||||
pushInterpolationSample (lastInputSamples, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
pushInterpolationSample (lastInputSamples, *in++);
|
||||
|
||||
if (--available <= 0)
|
||||
{
|
||||
if (wrap > 0)
|
||||
{
|
||||
in -= wrap;
|
||||
available += wrap;
|
||||
}
|
||||
else
|
||||
{
|
||||
exceeded = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pos -= 1.0;
|
||||
}
|
||||
|
||||
*out++ = InterpolatorType::valueAtOffset (lastInputSamples, (float) pos);
|
||||
pos += actualRatio;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = numOut; --i >= 0;)
|
||||
{
|
||||
while (pos < actualRatio)
|
||||
{
|
||||
if (exceeded)
|
||||
{
|
||||
pushInterpolationSample (lastInputSamples, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
pushInterpolationSample (lastInputSamples, *in++);
|
||||
|
||||
if (--available <= 0)
|
||||
{
|
||||
if (wrap > 0)
|
||||
{
|
||||
in -= wrap;
|
||||
available += wrap;
|
||||
}
|
||||
else
|
||||
{
|
||||
exceeded = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pos += 1.0;
|
||||
}
|
||||
|
||||
pos -= actualRatio;
|
||||
*out++ = InterpolatorType::valueAtOffset (lastInputSamples, jmax (0.0f, 1.0f - (float) pos));
|
||||
}
|
||||
}
|
||||
|
||||
subSamplePos = pos;
|
||||
|
||||
if (wrap == 0)
|
||||
return (int) (in - originalIn);
|
||||
|
||||
return ((int) (in - originalIn) + wrap) % wrap;
|
||||
}
|
||||
|
||||
template <typename InterpolatorType>
|
||||
static int interpolateAdding (float* lastInputSamples, double& subSamplePos, double actualRatio,
|
||||
const float* in, float* out, int numOut,
|
||||
int available, int wrap, float gain) noexcept
|
||||
{
|
||||
if (actualRatio == 1.0)
|
||||
{
|
||||
if (available >= numOut)
|
||||
{
|
||||
FloatVectorOperations::addWithMultiply (out, in, gain, numOut);
|
||||
pushInterpolationSamples (lastInputSamples, in, numOut, available, wrap);
|
||||
}
|
||||
else
|
||||
{
|
||||
FloatVectorOperations::addWithMultiply (out, in, gain, available);
|
||||
pushInterpolationSamples (lastInputSamples, in, available, available, wrap);
|
||||
|
||||
if (wrap > 0)
|
||||
{
|
||||
FloatVectorOperations::addWithMultiply (out, in - wrap, gain, numOut - available);
|
||||
pushInterpolationSamples (lastInputSamples, in - wrap, numOut - available, available, wrap);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < numOut-available; ++i)
|
||||
pushInterpolationSample (lastInputSamples, 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
return numOut;
|
||||
}
|
||||
|
||||
auto originalIn = in;
|
||||
auto pos = subSamplePos;
|
||||
bool exceeded = false;
|
||||
|
||||
if (actualRatio < 1.0)
|
||||
{
|
||||
for (int i = numOut; --i >= 0;)
|
||||
{
|
||||
if (pos >= 1.0)
|
||||
{
|
||||
if (exceeded)
|
||||
{
|
||||
pushInterpolationSample (lastInputSamples, 0.0);
|
||||
}
|
||||
else
|
||||
{
|
||||
pushInterpolationSample (lastInputSamples, *in++);
|
||||
|
||||
if (--available <= 0)
|
||||
{
|
||||
if (wrap > 0)
|
||||
{
|
||||
in -= wrap;
|
||||
available += wrap;
|
||||
}
|
||||
else
|
||||
{
|
||||
exceeded = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pos -= 1.0;
|
||||
}
|
||||
|
||||
*out++ += gain * InterpolatorType::valueAtOffset (lastInputSamples, (float) pos);
|
||||
pos += actualRatio;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = numOut; --i >= 0;)
|
||||
{
|
||||
while (pos < actualRatio)
|
||||
{
|
||||
if (exceeded)
|
||||
{
|
||||
pushInterpolationSample (lastInputSamples, 0.0);
|
||||
}
|
||||
else
|
||||
{
|
||||
pushInterpolationSample (lastInputSamples, *in++);
|
||||
|
||||
if (--available <= 0)
|
||||
{
|
||||
if (wrap > 0)
|
||||
{
|
||||
in -= wrap;
|
||||
available += wrap;
|
||||
}
|
||||
else
|
||||
{
|
||||
exceeded = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pos += 1.0;
|
||||
}
|
||||
|
||||
pos -= actualRatio;
|
||||
*out++ += gain * InterpolatorType::valueAtOffset (lastInputSamples, jmax (0.0f, 1.0f - (float) pos));
|
||||
}
|
||||
}
|
||||
|
||||
subSamplePos = pos;
|
||||
|
||||
if (wrap == 0)
|
||||
return (int) (in - originalIn);
|
||||
|
||||
return ((int) (in - originalIn) + wrap) % wrap;
|
||||
}
|
||||
|
||||
template <typename InterpolatorType>
|
||||
static int interpolateAdding (float* lastInputSamples, double& subSamplePos, double actualRatio,
|
||||
const float* in, float* out, int numOut, float gain) noexcept
|
||||
{
|
||||
auto pos = subSamplePos;
|
||||
|
||||
if (actualRatio == 1.0 && pos == 1.0)
|
||||
{
|
||||
FloatVectorOperations::addWithMultiply (out, in, gain, numOut);
|
||||
pushInterpolationSamples (lastInputSamples, in, numOut);
|
||||
return numOut;
|
||||
}
|
||||
|
||||
int numUsed = 0;
|
||||
|
||||
while (numOut > 0)
|
||||
{
|
||||
while (pos >= 1.0)
|
||||
{
|
||||
pushInterpolationSample (lastInputSamples, in[numUsed++]);
|
||||
pos -= 1.0;
|
||||
}
|
||||
|
||||
*out++ += gain * InterpolatorType::valueAtOffset (lastInputSamples, (float) pos);
|
||||
pos += actualRatio;
|
||||
--numOut;
|
||||
}
|
||||
|
||||
subSamplePos = pos;
|
||||
return numUsed;
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
template <int k>
|
||||
struct LagrangeResampleHelper
|
||||
{
|
||||
static forcedinline void calc (float& a, float b) noexcept { a *= b * (1.0f / k); }
|
||||
};
|
||||
|
||||
template<>
|
||||
struct LagrangeResampleHelper<0>
|
||||
{
|
||||
static forcedinline void calc (float&, float) noexcept {}
|
||||
};
|
||||
|
||||
struct LagrangeAlgorithm
|
||||
{
|
||||
static forcedinline float valueAtOffset (const float* inputs, float offset) noexcept
|
||||
{
|
||||
return calcCoefficient<0> (inputs[4], offset)
|
||||
+ calcCoefficient<1> (inputs[3], offset)
|
||||
+ calcCoefficient<2> (inputs[2], offset)
|
||||
+ calcCoefficient<3> (inputs[1], offset)
|
||||
+ calcCoefficient<4> (inputs[0], offset);
|
||||
}
|
||||
|
||||
template <int k>
|
||||
static forcedinline float calcCoefficient (float input, float offset) noexcept
|
||||
{
|
||||
LagrangeResampleHelper<0 - k>::calc (input, -2.0f - offset);
|
||||
LagrangeResampleHelper<1 - k>::calc (input, -1.0f - offset);
|
||||
LagrangeResampleHelper<2 - k>::calc (input, 0.0f - offset);
|
||||
LagrangeResampleHelper<3 - k>::calc (input, 1.0f - offset);
|
||||
LagrangeResampleHelper<4 - k>::calc (input, 2.0f - offset);
|
||||
return input;
|
||||
}
|
||||
};
|
||||
|
||||
LagrangeInterpolator::LagrangeInterpolator() noexcept { reset(); }
|
||||
LagrangeInterpolator::~LagrangeInterpolator() noexcept {}
|
||||
|
||||
void LagrangeInterpolator::reset() noexcept
|
||||
{
|
||||
subSamplePos = 1.0;
|
||||
|
||||
for (auto& s : lastInputSamples)
|
||||
s = 0;
|
||||
}
|
||||
|
||||
int LagrangeInterpolator::process (double actualRatio, const float* in, float* out, int numOut, int available, int wrap) noexcept
|
||||
{
|
||||
return interpolate<LagrangeAlgorithm> (lastInputSamples, subSamplePos, actualRatio, in, out, numOut, available, wrap);
|
||||
}
|
||||
|
||||
int LagrangeInterpolator::process (double actualRatio, const float* in, float* out, int numOut) noexcept
|
||||
{
|
||||
return interpolate<LagrangeAlgorithm> (lastInputSamples, subSamplePos, actualRatio, in, out, numOut);
|
||||
}
|
||||
|
||||
int LagrangeInterpolator::processAdding (double actualRatio, const float* in, float* out, int numOut, int available, int wrap, float gain) noexcept
|
||||
{
|
||||
return interpolateAdding<LagrangeAlgorithm> (lastInputSamples, subSamplePos, actualRatio, in, out, numOut, available, wrap, gain);
|
||||
}
|
||||
|
||||
int LagrangeInterpolator::processAdding (double actualRatio, const float* in, float* out, int numOut, float gain) noexcept
|
||||
{
|
||||
return interpolateAdding<LagrangeAlgorithm> (lastInputSamples, subSamplePos, actualRatio, in, out, numOut, gain);
|
||||
}
|
||||
|
||||
} // namespace juce
|
143
modules/juce_audio_basics/utilities/juce_LagrangeInterpolator.h
Normal file
143
modules/juce_audio_basics/utilities/juce_LagrangeInterpolator.h
Normal file
@ -0,0 +1,143 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
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
|
||||
{
|
||||
|
||||
/**
|
||||
Interpolator for resampling a stream of floats using 4-point lagrange interpolation.
|
||||
|
||||
Note that the resampler is stateful, so when there's a break in the continuity
|
||||
of the input stream you're feeding it, you should call reset() before feeding
|
||||
it any new data. And like with any other stateful filter, if you're resampling
|
||||
multiple channels, make sure each one uses its own LagrangeInterpolator
|
||||
object.
|
||||
|
||||
@see CatmullRomInterpolator
|
||||
|
||||
@tags{Audio}
|
||||
*/
|
||||
class JUCE_API LagrangeInterpolator
|
||||
{
|
||||
public:
|
||||
LagrangeInterpolator() noexcept;
|
||||
~LagrangeInterpolator() noexcept;
|
||||
|
||||
/** Resets the state of the interpolator.
|
||||
Call this when there's a break in the continuity of the input data stream.
|
||||
*/
|
||||
void reset() noexcept;
|
||||
|
||||
/** Resamples a stream of samples.
|
||||
|
||||
@param speedRatio the number of input samples to use for each output sample
|
||||
@param inputSamples the source data to read from. This must contain at
|
||||
least (speedRatio * numOutputSamplesToProduce) samples.
|
||||
@param outputSamples the buffer to write the results into
|
||||
@param numOutputSamplesToProduce the number of output samples that should be created
|
||||
|
||||
@returns the actual number of input samples that were used
|
||||
*/
|
||||
int process (double speedRatio,
|
||||
const float* inputSamples,
|
||||
float* outputSamples,
|
||||
int numOutputSamplesToProduce) noexcept;
|
||||
|
||||
/** Resamples a stream of samples.
|
||||
|
||||
@param speedRatio the number of input samples to use for each output sample
|
||||
@param inputSamples the source data to read from. This must contain at
|
||||
least (speedRatio * numOutputSamplesToProduce) samples.
|
||||
@param outputSamples the buffer to write the results into
|
||||
@param numOutputSamplesToProduce the number of output samples that should be created
|
||||
@param available the number of available input samples. If it needs more samples
|
||||
than available, it either wraps back for wrapAround samples, or
|
||||
it feeds zeroes
|
||||
@param wrapAround if the stream exceeds available samples, it wraps back for
|
||||
wrapAround samples. If wrapAround is set to 0, it will feed zeroes.
|
||||
|
||||
@returns the actual number of input samples that were used
|
||||
*/
|
||||
int process (double speedRatio,
|
||||
const float* inputSamples,
|
||||
float* outputSamples,
|
||||
int numOutputSamplesToProduce,
|
||||
int available,
|
||||
int wrapAround) noexcept;
|
||||
|
||||
/** Resamples a stream of samples, adding the results to the output data
|
||||
with a gain.
|
||||
|
||||
@param speedRatio the number of input samples to use for each output sample
|
||||
@param inputSamples the source data to read from. This must contain at
|
||||
least (speedRatio * numOutputSamplesToProduce) samples.
|
||||
@param outputSamples the buffer to write the results to - the result values will be added
|
||||
to any pre-existing data in this buffer after being multiplied by
|
||||
the gain factor
|
||||
@param numOutputSamplesToProduce the number of output samples that should be created
|
||||
@param gain a gain factor to multiply the resulting samples by before
|
||||
adding them to the destination buffer
|
||||
|
||||
@returns the actual number of input samples that were used
|
||||
*/
|
||||
int processAdding (double speedRatio,
|
||||
const float* inputSamples,
|
||||
float* outputSamples,
|
||||
int numOutputSamplesToProduce,
|
||||
float gain) noexcept;
|
||||
|
||||
/** Resamples a stream of samples, adding the results to the output data
|
||||
with a gain.
|
||||
|
||||
@param speedRatio the number of input samples to use for each output sample
|
||||
@param inputSamples the source data to read from. This must contain at
|
||||
least (speedRatio * numOutputSamplesToProduce) samples.
|
||||
@param outputSamples the buffer to write the results to - the result values will be added
|
||||
to any pre-existing data in this buffer after being multiplied by
|
||||
the gain factor
|
||||
@param numOutputSamplesToProduce the number of output samples that should be created
|
||||
@param available the number of available input samples. If it needs more samples
|
||||
than available, it either wraps back for wrapAround samples, or
|
||||
it feeds zeroes
|
||||
@param wrapAround if the stream exceeds available samples, it wraps back for
|
||||
wrapAround samples. If wrapAround is set to 0, it will feed zeroes.
|
||||
@param gain a gain factor to multiply the resulting samples by before
|
||||
adding them to the destination buffer
|
||||
|
||||
@returns the actual number of input samples that were used
|
||||
*/
|
||||
int processAdding (double speedRatio,
|
||||
const float* inputSamples,
|
||||
float* outputSamples,
|
||||
int numOutputSamplesToProduce,
|
||||
int available,
|
||||
int wrapAround,
|
||||
float gain) noexcept;
|
||||
|
||||
private:
|
||||
float lastInputSamples[5];
|
||||
double subSamplePos;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LagrangeInterpolator)
|
||||
};
|
||||
|
||||
} // namespace juce
|
313
modules/juce_audio_basics/utilities/juce_Reverb.h
Normal file
313
modules/juce_audio_basics/utilities/juce_Reverb.h
Normal file
@ -0,0 +1,313 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
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
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Performs a simple reverb effect on a stream of audio data.
|
||||
|
||||
This is a simple stereo reverb, based on the technique and tunings used in FreeVerb.
|
||||
Use setSampleRate() to prepare it, and then call processStereo() or processMono() to
|
||||
apply the reverb to your audio data.
|
||||
|
||||
@see ReverbAudioSource
|
||||
|
||||
@tags{Audio}
|
||||
*/
|
||||
class Reverb
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
Reverb()
|
||||
{
|
||||
setParameters (Parameters());
|
||||
setSampleRate (44100.0);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
/** Holds the parameters being used by a Reverb object. */
|
||||
struct Parameters
|
||||
{
|
||||
float roomSize = 0.5f; /**< Room size, 0 to 1.0, where 1.0 is big, 0 is small. */
|
||||
float damping = 0.5f; /**< Damping, 0 to 1.0, where 0 is not damped, 1.0 is fully damped. */
|
||||
float wetLevel = 0.33f; /**< Wet level, 0 to 1.0 */
|
||||
float dryLevel = 0.4f; /**< Dry level, 0 to 1.0 */
|
||||
float width = 1.0f; /**< Reverb width, 0 to 1.0, where 1.0 is very wide. */
|
||||
float freezeMode = 0.0f; /**< Freeze mode - values < 0.5 are "normal" mode, values > 0.5
|
||||
put the reverb into a continuous feedback loop. */
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the reverb's current parameters. */
|
||||
const Parameters& getParameters() const noexcept { return parameters; }
|
||||
|
||||
/** Applies a new set of parameters to the reverb.
|
||||
Note that this doesn't attempt to lock the reverb, so if you call this in parallel with
|
||||
the process method, you may get artifacts.
|
||||
*/
|
||||
void setParameters (const Parameters& newParams)
|
||||
{
|
||||
const float wetScaleFactor = 3.0f;
|
||||
const float dryScaleFactor = 2.0f;
|
||||
|
||||
const float wet = newParams.wetLevel * wetScaleFactor;
|
||||
dryGain.setTargetValue (newParams.dryLevel * dryScaleFactor);
|
||||
wetGain1.setTargetValue (0.5f * wet * (1.0f + newParams.width));
|
||||
wetGain2.setTargetValue (0.5f * wet * (1.0f - newParams.width));
|
||||
|
||||
gain = isFrozen (newParams.freezeMode) ? 0.0f : 0.015f;
|
||||
parameters = newParams;
|
||||
updateDamping();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
/** Sets the sample rate that will be used for the reverb.
|
||||
You must call this before the process methods, in order to tell it the correct sample rate.
|
||||
*/
|
||||
void setSampleRate (const double sampleRate)
|
||||
{
|
||||
jassert (sampleRate > 0);
|
||||
|
||||
static const short combTunings[] = { 1116, 1188, 1277, 1356, 1422, 1491, 1557, 1617 }; // (at 44100Hz)
|
||||
static const short allPassTunings[] = { 556, 441, 341, 225 };
|
||||
const int stereoSpread = 23;
|
||||
const int intSampleRate = (int) sampleRate;
|
||||
|
||||
for (int i = 0; i < numCombs; ++i)
|
||||
{
|
||||
comb[0][i].setSize ((intSampleRate * combTunings[i]) / 44100);
|
||||
comb[1][i].setSize ((intSampleRate * (combTunings[i] + stereoSpread)) / 44100);
|
||||
}
|
||||
|
||||
for (int i = 0; i < numAllPasses; ++i)
|
||||
{
|
||||
allPass[0][i].setSize ((intSampleRate * allPassTunings[i]) / 44100);
|
||||
allPass[1][i].setSize ((intSampleRate * (allPassTunings[i] + stereoSpread)) / 44100);
|
||||
}
|
||||
|
||||
const double smoothTime = 0.01;
|
||||
damping .reset (sampleRate, smoothTime);
|
||||
feedback.reset (sampleRate, smoothTime);
|
||||
dryGain .reset (sampleRate, smoothTime);
|
||||
wetGain1.reset (sampleRate, smoothTime);
|
||||
wetGain2.reset (sampleRate, smoothTime);
|
||||
}
|
||||
|
||||
/** Clears the reverb's buffers. */
|
||||
void reset()
|
||||
{
|
||||
for (int j = 0; j < numChannels; ++j)
|
||||
{
|
||||
for (int i = 0; i < numCombs; ++i)
|
||||
comb[j][i].clear();
|
||||
|
||||
for (int i = 0; i < numAllPasses; ++i)
|
||||
allPass[j][i].clear();
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
/** Applies the reverb to two stereo channels of audio data. */
|
||||
void processStereo (float* const left, float* const right, const int numSamples) noexcept
|
||||
{
|
||||
jassert (left != nullptr && right != nullptr);
|
||||
|
||||
for (int i = 0; i < numSamples; ++i)
|
||||
{
|
||||
const float input = (left[i] + right[i]) * gain;
|
||||
float outL = 0, outR = 0;
|
||||
|
||||
const float damp = damping.getNextValue();
|
||||
const float feedbck = feedback.getNextValue();
|
||||
|
||||
for (int j = 0; j < numCombs; ++j) // accumulate the comb filters in parallel
|
||||
{
|
||||
outL += comb[0][j].process (input, damp, feedbck);
|
||||
outR += comb[1][j].process (input, damp, feedbck);
|
||||
}
|
||||
|
||||
for (int j = 0; j < numAllPasses; ++j) // run the allpass filters in series
|
||||
{
|
||||
outL = allPass[0][j].process (outL);
|
||||
outR = allPass[1][j].process (outR);
|
||||
}
|
||||
|
||||
const float dry = dryGain.getNextValue();
|
||||
const float wet1 = wetGain1.getNextValue();
|
||||
const float wet2 = wetGain2.getNextValue();
|
||||
|
||||
left[i] = outL * wet1 + outR * wet2 + left[i] * dry;
|
||||
right[i] = outR * wet1 + outL * wet2 + right[i] * dry;
|
||||
}
|
||||
}
|
||||
|
||||
/** Applies the reverb to a single mono channel of audio data. */
|
||||
void processMono (float* const samples, const int numSamples) noexcept
|
||||
{
|
||||
jassert (samples != nullptr);
|
||||
|
||||
for (int i = 0; i < numSamples; ++i)
|
||||
{
|
||||
const float input = samples[i] * gain;
|
||||
float output = 0;
|
||||
|
||||
const float damp = damping.getNextValue();
|
||||
const float feedbck = feedback.getNextValue();
|
||||
|
||||
for (int j = 0; j < numCombs; ++j) // accumulate the comb filters in parallel
|
||||
output += comb[0][j].process (input, damp, feedbck);
|
||||
|
||||
for (int j = 0; j < numAllPasses; ++j) // run the allpass filters in series
|
||||
output = allPass[0][j].process (output);
|
||||
|
||||
const float dry = dryGain.getNextValue();
|
||||
const float wet1 = wetGain1.getNextValue();
|
||||
|
||||
samples[i] = output * wet1 + samples[i] * dry;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
static bool isFrozen (const float freezeMode) noexcept { return freezeMode >= 0.5f; }
|
||||
|
||||
void updateDamping() noexcept
|
||||
{
|
||||
const float roomScaleFactor = 0.28f;
|
||||
const float roomOffset = 0.7f;
|
||||
const float dampScaleFactor = 0.4f;
|
||||
|
||||
if (isFrozen (parameters.freezeMode))
|
||||
setDamping (0.0f, 1.0f);
|
||||
else
|
||||
setDamping (parameters.damping * dampScaleFactor,
|
||||
parameters.roomSize * roomScaleFactor + roomOffset);
|
||||
}
|
||||
|
||||
void setDamping (const float dampingToUse, const float roomSizeToUse) noexcept
|
||||
{
|
||||
damping.setTargetValue (dampingToUse);
|
||||
feedback.setTargetValue (roomSizeToUse);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
class CombFilter
|
||||
{
|
||||
public:
|
||||
CombFilter() noexcept {}
|
||||
|
||||
void setSize (const int size)
|
||||
{
|
||||
if (size != bufferSize)
|
||||
{
|
||||
bufferIndex = 0;
|
||||
buffer.malloc (size);
|
||||
bufferSize = size;
|
||||
}
|
||||
|
||||
clear();
|
||||
}
|
||||
|
||||
void clear() noexcept
|
||||
{
|
||||
last = 0;
|
||||
buffer.clear ((size_t) bufferSize);
|
||||
}
|
||||
|
||||
float process (const float input, const float damp, const float feedbackLevel) noexcept
|
||||
{
|
||||
const float output = buffer[bufferIndex];
|
||||
last = (output * (1.0f - damp)) + (last * damp);
|
||||
JUCE_UNDENORMALISE (last);
|
||||
|
||||
float temp = input + (last * feedbackLevel);
|
||||
JUCE_UNDENORMALISE (temp);
|
||||
buffer[bufferIndex] = temp;
|
||||
bufferIndex = (bufferIndex + 1) % bufferSize;
|
||||
return output;
|
||||
}
|
||||
|
||||
private:
|
||||
HeapBlock<float> buffer;
|
||||
int bufferSize = 0, bufferIndex = 0;
|
||||
float last = 0.0f;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE (CombFilter)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
class AllPassFilter
|
||||
{
|
||||
public:
|
||||
AllPassFilter() noexcept {}
|
||||
|
||||
void setSize (const int size)
|
||||
{
|
||||
if (size != bufferSize)
|
||||
{
|
||||
bufferIndex = 0;
|
||||
buffer.malloc (size);
|
||||
bufferSize = size;
|
||||
}
|
||||
|
||||
clear();
|
||||
}
|
||||
|
||||
void clear() noexcept
|
||||
{
|
||||
buffer.clear ((size_t) bufferSize);
|
||||
}
|
||||
|
||||
float process (const float input) noexcept
|
||||
{
|
||||
const float bufferedValue = buffer [bufferIndex];
|
||||
float temp = input + (bufferedValue * 0.5f);
|
||||
JUCE_UNDENORMALISE (temp);
|
||||
buffer [bufferIndex] = temp;
|
||||
bufferIndex = (bufferIndex + 1) % bufferSize;
|
||||
return bufferedValue - input;
|
||||
}
|
||||
|
||||
private:
|
||||
HeapBlock<float> buffer;
|
||||
int bufferSize = 0, bufferIndex = 0;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE (AllPassFilter)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
enum { numCombs = 8, numAllPasses = 4, numChannels = 2 };
|
||||
|
||||
Parameters parameters;
|
||||
float gain;
|
||||
|
||||
CombFilter comb [numChannels][numCombs];
|
||||
AllPassFilter allPass [numChannels][numAllPasses];
|
||||
|
||||
SmoothedValue<float> damping, feedback, dryGain, wetGain1, wetGain2;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Reverb)
|
||||
};
|
||||
|
||||
} // namespace juce
|
92
modules/juce_audio_basics/utilities/juce_SmoothedValue.cpp
Normal file
92
modules/juce_audio_basics/utilities/juce_SmoothedValue.cpp
Normal file
@ -0,0 +1,92 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
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
|
||||
|
||||
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
|
622
modules/juce_audio_basics/utilities/juce_SmoothedValue.h
Normal file
622
modules/juce_audio_basics/utilities/juce_SmoothedValue.h
Normal file
@ -0,0 +1,622 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
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.
|
||||
|
||||
This class is used to provide common functionality to the SmoothedValue and
|
||||
dsp::LogRampedValue classes.
|
||||
|
||||
@tags{Audio}
|
||||
*/
|
||||
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;
|
||||
|
||||
//==============================================================================
|
||||
/** Constructor. */
|
||||
SmoothedValueBase() = default;
|
||||
|
||||
virtual ~SmoothedValueBase() {}
|
||||
|
||||
//==============================================================================
|
||||
/** 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);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
FloatType getNextSmoothedValue() noexcept
|
||||
{
|
||||
return static_cast <SmoothedValueType*> (this)->getNextValue();
|
||||
}
|
||||
|
||||
protected:
|
||||
//==============================================================================
|
||||
FloatType currentValue = 0;
|
||||
FloatType target = currentValue;
|
||||
int countdown = 0;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A namespace containing a set of types used for specifying the smoothing
|
||||
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 to avoid audio glitches.
|
||||
|
||||
A ValueSmoothingTypes::Linear template parameter selects linear smoothing,
|
||||
which increments the SmoothedValue linearly towards its target value.
|
||||
|
||||
@code
|
||||
SmoothedValue<float, ValueSmoothingTypes::Linear> yourSmoothedValue;
|
||||
@endcode
|
||||
|
||||
A ValueSmoothingTypes::Multiplicative template parameter selects
|
||||
multiplicative smoothing increments towards the target value.
|
||||
|
||||
@code
|
||||
SmoothedValue<float, ValueSmoothingTypes::Multiplicative> yourSmoothedValue;
|
||||
@endcode
|
||||
|
||||
Multiplicative smoothing is useful when you are dealing with
|
||||
exponential/logarithmic values like volume in dB or frequency in Hz. For
|
||||
example a 12 step ramp from 440.0 Hz (A4) to 880.0 Hz (A5) will increase the
|
||||
frequency with an equal temperament tuning across the octave. A 10 step
|
||||
smoothing from 1.0 (0 dB) to 3.16228 (10 dB) will increase the value in
|
||||
increments of 1 dB.
|
||||
|
||||
Note that when you are using multiplicative smoothing you cannot ever reach a
|
||||
target value of zero!
|
||||
|
||||
@tags{Audio}
|
||||
*/
|
||||
template <typename FloatType, typename SmoothingType = ValueSmoothingTypes::Linear>
|
||||
class SmoothedValue : public SmoothedValueBase <SmoothedValue <FloatType, SmoothingType>>
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Constructor. */
|
||||
SmoothedValue() noexcept
|
||||
: SmoothedValue ((FloatType) (std::is_same<SmoothingType, ValueSmoothingTypes::Linear>::value ? 0 : 1))
|
||||
{
|
||||
}
|
||||
|
||||
/** Constructor. */
|
||||
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
|
||||
this->currentValue = initialValue;
|
||||
this->target = this->currentValue;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
/** 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;
|
||||
}
|
||||
|
||||
// Multiplicative smoothed values cannot ever reach 0!
|
||||
jassert (! (std::is_same<SmoothingType, ValueSmoothingTypes::Multiplicative>::value && newValue == 0));
|
||||
|
||||
this->target = newValue;
|
||||
this->countdown = stepsToTarget;
|
||||
|
||||
setStepSize();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
/** Compute the next value.
|
||||
@returns Smoothed value
|
||||
*/
|
||||
FloatType getNextValue() noexcept
|
||||
{
|
||||
if (! this->isSmoothing())
|
||||
return this->target;
|
||||
|
||||
--(this->countdown);
|
||||
|
||||
if (this->isSmoothing())
|
||||
setNextValue();
|
||||
else
|
||||
this->currentValue = 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;
|
||||
}
|
||||
|
||||
skipCurrentValue (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:
|
||||
//==============================================================================
|
||||
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();
|
||||
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", "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 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
|
Reference in New Issue
Block a user