git subrepo clone --branch=sono6good https://github.com/essej/JUCE.git deps/juce

subrepo:
  subdir:   "deps/juce"
  merged:   "b13f9084e"
upstream:
  origin:   "https://github.com/essej/JUCE.git"
  branch:   "sono6good"
  commit:   "b13f9084e"
git-subrepo:
  version:  "0.4.3"
  origin:   "https://github.com/ingydotnet/git-subrepo.git"
  commit:   "2f68596"
This commit is contained in:
essej
2022-04-18 17:51:22 -04:00
parent 63e175fee6
commit 25bd5d8adb
3210 changed files with 1045392 additions and 0 deletions

View File

@ -0,0 +1,269 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
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.
@tags{Audio}
*/
class JUCE_API ADSR
{
public:
//==============================================================================
ADSR()
{
recalculateRates();
}
//==============================================================================
/**
Holds the parameters being used by an ADSR object.
@tags{Audio}
*/
struct JUCE_API Parameters
{
Parameters() = default;
Parameters (float attackTimeSeconds,
float decayTimeSeconds,
float sustainLevel,
float releaseTimeSeconds)
: attack (attackTimeSeconds),
decay (decayTimeSeconds),
sustain (sustainLevel),
release (releaseTimeSeconds)
{
}
float attack = 0.1f, decay = 0.1f, sustain = 1.0f, 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)
{
// need to call setSampleRate() first!
jassert (sampleRate > 0.0);
parameters = newParameters;
recalculateRates();
}
/** Returns the parameters currently being used by an ADSR object.
@see setParameters
*/
const Parameters& getParameters() const noexcept { return parameters; }
/** Returns true if the envelope is in its attack, decay, sustain or release stage. */
bool isActive() const noexcept { return state != 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 newSampleRate) noexcept
{
jassert (newSampleRate > 0.0);
sampleRate = newSampleRate;
}
//==============================================================================
/** Resets the envelope to an idle state. */
void reset() noexcept
{
envelopeVal = 0.0f;
state = State::idle;
}
/** Starts the attack phase of the envelope. */
void noteOn() noexcept
{
if (attackRate > 0.0f)
{
state = State::attack;
}
else if (decayRate > 0.0f)
{
envelopeVal = 1.0f;
state = State::decay;
}
else
{
state = State::sustain;
}
}
/** Starts the release phase of the envelope. */
void noteOff() noexcept
{
if (state != State::idle)
{
if (parameters.release > 0.0f)
{
releaseRate = (float) (envelopeVal / (parameters.release * sampleRate));
state = State::release;
}
else
{
reset();
}
}
}
//==============================================================================
/** Returns the next sample value for an ADSR object.
@see applyEnvelopeToBuffer
*/
float getNextSample() noexcept
{
if (state == State::idle)
return 0.0f;
if (state == State::attack)
{
envelopeVal += attackRate;
if (envelopeVal >= 1.0f)
{
envelopeVal = 1.0f;
goToNextState();
}
}
else if (state == State::decay)
{
envelopeVal -= decayRate;
if (envelopeVal <= parameters.sustain)
{
envelopeVal = parameters.sustain;
goToNextState();
}
}
else if (state == State::sustain)
{
envelopeVal = parameters.sustain;
}
else if (state == State::release)
{
envelopeVal -= releaseRate;
if (envelopeVal <= 0.0f)
goToNextState();
}
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());
if (state == State::idle)
{
buffer.clear (startSample, numSamples);
return;
}
if (state == State::sustain)
{
buffer.applyGain (startSample, numSamples, parameters.sustain);
return;
}
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 recalculateRates() noexcept
{
auto getRate = [] (float distance, float timeInSeconds, double sr)
{
return timeInSeconds > 0.0f ? (float) (distance / (timeInSeconds * sr)) : -1.0f;
};
attackRate = getRate (1.0f, parameters.attack, sampleRate);
decayRate = getRate (1.0f - parameters.sustain, parameters.decay, sampleRate);
releaseRate = getRate (parameters.sustain, parameters.release, sampleRate);
if ((state == State::attack && attackRate <= 0.0f)
|| (state == State::decay && (decayRate <= 0.0f || envelopeVal <= parameters.sustain))
|| (state == State::release && releaseRate <= 0.0f))
{
goToNextState();
}
}
void goToNextState() noexcept
{
if (state == State::attack)
state = (decayRate > 0.0f ? State::decay : State::sustain);
else if (state == State::decay)
state = State::sustain;
else if (state == State::release)
reset();
}
//==============================================================================
enum class State { idle, attack, decay, sustain, release };
State state = State::idle;
Parameters parameters;
double sampleRate = 44100.0;
float envelopeVal = 0.0f, attackRate = 0.0f, decayRate = 0.0f, releaseRate = 0.0f;
};
} // namespace juce

View File

