migrating to the latest JUCE version
This commit is contained in:
@ -1,130 +1,130 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
namespace dsp
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
template <typename SampleType>
|
||||
BallisticsFilter<SampleType>::BallisticsFilter()
|
||||
{
|
||||
setAttackTime (attackTime);
|
||||
setReleaseTime (releaseTime);
|
||||
}
|
||||
|
||||
template <typename SampleType>
|
||||
void BallisticsFilter<SampleType>::setAttackTime (SampleType attackTimeMs)
|
||||
{
|
||||
attackTime = attackTimeMs;
|
||||
cteAT = calculateLimitedCte (static_cast<SampleType> (attackTime));
|
||||
}
|
||||
|
||||
template <typename SampleType>
|
||||
void BallisticsFilter<SampleType>::setReleaseTime (SampleType releaseTimeMs)
|
||||
{
|
||||
releaseTime = releaseTimeMs;
|
||||
cteRL = calculateLimitedCte (static_cast<SampleType> (releaseTime));
|
||||
}
|
||||
|
||||
template <typename SampleType>
|
||||
void BallisticsFilter<SampleType>::setLevelCalculationType (LevelCalculationType newLevelType)
|
||||
{
|
||||
levelType = newLevelType;
|
||||
reset();
|
||||
}
|
||||
|
||||
template <typename SampleType>
|
||||
void BallisticsFilter<SampleType>::prepare (const ProcessSpec& spec)
|
||||
{
|
||||
jassert (spec.sampleRate > 0);
|
||||
jassert (spec.numChannels > 0);
|
||||
|
||||
sampleRate = spec.sampleRate;
|
||||
expFactor = -2.0 * MathConstants<double>::pi * 1000.0 / sampleRate;
|
||||
|
||||
setAttackTime (attackTime);
|
||||
setReleaseTime (releaseTime);
|
||||
|
||||
yold.resize (spec.numChannels);
|
||||
|
||||
reset();
|
||||
}
|
||||
|
||||
template <typename SampleType>
|
||||
void BallisticsFilter<SampleType>::reset()
|
||||
{
|
||||
reset (0);
|
||||
}
|
||||
|
||||
template <typename SampleType>
|
||||
void BallisticsFilter<SampleType>::reset (SampleType initialValue)
|
||||
{
|
||||
for (auto& old : yold)
|
||||
old = initialValue;
|
||||
}
|
||||
|
||||
template <typename SampleType>
|
||||
SampleType BallisticsFilter<SampleType>::processSample (int channel, SampleType inputValue)
|
||||
{
|
||||
jassert (isPositiveAndBelow (channel, yold.size()));
|
||||
|
||||
if (levelType == LevelCalculationType::RMS)
|
||||
inputValue *= inputValue;
|
||||
else
|
||||
inputValue = std::abs (inputValue);
|
||||
|
||||
SampleType cte = (inputValue > yold[(size_t) channel] ? cteAT : cteRL);
|
||||
|
||||
SampleType result = inputValue + cte * (yold[(size_t) channel] - inputValue);
|
||||
yold[(size_t) channel] = result;
|
||||
|
||||
if (levelType == LevelCalculationType::RMS)
|
||||
return std::sqrt (result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename SampleType>
|
||||
void BallisticsFilter<SampleType>::snapToZero() noexcept
|
||||
{
|
||||
for (auto& old : yold)
|
||||
util::snapToZero (old);
|
||||
}
|
||||
|
||||
template <typename SampleType>
|
||||
SampleType BallisticsFilter<SampleType>::calculateLimitedCte (SampleType timeMs) const noexcept
|
||||
{
|
||||
return timeMs < static_cast<SampleType> (1.0e-3) ? 0
|
||||
: static_cast<SampleType> (std::exp (expFactor / timeMs));
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
template class BallisticsFilter<float>;
|
||||
template class BallisticsFilter<double>;
|
||||
|
||||
} // namespace dsp
|
||||
} // namespace juce
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
namespace dsp
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
template <typename SampleType>
|
||||
BallisticsFilter<SampleType>::BallisticsFilter()
|
||||
{
|
||||
setAttackTime (attackTime);
|
||||
setReleaseTime (releaseTime);
|
||||
}
|
||||
|
||||
template <typename SampleType>
|
||||
void BallisticsFilter<SampleType>::setAttackTime (SampleType attackTimeMs)
|
||||
{
|
||||
attackTime = attackTimeMs;
|
||||
cteAT = calculateLimitedCte (static_cast<SampleType> (attackTime));
|
||||
}
|
||||
|
||||
template <typename SampleType>
|
||||
void BallisticsFilter<SampleType>::setReleaseTime (SampleType releaseTimeMs)
|
||||
{
|
||||
releaseTime = releaseTimeMs;
|
||||
cteRL = calculateLimitedCte (static_cast<SampleType> (releaseTime));
|
||||
}
|
||||
|
||||
template <typename SampleType>
|
||||
void BallisticsFilter<SampleType>::setLevelCalculationType (LevelCalculationType newLevelType)
|
||||
{
|
||||
levelType = newLevelType;
|
||||
reset();
|
||||
}
|
||||
|
||||
template <typename SampleType>
|
||||
void BallisticsFilter<SampleType>::prepare (const ProcessSpec& spec)
|
||||
{
|
||||
jassert (spec.sampleRate > 0);
|
||||
jassert (spec.numChannels > 0);
|
||||
|
||||
sampleRate = spec.sampleRate;
|
||||
expFactor = -2.0 * MathConstants<double>::pi * 1000.0 / sampleRate;
|
||||
|
||||
setAttackTime (attackTime);
|
||||
setReleaseTime (releaseTime);
|
||||
|
||||
yold.resize (spec.numChannels);
|
||||
|
||||
reset();
|
||||
}
|
||||
|
||||
template <typename SampleType>
|
||||
void BallisticsFilter<SampleType>::reset()
|
||||
{
|
||||
reset (0);
|
||||
}
|
||||
|
||||
template <typename SampleType>
|
||||
void BallisticsFilter<SampleType>::reset (SampleType initialValue)
|
||||
{
|
||||
for (auto& old : yold)
|
||||
old = initialValue;
|
||||
}
|
||||
|
||||
template <typename SampleType>
|
||||
SampleType BallisticsFilter<SampleType>::processSample (int channel, SampleType inputValue)
|
||||
{
|
||||
jassert (isPositiveAndBelow (channel, yold.size()));
|
||||
|
||||
if (levelType == LevelCalculationType::RMS)
|
||||
inputValue *= inputValue;
|
||||
else
|
||||
inputValue = std::abs (inputValue);
|
||||
|
||||
SampleType cte = (inputValue > yold[(size_t) channel] ? cteAT : cteRL);
|
||||
|
||||
SampleType result = inputValue + cte * (yold[(size_t) channel] - inputValue);
|
||||
yold[(size_t) channel] = result;
|
||||
|
||||
if (levelType == LevelCalculationType::RMS)
|
||||
return std::sqrt (result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename SampleType>
|
||||
void BallisticsFilter<SampleType>::snapToZero() noexcept
|
||||
{
|
||||
for (auto& old : yold)
|
||||
util::snapToZero (old);
|
||||
}
|
||||
|
||||
template <typename SampleType>
|
||||
SampleType BallisticsFilter<SampleType>::calculateLimitedCte (SampleType timeMs) const noexcept
|
||||
{
|
||||
return timeMs < static_cast<SampleType> (1.0e-3) ? 0
|
||||
: static_cast<SampleType> (std::exp (expFactor / timeMs));
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
template class BallisticsFilter<float>;
|
||||
template class BallisticsFilter<double>;
|
||||
|
||||
} // namespace dsp
|
||||
} // namespace juce
|
||||
|
@ -1,150 +1,150 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
namespace dsp
|
||||
{
|
||||
|
||||
enum class BallisticsFilterLevelCalculationType
|
||||
{
|
||||
peak,
|
||||
RMS
|
||||
};
|
||||
|
||||
/**
|
||||
A processor to apply standard attack / release ballistics to an input signal.
|
||||
This is useful in dynamics processors, envelope followers, modulated audio
|
||||
effects and for smoothing animation in data visualisation.
|
||||
|
||||
@tags{DSP}
|
||||
*/
|
||||
template <typename SampleType>
|
||||
class BallisticsFilter
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
using LevelCalculationType = BallisticsFilterLevelCalculationType;
|
||||
|
||||
//==============================================================================
|
||||
/** Constructor. */
|
||||
BallisticsFilter();
|
||||
|
||||
//==============================================================================
|
||||
/** Sets the attack time in ms.
|
||||
|
||||
Attack times less than 0.001 ms will be snapped to zero and very long attack
|
||||
times will eventually saturate depending on the numerical precision used.
|
||||
*/
|
||||
void setAttackTime (SampleType attackTimeMs);
|
||||
|
||||
/** Sets the release time in ms.
|
||||
|
||||
Release times less than 0.001 ms will be snapped to zero and very long
|
||||
release times will eventually saturate depending on the numerical precision
|
||||
used.
|
||||
*/
|
||||
void setReleaseTime (SampleType releaseTimeMs);
|
||||
|
||||
/** Sets how the filter levels are calculated.
|
||||
|
||||
Level calculation in digital envelope followers is usually performed using
|
||||
peak detection with a rectifier function (like std::abs) and filtering,
|
||||
which returns an envelope dependant on the peak or maximum values of the
|
||||
signal amplitude.
|
||||
|
||||
To perform an estimation of the average value of the signal you can use
|
||||
an RMS (root mean squared) implementation of the ballistics filter instead.
|
||||
This is useful in some compressor and noise-gate designs, or in specific
|
||||
types of volume meters.
|
||||
*/
|
||||
void setLevelCalculationType (LevelCalculationType newCalculationType);
|
||||
|
||||
//==============================================================================
|
||||
/** Initialises the filter. */
|
||||
void prepare (const ProcessSpec& spec);
|
||||
|
||||
/** Resets the internal state variables of the filter. */
|
||||
void reset();
|
||||
|
||||
/** Resets the internal state variables of the filter to the given initial value. */
|
||||
void reset (SampleType initialValue);
|
||||
|
||||
//==============================================================================
|
||||
/** Processes the input and output samples supplied in the processing context. */
|
||||
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() <= yold.size());
|
||||
jassert (inputBlock.getNumChannels() == numChannels);
|
||||
jassert (inputBlock.getNumSamples() == numSamples);
|
||||
|
||||
if (context.isBypassed)
|
||||
{
|
||||
outputBlock.copyFrom (inputBlock);
|
||||
return;
|
||||
}
|
||||
|
||||
for (size_t channel = 0; channel < numChannels; ++channel)
|
||||
{
|
||||
auto* inputSamples = inputBlock .getChannelPointer (channel);
|
||||
auto* outputSamples = outputBlock.getChannelPointer (channel);
|
||||
|
||||
for (size_t i = 0; i < numSamples; ++i)
|
||||
outputSamples[i] = processSample ((int) channel, inputSamples[i]);
|
||||
}
|
||||
|
||||
#if JUCE_DSP_ENABLE_SNAP_TO_ZERO
|
||||
snapToZero();
|
||||
#endif
|
||||
}
|
||||
|
||||
/** Processes one sample at a time on a given channel. */
|
||||
SampleType processSample (int channel, SampleType inputValue);
|
||||
|
||||
/** 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:
|
||||
//==============================================================================
|
||||
SampleType calculateLimitedCte (SampleType) const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
std::vector<SampleType> yold;
|
||||
double sampleRate = 44100.0, expFactor = -0.142;
|
||||
SampleType attackTime = 1.0, releaseTime = 100.0, cteAT = 0.0, cteRL = 0.0;
|
||||
LevelCalculationType levelType = LevelCalculationType::peak;
|
||||
};
|
||||
|
||||
} // namespace dsp
|
||||
} // namespace juce
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
namespace dsp
|
||||
{
|
||||
|
||||
enum class BallisticsFilterLevelCalculationType
|
||||
{
|
||||
peak,
|
||||
RMS
|
||||
};
|
||||
|
||||
/**
|
||||
A processor to apply standard attack / release ballistics to an input signal.
|
||||
This is useful in dynamics processors, envelope followers, modulated audio
|
||||
effects and for smoothing animation in data visualisation.
|
||||
|
||||
@tags{DSP}
|
||||
*/
|
||||
template <typename SampleType>
|
||||
class BallisticsFilter
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
using LevelCalculationType = BallisticsFilterLevelCalculationType;
|
||||
|
||||
//==============================================================================
|
||||
/** Constructor. */
|
||||
BallisticsFilter();
|
||||
|
||||
//==============================================================================
|
||||
/** Sets the attack time in ms.
|
||||
|
||||
Attack times less than 0.001 ms will be snapped to zero and very long attack
|
||||
times will eventually saturate depending on the numerical precision used.
|
||||
*/
|
||||
void setAttackTime (SampleType attackTimeMs);
|
||||
|
||||
/** Sets the release time in ms.
|
||||
|
||||
Release times less than 0.001 ms will be snapped to zero and very long
|
||||
release times will eventually saturate depending on the numerical precision
|
||||
used.
|
||||
*/
|
||||
void setReleaseTime (SampleType releaseTimeMs);
|
||||
|
||||
/** Sets how the filter levels are calculated.
|
||||
|
||||
Level calculation in digital envelope followers is usually performed using
|
||||
peak detection with a rectifier function (like std::abs) and filtering,
|
||||
which returns an envelope dependant on the peak or maximum values of the
|
||||
signal amplitude.
|
||||
|
||||
To perform an estimation of the average value of the signal you can use
|
||||
an RMS (root mean squared) implementation of the ballistics filter instead.
|
||||
This is useful in some compressor and noise-gate designs, or in specific
|
||||
types of volume meters.
|
||||
*/
|
||||
void setLevelCalculationType (LevelCalculationType newCalculationType);
|
||||
|
||||
//==============================================================================
|
||||
/** Initialises the filter. */
|
||||
void prepare (const ProcessSpec& spec);
|
||||
|
||||
/** Resets the internal state variables of the filter. */
|
||||
void reset();
|
||||
|
||||
/** Resets the internal state variables of the filter to the given initial value. */
|
||||
void reset (SampleType initialValue);
|
||||
|
||||
//==============================================================================
|
||||
/** Processes the input and output samples supplied in the processing context. */
|
||||
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() <= yold.size());
|
||||
jassert (inputBlock.getNumChannels() == numChannels);
|
||||
jassert (inputBlock.getNumSamples() == numSamples);
|
||||
|
||||
if (context.isBypassed)
|
||||
{
|
||||
outputBlock.copyFrom (inputBlock);
|
||||
return;
|
||||
}
|
||||
|
||||
for (size_t channel = 0; channel < numChannels; ++channel)
|
||||
{
|
||||
auto* inputSamples = inputBlock .getChannelPointer (channel);
|
||||
auto* outputSamples = outputBlock.getChannelPointer (channel);
|
||||
|
||||
for (size_t i = 0; i < numSamples; ++i)
|
||||
outputSamples[i] = processSample ((int) channel, inputSamples[i]);
|
||||
}
|
||||
|
||||
#if JUCE_DSP_ENABLE_SNAP_TO_ZERO
|
||||
snapToZero();
|
||||
#endif
|
||||
}
|
||||
|
||||
/** Processes one sample at a time on a given channel. */
|
||||
SampleType processSample (int channel, SampleType inputValue);
|
||||
|
||||
/** 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:
|
||||
//==============================================================================
|
||||
SampleType calculateLimitedCte (SampleType) const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
std::vector<SampleType> yold;
|
||||
double sampleRate = 44100.0, expFactor = -0.142;
|
||||
SampleType attackTime = 1.0, releaseTime = 100.0, cteAT = 0.0, cteRL = 0.0;
|
||||
LevelCalculationType levelType = LevelCalculationType::peak;
|
||||
};
|
||||
|
||||
} // namespace dsp
|
||||
} // namespace juce
|
||||
|
@ -1,138 +1,138 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
namespace dsp
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
template <typename SampleType, typename InterpolationType>
|
||||
DelayLine<SampleType, InterpolationType>::DelayLine()
|
||||
: DelayLine (0)
|
||||
{
|
||||
}
|
||||
|
||||
template <typename SampleType, typename InterpolationType>
|
||||
DelayLine<SampleType, InterpolationType>::DelayLine (int maximumDelayInSamples)
|
||||
{
|
||||
jassert (maximumDelayInSamples >= 0);
|
||||
|
||||
sampleRate = 44100.0;
|
||||
|
||||
setMaximumDelayInSamples (maximumDelayInSamples);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
template <typename SampleType, typename InterpolationType>
|
||||
void DelayLine<SampleType, InterpolationType>::setDelay (SampleType newDelayInSamples)
|
||||
{
|
||||
auto upperLimit = (SampleType) getMaximumDelayInSamples();
|
||||
jassert (isPositiveAndNotGreaterThan (newDelayInSamples, upperLimit));
|
||||
|
||||
delay = jlimit ((SampleType) 0, upperLimit, newDelayInSamples);
|
||||
delayInt = static_cast<int> (std::floor (delay));
|
||||
delayFrac = delay - (SampleType) delayInt;
|
||||
|
||||
updateInternalVariables();
|
||||
}
|
||||
|
||||
template <typename SampleType, typename InterpolationType>
|
||||
SampleType DelayLine<SampleType, InterpolationType>::getDelay() const
|
||||
{
|
||||
return delay;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
template <typename SampleType, typename InterpolationType>
|
||||
void DelayLine<SampleType, InterpolationType>::prepare (const ProcessSpec& spec)
|
||||
{
|
||||
jassert (spec.numChannels > 0);
|
||||
|
||||
bufferData.setSize ((int) spec.numChannels, totalSize, false, false, true);
|
||||
|
||||
writePos.resize (spec.numChannels);
|
||||
readPos.resize (spec.numChannels);
|
||||
|
||||
v.resize (spec.numChannels);
|
||||
sampleRate = spec.sampleRate;
|
||||
|
||||
reset();
|
||||
}
|
||||
|
||||
template <typename SampleType, typename InterpolationType>
|
||||
void DelayLine<SampleType, InterpolationType>::setMaximumDelayInSamples (int maxDelayInSamples)
|
||||
{
|
||||
jassert (maxDelayInSamples >= 0);
|
||||
totalSize = jmax (4, maxDelayInSamples + 1);
|
||||
bufferData.setSize ((int) bufferData.getNumChannels(), totalSize, false, false, true);
|
||||
reset();
|
||||
}
|
||||
|
||||
template <typename SampleType, typename InterpolationType>
|
||||
void DelayLine<SampleType, InterpolationType>::reset()
|
||||
{
|
||||
for (auto vec : { &writePos, &readPos })
|
||||
std::fill (vec->begin(), vec->end(), 0);
|
||||
|
||||
std::fill (v.begin(), v.end(), static_cast<SampleType> (0));
|
||||
|
||||
bufferData.clear();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
template <typename SampleType, typename InterpolationType>
|
||||
void DelayLine<SampleType, InterpolationType>::pushSample (int channel, SampleType sample)
|
||||
{
|
||||
bufferData.setSample (channel, writePos[(size_t) channel], sample);
|
||||
writePos[(size_t) channel] = (writePos[(size_t) channel] + totalSize - 1) % totalSize;
|
||||
}
|
||||
|
||||
template <typename SampleType, typename InterpolationType>
|
||||
SampleType DelayLine<SampleType, InterpolationType>::popSample (int channel, SampleType delayInSamples, bool updateReadPointer)
|
||||
{
|
||||
if (delayInSamples >= 0)
|
||||
setDelay(delayInSamples);
|
||||
|
||||
auto result = interpolateSample (channel);
|
||||
|
||||
if (updateReadPointer)
|
||||
readPos[(size_t) channel] = (readPos[(size_t) channel] + totalSize - 1) % totalSize;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
template class DelayLine<float, DelayLineInterpolationTypes::None>;
|
||||
template class DelayLine<double, DelayLineInterpolationTypes::None>;
|
||||
template class DelayLine<float, DelayLineInterpolationTypes::Linear>;
|
||||
template class DelayLine<double, DelayLineInterpolationTypes::Linear>;
|
||||
template class DelayLine<float, DelayLineInterpolationTypes::Lagrange3rd>;
|
||||
template class DelayLine<double, DelayLineInterpolationTypes::Lagrange3rd>;
|
||||
template class DelayLine<float, DelayLineInterpolationTypes::Thiran>;
|
||||
template class DelayLine<double, DelayLineInterpolationTypes::Thiran>;
|
||||
|
||||
} // namespace dsp
|
||||
} // namespace juce
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
namespace dsp
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
template <typename SampleType, typename InterpolationType>
|
||||
DelayLine<SampleType, InterpolationType>::DelayLine()
|
||||
: DelayLine (0)
|
||||
{
|
||||
}
|
||||
|
||||
template <typename SampleType, typename InterpolationType>
|
||||
DelayLine<SampleType, InterpolationType>::DelayLine (int maximumDelayInSamples)
|
||||
{
|
||||
jassert (maximumDelayInSamples >= 0);
|
||||
|
||||
sampleRate = 44100.0;
|
||||
|
||||
setMaximumDelayInSamples (maximumDelayInSamples);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
template <typename SampleType, typename InterpolationType>
|
||||
void DelayLine<SampleType, InterpolationType>::setDelay (SampleType newDelayInSamples)
|
||||
{
|
||||
auto upperLimit = (SampleType) getMaximumDelayInSamples();
|
||||
jassert (isPositiveAndNotGreaterThan (newDelayInSamples, upperLimit));
|
||||
|
||||
delay = jlimit ((SampleType) 0, upperLimit, newDelayInSamples);
|
||||
delayInt = static_cast<int> (std::floor (delay));
|
||||
delayFrac = delay - (SampleType) delayInt;
|
||||
|
||||
updateInternalVariables();
|
||||
}
|
||||
|
||||
template <typename SampleType, typename InterpolationType>
|
||||
SampleType DelayLine<SampleType, InterpolationType>::getDelay() const
|
||||
{
|
||||
return delay;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
template <typename SampleType, typename InterpolationType>
|
||||
void DelayLine<SampleType, InterpolationType>::prepare (const ProcessSpec& spec)
|
||||
{
|
||||
jassert (spec.numChannels > 0);
|
||||
|
||||
bufferData.setSize ((int) spec.numChannels, totalSize, false, false, true);
|
||||
|
||||
writePos.resize (spec.numChannels);
|
||||
readPos.resize (spec.numChannels);
|
||||
|
||||
v.resize (spec.numChannels);
|
||||
sampleRate = spec.sampleRate;
|
||||
|
||||
reset();
|
||||
}
|
||||
|
||||
template <typename SampleType, typename InterpolationType>
|
||||
void DelayLine<SampleType, InterpolationType>::setMaximumDelayInSamples (int maxDelayInSamples)
|
||||
{
|
||||
jassert (maxDelayInSamples >= 0);
|
||||
totalSize = jmax (4, maxDelayInSamples + 1);
|
||||
bufferData.setSize ((int) bufferData.getNumChannels(), totalSize, false, false, true);
|
||||
reset();
|
||||
}
|
||||
|
||||
template <typename SampleType, typename InterpolationType>
|
||||
void DelayLine<SampleType, InterpolationType>::reset()
|
||||
{
|
||||
for (auto vec : { &writePos, &readPos })
|
||||
std::fill (vec->begin(), vec->end(), 0);
|
||||
|
||||
std::fill (v.begin(), v.end(), static_cast<SampleType> (0));
|
||||
|
||||
bufferData.clear();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
template <typename SampleType, typename InterpolationType>
|
||||
void DelayLine<SampleType, InterpolationType>::pushSample (int channel, SampleType sample)
|
||||
{
|
||||
bufferData.setSample (channel, writePos[(size_t) channel], sample);
|
||||
writePos[(size_t) channel] = (writePos[(size_t) channel] + totalSize - 1) % totalSize;
|
||||
}
|
||||
|
||||
template <typename SampleType, typename InterpolationType>
|
||||
SampleType DelayLine<SampleType, InterpolationType>::popSample (int channel, SampleType delayInSamples, bool updateReadPointer)
|
||||
{
|
||||
if (delayInSamples >= 0)
|
||||
setDelay(delayInSamples);
|
||||
|
||||
auto result = interpolateSample (channel);
|
||||
|
||||
if (updateReadPointer)
|
||||
readPos[(size_t) channel] = (readPos[(size_t) channel] + totalSize - 1) % totalSize;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
template class DelayLine<float, DelayLineInterpolationTypes::None>;
|
||||
template class DelayLine<double, DelayLineInterpolationTypes::None>;
|
||||
template class DelayLine<float, DelayLineInterpolationTypes::Linear>;
|
||||
template class DelayLine<double, DelayLineInterpolationTypes::Linear>;
|
||||
template class DelayLine<float, DelayLineInterpolationTypes::Lagrange3rd>;
|
||||
template class DelayLine<double, DelayLineInterpolationTypes::Lagrange3rd>;
|
||||
template class DelayLine<float, DelayLineInterpolationTypes::Thiran>;
|
||||
template class DelayLine<double, DelayLineInterpolationTypes::Thiran>;
|
||||
|
||||
} // namespace dsp
|
||||
} // namespace juce
|
||||
|
@ -1,339 +1,339 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
namespace dsp
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A collection of structs to pass as the template argument when setting the
|
||||
interpolation type for the DelayLine class.
|
||||
*/
|
||||
namespace DelayLineInterpolationTypes
|
||||
{
|
||||
/**
|
||||
No interpolation between successive samples in the delay line will be
|
||||
performed. This is useful when the delay is a constant integer or to
|
||||
create lo-fi audio effects.
|
||||
|
||||
@tags{DSP}
|
||||
*/
|
||||
struct None {};
|
||||
|
||||
/**
|
||||
Successive samples in the delay line will be linearly interpolated. This
|
||||
type of interpolation has a low compuational cost where the delay can be
|
||||
modulated in real time, but it also introduces a low-pass filtering effect
|
||||
into your audio signal.
|
||||
|
||||
@tags{DSP}
|
||||
*/
|
||||
struct Linear {};
|
||||
|
||||
/**
|
||||
Successive samples in the delay line will be interpolated using a 3rd order
|
||||
Lagrange interpolator. This method incurs more computational overhead than
|
||||
linear interpolation but reduces the low-pass filtering effect whilst
|
||||
remaining amenable to real time delay modulation.
|
||||
|
||||
@tags{DSP}
|
||||
*/
|
||||
struct Lagrange3rd {};
|
||||
|
||||
/**
|
||||
Successive samples in the delay line will be interpolated using 1st order
|
||||
Thiran interpolation. This method is very efficient, and features a flat
|
||||
amplitude frequency response in exchange for less accuracy in the phase
|
||||
response. This interpolation method is stateful so is unsuitable for
|
||||
applications requiring fast delay modulation.
|
||||
|
||||
@tags{DSP}
|
||||
*/
|
||||
struct Thiran {};
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A delay line processor featuring several algorithms for the fractional delay
|
||||
calculation, block processing, and sample-by-sample processing useful when
|
||||
modulating the delay in real time or creating a standard delay effect with
|
||||
feedback.
|
||||
|
||||
Note: If you intend to change the delay in real time, you may want to smooth
|
||||
changes to the delay systematically using either a ramp or a low-pass filter.
|
||||
|
||||
@see SmoothedValue, FirstOrderTPTFilter
|
||||
|
||||
@tags{DSP}
|
||||
*/
|
||||
template <typename SampleType, typename InterpolationType = DelayLineInterpolationTypes::Linear>
|
||||
class DelayLine
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Default constructor. */
|
||||
DelayLine();
|
||||
|
||||
/** Constructor. */
|
||||
explicit DelayLine (int maximumDelayInSamples);
|
||||
|
||||
//==============================================================================
|
||||
/** Sets the delay in samples. */
|
||||
void setDelay (SampleType newDelayInSamples);
|
||||
|
||||
/** Returns the current delay in samples. */
|
||||
SampleType getDelay() const;
|
||||
|
||||
//==============================================================================
|
||||
/** Initialises the processor. */
|
||||
void prepare (const ProcessSpec& spec);
|
||||
|
||||
/** Sets a new maximum delay in samples.
|
||||
|
||||
Also clears the delay line.
|
||||
|
||||
This may allocate internally, so you should never call it from the audio thread.
|
||||
*/
|
||||
void setMaximumDelayInSamples (int maxDelayInSamples);
|
||||
|
||||
/** Gets the maximum possible delay in samples.
|
||||
|
||||
For very short delay times, the result of getMaximumDelayInSamples() may
|
||||
differ from the last value passed to setMaximumDelayInSamples().
|
||||
*/
|
||||
int getMaximumDelayInSamples() const noexcept { return totalSize - 1; }
|
||||
|
||||
/** Resets the internal state variables of the processor. */
|
||||
void reset();
|
||||
|
||||
//==============================================================================
|
||||
/** Pushes a single sample into one channel of the delay line.
|
||||
|
||||
Use this function and popSample instead of process if you need to modulate
|
||||
the delay in real time instead of using a fixed delay value, or if you want
|
||||
to code a delay effect with a feedback loop.
|
||||
|
||||
@see setDelay, popSample, process
|
||||
*/
|
||||
void pushSample (int channel, SampleType sample);
|
||||
|
||||
/** Pops a single sample from one channel of the delay line.
|
||||
|
||||
Use this function to modulate the delay in real time or implement standard
|
||||
delay effects with feedback.
|
||||
|
||||
@param channel the target channel for the delay line.
|
||||
|
||||
@param delayInSamples sets the wanted fractional delay in samples, or -1
|
||||
to use the value being used before or set with
|
||||
setDelay function.
|
||||
|
||||
@param updateReadPointer should be set to true if you use the function
|
||||
once for each sample, or false if you need
|
||||
multi-tap delay capabilities.
|
||||
|
||||
@see setDelay, pushSample, process
|
||||
*/
|
||||
SampleType popSample (int channel, SampleType delayInSamples = -1, bool updateReadPointer = true);
|
||||
|
||||
//==============================================================================
|
||||
/** Processes the input and output samples supplied in the processing context.
|
||||
|
||||
Can be used for block processing when the delay is not going to change
|
||||
during processing. The delay must first be set by calling setDelay.
|
||||
|
||||
@see setDelay
|
||||
*/
|
||||
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() == numChannels);
|
||||
jassert (inputBlock.getNumChannels() == writePos.size());
|
||||
jassert (inputBlock.getNumSamples() == numSamples);
|
||||
|
||||
if (context.isBypassed)
|
||||
{
|
||||
outputBlock.copyFrom (inputBlock);
|
||||
return;
|
||||
}
|
||||
|
||||
for (size_t channel = 0; channel < numChannels; ++channel)
|
||||
{
|
||||
auto* inputSamples = inputBlock.getChannelPointer (channel);
|
||||
auto* outputSamples = outputBlock.getChannelPointer (channel);
|
||||
|
||||
for (size_t i = 0; i < numSamples; ++i)
|
||||
{
|
||||
pushSample ((int) channel, inputSamples[i]);
|
||||
outputSamples[i] = popSample ((int) channel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
template <typename T = InterpolationType>
|
||||
typename std::enable_if <std::is_same <T, DelayLineInterpolationTypes::None>::value, SampleType>::type
|
||||
interpolateSample (int channel) const
|
||||
{
|
||||
auto index = (readPos[(size_t) channel] + delayInt) % totalSize;
|
||||
return bufferData.getSample (channel, index);
|
||||
}
|
||||
|
||||
template <typename T = InterpolationType>
|
||||
typename std::enable_if <std::is_same <T, DelayLineInterpolationTypes::Linear>::value, SampleType>::type
|
||||
interpolateSample (int channel) const
|
||||
{
|
||||
auto index1 = readPos[(size_t) channel] + delayInt;
|
||||
auto index2 = index1 + 1;
|
||||
|
||||
if (index2 >= totalSize)
|
||||
{
|
||||
index1 %= totalSize;
|
||||
index2 %= totalSize;
|
||||
}
|
||||
|
||||
auto value1 = bufferData.getSample (channel, index1);
|
||||
auto value2 = bufferData.getSample (channel, index2);
|
||||
|
||||
return value1 + delayFrac * (value2 - value1);
|
||||
}
|
||||
|
||||
template <typename T = InterpolationType>
|
||||
typename std::enable_if <std::is_same <T, DelayLineInterpolationTypes::Lagrange3rd>::value, SampleType>::type
|
||||
interpolateSample (int channel) const
|
||||
{
|
||||
auto index1 = readPos[(size_t) channel] + delayInt;
|
||||
auto index2 = index1 + 1;
|
||||
auto index3 = index2 + 1;
|
||||
auto index4 = index3 + 1;
|
||||
|
||||
if (index4 >= totalSize)
|
||||
{
|
||||
index1 %= totalSize;
|
||||
index2 %= totalSize;
|
||||
index3 %= totalSize;
|
||||
index4 %= totalSize;
|
||||
}
|
||||
|
||||
auto* samples = bufferData.getReadPointer (channel);
|
||||
|
||||
auto value1 = samples[index1];
|
||||
auto value2 = samples[index2];
|
||||
auto value3 = samples[index3];
|
||||
auto value4 = samples[index4];
|
||||
|
||||
auto d1 = delayFrac - 1.f;
|
||||
auto d2 = delayFrac - 2.f;
|
||||
auto d3 = delayFrac - 3.f;
|
||||
|
||||
auto c1 = -d1 * d2 * d3 / 6.f;
|
||||
auto c2 = d2 * d3 * 0.5f;
|
||||
auto c3 = -d1 * d3 * 0.5f;
|
||||
auto c4 = d1 * d2 / 6.f;
|
||||
|
||||
return value1 * c1 + delayFrac * (value2 * c2 + value3 * c3 + value4 * c4);
|
||||
}
|
||||
|
||||
template <typename T = InterpolationType>
|
||||
typename std::enable_if <std::is_same <T, DelayLineInterpolationTypes::Thiran>::value, SampleType>::type
|
||||
interpolateSample (int channel)
|
||||
{
|
||||
auto index1 = readPos[(size_t) channel] + delayInt;
|
||||
auto index2 = index1 + 1;
|
||||
|
||||
if (index2 >= totalSize)
|
||||
{
|
||||
index1 %= totalSize;
|
||||
index2 %= totalSize;
|
||||
}
|
||||
|
||||
auto value1 = bufferData.getSample (channel, index1);
|
||||
auto value2 = bufferData.getSample (channel, index2);
|
||||
|
||||
auto output = delayFrac == 0 ? value1 : value2 + alpha * (value1 - v[(size_t) channel]);
|
||||
v[(size_t) channel] = output;
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
template <typename T = InterpolationType>
|
||||
typename std::enable_if <std::is_same <T, DelayLineInterpolationTypes::None>::value, void>::type
|
||||
updateInternalVariables()
|
||||
{
|
||||
}
|
||||
|
||||
template <typename T = InterpolationType>
|
||||
typename std::enable_if <std::is_same <T, DelayLineInterpolationTypes::Linear>::value, void>::type
|
||||
updateInternalVariables()
|
||||
{
|
||||
}
|
||||
|
||||
template <typename T = InterpolationType>
|
||||
typename std::enable_if <std::is_same <T, DelayLineInterpolationTypes::Lagrange3rd>::value, void>::type
|
||||
updateInternalVariables()
|
||||
{
|
||||
if (delayInt >= 1)
|
||||
{
|
||||
delayFrac++;
|
||||
delayInt--;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T = InterpolationType>
|
||||
typename std::enable_if <std::is_same <T, DelayLineInterpolationTypes::Thiran>::value, void>::type
|
||||
updateInternalVariables()
|
||||
{
|
||||
if (delayFrac < (SampleType) 0.618 && delayInt >= 1)
|
||||
{
|
||||
delayFrac++;
|
||||
delayInt--;
|
||||
}
|
||||
|
||||
alpha = (1 - delayFrac) / (1 + delayFrac);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
double sampleRate;
|
||||
|
||||
//==============================================================================
|
||||
AudioBuffer<SampleType> bufferData;
|
||||
std::vector<SampleType> v;
|
||||
std::vector<int> writePos, readPos;
|
||||
SampleType delay = 0.0, delayFrac = 0.0;
|
||||
int delayInt = 0, totalSize = 4;
|
||||
SampleType alpha = 0.0;
|
||||
};
|
||||
|
||||
} // namespace dsp
|
||||
} // namespace juce
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
namespace dsp
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A collection of structs to pass as the template argument when setting the
|
||||
interpolation type for the DelayLine class.
|
||||
*/
|
||||
namespace DelayLineInterpolationTypes
|
||||
{
|
||||
/**
|
||||
No interpolation between successive samples in the delay line will be
|
||||
performed. This is useful when the delay is a constant integer or to
|
||||
create lo-fi audio effects.
|
||||
|
||||
@tags{DSP}
|
||||
*/
|
||||
struct None {};
|
||||
|
||||
/**
|
||||
Successive samples in the delay line will be linearly interpolated. This
|
||||
type of interpolation has a low computational cost where the delay can be
|
||||
modulated in real time, but it also introduces a low-pass filtering effect
|
||||
into your audio signal.
|
||||
|
||||
@tags{DSP}
|
||||
*/
|
||||
struct Linear {};
|
||||
|
||||
/**
|
||||
Successive samples in the delay line will be interpolated using a 3rd order
|
||||
Lagrange interpolator. This method incurs more computational overhead than
|
||||
linear interpolation but reduces the low-pass filtering effect whilst
|
||||
remaining amenable to real time delay modulation.
|
||||
|
||||
@tags{DSP}
|
||||
*/
|
||||
struct Lagrange3rd {};
|
||||
|
||||
/**
|
||||
Successive samples in the delay line will be interpolated using 1st order
|
||||
Thiran interpolation. This method is very efficient, and features a flat
|
||||
amplitude frequency response in exchange for less accuracy in the phase
|
||||
response. This interpolation method is stateful so is unsuitable for
|
||||
applications requiring fast delay modulation.
|
||||
|
||||
@tags{DSP}
|
||||
*/
|
||||
struct Thiran {};
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A delay line processor featuring several algorithms for the fractional delay
|
||||
calculation, block processing, and sample-by-sample processing useful when
|
||||
modulating the delay in real time or creating a standard delay effect with
|
||||
feedback.
|
||||
|
||||
Note: If you intend to change the delay in real time, you may want to smooth
|
||||
changes to the delay systematically using either a ramp or a low-pass filter.
|
||||
|
||||
@see SmoothedValue, FirstOrderTPTFilter
|
||||
|
||||
@tags{DSP}
|
||||
*/
|
||||
template <typename SampleType, typename InterpolationType = DelayLineInterpolationTypes::Linear>
|
||||
class DelayLine
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Default constructor. */
|
||||
DelayLine();
|
||||
|
||||
/** Constructor. */
|
||||
explicit DelayLine (int maximumDelayInSamples);
|
||||
|
||||
//==============================================================================
|
||||
/** Sets the delay in samples. */
|
||||
void setDelay (SampleType newDelayInSamples);
|
||||
|
||||
/** Returns the current delay in samples. */
|
||||
SampleType getDelay() const;
|
||||
|
||||
//==============================================================================
|
||||
/** Initialises the processor. */
|
||||
void prepare (const ProcessSpec& spec);
|
||||
|
||||
/** Sets a new maximum delay in samples.
|
||||
|
||||
Also clears the delay line.
|
||||
|
||||
This may allocate internally, so you should never call it from the audio thread.
|
||||
*/
|
||||
void setMaximumDelayInSamples (int maxDelayInSamples);
|
||||
|
||||
/** Gets the maximum possible delay in samples.
|
||||
|
||||
For very short delay times, the result of getMaximumDelayInSamples() may
|
||||
differ from the last value passed to setMaximumDelayInSamples().
|
||||
*/
|
||||
int getMaximumDelayInSamples() const noexcept { return totalSize - 1; }
|
||||
|
||||
/** Resets the internal state variables of the processor. */
|
||||
void reset();
|
||||
|
||||
//==============================================================================
|
||||
/** Pushes a single sample into one channel of the delay line.
|
||||
|
||||
Use this function and popSample instead of process if you need to modulate
|
||||
the delay in real time instead of using a fixed delay value, or if you want
|
||||
to code a delay effect with a feedback loop.
|
||||
|
||||
@see setDelay, popSample, process
|
||||
*/
|
||||
void pushSample (int channel, SampleType sample);
|
||||
|
||||
/** Pops a single sample from one channel of the delay line.
|
||||
|
||||
Use this function to modulate the delay in real time or implement standard
|
||||
delay effects with feedback.
|
||||
|
||||
@param channel the target channel for the delay line.
|
||||
|
||||
@param delayInSamples sets the wanted fractional delay in samples, or -1
|
||||
to use the value being used before or set with
|
||||
setDelay function.
|
||||
|
||||
@param updateReadPointer should be set to true if you use the function
|
||||
once for each sample, or false if you need
|
||||
multi-tap delay capabilities.
|
||||
|
||||
@see setDelay, pushSample, process
|
||||
*/
|
||||
SampleType popSample (int channel, SampleType delayInSamples = -1, bool updateReadPointer = true);
|
||||
|
||||
//==============================================================================
|
||||
/** Processes the input and output samples supplied in the processing context.
|
||||
|
||||
Can be used for block processing when the delay is not going to change
|
||||
during processing. The delay must first be set by calling setDelay.
|
||||
|
||||
@see setDelay
|
||||
*/
|
||||
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() == numChannels);
|
||||
jassert (inputBlock.getNumChannels() == writePos.size());
|
||||
jassert (inputBlock.getNumSamples() == numSamples);
|
||||
|
||||
if (context.isBypassed)
|
||||
{
|
||||
outputBlock.copyFrom (inputBlock);
|
||||
return;
|
||||
}
|
||||
|
||||
for (size_t channel = 0; channel < numChannels; ++channel)
|
||||
{
|
||||
auto* inputSamples = inputBlock.getChannelPointer (channel);
|
||||
auto* outputSamples = outputBlock.getChannelPointer (channel);
|
||||
|
||||
for (size_t i = 0; i < numSamples; ++i)
|
||||
{
|
||||
pushSample ((int) channel, inputSamples[i]);
|
||||
outputSamples[i] = popSample ((int) channel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
template <typename T = InterpolationType>
|
||||
typename std::enable_if <std::is_same <T, DelayLineInterpolationTypes::None>::value, SampleType>::type
|
||||
interpolateSample (int channel) const
|
||||
{
|
||||
auto index = (readPos[(size_t) channel] + delayInt) % totalSize;
|
||||
return bufferData.getSample (channel, index);
|
||||
}
|
||||
|
||||
template <typename T = InterpolationType>
|
||||
typename std::enable_if <std::is_same <T, DelayLineInterpolationTypes::Linear>::value, SampleType>::type
|
||||
interpolateSample (int channel) const
|
||||
{
|
||||
auto index1 = readPos[(size_t) channel] + delayInt;
|
||||
auto index2 = index1 + 1;
|
||||
|
||||
if (index2 >= totalSize)
|
||||
{
|
||||
index1 %= totalSize;
|
||||
index2 %= totalSize;
|
||||
}
|
||||
|
||||
auto value1 = bufferData.getSample (channel, index1);
|
||||
auto value2 = bufferData.getSample (channel, index2);
|
||||
|
||||
return value1 + delayFrac * (value2 - value1);
|
||||
}
|
||||
|
||||
template <typename T = InterpolationType>
|
||||
typename std::enable_if <std::is_same <T, DelayLineInterpolationTypes::Lagrange3rd>::value, SampleType>::type
|
||||
interpolateSample (int channel) const
|
||||
{
|
||||
auto index1 = readPos[(size_t) channel] + delayInt;
|
||||
auto index2 = index1 + 1;
|
||||
auto index3 = index2 + 1;
|
||||
auto index4 = index3 + 1;
|
||||
|
||||
if (index4 >= totalSize)
|
||||
{
|
||||
index1 %= totalSize;
|
||||
index2 %= totalSize;
|
||||
index3 %= totalSize;
|
||||
index4 %= totalSize;
|
||||
}
|
||||
|
||||
auto* samples = bufferData.getReadPointer (channel);
|
||||
|
||||
auto value1 = samples[index1];
|
||||
auto value2 = samples[index2];
|
||||
auto value3 = samples[index3];
|
||||
auto value4 = samples[index4];
|
||||
|
||||
auto d1 = delayFrac - 1.f;
|
||||
auto d2 = delayFrac - 2.f;
|
||||
auto d3 = delayFrac - 3.f;
|
||||
|
||||
auto c1 = -d1 * d2 * d3 / 6.f;
|
||||
auto c2 = d2 * d3 * 0.5f;
|
||||
auto c3 = -d1 * d3 * 0.5f;
|
||||
auto c4 = d1 * d2 / 6.f;
|
||||
|
||||
return value1 * c1 + delayFrac * (value2 * c2 + value3 * c3 + value4 * c4);
|
||||
}
|
||||
|
||||
template <typename T = InterpolationType>
|
||||
typename std::enable_if <std::is_same <T, DelayLineInterpolationTypes::Thiran>::value, SampleType>::type
|
||||
interpolateSample (int channel)
|
||||
{
|
||||
auto index1 = readPos[(size_t) channel] + delayInt;
|
||||
auto index2 = index1 + 1;
|
||||
|
||||
if (index2 >= totalSize)
|
||||
{
|
||||
index1 %= totalSize;
|
||||
index2 %= totalSize;
|
||||
}
|
||||
|
||||
auto value1 = bufferData.getSample (channel, index1);
|
||||
auto value2 = bufferData.getSample (channel, index2);
|
||||
|
||||
auto output = delayFrac == 0 ? value1 : value2 + alpha * (value1 - v[(size_t) channel]);
|
||||
v[(size_t) channel] = output;
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
template <typename T = InterpolationType>
|
||||
typename std::enable_if <std::is_same <T, DelayLineInterpolationTypes::None>::value, void>::type
|
||||
updateInternalVariables()
|
||||
{
|
||||
}
|
||||
|
||||
template <typename T = InterpolationType>
|
||||
typename std::enable_if <std::is_same <T, DelayLineInterpolationTypes::Linear>::value, void>::type
|
||||
updateInternalVariables()
|
||||
{
|
||||
}
|
||||
|
||||
template <typename T = InterpolationType>
|
||||
typename std::enable_if <std::is_same <T, DelayLineInterpolationTypes::Lagrange3rd>::value, void>::type
|
||||
updateInternalVariables()
|
||||
{
|
||||
if (delayInt >= 1)
|
||||
{
|
||||
delayFrac++;
|
||||
delayInt--;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T = InterpolationType>
|
||||
typename std::enable_if <std::is_same <T, DelayLineInterpolationTypes::Thiran>::value, void>::type
|
||||
updateInternalVariables()
|
||||
{
|
||||
if (delayFrac < (SampleType) 0.618 && delayInt >= 1)
|
||||
{
|
||||
delayFrac++;
|
||||
delayInt--;
|
||||
}
|
||||
|
||||
alpha = (1 - delayFrac) / (1 + delayFrac);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
double sampleRate;
|
||||
|
||||
//==============================================================================
|
||||
AudioBuffer<SampleType> bufferData;
|
||||
std::vector<SampleType> v;
|
||||
std::vector<int> writePos, readPos;
|
||||
SampleType delay = 0.0, delayFrac = 0.0;
|
||||
int delayInt = 0, totalSize = 4;
|
||||
SampleType alpha = 0.0;
|
||||
};
|
||||
|
||||
} // namespace dsp
|
||||
} // namespace juce
|
||||
|
@ -1,371 +1,371 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
namespace dsp
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
template <typename SampleType>
|
||||
DryWetMixer<SampleType>::DryWetMixer()
|
||||
: DryWetMixer (0)
|
||||
{
|
||||
}
|
||||
|
||||
template <typename SampleType>
|
||||
DryWetMixer<SampleType>::DryWetMixer (int maximumWetLatencyInSamplesIn)
|
||||
: dryDelayLine (maximumWetLatencyInSamplesIn),
|
||||
maximumWetLatencyInSamples (maximumWetLatencyInSamplesIn)
|
||||
{
|
||||
dryDelayLine.setDelay (0);
|
||||
|
||||
update();
|
||||
reset();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
template <typename SampleType>
|
||||
void DryWetMixer<SampleType>::setMixingRule (MixingRule newRule)
|
||||
{
|
||||
currentMixingRule = newRule;
|
||||
update();
|
||||
}
|
||||
|
||||
template <typename SampleType>
|
||||
void DryWetMixer<SampleType>::setWetMixProportion (SampleType newWetMixProportion)
|
||||
{
|
||||
jassert (isPositiveAndNotGreaterThan (newWetMixProportion, 1.0));
|
||||
|
||||
mix = jlimit (static_cast<SampleType> (0.0), static_cast<SampleType> (1.0), newWetMixProportion);
|
||||
update();
|
||||
}
|
||||
|
||||
template <typename SampleType>
|
||||
void DryWetMixer<SampleType>::setWetLatency (SampleType wetLatencySamples)
|
||||
{
|
||||
dryDelayLine.setDelay (wetLatencySamples);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
template <typename SampleType>
|
||||
void DryWetMixer<SampleType>::prepare (const ProcessSpec& spec)
|
||||
{
|
||||
jassert (spec.sampleRate > 0);
|
||||
jassert (spec.numChannels > 0);
|
||||
|
||||
sampleRate = spec.sampleRate;
|
||||
|
||||
dryDelayLine.prepare (spec);
|
||||
bufferDry.setSize ((int) spec.numChannels, (int) spec.maximumBlockSize, false, false, true);
|
||||
|
||||
update();
|
||||
reset();
|
||||
}
|
||||
|
||||
template <typename SampleType>
|
||||
void DryWetMixer<SampleType>::reset()
|
||||
{
|
||||
dryVolume.reset (sampleRate, 0.05);
|
||||
wetVolume.reset (sampleRate, 0.05);
|
||||
|
||||
dryDelayLine.reset();
|
||||
|
||||
fifo = SingleThreadedAbstractFifo (nextPowerOfTwo (bufferDry.getNumSamples()));
|
||||
bufferDry.setSize (bufferDry.getNumChannels(), fifo.getSize(), false, false, true);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
template <typename SampleType>
|
||||
void DryWetMixer<SampleType>::pushDrySamples (const AudioBlock<const SampleType> drySamples)
|
||||
{
|
||||
jassert (drySamples.getNumChannels() <= (size_t) bufferDry.getNumChannels());
|
||||
jassert (drySamples.getNumSamples() <= (size_t) fifo.getRemainingSpace());
|
||||
|
||||
auto offset = 0;
|
||||
|
||||
for (const auto& range : fifo.write ((int) drySamples.getNumSamples()))
|
||||
{
|
||||
if (range.getLength() == 0)
|
||||
continue;
|
||||
|
||||
auto block = AudioBlock<SampleType> (bufferDry).getSubsetChannelBlock (0, drySamples.getNumChannels())
|
||||
.getSubBlock ((size_t) range.getStart(), (size_t) range.getLength());
|
||||
|
||||
auto inputBlock = drySamples.getSubBlock ((size_t) offset, (size_t) range.getLength());
|
||||
|
||||
if (maximumWetLatencyInSamples == 0)
|
||||
block.copyFrom (inputBlock);
|
||||
else
|
||||
dryDelayLine.process (ProcessContextNonReplacing<SampleType> (inputBlock, block));
|
||||
|
||||
offset += range.getLength();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename SampleType>
|
||||
void DryWetMixer<SampleType>::mixWetSamples (AudioBlock<SampleType> inOutBlock)
|
||||
{
|
||||
inOutBlock.multiplyBy (wetVolume);
|
||||
|
||||
jassert (inOutBlock.getNumSamples() <= (size_t) fifo.getNumReadable());
|
||||
|
||||
auto offset = 0;
|
||||
|
||||
for (const auto& range : fifo.read ((int) inOutBlock.getNumSamples()))
|
||||
{
|
||||
if (range.getLength() == 0)
|
||||
continue;
|
||||
|
||||
auto block = AudioBlock<SampleType> (bufferDry).getSubsetChannelBlock (0, inOutBlock.getNumChannels())
|
||||
.getSubBlock ((size_t) range.getStart(), (size_t) range.getLength());
|
||||
block.multiplyBy (dryVolume);
|
||||
inOutBlock.getSubBlock ((size_t) offset).add (block);
|
||||
|
||||
offset += range.getLength();
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
template <typename SampleType>
|
||||
void DryWetMixer<SampleType>::update()
|
||||
{
|
||||
SampleType dryValue, wetValue;
|
||||
|
||||
switch (currentMixingRule)
|
||||
{
|
||||
case MixingRule::balanced:
|
||||
dryValue = static_cast<SampleType> (2.0) * jmin (static_cast<SampleType> (0.5), static_cast<SampleType> (1.0) - mix);
|
||||
wetValue = static_cast<SampleType> (2.0) * jmin (static_cast<SampleType> (0.5), mix);
|
||||
break;
|
||||
|
||||
case MixingRule::linear:
|
||||
dryValue = static_cast<SampleType> (1.0) - mix;
|
||||
wetValue = mix;
|
||||
break;
|
||||
|
||||
case MixingRule::sin3dB:
|
||||
dryValue = static_cast<SampleType> (std::sin (0.5 * MathConstants<double>::pi * (1.0 - mix)));
|
||||
wetValue = static_cast<SampleType> (std::sin (0.5 * MathConstants<double>::pi * mix));
|
||||
break;
|
||||
|
||||
case MixingRule::sin4p5dB:
|
||||
dryValue = static_cast<SampleType> (std::pow (std::sin (0.5 * MathConstants<double>::pi * (1.0 - mix)), 1.5));
|
||||
wetValue = static_cast<SampleType> (std::pow (std::sin (0.5 * MathConstants<double>::pi * mix), 1.5));
|
||||
break;
|
||||
|
||||
case MixingRule::sin6dB:
|
||||
dryValue = static_cast<SampleType> (std::pow (std::sin (0.5 * MathConstants<double>::pi * (1.0 - mix)), 2.0));
|
||||
wetValue = static_cast<SampleType> (std::pow (std::sin (0.5 * MathConstants<double>::pi * mix), 2.0));
|
||||
break;
|
||||
|
||||
case MixingRule::squareRoot3dB:
|
||||
dryValue = std::sqrt (static_cast<SampleType> (1.0) - mix);
|
||||
wetValue = std::sqrt (mix);
|
||||
break;
|
||||
|
||||
case MixingRule::squareRoot4p5dB:
|
||||
dryValue = static_cast<SampleType> (std::pow (std::sqrt (1.0 - mix), 1.5));
|
||||
wetValue = static_cast<SampleType> (std::pow (std::sqrt (mix), 1.5));
|
||||
break;
|
||||
|
||||
default:
|
||||
dryValue = jmin (static_cast<SampleType> (0.5), static_cast<SampleType> (1.0) - mix);
|
||||
wetValue = jmin (static_cast<SampleType> (0.5), mix);
|
||||
break;
|
||||
}
|
||||
|
||||
dryVolume.setTargetValue (dryValue);
|
||||
wetVolume.setTargetValue (wetValue);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
template class DryWetMixer<float>;
|
||||
template class DryWetMixer<double>;
|
||||
|
||||
|
||||
//==============================================================================
|
||||
//==============================================================================
|
||||
#if JUCE_UNIT_TESTS
|
||||
|
||||
struct DryWetMixerTests : public UnitTest
|
||||
{
|
||||
DryWetMixerTests() : UnitTest ("DryWetMixer", UnitTestCategories::dsp) {}
|
||||
|
||||
enum class Kind { down, up };
|
||||
|
||||
static auto getRampBuffer (ProcessSpec spec, Kind kind)
|
||||
{
|
||||
AudioBuffer<float> buffer ((int) spec.numChannels, (int) spec.maximumBlockSize);
|
||||
|
||||
for (uint32_t sample = 0; sample < spec.maximumBlockSize; ++sample)
|
||||
{
|
||||
for (uint32_t channel = 0; channel < spec.numChannels; ++channel)
|
||||
{
|
||||
const auto ramp = kind == Kind::up ? sample : spec.maximumBlockSize - sample;
|
||||
|
||||
buffer.setSample ((int) channel,
|
||||
(int) sample,
|
||||
jmap ((float) ramp, 0.0f, (float) spec.maximumBlockSize, 0.0f, 1.0f));
|
||||
}
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
void runTest() override
|
||||
{
|
||||
constexpr ProcessSpec spec { 44100.0, 512, 2 };
|
||||
constexpr auto numBlocks = 5;
|
||||
|
||||
const auto wetBuffer = getRampBuffer (spec, Kind::up);
|
||||
const auto dryBuffer = getRampBuffer (spec, Kind::down);
|
||||
|
||||
for (auto maxLatency : { 0, 100, 200, 512 })
|
||||
{
|
||||
beginTest ("Mixer can push multiple small buffers");
|
||||
{
|
||||
DryWetMixer<float> mixer (maxLatency);
|
||||
mixer.setWetMixProportion (0.5f);
|
||||
mixer.prepare (spec);
|
||||
|
||||
for (auto block = 0; block < numBlocks; ++block)
|
||||
{
|
||||
// Push samples one-by-one
|
||||
for (uint32_t sample = 0; sample < spec.maximumBlockSize; ++sample)
|
||||
mixer.pushDrySamples (AudioBlock<const float> (dryBuffer).getSubBlock (sample, 1));
|
||||
|
||||
// Mix wet samples in one go
|
||||
auto outputBlock = wetBuffer;
|
||||
mixer.mixWetSamples ({ outputBlock });
|
||||
|
||||
// The output block should contain the wet and dry samples averaged
|
||||
for (uint32_t sample = 0; sample < spec.maximumBlockSize; ++sample)
|
||||
{
|
||||
for (uint32_t channel = 0; channel < spec.numChannels; ++channel)
|
||||
{
|
||||
const auto outputValue = outputBlock.getSample ((int) channel, (int) sample);
|
||||
expectWithinAbsoluteError (outputValue, 0.5f, 0.0001f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
beginTest ("Mixer can pop multiple small buffers");
|
||||
{
|
||||
DryWetMixer<float> mixer (maxLatency);
|
||||
mixer.setWetMixProportion (0.5f);
|
||||
mixer.prepare (spec);
|
||||
|
||||
for (auto block = 0; block < numBlocks; ++block)
|
||||
{
|
||||
// Push samples in one go
|
||||
mixer.pushDrySamples ({ dryBuffer });
|
||||
|
||||
// Process wet samples one-by-one
|
||||
for (uint32_t sample = 0; sample < spec.maximumBlockSize; ++sample)
|
||||
{
|
||||
AudioBuffer<float> outputBlock ((int) spec.numChannels, 1);
|
||||
AudioBlock<const float> (wetBuffer).getSubBlock (sample, 1).copyTo (outputBlock);
|
||||
mixer.mixWetSamples ({ outputBlock });
|
||||
|
||||
// The output block should contain the wet and dry samples averaged
|
||||
for (uint32_t channel = 0; channel < spec.numChannels; ++channel)
|
||||
{
|
||||
const auto outputValue = outputBlock.getSample ((int) channel, 0);
|
||||
expectWithinAbsoluteError (outputValue, 0.5f, 0.0001f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
beginTest ("Mixer can push and pop multiple small buffers");
|
||||
{
|
||||
DryWetMixer<float> mixer (maxLatency);
|
||||
mixer.setWetMixProportion (0.5f);
|
||||
mixer.prepare (spec);
|
||||
|
||||
for (auto block = 0; block < numBlocks; ++block)
|
||||
{
|
||||
// Push dry samples and process wet samples one-by-one
|
||||
for (uint32_t sample = 0; sample < spec.maximumBlockSize; ++sample)
|
||||
{
|
||||
mixer.pushDrySamples (AudioBlock<const float> (dryBuffer).getSubBlock (sample, 1));
|
||||
|
||||
AudioBuffer<float> outputBlock ((int) spec.numChannels, 1);
|
||||
AudioBlock<const float> (wetBuffer).getSubBlock (sample, 1).copyTo (outputBlock);
|
||||
mixer.mixWetSamples ({ outputBlock });
|
||||
|
||||
// The output block should contain the wet and dry samples averaged
|
||||
for (uint32_t channel = 0; channel < spec.numChannels; ++channel)
|
||||
{
|
||||
const auto outputValue = outputBlock.getSample ((int) channel, 0);
|
||||
expectWithinAbsoluteError (outputValue, 0.5f, 0.0001f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
beginTest ("Mixer can push and pop full-sized blocks after encountering a shorter block");
|
||||
{
|
||||
DryWetMixer<float> mixer (maxLatency);
|
||||
mixer.setWetMixProportion (0.5f);
|
||||
mixer.prepare (spec);
|
||||
|
||||
constexpr auto shortBlockLength = spec.maximumBlockSize / 2;
|
||||
AudioBuffer<float> shortBlock (spec.numChannels, shortBlockLength);
|
||||
mixer.pushDrySamples (AudioBlock<const float> (dryBuffer).getSubBlock (shortBlockLength));
|
||||
mixer.mixWetSamples ({ shortBlock });
|
||||
|
||||
for (auto block = 0; block < numBlocks; ++block)
|
||||
{
|
||||
// Push a full block of dry samples
|
||||
mixer.pushDrySamples ({ dryBuffer });
|
||||
|
||||
// Mix a full block of wet samples
|
||||
auto outputBlock = wetBuffer;
|
||||
mixer.mixWetSamples ({ outputBlock });
|
||||
|
||||
// The output block should contain the wet and dry samples averaged
|
||||
for (uint32_t sample = 0; sample < spec.maximumBlockSize; ++sample)
|
||||
{
|
||||
for (uint32_t channel = 0; channel < spec.numChannels; ++channel)
|
||||
{
|
||||
const auto outputValue = outputBlock.getSample ((int) channel, (int) sample);
|
||||
expectWithinAbsoluteError (outputValue, 0.5f, 0.0001f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static const DryWetMixerTests dryWetMixerTests;
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace dsp
|
||||
} // namespace juce
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
namespace dsp
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
template <typename SampleType>
|
||||
DryWetMixer<SampleType>::DryWetMixer()
|
||||
: DryWetMixer (0)
|
||||
{
|
||||
}
|
||||
|
||||
template <typename SampleType>
|
||||
DryWetMixer<SampleType>::DryWetMixer (int maximumWetLatencyInSamplesIn)
|
||||
: dryDelayLine (maximumWetLatencyInSamplesIn),
|
||||
maximumWetLatencyInSamples (maximumWetLatencyInSamplesIn)
|
||||
{
|
||||
dryDelayLine.setDelay (0);
|
||||
|
||||
update();
|
||||
reset();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
template <typename SampleType>
|
||||
void DryWetMixer<SampleType>::setMixingRule (MixingRule newRule)
|
||||
{
|
||||
currentMixingRule = newRule;
|
||||
update();
|
||||
}
|
||||
|
||||
template <typename SampleType>
|
||||
void DryWetMixer<SampleType>::setWetMixProportion (SampleType newWetMixProportion)
|
||||
{
|
||||
jassert (isPositiveAndNotGreaterThan (newWetMixProportion, 1.0));
|
||||
|
||||
mix = jlimit (static_cast<SampleType> (0.0), static_cast<SampleType> (1.0), newWetMixProportion);
|
||||
update();
|
||||
}
|
||||
|
||||
template <typename SampleType>
|
||||
void DryWetMixer<SampleType>::setWetLatency (SampleType wetLatencySamples)
|
||||
{
|
||||
dryDelayLine.setDelay (wetLatencySamples);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
template <typename SampleType>
|
||||
void DryWetMixer<SampleType>::prepare (const ProcessSpec& spec)
|
||||
{
|
||||
jassert (spec.sampleRate > 0);
|
||||
jassert (spec.numChannels > 0);
|
||||
|
||||
sampleRate = spec.sampleRate;
|
||||
|
||||
dryDelayLine.prepare (spec);
|
||||
bufferDry.setSize ((int) spec.numChannels, (int) spec.maximumBlockSize, false, false, true);
|
||||
|
||||
update();
|
||||
reset();
|
||||
}
|
||||
|
||||
template <typename SampleType>
|
||||
void DryWetMixer<SampleType>::reset()
|
||||
{
|
||||
dryVolume.reset (sampleRate, 0.05);
|
||||
wetVolume.reset (sampleRate, 0.05);
|
||||
|
||||
dryDelayLine.reset();
|
||||
|
||||
fifo = SingleThreadedAbstractFifo (nextPowerOfTwo (bufferDry.getNumSamples()));
|
||||
bufferDry.setSize (bufferDry.getNumChannels(), fifo.getSize(), false, false, true);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
template <typename SampleType>
|
||||
void DryWetMixer<SampleType>::pushDrySamples (const AudioBlock<const SampleType> drySamples)
|
||||
{
|
||||
jassert (drySamples.getNumChannels() <= (size_t) bufferDry.getNumChannels());
|
||||
jassert (drySamples.getNumSamples() <= (size_t) fifo.getRemainingSpace());
|
||||
|
||||
auto offset = 0;
|
||||
|
||||
for (const auto& range : fifo.write ((int) drySamples.getNumSamples()))
|
||||
{
|
||||
if (range.getLength() == 0)
|
||||
continue;
|
||||
|
||||
auto block = AudioBlock<SampleType> (bufferDry).getSubsetChannelBlock (0, drySamples.getNumChannels())
|
||||
.getSubBlock ((size_t) range.getStart(), (size_t) range.getLength());
|
||||
|
||||
auto inputBlock = drySamples.getSubBlock ((size_t) offset, (size_t) range.getLength());
|
||||
|
||||
if (maximumWetLatencyInSamples == 0)
|
||||
block.copyFrom (inputBlock);
|
||||
else
|
||||
dryDelayLine.process (ProcessContextNonReplacing<SampleType> (inputBlock, block));
|
||||
|
||||
offset += range.getLength();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename SampleType>
|
||||
void DryWetMixer<SampleType>::mixWetSamples (AudioBlock<SampleType> inOutBlock)
|
||||
{
|
||||
inOutBlock.multiplyBy (wetVolume);
|
||||
|
||||
jassert (inOutBlock.getNumSamples() <= (size_t) fifo.getNumReadable());
|
||||
|
||||
auto offset = 0;
|
||||
|
||||
for (const auto& range : fifo.read ((int) inOutBlock.getNumSamples()))
|
||||
{
|
||||
if (range.getLength() == 0)
|
||||
continue;
|
||||
|
||||
auto block = AudioBlock<SampleType> (bufferDry).getSubsetChannelBlock (0, inOutBlock.getNumChannels())
|
||||
.getSubBlock ((size_t) range.getStart(), (size_t) range.getLength());
|
||||
block.multiplyBy (dryVolume);
|
||||
inOutBlock.getSubBlock ((size_t) offset).add (block);
|
||||
|
||||
offset += range.getLength();
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
template <typename SampleType>
|
||||
void DryWetMixer<SampleType>::update()
|
||||
{
|
||||
SampleType dryValue, wetValue;
|
||||
|
||||
switch (currentMixingRule)
|
||||
{
|
||||
case MixingRule::balanced:
|
||||
dryValue = static_cast<SampleType> (2.0) * jmin (static_cast<SampleType> (0.5), static_cast<SampleType> (1.0) - mix);
|
||||
wetValue = static_cast<SampleType> (2.0) * jmin (static_cast<SampleType> (0.5), mix);
|
||||
break;
|
||||
|
||||
case MixingRule::linear:
|
||||
dryValue = static_cast<SampleType> (1.0) - mix;
|
||||
wetValue = mix;
|
||||
break;
|
||||
|
||||
case MixingRule::sin3dB:
|
||||
dryValue = static_cast<SampleType> (std::sin (0.5 * MathConstants<double>::pi * (1.0 - mix)));
|
||||
wetValue = static_cast<SampleType> (std::sin (0.5 * MathConstants<double>::pi * mix));
|
||||
break;
|
||||
|
||||
case MixingRule::sin4p5dB:
|
||||
dryValue = static_cast<SampleType> (std::pow (std::sin (0.5 * MathConstants<double>::pi * (1.0 - mix)), 1.5));
|
||||
wetValue = static_cast<SampleType> (std::pow (std::sin (0.5 * MathConstants<double>::pi * mix), 1.5));
|
||||
break;
|
||||
|
||||
case MixingRule::sin6dB:
|
||||
dryValue = static_cast<SampleType> (std::pow (std::sin (0.5 * MathConstants<double>::pi * (1.0 - mix)), 2.0));
|
||||
wetValue = static_cast<SampleType> (std::pow (std::sin (0.5 * MathConstants<double>::pi * mix), 2.0));
|
||||
break;
|
||||
|
||||
case MixingRule::squareRoot3dB:
|
||||
dryValue = std::sqrt (static_cast<SampleType> (1.0) - mix);
|
||||
wetValue = std::sqrt (mix);
|
||||
break;
|
||||
|
||||
case MixingRule::squareRoot4p5dB:
|
||||
dryValue = static_cast<SampleType> (std::pow (std::sqrt (1.0 - mix), 1.5));
|
||||
wetValue = static_cast<SampleType> (std::pow (std::sqrt (mix), 1.5));
|
||||
break;
|
||||
|
||||
default:
|
||||
dryValue = jmin (static_cast<SampleType> (0.5), static_cast<SampleType> (1.0) - mix);
|
||||
wetValue = jmin (static_cast<SampleType> (0.5), mix);
|
||||
break;
|
||||
}
|
||||
|
||||
dryVolume.setTargetValue (dryValue);
|
||||
wetVolume.setTargetValue (wetValue);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
template class DryWetMixer<float>;
|
||||
template class DryWetMixer<double>;
|
||||
|
||||
|
||||
//==============================================================================
|
||||
//==============================================================================
|
||||
#if JUCE_UNIT_TESTS
|
||||
|
||||
struct DryWetMixerTests : public UnitTest
|
||||
{
|
||||
DryWetMixerTests() : UnitTest ("DryWetMixer", UnitTestCategories::dsp) {}
|
||||
|
||||
enum class Kind { down, up };
|
||||
|
||||
static auto getRampBuffer (ProcessSpec spec, Kind kind)
|
||||
{
|
||||
AudioBuffer<float> buffer ((int) spec.numChannels, (int) spec.maximumBlockSize);
|
||||
|
||||
for (uint32_t sample = 0; sample < spec.maximumBlockSize; ++sample)
|
||||
{
|
||||
for (uint32_t channel = 0; channel < spec.numChannels; ++channel)
|
||||
{
|
||||
const auto ramp = kind == Kind::up ? sample : spec.maximumBlockSize - sample;
|
||||
|
||||
buffer.setSample ((int) channel,
|
||||
(int) sample,
|
||||
jmap ((float) ramp, 0.0f, (float) spec.maximumBlockSize, 0.0f, 1.0f));
|
||||
}
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
void runTest() override
|
||||
{
|
||||
constexpr ProcessSpec spec { 44100.0, 512, 2 };
|
||||
constexpr auto numBlocks = 5;
|
||||
|
||||
const auto wetBuffer = getRampBuffer (spec, Kind::up);
|
||||
const auto dryBuffer = getRampBuffer (spec, Kind::down);
|
||||
|
||||
for (auto maxLatency : { 0, 100, 200, 512 })
|
||||
{
|
||||
beginTest ("Mixer can push multiple small buffers");
|
||||
{
|
||||
DryWetMixer<float> mixer (maxLatency);
|
||||
mixer.setWetMixProportion (0.5f);
|
||||
mixer.prepare (spec);
|
||||
|
||||
for (auto block = 0; block < numBlocks; ++block)
|
||||
{
|
||||
// Push samples one-by-one
|
||||
for (uint32_t sample = 0; sample < spec.maximumBlockSize; ++sample)
|
||||
mixer.pushDrySamples (AudioBlock<const float> (dryBuffer).getSubBlock (sample, 1));
|
||||
|
||||
// Mix wet samples in one go
|
||||
auto outputBlock = wetBuffer;
|
||||
mixer.mixWetSamples ({ outputBlock });
|
||||
|
||||
// The output block should contain the wet and dry samples averaged
|
||||
for (uint32_t sample = 0; sample < spec.maximumBlockSize; ++sample)
|
||||
{
|
||||
for (uint32_t channel = 0; channel < spec.numChannels; ++channel)
|
||||
{
|
||||
const auto outputValue = outputBlock.getSample ((int) channel, (int) sample);
|
||||
expectWithinAbsoluteError (outputValue, 0.5f, 0.0001f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
beginTest ("Mixer can pop multiple small buffers");
|
||||
{
|
||||
DryWetMixer<float> mixer (maxLatency);
|
||||
mixer.setWetMixProportion (0.5f);
|
||||
mixer.prepare (spec);
|
||||
|
||||
for (auto block = 0; block < numBlocks; ++block)
|
||||
{
|
||||
// Push samples in one go
|
||||
mixer.pushDrySamples ({ dryBuffer });
|
||||
|
||||
// Process wet samples one-by-one
|
||||
for (uint32_t sample = 0; sample < spec.maximumBlockSize; ++sample)
|
||||
{
|
||||
AudioBuffer<float> outputBlock ((int) spec.numChannels, 1);
|
||||
AudioBlock<const float> (wetBuffer).getSubBlock (sample, 1).copyTo (outputBlock);
|
||||
mixer.mixWetSamples ({ outputBlock });
|
||||
|
||||
// The output block should contain the wet and dry samples averaged
|
||||
for (uint32_t channel = 0; channel < spec.numChannels; ++channel)
|
||||
{
|
||||
const auto outputValue = outputBlock.getSample ((int) channel, 0);
|
||||
expectWithinAbsoluteError (outputValue, 0.5f, 0.0001f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
beginTest ("Mixer can push and pop multiple small buffers");
|
||||
{
|
||||
DryWetMixer<float> mixer (maxLatency);
|
||||
mixer.setWetMixProportion (0.5f);
|
||||
mixer.prepare (spec);
|
||||
|
||||
for (auto block = 0; block < numBlocks; ++block)
|
||||
{
|
||||
// Push dry samples and process wet samples one-by-one
|
||||
for (uint32_t sample = 0; sample < spec.maximumBlockSize; ++sample)
|
||||
{
|
||||
mixer.pushDrySamples (AudioBlock<const float> (dryBuffer).getSubBlock (sample, 1));
|
||||
|
||||
AudioBuffer<float> outputBlock ((int) spec.numChannels, 1);
|
||||
AudioBlock<const float> (wetBuffer).getSubBlock (sample, 1).copyTo (outputBlock);
|
||||
mixer.mixWetSamples ({ outputBlock });
|
||||
|
||||
// The output block should contain the wet and dry samples averaged
|
||||
for (uint32_t channel = 0; channel < spec.numChannels; ++channel)
|
||||
{
|
||||
const auto outputValue = outputBlock.getSample ((int) channel, 0);
|
||||
expectWithinAbsoluteError (outputValue, 0.5f, 0.0001f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
beginTest ("Mixer can push and pop full-sized blocks after encountering a shorter block");
|
||||
{
|
||||
DryWetMixer<float> mixer (maxLatency);
|
||||
mixer.setWetMixProportion (0.5f);
|
||||
mixer.prepare (spec);
|
||||
|
||||
constexpr auto shortBlockLength = spec.maximumBlockSize / 2;
|
||||
AudioBuffer<float> shortBlock (spec.numChannels, shortBlockLength);
|
||||
mixer.pushDrySamples (AudioBlock<const float> (dryBuffer).getSubBlock (shortBlockLength));
|
||||
mixer.mixWetSamples ({ shortBlock });
|
||||
|
||||
for (auto block = 0; block < numBlocks; ++block)
|
||||
{
|
||||
// Push a full block of dry samples
|
||||
mixer.pushDrySamples ({ dryBuffer });
|
||||
|
||||
// Mix a full block of wet samples
|
||||
auto outputBlock = wetBuffer;
|
||||
mixer.mixWetSamples ({ outputBlock });
|
||||
|
||||
// The output block should contain the wet and dry samples averaged
|
||||
for (uint32_t sample = 0; sample < spec.maximumBlockSize; ++sample)
|
||||
{
|
||||
for (uint32_t channel = 0; channel < spec.numChannels; ++channel)
|
||||
{
|
||||
const auto outputValue = outputBlock.getSample ((int) channel, (int) sample);
|
||||
expectWithinAbsoluteError (outputValue, 0.5f, 0.0001f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static const DryWetMixerTests dryWetMixerTests;
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace dsp
|
||||
} // namespace juce
|
||||
|
@ -1,120 +1,120 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
namespace dsp
|
||||
{
|
||||
|
||||
enum class DryWetMixingRule
|
||||
{
|
||||
linear, // dry volume is equal to 1 - wet volume
|
||||
balanced, // both dry and wet are 1 when mix is 0.5, with dry decreasing to 0
|
||||
// above this value and wet decreasing to 0 below it
|
||||
sin3dB, // alternate dry/wet mixing rule using the 3 dB sine panning rule
|
||||
sin4p5dB, // alternate dry/wet mixing rule using the 4.5 dB sine panning rule
|
||||
sin6dB, // alternate dry/wet mixing rule using the 6 dB sine panning rule
|
||||
squareRoot3dB, // alternate dry/wet mixing rule using the regular 3 dB panning rule
|
||||
squareRoot4p5dB // alternate dry/wet mixing rule using the regular 4.5 dB panning rule
|
||||
};
|
||||
|
||||
/**
|
||||
A processor to handle dry/wet mixing of two audio signals, where the wet signal
|
||||
may have additional latency.
|
||||
|
||||
Once a DryWetMixer object is configured, push the dry samples using pushDrySamples
|
||||
and mix into the fully wet samples using mixWetSamples.
|
||||
|
||||
@tags{DSP}
|
||||
*/
|
||||
template <typename SampleType>
|
||||
class DryWetMixer
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
using MixingRule = DryWetMixingRule;
|
||||
|
||||
//==============================================================================
|
||||
/** Default constructor. */
|
||||
DryWetMixer();
|
||||
|
||||
/** Constructor. */
|
||||
explicit DryWetMixer (int maximumWetLatencyInSamples);
|
||||
|
||||
//==============================================================================
|
||||
/** Sets the mix rule. */
|
||||
void setMixingRule (MixingRule newRule);
|
||||
|
||||
/** Sets the current dry/wet mix proportion, with 0.0 being full dry and 1.0
|
||||
being fully wet.
|
||||
*/
|
||||
void setWetMixProportion (SampleType newWetMixProportion);
|
||||
|
||||
/** Sets the relative latency of the wet signal path compared to the dry signal
|
||||
path, and thus the amount of latency compensation that will be added to the
|
||||
dry samples in this processor.
|
||||
*/
|
||||
void setWetLatency (SampleType wetLatencyInSamples);
|
||||
|
||||
//==============================================================================
|
||||
/** Initialises the processor. */
|
||||
void prepare (const ProcessSpec& spec);
|
||||
|
||||
/** Resets the internal state variables of the processor. */
|
||||
void reset();
|
||||
|
||||
//==============================================================================
|
||||
/** Copies the dry path samples into an internal delay line. */
|
||||
void pushDrySamples (const AudioBlock<const SampleType> drySamples);
|
||||
|
||||
/** Mixes the supplied wet samples with the latency-compensated dry samples from
|
||||
pushDrySamples.
|
||||
|
||||
@param wetSamples Input: The AudioBlock references fully wet samples.
|
||||
Output: The AudioBlock references the wet samples mixed
|
||||
with the latency compensated dry samples.
|
||||
|
||||
@see pushDrySamples
|
||||
*/
|
||||
void mixWetSamples (AudioBlock<SampleType> wetSamples);
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
void update();
|
||||
|
||||
//==============================================================================
|
||||
SmoothedValue<SampleType, ValueSmoothingTypes::Linear> dryVolume, wetVolume;
|
||||
DelayLine<SampleType, DelayLineInterpolationTypes::Thiran> dryDelayLine;
|
||||
AudioBuffer<SampleType> bufferDry;
|
||||
|
||||
SingleThreadedAbstractFifo fifo;
|
||||
SampleType mix = 1.0;
|
||||
MixingRule currentMixingRule = MixingRule::linear;
|
||||
double sampleRate = 44100.0;
|
||||
int maximumWetLatencyInSamples = 0;
|
||||
};
|
||||
|
||||
} // namespace dsp
|
||||
} // namespace juce
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
namespace dsp
|
||||
{
|
||||
|
||||
enum class DryWetMixingRule
|
||||
{
|
||||
linear, // dry volume is equal to 1 - wet volume
|
||||
balanced, // both dry and wet are 1 when mix is 0.5, with dry decreasing to 0
|
||||
// above this value and wet decreasing to 0 below it
|
||||
sin3dB, // alternate dry/wet mixing rule using the 3 dB sine panning rule
|
||||
sin4p5dB, // alternate dry/wet mixing rule using the 4.5 dB sine panning rule
|
||||
sin6dB, // alternate dry/wet mixing rule using the 6 dB sine panning rule
|
||||
squareRoot3dB, // alternate dry/wet mixing rule using the regular 3 dB panning rule
|
||||
squareRoot4p5dB // alternate dry/wet mixing rule using the regular 4.5 dB panning rule
|
||||
};
|
||||
|
||||
/**
|
||||
A processor to handle dry/wet mixing of two audio signals, where the wet signal
|
||||
may have additional latency.
|
||||
|
||||
Once a DryWetMixer object is configured, push the dry samples using pushDrySamples
|
||||
and mix into the fully wet samples using mixWetSamples.
|
||||
|
||||
@tags{DSP}
|
||||
*/
|
||||
template <typename SampleType>
|
||||
class DryWetMixer
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
using MixingRule = DryWetMixingRule;
|
||||
|
||||
//==============================================================================
|
||||
/** Default constructor. */
|
||||
DryWetMixer();
|
||||
|
||||
/** Constructor. */
|
||||
explicit DryWetMixer (int maximumWetLatencyInSamples);
|
||||
|
||||
//==============================================================================
|
||||
/** Sets the mix rule. */
|
||||
void setMixingRule (MixingRule newRule);
|
||||
|
||||
/** Sets the current dry/wet mix proportion, with 0.0 being full dry and 1.0
|
||||
being fully wet.
|
||||
*/
|
||||
void setWetMixProportion (SampleType newWetMixProportion);
|
||||
|
||||
/** Sets the relative latency of the wet signal path compared to the dry signal
|
||||
path, and thus the amount of latency compensation that will be added to the
|
||||
dry samples in this processor.
|
||||
*/
|
||||
void setWetLatency (SampleType wetLatencyInSamples);
|
||||
|
||||
//==============================================================================
|
||||
/** Initialises the processor. */
|
||||
void prepare (const ProcessSpec& spec);
|
||||
|
||||
/** Resets the internal state variables of the processor. */
|
||||
void reset();
|
||||
|
||||
//==============================================================================
|
||||
/** Copies the dry path samples into an internal delay line. */
|
||||
void pushDrySamples (const AudioBlock<const SampleType> drySamples);
|
||||
|
||||
/** Mixes the supplied wet samples with the latency-compensated dry samples from
|
||||
pushDrySamples.
|
||||
|
||||
@param wetSamples Input: The AudioBlock references fully wet samples.
|
||||
Output: The AudioBlock references the wet samples mixed
|
||||
with the latency compensated dry samples.
|
||||
|
||||
@see pushDrySamples
|
||||
*/
|
||||
void mixWetSamples (AudioBlock<SampleType> wetSamples);
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
void update();
|
||||
|
||||
//==============================================================================
|
||||
SmoothedValue<SampleType, ValueSmoothingTypes::Linear> dryVolume, wetVolume;
|
||||
DelayLine<SampleType, DelayLineInterpolationTypes::Thiran> dryDelayLine;
|
||||
AudioBuffer<SampleType> bufferDry;
|
||||
|
||||
SingleThreadedAbstractFifo fifo;
|
||||
SampleType mix = 1.0;
|
||||
MixingRule currentMixingRule = MixingRule::linear;
|
||||
double sampleRate = 44100.0;
|
||||
int maximumWetLatencyInSamples = 0;
|
||||
};
|
||||
|
||||
} // namespace dsp
|
||||
} // namespace juce
|
||||
|
@ -1,161 +1,161 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
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
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
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
|
||||
|
@ -1,285 +1,285 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
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;
|
||||
|
||||
/** A typedef for a ref-counted pointer to the coefficients object */
|
||||
using CoefficientsPtr = typename Coefficients<NumericType>::Ptr;
|
||||
|
||||
//==============================================================================
|
||||
/** 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 (CoefficientsPtr coefficientsToUse) : coefficients (std::move (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.
|
||||
jassertquiet (spec.numChannels == 1);
|
||||
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 caller 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 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
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
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;
|
||||
|
||||
/** A typedef for a ref-counted pointer to the coefficients object */
|
||||
using CoefficientsPtr = typename Coefficients<NumericType>::Ptr;
|
||||
|
||||
//==============================================================================
|
||||
/** 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 (CoefficientsPtr coefficientsToUse) : coefficients (std::move (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.
|
||||
jassertquiet (spec.numChannels == 1);
|
||||
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 caller 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 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
|
||||
|
@ -1,222 +1,222 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
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<const 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<const 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", UnitTestCategories::dsp)
|
||||
{}
|
||||
|
||||
void runTest() override
|
||||
{
|
||||
runTestForAllTypes<LargeBlockTest> ("Large Blocks");
|
||||
runTestForAllTypes<SampleBySampleTest> ("Sample by Sample");
|
||||
runTestForAllTypes<SplitBlockTest> ("Split Block");
|
||||
}
|
||||
};
|
||||
|
||||
static FIRFilterTest firFilterUnitTest;
|
||||
|
||||
} // namespace dsp
|
||||
} // namespace juce
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
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<const 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<const 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", UnitTestCategories::dsp)
|
||||
{}
|
||||
|
||||
void runTest() override
|
||||
{
|
||||
runTestForAllTypes<LargeBlockTest> ("Large Blocks");
|
||||
runTestForAllTypes<SampleBySampleTest> ("Sample by Sample");
|
||||
runTestForAllTypes<SplitBlockTest> ("Split Block");
|
||||
}
|
||||
};
|
||||
|
||||
static FIRFilterTest firFilterUnitTest;
|
||||
|
||||
} // namespace dsp
|
||||
} // namespace juce
|
||||
|
@ -1,122 +1,122 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
namespace dsp
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
template <typename SampleType>
|
||||
FirstOrderTPTFilter<SampleType>::FirstOrderTPTFilter()
|
||||
{
|
||||
update();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
template <typename SampleType>
|
||||
void FirstOrderTPTFilter<SampleType>::setType (Type newValue)
|
||||
{
|
||||
filterType = newValue;
|
||||
}
|
||||
|
||||
template <typename SampleType>
|
||||
void FirstOrderTPTFilter<SampleType>::setCutoffFrequency (SampleType newValue)
|
||||
{
|
||||
jassert (isPositiveAndBelow (newValue, static_cast<SampleType> (sampleRate * 0.5)));
|
||||
|
||||
cutoffFrequency = newValue;
|
||||
update();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
template <typename SampleType>
|
||||
void FirstOrderTPTFilter<SampleType>::prepare (const ProcessSpec& spec)
|
||||
{
|
||||
jassert (spec.sampleRate > 0);
|
||||
jassert (spec.numChannels > 0);
|
||||
|
||||
sampleRate = spec.sampleRate;
|
||||
s1.resize (spec.numChannels);
|
||||
|
||||
update();
|
||||
reset();
|
||||
}
|
||||
|
||||
template <typename SampleType>
|
||||
void FirstOrderTPTFilter<SampleType>::reset()
|
||||
{
|
||||
reset (static_cast<SampleType> (0));
|
||||
}
|
||||
|
||||
template <typename SampleType>
|
||||
void FirstOrderTPTFilter<SampleType>::reset (SampleType newValue)
|
||||
{
|
||||
std::fill (s1.begin(), s1.end(), newValue);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
template <typename SampleType>
|
||||
SampleType FirstOrderTPTFilter<SampleType>::processSample (int channel, SampleType inputValue)
|
||||
{
|
||||
auto& s = s1[(size_t) channel];
|
||||
|
||||
auto v = G * (inputValue - s);
|
||||
auto y = v + s;
|
||||
s = y + v;
|
||||
|
||||
switch (filterType)
|
||||
{
|
||||
case Type::lowpass: return y;
|
||||
case Type::highpass: return inputValue - y;
|
||||
case Type::allpass: return 2 * y - inputValue;
|
||||
default: break;
|
||||
}
|
||||
|
||||
jassertfalse;
|
||||
return y;
|
||||
}
|
||||
|
||||
template <typename SampleType>
|
||||
void FirstOrderTPTFilter<SampleType>::snapToZero() noexcept
|
||||
{
|
||||
for (auto& s : s1)
|
||||
util::snapToZero (s);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
template <typename SampleType>
|
||||
void FirstOrderTPTFilter<SampleType>::update()
|
||||
{
|
||||
auto g = SampleType (std::tan (juce::MathConstants<double>::pi * cutoffFrequency / sampleRate));
|
||||
G = g / (1 + g);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
template class FirstOrderTPTFilter<float>;
|
||||
template class FirstOrderTPTFilter<double>;
|
||||
|
||||
} // namespace dsp
|
||||
} // namespace juce
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
namespace dsp
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
template <typename SampleType>
|
||||
FirstOrderTPTFilter<SampleType>::FirstOrderTPTFilter()
|
||||
{
|
||||
update();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
template <typename SampleType>
|
||||
void FirstOrderTPTFilter<SampleType>::setType (Type newValue)
|
||||
{
|
||||
filterType = newValue;
|
||||
}
|
||||
|
||||
template <typename SampleType>
|
||||
void FirstOrderTPTFilter<SampleType>::setCutoffFrequency (SampleType newValue)
|
||||
{
|
||||
jassert (isPositiveAndBelow (newValue, static_cast<SampleType> (sampleRate * 0.5)));
|
||||
|
||||
cutoffFrequency = newValue;
|
||||
update();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
template <typename SampleType>
|
||||
void FirstOrderTPTFilter<SampleType>::prepare (const ProcessSpec& spec)
|
||||
{
|
||||
jassert (spec.sampleRate > 0);
|
||||
jassert (spec.numChannels > 0);
|
||||
|
||||
sampleRate = spec.sampleRate;
|
||||
s1.resize (spec.numChannels);
|
||||
|
||||
update();
|
||||
reset();
|
||||
}
|
||||
|
||||
template <typename SampleType>
|
||||
void FirstOrderTPTFilter<SampleType>::reset()
|
||||
{
|
||||
reset (static_cast<SampleType> (0));
|
||||
}
|
||||
|
||||
template <typename SampleType>
|
||||
void FirstOrderTPTFilter<SampleType>::reset (SampleType newValue)
|
||||
{
|
||||
std::fill (s1.begin(), s1.end(), newValue);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
template <typename SampleType>
|
||||
SampleType FirstOrderTPTFilter<SampleType>::processSample (int channel, SampleType inputValue)
|
||||
{
|
||||
auto& s = s1[(size_t) channel];
|
||||
|
||||
auto v = G * (inputValue - s);
|
||||
auto y = v + s;
|
||||
s = y + v;
|
||||
|
||||
switch (filterType)
|
||||
{
|
||||
case Type::lowpass: return y;
|
||||
case Type::highpass: return inputValue - y;
|
||||
case Type::allpass: return 2 * y - inputValue;
|
||||
default: break;
|
||||
}
|
||||
|
||||
jassertfalse;
|
||||
return y;
|
||||
}
|
||||
|
||||
template <typename SampleType>
|
||||
void FirstOrderTPTFilter<SampleType>::snapToZero() noexcept
|
||||
{
|
||||
for (auto& s : s1)
|
||||
util::snapToZero (s);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
template <typename SampleType>
|
||||
void FirstOrderTPTFilter<SampleType>::update()
|
||||
{
|
||||
auto g = SampleType (std::tan (juce::MathConstants<double>::pi * cutoffFrequency / sampleRate));
|
||||
G = g / (1 + g);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
template class FirstOrderTPTFilter<float>;
|
||||
template class FirstOrderTPTFilter<double>;
|
||||
|
||||
} // namespace dsp
|
||||
} // namespace juce
|
||||
|
@ -1,151 +1,151 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
namespace dsp
|
||||
{
|
||||
|
||||
enum class FirstOrderTPTFilterType
|
||||
{
|
||||
lowpass,
|
||||
highpass,
|
||||
allpass
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A first order filter class using the TPT (Topology-Preserving Transform) structure.
|
||||
|
||||
This filter can be modulated at high rates without producing audio artefacts. See
|
||||
Vadim Zavalishin's documentation about TPT structures for more information.
|
||||
|
||||
Note: Using this class prevents some loud audio artefacts commonly encountered when
|
||||
changing the cutoff frequency using of other filter simulation structures and IIR
|
||||
filter classes. However, this class may still require additional smoothing for
|
||||
cutoff frequency changes.
|
||||
|
||||
see StateVariableFilter, IIRFilter, SmoothedValue
|
||||
|
||||
@tags{DSP}
|
||||
*/
|
||||
template <typename SampleType>
|
||||
class FirstOrderTPTFilter
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
using Type = FirstOrderTPTFilterType;
|
||||
|
||||
//==============================================================================
|
||||
/** Constructor. */
|
||||
FirstOrderTPTFilter();
|
||||
|
||||
//==============================================================================
|
||||
/** Sets the filter type. */
|
||||
void setType (Type newType);
|
||||
|
||||
/** Sets the cutoff frequency of the filter.
|
||||
|
||||
@param newFrequencyHz cutoff frequency in Hz.
|
||||
*/
|
||||
void setCutoffFrequency (SampleType newFrequencyHz);
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the type of the filter. */
|
||||
Type getType() const noexcept { return filterType; }
|
||||
|
||||
/** Returns the cutoff frequency of the filter. */
|
||||
SampleType getCutoffFrequency() const noexcept { return cutoffFrequency; }
|
||||
|
||||
//==============================================================================
|
||||
/** Initialises the filter. */
|
||||
void prepare (const ProcessSpec& spec);
|
||||
|
||||
/** Resets the internal state variables of the filter. */
|
||||
void reset();
|
||||
|
||||
/** Resets the internal state variables of the filter to a given value. */
|
||||
void reset (SampleType newValue);
|
||||
|
||||
//==============================================================================
|
||||
/** Processes the input and output samples supplied in the processing context. */
|
||||
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() <= s1.size());
|
||||
jassert (inputBlock.getNumChannels() == numChannels);
|
||||
jassert (inputBlock.getNumSamples() == numSamples);
|
||||
|
||||
if (context.isBypassed)
|
||||
{
|
||||
outputBlock.copyFrom (inputBlock);
|
||||
return;
|
||||
}
|
||||
|
||||
for (size_t channel = 0; channel < numChannels; ++channel)
|
||||
{
|
||||
auto* inputSamples = inputBlock .getChannelPointer (channel);
|
||||
auto* outputSamples = outputBlock.getChannelPointer (channel);
|
||||
|
||||
for (size_t i = 0; i < numSamples; ++i)
|
||||
outputSamples[i] = processSample ((int) channel, inputSamples[i]);
|
||||
}
|
||||
|
||||
#if JUCE_DSP_ENABLE_SNAP_TO_ZERO
|
||||
snapToZero();
|
||||
#endif
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
/** Processes one sample at a time on a given channel. */
|
||||
SampleType processSample (int channel, SampleType inputValue);
|
||||
|
||||
/** 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 update();
|
||||
|
||||
//==============================================================================
|
||||
SampleType G = 0;
|
||||
std::vector<SampleType> s1 { 2 };
|
||||
double sampleRate = 44100.0;
|
||||
|
||||
//==============================================================================
|
||||
Type filterType = Type::lowpass;
|
||||
SampleType cutoffFrequency = 1000.0;
|
||||
};
|
||||
|
||||
} // namespace dsp
|
||||
} // namespace juce
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
namespace dsp
|
||||
{
|
||||
|
||||
enum class FirstOrderTPTFilterType
|
||||
{
|
||||
lowpass,
|
||||
highpass,
|
||||
allpass
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A first order filter class using the TPT (Topology-Preserving Transform) structure.
|
||||
|
||||
This filter can be modulated at high rates without producing audio artefacts. See
|
||||
Vadim Zavalishin's documentation about TPT structures for more information.
|
||||
|
||||
Note: Using this class prevents some loud audio artefacts commonly encountered when
|
||||
changing the cutoff frequency using of other filter simulation structures and IIR
|
||||
filter classes. However, this class may still require additional smoothing for
|
||||
cutoff frequency changes.
|
||||
|
||||
see StateVariableFilter, IIRFilter, SmoothedValue
|
||||
|
||||
@tags{DSP}
|
||||
*/
|
||||
template <typename SampleType>
|
||||
class FirstOrderTPTFilter
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
using Type = FirstOrderTPTFilterType;
|
||||
|
||||
//==============================================================================
|
||||
/** Constructor. */
|
||||
FirstOrderTPTFilter();
|
||||
|
||||
//==============================================================================
|
||||
/** Sets the filter type. */
|
||||
void setType (Type newType);
|
||||
|
||||
/** Sets the cutoff frequency of the filter.
|
||||
|
||||
@param newFrequencyHz cutoff frequency in Hz.
|
||||
*/
|
||||
void setCutoffFrequency (SampleType newFrequencyHz);
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the type of the filter. */
|
||||
Type getType() const noexcept { return filterType; }
|
||||
|
||||
/** Returns the cutoff frequency of the filter. */
|
||||
SampleType getCutoffFrequency() const noexcept { return cutoffFrequency; }
|
||||
|
||||
//==============================================================================
|
||||
/** Initialises the filter. */
|
||||
void prepare (const ProcessSpec& spec);
|
||||
|
||||
/** Resets the internal state variables of the filter. */
|
||||
void reset();
|
||||
|
||||
/** Resets the internal state variables of the filter to a given value. */
|
||||
void reset (SampleType newValue);
|
||||
|
||||
//==============================================================================
|
||||
/** Processes the input and output samples supplied in the processing context. */
|
||||
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() <= s1.size());
|
||||
jassert (inputBlock.getNumChannels() == numChannels);
|
||||
jassert (inputBlock.getNumSamples() == numSamples);
|
||||
|
||||
if (context.isBypassed)
|
||||
{
|
||||
outputBlock.copyFrom (inputBlock);
|
||||
return;
|
||||
}
|
||||
|
||||
for (size_t channel = 0; channel < numChannels; ++channel)
|
||||
{
|
||||
auto* inputSamples = inputBlock .getChannelPointer (channel);
|
||||
auto* outputSamples = outputBlock.getChannelPointer (channel);
|
||||
|
||||
for (size_t i = 0; i < numSamples; ++i)
|
||||
outputSamples[i] = processSample ((int) channel, inputSamples[i]);
|
||||
}
|
||||
|
||||
#if JUCE_DSP_ENABLE_SNAP_TO_ZERO
|
||||
snapToZero();
|
||||
#endif
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
/** Processes one sample at a time on a given channel. */
|
||||
SampleType processSample (int channel, SampleType inputValue);
|
||||
|
||||
/** 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 update();
|
||||
|
||||
//==============================================================================
|
||||
SampleType G = 0;
|
||||
std::vector<SampleType> s1 { 2 };
|
||||
double sampleRate = 44100.0;
|
||||
|
||||
//==============================================================================
|
||||
Type filterType = Type::lowpass;
|
||||
SampleType cutoffFrequency = 1000.0;
|
||||
};
|
||||
|
||||
} // namespace dsp
|
||||
} // namespace juce
|
||||
|
1154
deps/juce/modules/juce_dsp/processors/juce_IIRFilter.cpp
vendored
1154
deps/juce/modules/juce_dsp/processors/juce_IIRFilter.cpp
vendored
File diff suppressed because it is too large
Load Diff
@ -1,403 +1,403 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
namespace dsp
|
||||
{
|
||||
|
||||
/**
|
||||
Classes for IIR filter processing.
|
||||
*/
|
||||
namespace IIR
|
||||
{
|
||||
/** A set of coefficients for use in an Filter object.
|
||||
|
||||
@tags{DSP}
|
||||
*/
|
||||
template <typename NumericType>
|
||||
struct ArrayCoefficients
|
||||
{
|
||||
/** Returns the coefficients for a first order low-pass filter. */
|
||||
static std::array<NumericType, 4> makeFirstOrderLowPass (double sampleRate, NumericType frequency);
|
||||
|
||||
/** Returns the coefficients for a first order high-pass filter. */
|
||||
static std::array<NumericType, 4> makeFirstOrderHighPass (double sampleRate, NumericType frequency);
|
||||
|
||||
/** Returns the coefficients for a first order all-pass filter. */
|
||||
static std::array<NumericType, 4> makeFirstOrderAllPass (double sampleRate, NumericType frequency);
|
||||
|
||||
/** Returns the coefficients for a low-pass filter. */
|
||||
static std::array<NumericType, 6> makeLowPass (double sampleRate, NumericType frequency);
|
||||
|
||||
/** Returns the coefficients for a low-pass filter with variable Q. */
|
||||
static std::array<NumericType, 6> makeLowPass (double sampleRate, NumericType frequency, NumericType Q);
|
||||
|
||||
/** Returns the coefficients for a high-pass filter. */
|
||||
static std::array<NumericType, 6> makeHighPass (double sampleRate, NumericType frequency);
|
||||
|
||||
/** Returns the coefficients for a high-pass filter with variable Q. */
|
||||
static std::array<NumericType, 6> makeHighPass (double sampleRate, NumericType frequency, NumericType Q);
|
||||
|
||||
/** Returns the coefficients for a band-pass filter. */
|
||||
static std::array<NumericType, 6> makeBandPass (double sampleRate, NumericType frequency);
|
||||
|
||||
/** Returns the coefficients for a band-pass filter with variable Q. */
|
||||
static std::array<NumericType, 6> makeBandPass (double sampleRate, NumericType frequency, NumericType Q);
|
||||
|
||||
/** Returns the coefficients for a notch filter. */
|
||||
static std::array<NumericType, 6> makeNotch (double sampleRate, NumericType frequency);
|
||||
|
||||
/** Returns the coefficients for a notch filter with variable Q. */
|
||||
static std::array<NumericType, 6> makeNotch (double sampleRate, NumericType frequency, NumericType Q);
|
||||
|
||||
/** Returns the coefficients for an all-pass filter. */
|
||||
static std::array<NumericType, 6> makeAllPass (double sampleRate, NumericType frequency);
|
||||
|
||||
/** Returns the coefficients for an all-pass filter with variable Q. */
|
||||
static std::array<NumericType, 6> 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 std::array<NumericType, 6> 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 std::array<NumericType, 6> 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 std::array<NumericType, 6> makePeakFilter (double sampleRate,
|
||||
NumericType centreFrequency,
|
||||
NumericType Q,
|
||||
NumericType gainFactor);
|
||||
|
||||
private:
|
||||
// Unfortunately, std::sqrt is not marked as constexpr just yet in all compilers
|
||||
static constexpr NumericType inverseRootTwo = static_cast<NumericType> (0.70710678118654752440L);
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** 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;
|
||||
|
||||
/** Constructs from an array. */
|
||||
template <size_t Num>
|
||||
explicit Coefficients (const std::array<NumericType, Num>& values) { assignImpl<Num> (values.data()); }
|
||||
|
||||
/** Assigns contents from an array. */
|
||||
template <size_t Num>
|
||||
Coefficients& operator= (const std::array<NumericType, Num>& values) { return assignImpl<Num> (values.data()); }
|
||||
|
||||
/** 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:
|
||||
using ArrayCoeffs = ArrayCoefficients<NumericType>;
|
||||
|
||||
template <size_t Num>
|
||||
Coefficients& assignImpl (const NumericType* values);
|
||||
|
||||
template <size_t Num>
|
||||
Coefficients& assign (const NumericType (& values)[Num]) { return assignImpl<Num> (values); }
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
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;
|
||||
|
||||
/** A typedef for a ref-counted pointer to the coefficients object */
|
||||
using CoefficientsPtr = typename Coefficients<NumericType>::Ptr;
|
||||
|
||||
//==============================================================================
|
||||
/** Creates a filter.
|
||||
|
||||
Initially the filter is inactive, so will have no effect on samples that
|
||||
you process with it. You can modify the coefficients member to turn it into
|
||||
the type of filter needed.
|
||||
*/
|
||||
Filter();
|
||||
|
||||
/** Creates a filter with a given set of coefficients. */
|
||||
Filter (CoefficientsPtr 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 caller 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.
|
||||
*/
|
||||
CoefficientsPtr 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 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);
|
||||
|
||||
#if JUCE_DSP_ENABLE_SNAP_TO_ZERO
|
||||
snapToZero();
|
||||
#endif
|
||||
}
|
||||
|
||||
/** 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 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)
|
||||
};
|
||||
} // namespace IIR
|
||||
} // namespace dsp
|
||||
} // namespace juce
|
||||
|
||||
#include "juce_IIRFilter_Impl.h"
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
namespace dsp
|
||||
{
|
||||
|
||||
/**
|
||||
Classes for IIR filter processing.
|
||||
*/
|
||||
namespace IIR
|
||||
{
|
||||
/** A set of coefficients for use in an Filter object.
|
||||
|
||||
@tags{DSP}
|
||||
*/
|
||||
template <typename NumericType>
|
||||
struct ArrayCoefficients
|
||||
{
|
||||
/** Returns the coefficients for a first order low-pass filter. */
|
||||
static std::array<NumericType, 4> makeFirstOrderLowPass (double sampleRate, NumericType frequency);
|
||||
|
||||
/** Returns the coefficients for a first order high-pass filter. */
|
||||
static std::array<NumericType, 4> makeFirstOrderHighPass (double sampleRate, NumericType frequency);
|
||||
|
||||
/** Returns the coefficients for a first order all-pass filter. */
|
||||
static std::array<NumericType, 4> makeFirstOrderAllPass (double sampleRate, NumericType frequency);
|
||||
|
||||
/** Returns the coefficients for a low-pass filter. */
|
||||
static std::array<NumericType, 6> makeLowPass (double sampleRate, NumericType frequency);
|
||||
|
||||
/** Returns the coefficients for a low-pass filter with variable Q. */
|
||||
static std::array<NumericType, 6> makeLowPass (double sampleRate, NumericType frequency, NumericType Q);
|
||||
|
||||
/** Returns the coefficients for a high-pass filter. */
|
||||
static std::array<NumericType, 6> makeHighPass (double sampleRate, NumericType frequency);
|
||||
|
||||
/** Returns the coefficients for a high-pass filter with variable Q. */
|
||||
static std::array<NumericType, 6> makeHighPass (double sampleRate, NumericType frequency, NumericType Q);
|
||||
|
||||
/** Returns the coefficients for a band-pass filter. */
|
||||
static std::array<NumericType, 6> makeBandPass (double sampleRate, NumericType frequency);
|
||||
|
||||
/** Returns the coefficients for a band-pass filter with variable Q. */
|
||||
static std::array<NumericType, 6> makeBandPass (double sampleRate, NumericType frequency, NumericType Q);
|
||||
|
||||
/** Returns the coefficients for a notch filter. */
|
||||
static std::array<NumericType, 6> makeNotch (double sampleRate, NumericType frequency);
|
||||
|
||||
/** Returns the coefficients for a notch filter with variable Q. */
|
||||
static std::array<NumericType, 6> makeNotch (double sampleRate, NumericType frequency, NumericType Q);
|
||||
|
||||
/** Returns the coefficients for an all-pass filter. */
|
||||
static std::array<NumericType, 6> makeAllPass (double sampleRate, NumericType frequency);
|
||||
|
||||
/** Returns the coefficients for an all-pass filter with variable Q. */
|
||||
static std::array<NumericType, 6> 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 std::array<NumericType, 6> 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 std::array<NumericType, 6> 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 std::array<NumericType, 6> makePeakFilter (double sampleRate,
|
||||
NumericType centreFrequency,
|
||||
NumericType Q,
|
||||
NumericType gainFactor);
|
||||
|
||||
private:
|
||||
// Unfortunately, std::sqrt is not marked as constexpr just yet in all compilers
|
||||
static constexpr NumericType inverseRootTwo = static_cast<NumericType> (0.70710678118654752440L);
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** 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;
|
||||
|
||||
/** Constructs from an array. */
|
||||
template <size_t Num>
|
||||
explicit Coefficients (const std::array<NumericType, Num>& values) { assignImpl<Num> (values.data()); }
|
||||
|
||||
/** Assigns contents from an array. */
|
||||
template <size_t Num>
|
||||
Coefficients& operator= (const std::array<NumericType, Num>& values) { return assignImpl<Num> (values.data()); }
|
||||
|
||||
/** 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:
|
||||
using ArrayCoeffs = ArrayCoefficients<NumericType>;
|
||||
|
||||
template <size_t Num>
|
||||
Coefficients& assignImpl (const NumericType* values);
|
||||
|
||||
template <size_t Num>
|
||||
Coefficients& assign (const NumericType (& values)[Num]) { return assignImpl<Num> (values); }
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
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;
|
||||
|
||||
/** A typedef for a ref-counted pointer to the coefficients object */
|
||||
using CoefficientsPtr = typename Coefficients<NumericType>::Ptr;
|
||||
|
||||
//==============================================================================
|
||||
/** Creates a filter.
|
||||
|
||||
Initially the filter is inactive, so will have no effect on samples that
|
||||
you process with it. You can modify the coefficients member to turn it into
|
||||
the type of filter needed.
|
||||
*/
|
||||
Filter();
|
||||
|
||||
/** Creates a filter with a given set of coefficients. */
|
||||
Filter (CoefficientsPtr 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 caller 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.
|
||||
*/
|
||||
CoefficientsPtr 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 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);
|
||||
|
||||
#if JUCE_DSP_ENABLE_SNAP_TO_ZERO
|
||||
snapToZero();
|
||||
#endif
|
||||
}
|
||||
|
||||
/** 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 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)
|
||||
};
|
||||
} // namespace IIR
|
||||
} // namespace dsp
|
||||
} // namespace juce
|
||||
|
||||
#include "juce_IIRFilter_Impl.h"
|
||||
|
@ -1,245 +1,245 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
namespace dsp
|
||||
{
|
||||
namespace IIR
|
||||
{
|
||||
|
||||
#ifndef DOXYGEN
|
||||
|
||||
template <typename NumericType>
|
||||
template <size_t Num>
|
||||
Coefficients<NumericType>& Coefficients<NumericType>::assignImpl (const NumericType* values)
|
||||
{
|
||||
static_assert (Num % 2 == 0, "Must supply an even number of coefficients");
|
||||
const auto a0Index = Num / 2;
|
||||
const auto a0 = values[a0Index];
|
||||
const auto a0Inv = a0 != NumericType() ? static_cast<NumericType> (1) / values[a0Index]
|
||||
: NumericType();
|
||||
|
||||
coefficients.clearQuick();
|
||||
coefficients.ensureStorageAllocated ((int) jmax ((size_t) 8, Num));
|
||||
|
||||
for (size_t i = 0; i < Num; ++i)
|
||||
if (i != a0Index)
|
||||
coefficients.add (values[i] * a0Inv);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
template <typename SampleType>
|
||||
Filter<SampleType>::Filter()
|
||||
: coefficients (new Coefficients<typename Filter<SampleType>::NumericType> (1, 0, 1, 0))
|
||||
{
|
||||
reset();
|
||||
}
|
||||
|
||||
template <typename SampleType>
|
||||
Filter<SampleType>::Filter (CoefficientsPtr c) : coefficients (std::move (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();
|
||||
|
||||
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 input = src[i];
|
||||
auto output = input * b0 + lv1;
|
||||
|
||||
dst[i] = bypassed ? input : output;
|
||||
|
||||
lv1 = (input * b1) - (output * 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 input = src[i];
|
||||
auto output = (input * b0) + lv1;
|
||||
dst[i] = bypassed ? input : output;
|
||||
|
||||
lv1 = (input * b1) - (output* a1) + lv2;
|
||||
lv2 = (input * b2) - (output* 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 input = src[i];
|
||||
auto output = (input * b0) + lv1;
|
||||
dst[i] = bypassed ? input : output;
|
||||
|
||||
lv1 = (input * b1) - (output* a1) + lv2;
|
||||
lv2 = (input * b2) - (output* a2) + lv3;
|
||||
lv3 = (input * b3) - (output* 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 input = src[i];
|
||||
auto output= (input * coeffs[0]) + state[0];
|
||||
dst[i] = bypassed ? input : output;
|
||||
|
||||
for (size_t j = 0; j < order - 1; ++j)
|
||||
state[j] = (input * coeffs[j + 1]) - (output* coeffs[order + j + 1]) + state[j + 1];
|
||||
|
||||
state[order - 1] = (input * coeffs[order]) - (output* coeffs[order * 2]);
|
||||
}
|
||||
|
||||
snapToZero();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename SampleType>
|
||||
SampleType JUCE_VECTOR_CALLTYPE Filter<SampleType>::processSample (SampleType sample) noexcept
|
||||
{
|
||||
check();
|
||||
auto* c = coefficients->getRawCoefficients();
|
||||
|
||||
auto output = (c[0] * sample) + state[0];
|
||||
|
||||
for (size_t j = 0; j < order - 1; ++j)
|
||||
state[j] = (c[j + 1] * sample) - (c[order + j + 1] * output) + state[j + 1];
|
||||
|
||||
state[order - 1] = (c[order] * sample) - (c[order * 2] * output);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
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
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
namespace dsp
|
||||
{
|
||||
namespace IIR
|
||||
{
|
||||
|
||||
#ifndef DOXYGEN
|
||||
|
||||
template <typename NumericType>
|
||||
template <size_t Num>
|
||||
Coefficients<NumericType>& Coefficients<NumericType>::assignImpl (const NumericType* values)
|
||||
{
|
||||
static_assert (Num % 2 == 0, "Must supply an even number of coefficients");
|
||||
const auto a0Index = Num / 2;
|
||||
const auto a0 = values[a0Index];
|
||||
const auto a0Inv = a0 != NumericType() ? static_cast<NumericType> (1) / values[a0Index]
|
||||
: NumericType();
|
||||
|
||||
coefficients.clearQuick();
|
||||
coefficients.ensureStorageAllocated ((int) jmax ((size_t) 8, Num));
|
||||
|
||||
for (size_t i = 0; i < Num; ++i)
|
||||
if (i != a0Index)
|
||||
coefficients.add (values[i] * a0Inv);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
template <typename SampleType>
|
||||
Filter<SampleType>::Filter()
|
||||
: coefficients (new Coefficients<typename Filter<SampleType>::NumericType> (1, 0, 1, 0))
|
||||
{
|
||||
reset();
|
||||
}
|
||||
|
||||
template <typename SampleType>
|
||||
Filter<SampleType>::Filter (CoefficientsPtr c) : coefficients (std::move (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();
|
||||
|
||||
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 input = src[i];
|
||||
auto output = input * b0 + lv1;
|
||||
|
||||
dst[i] = bypassed ? input : output;
|
||||
|
||||
lv1 = (input * b1) - (output * 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 input = src[i];
|
||||
auto output = (input * b0) + lv1;
|
||||
dst[i] = bypassed ? input : output;
|
||||
|
||||
lv1 = (input * b1) - (output* a1) + lv2;
|
||||
lv2 = (input * b2) - (output* 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 input = src[i];
|
||||
auto output = (input * b0) + lv1;
|
||||
dst[i] = bypassed ? input : output;
|
||||
|
||||
lv1 = (input * b1) - (output* a1) + lv2;
|
||||
lv2 = (input * b2) - (output* a2) + lv3;
|
||||
lv3 = (input * b3) - (output* 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 input = src[i];
|
||||
auto output= (input * coeffs[0]) + state[0];
|
||||
dst[i] = bypassed ? input : output;
|
||||
|
||||
for (size_t j = 0; j < order - 1; ++j)
|
||||
state[j] = (input * coeffs[j + 1]) - (output* coeffs[order + j + 1]) + state[j + 1];
|
||||
|
||||
state[order - 1] = (input * coeffs[order]) - (output* coeffs[order * 2]);
|
||||
}
|
||||
|
||||
snapToZero();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename SampleType>
|
||||
SampleType JUCE_VECTOR_CALLTYPE Filter<SampleType>::processSample (SampleType sample) noexcept
|
||||
{
|
||||
check();
|
||||
auto* c = coefficients->getRawCoefficients();
|
||||
|
||||
auto output = (c[0] * sample) + state[0];
|
||||
|
||||
for (size_t j = 0; j < order - 1; ++j)
|
||||
state[j] = (c[j + 1] * sample) - (c[order + j + 1] * output) + state[j + 1];
|
||||
|
||||
state[order - 1] = (c[order] * sample) - (c[order * 2] * output);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
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
|
||||
|
@ -1,149 +1,149 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
namespace dsp
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
template <typename SampleType>
|
||||
LinkwitzRileyFilter<SampleType>::LinkwitzRileyFilter()
|
||||
{
|
||||
update();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
template <typename SampleType>
|
||||
void LinkwitzRileyFilter<SampleType>::setType (Type newType)
|
||||
{
|
||||
filterType = newType;
|
||||
}
|
||||
|
||||
template <typename SampleType>
|
||||
void LinkwitzRileyFilter<SampleType>::setCutoffFrequency (SampleType newCutoffFrequencyHz)
|
||||
{
|
||||
jassert (isPositiveAndBelow (newCutoffFrequencyHz, static_cast<SampleType> (sampleRate * 0.5)));
|
||||
|
||||
cutoffFrequency = newCutoffFrequencyHz;
|
||||
update();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
template <typename SampleType>
|
||||
void LinkwitzRileyFilter<SampleType>::prepare (const ProcessSpec& spec)
|
||||
{
|
||||
jassert (spec.sampleRate > 0);
|
||||
jassert (spec.numChannels > 0);
|
||||
|
||||
sampleRate = spec.sampleRate;
|
||||
update();
|
||||
|
||||
s1.resize (spec.numChannels);
|
||||
s2.resize (spec.numChannels);
|
||||
s3.resize (spec.numChannels);
|
||||
s4.resize (spec.numChannels);
|
||||
|
||||
reset();
|
||||
}
|
||||
|
||||
template <typename SampleType>
|
||||
void LinkwitzRileyFilter<SampleType>::reset()
|
||||
{
|
||||
for (auto s : { &s1, &s2, &s3, &s4 })
|
||||
std::fill (s->begin(), s->end(), static_cast<SampleType> (0));
|
||||
}
|
||||
|
||||
template <typename SampleType>
|
||||
void LinkwitzRileyFilter<SampleType>::snapToZero() noexcept
|
||||
{
|
||||
for (auto s : { &s1, &s2, &s3, &s4 })
|
||||
for (auto& element : *s)
|
||||
util::snapToZero (element);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
template <typename SampleType>
|
||||
SampleType LinkwitzRileyFilter<SampleType>::processSample (int channel, SampleType inputValue)
|
||||
{
|
||||
auto yH = (inputValue - (R2 + g) * s1[(size_t) channel] - s2[(size_t) channel]) * h;
|
||||
|
||||
auto yB = g * yH + s1[(size_t) channel];
|
||||
s1[(size_t) channel] = g * yH + yB;
|
||||
|
||||
auto yL = g * yB + s2[(size_t) channel];
|
||||
s2[(size_t) channel] = g * yB + yL;
|
||||
|
||||
if (filterType == Type::allpass)
|
||||
return yL - R2 * yB + yH;
|
||||
|
||||
auto yH2 = ((filterType == Type::lowpass ? yL : yH) - (R2 + g) * s3[(size_t) channel] - s4[(size_t) channel]) * h;
|
||||
|
||||
auto yB2 = g * yH2 + s3[(size_t) channel];
|
||||
s3[(size_t) channel] = g * yH2 + yB2;
|
||||
|
||||
auto yL2 = g * yB2 + s4[(size_t) channel];
|
||||
s4[(size_t) channel] = g * yB2 + yL2;
|
||||
|
||||
return filterType == Type::lowpass ? yL2 : yH2;
|
||||
}
|
||||
|
||||
template <typename SampleType>
|
||||
void LinkwitzRileyFilter<SampleType>::processSample (int channel, SampleType inputValue, SampleType &outputLow, SampleType &outputHigh)
|
||||
{
|
||||
auto yH = (inputValue - (R2 + g) * s1[(size_t) channel] - s2[(size_t) channel]) * h;
|
||||
|
||||
auto yB = g * yH + s1[(size_t) channel];
|
||||
s1[(size_t) channel] = g * yH + yB;
|
||||
|
||||
auto yL = g * yB + s2[(size_t) channel];
|
||||
s2[(size_t) channel] = g * yB + yL;
|
||||
|
||||
auto yH2 = (yL - (R2 + g) * s3[(size_t) channel] - s4[(size_t) channel]) * h;
|
||||
|
||||
auto yB2 = g * yH2 + s3[(size_t) channel];
|
||||
s3[(size_t) channel] = g * yH2 + yB2;
|
||||
|
||||
auto yL2 = g * yB2 + s4[(size_t) channel];
|
||||
s4[(size_t) channel] = g * yB2 + yL2;
|
||||
|
||||
outputLow = yL2;
|
||||
outputHigh = yL - R2 * yB + yH - yL2;
|
||||
}
|
||||
|
||||
template <typename SampleType>
|
||||
void LinkwitzRileyFilter<SampleType>::update()
|
||||
{
|
||||
g = (SampleType) std::tan (MathConstants<double>::pi * cutoffFrequency / sampleRate);
|
||||
R2 = (SampleType) std::sqrt (2.0);
|
||||
h = (SampleType) (1.0 / (1.0 + R2 * g + g * g));
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
template class LinkwitzRileyFilter<float>;
|
||||
template class LinkwitzRileyFilter<double>;
|
||||
|
||||
} // namespace dsp
|
||||
} // namespace juce
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
namespace dsp
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
template <typename SampleType>
|
||||
LinkwitzRileyFilter<SampleType>::LinkwitzRileyFilter()
|
||||
{
|
||||
update();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
template <typename SampleType>
|
||||
void LinkwitzRileyFilter<SampleType>::setType (Type newType)
|
||||
{
|
||||
filterType = newType;
|
||||
}
|
||||
|
||||
template <typename SampleType>
|
||||
void LinkwitzRileyFilter<SampleType>::setCutoffFrequency (SampleType newCutoffFrequencyHz)
|
||||
{
|
||||
jassert (isPositiveAndBelow (newCutoffFrequencyHz, static_cast<SampleType> (sampleRate * 0.5)));
|
||||
|
||||
cutoffFrequency = newCutoffFrequencyHz;
|
||||
update();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
template <typename SampleType>
|
||||
void LinkwitzRileyFilter<SampleType>::prepare (const ProcessSpec& spec)
|
||||
{
|
||||
jassert (spec.sampleRate > 0);
|
||||
jassert (spec.numChannels > 0);
|
||||
|
||||
sampleRate = spec.sampleRate;
|
||||
update();
|
||||
|
||||
s1.resize (spec.numChannels);
|
||||
s2.resize (spec.numChannels);
|
||||
s3.resize (spec.numChannels);
|
||||
s4.resize (spec.numChannels);
|
||||
|
||||
reset();
|
||||
}
|
||||
|
||||
template <typename SampleType>
|
||||
void LinkwitzRileyFilter<SampleType>::reset()
|
||||
{
|
||||
for (auto s : { &s1, &s2, &s3, &s4 })
|
||||
std::fill (s->begin(), s->end(), static_cast<SampleType> (0));
|
||||
}
|
||||
|
||||
template <typename SampleType>
|
||||
void LinkwitzRileyFilter<SampleType>::snapToZero() noexcept
|
||||
{
|
||||
for (auto s : { &s1, &s2, &s3, &s4 })
|
||||
for (auto& element : *s)
|
||||
util::snapToZero (element);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
template <typename SampleType>
|
||||
SampleType LinkwitzRileyFilter<SampleType>::processSample (int channel, SampleType inputValue)
|
||||
{
|
||||
auto yH = (inputValue - (R2 + g) * s1[(size_t) channel] - s2[(size_t) channel]) * h;
|
||||
|
||||
auto yB = g * yH + s1[(size_t) channel];
|
||||
s1[(size_t) channel] = g * yH + yB;
|
||||
|
||||
auto yL = g * yB + s2[(size_t) channel];
|
||||
s2[(size_t) channel] = g * yB + yL;
|
||||
|
||||
if (filterType == Type::allpass)
|
||||
return yL - R2 * yB + yH;
|
||||
|
||||
auto yH2 = ((filterType == Type::lowpass ? yL : yH) - (R2 + g) * s3[(size_t) channel] - s4[(size_t) channel]) * h;
|
||||
|
||||
auto yB2 = g * yH2 + s3[(size_t) channel];
|
||||
s3[(size_t) channel] = g * yH2 + yB2;
|
||||
|
||||
auto yL2 = g * yB2 + s4[(size_t) channel];
|
||||
s4[(size_t) channel] = g * yB2 + yL2;
|
||||
|
||||
return filterType == Type::lowpass ? yL2 : yH2;
|
||||
}
|
||||
|
||||
template <typename SampleType>
|
||||
void LinkwitzRileyFilter<SampleType>::processSample (int channel, SampleType inputValue, SampleType &outputLow, SampleType &outputHigh)
|
||||
{
|
||||
auto yH = (inputValue - (R2 + g) * s1[(size_t) channel] - s2[(size_t) channel]) * h;
|
||||
|
||||
auto yB = g * yH + s1[(size_t) channel];
|
||||
s1[(size_t) channel] = g * yH + yB;
|
||||
|
||||
auto yL = g * yB + s2[(size_t) channel];
|
||||
s2[(size_t) channel] = g * yB + yL;
|
||||
|
||||
auto yH2 = (yL - (R2 + g) * s3[(size_t) channel] - s4[(size_t) channel]) * h;
|
||||
|
||||
auto yB2 = g * yH2 + s3[(size_t) channel];
|
||||
s3[(size_t) channel] = g * yH2 + yB2;
|
||||
|
||||
auto yL2 = g * yB2 + s4[(size_t) channel];
|
||||
s4[(size_t) channel] = g * yB2 + yL2;
|
||||
|
||||
outputLow = yL2;
|
||||
outputHigh = yL - R2 * yB + yH - yL2;
|
||||
}
|
||||
|
||||
template <typename SampleType>
|
||||
void LinkwitzRileyFilter<SampleType>::update()
|
||||
{
|
||||
g = (SampleType) std::tan (MathConstants<double>::pi * cutoffFrequency / sampleRate);
|
||||
R2 = (SampleType) std::sqrt (2.0);
|
||||
h = (SampleType) (1.0 / (1.0 + R2 * g + g * g));
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
template class LinkwitzRileyFilter<float>;
|
||||
template class LinkwitzRileyFilter<double>;
|
||||
|
||||
} // namespace dsp
|
||||
} // namespace juce
|
||||
|
@ -1,143 +1,143 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
namespace dsp
|
||||
{
|
||||
|
||||
enum class LinkwitzRileyFilterType
|
||||
{
|
||||
lowpass,
|
||||
highpass,
|
||||
allpass
|
||||
};
|
||||
|
||||
/**
|
||||
A filter class designed to perform multi-band separation using the TPT
|
||||
(Topology-Preserving Transform) structure.
|
||||
|
||||
Linkwitz-Riley filters are widely used in audio crossovers that have two outputs,
|
||||
a low-pass and a high-pass, such that their sum is equivalent to an all-pass filter
|
||||
with a flat magnitude frequency response. The Linkwitz-Riley filters available in
|
||||
this class are designed to have a -24 dB/octave slope (LR 4th order).
|
||||
|
||||
@tags{DSP}
|
||||
*/
|
||||
template <typename SampleType>
|
||||
class LinkwitzRileyFilter
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
using Type = LinkwitzRileyFilterType;
|
||||
|
||||
//==============================================================================
|
||||
/** Constructor. */
|
||||
LinkwitzRileyFilter();
|
||||
|
||||
//==============================================================================
|
||||
/** Sets the filter type. */
|
||||
void setType (Type newType);
|
||||
|
||||
/** Sets the cutoff frequency of the filter in Hz. */
|
||||
void setCutoffFrequency (SampleType newCutoffFrequencyHz);
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the type of the filter. */
|
||||
Type getType() const noexcept { return filterType; }
|
||||
|
||||
/** Returns the cutoff frequency of the filter. */
|
||||
SampleType getCutoffFrequency() const noexcept { return cutoffFrequency; }
|
||||
|
||||
//==============================================================================
|
||||
/** Initialises the filter. */
|
||||
void prepare (const ProcessSpec& spec);
|
||||
|
||||
/** Resets the internal state variables of the filter. */
|
||||
void reset();
|
||||
|
||||
//==============================================================================
|
||||
/** Processes the input and output samples supplied in the processing context. */
|
||||
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() <= s1.size());
|
||||
jassert (inputBlock.getNumChannels() == numChannels);
|
||||
jassert (inputBlock.getNumSamples() == numSamples);
|
||||
|
||||
if (context.isBypassed)
|
||||
{
|
||||
outputBlock.copyFrom (inputBlock);
|
||||
return;
|
||||
}
|
||||
|
||||
for (size_t channel = 0; channel < numChannels; ++channel)
|
||||
{
|
||||
auto* inputSamples = inputBlock.getChannelPointer (channel);
|
||||
auto* outputSamples = outputBlock.getChannelPointer (channel);
|
||||
|
||||
for (size_t i = 0; i < numSamples; ++i)
|
||||
outputSamples[i] = processSample ((int) channel, inputSamples[i]);
|
||||
}
|
||||
|
||||
#if JUCE_DSP_ENABLE_SNAP_TO_ZERO
|
||||
snapToZero();
|
||||
#endif
|
||||
}
|
||||
|
||||
/** Performs the filter operation on a single sample at a time. */
|
||||
SampleType processSample (int channel, SampleType inputValue);
|
||||
|
||||
/** Performs the filter operation on a single sample at a time, and returns both
|
||||
the low-pass and the high-pass outputs of the TPT structure.
|
||||
*/
|
||||
void processSample (int channel, SampleType inputValue, SampleType &outputLow, SampleType &outputHigh);
|
||||
|
||||
/** 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 update();
|
||||
|
||||
//==============================================================================
|
||||
SampleType g, R2, h;
|
||||
std::vector<SampleType> s1, s2, s3, s4;
|
||||
|
||||
double sampleRate = 44100.0;
|
||||
SampleType cutoffFrequency = 2000.0;
|
||||
Type filterType = Type::lowpass;
|
||||
};
|
||||
|
||||
} // namespace dsp
|
||||
} // namespace juce
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
namespace dsp
|
||||
{
|
||||
|
||||
enum class LinkwitzRileyFilterType
|
||||
{
|
||||
lowpass,
|
||||
highpass,
|
||||
allpass
|
||||
};
|
||||
|
||||
/**
|
||||
A filter class designed to perform multi-band separation using the TPT
|
||||
(Topology-Preserving Transform) structure.
|
||||
|
||||
Linkwitz-Riley filters are widely used in audio crossovers that have two outputs,
|
||||
a low-pass and a high-pass, such that their sum is equivalent to an all-pass filter
|
||||
with a flat magnitude frequency response. The Linkwitz-Riley filters available in
|
||||
this class are designed to have a -24 dB/octave slope (LR 4th order).
|
||||
|
||||
@tags{DSP}
|
||||
*/
|
||||
template <typename SampleType>
|
||||
class LinkwitzRileyFilter
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
using Type = LinkwitzRileyFilterType;
|
||||
|
||||
//==============================================================================
|
||||
/** Constructor. */
|
||||
LinkwitzRileyFilter();
|
||||
|
||||
//==============================================================================
|
||||
/** Sets the filter type. */
|
||||
void setType (Type newType);
|
||||
|
||||
/** Sets the cutoff frequency of the filter in Hz. */
|
||||
void setCutoffFrequency (SampleType newCutoffFrequencyHz);
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the type of the filter. */
|
||||
Type getType() const noexcept { return filterType; }
|
||||
|
||||
/** Returns the cutoff frequency of the filter. */
|
||||
SampleType getCutoffFrequency() const noexcept { return cutoffFrequency; }
|
||||
|
||||
//==============================================================================
|
||||
/** Initialises the filter. */
|
||||
void prepare (const ProcessSpec& spec);
|
||||
|
||||
/** Resets the internal state variables of the filter. */
|
||||
void reset();
|
||||
|
||||
//==============================================================================
|
||||
/** Processes the input and output samples supplied in the processing context. */
|
||||
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() <= s1.size());
|
||||
jassert (inputBlock.getNumChannels() == numChannels);
|
||||
jassert (inputBlock.getNumSamples() == numSamples);
|
||||
|
||||
if (context.isBypassed)
|
||||
{
|
||||
outputBlock.copyFrom (inputBlock);
|
||||
return;
|
||||
}
|
||||
|
||||
for (size_t channel = 0; channel < numChannels; ++channel)
|
||||
{
|
||||
auto* inputSamples = inputBlock.getChannelPointer (channel);
|
||||
auto* outputSamples = outputBlock.getChannelPointer (channel);
|
||||
|
||||
for (size_t i = 0; i < numSamples; ++i)
|
||||
outputSamples[i] = processSample ((int) channel, inputSamples[i]);
|
||||
}
|
||||
|
||||
#if JUCE_DSP_ENABLE_SNAP_TO_ZERO
|
||||
snapToZero();
|
||||
#endif
|
||||
}
|
||||
|
||||
/** Performs the filter operation on a single sample at a time. */
|
||||
SampleType processSample (int channel, SampleType inputValue);
|
||||
|
||||
/** Performs the filter operation on a single sample at a time, and returns both
|
||||
the low-pass and the high-pass outputs of the TPT structure.
|
||||
*/
|
||||
void processSample (int channel, SampleType inputValue, SampleType &outputLow, SampleType &outputHigh);
|
||||
|
||||
/** 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 update();
|
||||
|
||||
//==============================================================================
|
||||
SampleType g, R2, h;
|
||||
std::vector<SampleType> s1, s2, s3, s4;
|
||||
|
||||
double sampleRate = 44100.0;
|
||||
SampleType cutoffFrequency = 2000.0;
|
||||
Type filterType = Type::lowpass;
|
||||
};
|
||||
|
||||
} // namespace dsp
|
||||
} // namespace juce
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,214 +1,214 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
namespace dsp
|
||||
{
|
||||
|
||||
//===============================================================================
|
||||
/**
|
||||
A processor that performs multi-channel oversampling.
|
||||
|
||||
This class can be configured to do a factor of 2, 4, 8 or 16 times
|
||||
oversampling, using multiple stages, with polyphase allpass IIR filters or FIR
|
||||
filters, and latency compensation.
|
||||
|
||||
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 the input signal N times, processing the upsampled signal
|
||||
with the increased internal sample rate, then downsampling the result to get
|
||||
back to the original sample rate.
|
||||
|
||||
Choose between FIR or IIR filtering depending on your needs in terms of
|
||||
latency and phase distortion. With FIR filters the phase is linear but the
|
||||
latency is maximised. With IIR filtering the phase is compromised around the
|
||||
Nyquist frequency but the latency is minimised.
|
||||
|
||||
@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
|
||||
};
|
||||
|
||||
//===============================================================================
|
||||
/** The default constructor.
|
||||
|
||||
Note: This creates a "dummy" oversampling stage, which needs to be removed
|
||||
before adding proper oversampling stages.
|
||||
|
||||
@param numChannels the number of channels to process with this object
|
||||
|
||||
@see clearOversamplingStages, addOversamplingStage
|
||||
*/
|
||||
explicit Oversampling (size_t numChannels = 1);
|
||||
|
||||
/** Constructor.
|
||||
|
||||
@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, where
|
||||
the filters will be more efficient but the CPU load will
|
||||
increase as well
|
||||
@param useIntegerLatency if true this processor will add some fractional delay at the
|
||||
end of the signal path to ensure that the overall latency of
|
||||
the oversampling is an integer
|
||||
*/
|
||||
Oversampling (size_t numChannels,
|
||||
size_t factor,
|
||||
FilterType type,
|
||||
bool isMaxQuality = true,
|
||||
bool useIntegerLatency = false);
|
||||
|
||||
/** Destructor. */
|
||||
~Oversampling();
|
||||
|
||||
//===============================================================================
|
||||
/* Sets if this processor should add some fractional delay at the end of the signal
|
||||
path to ensure that the overall latency of the oversampling is an integer.
|
||||
*/
|
||||
void setUsingIntegerLatency (bool shouldUseIntegerLatency) noexcept;
|
||||
|
||||
/** Returns the latency in samples of the overall processing. You can use this
|
||||
information in your main processor to compensate the additional latency
|
||||
involved with the oversampling, for example with a dry / wet mixer, and to
|
||||
report the latency to the DAW.
|
||||
|
||||
Note: If you have not opted to use an integer latency then the latency may not be
|
||||
integer, so you might need to round its value or to compensate it properly in
|
||||
your processing code since plug-ins can only report integer latency values in
|
||||
samples to the DAW.
|
||||
*/
|
||||
SampleType getLatencyInSamples() const noexcept;
|
||||
|
||||
/** Returns the current oversampling factor. */
|
||||
size_t getOversamplingFactor() const 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.
|
||||
*/
|
||||
AudioBlock<SampleType> processSamplesUp (const AudioBlock<const 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 (AudioBlock<SampleType>& outputBlock) noexcept;
|
||||
|
||||
//===============================================================================
|
||||
/** Adds a new oversampling stage to the Oversampling class, multiplying the
|
||||
current oversampling factor by two. This is used with the default constructor
|
||||
to create custom oversampling chains, requiring a call to the
|
||||
clearOversamplingStages before any addition.
|
||||
|
||||
Note: Upsampling and downsampling filtering have different purposes, the
|
||||
former removes upsampling artefacts while the latter removes useless frequency
|
||||
content created by the oversampled process, so usually the attenuation is
|
||||
increased when upsampling compared to downsampling.
|
||||
|
||||
@param normalisedTransitionWidthUp a value between 0 and 0.5 which specifies how much
|
||||
the transition between passband and stopband is
|
||||
steep, for upsampling filtering (the lower the better)
|
||||
@param stopbandAmplitudedBUp the amplitude in dB in the stopband for upsampling
|
||||
filtering, must be negative
|
||||
@param normalisedTransitionWidthDown a value between 0 and 0.5 which specifies how much
|
||||
the transition between passband and stopband is
|
||||
steep, for downsampling filtering (the lower the better)
|
||||
@param stopbandAmplitudedBDown the amplitude in dB in the stopband for downsampling
|
||||
filtering, must be negative
|
||||
|
||||
@see clearOversamplingStages
|
||||
*/
|
||||
void addOversamplingStage (FilterType,
|
||||
float normalisedTransitionWidthUp, float stopbandAmplitudedBUp,
|
||||
float normalisedTransitionWidthDown, float stopbandAmplitudedBDown);
|
||||
|
||||
/** Adds a new "dummy" oversampling stage, which does nothing to the signal. Using
|
||||
one can be useful if your application features a customisable oversampling factor
|
||||
and if you want to select the current one from an OwnedArray without changing
|
||||
anything in the processing code.
|
||||
|
||||
@see OwnedArray, clearOversamplingStages, addOversamplingStage
|
||||
*/
|
||||
void addDummyOversamplingStage();
|
||||
|
||||
/** Removes all the previously registered oversampling stages, so you can add
|
||||
your own from scratch.
|
||||
|
||||
@see addOversamplingStage, addDummyOversamplingStage
|
||||
*/
|
||||
void clearOversamplingStages();
|
||||
|
||||
//===============================================================================
|
||||
size_t factorOversampling = 1;
|
||||
size_t numChannels = 1;
|
||||
|
||||
#ifndef DOXYGEN
|
||||
struct OversamplingStage;
|
||||
#endif
|
||||
|
||||
private:
|
||||
//===============================================================================
|
||||
void updateDelayLine();
|
||||
SampleType getUncompensatedLatency() const noexcept;
|
||||
|
||||
//===============================================================================
|
||||
OwnedArray<OversamplingStage> stages;
|
||||
bool isReady = false, shouldUseIntegerLatency = false;
|
||||
DelayLine<SampleType, DelayLineInterpolationTypes::Thiran> delay { 8 };
|
||||
SampleType fractionalDelay = 0;
|
||||
|
||||
//===============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Oversampling)
|
||||
};
|
||||
|
||||
} // namespace dsp
|
||||
} // namespace juce
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
namespace dsp
|
||||
{
|
||||
|
||||
//===============================================================================
|
||||
/**
|
||||
A processor that performs multi-channel oversampling.
|
||||
|
||||
This class can be configured to do a factor of 2, 4, 8 or 16 times
|
||||
oversampling, using multiple stages, with polyphase allpass IIR filters or FIR
|
||||
filters, and latency compensation.
|
||||
|
||||
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 the input signal N times, processing the upsampled signal
|
||||
with the increased internal sample rate, then downsampling the result to get
|
||||
back to the original sample rate.
|
||||
|
||||
Choose between FIR or IIR filtering depending on your needs in terms of
|
||||
latency and phase distortion. With FIR filters the phase is linear but the
|
||||
latency is maximised. With IIR filtering the phase is compromised around the
|
||||
Nyquist frequency but the latency is minimised.
|
||||
|
||||
@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
|
||||
};
|
||||
|
||||
//===============================================================================
|
||||
/** The default constructor.
|
||||
|
||||
Note: This creates a "dummy" oversampling stage, which needs to be removed
|
||||
before adding proper oversampling stages.
|
||||
|
||||
@param numChannels the number of channels to process with this object
|
||||
|
||||
@see clearOversamplingStages, addOversamplingStage
|
||||
*/
|
||||
explicit Oversampling (size_t numChannels = 1);
|
||||
|
||||
/** Constructor.
|
||||
|
||||
@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, where
|
||||
the filters will be more efficient but the CPU load will
|
||||
increase as well
|
||||
@param useIntegerLatency if true this processor will add some fractional delay at the
|
||||
end of the signal path to ensure that the overall latency of
|
||||
the oversampling is an integer
|
||||
*/
|
||||
Oversampling (size_t numChannels,
|
||||
size_t factor,
|
||||
FilterType type,
|
||||
bool isMaxQuality = true,
|
||||
bool useIntegerLatency = false);
|
||||
|
||||
/** Destructor. */
|
||||
~Oversampling();
|
||||
|
||||
//===============================================================================
|
||||
/* Sets if this processor should add some fractional delay at the end of the signal
|
||||
path to ensure that the overall latency of the oversampling is an integer.
|
||||
*/
|
||||
void setUsingIntegerLatency (bool shouldUseIntegerLatency) noexcept;
|
||||
|
||||
/** Returns the latency in samples of the overall processing. You can use this
|
||||
information in your main processor to compensate the additional latency
|
||||
involved with the oversampling, for example with a dry / wet mixer, and to
|
||||
report the latency to the DAW.
|
||||
|
||||
Note: If you have not opted to use an integer latency then the latency may not be
|
||||
integer, so you might need to round its value or to compensate it properly in
|
||||
your processing code since plug-ins can only report integer latency values in
|
||||
samples to the DAW.
|
||||
*/
|
||||
SampleType getLatencyInSamples() const noexcept;
|
||||
|
||||
/** Returns the current oversampling factor. */
|
||||
size_t getOversamplingFactor() const 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.
|
||||
*/
|
||||
AudioBlock<SampleType> processSamplesUp (const AudioBlock<const 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 (AudioBlock<SampleType>& outputBlock) noexcept;
|
||||
|
||||
//===============================================================================
|
||||
/** Adds a new oversampling stage to the Oversampling class, multiplying the
|
||||
current oversampling factor by two. This is used with the default constructor
|
||||
to create custom oversampling chains, requiring a call to the
|
||||
clearOversamplingStages before any addition.
|
||||
|
||||
Note: Upsampling and downsampling filtering have different purposes, the
|
||||
former removes upsampling artefacts while the latter removes useless frequency
|
||||
content created by the oversampled process, so usually the attenuation is
|
||||
increased when upsampling compared to downsampling.
|
||||
|
||||
@param normalisedTransitionWidthUp a value between 0 and 0.5 which specifies how much
|
||||
the transition between passband and stopband is
|
||||
steep, for upsampling filtering (the lower the better)
|
||||
@param stopbandAmplitudedBUp the amplitude in dB in the stopband for upsampling
|
||||
filtering, must be negative
|
||||
@param normalisedTransitionWidthDown a value between 0 and 0.5 which specifies how much
|
||||
the transition between passband and stopband is
|
||||
steep, for downsampling filtering (the lower the better)
|
||||
@param stopbandAmplitudedBDown the amplitude in dB in the stopband for downsampling
|
||||
filtering, must be negative
|
||||
|
||||
@see clearOversamplingStages
|
||||
*/
|
||||
void addOversamplingStage (FilterType,
|
||||
float normalisedTransitionWidthUp, float stopbandAmplitudedBUp,
|
||||
float normalisedTransitionWidthDown, float stopbandAmplitudedBDown);
|
||||
|
||||
/** Adds a new "dummy" oversampling stage, which does nothing to the signal. Using
|
||||
one can be useful if your application features a customisable oversampling factor
|
||||
and if you want to select the current one from an OwnedArray without changing
|
||||
anything in the processing code.
|
||||
|
||||
@see OwnedArray, clearOversamplingStages, addOversamplingStage
|
||||
*/
|
||||
void addDummyOversamplingStage();
|
||||
|
||||
/** Removes all the previously registered oversampling stages, so you can add
|
||||
your own from scratch.
|
||||
|
||||
@see addOversamplingStage, addDummyOversamplingStage
|
||||
*/
|
||||
void clearOversamplingStages();
|
||||
|
||||
//===============================================================================
|
||||
size_t factorOversampling = 1;
|
||||
size_t numChannels = 1;
|
||||
|
||||
#ifndef DOXYGEN
|
||||
struct OversamplingStage;
|
||||
#endif
|
||||
|
||||
private:
|
||||
//===============================================================================
|
||||
void updateDelayLine();
|
||||
SampleType getUncompensatedLatency() const noexcept;
|
||||
|
||||
//===============================================================================
|
||||
OwnedArray<OversamplingStage> stages;
|
||||
bool isReady = false, shouldUseIntegerLatency = false;
|
||||
DelayLine<SampleType, DelayLineInterpolationTypes::Thiran> delay { 8 };
|
||||
SampleType fractionalDelay = 0;
|
||||
|
||||
//===============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Oversampling)
|
||||
};
|
||||
|
||||
} // namespace dsp
|
||||
} // namespace juce
|
||||
|
@ -1,143 +1,143 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
namespace dsp
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
template <typename SampleType>
|
||||
Panner<SampleType>::Panner()
|
||||
{
|
||||
update();
|
||||
reset();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
template <typename SampleType>
|
||||
void Panner<SampleType>::setRule (Rule newRule)
|
||||
{
|
||||
currentRule = newRule;
|
||||
update();
|
||||
}
|
||||
|
||||
template <typename SampleType>
|
||||
void Panner<SampleType>::setPan (SampleType newPan)
|
||||
{
|
||||
jassert (newPan >= -1.0 && newPan <= 1.0);
|
||||
|
||||
pan = jlimit (static_cast<SampleType> (-1.0), static_cast<SampleType> (1.0), newPan);
|
||||
update();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
template <typename SampleType>
|
||||
void Panner<SampleType>::prepare (const ProcessSpec& spec)
|
||||
{
|
||||
jassert (spec.sampleRate > 0);
|
||||
jassert (spec.numChannels > 0);
|
||||
|
||||
sampleRate = spec.sampleRate;
|
||||
|
||||
reset();
|
||||
}
|
||||
|
||||
template <typename SampleType>
|
||||
void Panner<SampleType>::reset()
|
||||
{
|
||||
leftVolume .reset (sampleRate, 0.05);
|
||||
rightVolume.reset (sampleRate, 0.05);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
template <typename SampleType>
|
||||
void Panner<SampleType>::update()
|
||||
{
|
||||
SampleType leftValue, rightValue, boostValue;
|
||||
|
||||
auto normalisedPan = static_cast<SampleType> (0.5) * (pan + static_cast<SampleType> (1.0));
|
||||
|
||||
switch (currentRule)
|
||||
{
|
||||
case Rule::balanced:
|
||||
leftValue = jmin (static_cast<SampleType> (0.5), static_cast<SampleType> (1.0) - normalisedPan);
|
||||
rightValue = jmin (static_cast<SampleType> (0.5), normalisedPan);
|
||||
boostValue = static_cast<SampleType> (2.0);
|
||||
break;
|
||||
|
||||
case Rule::linear:
|
||||
leftValue = static_cast<SampleType> (1.0) - normalisedPan;
|
||||
rightValue = normalisedPan;
|
||||
boostValue = static_cast<SampleType> (2.0);
|
||||
break;
|
||||
|
||||
case Rule::sin3dB:
|
||||
leftValue = static_cast<SampleType> (std::sin (0.5 * MathConstants<double>::pi * (1.0 - normalisedPan)));
|
||||
rightValue = static_cast<SampleType> (std::sin (0.5 * MathConstants<double>::pi * normalisedPan));
|
||||
boostValue = std::sqrt (static_cast<SampleType> (2.0));
|
||||
break;
|
||||
|
||||
case Rule::sin4p5dB:
|
||||
leftValue = static_cast<SampleType> (std::pow (std::sin (0.5 * MathConstants<double>::pi * (1.0 - normalisedPan)), 1.5));
|
||||
rightValue = static_cast<SampleType> (std::pow (std::sin (0.5 * MathConstants<double>::pi * normalisedPan), 1.5));
|
||||
boostValue = static_cast<SampleType> (std::pow (2.0, 3.0 / 4.0));
|
||||
break;
|
||||
|
||||
case Rule::sin6dB:
|
||||
leftValue = static_cast<SampleType> (std::pow (std::sin (0.5 * MathConstants<double>::pi * (1.0 - normalisedPan)), 2.0));
|
||||
rightValue = static_cast<SampleType> (std::pow (std::sin (0.5 * MathConstants<double>::pi * normalisedPan), 2.0));
|
||||
boostValue = static_cast<SampleType> (2.0);
|
||||
break;
|
||||
|
||||
case Rule::squareRoot3dB:
|
||||
leftValue = std::sqrt (static_cast<SampleType> (1.0) - normalisedPan);
|
||||
rightValue = std::sqrt (normalisedPan);
|
||||
boostValue = std::sqrt (static_cast<SampleType> (2.0));
|
||||
break;
|
||||
|
||||
case Rule::squareRoot4p5dB:
|
||||
leftValue = static_cast<SampleType> (std::pow (std::sqrt (1.0 - normalisedPan), 1.5));
|
||||
rightValue = static_cast<SampleType> (std::pow (std::sqrt (normalisedPan), 1.5));
|
||||
boostValue = static_cast<SampleType> (std::pow (2.0, 3.0 / 4.0));
|
||||
break;
|
||||
|
||||
default:
|
||||
leftValue = jmin (static_cast<SampleType> (0.5), static_cast<SampleType> (1.0) - normalisedPan);
|
||||
rightValue = jmin (static_cast<SampleType> (0.5), normalisedPan);
|
||||
boostValue = static_cast<SampleType> (2.0);
|
||||
break;
|
||||
}
|
||||
|
||||
leftVolume .setTargetValue (leftValue * boostValue);
|
||||
rightVolume.setTargetValue (rightValue * boostValue);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
template class Panner<float>;
|
||||
template class Panner<double>;
|
||||
|
||||
} // namespace dsp
|
||||
} // namespace juce
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
namespace dsp
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
template <typename SampleType>
|
||||
Panner<SampleType>::Panner()
|
||||
{
|
||||
update();
|
||||
reset();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
template <typename SampleType>
|
||||
void Panner<SampleType>::setRule (Rule newRule)
|
||||
{
|
||||
currentRule = newRule;
|
||||
update();
|
||||
}
|
||||
|
||||
template <typename SampleType>
|
||||
void Panner<SampleType>::setPan (SampleType newPan)
|
||||
{
|
||||
jassert (newPan >= -1.0 && newPan <= 1.0);
|
||||
|
||||
pan = jlimit (static_cast<SampleType> (-1.0), static_cast<SampleType> (1.0), newPan);
|
||||
update();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
template <typename SampleType>
|
||||
void Panner<SampleType>::prepare (const ProcessSpec& spec)
|
||||
{
|
||||
jassert (spec.sampleRate > 0);
|
||||
jassert (spec.numChannels > 0);
|
||||
|
||||
sampleRate = spec.sampleRate;
|
||||
|
||||
reset();
|
||||
}
|
||||
|
||||
template <typename SampleType>
|
||||
void Panner<SampleType>::reset()
|
||||
{
|
||||
leftVolume .reset (sampleRate, 0.05);
|
||||
rightVolume.reset (sampleRate, 0.05);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
template <typename SampleType>
|
||||
void Panner<SampleType>::update()
|
||||
{
|
||||
SampleType leftValue, rightValue, boostValue;
|
||||
|
||||
auto normalisedPan = static_cast<SampleType> (0.5) * (pan + static_cast<SampleType> (1.0));
|
||||
|
||||
switch (currentRule)
|
||||
{
|
||||
case Rule::balanced:
|
||||
leftValue = jmin (static_cast<SampleType> (0.5), static_cast<SampleType> (1.0) - normalisedPan);
|
||||
rightValue = jmin (static_cast<SampleType> (0.5), normalisedPan);
|
||||
boostValue = static_cast<SampleType> (2.0);
|
||||
break;
|
||||
|
||||
case Rule::linear:
|
||||
leftValue = static_cast<SampleType> (1.0) - normalisedPan;
|
||||
rightValue = normalisedPan;
|
||||
boostValue = static_cast<SampleType> (2.0);
|
||||
break;
|
||||
|
||||
case Rule::sin3dB:
|
||||
leftValue = static_cast<SampleType> (std::sin (0.5 * MathConstants<double>::pi * (1.0 - normalisedPan)));
|
||||
rightValue = static_cast<SampleType> (std::sin (0.5 * MathConstants<double>::pi * normalisedPan));
|
||||
boostValue = std::sqrt (static_cast<SampleType> (2.0));
|
||||
break;
|
||||
|
||||
case Rule::sin4p5dB:
|
||||
leftValue = static_cast<SampleType> (std::pow (std::sin (0.5 * MathConstants<double>::pi * (1.0 - normalisedPan)), 1.5));
|
||||
rightValue = static_cast<SampleType> (std::pow (std::sin (0.5 * MathConstants<double>::pi * normalisedPan), 1.5));
|
||||
boostValue = static_cast<SampleType> (std::pow (2.0, 3.0 / 4.0));
|
||||
break;
|
||||
|
||||
case Rule::sin6dB:
|
||||
leftValue = static_cast<SampleType> (std::pow (std::sin (0.5 * MathConstants<double>::pi * (1.0 - normalisedPan)), 2.0));
|
||||
rightValue = static_cast<SampleType> (std::pow (std::sin (0.5 * MathConstants<double>::pi * normalisedPan), 2.0));
|
||||
boostValue = static_cast<SampleType> (2.0);
|
||||
break;
|
||||
|
||||
case Rule::squareRoot3dB:
|
||||
leftValue = std::sqrt (static_cast<SampleType> (1.0) - normalisedPan);
|
||||
rightValue = std::sqrt (normalisedPan);
|
||||
boostValue = std::sqrt (static_cast<SampleType> (2.0));
|
||||
break;
|
||||
|
||||
case Rule::squareRoot4p5dB:
|
||||
leftValue = static_cast<SampleType> (std::pow (std::sqrt (1.0 - normalisedPan), 1.5));
|
||||
rightValue = static_cast<SampleType> (std::pow (std::sqrt (normalisedPan), 1.5));
|
||||
boostValue = static_cast<SampleType> (std::pow (2.0, 3.0 / 4.0));
|
||||
break;
|
||||
|
||||
default:
|
||||
leftValue = jmin (static_cast<SampleType> (0.5), static_cast<SampleType> (1.0) - normalisedPan);
|
||||
rightValue = jmin (static_cast<SampleType> (0.5), normalisedPan);
|
||||
boostValue = static_cast<SampleType> (2.0);
|
||||
break;
|
||||
}
|
||||
|
||||
leftVolume .setTargetValue (leftValue * boostValue);
|
||||
rightVolume.setTargetValue (rightValue * boostValue);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
template class Panner<float>;
|
||||
template class Panner<double>;
|
||||
|
||||
} // namespace dsp
|
||||
} // namespace juce
|
||||
|
242
deps/juce/modules/juce_dsp/processors/juce_Panner.h
vendored
242
deps/juce/modules/juce_dsp/processors/juce_Panner.h
vendored
@ -1,121 +1,121 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
namespace dsp
|
||||
{
|
||||
|
||||
enum class PannerRule
|
||||
{
|
||||
linear, // regular 6 dB or linear panning rule, allows the panned sound to be
|
||||
// perceived as having a constant level when summed to mono
|
||||
balanced, // both left and right are 1 when pan value is 0, with left decreasing
|
||||
// to 0 above this value and right decreasing to 0 below it
|
||||
sin3dB, // alternate version of the regular 3 dB panning rule with a sine curve
|
||||
sin4p5dB, // alternate version of the regular 4.5 dB panning rule with a sine curve
|
||||
sin6dB, // alternate version of the regular 6 dB panning rule with a sine curve
|
||||
squareRoot3dB, // regular 3 dB or constant power panning rule, allows the panned sound
|
||||
// to be perceived as having a constant level regardless of the pan position
|
||||
squareRoot4p5dB // regular 4.5 dB panning rule, a compromise option between 3 dB and 6 dB panning rules
|
||||
};
|
||||
|
||||
/**
|
||||
A processor to perform panning operations on stereo buffers.
|
||||
|
||||
@tags{DSP}
|
||||
*/
|
||||
template <typename SampleType>
|
||||
class Panner
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
using Rule = PannerRule;
|
||||
|
||||
//==============================================================================
|
||||
/** Constructor. */
|
||||
Panner();
|
||||
|
||||
//==============================================================================
|
||||
/** Sets the panning rule. */
|
||||
void setRule (Rule newRule);
|
||||
|
||||
/** Sets the current panning value, between -1 (full left) and 1 (full right). */
|
||||
void setPan (SampleType newPan);
|
||||
|
||||
//==============================================================================
|
||||
/** Initialises the processor. */
|
||||
void prepare (const ProcessSpec& spec);
|
||||
|
||||
/** Resets the internal state variables of the processor. */
|
||||
void reset();
|
||||
|
||||
//==============================================================================
|
||||
/** Processes the input and output samples supplied in the processing context. */
|
||||
template <typename ProcessContext>
|
||||
void process (const ProcessContext& context) noexcept
|
||||
{
|
||||
const auto& inputBlock = context.getInputBlock();
|
||||
auto& outputBlock = context.getOutputBlock();
|
||||
|
||||
const auto numInputChannels = inputBlock.getNumChannels();
|
||||
const auto numOutputChannels = outputBlock.getNumChannels();
|
||||
const auto numSamples = outputBlock.getNumSamples();
|
||||
|
||||
jassertquiet (inputBlock.getNumSamples() == numSamples);
|
||||
|
||||
if (numOutputChannels != 2 || numInputChannels == 0 || numInputChannels > 2)
|
||||
return;
|
||||
|
||||
if (numInputChannels == 2)
|
||||
{
|
||||
outputBlock.copyFrom (inputBlock);
|
||||
}
|
||||
else
|
||||
{
|
||||
outputBlock.getSingleChannelBlock (0).copyFrom (inputBlock);
|
||||
outputBlock.getSingleChannelBlock (1).copyFrom (inputBlock);
|
||||
}
|
||||
|
||||
if (context.isBypassed)
|
||||
return;
|
||||
|
||||
outputBlock.getSingleChannelBlock (0).multiplyBy (leftVolume);
|
||||
outputBlock.getSingleChannelBlock (1).multiplyBy (rightVolume);
|
||||
}
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
void update();
|
||||
|
||||
//==============================================================================
|
||||
Rule currentRule = Rule::balanced;
|
||||
SampleType pan = 0.0;
|
||||
SmoothedValue<SampleType> leftVolume, rightVolume;
|
||||
double sampleRate = 44100.0;
|
||||
};
|
||||
|
||||
} // namespace dsp
|
||||
} // namespace juce
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
namespace dsp
|
||||
{
|
||||
|
||||
enum class PannerRule
|
||||
{
|
||||
linear, // regular 6 dB or linear panning rule, allows the panned sound to be
|
||||
// perceived as having a constant level when summed to mono
|
||||
balanced, // both left and right are 1 when pan value is 0, with left decreasing
|
||||
// to 0 above this value and right decreasing to 0 below it
|
||||
sin3dB, // alternate version of the regular 3 dB panning rule with a sine curve
|
||||
sin4p5dB, // alternate version of the regular 4.5 dB panning rule with a sine curve
|
||||
sin6dB, // alternate version of the regular 6 dB panning rule with a sine curve
|
||||
squareRoot3dB, // regular 3 dB or constant power panning rule, allows the panned sound
|
||||
// to be perceived as having a constant level regardless of the pan position
|
||||
squareRoot4p5dB // regular 4.5 dB panning rule, a compromise option between 3 dB and 6 dB panning rules
|
||||
};
|
||||
|
||||
/**
|
||||
A processor to perform panning operations on stereo buffers.
|
||||
|
||||
@tags{DSP}
|
||||
*/
|
||||
template <typename SampleType>
|
||||
class Panner
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
using Rule = PannerRule;
|
||||
|
||||
//==============================================================================
|
||||
/** Constructor. */
|
||||
Panner();
|
||||
|
||||
//==============================================================================
|
||||
/** Sets the panning rule. */
|
||||
void setRule (Rule newRule);
|
||||
|
||||
/** Sets the current panning value, between -1 (full left) and 1 (full right). */
|
||||
void setPan (SampleType newPan);
|
||||
|
||||
//==============================================================================
|
||||
/** Initialises the processor. */
|
||||
void prepare (const ProcessSpec& spec);
|
||||
|
||||
/** Resets the internal state variables of the processor. */
|
||||
void reset();
|
||||
|
||||
//==============================================================================
|
||||
/** Processes the input and output samples supplied in the processing context. */
|
||||
template <typename ProcessContext>
|
||||
void process (const ProcessContext& context) noexcept
|
||||
{
|
||||
const auto& inputBlock = context.getInputBlock();
|
||||
auto& outputBlock = context.getOutputBlock();
|
||||
|
||||
const auto numInputChannels = inputBlock.getNumChannels();
|
||||
const auto numOutputChannels = outputBlock.getNumChannels();
|
||||
const auto numSamples = outputBlock.getNumSamples();
|
||||
|
||||
jassertquiet (inputBlock.getNumSamples() == numSamples);
|
||||
|
||||
if (numOutputChannels != 2 || numInputChannels == 0 || numInputChannels > 2)
|
||||
return;
|
||||
|
||||
if (numInputChannels == 2)
|
||||
{
|
||||
outputBlock.copyFrom (inputBlock);
|
||||
}
|
||||
else
|
||||
{
|
||||
outputBlock.getSingleChannelBlock (0).copyFrom (inputBlock);
|
||||
outputBlock.getSingleChannelBlock (1).copyFrom (inputBlock);
|
||||
}
|
||||
|
||||
if (context.isBypassed)
|
||||
return;
|
||||
|
||||
outputBlock.getSingleChannelBlock (0).multiplyBy (leftVolume);
|
||||
outputBlock.getSingleChannelBlock (1).multiplyBy (rightVolume);
|
||||
}
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
void update();
|
||||
|
||||
//==============================================================================
|
||||
Rule currentRule = Rule::balanced;
|
||||
SampleType pan = 0.0;
|
||||
SmoothedValue<SampleType> leftVolume, rightVolume;
|
||||
double sampleRate = 44100.0;
|
||||
};
|
||||
|
||||
} // namespace dsp
|
||||
} // namespace juce
|
||||
|
@ -1,177 +1,186 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
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 processors. This is useful 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>;
|
||||
using ConstAudioBlockType = AudioBlock<const 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 ConstAudioBlockType& getInputBlock() const noexcept { return constBlock; }
|
||||
|
||||
/** Returns the audio block to use as the output to a process function. */
|
||||
AudioBlockType& getOutputBlock() const noexcept { return 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;
|
||||
ConstAudioBlockType constBlock { 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>;
|
||||
using ConstAudioBlockType = AudioBlock<const 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 ConstAudioBlockType& input, AudioBlockType& output) noexcept
|
||||
: inputBlock (input), outputBlock (output)
|
||||
{
|
||||
// If the input and output blocks are the same then you should use
|
||||
// ProcessContextReplacing instead.
|
||||
jassert (input != output);
|
||||
}
|
||||
|
||||
ProcessContextNonReplacing (const ProcessContextNonReplacing&) = default;
|
||||
ProcessContextNonReplacing (ProcessContextNonReplacing&&) = default;
|
||||
|
||||
/** Returns the audio block to use as the input to a process function. */
|
||||
const ConstAudioBlockType& getInputBlock() const noexcept { return inputBlock; }
|
||||
|
||||
/** Returns the audio block to use as the output to a process function. */
|
||||
AudioBlockType& getOutputBlock() const noexcept { return 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:
|
||||
ConstAudioBlockType inputBlock;
|
||||
AudioBlockType& outputBlock;
|
||||
};
|
||||
|
||||
} // namespace dsp
|
||||
} // namespace juce
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
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;
|
||||
};
|
||||
|
||||
constexpr bool operator== (const ProcessSpec& a, const ProcessSpec& b)
|
||||
{
|
||||
return a.sampleRate == b.sampleRate
|
||||
&& a.maximumBlockSize == b.maximumBlockSize
|
||||
&& a.numChannels == b.numChannels;
|
||||
}
|
||||
|
||||
constexpr bool operator!= (const ProcessSpec& a, const ProcessSpec& b) { return ! (a == b); }
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
This is a handy base class for the state of a processor (such as parameter values)
|
||||
which is typically shared among several processors. This is useful 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>;
|
||||
using ConstAudioBlockType = AudioBlock<const 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 ConstAudioBlockType& getInputBlock() const noexcept { return constBlock; }
|
||||
|
||||
/** Returns the audio block to use as the output to a process function. */
|
||||
AudioBlockType& getOutputBlock() const noexcept { return 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;
|
||||
ConstAudioBlockType constBlock { 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>;
|
||||
using ConstAudioBlockType = AudioBlock<const SampleType>;
|
||||
|
||||
/** Creates a ProcessContextNonReplacing 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 ConstAudioBlockType& input, AudioBlockType& output) noexcept
|
||||
: inputBlock (input), outputBlock (output)
|
||||
{
|
||||
// If the input and output blocks are the same then you should use
|
||||
// ProcessContextReplacing instead.
|
||||
jassert (input != output);
|
||||
}
|
||||
|
||||
ProcessContextNonReplacing (const ProcessContextNonReplacing&) = default;
|
||||
ProcessContextNonReplacing (ProcessContextNonReplacing&&) = default;
|
||||
|
||||
/** Returns the audio block to use as the input to a process function. */
|
||||
const ConstAudioBlockType& getInputBlock() const noexcept { return inputBlock; }
|
||||
|
||||
/** Returns the audio block to use as the output to a process function. */
|
||||
AudioBlockType& getOutputBlock() const noexcept { return 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:
|
||||
ConstAudioBlockType inputBlock;
|
||||
AudioBlockType& outputBlock;
|
||||
};
|
||||
|
||||
} // namespace dsp
|
||||
} // namespace juce
|
||||
|
@ -1,175 +1,185 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
namespace dsp
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
#ifndef DOXYGEN
|
||||
/** The contents of this namespace are used to implement ProcessorChain and should
|
||||
not be used elsewhere. Their interfaces (and existence) are liable to change!
|
||||
*/
|
||||
namespace detail
|
||||
{
|
||||
template <typename Fn, typename Tuple, size_t... Ix>
|
||||
constexpr void forEachInTuple (Fn&& fn, Tuple&& tuple, std::index_sequence<Ix...>)
|
||||
noexcept (noexcept (std::initializer_list<int> { (fn (std::get<Ix> (tuple), Ix), 0)... }))
|
||||
{
|
||||
(void) std::initializer_list<int> { ((void) fn (std::get<Ix> (tuple), Ix), 0)... };
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
using TupleIndexSequence = std::make_index_sequence<std::tuple_size<std::remove_cv_t<std::remove_reference_t<T>>>::value>;
|
||||
|
||||
template <typename Fn, typename Tuple>
|
||||
constexpr void forEachInTuple (Fn&& fn, Tuple&& tuple)
|
||||
noexcept (noexcept (forEachInTuple (std::forward<Fn> (fn), std::forward<Tuple> (tuple), TupleIndexSequence<Tuple>{})))
|
||||
{
|
||||
forEachInTuple (std::forward<Fn> (fn), std::forward<Tuple> (tuple), TupleIndexSequence<Tuple>{});
|
||||
}
|
||||
}
|
||||
#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.
|
||||
|
||||
@tags{DSP}
|
||||
*/
|
||||
template <typename... Processors>
|
||||
class ProcessorChain
|
||||
{
|
||||
public:
|
||||
/** Get a reference to the processor at index `Index`. */
|
||||
template <int Index> auto& get() noexcept { return std::get<Index> (processors); }
|
||||
|
||||
/** Get a reference to the processor at index `Index`. */
|
||||
template <int Index> const auto& get() const noexcept { return std::get<Index> (processors); }
|
||||
|
||||
/** Set the processor at index `Index` to be bypassed or enabled. */
|
||||
template <int Index>
|
||||
void setBypassed (bool b) noexcept { bypassed[(size_t) Index] = b; }
|
||||
|
||||
/** Query whether the processor at index `Index` is bypassed. */
|
||||
template <int Index>
|
||||
bool isBypassed() const noexcept { return bypassed[(size_t) Index]; }
|
||||
|
||||
/** Prepare all inner processors with the provided `ProcessSpec`. */
|
||||
void prepare (const ProcessSpec& spec)
|
||||
{
|
||||
detail::forEachInTuple ([&] (auto& proc, size_t) { proc.prepare (spec); }, processors);
|
||||
}
|
||||
|
||||
/** Reset all inner processors. */
|
||||
void reset()
|
||||
{
|
||||
detail::forEachInTuple ([] (auto& proc, size_t) { proc.reset(); }, processors);
|
||||
}
|
||||
|
||||
/** Process `context` through all inner processors in sequence. */
|
||||
template <typename ProcessContext>
|
||||
void process (const ProcessContext& context) noexcept
|
||||
{
|
||||
detail::forEachInTuple ([&] (auto& proc, size_t index) noexcept
|
||||
{
|
||||
if (context.usesSeparateInputAndOutputBlocks() && index != 0)
|
||||
{
|
||||
jassert (context.getOutputBlock().getNumChannels() == context.getInputBlock().getNumChannels());
|
||||
ProcessContextReplacing<typename ProcessContext::SampleType> replacingContext (context.getOutputBlock());
|
||||
replacingContext.isBypassed = (bypassed[index] || context.isBypassed);
|
||||
|
||||
proc.process (replacingContext);
|
||||
}
|
||||
else
|
||||
{
|
||||
ProcessContext contextCopy (context);
|
||||
contextCopy.isBypassed = (bypassed[index] || context.isBypassed);
|
||||
|
||||
proc.process (contextCopy);
|
||||
}
|
||||
}, processors);
|
||||
}
|
||||
|
||||
private:
|
||||
std::tuple<Processors...> processors;
|
||||
std::array<bool, sizeof...(Processors)> bypassed { {} };
|
||||
};
|
||||
|
||||
/** Non-member equivalent of ProcessorChain::get which avoids awkward
|
||||
member template syntax.
|
||||
*/
|
||||
template <int Index, typename... Processors>
|
||||
inline auto& get (ProcessorChain<Processors...>& chain) noexcept
|
||||
{
|
||||
return chain.template get<Index>();
|
||||
}
|
||||
|
||||
/** Non-member equivalent of ProcessorChain::get which avoids awkward
|
||||
member template syntax.
|
||||
*/
|
||||
template <int Index, typename... Processors>
|
||||
inline auto& get (const ProcessorChain<Processors...>& chain) noexcept
|
||||
{
|
||||
return chain.template get<Index>();
|
||||
}
|
||||
|
||||
/** Non-member equivalent of ProcessorChain::setBypassed which avoids awkward
|
||||
member template syntax.
|
||||
*/
|
||||
template <int Index, typename... Processors>
|
||||
inline void setBypassed (ProcessorChain<Processors...>& chain, bool bypassed) noexcept
|
||||
{
|
||||
chain.template setBypassed<Index> (bypassed);
|
||||
}
|
||||
|
||||
/** Non-member equivalent of ProcessorChain::isBypassed which avoids awkward
|
||||
member template syntax.
|
||||
*/
|
||||
template <int Index, typename... Processors>
|
||||
inline bool isBypassed (const ProcessorChain<Processors...>& chain) noexcept
|
||||
{
|
||||
return chain.template isBypassed<Index>();
|
||||
}
|
||||
|
||||
} // namespace dsp
|
||||
} // namespace juce
|
||||
|
||||
namespace std
|
||||
{
|
||||
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wmismatched-tags")
|
||||
|
||||
/** Adds support for C++17 structured bindings. */
|
||||
template <typename... Processors>
|
||||
struct tuple_size<::juce::dsp::ProcessorChain<Processors...>> : integral_constant<size_t, sizeof... (Processors)> {};
|
||||
|
||||
/** Adds support for C++17 structured bindings. */
|
||||
template <size_t I, typename... Processors>
|
||||
struct tuple_element<I, ::juce::dsp::ProcessorChain<Processors...>> : tuple_element<I, tuple<Processors...>> {};
|
||||
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
|
||||
} // namespace std
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
namespace dsp
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
#ifndef DOXYGEN
|
||||
/** The contents of this namespace are used to implement ProcessorChain and should
|
||||
not be used elsewhere. Their interfaces (and existence) are liable to change!
|
||||
*/
|
||||
namespace detail
|
||||
{
|
||||
template <typename Fn, typename Tuple, size_t... Ix>
|
||||
constexpr void forEachInTuple (Fn&& fn, Tuple&& tuple, std::index_sequence<Ix...>)
|
||||
{
|
||||
(void) std::initializer_list<int> { ((void) fn (std::get<Ix> (tuple), std::integral_constant<size_t, Ix>()), 0)... };
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
using TupleIndexSequence = std::make_index_sequence<std::tuple_size<std::remove_cv_t<std::remove_reference_t<T>>>::value>;
|
||||
|
||||
template <typename Fn, typename Tuple>
|
||||
constexpr void forEachInTuple (Fn&& fn, Tuple&& tuple)
|
||||
{
|
||||
forEachInTuple (std::forward<Fn> (fn), std::forward<Tuple> (tuple), TupleIndexSequence<Tuple>{});
|
||||
}
|
||||
|
||||
// This could be a template variable, but that code causes an internal compiler error in MSVC 19.00.24215
|
||||
template <typename Context, size_t Ix>
|
||||
struct UseContextDirectly
|
||||
{
|
||||
static constexpr auto value = ! Context::usesSeparateInputAndOutputBlocks() || Ix == 0;
|
||||
};
|
||||
}
|
||||
#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.
|
||||
|
||||
@tags{DSP}
|
||||
*/
|
||||
template <typename... Processors>
|
||||
class ProcessorChain
|
||||
{
|
||||
public:
|
||||
/** Get a reference to the processor at index `Index`. */
|
||||
template <int Index> auto& get() noexcept { return std::get<Index> (processors); }
|
||||
|
||||
/** Get a reference to the processor at index `Index`. */
|
||||
template <int Index> const auto& get() const noexcept { return std::get<Index> (processors); }
|
||||
|
||||
/** Set the processor at index `Index` to be bypassed or enabled. */
|
||||
template <int Index>
|
||||
void setBypassed (bool b) noexcept { bypassed[(size_t) Index] = b; }
|
||||
|
||||
/** Query whether the processor at index `Index` is bypassed. */
|
||||
template <int Index>
|
||||
bool isBypassed() const noexcept { return bypassed[(size_t) Index]; }
|
||||
|
||||
/** Prepare all inner processors with the provided `ProcessSpec`. */
|
||||
void prepare (const ProcessSpec& spec)
|
||||
{
|
||||
detail::forEachInTuple ([&] (auto& proc, auto) { proc.prepare (spec); }, processors);
|
||||
}
|
||||
|
||||
/** Reset all inner processors. */
|
||||
void reset()
|
||||
{
|
||||
detail::forEachInTuple ([] (auto& proc, auto) { proc.reset(); }, processors);
|
||||
}
|
||||
|
||||
/** Process `context` through all inner processors in sequence. */
|
||||
template <typename ProcessContext>
|
||||
void process (const ProcessContext& context) noexcept
|
||||
{
|
||||
detail::forEachInTuple ([this, &context] (auto& proc, auto index) noexcept { this->processOne (context, proc, index); },
|
||||
processors);
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename Context, typename Proc, size_t Ix, std::enable_if_t<! detail::UseContextDirectly<Context, Ix>::value, int> = 0>
|
||||
void processOne (const Context& context, Proc& proc, std::integral_constant<size_t, Ix>) noexcept
|
||||
{
|
||||
jassert (context.getOutputBlock().getNumChannels() == context.getInputBlock().getNumChannels());
|
||||
ProcessContextReplacing<typename Context::SampleType> replacingContext (context.getOutputBlock());
|
||||
replacingContext.isBypassed = (bypassed[Ix] || context.isBypassed);
|
||||
|
||||
proc.process (replacingContext);
|
||||
}
|
||||
|
||||
template <typename Context, typename Proc, size_t Ix, std::enable_if_t<detail::UseContextDirectly<Context, Ix>::value, int> = 0>
|
||||
void processOne (const Context& context, Proc& proc, std::integral_constant<size_t, Ix>) noexcept
|
||||
{
|
||||
auto contextCopy = context;
|
||||
contextCopy.isBypassed = (bypassed[Ix] || context.isBypassed);
|
||||
|
||||
proc.process (contextCopy);
|
||||
}
|
||||
|
||||
std::tuple<Processors...> processors;
|
||||
std::array<bool, sizeof...(Processors)> bypassed { {} };
|
||||
};
|
||||
|
||||
/** Non-member equivalent of ProcessorChain::get which avoids awkward
|
||||
member template syntax.
|
||||
*/
|
||||
template <int Index, typename... Processors>
|
||||
inline auto& get (ProcessorChain<Processors...>& chain) noexcept
|
||||
{
|
||||
return chain.template get<Index>();
|
||||
}
|
||||
|
||||
/** Non-member equivalent of ProcessorChain::get which avoids awkward
|
||||
member template syntax.
|
||||
*/
|
||||
template <int Index, typename... Processors>
|
||||
inline auto& get (const ProcessorChain<Processors...>& chain) noexcept
|
||||
{
|
||||
return chain.template get<Index>();
|
||||
}
|
||||
|
||||
/** Non-member equivalent of ProcessorChain::setBypassed which avoids awkward
|
||||
member template syntax.
|
||||
*/
|
||||
template <int Index, typename... Processors>
|
||||
inline void setBypassed (ProcessorChain<Processors...>& chain, bool bypassed) noexcept
|
||||
{
|
||||
chain.template setBypassed<Index> (bypassed);
|
||||
}
|
||||
|
||||
/** Non-member equivalent of ProcessorChain::isBypassed which avoids awkward
|
||||
member template syntax.
|
||||
*/
|
||||
template <int Index, typename... Processors>
|
||||
inline bool isBypassed (const ProcessorChain<Processors...>& chain) noexcept
|
||||
{
|
||||
return chain.template isBypassed<Index>();
|
||||
}
|
||||
|
||||
} // namespace dsp
|
||||
} // namespace juce
|
||||
|
||||
#ifndef DOXYGEN
|
||||
namespace std
|
||||
{
|
||||
|
||||
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wmismatched-tags")
|
||||
|
||||
/** Adds support for C++17 structured bindings. */
|
||||
template <typename... Processors>
|
||||
struct tuple_size<::juce::dsp::ProcessorChain<Processors...>> : integral_constant<size_t, sizeof... (Processors)> {};
|
||||
|
||||
/** Adds support for C++17 structured bindings. */
|
||||
template <size_t I, typename... Processors>
|
||||
struct tuple_element<I, ::juce::dsp::ProcessorChain<Processors...>> : tuple_element<I, tuple<Processors...>> {};
|
||||
|
||||
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
|
||||
|
||||
} // namespace std
|
||||
#endif
|
||||
|
@ -1,141 +1,167 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
namespace dsp
|
||||
{
|
||||
|
||||
class ProcessorChainTest : public UnitTest
|
||||
{
|
||||
template <int AddValue>
|
||||
struct MockProcessor
|
||||
{
|
||||
void prepare (const ProcessSpec&) noexcept { isPrepared = true; }
|
||||
void reset() noexcept { isReset = true; }
|
||||
|
||||
template <typename Context>
|
||||
void process (const Context& context) noexcept
|
||||
{
|
||||
bufferWasClear = context.getInputBlock().getSample (0, 0) == 0;
|
||||
|
||||
if (! context.isBypassed)
|
||||
context.getOutputBlock().add (AddValue);
|
||||
}
|
||||
|
||||
bool isPrepared = false;
|
||||
bool isReset = false;
|
||||
bool bufferWasClear = false;
|
||||
};
|
||||
|
||||
public:
|
||||
ProcessorChainTest()
|
||||
: UnitTest ("ProcessorChain", UnitTestCategories::dsp) {}
|
||||
|
||||
void runTest() override
|
||||
{
|
||||
beginTest ("After calling setBypass, processor is bypassed");
|
||||
{
|
||||
ProcessorChain<MockProcessor<1>, MockProcessor<2>> chain;
|
||||
|
||||
setBypassed<0> (chain, true);
|
||||
expect (isBypassed<0> (chain));
|
||||
setBypassed<0> (chain, false);
|
||||
expect (! isBypassed<0> (chain));
|
||||
|
||||
setBypassed<1> (chain, true);
|
||||
expect (isBypassed<1> (chain));
|
||||
setBypassed<1> (chain, false);
|
||||
expect (! isBypassed<1> (chain));
|
||||
}
|
||||
|
||||
beginTest ("After calling prepare, all processors are prepared");
|
||||
{
|
||||
ProcessorChain<MockProcessor<1>, MockProcessor<2>> chain;
|
||||
|
||||
expect (! get<0> (chain).isPrepared);
|
||||
expect (! get<1> (chain).isPrepared);
|
||||
|
||||
chain.prepare (ProcessSpec{});
|
||||
|
||||
expect (get<0> (chain).isPrepared);
|
||||
expect (get<1> (chain).isPrepared);
|
||||
}
|
||||
|
||||
beginTest ("After calling reset, all processors are reset");
|
||||
{
|
||||
ProcessorChain<MockProcessor<1>, MockProcessor<2>> chain;
|
||||
|
||||
expect (! get<0> (chain).isReset);
|
||||
expect (! get<1> (chain).isReset);
|
||||
|
||||
chain.reset();
|
||||
|
||||
expect (get<0> (chain).isReset);
|
||||
expect (get<1> (chain).isReset);
|
||||
}
|
||||
|
||||
beginTest ("After calling process, all processors contribute to processing");
|
||||
{
|
||||
ProcessorChain<MockProcessor<1>, MockProcessor<2>> chain;
|
||||
|
||||
AudioBuffer<float> buffer (1, 1);
|
||||
AudioBlock<float> block (buffer);
|
||||
ProcessContextReplacing<float> context (block);
|
||||
|
||||
block.clear();
|
||||
chain.process (context);
|
||||
expectEquals (buffer.getSample (0, 0), 3.0f);
|
||||
expect (get<0> (chain).bufferWasClear);
|
||||
expect (! get<1> (chain).bufferWasClear);
|
||||
|
||||
setBypassed<0> (chain, true);
|
||||
block.clear();
|
||||
chain.process (context);
|
||||
expectEquals (buffer.getSample (0, 0), 2.0f);
|
||||
expect (get<0> (chain).bufferWasClear);
|
||||
expect (get<1> (chain).bufferWasClear);
|
||||
|
||||
setBypassed<1> (chain, true);
|
||||
block.clear();
|
||||
chain.process (context);
|
||||
expectEquals (buffer.getSample (0, 0), 0.0f);
|
||||
expect (get<0> (chain).bufferWasClear);
|
||||
expect (get<1> (chain).bufferWasClear);
|
||||
|
||||
setBypassed<0> (chain, false);
|
||||
block.clear();
|
||||
chain.process (context);
|
||||
expectEquals (buffer.getSample (0, 0), 1.0f);
|
||||
expect (get<0> (chain).bufferWasClear);
|
||||
expect (! get<1> (chain).bufferWasClear);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static ProcessorChainTest processorChainUnitTest;
|
||||
|
||||
} // namespace dsp
|
||||
} // namespace juce
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
namespace dsp
|
||||
{
|
||||
|
||||
class ProcessorChainTest : public UnitTest
|
||||
{
|
||||
template <int AddValue>
|
||||
struct MockProcessor
|
||||
{
|
||||
void prepare (const ProcessSpec&) noexcept { isPrepared = true; }
|
||||
void reset() noexcept { isReset = true; }
|
||||
|
||||
template <typename Context>
|
||||
void process (const Context& context) noexcept
|
||||
{
|
||||
bufferWasClear = context.getInputBlock().getSample (0, 0) == 0;
|
||||
|
||||
if (! context.isBypassed)
|
||||
context.getOutputBlock().add (AddValue);
|
||||
}
|
||||
|
||||
bool isPrepared = false;
|
||||
bool isReset = false;
|
||||
bool bufferWasClear = false;
|
||||
};
|
||||
|
||||
public:
|
||||
ProcessorChainTest()
|
||||
: UnitTest ("ProcessorChain", UnitTestCategories::dsp) {}
|
||||
|
||||
void runTest() override
|
||||
{
|
||||
beginTest ("After calling setBypass, processor is bypassed");
|
||||
{
|
||||
ProcessorChain<MockProcessor<1>, MockProcessor<2>> chain;
|
||||
|
||||
setBypassed<0> (chain, true);
|
||||
expect (isBypassed<0> (chain));
|
||||
setBypassed<0> (chain, false);
|
||||
expect (! isBypassed<0> (chain));
|
||||
|
||||
setBypassed<1> (chain, true);
|
||||
expect (isBypassed<1> (chain));
|
||||
setBypassed<1> (chain, false);
|
||||
expect (! isBypassed<1> (chain));
|
||||
}
|
||||
|
||||
beginTest ("After calling prepare, all processors are prepared");
|
||||
{
|
||||
ProcessorChain<MockProcessor<1>, MockProcessor<2>> chain;
|
||||
|
||||
expect (! get<0> (chain).isPrepared);
|
||||
expect (! get<1> (chain).isPrepared);
|
||||
|
||||
chain.prepare (ProcessSpec{});
|
||||
|
||||
expect (get<0> (chain).isPrepared);
|
||||
expect (get<1> (chain).isPrepared);
|
||||
}
|
||||
|
||||
beginTest ("After calling reset, all processors are reset");
|
||||
{
|
||||
ProcessorChain<MockProcessor<1>, MockProcessor<2>> chain;
|
||||
|
||||
expect (! get<0> (chain).isReset);
|
||||
expect (! get<1> (chain).isReset);
|
||||
|
||||
chain.reset();
|
||||
|
||||
expect (get<0> (chain).isReset);
|
||||
expect (get<1> (chain).isReset);
|
||||
}
|
||||
|
||||
beginTest ("After calling process, all processors contribute to processing");
|
||||
{
|
||||
ProcessorChain<MockProcessor<1>, MockProcessor<2>> chain;
|
||||
|
||||
AudioBuffer<float> buffer (1, 1);
|
||||
AudioBlock<float> block (buffer);
|
||||
ProcessContextReplacing<float> context (block);
|
||||
|
||||
block.clear();
|
||||
chain.process (context);
|
||||
expectEquals (buffer.getSample (0, 0), 3.0f);
|
||||
expect (get<0> (chain).bufferWasClear);
|
||||
expect (! get<1> (chain).bufferWasClear);
|
||||
|
||||
setBypassed<0> (chain, true);
|
||||
block.clear();
|
||||
chain.process (context);
|
||||
expectEquals (buffer.getSample (0, 0), 2.0f);
|
||||
expect (get<0> (chain).bufferWasClear);
|
||||
expect (get<1> (chain).bufferWasClear);
|
||||
|
||||
setBypassed<1> (chain, true);
|
||||
block.clear();
|
||||
chain.process (context);
|
||||
expectEquals (buffer.getSample (0, 0), 0.0f);
|
||||
expect (get<0> (chain).bufferWasClear);
|
||||
expect (get<1> (chain).bufferWasClear);
|
||||
|
||||
setBypassed<0> (chain, false);
|
||||
block.clear();
|
||||
chain.process (context);
|
||||
expectEquals (buffer.getSample (0, 0), 1.0f);
|
||||
expect (get<0> (chain).bufferWasClear);
|
||||
expect (! get<1> (chain).bufferWasClear);
|
||||
}
|
||||
|
||||
beginTest ("Chains with trailing items that only support replacing contexts can be built");
|
||||
{
|
||||
AudioBuffer<float> inBuf (1, 1), outBuf (1, 1);
|
||||
juce::dsp::AudioBlock<float> in (inBuf), out (outBuf);
|
||||
|
||||
struct OnlyReplacing
|
||||
{
|
||||
void prepare (const juce::dsp::ProcessSpec&) {}
|
||||
void process (const juce::dsp::ProcessContextReplacing<float>& c)
|
||||
{
|
||||
c.getOutputBlock().multiplyBy (2.0f);
|
||||
}
|
||||
void reset() {}
|
||||
};
|
||||
|
||||
{
|
||||
juce::dsp::ProcessorChain<juce::dsp::Gain<float>, OnlyReplacing, OnlyReplacing> c;
|
||||
juce::dsp::ProcessContextNonReplacing<float> context (in, out);
|
||||
get<0> (c).setGainLinear (1.0f);
|
||||
c.prepare (ProcessSpec{});
|
||||
inBuf.setSample (0, 0, 1.0f);
|
||||
c.process (context);
|
||||
expectEquals (outBuf.getSample (0, 0), 4.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static ProcessorChainTest processorChainUnitTest;
|
||||
|
||||
} // namespace dsp
|
||||
} // namespace juce
|
||||
|
@ -1,99 +1,99 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
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 (typename StateType::Ptr stateToUse) : state (std::move (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::ConstAudioBlockType 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
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
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 (typename StateType::Ptr stateToUse) : state (std::move (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::ConstAudioBlockType 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
|
||||
|
@ -1,81 +1,81 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
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
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
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
|
||||
|
@ -1,260 +1,260 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
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 per 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 as its gain can be
|
||||
higher than 0 dB. For the classic 0 dB bandpass, we need to multiply the
|
||||
result by R2.
|
||||
|
||||
Note 2: Using this class prevents some loud audio artefacts commonly encountered when
|
||||
changing the cutoff frequency using other filter simulation structures and IIR
|
||||
filter classes. However, this class may still require additional smoothing for
|
||||
cutoff frequency changes.
|
||||
|
||||
see IIRFilter, SmoothedValue
|
||||
|
||||
@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;
|
||||
|
||||
/** A typedef for a ref-counted pointer to the coefficients object */
|
||||
using ParametersPtr = typename Parameters<NumericType>::Ptr;
|
||||
|
||||
//==============================================================================
|
||||
#ifndef DOXYGEN
|
||||
/** Creates a filter with default parameters. */
|
||||
[[deprecated ("The classes in the StateVariableFilter namespace are deprecated. you should "
|
||||
"use the equivalent functionality in the StateVariableTPTFilter class.")]]
|
||||
Filter() : parameters (new Parameters<NumericType>) { reset(); }
|
||||
|
||||
/** Creates a filter using some parameters. */
|
||||
[[deprecated ("The classes in the StateVariableFilter namespace are deprecated. you should "
|
||||
"use the equivalent functionality in the StateVariableTPTFilter class.")]]
|
||||
Filter (ParametersPtr parametersToUse) : parameters (std::move (parametersToUse)) { reset(); }
|
||||
#endif
|
||||
|
||||
/** 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 caller 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);
|
||||
|
||||
#if JUCE_DSP_ENABLE_SNAP_TO_ZERO
|
||||
snapToZero();
|
||||
#endif
|
||||
|
||||
*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)
|
||||
};
|
||||
|
||||
enum class StateVariableFilterType
|
||||
{
|
||||
lowPass,
|
||||
bandPass,
|
||||
highPass
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Structure used for the state variable filter parameters.
|
||||
|
||||
@tags{DSP}
|
||||
*/
|
||||
template <typename NumericType>
|
||||
struct Parameters : public ProcessorState
|
||||
{
|
||||
//==============================================================================
|
||||
using Type = StateVariableFilterType;
|
||||
|
||||
//==============================================================================
|
||||
/** 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
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
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 per 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 as its gain can be
|
||||
higher than 0 dB. For the classic 0 dB bandpass, we need to multiply the
|
||||
result by R2.
|
||||
|
||||
Note 2: Using this class prevents some loud audio artefacts commonly encountered when
|
||||
changing the cutoff frequency using other filter simulation structures and IIR
|
||||
filter classes. However, this class may still require additional smoothing for
|
||||
cutoff frequency changes.
|
||||
|
||||
see IIRFilter, SmoothedValue
|
||||
|
||||
@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;
|
||||
|
||||
/** A typedef for a ref-counted pointer to the coefficients object */
|
||||
using ParametersPtr = typename Parameters<NumericType>::Ptr;
|
||||
|
||||
//==============================================================================
|
||||
#ifndef DOXYGEN
|
||||
/** Creates a filter with default parameters. */
|
||||
[[deprecated ("The classes in the StateVariableFilter namespace are deprecated. you should "
|
||||
"use the equivalent functionality in the StateVariableTPTFilter class.")]]
|
||||
Filter() : parameters (new Parameters<NumericType>) { reset(); }
|
||||
|
||||
/** Creates a filter using some parameters. */
|
||||
[[deprecated ("The classes in the StateVariableFilter namespace are deprecated. you should "
|
||||
"use the equivalent functionality in the StateVariableTPTFilter class.")]]
|
||||
Filter (ParametersPtr parametersToUse) : parameters (std::move (parametersToUse)) { reset(); }
|
||||
#endif
|
||||
|
||||
/** 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 caller 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);
|
||||
|
||||
#if JUCE_DSP_ENABLE_SNAP_TO_ZERO
|
||||
snapToZero();
|
||||
#endif
|
||||
|
||||
*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)
|
||||
};
|
||||
|
||||
enum class StateVariableFilterType
|
||||
{
|
||||
lowPass,
|
||||
bandPass,
|
||||
highPass
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Structure used for the state variable filter parameters.
|
||||
|
||||
@tags{DSP}
|
||||
*/
|
||||
template <typename NumericType>
|
||||
struct Parameters : public ProcessorState
|
||||
{
|
||||
//==============================================================================
|
||||
using Type = StateVariableFilterType;
|
||||
|
||||
//==============================================================================
|
||||
/** 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
|
||||
|
@ -1,137 +1,137 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
namespace dsp
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
template <typename SampleType>
|
||||
StateVariableTPTFilter<SampleType>::StateVariableTPTFilter()
|
||||
{
|
||||
update();
|
||||
}
|
||||
|
||||
template <typename SampleType>
|
||||
void StateVariableTPTFilter<SampleType>::setType (Type newValue)
|
||||
{
|
||||
filterType = newValue;
|
||||
}
|
||||
|
||||
template <typename SampleType>
|
||||
void StateVariableTPTFilter<SampleType>::setCutoffFrequency (SampleType newCutoffFrequencyHz)
|
||||
{
|
||||
jassert (isPositiveAndBelow (newCutoffFrequencyHz, static_cast<SampleType> (sampleRate * 0.5)));
|
||||
|
||||
cutoffFrequency = newCutoffFrequencyHz;
|
||||
update();
|
||||
}
|
||||
|
||||
template <typename SampleType>
|
||||
void StateVariableTPTFilter<SampleType>::setResonance (SampleType newResonance)
|
||||
{
|
||||
jassert (newResonance > static_cast<SampleType> (0));
|
||||
|
||||
resonance = newResonance;
|
||||
update();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
template <typename SampleType>
|
||||
void StateVariableTPTFilter<SampleType>::prepare (const ProcessSpec& spec)
|
||||
{
|
||||
jassert (spec.sampleRate > 0);
|
||||
jassert (spec.numChannels > 0);
|
||||
|
||||
sampleRate = spec.sampleRate;
|
||||
|
||||
s1.resize (spec.numChannels);
|
||||
s2.resize (spec.numChannels);
|
||||
|
||||
reset();
|
||||
update();
|
||||
}
|
||||
|
||||
template <typename SampleType>
|
||||
void StateVariableTPTFilter<SampleType>::reset()
|
||||
{
|
||||
reset (static_cast<SampleType> (0));
|
||||
}
|
||||
|
||||
template <typename SampleType>
|
||||
void StateVariableTPTFilter<SampleType>::reset (SampleType newValue)
|
||||
{
|
||||
for (auto v : { &s1, &s2 })
|
||||
std::fill (v->begin(), v->end(), newValue);
|
||||
}
|
||||
|
||||
template <typename SampleType>
|
||||
void StateVariableTPTFilter<SampleType>::snapToZero() noexcept
|
||||
{
|
||||
for (auto v : { &s1, &s2 })
|
||||
for (auto& element : *v)
|
||||
util::snapToZero (element);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
template <typename SampleType>
|
||||
SampleType StateVariableTPTFilter<SampleType>::processSample (int channel, SampleType inputValue)
|
||||
{
|
||||
auto& ls1 = s1[(size_t) channel];
|
||||
auto& ls2 = s2[(size_t) channel];
|
||||
|
||||
auto yHP = h * (inputValue - ls1 * (g + R2) - ls2);
|
||||
|
||||
auto yBP = yHP * g + ls1;
|
||||
ls1 = yHP * g + yBP;
|
||||
|
||||
auto yLP = yBP * g + ls2;
|
||||
ls2 = yBP * g + yLP;
|
||||
|
||||
switch (filterType)
|
||||
{
|
||||
case Type::lowpass: return yLP;
|
||||
case Type::bandpass: return yBP;
|
||||
case Type::highpass: return yHP;
|
||||
default: return yLP;
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
template <typename SampleType>
|
||||
void StateVariableTPTFilter<SampleType>::update()
|
||||
{
|
||||
g = static_cast<SampleType> (std::tan (juce::MathConstants<double>::pi * cutoffFrequency / sampleRate));
|
||||
R2 = static_cast<SampleType> (1.0 / resonance);
|
||||
h = static_cast<SampleType> (1.0 / (1.0 + R2 * g + g * g));
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
template class StateVariableTPTFilter<float>;
|
||||
template class StateVariableTPTFilter<double>;
|
||||
|
||||
} // namespace dsp
|
||||
} // namespace juce
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
namespace dsp
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
template <typename SampleType>
|
||||
StateVariableTPTFilter<SampleType>::StateVariableTPTFilter()
|
||||
{
|
||||
update();
|
||||
}
|
||||
|
||||
template <typename SampleType>
|
||||
void StateVariableTPTFilter<SampleType>::setType (Type newValue)
|
||||
{
|
||||
filterType = newValue;
|
||||
}
|
||||
|
||||
template <typename SampleType>
|
||||
void StateVariableTPTFilter<SampleType>::setCutoffFrequency (SampleType newCutoffFrequencyHz)
|
||||
{
|
||||
jassert (isPositiveAndBelow (newCutoffFrequencyHz, static_cast<SampleType> (sampleRate * 0.5)));
|
||||
|
||||
cutoffFrequency = newCutoffFrequencyHz;
|
||||
update();
|
||||
}
|
||||
|
||||
template <typename SampleType>
|
||||
void StateVariableTPTFilter<SampleType>::setResonance (SampleType newResonance)
|
||||
{
|
||||
jassert (newResonance > static_cast<SampleType> (0));
|
||||
|
||||
resonance = newResonance;
|
||||
update();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
template <typename SampleType>
|
||||
void StateVariableTPTFilter<SampleType>::prepare (const ProcessSpec& spec)
|
||||
{
|
||||
jassert (spec.sampleRate > 0);
|
||||
jassert (spec.numChannels > 0);
|
||||
|
||||
sampleRate = spec.sampleRate;
|
||||
|
||||
s1.resize (spec.numChannels);
|
||||
s2.resize (spec.numChannels);
|
||||
|
||||
reset();
|
||||
update();
|
||||
}
|
||||
|
||||
template <typename SampleType>
|
||||
void StateVariableTPTFilter<SampleType>::reset()
|
||||
{
|
||||
reset (static_cast<SampleType> (0));
|
||||
}
|
||||
|
||||
template <typename SampleType>
|
||||
void StateVariableTPTFilter<SampleType>::reset (SampleType newValue)
|
||||
{
|
||||
for (auto v : { &s1, &s2 })
|
||||
std::fill (v->begin(), v->end(), newValue);
|
||||
}
|
||||
|
||||
template <typename SampleType>
|
||||
void StateVariableTPTFilter<SampleType>::snapToZero() noexcept
|
||||
{
|
||||
for (auto v : { &s1, &s2 })
|
||||
for (auto& element : *v)
|
||||
util::snapToZero (element);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
template <typename SampleType>
|
||||
SampleType StateVariableTPTFilter<SampleType>::processSample (int channel, SampleType inputValue)
|
||||
{
|
||||
auto& ls1 = s1[(size_t) channel];
|
||||
auto& ls2 = s2[(size_t) channel];
|
||||
|
||||
auto yHP = h * (inputValue - ls1 * (g + R2) - ls2);
|
||||
|
||||
auto yBP = yHP * g + ls1;
|
||||
ls1 = yHP * g + yBP;
|
||||
|
||||
auto yLP = yBP * g + ls2;
|
||||
ls2 = yBP * g + yLP;
|
||||
|
||||
switch (filterType)
|
||||
{
|
||||
case Type::lowpass: return yLP;
|
||||
case Type::bandpass: return yBP;
|
||||
case Type::highpass: return yHP;
|
||||
default: return yLP;
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
template <typename SampleType>
|
||||
void StateVariableTPTFilter<SampleType>::update()
|
||||
{
|
||||
g = static_cast<SampleType> (std::tan (juce::MathConstants<double>::pi * cutoffFrequency / sampleRate));
|
||||
R2 = static_cast<SampleType> (1.0 / resonance);
|
||||
h = static_cast<SampleType> (1.0 / (1.0 + R2 * g + g * g));
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
template class StateVariableTPTFilter<float>;
|
||||
template class StateVariableTPTFilter<double>;
|
||||
|
||||
} // namespace dsp
|
||||
} // namespace juce
|
||||
|
@ -1,166 +1,166 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2020 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
|
||||
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
||||
|
||||
End User License Agreement: www.juce.com/juce-6-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
namespace dsp
|
||||
{
|
||||
|
||||
enum class StateVariableTPTFilterType
|
||||
{
|
||||
lowpass,
|
||||
bandpass,
|
||||
highpass
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** An IIR filter that can perform low, band and high-pass filtering on an audio
|
||||
signal, with 12 dB of attenuation per 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 as its gain can be
|
||||
higher than 0 dB. For the classic 0 dB bandpass, we need to multiply the
|
||||
result by R2.
|
||||
|
||||
Note 2: Using this class prevents some loud audio artefacts commonly encountered when
|
||||
changing the cutoff frequency using other filter simulation structures and IIR
|
||||
filter classes. However, this class may still require additional smoothing for
|
||||
cutoff frequency changes.
|
||||
|
||||
see IIRFilter, SmoothedValue
|
||||
|
||||
@tags{DSP}
|
||||
*/
|
||||
template <typename SampleType>
|
||||
class StateVariableTPTFilter
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
using Type = StateVariableTPTFilterType;
|
||||
|
||||
//==============================================================================
|
||||
/** Constructor. */
|
||||
StateVariableTPTFilter();
|
||||
|
||||
//==============================================================================
|
||||
/** Sets the filter type. */
|
||||
void setType (Type newType);
|
||||
|
||||
/** Sets the cutoff frequency of the filter.
|
||||
|
||||
@param newFrequencyHz the new cutoff frequency in Hz.
|
||||
*/
|
||||
void setCutoffFrequency (SampleType newFrequencyHz);
|
||||
|
||||
/** Sets the resonance of the 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 setResonance (SampleType newResonance);
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the type of the filter. */
|
||||
Type getType() const noexcept { return filterType; }
|
||||
|
||||
/** Returns the cutoff frequency of the filter. */
|
||||
SampleType getCutoffFrequency() const noexcept { return cutoffFrequency; }
|
||||
|
||||
/** Returns the resonance of the filter. */
|
||||
SampleType getResonance() const noexcept { return resonance; }
|
||||
|
||||
//==============================================================================
|
||||
/** Initialises the filter. */
|
||||
void prepare (const ProcessSpec& spec);
|
||||
|
||||
/** Resets the internal state variables of the filter. */
|
||||
void reset();
|
||||
|
||||
/** Resets the internal state variables of the filter to a given value. */
|
||||
void reset (SampleType newValue);
|
||||
|
||||
/** 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;
|
||||
|
||||
//==============================================================================
|
||||
/** Processes the input and output samples supplied in the processing context. */
|
||||
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() <= s1.size());
|
||||
jassert (inputBlock.getNumChannels() == numChannels);
|
||||
jassert (inputBlock.getNumSamples() == numSamples);
|
||||
|
||||
if (context.isBypassed)
|
||||
{
|
||||
outputBlock.copyFrom (inputBlock);
|
||||
return;
|
||||
}
|
||||
|
||||
for (size_t channel = 0; channel < numChannels; ++channel)
|
||||
{
|
||||
auto* inputSamples = inputBlock .getChannelPointer (channel);
|
||||
auto* outputSamples = outputBlock.getChannelPointer (channel);
|
||||
|
||||
for (size_t i = 0; i < numSamples; ++i)
|
||||
outputSamples[i] = processSample ((int) channel, inputSamples[i]);
|
||||
}
|
||||
|
||||
#if JUCE_DSP_ENABLE_SNAP_TO_ZERO
|
||||
snapToZero();
|
||||
#endif
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
/** Processes one sample at a time on a given channel. */
|
||||
SampleType processSample (int channel, SampleType inputValue);
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
void update();
|
||||
|
||||
//==============================================================================
|
||||
SampleType g, h, R2;
|
||||
std::vector<SampleType> s1 { 2 }, s2 { 2 };
|
||||
|
||||
double sampleRate = 44100.0;
|
||||
Type filterType = Type::lowpass;
|
||||
SampleType cutoffFrequency = static_cast<SampleType> (1000.0),
|
||||
resonance = static_cast<SampleType> (1.0 / std::sqrt (2.0));
|
||||
};
|
||||
|
||||
} // namespace dsp
|
||||
} // namespace juce
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2022 - Raw Material Software Limited
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||||
Agreement and JUCE Privacy Policy.
|
||||
|
||||
End User License Agreement: www.juce.com/juce-7-licence
|
||||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||||
|
||||
Or: You may also use this code under the terms of the GPL v3 (see
|
||||
www.gnu.org/licenses).
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
namespace dsp
|
||||
{
|
||||
|
||||
enum class StateVariableTPTFilterType
|
||||
{
|
||||
lowpass,
|
||||
bandpass,
|
||||
highpass
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/** An IIR filter that can perform low, band and high-pass filtering on an audio
|
||||
signal, with 12 dB of attenuation per 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 as its gain can be
|
||||
higher than 0 dB. For the classic 0 dB bandpass, we need to multiply the
|
||||
result by R2.
|
||||
|
||||
Note 2: Using this class prevents some loud audio artefacts commonly encountered when
|
||||
changing the cutoff frequency using other filter simulation structures and IIR
|
||||
filter classes. However, this class may still require additional smoothing for
|
||||
cutoff frequency changes.
|
||||
|
||||
see IIRFilter, SmoothedValue
|
||||
|
||||
@tags{DSP}
|
||||
*/
|
||||
template <typename SampleType>
|
||||
class StateVariableTPTFilter
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
using Type = StateVariableTPTFilterType;
|
||||
|
||||
//==============================================================================
|
||||
/** Constructor. */
|
||||
StateVariableTPTFilter();
|
||||
|
||||
//==============================================================================
|
||||
/** Sets the filter type. */
|
||||
void setType (Type newType);
|
||||
|
||||
/** Sets the cutoff frequency of the filter.
|
||||
|
||||
@param newFrequencyHz the new cutoff frequency in Hz.
|
||||
*/
|
||||
void setCutoffFrequency (SampleType newFrequencyHz);
|
||||
|
||||
/** Sets the resonance of the 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 setResonance (SampleType newResonance);
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the type of the filter. */
|
||||
Type getType() const noexcept { return filterType; }
|
||||
|
||||
/** Returns the cutoff frequency of the filter. */
|
||||
SampleType getCutoffFrequency() const noexcept { return cutoffFrequency; }
|
||||
|
||||
/** Returns the resonance of the filter. */
|
||||
SampleType getResonance() const noexcept { return resonance; }
|
||||
|
||||
//==============================================================================
|
||||
/** Initialises the filter. */
|
||||
void prepare (const ProcessSpec& spec);
|
||||
|
||||
/** Resets the internal state variables of the filter. */
|
||||
void reset();
|
||||
|
||||
/** Resets the internal state variables of the filter to a given value. */
|
||||
void reset (SampleType newValue);
|
||||
|
||||
/** 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;
|
||||
|
||||
//==============================================================================
|
||||
/** Processes the input and output samples supplied in the processing context. */
|
||||
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() <= s1.size());
|
||||
jassert (inputBlock.getNumChannels() == numChannels);
|
||||
jassert (inputBlock.getNumSamples() == numSamples);
|
||||
|
||||
if (context.isBypassed)
|
||||
{
|
||||
outputBlock.copyFrom (inputBlock);
|
||||
return;
|
||||
}
|
||||
|
||||
for (size_t channel = 0; channel < numChannels; ++channel)
|
||||
{
|
||||
auto* inputSamples = inputBlock .getChannelPointer (channel);
|
||||
auto* outputSamples = outputBlock.getChannelPointer (channel);
|
||||
|
||||
for (size_t i = 0; i < numSamples; ++i)
|
||||
outputSamples[i] = processSample ((int) channel, inputSamples[i]);
|
||||
}
|
||||
|
||||
#if JUCE_DSP_ENABLE_SNAP_TO_ZERO
|
||||
snapToZero();
|
||||
#endif
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
/** Processes one sample at a time on a given channel. */
|
||||
SampleType processSample (int channel, SampleType inputValue);
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
void update();
|
||||
|
||||
//==============================================================================
|
||||
SampleType g, h, R2;
|
||||
std::vector<SampleType> s1 { 2 }, s2 { 2 };
|
||||
|
||||
double sampleRate = 44100.0;
|
||||
Type filterType = Type::lowpass;
|
||||
SampleType cutoffFrequency = static_cast<SampleType> (1000.0),
|
||||
resonance = static_cast<SampleType> (1.0 / std::sqrt (2.0));
|
||||
};
|
||||
|
||||
} // namespace dsp
|
||||
} // namespace juce
|
||||
|
Reference in New Issue
Block a user