fix macOS build (following Projucer changes made in Windows, which removed /Applications/JUCE/modules from its headers). move JUCE headers under source control, so that Windows and macOS can both build against same version of JUCE. remove AUv3 target (I think it's an iOS thing, so it will never work with this macOS fluidsynth dylib).
This commit is contained in:
158
modules/juce_dsp/processors/juce_Bias.h
Normal file
158
modules/juce_dsp/processors/juce_Bias.h
Normal file
@ -0,0 +1,158 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
namespace dsp
|
||||
{
|
||||
|
||||
/**
|
||||
Adds a DC offset (voltage bias) to the audio samples.
|
||||
|
||||
This is a useful preprocessor for asymmetric waveshaping when a waveshaper is
|
||||
bookended by a bias on input and a DC-offset removing high pass filter on output.
|
||||
|
||||
This is an extremely simple bias implementation that simply adds a value to a signal.
|
||||
More complicated bias behaviours exist in real circuits - for your homework ;).
|
||||
|
||||
@tags{DSP}
|
||||
*/
|
||||
template <typename FloatType>
|
||||
class Bias
|
||||
{
|
||||
public:
|
||||
Bias() noexcept {}
|
||||
|
||||
//==============================================================================
|
||||
/** Sets the DC bias
|
||||
@param newBias DC offset in range [-1, 1]
|
||||
*/
|
||||
void setBias (FloatType newBias) noexcept
|
||||
{
|
||||
jassert (newBias >= static_cast<FloatType> (-1) && newBias <= static_cast<FloatType> (1));
|
||||
bias.setValue(newBias);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the DC bias
|
||||
@return DC bias, which should be in the range [-1, 1]
|
||||
*/
|
||||
FloatType getBias() const noexcept { return bias.getTargetValue(); }
|
||||
|
||||
/** Sets the length of the ramp used for smoothing gain changes. */
|
||||
void setRampDurationSeconds (double newDurationSeconds) noexcept
|
||||
{
|
||||
if (rampDurationSeconds != newDurationSeconds)
|
||||
{
|
||||
rampDurationSeconds = newDurationSeconds;
|
||||
updateRamp();
|
||||
}
|
||||
}
|
||||
|
||||
double getRampDurationSeconds() const noexcept { return rampDurationSeconds; }
|
||||
|
||||
//==============================================================================
|
||||
/** Called before processing starts */
|
||||
void prepare (const ProcessSpec& spec) noexcept
|
||||
{
|
||||
sampleRate = spec.sampleRate;
|
||||
updateRamp();
|
||||
}
|
||||
|
||||
void reset() noexcept
|
||||
{
|
||||
bias.reset (sampleRate, rampDurationSeconds);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the result of processing a single sample. */
|
||||
template <typename SampleType>
|
||||
SampleType processSample (SampleType inputSample) const noexcept
|
||||
{
|
||||
return inputSample + bias.getNextValue();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
/** Processes the input and output buffers supplied in the processing context. */
|
||||
template<typename ProcessContext>
|
||||
void process (const ProcessContext& context) noexcept
|
||||
{
|
||||
auto&& inBlock = context.getInputBlock();
|
||||
auto&& outBlock = context.getOutputBlock();
|
||||
|
||||
jassert (inBlock.getNumChannels() == outBlock.getNumChannels());
|
||||
jassert (inBlock.getNumSamples() == outBlock.getNumSamples());
|
||||
|
||||
auto len = inBlock.getNumSamples();
|
||||
auto numChannels = inBlock.getNumChannels();
|
||||
|
||||
if (context.isBypassed)
|
||||
{
|
||||
bias.skip (static_cast<int> (len));
|
||||
|
||||
if (context.usesSeparateInputAndOutputBlocks())
|
||||
outBlock.copy (inBlock);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (numChannels == 1)
|
||||
{
|
||||
auto* src = inBlock.getChannelPointer (0);
|
||||
auto* dst = outBlock.getChannelPointer (0);
|
||||
|
||||
for (size_t i = 0; i < len; ++i)
|
||||
dst[i] = src[i] + bias.getNextValue();
|
||||
}
|
||||
else
|
||||
{
|
||||
auto* biases = static_cast<FloatType*> (alloca (sizeof (FloatType) * len));
|
||||
|
||||
for (size_t i = 0; i < len; ++i)
|
||||
biases[i] = bias.getNextValue();
|
||||
|
||||
for (size_t chan = 0; chan < numChannels; ++chan)
|
||||
FloatVectorOperations::add (outBlock.getChannelPointer (chan),
|
||||
inBlock.getChannelPointer (chan),
|
||||
biases, static_cast<int> (len));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
LinearSmoothedValue<FloatType> bias;
|
||||
double sampleRate = 0, rampDurationSeconds = 0;
|
||||
|
||||
void updateRamp() noexcept
|
||||
{
|
||||
if (sampleRate > 0)
|
||||
bias.reset (sampleRate, rampDurationSeconds);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace dsp
|
||||
} // namespace juce
|
162
modules/juce_dsp/processors/juce_FIRFilter.cpp
Normal file
162
modules/juce_dsp/processors/juce_FIRFilter.cpp
Normal file
@ -0,0 +1,162 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
namespace dsp
|
||||
{
|
||||
|
||||
template <typename NumericType>
|
||||
double FIR::Coefficients<NumericType>::Coefficients::getMagnitudeForFrequency (double frequency, double theSampleRate) const noexcept
|
||||
{
|
||||
jassert (theSampleRate > 0.0);
|
||||
jassert (frequency >= 0.0 && frequency <= theSampleRate * 0.5);
|
||||
|
||||
constexpr Complex<double> j (0, 1);
|
||||
auto order = getFilterOrder();
|
||||
|
||||
Complex<double> numerator = 0.0, factor = 1.0;
|
||||
Complex<double> jw = std::exp (-MathConstants<double>::twoPi * frequency * j / theSampleRate);
|
||||
|
||||
const auto* coefs = coefficients.begin();
|
||||
|
||||
for (size_t n = 0; n <= order; ++n)
|
||||
{
|
||||
numerator += static_cast<double> (coefs[n]) * factor;
|
||||
factor *= jw;
|
||||
}
|
||||
|
||||
return std::abs (numerator);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
template <typename NumericType>
|
||||
void FIR::Coefficients<NumericType>::Coefficients::getMagnitudeForFrequencyArray (double* frequencies, double* magnitudes,
|
||||
size_t numSamples, double theSampleRate) const noexcept
|
||||
{
|
||||
jassert (theSampleRate > 0.0);
|
||||
|
||||
constexpr Complex<double> j (0, 1);
|
||||
const auto* coefs = coefficients.begin();
|
||||
auto order = getFilterOrder();
|
||||
|
||||
for (size_t i = 0; i < numSamples; ++i)
|
||||
{
|
||||
jassert (frequencies[i] >= 0.0 && frequencies[i] <= theSampleRate * 0.5);
|
||||
|
||||
Complex<double> numerator = 0.0;
|
||||
Complex<double> factor = 1.0;
|
||||
Complex<double> jw = std::exp (-MathConstants<double>::twoPi * frequencies[i] * j / theSampleRate);
|
||||
|
||||
for (size_t n = 0; n <= order; ++n)
|
||||
{
|
||||
numerator += static_cast<double> (coefs[n]) * factor;
|
||||
factor *= jw;
|
||||
}
|
||||
|
||||
magnitudes[i] = std::abs (numerator);
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
template <typename NumericType>
|
||||
double FIR::Coefficients<NumericType>::Coefficients::getPhaseForFrequency (double frequency, double theSampleRate) const noexcept
|
||||
{
|
||||
jassert (theSampleRate > 0.0);
|
||||
jassert (frequency >= 0.0 && frequency <= theSampleRate * 0.5);
|
||||
|
||||
constexpr Complex<double> j (0, 1);
|
||||
|
||||
Complex<double> numerator = 0.0;
|
||||
Complex<double> factor = 1.0;
|
||||
Complex<double> jw = std::exp (-MathConstants<double>::twoPi * frequency * j / theSampleRate);
|
||||
|
||||
const auto* coefs = coefficients.begin();
|
||||
auto order = getFilterOrder();
|
||||
|
||||
for (size_t n = 0; n <= order; ++n)
|
||||
{
|
||||
numerator += static_cast<double> (coefs[n]) * factor;
|
||||
factor *= jw;
|
||||
}
|
||||
|
||||
return std::arg (numerator);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
template <typename NumericType>
|
||||
void FIR::Coefficients<NumericType>::Coefficients::getPhaseForFrequencyArray (double* frequencies, double* phases,
|
||||
size_t numSamples, double theSampleRate) const noexcept
|
||||
{
|
||||
jassert (theSampleRate > 0.0);
|
||||
|
||||
constexpr Complex<double> j (0, 1);
|
||||
const auto* coefs = coefficients.begin();
|
||||
auto order = getFilterOrder();
|
||||
|
||||
for (size_t i = 0; i < numSamples; ++i)
|
||||
{
|
||||
jassert (frequencies[i] >= 0.0 && frequencies[i] <= theSampleRate * 0.5);
|
||||
|
||||
Complex<double> numerator = 0.0, factor = 1.0;
|
||||
Complex<double> jw = std::exp (-MathConstants<double>::twoPi * frequencies[i] * j / theSampleRate);
|
||||
|
||||
for (size_t n = 0; n <= order; ++n)
|
||||
{
|
||||
numerator += static_cast<double> (coefs[n]) * factor;
|
||||
factor *= jw;
|
||||
}
|
||||
|
||||
phases[i] = std::arg (numerator);
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
template <typename NumericType>
|
||||
void FIR::Coefficients<NumericType>::Coefficients::normalise() noexcept
|
||||
{
|
||||
auto magnitude = static_cast<NumericType> (0);
|
||||
|
||||
auto* coefs = coefficients.getRawDataPointer();
|
||||
auto n = static_cast<size_t> (coefficients.size());
|
||||
|
||||
for (size_t i = 0; i < n; ++i)
|
||||
{
|
||||
auto c = coefs[i];
|
||||
magnitude += c * c;
|
||||
}
|
||||
|
||||
auto magnitudeInv = 1 / (4 * std::sqrt (magnitude));
|
||||
|
||||
FloatVectorOperations::multiply (coefs, magnitudeInv, static_cast<int> (n));
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
template struct FIR::Coefficients<float>;
|
||||
template struct FIR::Coefficients<double>;
|
||||
|
||||
} // namespace dsp
|
||||
} // namespace juce
|
284
modules/juce_dsp/processors/juce_FIRFilter.h
Normal file
284
modules/juce_dsp/processors/juce_FIRFilter.h
Normal file
@ -0,0 +1,284 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
namespace dsp
|
||||
{
|
||||
|
||||
/**
|
||||
Classes for FIR filter processing.
|
||||
*/
|
||||
namespace FIR
|
||||
{
|
||||
template <typename NumericType>
|
||||
struct Coefficients;
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A processing class that can perform FIR filtering on an audio signal, in the
|
||||
time domain.
|
||||
|
||||
Using FIRFilter is fast enough for FIRCoefficients with a size lower than 128
|
||||
samples. For longer filters, it might be more efficient to use the class
|
||||
Convolution instead, which does the same processing in the frequency domain
|
||||
thanks to FFT.
|
||||
|
||||
@see FIRFilter::Coefficients, Convolution, FFT
|
||||
|
||||
@tags{DSP}
|
||||
*/
|
||||
template <typename SampleType>
|
||||
class Filter
|
||||
{
|
||||
public:
|
||||
/** The NumericType is the underlying primitive type used by the SampleType (which
|
||||
could be either a primitive or vector)
|
||||
*/
|
||||
using NumericType = typename SampleTypeHelpers::ElementType<SampleType>::Type;
|
||||
|
||||
//==============================================================================
|
||||
/** This will create a filter which will produce silence. */
|
||||
Filter() : coefficients (new Coefficients<NumericType>) { reset(); }
|
||||
|
||||
/** Creates a filter with a given set of coefficients. */
|
||||
Filter (Coefficients<NumericType>* coefficientsToUse) : coefficients (coefficientsToUse) { reset(); }
|
||||
|
||||
Filter (const Filter&) = default;
|
||||
Filter (Filter&&) = default;
|
||||
Filter& operator= (const Filter&) = default;
|
||||
Filter& operator= (Filter&&) = default;
|
||||
|
||||
//==============================================================================
|
||||
/** Prepare this filter for processing. */
|
||||
inline void prepare (const ProcessSpec& spec) noexcept
|
||||
{
|
||||
// This class can only process mono signals. Use the ProcessorDuplicator class
|
||||
// to apply this filter on a multi-channel audio stream.
|
||||
jassert (spec.numChannels == 1);
|
||||
ignoreUnused (spec);
|
||||
reset();
|
||||
}
|
||||
|
||||
/** 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 disable the filter, call setEnabled (false).
|
||||
*/
|
||||
void reset()
|
||||
{
|
||||
if (coefficients != nullptr)
|
||||
{
|
||||
auto newSize = coefficients->getFilterOrder() + 1;
|
||||
|
||||
if (newSize != size)
|
||||
{
|
||||
memory.malloc (1 + jmax (newSize, size, static_cast<size_t> (128)));
|
||||
|
||||
fifo = snapPointerToAlignment (memory.getData(), sizeof (SampleType));
|
||||
size = newSize;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < size; ++i)
|
||||
fifo[i] = SampleType {0};
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
/** The coefficients of the FIR filter. It's up to the called to ensure that
|
||||
these coefficients are modified in a thread-safe way.
|
||||
|
||||
If you change the order of the coefficients then you must call reset after
|
||||
modifying them.
|
||||
*/
|
||||
typename Coefficients<NumericType>::Ptr coefficients;
|
||||
|
||||
//==============================================================================
|
||||
/** Processes as a block of samples */
|
||||
template <typename ProcessContext>
|
||||
void process (const ProcessContext& context) noexcept
|
||||
{
|
||||
static_assert (std::is_same<typename ProcessContext::SampleType, SampleType>::value,
|
||||
"The sample-type of the FIR filter must match the sample-type supplied to this process callback");
|
||||
check();
|
||||
|
||||
auto&& inputBlock = context.getInputBlock();
|
||||
auto&& outputBlock = context.getOutputBlock();
|
||||
|
||||
// This class can only process mono signals. Use the ProcessorDuplicator class
|
||||
// to apply this filter on a multi-channel audio stream.
|
||||
jassert (inputBlock.getNumChannels() == 1);
|
||||
jassert (outputBlock.getNumChannels() == 1);
|
||||
|
||||
auto numSamples = inputBlock.getNumSamples();
|
||||
auto* src = inputBlock .getChannelPointer (0);
|
||||
auto* dst = outputBlock.getChannelPointer (0);
|
||||
|
||||
auto* fir = coefficients->getRawCoefficients();
|
||||
size_t p = pos;
|
||||
|
||||
if (context.isBypassed)
|
||||
{
|
||||
for (size_t i = 0; i < numSamples; ++i)
|
||||
{
|
||||
fifo[p] = dst[i] = src[i];
|
||||
p = (p == 0 ? size - 1 : p - 1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (size_t i = 0; i < numSamples; ++i)
|
||||
dst[i] = processSingleSample (src[i], fifo, fir, size, p);
|
||||
}
|
||||
|
||||
pos = p;
|
||||
}
|
||||
|
||||
|
||||
/** Processes a single sample, without any locking.
|
||||
Use this if you need processing of a single value.
|
||||
*/
|
||||
SampleType JUCE_VECTOR_CALLTYPE processSample (SampleType sample) noexcept
|
||||
{
|
||||
check();
|
||||
return processSingleSample (sample, fifo, coefficients->getRawCoefficients(), size, pos);
|
||||
}
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
HeapBlock<SampleType> memory;
|
||||
SampleType* fifo = nullptr;
|
||||
size_t pos = 0, size = 0;
|
||||
|
||||
//==============================================================================
|
||||
void check()
|
||||
{
|
||||
jassert (coefficients != nullptr);
|
||||
|
||||
if (size != (coefficients->getFilterOrder() + 1))
|
||||
reset();
|
||||
}
|
||||
|
||||
static SampleType JUCE_VECTOR_CALLTYPE processSingleSample (SampleType sample, SampleType* buf,
|
||||
const NumericType* fir, size_t m, size_t& p) noexcept
|
||||
{
|
||||
SampleType out (0);
|
||||
|
||||
buf[p] = sample;
|
||||
|
||||
size_t k;
|
||||
for (k = 0; k < m - p; ++k)
|
||||
out += buf[(p + k)] * fir[k];
|
||||
|
||||
for (size_t j = 0; j < p; ++j)
|
||||
out += buf[j] * fir[j + k];
|
||||
|
||||
p = (p == 0 ? m - 1 : p - 1);
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
JUCE_LEAK_DETECTOR (Filter)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A set of coefficients for use in an FIRFilter object.
|
||||
|
||||
@see FIRFilter
|
||||
|
||||
@tags{DSP}
|
||||
*/
|
||||
template <typename NumericType>
|
||||
struct Coefficients : public ProcessorState
|
||||
{
|
||||
//==============================================================================
|
||||
/** Creates a null set of coefficients (which will produce silence). */
|
||||
Coefficients() : coefficients ({ NumericType() }) {}
|
||||
|
||||
/** Creates a null set of coefficients of a given size. */
|
||||
Coefficients (size_t size) { coefficients.resize ((int) size); }
|
||||
|
||||
/** Creates a set of coefficients from an array of samples. */
|
||||
Coefficients (const NumericType* samples, size_t numSamples) : coefficients (samples, (int) numSamples) {}
|
||||
|
||||
Coefficients (const Coefficients&) = default;
|
||||
Coefficients (Coefficients&&) = default;
|
||||
Coefficients& operator= (const Coefficients&) = default;
|
||||
Coefficients& operator= (Coefficients&&) = default;
|
||||
|
||||
/** The Coefficients structure is ref-counted, so this is a handy type that can be used
|
||||
as a pointer to one.
|
||||
*/
|
||||
using Ptr = ReferenceCountedObjectPtr<Coefficients>;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the filter order associated with the coefficients. */
|
||||
size_t getFilterOrder() const noexcept { return static_cast<size_t> (coefficients.size()) - 1; }
|
||||
|
||||
/** Returns the magnitude frequency response of the filter for a given frequency
|
||||
and sample rate.
|
||||
*/
|
||||
double getMagnitudeForFrequency (double frequency, double sampleRate) const noexcept;
|
||||
|
||||
/** Returns the magnitude frequency response of the filter for a given frequency array
|
||||
and sample rate.
|
||||
*/
|
||||
void getMagnitudeForFrequencyArray (double* frequencies, double* magnitudes,
|
||||
size_t numSamples, double sampleRate) const noexcept;
|
||||
|
||||
/** Returns the phase frequency response of the filter for a given frequency and
|
||||
sample rate.
|
||||
*/
|
||||
double getPhaseForFrequency (double frequency, double sampleRate) const noexcept;
|
||||
|
||||
/** Returns the phase frequency response of the filter for a given frequency array
|
||||
and sample rate.
|
||||
*/
|
||||
void getPhaseForFrequencyArray (double* frequencies, double* phases,
|
||||
size_t numSamples, double sampleRate) const noexcept;
|
||||
|
||||
/** Returns a raw data pointer to the coefficients. */
|
||||
NumericType* getRawCoefficients() noexcept { return coefficients.getRawDataPointer(); }
|
||||
|
||||
/** Returns a raw data pointer to the coefficients. */
|
||||
const NumericType* getRawCoefficients() const noexcept { return coefficients.begin(); }
|
||||
|
||||
//==============================================================================
|
||||
/** Scales the values of the FIR filter with the sum of the squared coefficients. */
|
||||
void normalise() noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** The raw coefficients.
|
||||
You should leave these numbers alone unless you really know what you're doing.
|
||||
*/
|
||||
Array<NumericType> coefficients;
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace dsp
|
||||
} // namespace juce
|
221
modules/juce_dsp/processors/juce_FIRFilter_test.cpp
Normal file
221
modules/juce_dsp/processors/juce_FIRFilter_test.cpp
Normal file
@ -0,0 +1,221 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
namespace dsp
|
||||
{
|
||||
|
||||
class FIRFilterTest : public UnitTest
|
||||
{
|
||||
template <typename Type>
|
||||
struct Helpers
|
||||
{
|
||||
static void fillRandom (Random& random, Type* buffer, size_t n)
|
||||
{
|
||||
for (size_t i = 0; i < n; ++i)
|
||||
buffer[i] = (2.0f * random.nextFloat()) - 1.0f;
|
||||
}
|
||||
|
||||
static bool checkArrayIsSimilar (Type* a, Type* b, size_t n) noexcept
|
||||
{
|
||||
for (size_t i = 0; i < n; ++i)
|
||||
if (std::abs (a[i] - b[i]) > 1e-6f)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
#if JUCE_USE_SIMD
|
||||
template <typename Type>
|
||||
struct Helpers<SIMDRegister<Type>>
|
||||
{
|
||||
static void fillRandom (Random& random, SIMDRegister<Type>* buffer, size_t n)
|
||||
{
|
||||
Helpers<Type>::fillRandom (random, reinterpret_cast<Type*> (buffer), n * SIMDRegister<Type>::size());
|
||||
}
|
||||
|
||||
static bool checkArrayIsSimilar (SIMDRegister<Type>* a, SIMDRegister<Type>* b, size_t n) noexcept
|
||||
{
|
||||
return Helpers<Type>::checkArrayIsSimilar (reinterpret_cast<Type*> (a),
|
||||
reinterpret_cast<Type*> (b),
|
||||
n * SIMDRegister<Type>::size());
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
template <typename Type>
|
||||
static void fillRandom (Random& random, Type* buffer, size_t n) { Helpers<Type>::fillRandom (random, buffer, n); }
|
||||
|
||||
template <typename Type>
|
||||
static bool checkArrayIsSimilar (Type* a, Type* b, size_t n) noexcept { return Helpers<Type>::checkArrayIsSimilar (a, b, n); }
|
||||
|
||||
//==============================================================================
|
||||
// reference implementation of an FIR
|
||||
template <typename SampleType, typename NumericType>
|
||||
static void reference (const NumericType* firCoefficients, size_t numCoefficients,
|
||||
const SampleType* input, SampleType* output, size_t n) noexcept
|
||||
{
|
||||
if (numCoefficients == 0)
|
||||
{
|
||||
zeromem (output, sizeof (SampleType) * n);
|
||||
return;
|
||||
}
|
||||
|
||||
HeapBlock<SampleType> scratchBuffer (numCoefficients
|
||||
#if JUCE_USE_SIMD
|
||||
+ (SIMDRegister<NumericType>::SIMDRegisterSize / sizeof (SampleType))
|
||||
#endif
|
||||
);
|
||||
#if JUCE_USE_SIMD
|
||||
SampleType* buffer = reinterpret_cast<SampleType*> (SIMDRegister<NumericType>::getNextSIMDAlignedPtr (reinterpret_cast<NumericType*> (scratchBuffer.getData())));
|
||||
#else
|
||||
SampleType* buffer = scratchBuffer.getData();
|
||||
#endif
|
||||
|
||||
zeromem (buffer, sizeof (SampleType) * numCoefficients);
|
||||
|
||||
for (size_t i = 0; i < n; ++i)
|
||||
{
|
||||
for (size_t j = (numCoefficients - 1); j >= 1; --j)
|
||||
buffer[j] = buffer[j-1];
|
||||
|
||||
buffer[0] = input[i];
|
||||
|
||||
SampleType sum (0);
|
||||
|
||||
for (size_t j = 0; j < numCoefficients; ++j)
|
||||
sum += buffer[j] * firCoefficients[j];
|
||||
|
||||
output[i] = sum;
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
struct LargeBlockTest
|
||||
{
|
||||
template <typename FloatType>
|
||||
static void run (FIR::Filter<FloatType>& filter, FloatType* src, FloatType* dst, size_t n)
|
||||
{
|
||||
AudioBlock<FloatType> input (&src, 1, n);
|
||||
AudioBlock<FloatType> output (&dst, 1, n);
|
||||
ProcessContextNonReplacing<FloatType> context (input, output);
|
||||
|
||||
filter.process (context);
|
||||
}
|
||||
};
|
||||
|
||||
struct SampleBySampleTest
|
||||
{
|
||||
template <typename FloatType>
|
||||
static void run (FIR::Filter<FloatType>& filter, FloatType* src, FloatType* dst, size_t n)
|
||||
{
|
||||
for (size_t i = 0; i < n; ++i)
|
||||
dst[i] = filter.processSample (src[i]);
|
||||
}
|
||||
};
|
||||
|
||||
struct SplitBlockTest
|
||||
{
|
||||
template <typename FloatType>
|
||||
static void run (FIR::Filter<FloatType>& filter, FloatType* input, FloatType* output, size_t n)
|
||||
{
|
||||
size_t len = 0;
|
||||
for (size_t i = 0; i < n; i += len)
|
||||
{
|
||||
len = jmin (n - i, n / 3);
|
||||
auto* src = input + i;
|
||||
auto* dst = output + i;
|
||||
|
||||
AudioBlock<FloatType> inBlock (&src, 1, len);
|
||||
AudioBlock<FloatType> outBlock (&dst, 1, len);
|
||||
ProcessContextNonReplacing<FloatType> context (inBlock, outBlock);
|
||||
|
||||
filter.process (context);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
template <typename TheTest, typename SampleType, typename NumericType>
|
||||
void runTestForType()
|
||||
{
|
||||
Random random (8392829);
|
||||
|
||||
for (auto size : {1, 2, 4, 8, 12, 13, 25})
|
||||
{
|
||||
constexpr size_t n = 813;
|
||||
|
||||
HeapBlock<char> inputBuffer, outputBuffer, refBuffer;
|
||||
AudioBlock<SampleType> input (inputBuffer, 1, n), output (outputBuffer, 1, n), ref (refBuffer, 1, n);
|
||||
fillRandom (random, input.getChannelPointer (0), n);
|
||||
|
||||
HeapBlock<char> firBlock;
|
||||
AudioBlock<NumericType> fir (firBlock, 1, static_cast<size_t> (size));
|
||||
fillRandom (random, fir.getChannelPointer (0), static_cast<size_t> (size));
|
||||
|
||||
FIR::Filter<SampleType> filter (new FIR::Coefficients<NumericType> (fir.getChannelPointer (0), static_cast<size_t> (size)));
|
||||
ProcessSpec spec {0.0, n, 1};
|
||||
filter.prepare (spec);
|
||||
|
||||
reference<SampleType, NumericType> (fir.getChannelPointer (0), static_cast<size_t> (size),
|
||||
input.getChannelPointer (0), ref.getChannelPointer (0), n);
|
||||
|
||||
TheTest::template run<SampleType> (filter, input.getChannelPointer (0), output.getChannelPointer (0), n);
|
||||
expect (checkArrayIsSimilar (output.getChannelPointer (0), ref.getChannelPointer (0), n));
|
||||
}
|
||||
}
|
||||
|
||||
template <typename TheTest>
|
||||
void runTestForAllTypes (const char* unitTestName)
|
||||
{
|
||||
beginTest (unitTestName);
|
||||
|
||||
runTestForType<TheTest, float, float>();
|
||||
runTestForType<TheTest, double, double>();
|
||||
#if JUCE_USE_SIMD
|
||||
runTestForType<TheTest, SIMDRegister<float>, float>();
|
||||
runTestForType<TheTest, SIMDRegister<double>, double>();
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
public:
|
||||
FIRFilterTest() : UnitTest ("FIR Filter", "DSP") {}
|
||||
|
||||
void runTest() override
|
||||
{
|
||||
runTestForAllTypes<LargeBlockTest> ("Large Blocks");
|
||||
runTestForAllTypes<SampleBySampleTest> ("Sample by Sample");
|
||||
runTestForAllTypes<SplitBlockTest> ("Split Block");
|
||||
}
|
||||
};
|
||||
|
||||
static FIRFilterTest firFilterUnitTest;
|
||||
|
||||
} // namespace dsp
|
||||
} // namespace juce
|
147
modules/juce_dsp/processors/juce_Gain.h
Normal file
147
modules/juce_dsp/processors/juce_Gain.h
Normal file
@ -0,0 +1,147 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
namespace dsp
|
||||
{
|
||||
|
||||
/**
|
||||
Applies a gain to audio samples as single samples or AudioBlocks.
|
||||
|
||||
@tags{DSP}
|
||||
*/
|
||||
template <typename FloatType>
|
||||
class Gain
|
||||
{
|
||||
public:
|
||||
Gain() noexcept {}
|
||||
|
||||
//==============================================================================
|
||||
/** Applies a new gain as a linear value. */
|
||||
void setGainLinear (FloatType newGain) noexcept { gain.setValue (newGain); }
|
||||
|
||||
/** Applies a new gain as a decibel value. */
|
||||
void setGainDecibels (FloatType newGainDecibels) noexcept { setGainLinear (Decibels::decibelsToGain<FloatType> (newGainDecibels)); }
|
||||
|
||||
/** Returns the current gain as a linear value. */
|
||||
FloatType getGainLinear() const noexcept { return gain.getTargetValue(); }
|
||||
|
||||
/** Returns the current gain in decibels. */
|
||||
FloatType getGainDecibels() const noexcept { return Decibels::gainToDecibels<FloatType> (getGainLinear()); }
|
||||
|
||||
/** Sets the length of the ramp used for smoothing gain changes. */
|
||||
void setRampDurationSeconds (double newDurationSeconds) noexcept
|
||||
{
|
||||
if (rampDurationSeconds != newDurationSeconds)
|
||||
{
|
||||
rampDurationSeconds = newDurationSeconds;
|
||||
reset();
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns the ramp duration in seconds. */
|
||||
double getRampDurationSeconds() const noexcept { return rampDurationSeconds; }
|
||||
|
||||
/** Returns true if the current value is currently being interpolated. */
|
||||
bool isSmoothing() const noexcept { return gain.isSmoothing(); }
|
||||
|
||||
//==============================================================================
|
||||
/** Called before processing starts. */
|
||||
void prepare (const ProcessSpec& spec) noexcept
|
||||
{
|
||||
sampleRate = spec.sampleRate;
|
||||
reset();
|
||||
}
|
||||
|
||||
/** Resets the internal state of the gain */
|
||||
void reset() noexcept
|
||||
{
|
||||
if (sampleRate > 0)
|
||||
gain.reset (sampleRate, rampDurationSeconds);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the result of processing a single sample. */
|
||||
template <typename SampleType>
|
||||
SampleType JUCE_VECTOR_CALLTYPE processSample (SampleType s) noexcept
|
||||
{
|
||||
return s * gain.getNextValue();
|
||||
}
|
||||
|
||||
/** Processes the input and output buffers supplied in the processing context. */
|
||||
template <typename ProcessContext>
|
||||
void process (const ProcessContext& context) noexcept
|
||||
{
|
||||
auto&& inBlock = context.getInputBlock();
|
||||
auto&& outBlock = context.getOutputBlock();
|
||||
|
||||
jassert (inBlock.getNumChannels() == outBlock.getNumChannels());
|
||||
jassert (inBlock.getNumSamples() == outBlock.getNumSamples());
|
||||
|
||||
auto len = inBlock.getNumSamples();
|
||||
auto numChannels = inBlock.getNumChannels();
|
||||
|
||||
if (context.isBypassed)
|
||||
{
|
||||
gain.skip (static_cast<int> (len));
|
||||
|
||||
if (context.usesSeparateInputAndOutputBlocks())
|
||||
outBlock.copy (inBlock);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (numChannels == 1)
|
||||
{
|
||||
auto* src = inBlock.getChannelPointer (0);
|
||||
auto* dst = outBlock.getChannelPointer (0);
|
||||
|
||||
for (size_t i = 0; i < len; ++i)
|
||||
dst[i] = src[i] * gain.getNextValue();
|
||||
}
|
||||
else
|
||||
{
|
||||
auto* gains = static_cast<FloatType*> (alloca (sizeof (FloatType) * len));
|
||||
|
||||
for (size_t i = 0; i < len; ++i)
|
||||
gains[i] = gain.getNextValue();
|
||||
|
||||
for (size_t chan = 0; chan < numChannels; ++chan)
|
||||
FloatVectorOperations::multiply (outBlock.getChannelPointer (chan),
|
||||
inBlock.getChannelPointer (chan),
|
||||
gains, static_cast<int> (len));
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
LinearSmoothedValue<FloatType> gain;
|
||||
double sampleRate = 0, rampDurationSeconds = 0;
|
||||
};
|
||||
|
||||
} // namespace dsp
|
||||
} // namespace juce
|
481
modules/juce_dsp/processors/juce_IIRFilter.cpp
Normal file
481
modules/juce_dsp/processors/juce_IIRFilter.cpp
Normal file
@ -0,0 +1,481 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
namespace dsp
|
||||
{
|
||||
|
||||
template <typename NumericType>
|
||||
IIR::Coefficients<NumericType>::Coefficients()
|
||||
: coefficients ({ NumericType(),
|
||||
NumericType(),
|
||||
NumericType(),
|
||||
NumericType(),
|
||||
NumericType() })
|
||||
{
|
||||
}
|
||||
|
||||
template <typename NumericType>
|
||||
IIR::Coefficients<NumericType>::Coefficients (NumericType b0, NumericType b1,
|
||||
NumericType a0, NumericType a1)
|
||||
{
|
||||
jassert (a0 != 0);
|
||||
|
||||
coefficients.clear();
|
||||
|
||||
auto a0inv = static_cast<NumericType> (1) / a0;
|
||||
|
||||
coefficients.add (b0 * a0inv,
|
||||
b1 * a0inv,
|
||||
a1 * a0inv);
|
||||
}
|
||||
|
||||
template <typename NumericType>
|
||||
IIR::Coefficients<NumericType>::Coefficients (NumericType b0, NumericType b1, NumericType b2,
|
||||
NumericType a0, NumericType a1, NumericType a2)
|
||||
{
|
||||
jassert (a0 != 0);
|
||||
|
||||
coefficients.clear();
|
||||
|
||||
auto a0inv = static_cast<NumericType> (1) / a0;
|
||||
|
||||
coefficients.add (b0 * a0inv,
|
||||
b1 * a0inv,
|
||||
b2 * a0inv,
|
||||
a1 * a0inv,
|
||||
a2 * a0inv);
|
||||
}
|
||||
|
||||
template <typename NumericType>
|
||||
IIR::Coefficients<NumericType>::Coefficients (NumericType b0, NumericType b1, NumericType b2, NumericType b3,
|
||||
NumericType a0, NumericType a1, NumericType a2, NumericType a3)
|
||||
{
|
||||
jassert (a0 != 0);
|
||||
|
||||
coefficients.clear();
|
||||
|
||||
auto a0inv = static_cast<NumericType> (1) / a0;
|
||||
|
||||
coefficients.add (b0 * a0inv,
|
||||
b1 * a0inv,
|
||||
b2 * a0inv,
|
||||
b3 * a0inv,
|
||||
a1 * a0inv,
|
||||
a2 * a0inv,
|
||||
a3 * a0inv);
|
||||
}
|
||||
|
||||
template <typename NumericType>
|
||||
typename IIR::Coefficients<NumericType>::Ptr IIR::Coefficients<NumericType>::makeFirstOrderLowPass (double sampleRate,
|
||||
NumericType frequency)
|
||||
{
|
||||
jassert (sampleRate > 0.0);
|
||||
jassert (frequency > 0 && frequency <= static_cast<float> (sampleRate * 0.5));
|
||||
|
||||
auto n = std::tan (MathConstants<NumericType>::pi * frequency / static_cast<NumericType> (sampleRate));
|
||||
|
||||
return new Coefficients (n, n, n + 1, n - 1);
|
||||
}
|
||||
|
||||
template <typename NumericType>
|
||||
typename IIR::Coefficients<NumericType>::Ptr IIR::Coefficients<NumericType>::makeFirstOrderHighPass (double sampleRate,
|
||||
NumericType frequency)
|
||||
{
|
||||
jassert (sampleRate > 0.0);
|
||||
jassert (frequency > 0 && frequency <= static_cast<float> (sampleRate * 0.5));
|
||||
|
||||
auto n = std::tan (MathConstants<NumericType>::pi * frequency / static_cast<NumericType> (sampleRate));
|
||||
|
||||
return new Coefficients (1, -1, n + 1, n - 1);
|
||||
}
|
||||
|
||||
template <typename NumericType>
|
||||
typename IIR::Coefficients<NumericType>::Ptr IIR::Coefficients<NumericType>::makeFirstOrderAllPass (double sampleRate,
|
||||
NumericType frequency)
|
||||
{
|
||||
jassert (sampleRate > 0.0);
|
||||
jassert (frequency > 0 && frequency <= static_cast<float> (sampleRate * 0.5));
|
||||
|
||||
auto n = std::tan (MathConstants<NumericType>::pi * frequency / static_cast<NumericType> (sampleRate));
|
||||
|
||||
return new Coefficients (n - 1, n + 1, n + 1, n - 1);
|
||||
}
|
||||
|
||||
template <typename NumericType>
|
||||
typename IIR::Coefficients<NumericType>::Ptr IIR::Coefficients<NumericType>::makeLowPass (double sampleRate,
|
||||
NumericType frequency)
|
||||
{
|
||||
return makeLowPass (sampleRate, frequency, inverseRootTwo);
|
||||
}
|
||||
|
||||
template <typename NumericType>
|
||||
typename IIR::Coefficients<NumericType>::Ptr IIR::Coefficients<NumericType>::makeLowPass (double sampleRate,
|
||||
NumericType frequency,
|
||||
NumericType Q)
|
||||
{
|
||||
jassert (sampleRate > 0.0);
|
||||
jassert (frequency > 0 && frequency <= static_cast<float> (sampleRate * 0.5));
|
||||
jassert (Q > 0.0);
|
||||
|
||||
auto n = 1 / std::tan (MathConstants<NumericType>::pi * frequency / static_cast<NumericType> (sampleRate));
|
||||
auto nSquared = n * n;
|
||||
auto invQ = 1 / Q;
|
||||
auto c1 = 1 / (1 + invQ * n + nSquared);
|
||||
|
||||
return new Coefficients (c1, c1 * 2, c1,
|
||||
1, c1 * 2 * (1 - nSquared),
|
||||
c1 * (1 - invQ * n + nSquared));
|
||||
}
|
||||
|
||||
template <typename NumericType>
|
||||
typename IIR::Coefficients<NumericType>::Ptr IIR::Coefficients<NumericType>::makeHighPass (double sampleRate,
|
||||
NumericType frequency)
|
||||
{
|
||||
return makeHighPass (sampleRate, frequency, inverseRootTwo);
|
||||
}
|
||||
|
||||
template <typename NumericType>
|
||||
typename IIR::Coefficients<NumericType>::Ptr IIR::Coefficients<NumericType>::makeHighPass (double sampleRate,
|
||||
NumericType frequency,
|
||||
NumericType Q)
|
||||
{
|
||||
jassert (sampleRate > 0.0);
|
||||
jassert (frequency > 0 && frequency <= static_cast<float> (sampleRate * 0.5));
|
||||
jassert (Q > 0.0);
|
||||
|
||||
auto n = std::tan (MathConstants<NumericType>::pi * frequency / static_cast<NumericType> (sampleRate));
|
||||
auto nSquared = n * n;
|
||||
auto invQ = 1 / Q;
|
||||
auto c1 = 1 / (1 + invQ * n + nSquared);
|
||||
|
||||
return new Coefficients (c1, c1 * -2, c1,
|
||||
1, c1 * 2 * (nSquared - 1),
|
||||
c1 * (1 - invQ * n + nSquared));
|
||||
}
|
||||
|
||||
template <typename NumericType>
|
||||
typename IIR::Coefficients<NumericType>::Ptr IIR::Coefficients<NumericType>::makeBandPass (double sampleRate,
|
||||
NumericType frequency)
|
||||
{
|
||||
return makeBandPass (sampleRate, frequency, inverseRootTwo);
|
||||
}
|
||||
|
||||
template <typename NumericType>
|
||||
typename IIR::Coefficients<NumericType>::Ptr IIR::Coefficients<NumericType>::makeBandPass (double sampleRate,
|
||||
NumericType frequency,
|
||||
NumericType Q)
|
||||
{
|
||||
jassert (sampleRate > 0.0);
|
||||
jassert (frequency > 0 && frequency <= static_cast<float> (sampleRate * 0.5));
|
||||
jassert (Q > 0.0);
|
||||
|
||||
auto n = 1 / std::tan (MathConstants<NumericType>::pi * frequency / static_cast<NumericType> (sampleRate));
|
||||
auto nSquared = n * n;
|
||||
auto invQ = 1 / Q;
|
||||
auto c1 = 1 / (1 + invQ * n + nSquared);
|
||||
|
||||
return new Coefficients (c1 * n * invQ, 0,
|
||||
-c1 * n * invQ, 1,
|
||||
c1 * 2 * (1 - nSquared),
|
||||
c1 * (1 - invQ * n + nSquared));
|
||||
}
|
||||
|
||||
template <typename NumericType>
|
||||
typename IIR::Coefficients<NumericType>::Ptr IIR::Coefficients<NumericType>::makeNotch (double sampleRate,
|
||||
NumericType frequency)
|
||||
{
|
||||
return makeNotch (sampleRate, frequency, inverseRootTwo);
|
||||
}
|
||||
|
||||
template <typename NumericType>
|
||||
typename IIR::Coefficients<NumericType>::Ptr IIR::Coefficients<NumericType>::makeNotch (double sampleRate,
|
||||
NumericType frequency,
|
||||
NumericType Q)
|
||||
{
|
||||
jassert (sampleRate > 0.0);
|
||||
jassert (frequency > 0 && frequency <= static_cast<float> (sampleRate * 0.5));
|
||||
jassert (Q > 0.0);
|
||||
|
||||
auto n = 1 / std::tan (MathConstants<NumericType>::pi * frequency / static_cast<NumericType> (sampleRate));
|
||||
auto nSquared = n * n;
|
||||
auto invQ = 1 / Q;
|
||||
auto c1 = 1 / (1 + n * invQ + nSquared);
|
||||
auto b0 = c1 * (1 + nSquared);
|
||||
auto b1 = 2 * c1 * (1 - nSquared);
|
||||
|
||||
return new Coefficients (b0, b1, b0, 1, b1, c1 * (1 - n * invQ + nSquared));
|
||||
}
|
||||
|
||||
template <typename NumericType>
|
||||
typename IIR::Coefficients<NumericType>::Ptr IIR::Coefficients<NumericType>::makeAllPass (double sampleRate,
|
||||
NumericType frequency)
|
||||
{
|
||||
return makeAllPass (sampleRate, frequency, inverseRootTwo);
|
||||
}
|
||||
|
||||
template <typename NumericType>
|
||||
typename IIR::Coefficients<NumericType>::Ptr IIR::Coefficients<NumericType>::makeAllPass (double sampleRate,
|
||||
NumericType frequency,
|
||||
NumericType Q)
|
||||
{
|
||||
jassert (sampleRate > 0);
|
||||
jassert (frequency > 0 && frequency <= sampleRate * 0.5);
|
||||
jassert (Q > 0);
|
||||
|
||||
auto n = 1 / std::tan (MathConstants<NumericType>::pi * frequency / static_cast<NumericType> (sampleRate));
|
||||
auto nSquared = n * n;
|
||||
auto invQ = 1 / Q;
|
||||
auto c1 = 1 / (1 + invQ * n + nSquared);
|
||||
auto b0 = c1 * (1 - n * invQ + nSquared);
|
||||
auto b1 = c1 * 2 * (1 - nSquared);
|
||||
|
||||
return new Coefficients (b0, b1, 1, 1, b1, b0);
|
||||
}
|
||||
|
||||
template <typename NumericType>
|
||||
typename IIR::Coefficients<NumericType>::Ptr IIR::Coefficients<NumericType>::makeLowShelf (double sampleRate,
|
||||
NumericType cutOffFrequency,
|
||||
NumericType Q,
|
||||
NumericType gainFactor)
|
||||
{
|
||||
jassert (sampleRate > 0.0);
|
||||
jassert (cutOffFrequency > 0.0 && cutOffFrequency <= sampleRate * 0.5);
|
||||
jassert (Q > 0.0);
|
||||
|
||||
auto A = jmax (static_cast<NumericType> (0.0), std::sqrt (gainFactor));
|
||||
auto aminus1 = A - 1;
|
||||
auto aplus1 = A + 1;
|
||||
auto omega = (2 * MathConstants<NumericType>::pi * jmax (cutOffFrequency, static_cast<NumericType> (2.0))) / static_cast<NumericType> (sampleRate);
|
||||
auto coso = std::cos (omega);
|
||||
auto beta = std::sin (omega) * std::sqrt (A) / Q;
|
||||
auto aminus1TimesCoso = aminus1 * coso;
|
||||
|
||||
return new Coefficients (A * (aplus1 - aminus1TimesCoso + beta),
|
||||
A * 2 * (aminus1 - aplus1 * coso),
|
||||
A * (aplus1 - aminus1TimesCoso - beta),
|
||||
aplus1 + aminus1TimesCoso + beta,
|
||||
-2 * (aminus1 + aplus1 * coso),
|
||||
aplus1 + aminus1TimesCoso - beta);
|
||||
}
|
||||
|
||||
template <typename NumericType>
|
||||
typename IIR::Coefficients<NumericType>::Ptr IIR::Coefficients<NumericType>::makeHighShelf (double sampleRate,
|
||||
NumericType cutOffFrequency,
|
||||
NumericType Q,
|
||||
NumericType gainFactor)
|
||||
{
|
||||
jassert (sampleRate > 0);
|
||||
jassert (cutOffFrequency > 0 && cutOffFrequency <= static_cast<NumericType> (sampleRate * 0.5));
|
||||
jassert (Q > 0);
|
||||
|
||||
auto A = jmax (static_cast<NumericType> (0.0), std::sqrt (gainFactor));
|
||||
auto aminus1 = A - 1;
|
||||
auto aplus1 = A + 1;
|
||||
auto omega = (2 * MathConstants<NumericType>::pi * jmax (cutOffFrequency, static_cast<NumericType> (2.0))) / static_cast<NumericType> (sampleRate);
|
||||
auto coso = std::cos (omega);
|
||||
auto beta = std::sin (omega) * std::sqrt (A) / Q;
|
||||
auto aminus1TimesCoso = aminus1 * coso;
|
||||
|
||||
return new Coefficients (A * (aplus1 + aminus1TimesCoso + beta),
|
||||
A * -2 * (aminus1 + aplus1 * coso),
|
||||
A * (aplus1 + aminus1TimesCoso - beta),
|
||||
aplus1 - aminus1TimesCoso + beta,
|
||||
2 * (aminus1 - aplus1 * coso),
|
||||
aplus1 - aminus1TimesCoso - beta);
|
||||
}
|
||||
|
||||
template <typename NumericType>
|
||||
typename IIR::Coefficients<NumericType>::Ptr IIR::Coefficients<NumericType>::makePeakFilter (double sampleRate,
|
||||
NumericType frequency,
|
||||
NumericType Q,
|
||||
NumericType gainFactor)
|
||||
{
|
||||
jassert (sampleRate > 0);
|
||||
jassert (frequency > 0 && frequency <= static_cast<NumericType> (sampleRate * 0.5));
|
||||
jassert (Q > 0);
|
||||
jassert (gainFactor > 0);
|
||||
|
||||
auto A = jmax (static_cast<NumericType> (0.0), std::sqrt (gainFactor));
|
||||
auto omega = (2 * MathConstants<NumericType>::pi * jmax (frequency, static_cast<NumericType> (2.0))) / static_cast<NumericType> (sampleRate);
|
||||
auto alpha = std::sin (omega) / (Q * 2);
|
||||
auto c2 = -2 * std::cos (omega);
|
||||
auto alphaTimesA = alpha * A;
|
||||
auto alphaOverA = alpha / A;
|
||||
|
||||
return new Coefficients (1 + alphaTimesA, c2,
|
||||
1 - alphaTimesA,
|
||||
1 + alphaOverA, c2,
|
||||
1 - alphaOverA);
|
||||
}
|
||||
|
||||
template <typename NumericType>
|
||||
size_t IIR::Coefficients<NumericType>::getFilterOrder() const noexcept
|
||||
{
|
||||
return (static_cast<size_t> (coefficients.size()) - 1) / 2;
|
||||
}
|
||||
|
||||
template <typename NumericType>
|
||||
double IIR::Coefficients<NumericType>::getMagnitudeForFrequency (double frequency, double sampleRate) const noexcept
|
||||
{
|
||||
constexpr Complex<double> j (0, 1);
|
||||
const auto order = getFilterOrder();
|
||||
const auto* coefs = coefficients.begin();
|
||||
|
||||
jassert (frequency >= 0 && frequency <= sampleRate * 0.5);
|
||||
|
||||
Complex<double> numerator = 0.0, denominator = 0.0, factor = 1.0;
|
||||
Complex<double> jw = std::exp (-MathConstants<double>::twoPi * frequency * j / sampleRate);
|
||||
|
||||
for (size_t n = 0; n <= order; ++n)
|
||||
{
|
||||
numerator += static_cast<double> (coefs[n]) * factor;
|
||||
factor *= jw;
|
||||
}
|
||||
|
||||
denominator = 1.0;
|
||||
factor = jw;
|
||||
|
||||
for (size_t n = order + 1; n <= 2 * order; ++n)
|
||||
{
|
||||
denominator += static_cast<double> (coefs[n]) * factor;
|
||||
factor *= jw;
|
||||
}
|
||||
|
||||
return std::abs (numerator / denominator);
|
||||
}
|
||||
|
||||
template <typename NumericType>
|
||||
void IIR::Coefficients<NumericType>::getMagnitudeForFrequencyArray (const double* frequencies, double* magnitudes,
|
||||
size_t numSamples, double sampleRate) const noexcept
|
||||
{
|
||||
constexpr Complex<double> j (0, 1);
|
||||
const auto order = getFilterOrder();
|
||||
const auto* coefs = coefficients.begin();
|
||||
|
||||
jassert (order >= 0);
|
||||
|
||||
for (size_t i = 0; i < numSamples; ++i)
|
||||
{
|
||||
jassert (frequencies[i] >= 0 && frequencies[i] <= sampleRate * 0.5);
|
||||
|
||||
Complex<double> numerator = 0.0, denominator = 0.0, factor = 1.0;
|
||||
Complex<double> jw = std::exp (-MathConstants<double>::twoPi * frequencies[i] * j / sampleRate);
|
||||
|
||||
for (size_t n = 0; n <= order; ++n)
|
||||
{
|
||||
numerator += static_cast<double> (coefs[n]) * factor;
|
||||
factor *= jw;
|
||||
}
|
||||
|
||||
denominator = 1.0;
|
||||
factor = jw;
|
||||
|
||||
for (size_t n = order + 1; n <= 2 * order; ++n)
|
||||
{
|
||||
denominator += static_cast<double> (coefs[n]) * factor;
|
||||
factor *= jw;
|
||||
}
|
||||
|
||||
magnitudes[i] = std::abs(numerator / denominator);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename NumericType>
|
||||
double IIR::Coefficients<NumericType>::getPhaseForFrequency (double frequency, double sampleRate) const noexcept
|
||||
{
|
||||
constexpr Complex<double> j (0, 1);
|
||||
const auto order = getFilterOrder();
|
||||
const auto* coefs = coefficients.begin();
|
||||
|
||||
jassert (frequency >= 0 && frequency <= sampleRate * 0.5);
|
||||
|
||||
Complex<double> numerator = 0.0, denominator = 0.0, factor = 1.0;
|
||||
Complex<double> jw = std::exp (-MathConstants<double>::twoPi * frequency * j / sampleRate);
|
||||
|
||||
for (size_t n = 0; n <= order; ++n)
|
||||
{
|
||||
numerator += static_cast<double> (coefs[n]) * factor;
|
||||
factor *= jw;
|
||||
}
|
||||
|
||||
denominator = 1.0;
|
||||
factor = jw;
|
||||
|
||||
for (size_t n = order + 1; n <= 2 * order; ++n)
|
||||
{
|
||||
denominator += static_cast<double> (coefs[n]) * factor;
|
||||
factor *= jw;
|
||||
}
|
||||
|
||||
return std::arg (numerator / denominator);
|
||||
}
|
||||
|
||||
template <typename NumericType>
|
||||
void IIR::Coefficients<NumericType>::getPhaseForFrequencyArray (double* frequencies, double* phases,
|
||||
size_t numSamples, double sampleRate) const noexcept
|
||||
{
|
||||
jassert (sampleRate > 0);
|
||||
|
||||
constexpr Complex<double> j (0, 1);
|
||||
const auto order = getFilterOrder();
|
||||
const auto* coefs = coefficients.begin();
|
||||
auto invSampleRate = 1 / sampleRate;
|
||||
|
||||
jassert (order >= 0);
|
||||
|
||||
for (size_t i = 0; i < numSamples; ++i)
|
||||
{
|
||||
jassert (frequencies[i] >= 0 && frequencies[i] <= sampleRate * 0.5);
|
||||
|
||||
Complex<double> numerator = 0.0, denominator = 0.0, factor = 1.0;
|
||||
Complex<double> jw = std::exp (-MathConstants<double>::twoPi * frequencies[i] * j * invSampleRate);
|
||||
|
||||
for (size_t n = 0; n <= order; ++n)
|
||||
{
|
||||
numerator += static_cast<double> (coefs[n]) * factor;
|
||||
factor *= jw;
|
||||
}
|
||||
|
||||
denominator = 1.0;
|
||||
factor = jw;
|
||||
|
||||
for (size_t n = order + 1; n <= 2 * order; ++n)
|
||||
{
|
||||
denominator += static_cast<double> (coefs[n]) * factor;
|
||||
factor *= jw;
|
||||
}
|
||||
|
||||
phases[i] = std::arg (numerator / denominator);
|
||||
}
|
||||
}
|
||||
|
||||
template struct IIR::Coefficients<float>;
|
||||
template struct IIR::Coefficients<double>;
|
||||
|
||||
} // namespace dsp
|
||||
} // namespace juce
|
303
modules/juce_dsp/processors/juce_IIRFilter.h
Normal file
303
modules/juce_dsp/processors/juce_IIRFilter.h
Normal file
@ -0,0 +1,303 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
namespace dsp
|
||||
{
|
||||
|
||||
/**
|
||||
Classes for IIR filter processing.
|
||||
*/
|
||||
namespace IIR
|
||||
{
|
||||
template <typename NumericType>
|
||||
struct Coefficients;
|
||||
|
||||
/**
|
||||
A processing class that can perform IIR filtering on an audio signal, using
|
||||
the Transposed Direct Form II digital structure.
|
||||
|
||||
If you need a lowpass, bandpass or highpass filter with fast modulation of
|
||||
its cutoff frequency, you might use the class StateVariableFilter instead,
|
||||
which is designed to prevent artefacts at parameter changes, instead of the
|
||||
class Filter.
|
||||
|
||||
@see Filter::Coefficients, FilterAudioSource, StateVariableFilter
|
||||
|
||||
@tags{DSP}
|
||||
*/
|
||||
template <typename SampleType>
|
||||
class Filter
|
||||
{
|
||||
public:
|
||||
/** The NumericType is the underlying primitive type used by the SampleType (which
|
||||
could be either a primitive or vector)
|
||||
*/
|
||||
using NumericType = typename SampleTypeHelpers::ElementType<SampleType>::Type;
|
||||
|
||||
//==============================================================================
|
||||
/** 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.
|
||||
*/
|
||||
Filter();
|
||||
|
||||
/** Creates a filter with a given set of coefficients. */
|
||||
Filter (Coefficients<NumericType>* coefficientsToUse);
|
||||
|
||||
Filter (const Filter&) = default;
|
||||
Filter (Filter&&) = default;
|
||||
Filter& operator= (const Filter&) = default;
|
||||
Filter& operator= (Filter&&) = default;
|
||||
|
||||
//==============================================================================
|
||||
/** The coefficients of the IIR filter. It's up to the called to ensure that
|
||||
these coefficients are modified in a thread-safe way.
|
||||
|
||||
If you change the order of the coefficients then you must call reset after
|
||||
modifying them.
|
||||
*/
|
||||
typename Coefficients<NumericType>::Ptr 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.
|
||||
*/
|
||||
void reset() { reset (SampleType {0}); }
|
||||
|
||||
/** Resets the filter's processing pipeline to a specific value.
|
||||
@see reset
|
||||
*/
|
||||
void reset (SampleType resetToValue);
|
||||
|
||||
//==============================================================================
|
||||
/** Called before processing starts. */
|
||||
void prepare (const ProcessSpec&) noexcept;
|
||||
|
||||
/** Processes as a block of samples */
|
||||
template <typename ProcessContext>
|
||||
void process (const ProcessContext& context) noexcept
|
||||
{
|
||||
if (context.isBypassed)
|
||||
processInternal<ProcessContext, true> (context);
|
||||
else
|
||||
processInternal<ProcessContext, false> (context);
|
||||
}
|
||||
|
||||
/** Processes a single sample, without any locking.
|
||||
|
||||
Use this if you need processing of a single value.
|
||||
|
||||
Moreover, you might need the function snapToZero after a few calls to avoid
|
||||
potential denormalisation issues.
|
||||
*/
|
||||
SampleType JUCE_VECTOR_CALLTYPE processSample (SampleType sample) noexcept;
|
||||
|
||||
/** Ensure that the state variables are rounded to zero if the state
|
||||
variables are denormals. This is only needed if you are doing
|
||||
sample by sample processing.
|
||||
*/
|
||||
void snapToZero() noexcept;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
void check();
|
||||
|
||||
/** Processes as a block of samples */
|
||||
template <typename ProcessContext, bool isBypassed>
|
||||
void processInternal (const ProcessContext& context) noexcept;
|
||||
|
||||
//==============================================================================
|
||||
HeapBlock<SampleType> memory;
|
||||
SampleType* state = nullptr;
|
||||
size_t order = 0;
|
||||
|
||||
JUCE_LEAK_DETECTOR (Filter)
|
||||
};
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/** A set of coefficients for use in an Filter object.
|
||||
@see IIR::Filter
|
||||
|
||||
@tags{DSP}
|
||||
*/
|
||||
template <typename NumericType>
|
||||
struct Coefficients : public ProcessorState
|
||||
{
|
||||
/** Creates a null set of coefficients (which will produce silence). */
|
||||
Coefficients();
|
||||
|
||||
/** 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!
|
||||
*/
|
||||
Coefficients (NumericType b0, NumericType b1,
|
||||
NumericType a0, NumericType a1);
|
||||
|
||||
Coefficients (NumericType b0, NumericType b1, NumericType b2,
|
||||
NumericType a0, NumericType a1, NumericType a2);
|
||||
|
||||
Coefficients (NumericType b0, NumericType b1, NumericType b2, NumericType b3,
|
||||
NumericType a0, NumericType a1, NumericType a2, NumericType a3);
|
||||
|
||||
Coefficients (const Coefficients&) = default;
|
||||
Coefficients (Coefficients&&) = default;
|
||||
Coefficients& operator= (const Coefficients&) = default;
|
||||
Coefficients& operator= (Coefficients&&) = default;
|
||||
|
||||
/** The Coefficients structure is ref-counted, so this is a handy type that can be used
|
||||
as a pointer to one.
|
||||
*/
|
||||
using Ptr = ReferenceCountedObjectPtr<Coefficients>;
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the coefficients for a first order low-pass filter. */
|
||||
static Ptr makeFirstOrderLowPass (double sampleRate, NumericType frequency);
|
||||
|
||||
/** Returns the coefficients for a first order high-pass filter. */
|
||||
static Ptr makeFirstOrderHighPass (double sampleRate, NumericType frequency);
|
||||
|
||||
/** Returns the coefficients for a first order all-pass filter. */
|
||||
static Ptr makeFirstOrderAllPass (double sampleRate, NumericType frequency);
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the coefficients for a low-pass filter. */
|
||||
static Ptr makeLowPass (double sampleRate, NumericType frequency);
|
||||
|
||||
/** Returns the coefficients for a low-pass filter with variable Q. */
|
||||
static Ptr makeLowPass (double sampleRate, NumericType frequency, NumericType Q);
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the coefficients for a high-pass filter. */
|
||||
static Ptr makeHighPass (double sampleRate, NumericType frequency);
|
||||
|
||||
/** Returns the coefficients for a high-pass filter with variable Q. */
|
||||
static Ptr makeHighPass (double sampleRate, NumericType frequency, NumericType Q);
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the coefficients for a band-pass filter. */
|
||||
static Ptr makeBandPass (double sampleRate, NumericType frequency);
|
||||
|
||||
/** Returns the coefficients for a band-pass filter with variable Q. */
|
||||
static Ptr makeBandPass (double sampleRate, NumericType frequency, NumericType Q);
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the coefficients for a notch filter. */
|
||||
static Ptr makeNotch (double sampleRate, NumericType frequency);
|
||||
|
||||
/** Returns the coefficients for a notch filter with variable Q. */
|
||||
static Ptr makeNotch (double sampleRate, NumericType frequency, NumericType Q);
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the coefficients for an all-pass filter. */
|
||||
static Ptr makeAllPass (double sampleRate, NumericType frequency);
|
||||
|
||||
/** Returns the coefficients for an all-pass filter with variable Q. */
|
||||
static Ptr makeAllPass (double sampleRate, NumericType frequency, NumericType Q);
|
||||
|
||||
//==============================================================================
|
||||
/** 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 Ptr makeLowShelf (double sampleRate, NumericType cutOffFrequency,
|
||||
NumericType Q, NumericType gainFactor);
|
||||
|
||||
/** 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 Ptr makeHighShelf (double sampleRate, NumericType cutOffFrequency,
|
||||
NumericType Q, NumericType gainFactor);
|
||||
|
||||
/** 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 Ptr makePeakFilter (double sampleRate, NumericType centreFrequency,
|
||||
NumericType Q, NumericType gainFactor);
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the filter order associated with the coefficients */
|
||||
size_t getFilterOrder() const noexcept;
|
||||
|
||||
/** Returns the magnitude frequency response of the filter for a given frequency
|
||||
and sample rate
|
||||
*/
|
||||
double getMagnitudeForFrequency (double frequency, double sampleRate) const noexcept;
|
||||
|
||||
/** Returns the magnitude frequency response of the filter for a given frequency array
|
||||
and sample rate.
|
||||
*/
|
||||
void getMagnitudeForFrequencyArray (const double* frequencies, double* magnitudes,
|
||||
size_t numSamples, double sampleRate) const noexcept;
|
||||
|
||||
/** Returns the phase frequency response of the filter for a given frequency and
|
||||
sample rate
|
||||
*/
|
||||
double getPhaseForFrequency (double frequency, double sampleRate) const noexcept;
|
||||
|
||||
/** Returns the phase frequency response of the filter for a given frequency array
|
||||
and sample rate.
|
||||
*/
|
||||
void getPhaseForFrequencyArray (double* frequencies, double* phases,
|
||||
size_t numSamples, double sampleRate) const noexcept;
|
||||
|
||||
/** Returns a raw data pointer to the coefficients. */
|
||||
NumericType* getRawCoefficients() noexcept { return coefficients.getRawDataPointer(); }
|
||||
|
||||
/** Returns a raw data pointer to the coefficients. */
|
||||
const NumericType* getRawCoefficients() const noexcept { return coefficients.begin(); }
|
||||
|
||||
//==============================================================================
|
||||
/** The raw coefficients.
|
||||
You should leave these numbers alone unless you really know what you're doing.
|
||||
*/
|
||||
Array<NumericType> coefficients;
|
||||
|
||||
private:
|
||||
// Unfortunately, std::sqrt is not marked as constexpr just yet in all compilers
|
||||
static constexpr NumericType inverseRootTwo = static_cast<NumericType> (0.70710678118654752440L);
|
||||
};
|
||||
|
||||
} // namespace IIR
|
||||
} // namespace dsp
|
||||
} // namespace juce
|
||||
|
||||
#include "juce_IIRFilter_Impl.h"
|
233
modules/juce_dsp/processors/juce_IIRFilter_Impl.h
Normal file
233
modules/juce_dsp/processors/juce_IIRFilter_Impl.h
Normal file
@ -0,0 +1,233 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
namespace dsp
|
||||
{
|
||||
namespace IIR
|
||||
{
|
||||
|
||||
#ifndef DOXYGEN
|
||||
|
||||
//==============================================================================
|
||||
template <typename SampleType>
|
||||
Filter<SampleType>::Filter()
|
||||
: coefficients (new Coefficients<typename Filter<SampleType>::NumericType> (1, 0, 1, 0))
|
||||
{
|
||||
reset();
|
||||
}
|
||||
|
||||
template <typename SampleType>
|
||||
Filter<SampleType>::Filter (Coefficients<typename Filter<SampleType>::NumericType>* c)
|
||||
: coefficients (c)
|
||||
{
|
||||
reset();
|
||||
}
|
||||
|
||||
template <typename SampleType>
|
||||
void Filter<SampleType>::reset (SampleType resetToValue)
|
||||
{
|
||||
auto newOrder = coefficients->getFilterOrder();
|
||||
|
||||
if (newOrder != order)
|
||||
{
|
||||
memory.malloc (jmax (order, newOrder, static_cast<size_t> (3)) + 1);
|
||||
state = snapPointerToAlignment (memory.getData(), sizeof (SampleType));
|
||||
order = newOrder;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < order; ++i)
|
||||
state[i] = resetToValue;
|
||||
}
|
||||
|
||||
template <typename SampleType>
|
||||
void Filter<SampleType>::prepare (const ProcessSpec&) noexcept { reset(); }
|
||||
|
||||
|
||||
template <typename SampleType>
|
||||
template <typename ProcessContext, bool bypassed>
|
||||
void Filter<SampleType>::processInternal (const ProcessContext& context) noexcept
|
||||
{
|
||||
static_assert (std::is_same<typename ProcessContext::SampleType, SampleType>::value,
|
||||
"The sample-type of the IIR filter must match the sample-type supplied to this process callback");
|
||||
check();
|
||||
|
||||
auto&& inputBlock = context.getInputBlock();
|
||||
auto&& outputBlock = context.getOutputBlock();
|
||||
|
||||
// This class can only process mono signals. Use the ProcessorDuplicator class
|
||||
// to apply this filter on a multi-channel audio stream.
|
||||
jassert (inputBlock.getNumChannels() == 1);
|
||||
jassert (outputBlock.getNumChannels() == 1);
|
||||
|
||||
auto numSamples = inputBlock.getNumSamples();
|
||||
auto* src = inputBlock .getChannelPointer (0);
|
||||
auto* dst = outputBlock.getChannelPointer (0);
|
||||
auto* coeffs = coefficients->getRawCoefficients();
|
||||
|
||||
// we need to copy this template parameter into a constexpr
|
||||
// otherwise MSVC will moan that the tenary expressions below
|
||||
// are constant conditional expressions
|
||||
constexpr bool isBypassed = bypassed;
|
||||
|
||||
switch (order)
|
||||
{
|
||||
case 1:
|
||||
{
|
||||
auto b0 = coeffs[0];
|
||||
auto b1 = coeffs[1];
|
||||
auto a1 = coeffs[2];
|
||||
|
||||
auto lv1 = state[0];
|
||||
|
||||
for (size_t i = 0; i < numSamples; ++i)
|
||||
{
|
||||
auto in = src[i];
|
||||
auto out = in * b0 + lv1;
|
||||
|
||||
dst[i] = isBypassed ? in : out;
|
||||
|
||||
lv1 = (in * b1) - (out * a1);
|
||||
}
|
||||
|
||||
util::snapToZero (lv1); state[0] = lv1;
|
||||
}
|
||||
break;
|
||||
|
||||
case 2:
|
||||
{
|
||||
auto b0 = coeffs[0];
|
||||
auto b1 = coeffs[1];
|
||||
auto b2 = coeffs[2];
|
||||
auto a1 = coeffs[3];
|
||||
auto a2 = coeffs[4];
|
||||
|
||||
auto lv1 = state[0];
|
||||
auto lv2 = state[1];
|
||||
|
||||
for (size_t i = 0; i < numSamples; ++i)
|
||||
{
|
||||
auto in = src[i];
|
||||
auto out = (in * b0) + lv1;
|
||||
dst[i] = isBypassed ? in : out;
|
||||
|
||||
lv1 = (in * b1) - (out * a1) + lv2;
|
||||
lv2 = (in * b2) - (out * a2);
|
||||
}
|
||||
|
||||
util::snapToZero (lv1); state[0] = lv1;
|
||||
util::snapToZero (lv2); state[1] = lv2;
|
||||
}
|
||||
break;
|
||||
|
||||
case 3:
|
||||
{
|
||||
auto b0 = coeffs[0];
|
||||
auto b1 = coeffs[1];
|
||||
auto b2 = coeffs[2];
|
||||
auto b3 = coeffs[3];
|
||||
auto a1 = coeffs[4];
|
||||
auto a2 = coeffs[5];
|
||||
auto a3 = coeffs[6];
|
||||
|
||||
auto lv1 = state[0];
|
||||
auto lv2 = state[1];
|
||||
auto lv3 = state[2];
|
||||
|
||||
for (size_t i = 0; i < numSamples; ++i)
|
||||
{
|
||||
auto in = src[i];
|
||||
auto out = (in * b0) + lv1;
|
||||
dst[i] = isBypassed ? in : out;
|
||||
|
||||
lv1 = (in * b1) - (out * a1) + lv2;
|
||||
lv2 = (in * b2) - (out * a2) + lv3;
|
||||
lv3 = (in * b3) - (out * a3);
|
||||
}
|
||||
|
||||
util::snapToZero (lv1); state[0] = lv1;
|
||||
util::snapToZero (lv2); state[1] = lv2;
|
||||
util::snapToZero (lv3); state[2] = lv3;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
{
|
||||
for (size_t i = 0; i < numSamples; ++i)
|
||||
{
|
||||
auto in = src[i];
|
||||
auto out = (in * coeffs[0]) + state[0];
|
||||
dst[i] = isBypassed ? in : out;
|
||||
|
||||
for (size_t j = 0; j < order - 1; ++j)
|
||||
state[j] = (in * coeffs[j + 1]) - (out * coeffs[order + j + 1]) + state[j + 1];
|
||||
|
||||
state[order - 1] = (in * coeffs[order]) - (out * coeffs[order * 2]);
|
||||
}
|
||||
|
||||
snapToZero();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename SampleType>
|
||||
SampleType JUCE_VECTOR_CALLTYPE Filter<SampleType>::processSample (SampleType sample) noexcept
|
||||
{
|
||||
check();
|
||||
auto* c = coefficients->getRawCoefficients();
|
||||
|
||||
auto out = (c[0] * sample) + state[0];
|
||||
|
||||
for (size_t j = 0; j < order - 1; ++j)
|
||||
state[j] = (c[j + 1] * sample) - (c[order + j + 1] * out) + state[j + 1];
|
||||
|
||||
state[order - 1] = (c[order] * sample) - (c[order * 2] * out);
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
template <typename SampleType>
|
||||
void Filter<SampleType>::snapToZero() noexcept
|
||||
{
|
||||
for (size_t i = 0; i < order; ++i)
|
||||
util::snapToZero (state[i]);
|
||||
}
|
||||
|
||||
template <typename SampleType>
|
||||
void Filter<SampleType>::check()
|
||||
{
|
||||
jassert (coefficients != nullptr);
|
||||
|
||||
if (order != coefficients->getFilterOrder())
|
||||
reset();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace IIR
|
||||
} // namespace dsp
|
||||
} // namespace juce
|
170
modules/juce_dsp/processors/juce_LadderFilter.cpp
Normal file
170
modules/juce_dsp/processors/juce_LadderFilter.cpp
Normal file
@ -0,0 +1,170 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
namespace dsp
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
template <typename Type>
|
||||
LadderFilter<Type>::LadderFilter() : state (2)
|
||||
{
|
||||
setSampleRate (Type (1000)); // intentionally setting unrealistic default
|
||||
// sample rate to catch missing initialisation bugs
|
||||
setResonance (Type (0));
|
||||
setDrive (Type (1.2));
|
||||
setMode (Mode::LPF12);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
template <typename Type>
|
||||
void LadderFilter<Type>::setMode (Mode newValue) noexcept
|
||||
{
|
||||
switch (newValue)
|
||||
{
|
||||
case Mode::LPF12: A = {{ Type (0), Type (0), Type (1), Type (0), Type (0) }}; comp = Type (0.5); break;
|
||||
case Mode::HPF12: A = {{ Type (1), Type (-2), Type (1), Type (0), Type (0) }}; comp = Type (0); break;
|
||||
case Mode::LPF24: A = {{ Type (0), Type (0), Type (0), Type (0), Type (1) }}; comp = Type (0.5); break;
|
||||
case Mode::HPF24: A = {{ Type (1), Type (-4), Type (6), Type (-4), Type (1) }}; comp = Type (0); break;
|
||||
default: jassertfalse; break;
|
||||
}
|
||||
|
||||
static constexpr auto outputGain = Type (1.2);
|
||||
|
||||
for (auto& a : A)
|
||||
a *= outputGain;
|
||||
|
||||
mode = newValue;
|
||||
reset();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
template <typename Type>
|
||||
void LadderFilter<Type>::prepare (const juce::dsp::ProcessSpec& spec)
|
||||
{
|
||||
setSampleRate (Type (spec.sampleRate));
|
||||
setNumChannels (spec.numChannels);
|
||||
reset();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
template <typename Type>
|
||||
void LadderFilter<Type>::reset() noexcept
|
||||
{
|
||||
for (auto& s : state)
|
||||
s.fill (Type (0));
|
||||
|
||||
cutoffTransformSmoother.setValue (cutoffTransformSmoother.getTargetValue(), true);
|
||||
scaledResonanceSmoother.setValue (scaledResonanceSmoother.getTargetValue(), true);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
template <typename Type>
|
||||
void LadderFilter<Type>::setCutoffFrequencyHz (Type newValue) noexcept
|
||||
{
|
||||
jassert (newValue > Type (0));
|
||||
cutoffFreqHz = newValue;
|
||||
updateCutoffFreq();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
template <typename Type>
|
||||
void LadderFilter<Type>::setResonance (Type newValue) noexcept
|
||||
{
|
||||
jassert (newValue >= Type (0) && newValue <= Type (1));
|
||||
resonance = newValue;
|
||||
updateResonance();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
template <typename Type>
|
||||
void LadderFilter<Type>::setDrive (Type newValue) noexcept
|
||||
{
|
||||
jassert (newValue >= Type (1));
|
||||
|
||||
drive = newValue;
|
||||
gain = std::pow (drive, Type (-2.642)) * Type (0.6103) + Type (0.3903);
|
||||
drive2 = drive * Type (0.04) + Type (0.96);
|
||||
gain2 = std::pow (drive2, Type (-2.642)) * Type (0.6103) + Type (0.3903);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
template <typename Type>
|
||||
Type LadderFilter<Type>::processSample (Type inputValue, size_t channelToUse) noexcept
|
||||
{
|
||||
auto& s = state[channelToUse];
|
||||
|
||||
const auto a1 = cutoffTransformValue;
|
||||
const auto g = a1 * Type (-1) + Type (1);
|
||||
const auto b0 = g * Type (0.76923076923);
|
||||
const auto b1 = g * Type (0.23076923076);
|
||||
|
||||
const auto dx = gain * saturationLUT (drive * inputValue);
|
||||
const auto a = dx + scaledResonanceValue * Type (-4) * (gain2 * saturationLUT (drive2 * s[4]) - dx * comp);
|
||||
|
||||
const auto b = b1 * s[0] + a1 * s[1] + b0 * a;
|
||||
const auto c = b1 * s[1] + a1 * s[2] + b0 * b;
|
||||
const auto d = b1 * s[2] + a1 * s[3] + b0 * c;
|
||||
const auto e = b1 * s[3] + a1 * s[4] + b0 * d;
|
||||
|
||||
s[0] = a;
|
||||
s[1] = b;
|
||||
s[2] = c;
|
||||
s[3] = d;
|
||||
s[4] = e;
|
||||
|
||||
return a * A[0] + b * A[1] + c * A[2] + d * A[3] + e * A[4];
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
template <typename Type>
|
||||
void LadderFilter<Type>::updateSmoothers() noexcept
|
||||
{
|
||||
cutoffTransformValue = cutoffTransformSmoother.getNextValue();
|
||||
scaledResonanceValue = scaledResonanceSmoother.getNextValue();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
template <typename Type>
|
||||
void LadderFilter<Type>::setSampleRate (Type newValue) noexcept
|
||||
{
|
||||
jassert (newValue > Type (0));
|
||||
cutoffFreqScaler = Type (-2.0 * juce::MathConstants<double>::pi) / newValue;
|
||||
|
||||
static constexpr Type smootherRampTimeSec = Type (0.05);
|
||||
cutoffTransformSmoother.reset (newValue, smootherRampTimeSec);
|
||||
scaledResonanceSmoother.reset (newValue, smootherRampTimeSec);
|
||||
|
||||
updateCutoffFreq();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
template class LadderFilter<float>;
|
||||
template class LadderFilter<double>;
|
||||
|
||||
} // namespace dsp
|
||||
} // namespace juce
|
144
modules/juce_dsp/processors/juce_LadderFilter.h
Normal file
144
modules/juce_dsp/processors/juce_LadderFilter.h
Normal file
@ -0,0 +1,144 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
namespace dsp
|
||||
{
|
||||
|
||||
/**
|
||||
Multi-mode filter based on the Moog ladder filter.
|
||||
|
||||
@tags{DSP}
|
||||
*/
|
||||
template <typename Type>
|
||||
class LadderFilter
|
||||
{
|
||||
public:
|
||||
enum class Mode
|
||||
{
|
||||
LPF12, // low-pass 12 dB/octave
|
||||
HPF12, // high-pass 12 dB/octave
|
||||
LPF24, // low-pass 24 dB/octave
|
||||
HPF24 // high-pass 24 dB/octave
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** Creates an uninitialised filter. Call prepare() before first use. */
|
||||
LadderFilter();
|
||||
|
||||
/** Enables or disables the filter. If disabled it will simply pass through the input signal. */
|
||||
void setEnabled (bool newValue) noexcept { enabled = newValue; }
|
||||
|
||||
/** Sets filter mode. */
|
||||
void setMode (Mode newValue) noexcept;
|
||||
|
||||
/** Initialises the filter. */
|
||||
void prepare (const juce::dsp::ProcessSpec& spec);
|
||||
|
||||
/** Returns the current number of channels. */
|
||||
size_t getNumChannels() const noexcept { return state.size(); }
|
||||
|
||||
/** Resets the internal state variables of the filter. */
|
||||
void reset() noexcept;
|
||||
|
||||
/** Sets the cutoff frequency of the filter.
|
||||
@param newValue cutoff frequency in Hz */
|
||||
void setCutoffFrequencyHz (Type newValue) noexcept;
|
||||
|
||||
/** Sets the resonance of the filter.
|
||||
@param newValue a value between 0 and 1; higher values increase the resonance and can result in self oscillation! */
|
||||
void setResonance (Type newValue) noexcept;
|
||||
|
||||
/** Sets the amound of saturation in the filter.
|
||||
@param newValue saturation amount; it can be any number greater than or equal to one. Higher values result in more distortion.*/
|
||||
void setDrive (Type newValue) noexcept;
|
||||
|
||||
//==============================================================================
|
||||
template <typename ProcessContext>
|
||||
void process (const ProcessContext& context) noexcept
|
||||
{
|
||||
const auto& inputBlock = context.getInputBlock();
|
||||
auto& outputBlock = context.getOutputBlock();
|
||||
const auto numChannels = outputBlock.getNumChannels();
|
||||
const auto numSamples = outputBlock.getNumSamples();
|
||||
|
||||
jassert (inputBlock.getNumChannels() <= getNumChannels());
|
||||
jassert (inputBlock.getNumChannels() == numChannels);
|
||||
jassert (inputBlock.getNumSamples() == numSamples);
|
||||
|
||||
if (! enabled || context.isBypassed)
|
||||
{
|
||||
outputBlock.copy (inputBlock);
|
||||
return;
|
||||
}
|
||||
|
||||
for (size_t n = 0; n < numSamples; ++n)
|
||||
{
|
||||
updateSmoothers();
|
||||
|
||||
for (size_t ch = 0; ch < numChannels; ++ch)
|
||||
outputBlock.getChannelPointer (ch)[n] = processSample (inputBlock.getChannelPointer (ch)[n], ch);
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
//==============================================================================
|
||||
Type processSample (Type inputValue, size_t channelToUse) noexcept;
|
||||
void updateSmoothers() noexcept;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
Type drive, drive2, gain, gain2, comp;
|
||||
|
||||
static constexpr size_t numStates = 5;
|
||||
std::vector<std::array<Type, numStates>> state;
|
||||
std::array<Type, numStates> A;
|
||||
|
||||
LinearSmoothedValue<Type> cutoffTransformSmoother;
|
||||
LinearSmoothedValue<Type> scaledResonanceSmoother;
|
||||
Type cutoffTransformValue;
|
||||
Type scaledResonanceValue;
|
||||
|
||||
LookupTableTransform<Type> saturationLUT { [] (Type x) { return std::tanh (x); }, Type (-5), Type (5), 128 };
|
||||
|
||||
Type cutoffFreqHz { Type (200) };
|
||||
Type resonance;
|
||||
|
||||
Type cutoffFreqScaler;
|
||||
|
||||
Mode mode;
|
||||
bool enabled = true;
|
||||
|
||||
//==============================================================================
|
||||
void setSampleRate (Type newValue) noexcept;
|
||||
void setNumChannels (size_t newValue) { state.resize (newValue); }
|
||||
void updateCutoffFreq() noexcept { cutoffTransformSmoother.setValue (std::exp (cutoffFreqHz * cutoffFreqScaler)); }
|
||||
void updateResonance() noexcept { scaledResonanceSmoother.setValue (jmap (resonance, Type (0.1), Type (1.0))); }
|
||||
};
|
||||
|
||||
} // namespace dsp
|
||||
} // namespace juce
|
244
modules/juce_dsp/processors/juce_Oscillator.h
Normal file
244
modules/juce_dsp/processors/juce_Oscillator.h
Normal file
@ -0,0 +1,244 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
namespace dsp
|
||||
{
|
||||
|
||||
/**
|
||||
Generates a signal based on a user-supplied function.
|
||||
|
||||
@tags{DSP}
|
||||
*/
|
||||
template <typename SampleType>
|
||||
class Oscillator
|
||||
{
|
||||
public:
|
||||
/** The NumericType is the underlying primitive type used by the SampleType (which
|
||||
could be either a primitive or vector)
|
||||
*/
|
||||
using NumericType = typename SampleTypeHelpers::ElementType<SampleType>::Type;
|
||||
|
||||
/** Creates an uninitialised oscillator. Call initialise before first use. */
|
||||
Oscillator()
|
||||
{}
|
||||
|
||||
/** Creates an oscillator with a periodic input function (-pi..pi).
|
||||
|
||||
If lookup table is not zero, then the function will be approximated
|
||||
with a lookup table.
|
||||
*/
|
||||
Oscillator (const std::function<NumericType (NumericType)>& function,
|
||||
size_t lookupTableNumPoints = 0)
|
||||
{
|
||||
initialise (function, lookupTableNumPoints);
|
||||
}
|
||||
|
||||
/** Returns true if the Oscillator has been initialised. */
|
||||
bool isInitialised() const noexcept { return static_cast<bool> (generator); }
|
||||
|
||||
/** Initialises the oscillator with a waveform. */
|
||||
void initialise (const std::function<NumericType (NumericType)>& function,
|
||||
size_t lookupTableNumPoints = 0)
|
||||
{
|
||||
if (lookupTableNumPoints != 0)
|
||||
{
|
||||
auto* table = new LookupTableTransform<NumericType> (function,
|
||||
-MathConstants<NumericType>::pi,
|
||||
MathConstants<NumericType>::pi,
|
||||
lookupTableNumPoints);
|
||||
|
||||
lookupTable.reset (table);
|
||||
generator = [table] (NumericType x) { return (*table) (x); };
|
||||
}
|
||||
else
|
||||
{
|
||||
generator = function;
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
/** Sets the frequency of the oscillator. */
|
||||
void setFrequency (NumericType newFrequency, bool force = false) noexcept { frequency.setValue (newFrequency, force); }
|
||||
|
||||
/** Returns the current frequency of the oscillator. */
|
||||
NumericType getFrequency() const noexcept { return frequency.getTargetValue(); }
|
||||
|
||||
//==============================================================================
|
||||
/** Called before processing starts. */
|
||||
void prepare (const ProcessSpec& spec) noexcept
|
||||
{
|
||||
sampleRate = static_cast<NumericType> (spec.sampleRate);
|
||||
rampBuffer.resize ((int) spec.maximumBlockSize);
|
||||
|
||||
reset();
|
||||
}
|
||||
|
||||
/** Resets the internal state of the oscillator */
|
||||
void reset() noexcept
|
||||
{
|
||||
phase.reset();
|
||||
|
||||
if (sampleRate > 0)
|
||||
frequency.reset (sampleRate, 0.05);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the result of processing a single sample. */
|
||||
SampleType JUCE_VECTOR_CALLTYPE processSample (SampleType input) noexcept
|
||||
{
|
||||
jassert (isInitialised());
|
||||
auto increment = MathConstants<NumericType>::twoPi * frequency.getNextValue() / sampleRate;
|
||||
return input + generator (phase.advance (increment) - MathConstants<NumericType>::pi);
|
||||
}
|
||||
|
||||
/** Processes the input and output buffers supplied in the processing context. */
|
||||
template <typename ProcessContext>
|
||||
void process (const ProcessContext& context) noexcept
|
||||
{
|
||||
jassert (isInitialised());
|
||||
auto&& outBlock = context.getOutputBlock();
|
||||
auto&& inBlock = context.getInputBlock();
|
||||
|
||||
// this is an output-only processory
|
||||
jassert (outBlock.getNumSamples() <= static_cast<size_t> (rampBuffer.size()));
|
||||
|
||||
auto len = outBlock.getNumSamples();
|
||||
auto numChannels = outBlock.getNumChannels();
|
||||
auto inputChannels = inBlock.getNumChannels();
|
||||
auto baseIncrement = MathConstants<NumericType>::twoPi / sampleRate;
|
||||
|
||||
if (context.isBypassed)
|
||||
context.getOutputBlock().clear();
|
||||
|
||||
if (frequency.isSmoothing())
|
||||
{
|
||||
auto* buffer = rampBuffer.getRawDataPointer();
|
||||
|
||||
for (size_t i = 0; i < len; ++i)
|
||||
buffer[i] = phase.advance (baseIncrement * frequency.getNextValue())
|
||||
- MathConstants<NumericType>::pi;
|
||||
|
||||
if (! context.isBypassed)
|
||||
{
|
||||
size_t ch;
|
||||
|
||||
if (context.usesSeparateInputAndOutputBlocks())
|
||||
{
|
||||
for (ch = 0; ch < jmin (numChannels, inputChannels); ++ch)
|
||||
{
|
||||
auto* dst = outBlock.getChannelPointer (ch);
|
||||
auto* src = inBlock.getChannelPointer (ch);
|
||||
|
||||
for (size_t i = 0; i < len; ++i)
|
||||
dst[i] = src[i] + generator (buffer[i]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (ch = 0; ch < jmin (numChannels, inputChannels); ++ch)
|
||||
{
|
||||
auto* dst = outBlock.getChannelPointer (ch);
|
||||
|
||||
for (size_t i = 0; i < len; ++i)
|
||||
dst[i] += generator (buffer[i]);
|
||||
}
|
||||
}
|
||||
|
||||
for (; ch < numChannels; ++ch)
|
||||
{
|
||||
auto* dst = outBlock.getChannelPointer (ch);
|
||||
|
||||
for (size_t i = 0; i < len; ++i)
|
||||
dst[i] = generator (buffer[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
auto freq = baseIncrement * frequency.getNextValue();
|
||||
auto p = phase;
|
||||
|
||||
if (context.isBypassed)
|
||||
{
|
||||
frequency.skip (static_cast<int> (len));
|
||||
p.advance (freq * static_cast<NumericType> (len));
|
||||
}
|
||||
else
|
||||
{
|
||||
size_t ch;
|
||||
|
||||
if (context.usesSeparateInputAndOutputBlocks())
|
||||
{
|
||||
for (ch = 0; ch < jmin (numChannels, inputChannels); ++ch)
|
||||
{
|
||||
p = phase;
|
||||
auto* dst = outBlock.getChannelPointer (ch);
|
||||
auto* src = inBlock.getChannelPointer (ch);
|
||||
|
||||
for (size_t i = 0; i < len; ++i)
|
||||
dst[i] = src[i] + generator (p.advance (freq) - MathConstants<NumericType>::pi);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (ch = 0; ch < jmin (numChannels, inputChannels); ++ch)
|
||||
{
|
||||
p = phase;
|
||||
auto* dst = outBlock.getChannelPointer (ch);
|
||||
|
||||
for (size_t i = 0; i < len; ++i)
|
||||
dst[i] += generator (p.advance (freq) - MathConstants<NumericType>::pi);
|
||||
}
|
||||
}
|
||||
|
||||
for (; ch < numChannels; ++ch)
|
||||
{
|
||||
p = phase;
|
||||
auto* dst = outBlock.getChannelPointer (ch);
|
||||
|
||||
for (size_t i = 0; i < len; ++i)
|
||||
dst[i] = generator (p.advance (freq) - MathConstants<NumericType>::pi);
|
||||
}
|
||||
}
|
||||
|
||||
phase = p;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
std::function<NumericType (NumericType)> generator;
|
||||
std::unique_ptr<LookupTableTransform<NumericType>> lookupTable;
|
||||
Array<NumericType> rampBuffer;
|
||||
LinearSmoothedValue<NumericType> frequency { static_cast<NumericType> (440.0) };
|
||||
NumericType sampleRate = 48000.0;
|
||||
Phase<NumericType> phase;
|
||||
};
|
||||
|
||||
} // namespace dsp
|
||||
} // namespace juce
|
717
modules/juce_dsp/processors/juce_Oversampling.cpp
Normal file
717
modules/juce_dsp/processors/juce_Oversampling.cpp
Normal file
@ -0,0 +1,717 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
namespace dsp
|
||||
{
|
||||
|
||||
/** Abstract class for the provided oversampling engines used internally in
|
||||
the Oversampling class.
|
||||
*/
|
||||
template <typename SampleType>
|
||||
class OversamplingEngine
|
||||
{
|
||||
public:
|
||||
//===============================================================================
|
||||
OversamplingEngine (size_t newNumChannels, size_t newFactor)
|
||||
{
|
||||
numChannels = newNumChannels;
|
||||
factor = newFactor;
|
||||
}
|
||||
|
||||
virtual ~OversamplingEngine() {}
|
||||
|
||||
//===============================================================================
|
||||
virtual SampleType getLatencyInSamples() = 0;
|
||||
size_t getFactor() { return factor; }
|
||||
|
||||
virtual void initProcessing (size_t maximumNumberOfSamplesBeforeOversampling)
|
||||
{
|
||||
buffer.setSize (static_cast<int> (numChannels), static_cast<int> (maximumNumberOfSamplesBeforeOversampling * factor), false, false, true);
|
||||
}
|
||||
|
||||
virtual void reset()
|
||||
{
|
||||
buffer.clear();
|
||||
}
|
||||
|
||||
dsp::AudioBlock<SampleType> getProcessedSamples (size_t numSamples)
|
||||
{
|
||||
return dsp::AudioBlock<SampleType> (buffer).getSubBlock (0, numSamples);
|
||||
}
|
||||
|
||||
virtual void processSamplesUp (dsp::AudioBlock<SampleType> &inputBlock) = 0;
|
||||
virtual void processSamplesDown (dsp::AudioBlock<SampleType> &outputBlock) = 0;
|
||||
|
||||
protected:
|
||||
//===============================================================================
|
||||
AudioBuffer<SampleType> buffer;
|
||||
size_t factor;
|
||||
size_t numChannels;
|
||||
};
|
||||
|
||||
|
||||
//===============================================================================
|
||||
/** Dummy oversampling engine class which simply copies and pastes the input
|
||||
signal, which could be equivalent to a "one time" oversampling processing.
|
||||
*/
|
||||
template <typename SampleType>
|
||||
class OversamplingDummy : public OversamplingEngine<SampleType>
|
||||
{
|
||||
public:
|
||||
//===============================================================================
|
||||
OversamplingDummy (size_t numChannels) : OversamplingEngine<SampleType> (numChannels, 1) {}
|
||||
~OversamplingDummy() {}
|
||||
|
||||
//===============================================================================
|
||||
SampleType getLatencyInSamples() override
|
||||
{
|
||||
return 0.f;
|
||||
}
|
||||
|
||||
void processSamplesUp (dsp::AudioBlock<SampleType> &inputBlock) override
|
||||
{
|
||||
jassert (inputBlock.getNumChannels() <= static_cast<size_t> (OversamplingEngine<SampleType>::buffer.getNumChannels()));
|
||||
jassert (inputBlock.getNumSamples() * OversamplingEngine<SampleType>::factor <= static_cast<size_t> (OversamplingEngine<SampleType>::buffer.getNumSamples()));
|
||||
|
||||
for (size_t channel = 0; channel < inputBlock.getNumChannels(); channel++)
|
||||
OversamplingEngine<SampleType>::buffer.copyFrom (static_cast<int> (channel), 0,
|
||||
inputBlock.getChannelPointer (channel), static_cast<int> (inputBlock.getNumSamples()));
|
||||
}
|
||||
|
||||
void processSamplesDown (dsp::AudioBlock<SampleType> &outputBlock) override
|
||||
{
|
||||
jassert (outputBlock.getNumChannels() <= static_cast<size_t> (OversamplingEngine<SampleType>::buffer.getNumChannels()));
|
||||
jassert (outputBlock.getNumSamples() * OversamplingEngine<SampleType>::factor <= static_cast<size_t> (OversamplingEngine<SampleType>::buffer.getNumSamples()));
|
||||
|
||||
outputBlock.copy (OversamplingEngine<SampleType>::getProcessedSamples (outputBlock.getNumSamples()));
|
||||
}
|
||||
|
||||
private:
|
||||
//===============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OversamplingDummy)
|
||||
};
|
||||
|
||||
//===============================================================================
|
||||
/** Oversampling engine class performing 2 times oversampling using the Filter
|
||||
Design FIR Equiripple method. The resulting filter is linear phase,
|
||||
symmetric, and has every two samples but the middle one equal to zero,
|
||||
leading to specific processing optimizations.
|
||||
*/
|
||||
template <typename SampleType>
|
||||
class Oversampling2TimesEquirippleFIR : public OversamplingEngine<SampleType>
|
||||
{
|
||||
public:
|
||||
//===============================================================================
|
||||
Oversampling2TimesEquirippleFIR (size_t numChannels,
|
||||
SampleType normalizedTransitionWidthUp,
|
||||
SampleType stopbandAttenuationdBUp,
|
||||
SampleType normalizedTransitionWidthDown,
|
||||
SampleType stopbandAttenuationdBDown) : OversamplingEngine<SampleType> (numChannels, 2)
|
||||
{
|
||||
coefficientsUp = *dsp::FilterDesign<SampleType>::designFIRLowpassHalfBandEquirippleMethod (normalizedTransitionWidthUp, stopbandAttenuationdBUp);
|
||||
coefficientsDown = *dsp::FilterDesign<SampleType>::designFIRLowpassHalfBandEquirippleMethod (normalizedTransitionWidthDown, stopbandAttenuationdBDown);
|
||||
|
||||
auto N = coefficientsUp.getFilterOrder() + 1;
|
||||
stateUp.setSize (static_cast<int> (numChannels), static_cast<int> (N));
|
||||
|
||||
N = coefficientsDown.getFilterOrder() + 1;
|
||||
auto Ndiv2 = N / 2;
|
||||
auto Ndiv4 = Ndiv2 / 2;
|
||||
|
||||
stateDown.setSize (static_cast<int> (numChannels), static_cast<int> (N));
|
||||
stateDown2.setSize (static_cast<int> (numChannels), static_cast<int> (Ndiv4 + 1));
|
||||
|
||||
position.resize (static_cast<int> (numChannels));
|
||||
}
|
||||
|
||||
~Oversampling2TimesEquirippleFIR() {}
|
||||
|
||||
//===============================================================================
|
||||
SampleType getLatencyInSamples() override
|
||||
{
|
||||
return static_cast<SampleType> (coefficientsUp.getFilterOrder() + coefficientsDown.getFilterOrder()) * 0.5f;
|
||||
}
|
||||
|
||||
void reset() override
|
||||
{
|
||||
OversamplingEngine<SampleType>::reset();
|
||||
|
||||
stateUp.clear();
|
||||
stateDown.clear();
|
||||
stateDown2.clear();
|
||||
|
||||
position.fill (0);
|
||||
}
|
||||
|
||||
void processSamplesUp (dsp::AudioBlock<SampleType> &inputBlock) override
|
||||
{
|
||||
jassert (inputBlock.getNumChannels() <= static_cast<size_t> (OversamplingEngine<SampleType>::buffer.getNumChannels()));
|
||||
jassert (inputBlock.getNumSamples() * OversamplingEngine<SampleType>::factor <= static_cast<size_t> (OversamplingEngine<SampleType>::buffer.getNumSamples()));
|
||||
|
||||
// Initialization
|
||||
auto fir = coefficientsUp.getRawCoefficients();
|
||||
auto N = coefficientsUp.getFilterOrder() + 1;
|
||||
auto Ndiv2 = N / 2;
|
||||
auto numSamples = inputBlock.getNumSamples();
|
||||
|
||||
// Processing
|
||||
for (size_t channel = 0; channel < inputBlock.getNumChannels(); channel++)
|
||||
{
|
||||
auto bufferSamples = OversamplingEngine<SampleType>::buffer.getWritePointer (static_cast<int> (channel));
|
||||
auto buf = stateUp.getWritePointer (static_cast<int> (channel));
|
||||
auto samples = inputBlock.getChannelPointer (channel);
|
||||
|
||||
for (size_t i = 0; i < numSamples; i++)
|
||||
{
|
||||
// Input
|
||||
buf[N - 1] = 2 * samples[i];
|
||||
|
||||
// Convolution
|
||||
auto out = static_cast<SampleType> (0.0);
|
||||
for (size_t k = 0; k < Ndiv2; k += 2)
|
||||
out += (buf[k] + buf[N - k - 1]) * fir[k];
|
||||
|
||||
// Outputs
|
||||
bufferSamples[i << 1] = out;
|
||||
bufferSamples[(i << 1) + 1] = buf[Ndiv2 + 1] * fir[Ndiv2];
|
||||
|
||||
// Shift data
|
||||
for (size_t k = 0; k < N - 2; k += 2)
|
||||
buf[k] = buf[k + 2];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void processSamplesDown (dsp::AudioBlock<SampleType> &outputBlock) override
|
||||
{
|
||||
jassert (outputBlock.getNumChannels() <= static_cast<size_t> (OversamplingEngine<SampleType>::buffer.getNumChannels()));
|
||||
jassert (outputBlock.getNumSamples() * OversamplingEngine<SampleType>::factor <= static_cast<size_t> (OversamplingEngine<SampleType>::buffer.getNumSamples()));
|
||||
|
||||
// Initialization
|
||||
auto fir = coefficientsDown.getRawCoefficients();
|
||||
auto N = coefficientsDown.getFilterOrder() + 1;
|
||||
auto Ndiv2 = N / 2;
|
||||
auto Ndiv4 = Ndiv2 / 2;
|
||||
auto numSamples = outputBlock.getNumSamples();
|
||||
|
||||
// Processing
|
||||
for (size_t channel = 0; channel < outputBlock.getNumChannels(); channel++)
|
||||
{
|
||||
auto bufferSamples = OversamplingEngine<SampleType>::buffer.getWritePointer (static_cast<int> (channel));
|
||||
auto buf = stateDown.getWritePointer (static_cast<int> (channel));
|
||||
auto buf2 = stateDown2.getWritePointer (static_cast<int> (channel));
|
||||
auto samples = outputBlock.getChannelPointer (channel);
|
||||
auto pos = position.getUnchecked (static_cast<int> (channel));
|
||||
|
||||
for (size_t i = 0; i < numSamples; i++)
|
||||
{
|
||||
// Input
|
||||
buf[N - 1] = bufferSamples[i << 1];
|
||||
|
||||
// Convolution
|
||||
auto out = static_cast<SampleType> (0.0);
|
||||
for (size_t k = 0; k < Ndiv2; k += 2)
|
||||
out += (buf[k] + buf[N - k - 1]) * fir[k];
|
||||
|
||||
// Output
|
||||
out += buf2[pos] * fir[Ndiv2];
|
||||
buf2[pos] = bufferSamples[(i << 1) + 1];
|
||||
|
||||
samples[i] = out;
|
||||
|
||||
// Shift data
|
||||
for (size_t k = 0; k < N - 2; k++)
|
||||
buf[k] = buf[k + 2];
|
||||
|
||||
// Circular buffer
|
||||
pos = (pos == 0 ? Ndiv4 : pos - 1);
|
||||
}
|
||||
|
||||
position.setUnchecked (static_cast<int> (channel), pos);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private:
|
||||
//===============================================================================
|
||||
dsp::FIR::Coefficients<SampleType> coefficientsUp, coefficientsDown;
|
||||
AudioBuffer<SampleType> stateUp, stateDown, stateDown2;
|
||||
Array<size_t> position;
|
||||
|
||||
//===============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Oversampling2TimesEquirippleFIR)
|
||||
};
|
||||
|
||||
|
||||
//===============================================================================
|
||||
/** Oversampling engine class performing 2 times oversampling using the Filter
|
||||
Design IIR Polyphase Allpass Cascaded method. The resulting filter is minimum
|
||||
phase, and provided with a method to get the exact resulting latency.
|
||||
*/
|
||||
template <typename SampleType>
|
||||
class Oversampling2TimesPolyphaseIIR : public OversamplingEngine<SampleType>
|
||||
{
|
||||
public:
|
||||
//===============================================================================
|
||||
Oversampling2TimesPolyphaseIIR (size_t numChannels,
|
||||
SampleType normalizedTransitionWidthUp,
|
||||
SampleType stopbandAttenuationdBUp,
|
||||
SampleType normalizedTransitionWidthDown,
|
||||
SampleType stopbandAttenuationdBDown) : OversamplingEngine<SampleType> (numChannels, 2)
|
||||
{
|
||||
auto structureUp = dsp::FilterDesign<SampleType>::designIIRLowpassHalfBandPolyphaseAllpassMethod (normalizedTransitionWidthUp, stopbandAttenuationdBUp);
|
||||
dsp::IIR::Coefficients<SampleType> coeffsUp = getCoefficients (structureUp);
|
||||
latency = static_cast<SampleType> (-(coeffsUp.getPhaseForFrequency (0.0001, 1.0)) / (0.0001 * MathConstants<double>::twoPi));
|
||||
|
||||
auto structureDown = dsp::FilterDesign<SampleType>::designIIRLowpassHalfBandPolyphaseAllpassMethod (normalizedTransitionWidthDown, stopbandAttenuationdBDown);
|
||||
dsp::IIR::Coefficients<SampleType> coeffsDown = getCoefficients (structureDown);
|
||||
latency += static_cast<SampleType> (-(coeffsDown.getPhaseForFrequency (0.0001, 1.0)) / (0.0001 * MathConstants<double>::twoPi));
|
||||
|
||||
for (auto i = 0; i < structureUp.directPath.size(); i++)
|
||||
coefficientsUp.add (structureUp.directPath[i].coefficients[0]);
|
||||
|
||||
for (auto i = 1; i < structureUp.delayedPath.size(); i++)
|
||||
coefficientsUp.add (structureUp.delayedPath[i].coefficients[0]);
|
||||
|
||||
for (auto i = 0; i < structureDown.directPath.size(); i++)
|
||||
coefficientsDown.add (structureDown.directPath[i].coefficients[0]);
|
||||
|
||||
for (auto i = 1; i < structureDown.delayedPath.size(); i++)
|
||||
coefficientsDown.add (structureDown.delayedPath[i].coefficients[0]);
|
||||
|
||||
v1Up.setSize (static_cast<int> (numChannels), coefficientsUp.size());
|
||||
v1Down.setSize (static_cast<int> (numChannels), coefficientsDown.size());
|
||||
delayDown.resize (static_cast<int> (numChannels));
|
||||
}
|
||||
|
||||
~Oversampling2TimesPolyphaseIIR() {}
|
||||
|
||||
//===============================================================================
|
||||
SampleType getLatencyInSamples() override
|
||||
{
|
||||
return latency;
|
||||
}
|
||||
|
||||
void reset() override
|
||||
{
|
||||
OversamplingEngine<SampleType>::reset();
|
||||
|
||||
v1Up.clear();
|
||||
v1Down.clear();
|
||||
delayDown.fill (0);
|
||||
}
|
||||
|
||||
void processSamplesUp (dsp::AudioBlock<SampleType> &inputBlock) override
|
||||
{
|
||||
jassert (inputBlock.getNumChannels() <= static_cast<size_t> (OversamplingEngine<SampleType>::buffer.getNumChannels()));
|
||||
jassert (inputBlock.getNumSamples() * OversamplingEngine<SampleType>::factor <= static_cast<size_t> (OversamplingEngine<SampleType>::buffer.getNumSamples()));
|
||||
|
||||
// Initialization
|
||||
auto coeffs = coefficientsUp.getRawDataPointer();
|
||||
auto numStages = coefficientsUp.size();
|
||||
auto delayedStages = numStages / 2;
|
||||
auto directStages = numStages - delayedStages;
|
||||
auto numSamples = inputBlock.getNumSamples();
|
||||
|
||||
// Processing
|
||||
for (size_t channel = 0; channel < inputBlock.getNumChannels(); channel++)
|
||||
{
|
||||
auto bufferSamples = OversamplingEngine<SampleType>::buffer.getWritePointer (static_cast<int> (channel));
|
||||
auto lv1 = v1Up.getWritePointer (static_cast<int> (channel));
|
||||
auto samples = inputBlock.getChannelPointer (channel);
|
||||
|
||||
for (size_t i = 0; i < numSamples; i++)
|
||||
{
|
||||
// Direct path cascaded allpass filters
|
||||
auto input = samples[i];
|
||||
for (auto n = 0; n < directStages; n++)
|
||||
{
|
||||
auto alpha = coeffs[n];
|
||||
auto output = alpha * input + lv1[n];
|
||||
lv1[n] = input - alpha * output;
|
||||
input = output;
|
||||
}
|
||||
|
||||
// Output
|
||||
bufferSamples[i << 1] = input;
|
||||
|
||||
// Delayed path cascaded allpass filters
|
||||
input = samples[i];
|
||||
for (auto n = directStages; n < numStages; n++)
|
||||
{
|
||||
auto alpha = coeffs[n];
|
||||
auto output = alpha * input + lv1[n];
|
||||
lv1[n] = input - alpha * output;
|
||||
input = output;
|
||||
}
|
||||
|
||||
// Output
|
||||
bufferSamples[(i << 1) + 1] = input;
|
||||
}
|
||||
}
|
||||
|
||||
// Snap To Zero
|
||||
snapToZero (true);
|
||||
}
|
||||
|
||||
void processSamplesDown (dsp::AudioBlock<SampleType> &outputBlock) override
|
||||
{
|
||||
jassert (outputBlock.getNumChannels() <= static_cast<size_t> (OversamplingEngine<SampleType>::buffer.getNumChannels()));
|
||||
jassert (outputBlock.getNumSamples() * OversamplingEngine<SampleType>::factor <= static_cast<size_t> (OversamplingEngine<SampleType>::buffer.getNumSamples()));
|
||||
|
||||
// Initialization
|
||||
auto coeffs = coefficientsDown.getRawDataPointer();
|
||||
auto numStages = coefficientsDown.size();
|
||||
auto delayedStages = numStages / 2;
|
||||
auto directStages = numStages - delayedStages;
|
||||
auto numSamples = outputBlock.getNumSamples();
|
||||
|
||||
// Processing
|
||||
for (size_t channel = 0; channel < outputBlock.getNumChannels(); channel++)
|
||||
{
|
||||
auto bufferSamples = OversamplingEngine<SampleType>::buffer.getWritePointer (static_cast<int> (channel));
|
||||
auto lv1 = v1Down.getWritePointer (static_cast<int> (channel));
|
||||
auto samples = outputBlock.getChannelPointer (channel);
|
||||
auto delay = delayDown.getUnchecked (static_cast<int> (channel));
|
||||
|
||||
for (size_t i = 0; i < numSamples; i++)
|
||||
{
|
||||
// Direct path cascaded allpass filters
|
||||
auto input = bufferSamples[i << 1];
|
||||
for (auto n = 0; n < directStages; n++)
|
||||
{
|
||||
auto alpha = coeffs[n];
|
||||
auto output = alpha * input + lv1[n];
|
||||
lv1[n] = input - alpha * output;
|
||||
input = output;
|
||||
}
|
||||
auto directOut = input;
|
||||
|
||||
// Delayed path cascaded allpass filters
|
||||
input = bufferSamples[(i << 1) + 1];
|
||||
for (auto n = directStages; n < numStages; n++)
|
||||
{
|
||||
auto alpha = coeffs[n];
|
||||
auto output = alpha * input + lv1[n];
|
||||
lv1[n] = input - alpha * output;
|
||||
input = output;
|
||||
}
|
||||
|
||||
// Output
|
||||
samples[i] = (delay + directOut) * static_cast<SampleType> (0.5);
|
||||
delay = input;
|
||||
}
|
||||
|
||||
delayDown.setUnchecked (static_cast<int> (channel), delay);
|
||||
}
|
||||
|
||||
// Snap To Zero
|
||||
snapToZero (false);
|
||||
}
|
||||
|
||||
void snapToZero (bool snapUpProcessing)
|
||||
{
|
||||
if (snapUpProcessing)
|
||||
{
|
||||
for (auto channel = 0; channel < OversamplingEngine<SampleType>::buffer.getNumChannels(); channel++)
|
||||
{
|
||||
auto lv1 = v1Up.getWritePointer (channel);
|
||||
auto numStages = coefficientsUp.size();
|
||||
|
||||
for (auto n = 0; n < numStages; n++)
|
||||
util::snapToZero (lv1[n]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (auto channel = 0; channel < OversamplingEngine<SampleType>::buffer.getNumChannels(); channel++)
|
||||
{
|
||||
auto lv1 = v1Down.getWritePointer (channel);
|
||||
auto numStages = coefficientsDown.size();
|
||||
|
||||
for (auto n = 0; n < numStages; n++)
|
||||
util::snapToZero (lv1[n]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
//===============================================================================
|
||||
/** This function calculates the equivalent high order IIR filter of a given
|
||||
polyphase cascaded allpass filters structure.
|
||||
*/
|
||||
const dsp::IIR::Coefficients<SampleType> getCoefficients (typename dsp::FilterDesign<SampleType>::IIRPolyphaseAllpassStructure& structure) const
|
||||
{
|
||||
dsp::Polynomial<SampleType> numerator1 ({ static_cast<SampleType> (1.0) });
|
||||
dsp::Polynomial<SampleType> denominator1 ({ static_cast<SampleType> (1.0) });
|
||||
dsp::Polynomial<SampleType> numerator2 ({ static_cast<SampleType> (1.0) });
|
||||
dsp::Polynomial<SampleType> denominator2 ({ static_cast<SampleType> (1.0) });
|
||||
|
||||
dsp::Polynomial<SampleType> temp;
|
||||
|
||||
for (auto n = 0; n < structure.directPath.size(); n++)
|
||||
{
|
||||
auto* coeffs = structure.directPath.getReference (n).getRawCoefficients();
|
||||
|
||||
if (structure.directPath[n].getFilterOrder() == 1)
|
||||
{
|
||||
temp = dsp::Polynomial<SampleType> ({ coeffs[0], coeffs[1] });
|
||||
numerator1 = numerator1.getProductWith (temp);
|
||||
|
||||
temp = dsp::Polynomial<SampleType> ({ static_cast<SampleType> (1.0), coeffs[2] });
|
||||
denominator1 = denominator1.getProductWith (temp);
|
||||
}
|
||||
else
|
||||
{
|
||||
temp = dsp::Polynomial<SampleType> ({ coeffs[0], coeffs[1], coeffs[2] });
|
||||
numerator1 = numerator1.getProductWith (temp);
|
||||
|
||||
temp = dsp::Polynomial<SampleType> ({ static_cast<SampleType> (1.0), coeffs[3], coeffs[4] });
|
||||
denominator1 = denominator1.getProductWith (temp);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto n = 0; n < structure.delayedPath.size(); n++)
|
||||
{
|
||||
auto* coeffs = structure.delayedPath.getReference (n).getRawCoefficients();
|
||||
|
||||
if (structure.delayedPath[n].getFilterOrder() == 1)
|
||||
{
|
||||
temp = dsp::Polynomial<SampleType> ({ coeffs[0], coeffs[1] });
|
||||
numerator2 = numerator2.getProductWith (temp);
|
||||
|
||||
temp = dsp::Polynomial<SampleType> ({ static_cast<SampleType> (1.0), coeffs[2] });
|
||||
denominator2 = denominator2.getProductWith (temp);
|
||||
}
|
||||
else
|
||||
{
|
||||
temp = dsp::Polynomial<SampleType> ({ coeffs[0], coeffs[1], coeffs[2] });
|
||||
numerator2 = numerator2.getProductWith (temp);
|
||||
|
||||
temp = dsp::Polynomial<SampleType> ({ static_cast<SampleType> (1.0), coeffs[3], coeffs[4] });
|
||||
denominator2 = denominator2.getProductWith (temp);
|
||||
}
|
||||
}
|
||||
|
||||
dsp::Polynomial<SampleType> numeratorf1 = numerator1.getProductWith (denominator2);
|
||||
dsp::Polynomial<SampleType> numeratorf2 = numerator2.getProductWith (denominator1);
|
||||
dsp::Polynomial<SampleType> numerator = numeratorf1.getSumWith (numeratorf2);
|
||||
dsp::Polynomial<SampleType> denominator = denominator1.getProductWith (denominator2);
|
||||
|
||||
dsp::IIR::Coefficients<SampleType> coeffs;
|
||||
|
||||
coeffs.coefficients.clear();
|
||||
auto inversion = static_cast<SampleType> (1.0) / denominator[0];
|
||||
|
||||
for (auto i = 0; i <= numerator.getOrder(); i++)
|
||||
coeffs.coefficients.add (numerator[i] * inversion);
|
||||
|
||||
for (auto i = 1; i <= denominator.getOrder(); i++)
|
||||
coeffs.coefficients.add (denominator[i] * inversion);
|
||||
|
||||
return coeffs;
|
||||
}
|
||||
|
||||
//===============================================================================
|
||||
Array<SampleType> coefficientsUp, coefficientsDown;
|
||||
SampleType latency;
|
||||
|
||||
AudioBuffer<SampleType> v1Up, v1Down;
|
||||
Array<SampleType> delayDown;
|
||||
|
||||
//===============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Oversampling2TimesPolyphaseIIR)
|
||||
};
|
||||
|
||||
|
||||
//===============================================================================
|
||||
template <typename SampleType>
|
||||
Oversampling<SampleType>::Oversampling (size_t newNumChannels, size_t newFactor, FilterType newType, bool newMaxQuality)
|
||||
{
|
||||
jassert (newFactor >= 0 && newFactor <= 4 && newNumChannels > 0);
|
||||
|
||||
factorOversampling = static_cast<size_t> (1) << newFactor;
|
||||
isMaximumQuality = newMaxQuality;
|
||||
type = newType;
|
||||
numChannels = newNumChannels;
|
||||
|
||||
if (newFactor == 0)
|
||||
{
|
||||
numStages = 1;
|
||||
engines.add (new OversamplingDummy<SampleType> (numChannels));
|
||||
}
|
||||
else if (type == FilterType::filterHalfBandPolyphaseIIR)
|
||||
{
|
||||
numStages = newFactor;
|
||||
|
||||
for (size_t n = 0; n < numStages; n++)
|
||||
{
|
||||
auto twUp = (isMaximumQuality ? 0.10f : 0.12f) * (n == 0 ? 0.5f : 1.f);
|
||||
auto twDown = (isMaximumQuality ? 0.12f : 0.15f) * (n == 0 ? 0.5f : 1.f);
|
||||
|
||||
auto gaindBStartUp = (isMaximumQuality ? -75.f : -65.f);
|
||||
auto gaindBStartDown = (isMaximumQuality ? -70.f : -60.f);
|
||||
auto gaindBFactorUp = (isMaximumQuality ? 10.f : 8.f);
|
||||
auto gaindBFactorDown = (isMaximumQuality ? 10.f : 8.f);
|
||||
|
||||
engines.add (new Oversampling2TimesPolyphaseIIR<SampleType> (numChannels,
|
||||
twUp, gaindBStartUp + gaindBFactorUp * n,
|
||||
twDown, gaindBStartDown + gaindBFactorDown * n));
|
||||
}
|
||||
}
|
||||
else if (type == FilterType::filterHalfBandFIREquiripple)
|
||||
{
|
||||
numStages = newFactor;
|
||||
|
||||
for (size_t n = 0; n < numStages; n++)
|
||||
{
|
||||
auto twUp = (isMaximumQuality ? 0.10f : 0.12f) * (n == 0 ? 0.5f : 1.f);
|
||||
auto twDown = (isMaximumQuality ? 0.12f : 0.15f) * (n == 0 ? 0.5f : 1.f);
|
||||
|
||||
auto gaindBStartUp = (isMaximumQuality ? -90.f : -70.f);
|
||||
auto gaindBStartDown = (isMaximumQuality ? -70.f : -60.f);
|
||||
auto gaindBFactorUp = (isMaximumQuality ? 10.f : 8.f);
|
||||
auto gaindBFactorDown = (isMaximumQuality ? 10.f : 8.f);
|
||||
|
||||
engines.add (new Oversampling2TimesEquirippleFIR<SampleType> (numChannels,
|
||||
twUp, gaindBStartUp + gaindBFactorUp * n,
|
||||
twDown, gaindBStartDown + gaindBFactorDown * n));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename SampleType>
|
||||
Oversampling<SampleType>::~Oversampling()
|
||||
{
|
||||
engines.clear();
|
||||
}
|
||||
|
||||
//===============================================================================
|
||||
template <typename SampleType>
|
||||
SampleType Oversampling<SampleType>::getLatencyInSamples() noexcept
|
||||
{
|
||||
auto latency = static_cast<SampleType> (0);
|
||||
size_t order = 1;
|
||||
|
||||
for (size_t n = 0; n < numStages; n++)
|
||||
{
|
||||
auto& engine = *engines[static_cast<int> (n)];
|
||||
|
||||
order *= engine.getFactor();
|
||||
latency += engine.getLatencyInSamples() / static_cast<SampleType> (order);
|
||||
}
|
||||
|
||||
return latency;
|
||||
}
|
||||
|
||||
template <typename SampleType>
|
||||
size_t Oversampling<SampleType>::getOversamplingFactor() noexcept
|
||||
{
|
||||
return factorOversampling;
|
||||
}
|
||||
|
||||
//===============================================================================
|
||||
template <typename SampleType>
|
||||
void Oversampling<SampleType>::initProcessing (size_t maximumNumberOfSamplesBeforeOversampling)
|
||||
{
|
||||
jassert (! engines.isEmpty());
|
||||
auto currentNumSamples = maximumNumberOfSamplesBeforeOversampling;
|
||||
|
||||
for (size_t n = 0; n < numStages; n++)
|
||||
{
|
||||
auto& engine = *engines[static_cast<int> (n)];
|
||||
|
||||
engine.initProcessing (currentNumSamples);
|
||||
currentNumSamples *= engine.getFactor();
|
||||
}
|
||||
|
||||
isReady = true;
|
||||
reset();
|
||||
}
|
||||
|
||||
template <typename SampleType>
|
||||
void Oversampling<SampleType>::reset() noexcept
|
||||
{
|
||||
jassert (! engines.isEmpty());
|
||||
|
||||
if (isReady)
|
||||
for (auto n = 0; n < engines.size(); n++)
|
||||
engines[n]->reset();
|
||||
}
|
||||
|
||||
template <typename SampleType>
|
||||
typename dsp::AudioBlock<SampleType> Oversampling<SampleType>::processSamplesUp (const dsp::AudioBlock<SampleType> &inputBlock) noexcept
|
||||
{
|
||||
jassert (! engines.isEmpty());
|
||||
|
||||
if (! isReady)
|
||||
return dsp::AudioBlock<SampleType>();
|
||||
|
||||
dsp::AudioBlock<SampleType> audioBlock = inputBlock;
|
||||
|
||||
for (size_t n = 0; n < numStages; n++)
|
||||
{
|
||||
auto& engine = *engines[static_cast<int> (n)];
|
||||
engine.processSamplesUp (audioBlock);
|
||||
audioBlock = engine.getProcessedSamples (audioBlock.getNumSamples() * engine.getFactor());
|
||||
}
|
||||
|
||||
return audioBlock;
|
||||
}
|
||||
|
||||
template <typename SampleType>
|
||||
void Oversampling<SampleType>::processSamplesDown (dsp::AudioBlock<SampleType> &outputBlock) noexcept
|
||||
{
|
||||
jassert (! engines.isEmpty());
|
||||
|
||||
if (! isReady)
|
||||
return;
|
||||
|
||||
auto currentNumSamples = outputBlock.getNumSamples();
|
||||
|
||||
for (size_t n = 0; n < numStages - 1; n++)
|
||||
currentNumSamples *= engines[static_cast<int> (n)]->getFactor();
|
||||
|
||||
for (size_t n = numStages - 1; n > 0; n--)
|
||||
{
|
||||
auto& engine = *engines[static_cast<int> (n)];
|
||||
|
||||
auto audioBlock = engines[static_cast<int> (n - 1)]->getProcessedSamples (currentNumSamples);
|
||||
engine.processSamplesDown (audioBlock);
|
||||
|
||||
currentNumSamples /= engine.getFactor();
|
||||
}
|
||||
|
||||
engines[static_cast<int> (0)]->processSamplesDown (outputBlock);
|
||||
}
|
||||
|
||||
template class Oversampling<float>;
|
||||
template class Oversampling<double>;
|
||||
|
||||
} // namespace dsp
|
||||
} // namespace juce
|
150
modules/juce_dsp/processors/juce_Oversampling.h
Normal file
150
modules/juce_dsp/processors/juce_Oversampling.h
Normal file
@ -0,0 +1,150 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
namespace dsp
|
||||
{
|
||||
|
||||
#ifndef DOXYGEN
|
||||
template <typename NumericType>
|
||||
class OversamplingEngine;
|
||||
#endif
|
||||
|
||||
//===============================================================================
|
||||
/**
|
||||
A processing class performing multi-channel oversampling.
|
||||
|
||||
It can be configured to do 2 times, 4 times, 8 times or 16 times oversampling
|
||||
using a multi-stage approach, either polyphase allpass IIR filters or FIR
|
||||
filters for the filtering, and reports successfully the latency added by the
|
||||
filter stages.
|
||||
|
||||
The principle of oversampling is to increase the sample rate of a given
|
||||
non-linear process, to prevent it from creating aliasing. Oversampling works
|
||||
by upsampling N times the input signal, processing the upsampling signal
|
||||
with the increased internal sample rate, and downsample the result to get
|
||||
back the original processing sample rate.
|
||||
|
||||
Choose between FIR or IIR filtering depending on your needs in term of
|
||||
latency and phase distortion. With FIR filters, the phase is linear but the
|
||||
latency is maximum. With IIR filtering, the phase is compromised around the
|
||||
Nyquist frequency but the latency is minimum.
|
||||
|
||||
@see FilterDesign.
|
||||
|
||||
@tags{DSP}
|
||||
*/
|
||||
template <typename SampleType>
|
||||
class JUCE_API Oversampling
|
||||
{
|
||||
public:
|
||||
/** The type of filter that can be used for the oversampling processing. */
|
||||
enum FilterType
|
||||
{
|
||||
filterHalfBandFIREquiripple = 0,
|
||||
filterHalfBandPolyphaseIIR,
|
||||
numFilterTypes
|
||||
};
|
||||
|
||||
//===============================================================================
|
||||
/**
|
||||
Constructor of the oversampling class. All the processing parameters must be
|
||||
provided at the creation of the oversampling object.
|
||||
|
||||
Note : you might want to create a class heriting from Oversampling with a
|
||||
different constructor if you need more control on what happens in the process.
|
||||
|
||||
@param numChannels the number of channels to process with this object
|
||||
@param factor the processing will perform 2 ^ factor times oversampling
|
||||
@param type the type of filter design employed for filtering during
|
||||
oversampling
|
||||
@param isMaxQuality if the oversampling is done using the maximum quality,
|
||||
the filters will be more efficient, but the CPU load will
|
||||
increase as well
|
||||
*/
|
||||
Oversampling (size_t numChannels, size_t factor, FilterType type, bool isMaxQuality = true);
|
||||
|
||||
/** Destructor. */
|
||||
~Oversampling();
|
||||
|
||||
//===============================================================================
|
||||
/** Returns the latency in samples of the whole processing. Use this information
|
||||
in your main processor to compensate the additional latency involved with
|
||||
the oversampling, for example with a dry / wet functionality, and to report
|
||||
the latency to the DAW.
|
||||
|
||||
Note : the latency might not be integer, so you might need to round its value
|
||||
or to compensate it properly in your processing code.
|
||||
*/
|
||||
SampleType getLatencyInSamples() noexcept;
|
||||
|
||||
/** Returns the current oversampling factor. */
|
||||
size_t getOversamplingFactor() noexcept;
|
||||
|
||||
//===============================================================================
|
||||
/** Must be called before any processing, to set the buffer sizes of the internal
|
||||
buffers of the oversampling processing.
|
||||
*/
|
||||
void initProcessing (size_t maximumNumberOfSamplesBeforeOversampling);
|
||||
|
||||
/** Resets the processing pipeline, ready to oversample a new stream of data. */
|
||||
void reset() noexcept;
|
||||
|
||||
/** Must be called to perform the upsampling, prior to any oversampled processing.
|
||||
|
||||
Returns an AudioBlock referencing the oversampled input signal, which must be
|
||||
used to perform the non-linear processing which needs the higher sample rate.
|
||||
Don't forget to set the sample rate of that processing to N times the original
|
||||
sample rate.
|
||||
*/
|
||||
dsp::AudioBlock<SampleType> processSamplesUp (const dsp::AudioBlock<SampleType> &inputBlock) noexcept;
|
||||
|
||||
/** Must be called to perform the downsampling, after the upsampling and the
|
||||
non-linear processing. The output signal is probably delayed by the internal
|
||||
latency of the whole oversampling behaviour, so don't forget to take this
|
||||
into account.
|
||||
*/
|
||||
void processSamplesDown (dsp::AudioBlock<SampleType> &outputBlock) noexcept;
|
||||
|
||||
private:
|
||||
//===============================================================================
|
||||
bool isMaximumQuality;
|
||||
size_t factorOversampling, numStages;
|
||||
FilterType type;
|
||||
size_t numChannels;
|
||||
|
||||
//===============================================================================
|
||||
bool isReady = false;
|
||||
|
||||
OwnedArray<OversamplingEngine<SampleType>> engines;
|
||||
|
||||
//===============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Oversampling)
|
||||
};
|
||||
|
||||
} // namespace dsp
|
||||
} // namespace juce
|
170
modules/juce_dsp/processors/juce_ProcessContext.h
Normal file
170
modules/juce_dsp/processors/juce_ProcessContext.h
Normal file
@ -0,0 +1,170 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
namespace dsp
|
||||
{
|
||||
|
||||
/**
|
||||
This structure is passed into a DSP algorithm's prepare() method, and contains
|
||||
information about various aspects of the context in which it can expect to be called.
|
||||
|
||||
@tags{DSP}
|
||||
*/
|
||||
struct ProcessSpec
|
||||
{
|
||||
/** The sample rate that will be used for the data that is sent to the processor. */
|
||||
double sampleRate;
|
||||
|
||||
/** The maximum number of samples that will be in the blocks sent to process() method. */
|
||||
uint32 maximumBlockSize;
|
||||
|
||||
/** The number of channels that the process() method will be expected to handle. */
|
||||
uint32 numChannels;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
This is a handy base class for the state of a processor (such as parameter values)
|
||||
which is typically shared among several procoessors. This is useful to for
|
||||
multi-mono filters which share the same state among several mono processors.
|
||||
|
||||
@tags{DSP}
|
||||
*/
|
||||
struct ProcessorState : public ReferenceCountedObject
|
||||
{
|
||||
/** The ProcessorState structure is ref-counted, so this is a handy type that can be used
|
||||
as a pointer to one.
|
||||
*/
|
||||
using Ptr = ReferenceCountedObjectPtr<ProcessorState>;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Contains context information that is passed into an algorithm's process method.
|
||||
|
||||
This context is intended for use in situations where a single block is being used
|
||||
for both the input and output, so it will return the same object for both its
|
||||
getInputBlock() and getOutputBlock() methods.
|
||||
|
||||
@see ProcessContextNonReplacing
|
||||
|
||||
@tags{DSP}
|
||||
*/
|
||||
template <typename ContextSampleType>
|
||||
struct ProcessContextReplacing
|
||||
{
|
||||
public:
|
||||
/** The type of a single sample (which may be a vector if multichannel). */
|
||||
using SampleType = ContextSampleType;
|
||||
/** The type of audio block that this context handles. */
|
||||
using AudioBlockType = AudioBlock<SampleType>;
|
||||
|
||||
/** Creates a ProcessContextReplacing that uses the given audio block.
|
||||
Note that the caller must not delete the block while it is still in use by this object!
|
||||
*/
|
||||
ProcessContextReplacing (AudioBlockType& block) noexcept : ioBlock (block) {}
|
||||
|
||||
ProcessContextReplacing (const ProcessContextReplacing&) = default;
|
||||
ProcessContextReplacing (ProcessContextReplacing&&) = default;
|
||||
|
||||
/** Returns the audio block to use as the input to a process function. */
|
||||
const AudioBlockType& getInputBlock() const noexcept { return ioBlock; }
|
||||
|
||||
/** Returns the audio block to use as the output to a process function. */
|
||||
AudioBlockType& getOutputBlock() const noexcept { return const_cast<AudioBlockType&> (ioBlock); }
|
||||
|
||||
/** All process context classes will define this constant method so that templated
|
||||
code can determine whether the input and output blocks refer to the same buffer,
|
||||
or to two different ones.
|
||||
*/
|
||||
static constexpr bool usesSeparateInputAndOutputBlocks() { return false; }
|
||||
|
||||
/** If set to true, then a processor's process() method is expected to do whatever
|
||||
is appropriate for it to be in a bypassed state.
|
||||
*/
|
||||
bool isBypassed = false;
|
||||
|
||||
private:
|
||||
AudioBlockType& ioBlock;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Contains context information that is passed into an algorithm's process method.
|
||||
|
||||
This context is intended for use in situations where two different blocks are being
|
||||
used the input and output to the process algorithm, so the processor must read from
|
||||
the block returned by getInputBlock() and write its results to the block returned by
|
||||
getOutputBlock().
|
||||
|
||||
@see ProcessContextReplacing
|
||||
|
||||
@tags{DSP}
|
||||
*/
|
||||
template <typename ContextSampleType>
|
||||
struct ProcessContextNonReplacing
|
||||
{
|
||||
public:
|
||||
/** The type of a single sample (which may be a vector if multichannel). */
|
||||
using SampleType = ContextSampleType;
|
||||
/** The type of audio block that this context handles. */
|
||||
using AudioBlockType = AudioBlock<SampleType>;
|
||||
|
||||
/** Creates a ProcessContextReplacing that uses the given input and output blocks.
|
||||
Note that the caller must not delete these blocks while they are still in use by this object!
|
||||
*/
|
||||
ProcessContextNonReplacing (const AudioBlockType& input, AudioBlockType& output) noexcept
|
||||
: inputBlock (input), outputBlock (output) {}
|
||||
|
||||
ProcessContextNonReplacing (const ProcessContextNonReplacing&) = default;
|
||||
ProcessContextNonReplacing (ProcessContextNonReplacing&&) = default;
|
||||
|
||||
/** Returns the audio block to use as the input to a process function. */
|
||||
const AudioBlockType& getInputBlock() const noexcept { return inputBlock; }
|
||||
|
||||
/** Returns the audio block to use as the output to a process function. */
|
||||
AudioBlockType& getOutputBlock() const noexcept { return const_cast<AudioBlockType&> (outputBlock); }
|
||||
|
||||
/** All process context classes will define this constant method so that templated
|
||||
code can determine whether the input and output blocks refer to the same buffer,
|
||||
or to two different ones.
|
||||
*/
|
||||
static constexpr bool usesSeparateInputAndOutputBlocks() { return true; }
|
||||
|
||||
/** If set to true, then a processor's process() method is expected to do whatever
|
||||
is appropriate for it to be in a bypassed state.
|
||||
*/
|
||||
bool isBypassed = false;
|
||||
|
||||
private:
|
||||
const AudioBlockType& inputBlock;
|
||||
AudioBlockType& outputBlock;
|
||||
};
|
||||
|
||||
} // namespace dsp
|
||||
} // namespace juce
|
128
modules/juce_dsp/processors/juce_ProcessorChain.h
Normal file
128
modules/juce_dsp/processors/juce_ProcessorChain.h
Normal file
@ -0,0 +1,128 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
namespace dsp
|
||||
{
|
||||
|
||||
#ifndef DOXYGEN
|
||||
namespace ProcessorHelpers // Internal helper classes used in building the ProcessorChain
|
||||
{
|
||||
template <int arg>
|
||||
struct AccessHelper
|
||||
{
|
||||
template <typename ProcessorType>
|
||||
static auto& get (ProcessorType& a) noexcept { return AccessHelper<arg - 1>::get (a.processors); }
|
||||
|
||||
template <typename ProcessorType>
|
||||
static void setBypassed (ProcessorType& a, bool bypassed) { AccessHelper<arg - 1>::setBypassed (a.processors, bypassed); }
|
||||
};
|
||||
|
||||
template <>
|
||||
struct AccessHelper<0>
|
||||
{
|
||||
template <typename ProcessorType>
|
||||
static auto& get (ProcessorType& a) noexcept { return a.getProcessor(); }
|
||||
|
||||
template <typename ProcessorType>
|
||||
static void setBypassed (ProcessorType& a, bool bypassed) { a.isBypassed = bypassed; }
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
template <bool isFirst, typename Processor, typename Subclass>
|
||||
struct ChainElement
|
||||
{
|
||||
void prepare (const ProcessSpec& spec)
|
||||
{
|
||||
processor.prepare (spec);
|
||||
}
|
||||
|
||||
template <typename ProcessContext>
|
||||
void process (const ProcessContext& context) noexcept
|
||||
{
|
||||
if (context.usesSeparateInputAndOutputBlocks() && ! isFirst)
|
||||
{
|
||||
jassert (context.getOutputBlock().getNumChannels() == context.getInputBlock().getNumChannels());
|
||||
ProcessContextReplacing<typename ProcessContext::SampleType> replacingContext (context.getOutputBlock());
|
||||
replacingContext.isBypassed = (isBypassed || context.isBypassed);
|
||||
|
||||
processor.process (replacingContext);
|
||||
}
|
||||
else
|
||||
{
|
||||
ProcessContext contextCopy (context);
|
||||
contextCopy.isBypassed = (isBypassed || context.isBypassed);
|
||||
|
||||
processor.process (contextCopy);
|
||||
}
|
||||
}
|
||||
|
||||
void reset()
|
||||
{
|
||||
processor.reset();
|
||||
}
|
||||
|
||||
bool isBypassed = false;
|
||||
Processor processor;
|
||||
|
||||
Processor& getProcessor() noexcept { return processor; }
|
||||
Subclass& getThis() noexcept { return *static_cast<Subclass*> (this); }
|
||||
|
||||
template <int arg> auto& get() noexcept { return AccessHelper<arg>::get (getThis()); }
|
||||
template <int arg> void setBypassed (bool bypassed) noexcept { AccessHelper<arg>::setBypassed (getThis(), bypassed); }
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
template <bool isFirst, typename FirstProcessor, typename... SubsequentProcessors>
|
||||
struct ChainBase : public ChainElement<isFirst, FirstProcessor, ChainBase<isFirst, FirstProcessor, SubsequentProcessors...>>
|
||||
{
|
||||
using Base = ChainElement<isFirst, FirstProcessor, ChainBase<isFirst, FirstProcessor, SubsequentProcessors...>>;
|
||||
|
||||
template <typename ProcessContext>
|
||||
void process (const ProcessContext& context) noexcept { Base::process (context); processors.process (context); }
|
||||
void prepare (const ProcessSpec& spec) { Base::prepare (spec); processors.prepare (spec); }
|
||||
void reset() { Base::reset(); processors.reset(); }
|
||||
|
||||
ChainBase<false, SubsequentProcessors...> processors;
|
||||
};
|
||||
|
||||
template <bool isFirst, typename ProcessorType>
|
||||
struct ChainBase<isFirst, ProcessorType> : public ChainElement<isFirst, ProcessorType, ChainBase<isFirst, ProcessorType>> {};
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
This variadically-templated class lets you join together any number of processor
|
||||
classes into a single processor which will call process() on them all in sequence.
|
||||
*/
|
||||
template <typename... Processors>
|
||||
using ProcessorChain = ProcessorHelpers::ChainBase<true, Processors...>;
|
||||
|
||||
} // namespace dsp
|
||||
} // namespace juce
|
99
modules/juce_dsp/processors/juce_ProcessorDuplicator.h
Normal file
99
modules/juce_dsp/processors/juce_ProcessorDuplicator.h
Normal file
@ -0,0 +1,99 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
namespace dsp
|
||||
{
|
||||
|
||||
/**
|
||||
Converts a mono processor class into a multi-channel version by duplicating it
|
||||
and applying multichannel buffers across an array of instances.
|
||||
|
||||
When the prepare method is called, it uses the specified number of channels to
|
||||
instantiate the appropriate number of instances, which it then uses in its
|
||||
process() method.
|
||||
|
||||
@tags{DSP}
|
||||
*/
|
||||
template <typename MonoProcessorType, typename StateType>
|
||||
struct ProcessorDuplicator
|
||||
{
|
||||
ProcessorDuplicator() : state (new StateType()) {}
|
||||
ProcessorDuplicator (StateType* stateToUse) : state (stateToUse) {}
|
||||
ProcessorDuplicator (const ProcessorDuplicator&) = default;
|
||||
ProcessorDuplicator (ProcessorDuplicator&&) = default;
|
||||
|
||||
void prepare (const ProcessSpec& spec)
|
||||
{
|
||||
processors.removeRange ((int) spec.numChannels, processors.size());
|
||||
|
||||
while (static_cast<size_t> (processors.size()) < spec.numChannels)
|
||||
processors.add (new MonoProcessorType (state));
|
||||
|
||||
auto monoSpec = spec;
|
||||
monoSpec.numChannels = 1;
|
||||
|
||||
for (auto* p : processors)
|
||||
p->prepare (monoSpec);
|
||||
}
|
||||
|
||||
void reset() noexcept { for (auto* p : processors) p->reset(); }
|
||||
|
||||
template<typename ProcessContext>
|
||||
void process (const ProcessContext& context) noexcept
|
||||
{
|
||||
jassert ((int) context.getInputBlock().getNumChannels() <= processors.size());
|
||||
jassert ((int) context.getOutputBlock().getNumChannels() <= processors.size());
|
||||
|
||||
auto numChannels = static_cast<size_t> (jmin (context.getInputBlock().getNumChannels(),
|
||||
context.getOutputBlock().getNumChannels()));
|
||||
|
||||
for (size_t chan = 0; chan < numChannels; ++chan)
|
||||
processors[(int) chan]->process (MonoProcessContext<ProcessContext> (context, chan));
|
||||
}
|
||||
|
||||
typename StateType::Ptr state;
|
||||
|
||||
private:
|
||||
template <typename ProcessContext>
|
||||
struct MonoProcessContext : public ProcessContext
|
||||
{
|
||||
MonoProcessContext (const ProcessContext& multiChannelContext, size_t channelToUse)
|
||||
: ProcessContext (multiChannelContext), channel (channelToUse)
|
||||
{}
|
||||
|
||||
size_t channel;
|
||||
|
||||
typename ProcessContext::AudioBlockType getInputBlock() const noexcept { return ProcessContext::getInputBlock().getSingleChannelBlock (channel); }
|
||||
typename ProcessContext::AudioBlockType getOutputBlock() const noexcept { return ProcessContext::getOutputBlock().getSingleChannelBlock (channel); }
|
||||
};
|
||||
|
||||
juce::OwnedArray<MonoProcessorType> processors;
|
||||
};
|
||||
|
||||
} // namespace dsp
|
||||
} // namespace juce
|
82
modules/juce_dsp/processors/juce_ProcessorWrapper.h
Normal file
82
modules/juce_dsp/processors/juce_ProcessorWrapper.h
Normal file
@ -0,0 +1,82 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
namespace dsp
|
||||
{
|
||||
|
||||
/**
|
||||
Acts as a polymorphic base class for processors.
|
||||
This exposes the same set of methods that a processor must implement as virtual
|
||||
methods, so that you can use the ProcessorWrapper class to wrap an instance of
|
||||
a subclass, and then pass that around using ProcessorBase as a base class.
|
||||
@see ProcessorWrapper
|
||||
|
||||
@tags{DSP}
|
||||
*/
|
||||
struct ProcessorBase
|
||||
{
|
||||
ProcessorBase() = default;
|
||||
virtual ~ProcessorBase() = default;
|
||||
|
||||
virtual void prepare (const ProcessSpec&) = 0;
|
||||
virtual void process (const ProcessContextReplacing<float>&) = 0;
|
||||
virtual void reset() = 0;
|
||||
};
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Wraps an instance of a given processor class, and exposes it through the
|
||||
ProcessorBase interface.
|
||||
@see ProcessorBase
|
||||
|
||||
@tags{DSP}
|
||||
*/
|
||||
template <typename ProcessorType>
|
||||
struct ProcessorWrapper : public ProcessorBase
|
||||
{
|
||||
void prepare (const ProcessSpec& spec) override
|
||||
{
|
||||
processor.prepare (spec);
|
||||
}
|
||||
|
||||
void process (const ProcessContextReplacing<float>& context) override
|
||||
{
|
||||
processor.process (context);
|
||||
}
|
||||
|
||||
void reset() override
|
||||
{
|
||||
processor.reset();
|
||||
}
|
||||
|
||||
ProcessorType processor;
|
||||
};
|
||||
|
||||
} // namespace dsp
|
||||
} // namespace juce
|
117
modules/juce_dsp/processors/juce_Reverb.h
Normal file
117
modules/juce_dsp/processors/juce_Reverb.h
Normal file
@ -0,0 +1,117 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
namespace dsp
|
||||
{
|
||||
|
||||
/**
|
||||
Processor wrapper around juce::Reverb for easy integration into ProcessorChain.
|
||||
|
||||
@tags{DSP}
|
||||
*/
|
||||
class Reverb
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates an uninitialised Reverb processor. Call prepare() before first use. */
|
||||
Reverb()
|
||||
{}
|
||||
|
||||
//==============================================================================
|
||||
using Parameters = juce::Reverb::Parameters;
|
||||
|
||||
/** Returns the reverb's current parameters. */
|
||||
const Parameters& getParameters() const noexcept { return reverb.getParameters(); }
|
||||
|
||||
/** 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) { reverb.setParameters (newParams); }
|
||||
|
||||
/** Returns true if the reverb is enabled. */
|
||||
bool isEnabled() const noexcept { return enabled; }
|
||||
|
||||
/** Enables/disables the reverb. */
|
||||
void setEnabled (bool newValue) noexcept { enabled = newValue; }
|
||||
|
||||
//==============================================================================
|
||||
/** Initialises the reverb. */
|
||||
void prepare (const juce::dsp::ProcessSpec& spec)
|
||||
{
|
||||
reverb.setSampleRate (spec.sampleRate);
|
||||
}
|
||||
|
||||
/** Resets the reverb's internal state. */
|
||||
void reset() noexcept
|
||||
{
|
||||
reverb.reset();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
/** Applies the reverb to a mono or stereo buffer. */
|
||||
template <typename ProcessContext>
|
||||
void process (const ProcessContext& context) noexcept
|
||||
{
|
||||
const auto& inputBlock = context.getInputBlock();
|
||||
auto& outputBlock = context.getOutputBlock();
|
||||
const auto numInChannels = inputBlock.getNumChannels();
|
||||
const auto numOutChannels = outputBlock.getNumChannels();
|
||||
const auto numSamples = outputBlock.getNumSamples();
|
||||
|
||||
jassert (inputBlock.getNumSamples() == numSamples);
|
||||
|
||||
outputBlock.copy (inputBlock);
|
||||
|
||||
if (! enabled || context.isBypassed)
|
||||
return;
|
||||
|
||||
if (numInChannels == 1 && numOutChannels == 1)
|
||||
{
|
||||
reverb.processMono (outputBlock.getChannelPointer (0), (int) numSamples);
|
||||
}
|
||||
else if (numInChannels == 2 && numOutChannels == 2)
|
||||
{
|
||||
reverb.processStereo (outputBlock.getChannelPointer (0),
|
||||
outputBlock.getChannelPointer (1),
|
||||
(int) numSamples);
|
||||
}
|
||||
else
|
||||
{
|
||||
jassertfalse; // invalid channel configuration
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
juce::Reverb reverb;
|
||||
bool enabled = true;
|
||||
};
|
||||
|
||||
} // namespace dsp
|
||||
} // namespace juce
|
238
modules/juce_dsp/processors/juce_StateVariableFilter.h
Normal file
238
modules/juce_dsp/processors/juce_StateVariableFilter.h
Normal file
@ -0,0 +1,238 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
namespace dsp
|
||||
{
|
||||
|
||||
/**
|
||||
Classes for state variable filter processing.
|
||||
*/
|
||||
namespace StateVariableFilter
|
||||
{
|
||||
template <typename NumericType>
|
||||
struct Parameters;
|
||||
|
||||
/**
|
||||
An IIR filter that can perform low, band and high-pass filtering on an audio
|
||||
signal, with 12 dB of attenuation / octave, using a TPT structure, designed
|
||||
for fast modulation (see Vadim Zavalishin's documentation about TPT
|
||||
structures for more information). Its behaviour is based on the analog
|
||||
state variable filter circuit.
|
||||
|
||||
Note : the bandpass here is not the one in the RBJ CookBook, its gain can be
|
||||
higher than 0 dB. For the classic 0 dB bandpass, we need to multiply the
|
||||
result with R2
|
||||
|
||||
@tags{DSP}
|
||||
*/
|
||||
template <typename SampleType>
|
||||
class Filter
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** The NumericType is the underlying primitive type used by the SampleType (which
|
||||
could be either a primitive or vector)
|
||||
*/
|
||||
using NumericType = typename SampleTypeHelpers::ElementType<SampleType>::Type;
|
||||
|
||||
//==============================================================================
|
||||
/** Creates a filter with default parameters. */
|
||||
Filter() : parameters (new Parameters<NumericType>) { reset(); }
|
||||
|
||||
Filter (Parameters<NumericType>* paramtersToUse) : parameters (paramtersToUse) { reset(); }
|
||||
|
||||
/** Creates a copy of another filter. */
|
||||
Filter (const Filter&) = default;
|
||||
|
||||
/** Move constructor */
|
||||
Filter (Filter&&) = default;
|
||||
|
||||
//==============================================================================
|
||||
/** Initialization of the filter */
|
||||
void prepare (const ProcessSpec&) noexcept { reset(); }
|
||||
|
||||
/** Resets the filter's processing pipeline. */
|
||||
void reset() noexcept { s1 = s2 = SampleType {0}; }
|
||||
|
||||
/** Ensure that the state variables are rounded to zero if the state
|
||||
variables are denormals. This is only needed if you are doing
|
||||
sample by sample processing.
|
||||
*/
|
||||
void snapToZero() noexcept { util::snapToZero (s1); util::snapToZero (s2); }
|
||||
|
||||
//==============================================================================
|
||||
/** The parameters of the state variable filter. It's up to the called to ensure
|
||||
that these parameters are modified in a thread-safe way. */
|
||||
typename Parameters<NumericType>::Ptr parameters;
|
||||
|
||||
//==============================================================================
|
||||
template <typename ProcessContext>
|
||||
void process (const ProcessContext& context) noexcept
|
||||
{
|
||||
static_assert (std::is_same<typename ProcessContext::SampleType, SampleType>::value,
|
||||
"The sample-type of the filter must match the sample-type supplied to this process callback");
|
||||
|
||||
if (context.isBypassed)
|
||||
processInternal<true, ProcessContext> (context);
|
||||
else
|
||||
processInternal<false, ProcessContext> (context);
|
||||
}
|
||||
|
||||
/** Processes a single sample, without any locking or checking.
|
||||
Use this if you need processing of a single value. */
|
||||
SampleType JUCE_VECTOR_CALLTYPE processSample (SampleType sample) noexcept
|
||||
{
|
||||
switch (parameters->type)
|
||||
{
|
||||
case Parameters<NumericType>::Type::lowPass: return processLoop<false, Parameters<NumericType>::Type::lowPass> (sample, *parameters); break;
|
||||
case Parameters<NumericType>::Type::bandPass: return processLoop<false, Parameters<NumericType>::Type::bandPass> (sample, *parameters); break;
|
||||
case Parameters<NumericType>::Type::highPass: return processLoop<false, Parameters<NumericType>::Type::highPass> (sample, *parameters); break;
|
||||
default: jassertfalse;
|
||||
}
|
||||
|
||||
return SampleType{0};
|
||||
}
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
template <bool isBypassed, typename Parameters<NumericType>::Type type>
|
||||
SampleType JUCE_VECTOR_CALLTYPE processLoop (SampleType sample, Parameters<NumericType>& state) noexcept
|
||||
{
|
||||
y[2] = (sample - s1 * state.R2 - s1 * state.g - s2) * state.h;
|
||||
|
||||
y[1] = y[2] * state.g + s1;
|
||||
s1 = y[2] * state.g + y[1];
|
||||
|
||||
y[0] = y[1] * state.g + s2;
|
||||
s2 = y[1] * state.g + y[0];
|
||||
|
||||
return isBypassed ? sample : y[static_cast<size_t> (type)];
|
||||
}
|
||||
|
||||
template <bool isBypassed, typename Parameters<NumericType>::Type type>
|
||||
void processBlock (const SampleType* input, SampleType* output, size_t n) noexcept
|
||||
{
|
||||
auto state = *parameters;
|
||||
|
||||
for (size_t i = 0 ; i < n; ++i)
|
||||
output[i] = processLoop<isBypassed, type> (input[i], state);
|
||||
|
||||
snapToZero();
|
||||
*parameters = state;
|
||||
}
|
||||
|
||||
template <bool isBypassed, typename ProcessContext>
|
||||
void processInternal (const ProcessContext& context) noexcept
|
||||
{
|
||||
auto&& inputBlock = context.getInputBlock();
|
||||
auto&& outputBlock = context.getOutputBlock();
|
||||
|
||||
// This class can only process mono signals. Use the ProcessorDuplicator class
|
||||
// to apply this filter on a multi-channel audio stream.
|
||||
jassert (inputBlock.getNumChannels() == 1);
|
||||
jassert (outputBlock.getNumChannels() == 1);
|
||||
|
||||
auto n = inputBlock.getNumSamples();
|
||||
auto* src = inputBlock .getChannelPointer (0);
|
||||
auto* dst = outputBlock.getChannelPointer (0);
|
||||
|
||||
switch (parameters->type)
|
||||
{
|
||||
case Parameters<NumericType>::Type::lowPass: processBlock<isBypassed, Parameters<NumericType>::Type::lowPass> (src, dst, n); break;
|
||||
case Parameters<NumericType>::Type::bandPass: processBlock<isBypassed, Parameters<NumericType>::Type::bandPass> (src, dst, n); break;
|
||||
case Parameters<NumericType>::Type::highPass: processBlock<isBypassed, Parameters<NumericType>::Type::highPass> (src, dst, n); break;
|
||||
default: jassertfalse;
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
std::array<SampleType, 3> y;
|
||||
SampleType s1, s2;
|
||||
|
||||
//==============================================================================
|
||||
JUCE_LEAK_DETECTOR (Filter)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Structure used for the state variable filter parameters.
|
||||
|
||||
@tags{DSP}
|
||||
*/
|
||||
template <typename NumericType>
|
||||
struct Parameters : public ProcessorState
|
||||
{
|
||||
//==============================================================================
|
||||
enum class Type
|
||||
{
|
||||
lowPass,
|
||||
bandPass,
|
||||
highPass
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** The type of the IIR filter */
|
||||
Type type = Type::lowPass;
|
||||
|
||||
/** Sets the cutoff frequency and resonance of the IIR filter.
|
||||
Note : the bandwidth of the resonance increases with the value of the
|
||||
parameter. To have a standard 12 dB/octave filter, the value must be set
|
||||
at 1 / sqrt(2).
|
||||
*/
|
||||
void setCutOffFrequency (double sampleRate, NumericType frequency,
|
||||
NumericType resonance = static_cast<NumericType> (1.0 / MathConstants<double>::sqrt2)) noexcept
|
||||
{
|
||||
jassert (sampleRate > 0);
|
||||
jassert (resonance > NumericType (0));
|
||||
jassert (frequency > NumericType (0) && frequency <= NumericType (sampleRate * 0.5));
|
||||
|
||||
g = static_cast<NumericType> (std::tan (MathConstants<double>::pi * frequency / sampleRate));
|
||||
R2 = static_cast<NumericType> (1.0 / resonance);
|
||||
h = static_cast<NumericType> (1.0 / (1.0 + R2 * g + g * g));
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
/** The Coefficients structure is ref-counted, so this is a handy type that can be used
|
||||
as a pointer to one.
|
||||
*/
|
||||
using Ptr = ReferenceCountedObjectPtr<Parameters>;
|
||||
|
||||
//==============================================================================
|
||||
Parameters() = default;
|
||||
Parameters (const Parameters& o) : g (o.g), R2 (o.R2), h (o.h) {}
|
||||
Parameters& operator= (const Parameters& o) noexcept { g = o.g; R2 = o.R2; h = o.h; return *this; }
|
||||
|
||||
//==============================================================================
|
||||
NumericType g = static_cast<NumericType> (std::tan (MathConstants<double>::pi * 200.0 / 44100.0));
|
||||
NumericType R2 = static_cast<NumericType> (MathConstants<double>::sqrt2);
|
||||
NumericType h = static_cast<NumericType> (1.0 / (1.0 + R2 * g + g * g));
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace dsp
|
||||
} // namespace juce
|
86
modules/juce_dsp/processors/juce_WaveShaper.h
Normal file
86
modules/juce_dsp/processors/juce_WaveShaper.h
Normal file
@ -0,0 +1,86 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
|
||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||||
27th April 2017).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-5-licence
|
||||
Privacy Policy: www.juce.com/juce-5-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
namespace dsp
|
||||
{
|
||||
|
||||
/**
|
||||
Applies waveshaping to audio samples as single samples or AudioBlocks.
|
||||
|
||||
@tags{DSP}
|
||||
*/
|
||||
template <typename FloatType, typename Function = FloatType (*) (FloatType)>
|
||||
struct WaveShaper
|
||||
{
|
||||
Function functionToUse;
|
||||
|
||||
//==============================================================================
|
||||
/** Called before processing starts. */
|
||||
void prepare (const ProcessSpec&) noexcept {}
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the result of processing a single sample. */
|
||||
template <typename SampleType>
|
||||
SampleType JUCE_VECTOR_CALLTYPE processSample (SampleType inputSample) const noexcept
|
||||
{
|
||||
return functionToUse (inputSample);
|
||||
}
|
||||
|
||||
/** Processes the input and output buffers supplied in the processing context. */
|
||||
template <typename ProcessContext>
|
||||
void process (const ProcessContext& context) const noexcept
|
||||
{
|
||||
if (context.isBypassed)
|
||||
{
|
||||
if (context.usesSeparateInputAndOutputBlocks())
|
||||
context.getOutputBlock().copy (context.getInputBlock());
|
||||
}
|
||||
else
|
||||
{
|
||||
AudioBlock<FloatType>::process (context.getInputBlock(),
|
||||
context.getOutputBlock(),
|
||||
functionToUse);
|
||||
}
|
||||
}
|
||||
|
||||
void reset() noexcept {}
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
// Although clang supports C++17, their standard library still has no invoke_result
|
||||
// support. Remove the "|| JUCE_CLANG" once clang supports this properly!
|
||||
#if (! JUCE_CXX17_IS_AVAILABLE) || JUCE_CLANG
|
||||
template <typename Functor>
|
||||
static WaveShaper<typename std::result_of<Functor>, Functor> CreateWaveShaper (Functor functionToUse) { return {functionToUse}; }
|
||||
#else
|
||||
template <typename Functor>
|
||||
static WaveShaper<typename std::invoke_result<Functor>, Functor> CreateWaveShaper (Functor functionToUse) { return {functionToUse}; }
|
||||
#endif
|
||||
|
||||
} // namespace dsp
|
||||
} // namespace juce
|
Reference in New Issue
Block a user