@ -0,0 +1,243 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
struct ADSRTests : public UnitTest
{
ADSRTests() : UnitTest ("ADSR", UnitTestCategories::audio) {}
void runTest() override
{
constexpr double sampleRate = 44100.0;
const ADSR::Parameters parameters { 0.1f, 0.1f, 0.5f, 0.1f };
ADSR adsr;
adsr.setSampleRate (sampleRate);
adsr.setParameters (parameters);
beginTest ("Idle");
{
adsr.reset();
expect (! adsr.isActive());
expectEquals (adsr.getNextSample(), 0.0f);
}
beginTest ("Attack");
{
adsr.reset();
adsr.noteOn();
expect (adsr.isActive());
auto buffer = getTestBuffer (sampleRate, parameters.attack);
adsr.applyEnvelopeToBuffer (buffer, 0, buffer.getNumSamples());
expect (isIncreasing (buffer));
}
beginTest ("Decay");
{
adsr.reset();
adsr.noteOn();
advanceADSR (adsr, roundToInt (parameters.attack * sampleRate));
auto buffer = getTestBuffer (sampleRate, parameters.decay);
adsr.applyEnvelopeToBuffer (buffer, 0, buffer.getNumSamples());
expect (isDecreasing (buffer));
}
beginTest ("Sustain");
{
adsr.reset();
adsr.noteOn();
advanceADSR (adsr, roundToInt ((parameters.attack + parameters.decay + 0.01) * sampleRate));
auto random = getRandom();
for (int numTests = 0; numTests < 100; ++numTests)
{
const auto sustainLevel = random.nextFloat();
const auto sustainLength = jmax (0.1f, random.nextFloat());
adsr.setParameters ({ parameters.attack, parameters.decay, sustainLevel, parameters.release });
auto buffer = getTestBuffer (sampleRate, sustainLength);
adsr.applyEnvelopeToBuffer (buffer, 0, buffer.getNumSamples());
expect (isSustained (buffer, sustainLevel));
}
}
beginTest ("Release");
{
adsr.reset();
adsr.noteOn();
advanceADSR (adsr, roundToInt ((parameters.attack + parameters.decay) * sampleRate));
adsr.noteOff();
auto buffer = getTestBuffer (sampleRate, parameters.release);
adsr.applyEnvelopeToBuffer (buffer, 0, buffer.getNumSamples());
expect (isDecreasing (buffer));
}
beginTest ("Zero-length attack jumps to decay");
{
adsr.reset();
adsr.setParameters ({ 0.0f, parameters.decay, parameters.sustain, parameters.release });
adsr.noteOn();
auto buffer = getTestBuffer (sampleRate, parameters.decay);
adsr.applyEnvelopeToBuffer (buffer, 0, buffer.getNumSamples());
expect (isDecreasing (buffer));
}
beginTest ("Zero-length decay jumps to sustain");
{
adsr.reset();
adsr.setParameters ({ parameters.attack, 0.0f, parameters.sustain, parameters.release });
adsr.noteOn();
advanceADSR (adsr, roundToInt (parameters.attack * sampleRate));
adsr.getNextSample();
expectEquals (adsr.getNextSample(), parameters.sustain);
auto buffer = getTestBuffer (sampleRate, 1);
adsr.applyEnvelopeToBuffer (buffer, 0, buffer.getNumSamples());
expect (isSustained (buffer, parameters.sustain));
}
beginTest ("Zero-length attack and decay jumps to sustain");
{
adsr.reset();
adsr.setParameters ({ 0.0f, 0.0f, parameters.sustain, parameters.release });
adsr.noteOn();
expectEquals (adsr.getNextSample(), parameters.sustain);
auto buffer = getTestBuffer (sampleRate, 1);
adsr.applyEnvelopeToBuffer (buffer, 0, buffer.getNumSamples());
expect (isSustained (buffer, parameters.sustain));
}
beginTest ("Zero-length release resets to idle");
{
adsr.reset();
adsr.setParameters ({ parameters.attack, parameters.decay, parameters.sustain, 0.0f });
adsr.noteOn();
advanceADSR (adsr, roundToInt ((parameters.attack + parameters.decay) * sampleRate));
adsr.noteOff();
expect (! adsr.isActive());
}
}
static void advanceADSR (ADSR& adsr, int numSamplesToAdvance)
{
while (--numSamplesToAdvance >= 0)
adsr.getNextSample();
}
static AudioBuffer<float> getTestBuffer (double sampleRate, float lengthInSeconds)
{
AudioBuffer<float> buffer { 2, roundToInt (lengthInSeconds * sampleRate) };
for (int channel = 0; channel < buffer.getNumChannels(); ++channel)
for (int sample = 0; sample < buffer.getNumSamples(); ++sample)
buffer.setSample (channel, sample, 1.0f);
return buffer;
}
static bool isIncreasing (const AudioBuffer<float>& b)
{
jassert (b.getNumChannels() > 0 && b.getNumSamples() > 0);
for (int channel = 0; channel < b.getNumChannels(); ++channel)
{
float previousSample = -1.0f;
for (int sample = 0; sample < b.getNumSamples(); ++sample)
{
const auto currentSample = b.getSample (channel, sample);
if (currentSample <= previousSample)
return false;
previousSample = currentSample;
}
}
return true;
}
static bool isDecreasing (const AudioBuffer<float>& b)
{
jassert (b.getNumChannels() > 0 && b.getNumSamples() > 0);
for (int channel = 0; channel < b.getNumChannels(); ++channel)
{
float previousSample = std::numeric_limits<float>::max();
for (int sample = 0; sample < b.getNumSamples(); ++sample)
{
const auto currentSample = b.getSample (channel, sample);
if (currentSample >= previousSample)
return false;
previousSample = currentSample;
}
}
return true;
}
static bool isSustained (const AudioBuffer<float>& b, float sustainLevel)
{
jassert (b.getNumChannels() > 0 && b.getNumSamples() > 0);
for (int channel = 0; channel < b.getNumChannels(); ++channel)
if (b.findMinMax (channel, 0, b.getNumSamples()) != Range<float> { sustainLevel, sustainLevel })
return false;
return true;
}
};
static ADSRTests adsrTests;
} // namespace juce

View File

@ -0,0 +1,112 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
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

View File

