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:
269
deps/juce/modules/juce_audio_basics/utilities/juce_ADSR.h
vendored
Normal file
269
deps/juce/modules/juce_audio_basics/utilities/juce_ADSR.h
vendored
Normal 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
|
243
deps/juce/modules/juce_audio_basics/utilities/juce_ADSR_test.cpp
vendored
Normal file
243
deps/juce/modules/juce_audio_basics/utilities/juce_ADSR_test.cpp
vendored
Normal 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
|
112
deps/juce/modules/juce_audio_basics/utilities/juce_Decibels.h
vendored
Normal file
112
deps/juce/modules/juce_audio_basics/utilities/juce_Decibels.h
vendored
Normal 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
|
500
deps/juce/modules/juce_audio_basics/utilities/juce_GenericInterpolator.h
vendored
Normal file
500
deps/juce/modules/juce_audio_basics/utilities/juce_GenericInterpolator.h
vendored
Normal 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
|
340
deps/juce/modules/juce_audio_basics/utilities/juce_IIRFilter.cpp
vendored
Normal file
340
deps/juce/modules/juce_audio_basics/utilities/juce_IIRFilter.cpp
vendored
Normal 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
|
254
deps/juce/modules/juce_audio_basics/utilities/juce_IIRFilter.h
vendored
Normal file
254
deps/juce/modules/juce_audio_basics/utilities/juce_IIRFilter.h
vendored
Normal 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
|
191
deps/juce/modules/juce_audio_basics/utilities/juce_Interpolators.cpp
vendored
Normal file
191
deps/juce/modules/juce_audio_basics/utilities/juce_Interpolators.cpp
vendored
Normal 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
|
245
deps/juce/modules/juce_audio_basics/utilities/juce_Interpolators.h
vendored
Normal file
245
deps/juce/modules/juce_audio_basics/utilities/juce_Interpolators.h
vendored
Normal 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
|
62
deps/juce/modules/juce_audio_basics/utilities/juce_LagrangeInterpolator.cpp
vendored
Normal file
62
deps/juce/modules/juce_audio_basics/utilities/juce_LagrangeInterpolator.cpp
vendored
Normal 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
|
317
deps/juce/modules/juce_audio_basics/utilities/juce_Reverb.h
vendored
Normal file
317
deps/juce/modules/juce_audio_basics/utilities/juce_Reverb.h
vendored
Normal 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
|
92
deps/juce/modules/juce_audio_basics/utilities/juce_SmoothedValue.cpp
vendored
Normal file
92
deps/juce/modules/juce_audio_basics/utilities/juce_SmoothedValue.cpp
vendored
Normal 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
|
631
deps/juce/modules/juce_audio_basics/utilities/juce_SmoothedValue.h
vendored
Normal file
631
deps/juce/modules/juce_audio_basics/utilities/juce_SmoothedValue.h
vendored
Normal 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
|
10033
deps/juce/modules/juce_audio_basics/utilities/juce_WindowedSincInterpolator.cpp
vendored
Normal file
10033
deps/juce/modules/juce_audio_basics/utilities/juce_WindowedSincInterpolator.cpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user