@ -0,0 +1,500 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
/**
An interpolator base class for resampling streams of floats.
Note that the resamplers are 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 interpolator object.
@see LagrangeInterpolator, CatmullRomInterpolator, WindowedSincInterpolator,
LinearInterpolator, ZeroOrderHoldInterpolator
@tags{Audio}
*/
template <class InterpolatorTraits, int memorySize>
class JUCE_API GenericInterpolator
{
public:
GenericInterpolator() noexcept { reset(); }
GenericInterpolator (GenericInterpolator&&) noexcept = default;
GenericInterpolator& operator= (GenericInterpolator&&) noexcept = default;
/** Returns the latency of the interpolation algorithm in isolation.
In the context of resampling the total latency of a process using
the interpolator is the base latency divided by the speed ratio.
*/
static constexpr float getBaseLatency() noexcept
{
return InterpolatorTraits::algorithmicLatency;
}
/** Resets the state of the interpolator.
Call this when there's a break in the continuity of the input data stream.
*/
void reset() noexcept
{
indexBuffer = 0;
subSamplePos = 1.0;
std::fill (std::begin (lastInputSamples), std::end (lastInputSamples), 0.0f);
}
/** 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
{
return interpolate (speedRatio, inputSamples, outputSamples, numOutputSamplesToProduce);
}
/** 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 numInputSamplesAvailable 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 numInputSamplesAvailable,
int wrapAround) noexcept
{
return interpolate (speedRatio, inputSamples, outputSamples,
numOutputSamplesToProduce, numInputSamplesAvailable, wrapAround);
}
/** 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
{
return interpolateAdding (speedRatio, inputSamples, outputSamples, numOutputSamplesToProduce, gain);
}
/** 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 numInputSamplesAvailable 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 numInputSamplesAvailable,
int wrapAround,
float gain) noexcept
{
return interpolateAdding (speedRatio, inputSamples, outputSamples,
numOutputSamplesToProduce, numInputSamplesAvailable, wrapAround, gain);
}
private:
//==============================================================================
forcedinline void pushInterpolationSample (float newValue) noexcept
{
lastInputSamples[indexBuffer] = newValue;
if (++indexBuffer == memorySize)
indexBuffer = 0;
}
forcedinline void pushInterpolationSamples (const float* input,
int numOutputSamplesToProduce) noexcept
{
if (numOutputSamplesToProduce >= memorySize)
{
const auto* const offsetInput = input + (numOutputSamplesToProduce - memorySize);
for (int i = 0; i < memorySize; ++i)
pushInterpolationSample (offsetInput[i]);
}
else
{
for (int i = 0; i < numOutputSamplesToProduce; ++i)
pushInterpolationSample (input[i]);
}
}
forcedinline void pushInterpolationSamples (const float* input,
int numOutputSamplesToProduce,
int numInputSamplesAvailable,
int wrapAround) noexcept
{
if (numOutputSamplesToProduce >= memorySize)
{
if (numInputSamplesAvailable >= memorySize)
{
pushInterpolationSamples (input,
numOutputSamplesToProduce);
}
else
{
pushInterpolationSamples (input + ((numOutputSamplesToProduce - numInputSamplesAvailable) - 1),
numInputSamplesAvailable);
if (wrapAround > 0)
{
numOutputSamplesToProduce -= wrapAround;
pushInterpolationSamples (input + ((numOutputSamplesToProduce - (memorySize - numInputSamplesAvailable)) - 1),
memorySize - numInputSamplesAvailable);
}
else
{
for (int i = numInputSamplesAvailable; i < memorySize; ++i)
pushInterpolationSample (0.0f);
}
}
}
else
{
if (numOutputSamplesToProduce > numInputSamplesAvailable)
{
for (int i = 0; i < numInputSamplesAvailable; ++i)
pushInterpolationSample (input[i]);
const auto extraSamples = numOutputSamplesToProduce - numInputSamplesAvailable;
if (wrapAround > 0)
{
const auto* const offsetInput = input + (numInputSamplesAvailable - wrapAround);
for (int i = 0; i < extraSamples; ++i)
pushInterpolationSample (offsetInput[i]);
}
else
{
for (int i = 0; i < extraSamples; ++i)
pushInterpolationSample (0.0f);
}
}
else
{
for (int i = 0; i < numOutputSamplesToProduce; ++i)
pushInterpolationSample (input[i]);
}
}
}
//==============================================================================
int interpolate (double speedRatio,
const float* input,
float* output,
int numOutputSamplesToProduce) noexcept
{
auto pos = subSamplePos;
int numUsed = 0;
while (numOutputSamplesToProduce > 0)
{
while (pos >= 1.0)
{
pushInterpolationSample (input[numUsed++]);
pos -= 1.0;
}
*output++ = InterpolatorTraits::valueAtOffset (lastInputSamples, (float) pos, indexBuffer);
pos += speedRatio;
--numOutputSamplesToProduce;
}
subSamplePos = pos;
return numUsed;
}
int interpolate (double speedRatio,
const float* input, float* output,
int numOutputSamplesToProduce,
int numInputSamplesAvailable,
int wrap) noexcept
{
auto originalIn = input;
auto pos = subSamplePos;
bool exceeded = false;
if (speedRatio < 1.0)
{
for (int i = numOutputSamplesToProduce; --i >= 0;)
{
if (pos >= 1.0)
{
if (exceeded)
{
pushInterpolationSample (0.0f);
}
else
{
pushInterpolationSample (*input++);
if (--numInputSamplesAvailable <= 0)
{
if (wrap > 0)
{
input -= wrap;
numInputSamplesAvailable += wrap;
}
else
{
exceeded = true;
}
}
}
pos -= 1.0;
}
*output++ = InterpolatorTraits::valueAtOffset (lastInputSamples, (float) pos, indexBuffer);
pos += speedRatio;
}
}
else
{
for (int i = numOutputSamplesToProduce; --i >= 0;)
{
while (pos < speedRatio)
{
if (exceeded)
{
pushInterpolationSample (0);
}
else
{
pushInterpolationSample (*input++);
if (--numInputSamplesAvailable <= 0)
{
if (wrap > 0)
{
input -= wrap;
numInputSamplesAvailable += wrap;
}
else
{
exceeded = true;
}
}
}
pos += 1.0;
}
pos -= speedRatio;
*output++ = InterpolatorTraits::valueAtOffset (lastInputSamples, jmax (0.0f, 1.0f - (float) pos), indexBuffer);
}
}
subSamplePos = pos;
if (wrap == 0)
return (int) (input - originalIn);
return ((int) (input - originalIn) + wrap) % wrap;
}
int interpolateAdding (double speedRatio,
const float* input,
float* output,
int numOutputSamplesToProduce,
int numInputSamplesAvailable,
int wrap,
float gain) noexcept
{
auto originalIn = input;
auto pos = subSamplePos;
bool exceeded = false;
if (speedRatio < 1.0)
{
for (int i = numOutputSamplesToProduce; --i >= 0;)
{
if (pos >= 1.0)
{
if (exceeded)
{
pushInterpolationSample (0.0);
}
else
{
pushInterpolationSample (*input++);
if (--numInputSamplesAvailable <= 0)
{
if (wrap > 0)
{
input -= wrap;
numInputSamplesAvailable += wrap;
}
else
{
numInputSamplesAvailable = true;
}
}
}
pos -= 1.0;
}
*output++ += gain * InterpolatorTraits::valueAtOffset (lastInputSamples, (float) pos, indexBuffer);
pos += speedRatio;
}
}
else
{
for (int i = numOutputSamplesToProduce; --i >= 0;)
{
while (pos < speedRatio)
{
if (exceeded)
{
pushInterpolationSample (0.0);
}
else
{
pushInterpolationSample (*input++);
if (--numInputSamplesAvailable <= 0)
{
if (wrap > 0)
{
input -= wrap;
numInputSamplesAvailable += wrap;
}
else
{
exceeded = true;
}
}
}
pos += 1.0;
}
pos -= speedRatio;
*output++ += gain * InterpolatorTraits::valueAtOffset (lastInputSamples, jmax (0.0f, 1.0f - (float) pos), indexBuffer);
}
}
subSamplePos = pos;
if (wrap == 0)
return (int) (input - originalIn);
return ((int) (input - originalIn) + wrap) % wrap;
}
int interpolateAdding (double speedRatio,
const float* input,
float* output,
int numOutputSamplesToProduce,
float gain) noexcept
{
auto pos = subSamplePos;
int numUsed = 0;
while (numOutputSamplesToProduce > 0)
{
while (pos >= 1.0)
{
pushInterpolationSample (input[numUsed++]);
pos -= 1.0;
}
*output++ += gain * InterpolatorTraits::valueAtOffset (lastInputSamples, (float) pos, indexBuffer);
pos += speedRatio;
--numOutputSamplesToProduce;
}
subSamplePos = pos;
return numUsed;
}
//==============================================================================
float lastInputSamples[(size_t) memorySize];
double subSamplePos = 1.0;
int indexBuffer = 0;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (GenericInterpolator)
};
} // namespace juce

View File

@ -0,0 +1,340 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
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);
}
//==============================================================================
template <typename Mutex>
IIRFilterBase<Mutex>::IIRFilterBase() noexcept = default;
template <typename Mutex>
IIRFilterBase<Mutex>::IIRFilterBase (const IIRFilterBase& other) noexcept : active (other.active)
{
const typename Mutex::ScopedLockType sl (other.processLock);
coefficients = other.coefficients;
}
//==============================================================================
template <typename Mutex>
void IIRFilterBase<Mutex>::makeInactive() noexcept
{
const typename Mutex::ScopedLockType sl (processLock);
active = false;
}
template <typename Mutex>
void IIRFilterBase<Mutex>::setCoefficients (const IIRCoefficients& newCoefficients) noexcept
{
const typename Mutex::ScopedLockType sl (processLock);
coefficients = newCoefficients;
active = true;
}
//==============================================================================
template <typename Mutex>
void IIRFilterBase<Mutex>::reset() noexcept
{
const typename Mutex::ScopedLockType sl (processLock);
v1 = v2 = 0.0;
}
template <typename Mutex>
float IIRFilterBase<Mutex>::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;
}
template <typename Mutex>
void IIRFilterBase<Mutex>::processSamples (float* const samples, const int numSamples) noexcept
{
const typename Mutex::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;
}
}
template class IIRFilterBase<SpinLock>;
template class IIRFilterBase<DummyCriticalSection>;
} // namespace juce

View File

@ -0,0 +1,254 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
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}
*/
template <typename Mutex>
class JUCE_API IIRFilterBase
{
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.
*/
IIRFilterBase() noexcept;
/** Creates a copy of another filter. */
IIRFilterBase (const IIRFilterBase&) 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:
//==============================================================================
Mutex 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)
};
/**
An IIR filter that can perform low, high, or band-pass filtering on an
audio signal, and which attempts to implement basic thread-safety.
This class synchronises calls to some of its member functions, making it
safe (although not necessarily real-time-safe) to reset the filter or
apply new coefficients while the filter is processing on another thread.
In most cases this style of internal locking should not be used, and you
should attempt to provide thread-safety at a higher level in your program.
If you can guarantee that calls to the filter will be synchronised externally,
you could consider switching to SingleThreadedIIRFilter instead.
@see SingleThreadedIIRFilter, IIRCoefficient, IIRFilterAudioSource
@tags{Audio}
*/
class IIRFilter : public IIRFilterBase<SpinLock>
{
public:
using IIRFilterBase::IIRFilterBase;
};
/**
An IIR filter that can perform low, high, or band-pass filtering on an
audio signal, with no thread-safety guarantees.
You should use this class if you need an IIR filter, and don't plan to
call its member functions from multiple threads at once.
@see IIRFilter, IIRCoefficient, IIRFilterAudioSource
@tags{Audio}
*/
class SingleThreadedIIRFilter : public IIRFilterBase<DummyCriticalSection>
{
public:
using IIRFilterBase::IIRFilterBase;
};
} // namespace juce

View File

@ -0,0 +1,191 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
#if JUCE_UNIT_TESTS
class InterpolatorTests : public UnitTest
{
public:
InterpolatorTests()
: UnitTest ("InterpolatorTests", UnitTestCategories::audio)
{
}
private:
template <typename InterpolatorType>
void runInterplatorTests (const String& interpolatorName)
{
auto createGaussian = [] (std::vector<float>& destination, float scale, float centreInSamples, float width)
{
for (size_t i = 0; i < destination.size(); ++i)
{
auto x = (((float) i) - centreInSamples) * width;
destination[i] = std::exp (-(x * x));
}
FloatVectorOperations::multiply (destination.data(), scale, (int) destination.size());
};
auto findGaussianPeak = [] (const std::vector<float>& input) -> float
{
auto max = std::max_element (std::begin (input), std::end (input));
auto maxPrev = max - 1;
jassert (maxPrev >= std::begin (input));
auto maxNext = max + 1;
jassert (maxNext < std::end (input));
auto quadraticMaxLoc = (*maxPrev - *maxNext) / (2.0f * ((*maxNext + *maxPrev) - (2.0f * *max)));
return quadraticMaxLoc + (float) std::distance (std::begin (input), max);
};
auto expectAllElementsWithin = [this] (const std::vector<float>& v1, const std::vector<float>& v2, float tolerance)
{
expectEquals ((int) v1.size(), (int) v2.size());
for (size_t i = 0; i < v1.size(); ++i)
expectWithinAbsoluteError (v1[i], v2[i], tolerance);
};
InterpolatorType interpolator;
constexpr size_t inputSize = 1001;
static_assert (inputSize > 800 + InterpolatorType::getBaseLatency(),
"The test InterpolatorTests input buffer is too small");
std::vector<float> input (inputSize);
constexpr auto inputGaussianMidpoint = (float) (inputSize - 1) / 2.0f;
constexpr auto inputGaussianValueAtEnds = 0.000001f;
const auto inputGaussianWidth = std::sqrt (-std::log (inputGaussianValueAtEnds)) / inputGaussianMidpoint;
createGaussian (input, 1.0f, inputGaussianMidpoint, inputGaussianWidth);
for (auto speedRatio : { 0.4, 0.8263, 1.0, 1.05, 1.2384, 1.6 })
{
const auto expectedGaussianMidpoint = (inputGaussianMidpoint + InterpolatorType::getBaseLatency()) / (float) speedRatio;
const auto expectedGaussianWidth = inputGaussianWidth * (float) speedRatio;
const auto outputBufferSize = (size_t) std::floor ((float) input.size() / speedRatio);
for (int numBlocks : { 1, 5 })
{
const auto inputBlockSize = (float) input.size() / (float) numBlocks;
const auto outputBlockSize = (int) std::floor (inputBlockSize / speedRatio);
std::vector<float> output (outputBufferSize, std::numeric_limits<float>::min());
beginTest (interpolatorName + " process " + String (numBlocks) + " blocks ratio " + String (speedRatio));
interpolator.reset();
{
auto* inputPtr = input.data();
auto* outputPtr = output.data();
for (int i = 0; i < numBlocks; ++i)
{
auto numInputSamplesRead = interpolator.process (speedRatio, inputPtr, outputPtr, outputBlockSize);
inputPtr += numInputSamplesRead;
outputPtr += outputBlockSize;
}
}
expectWithinAbsoluteError (findGaussianPeak (output), expectedGaussianMidpoint, 0.1f);
std::vector<float> expectedOutput (output.size());
createGaussian (expectedOutput, 1.0f, expectedGaussianMidpoint, expectedGaussianWidth);
expectAllElementsWithin (output, expectedOutput, 0.02f);
beginTest (interpolatorName + " process adding " + String (numBlocks) + " blocks ratio " + String (speedRatio));
interpolator.reset();
constexpr float addingGain = 0.7384f;
{
auto* inputPtr = input.data();
auto* outputPtr = output.data();
for (int i = 0; i < numBlocks; ++i)
{
auto numInputSamplesRead = interpolator.processAdding (speedRatio, inputPtr, outputPtr, outputBlockSize, addingGain);
inputPtr += numInputSamplesRead;
outputPtr += outputBlockSize;
}
}
expectWithinAbsoluteError (findGaussianPeak (output), expectedGaussianMidpoint, 0.1f);
std::vector<float> additionalOutput (output.size());
createGaussian (additionalOutput, addingGain, expectedGaussianMidpoint, expectedGaussianWidth);
FloatVectorOperations::add (expectedOutput.data(), additionalOutput.data(), (int) additionalOutput.size());
expectAllElementsWithin (output, expectedOutput, 0.02f);
}
beginTest (interpolatorName + " process wrap 0 ratio " + String (speedRatio));
std::vector<float> doubleLengthOutput (2 * outputBufferSize, std::numeric_limits<float>::min());
interpolator.reset();
interpolator.process (speedRatio, input.data(), doubleLengthOutput.data(), (int) doubleLengthOutput.size(),
(int) input.size(), 0);
std::vector<float> expectedDoubleLengthOutput (doubleLengthOutput.size());
createGaussian (expectedDoubleLengthOutput, 1.0f, expectedGaussianMidpoint, expectedGaussianWidth);
expectAllElementsWithin (doubleLengthOutput, expectedDoubleLengthOutput, 0.02f);
beginTest (interpolatorName + " process wrap double ratio " + String (speedRatio));
interpolator.reset();
interpolator.process (speedRatio, input.data(), doubleLengthOutput.data(), (int) doubleLengthOutput.size(),
(int) input.size(), (int) input.size());
std::vector<float> secondGaussian (doubleLengthOutput.size());
createGaussian (secondGaussian, 1.0f, expectedGaussianMidpoint + (float) outputBufferSize, expectedGaussianWidth);
FloatVectorOperations::add (expectedDoubleLengthOutput.data(), secondGaussian.data(), (int) expectedDoubleLengthOutput.size());
expectAllElementsWithin (doubleLengthOutput, expectedDoubleLengthOutput, 0.02f);
}
}
public:
void runTest() override
{
runInterplatorTests<WindowedSincInterpolator> ("WindowedSincInterpolator");
runInterplatorTests<LagrangeInterpolator> ("LagrangeInterpolator");
runInterplatorTests<CatmullRomInterpolator> ("CatmullRomInterpolator");
runInterplatorTests<LinearInterpolator> ("LinearInterpolator");
}
};
static InterpolatorTests interpolatorTests;
#endif
} // namespace juce

View File

@ -0,0 +1,245 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
/**
A collection of different interpolators for resampling streams of floats.
@see GenericInterpolator, WindowedSincInterpolator, LagrangeInterpolator,
CatmullRomInterpolator, LinearInterpolator, ZeroOrderHoldInterpolator
@tags{Audio}
*/
class Interpolators
{
private:
struct WindowedSincTraits
{
static constexpr float algorithmicLatency = 100.0f;
static forcedinline float windowedSinc (float firstFrac, int index) noexcept
{
auto index2 = index + 1;
auto frac = firstFrac;
auto value1 = lookupTable[index];
auto value2 = lookupTable[index2];
return value1 + (frac * (value2 - value1));
}
static forcedinline float valueAtOffset (const float* const inputs, const float offset, int indexBuffer) noexcept
{
const int numCrossings = 100;
const float floatCrossings = (float) numCrossings;
float result = 0.0f;
auto samplePosition = indexBuffer;
float firstFrac = 0.0f;
float lastSincPosition = -1.0f;
int index = 0, sign = -1;
for (int i = -numCrossings; i <= numCrossings; ++i)
{
auto sincPosition = (1.0f - offset) + (float) i;
if (i == -numCrossings || (sincPosition >= 0 && lastSincPosition < 0))
{
auto indexFloat = (sincPosition >= 0.f ? sincPosition : -sincPosition) * 100.0f;
auto indexFloored = std::floor (indexFloat);
index = (int) indexFloored;
firstFrac = indexFloat - indexFloored;
sign = (sincPosition < 0 ? -1 : 1);
}
if (sincPosition == 0.0f)
result += inputs[samplePosition];
else if (sincPosition < floatCrossings && sincPosition > -floatCrossings)
result += inputs[samplePosition] * windowedSinc (firstFrac, index);
if (++samplePosition == numCrossings * 2)
samplePosition = 0;
lastSincPosition = sincPosition;
index += 100 * sign;
}
return result;
}
static const float lookupTable[10001];
};
struct LagrangeTraits
{
static constexpr float algorithmicLatency = 2.0f;
static float valueAtOffset (const float*, float, int) noexcept;
};
struct CatmullRomTraits
{
//==============================================================================
static constexpr float algorithmicLatency = 2.0f;
static forcedinline float valueAtOffset (const float* const inputs, const float offset, int index) noexcept
{
auto y0 = inputs[index]; if (++index == 4) index = 0;
auto y1 = inputs[index]; if (++index == 4) index = 0;
auto y2 = inputs[index]; if (++index == 4) index = 0;
auto y3 = inputs[index];
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))))));
}
};
struct LinearTraits
{
static constexpr float algorithmicLatency = 1.0f;
static forcedinline float valueAtOffset (const float* const inputs, const float offset, int index) noexcept
{
auto y0 = inputs[index];
auto y1 = inputs[index == 0 ? 1 : 0];
return y1 * offset + y0 * (1.0f - offset);
}
};
struct ZeroOrderHoldTraits
{
static constexpr float algorithmicLatency = 0.0f;
static forcedinline float valueAtOffset (const float* const inputs, const float, int) noexcept
{
return inputs[0];
}
};
public:
using WindowedSinc = GenericInterpolator<WindowedSincTraits, 200>;
using Lagrange = GenericInterpolator<LagrangeTraits, 5>;
using CatmullRom = GenericInterpolator<CatmullRomTraits, 4>;
using Linear = GenericInterpolator<LinearTraits, 2>;
using ZeroOrderHold = GenericInterpolator<ZeroOrderHoldTraits, 1>;
};
//==============================================================================
/**
An interpolator for resampling a stream of floats using high order windowed
(hann) sinc interpolation, recommended for high quality resampling.
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 LinearInterpolator object.
@see GenericInterpolator
@see LagrangeInterpolator, CatmullRomInterpolator, LinearInterpolator,
ZeroOrderHoldInterpolator
@tags{Audio}
*/
using WindowedSincInterpolator = Interpolators::WindowedSinc;
/**
An 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 GenericInterpolator
@see CatmullRomInterpolator, WindowedSincInterpolator, LinearInterpolator,
ZeroOrderHoldInterpolator
@tags{Audio}
*/
using LagrangeInterpolator = Interpolators::Lagrange;
/**
An 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 GenericInterpolator
@see LagrangeInterpolator, WindowedSincInterpolator, LinearInterpolator,
ZeroOrderHoldInterpolator
@tags{Audio}
*/
using CatmullRomInterpolator = Interpolators::CatmullRom;
/**
An interpolator for resampling a stream of floats using linear 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 LinearInterpolator object.
@see GenericInterpolator
@see LagrangeInterpolator, CatmullRomInterpolator, WindowedSincInterpolator,
ZeroOrderHoldInterpolator
@tags{Audio}
*/
using LinearInterpolator = Interpolators::Linear;
/**
An interpolator for resampling a stream of floats using zero order hold
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 ZeroOrderHoldInterpolator
object.
@see GenericInterpolator
@see LagrangeInterpolator, CatmullRomInterpolator, WindowedSincInterpolator,
LinearInterpolator
@tags{Audio}
*/
using ZeroOrderHoldInterpolator = Interpolators::ZeroOrderHold;
} // namespace juce

View File

@ -0,0 +1,62 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
template <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 {}
};
template <int k>
static 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;
}
float Interpolators::LagrangeTraits::valueAtOffset (const float* inputs, float offset, int index) noexcept
{
float result = 0.0f;
result += calcCoefficient<0> (inputs[index], offset); if (++index == 5) index = 0;
result += calcCoefficient<1> (inputs[index], offset); if (++index == 5) index = 0;
result += calcCoefficient<2> (inputs[index], offset); if (++index == 5) index = 0;
result += calcCoefficient<3> (inputs[index], offset); if (++index == 5) index = 0;
result += calcCoefficient<4> (inputs[index], offset);
return result;
}
} // namespace juce

View File

@ -0,0 +1,317 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
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
{
JUCE_BEGIN_IGNORE_WARNINGS_MSVC (6011)
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;
}
JUCE_END_IGNORE_WARNINGS_MSVC
}
/** Applies the reverb to a single mono channel of audio data. */
void processMono (float* const samples, const int numSamples) noexcept
{
JUCE_BEGIN_IGNORE_WARNINGS_MSVC (6011)
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;
}
JUCE_END_IGNORE_WARNINGS_MSVC
}
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

View File

@ -0,0 +1,92 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
#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", UnitTestCategories::smoothedValues)
{}
void runTest() override
{
beginTest ("Linear moving target");
{
SmoothedValue<float, ValueSmoothingTypes::Linear> sv;
sv.reset (12);
float initialValue = 0.0f;
sv.setCurrentAndTargetValue (initialValue);
sv.setTargetValue (1.0f);
auto delta = sv.getNextValue() - initialValue;
sv.skip (6);
auto newInitialValue = sv.getCurrentValue();
sv.setTargetValue (newInitialValue + 2.0f);
auto doubleDelta = sv.getNextValue() - newInitialValue;
expectWithinAbsoluteError (doubleDelta, delta * 2.0f, 1.0e-7f);
}
beginTest ("Multiplicative curve");
{
SmoothedValue<double, ValueSmoothingTypes::Multiplicative> sv;
auto numSamples = 12;
AudioBuffer<double> values (2, numSamples + 1);
sv.reset (numSamples);
sv.setCurrentAndTargetValue (1.0);
sv.setTargetValue (2.0f);
values.setSample (0, 0, sv.getCurrentValue());
for (int i = 1; i < values.getNumSamples(); ++i)
values.setSample (0, i, sv.getNextValue());
sv.setTargetValue (1.0f);
values.setSample (1, values.getNumSamples() - 1, sv.getCurrentValue());
for (int i = values.getNumSamples() - 2; i >= 0 ; --i)
values.setSample (1, i, sv.getNextValue());
for (int i = 0; i < values.getNumSamples(); ++i)
expectWithinAbsoluteError (values.getSample (0, i), values.getSample (1, i), 1.0e-9);
}
}
};
static SmoothedValueTests smoothedValueTests;
#endif
} // namespace juce

View File

@ -0,0 +1,631 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
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.
@tags{Audio}
*/
struct Linear {};
/**
Used to indicate a smoothing between multiplicative values.
@tags{Audio}
*/
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;
}
//==============================================================================
#ifndef DOXYGEN
/** Using the new methods:
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
*/
[[deprecated ("Use setTargetValue and setCurrentAndTargetValue instead.")]]
void setValue (FloatType newValue, bool force = false) noexcept
{
if (force)
{
this->setCurrentAndTargetValue (newValue);
return;
}
setTargetValue (newValue);
}
#endif
private:
//==============================================================================
template <typename T>
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))) / (FloatType) 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", UnitTestCategories::smoothedValues)
{}
void runTest() override
{
beginTest ("Initial state");
{
SmoothedValueType sv;
auto value = sv.getCurrentValue();
expectEquals (sv.getTargetValue(), value);
sv.getNextValue();
expectEquals (sv.getCurrentValue(), value);
expect (! sv.isSmoothing());
}
beginTest ("Resetting");
{
auto initialValue = 15.0f;
SmoothedValueType sv (initialValue);
sv.reset (3);
expectEquals (sv.getCurrentValue(), initialValue);
auto targetValue = initialValue + 1.0f;
sv.setTargetValue (targetValue);
expectEquals (sv.getTargetValue(), targetValue);
expectEquals (sv.getCurrentValue(), initialValue);
expect (sv.isSmoothing());
auto currentValue = sv.getNextValue();
expect (currentValue > initialValue);
expectEquals (sv.getCurrentValue(), currentValue);
expectEquals (sv.getTargetValue(), targetValue);
expect (sv.isSmoothing());
sv.reset (5);
expectEquals (sv.getCurrentValue(), targetValue);
expectEquals (sv.getTargetValue(), targetValue);
expect (! sv.isSmoothing());
sv.getNextValue();
expectEquals (sv.getCurrentValue(), targetValue);
sv.setTargetValue (1.5f);
sv.getNextValue();
float newStart = 0.2f;
sv.setCurrentAndTargetValue (newStart);
expectEquals (sv.getNextValue(), newStart);
expectEquals (sv.getTargetValue(), newStart);
expectEquals (sv.getCurrentValue(), newStart);
expect (! sv.isSmoothing());
}
beginTest ("Sample rate");
{
SmoothedValueType svSamples { 3.0f };
auto svTime = svSamples;
auto numSamples = 12;
svSamples.reset (numSamples);
svTime.reset (numSamples * 2, 1.0);
for (int i = 0; i < numSamples; ++i)
{
svTime.skip (1);
expectWithinAbsoluteError (svSamples.getNextValue(),
svTime.getNextValue(),
1.0e-7f);
}
}
beginTest ("Block processing");
{
SmoothedValueType sv (1.0f);
sv.reset (12);
sv.setTargetValue (2.0f);
const auto numSamples = 15;
AudioBuffer<float> referenceData (1, numSamples);
for (int i = 0; i < numSamples; ++i)
referenceData.setSample (0, i, sv.getNextValue());
expect (referenceData.getSample (0, 0) > 0);
expect (referenceData.getSample (0, 10) < sv.getTargetValue());
expectWithinAbsoluteError (referenceData.getSample (0, 11),
sv.getTargetValue(),
2.0e-7f);
auto getUnitData = [] (int numSamplesToGenerate)
{
AudioBuffer<float> result (1, numSamplesToGenerate);
for (int i = 0; i < numSamplesToGenerate; ++i)
result.setSample (0, i, 1.0f);
return result;
};
auto compareData = [this] (const AudioBuffer<float>& test,
const AudioBuffer<float>& reference)
{
for (int i = 0; i < test.getNumSamples(); ++i)
expectWithinAbsoluteError (test.getSample (0, i),
reference.getSample (0, i),
2.0e-7f);
};
auto testData = getUnitData (numSamples);
sv.setCurrentAndTargetValue (1.0f);
sv.setTargetValue (2.0f);
sv.applyGain (testData.getWritePointer (0), numSamples);
compareData (testData, referenceData);
testData = getUnitData (numSamples);
AudioBuffer<float> destData (1, numSamples);
sv.setCurrentAndTargetValue (1.0f);
sv.setTargetValue (2.0f);
sv.applyGain (destData.getWritePointer (0),
testData.getReadPointer (0),
numSamples);
compareData (destData, referenceData);
compareData (testData, getUnitData (numSamples));
testData = getUnitData (numSamples);
sv.setCurrentAndTargetValue (1.0f);
sv.setTargetValue (2.0f);
sv.applyGain (testData, numSamples);
compareData (testData, referenceData);
}
beginTest ("Skip");
{
SmoothedValueType sv;
sv.reset (12);
sv.setCurrentAndTargetValue (1.0f);
sv.setTargetValue (2.0f);
Array<float> reference;
for (int i = 0; i < 15; ++i)
reference.add (sv.getNextValue());
sv.setCurrentAndTargetValue (1.0f);
sv.setTargetValue (2.0f);
expectWithinAbsoluteError (sv.skip (1), reference[0], 1.0e-6f);
expectWithinAbsoluteError (sv.skip (1), reference[1], 1.0e-6f);
expectWithinAbsoluteError (sv.skip (2), reference[3], 1.0e-6f);
sv.skip (3);
expectWithinAbsoluteError (sv.getCurrentValue(), reference[6], 1.0e-6f);
expectEquals (sv.skip (300), sv.getTargetValue());
expectEquals (sv.getCurrentValue(), sv.getTargetValue());
}
beginTest ("Negative");
{
SmoothedValueType sv;
auto numValues = 12;
sv.reset (numValues);
std::vector<std::pair<float, float>> ranges = { { -1.0f, -2.0f },
{ -100.0f, -3.0f } };
for (auto range : ranges)
{
auto start = range.first, end = range.second;
sv.setCurrentAndTargetValue (start);
sv.setTargetValue (end);
auto val = sv.skip (numValues / 2);
if (end > start)
expect (val > start && val < end);
else
expect (val < start && val > end);
auto nextVal = sv.getNextValue();
expect (end > start ? (nextVal > val) : (nextVal < val));
auto endVal = sv.skip (500);
expectEquals (endVal, end);
expectEquals (sv.getNextValue(), end);
expectEquals (sv.getCurrentValue(), end);
sv.setCurrentAndTargetValue (start);
sv.setTargetValue (end);
SmoothedValueType positiveSv { -start };
positiveSv.reset (numValues);
positiveSv.setTargetValue (-end);
for (int i = 0; i < numValues + 2; ++i)
expectEquals (sv.getNextValue(), -positiveSv.getNextValue());
}
}
}
};
#endif
} // namespace juce

File diff suppressed because it is too large Load Diff