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

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

View File

@ -0,0 +1,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

View File

@ -0,0 +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

View File

@ -0,0 +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

View File

@ -0,0 +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

View File

@ -0,0 +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

View File

@ -0,0 +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

View File

@ -0,0 +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

View File

@ -0,0 +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

View File

@ -0,0 +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

View File

@ -0,0 +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

View File

@ -0,0 +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

View File

@ -0,0 +1,577 @@
/*
==============================================================================
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
{
template <typename NumericType>
std::array<NumericType, 4> ArrayCoefficients<NumericType>::makeFirstOrderLowPass (double sampleRate,
NumericType frequency)
{
jassert (sampleRate > 0.0);
jassert (frequency > 0 && frequency <= static_cast<float> (sampleRate * 0.5));
auto n = std::tan (MathConstants<NumericType>::pi * frequency / static_cast<NumericType> (sampleRate));
return { { n, n, n + 1, n - 1 } };
}
template <typename NumericType>
std::array<NumericType, 4> ArrayCoefficients<NumericType>::makeFirstOrderHighPass (double sampleRate,
NumericType frequency)
{
jassert (sampleRate > 0.0);
jassert (frequency > 0 && frequency <= static_cast<float> (sampleRate * 0.5));
auto n = std::tan (MathConstants<NumericType>::pi * frequency / static_cast<NumericType> (sampleRate));
return { { 1, -1, n + 1, n - 1 } };
}
template <typename NumericType>
std::array<NumericType, 4> ArrayCoefficients<NumericType>::makeFirstOrderAllPass (double sampleRate,
NumericType frequency)
{
jassert (sampleRate > 0.0);
jassert (frequency > 0 && frequency <= static_cast<float> (sampleRate * 0.5));
auto n = std::tan (MathConstants<NumericType>::pi * frequency / static_cast<NumericType> (sampleRate));
return { { n - 1, n + 1, n + 1, n - 1 } };
}
template <typename NumericType>
std::array<NumericType, 6> ArrayCoefficients<NumericType>::makeLowPass (double sampleRate,
NumericType frequency)
{
return makeLowPass (sampleRate, frequency, inverseRootTwo);
}
template <typename NumericType>
std::array<NumericType, 6> ArrayCoefficients<NumericType>::makeLowPass (double sampleRate,
NumericType frequency,
NumericType Q)
{
jassert (sampleRate > 0.0);
jassert (frequency > 0 && frequency <= static_cast<float> (sampleRate * 0.5));
jassert (Q > 0.0);
auto n = 1 / std::tan (MathConstants<NumericType>::pi * frequency / static_cast<NumericType> (sampleRate));
auto nSquared = n * n;
auto invQ = 1 / Q;
auto c1 = 1 / (1 + invQ * n + nSquared);
return { { c1, c1 * 2, c1,
1, c1 * 2 * (1 - nSquared),
c1 * (1 - invQ * n + nSquared) } };
}
template <typename NumericType>
std::array<NumericType, 6> ArrayCoefficients<NumericType>::makeHighPass (double sampleRate,
NumericType frequency)
{
return makeHighPass (sampleRate, frequency, inverseRootTwo);
}
template <typename NumericType>
std::array<NumericType, 6> ArrayCoefficients<NumericType>::makeHighPass (double sampleRate,
NumericType frequency,
NumericType Q)
{
jassert (sampleRate > 0.0);
jassert (frequency > 0 && frequency <= static_cast<float> (sampleRate * 0.5));
jassert (Q > 0.0);
auto n = std::tan (MathConstants<NumericType>::pi * frequency / static_cast<NumericType> (sampleRate));
auto nSquared = n * n;
auto invQ = 1 / Q;
auto c1 = 1 / (1 + invQ * n + nSquared);
return { { c1, c1 * -2, c1,
1, c1 * 2 * (nSquared - 1),
c1 * (1 - invQ * n + nSquared) } };
}
template <typename NumericType>
std::array<NumericType, 6> ArrayCoefficients<NumericType>::makeBandPass (double sampleRate,
NumericType frequency)
{
return makeBandPass (sampleRate, frequency, inverseRootTwo);
}
template <typename NumericType>
std::array<NumericType, 6> ArrayCoefficients<NumericType>::makeBandPass (double sampleRate,
NumericType frequency,
NumericType Q)
{
jassert (sampleRate > 0.0);
jassert (frequency > 0 && frequency <= static_cast<float> (sampleRate * 0.5));
jassert (Q > 0.0);
auto n = 1 / std::tan (MathConstants<NumericType>::pi * frequency / static_cast<NumericType> (sampleRate));
auto nSquared = n * n;
auto invQ = 1 / Q;
auto c1 = 1 / (1 + invQ * n + nSquared);
return { { c1 * n * invQ, 0,
-c1 * n * invQ, 1,
c1 * 2 * (1 - nSquared),
c1 * (1 - invQ * n + nSquared) } };
}
template <typename NumericType>
std::array<NumericType, 6> ArrayCoefficients<NumericType>::makeNotch (double sampleRate,
NumericType frequency)
{
return makeNotch (sampleRate, frequency, inverseRootTwo);
}
template <typename NumericType>
std::array<NumericType, 6> ArrayCoefficients<NumericType>::makeNotch (double sampleRate,
NumericType frequency,
NumericType Q)
{
jassert (sampleRate > 0.0);
jassert (frequency > 0 && frequency <= static_cast<float> (sampleRate * 0.5));
jassert (Q > 0.0);
auto n = 1 / std::tan (MathConstants<NumericType>::pi * frequency / static_cast<NumericType> (sampleRate));
auto nSquared = n * n;
auto invQ = 1 / Q;
auto c1 = 1 / (1 + n * invQ + nSquared);
auto b0 = c1 * (1 + nSquared);
auto b1 = 2 * c1 * (1 - nSquared);
return { { b0, b1, b0, 1, b1, c1 * (1 - n * invQ + nSquared) } };
}
template <typename NumericType>
std::array<NumericType, 6> ArrayCoefficients<NumericType>::makeAllPass (double sampleRate,
NumericType frequency)
{
return makeAllPass (sampleRate, frequency, inverseRootTwo);
}
template <typename NumericType>
std::array<NumericType, 6> ArrayCoefficients<NumericType>::makeAllPass (double sampleRate,
NumericType frequency,
NumericType Q)
{
jassert (sampleRate > 0);
jassert (frequency > 0 && frequency <= sampleRate * 0.5);
jassert (Q > 0);
auto n = 1 / std::tan (MathConstants<NumericType>::pi * frequency / static_cast<NumericType> (sampleRate));
auto nSquared = n * n;
auto invQ = 1 / Q;
auto c1 = 1 / (1 + invQ * n + nSquared);
auto b0 = c1 * (1 - n * invQ + nSquared);
auto b1 = c1 * 2 * (1 - nSquared);
return { { b0, b1, 1, 1, b1, b0 } };
}
template <typename NumericType>
std::array<NumericType, 6> ArrayCoefficients<NumericType>::makeLowShelf (double sampleRate,
NumericType cutOffFrequency,
NumericType Q,
NumericType gainFactor)
{
jassert (sampleRate > 0.0);
jassert (cutOffFrequency > 0.0 && cutOffFrequency <= sampleRate * 0.5);
jassert (Q > 0.0);
auto A = jmax (static_cast<NumericType> (0.0), std::sqrt (gainFactor));
auto aminus1 = A - 1;
auto aplus1 = A + 1;
auto omega = (2 * MathConstants<NumericType>::pi * jmax (cutOffFrequency, static_cast<NumericType> (2.0))) / static_cast<NumericType> (sampleRate);
auto coso = std::cos (omega);
auto beta = std::sin (omega) * std::sqrt (A) / Q;
auto aminus1TimesCoso = aminus1 * coso;
return { { A * (aplus1 - aminus1TimesCoso + beta),
A * 2 * (aminus1 - aplus1 * coso),
A * (aplus1 - aminus1TimesCoso - beta),
aplus1 + aminus1TimesCoso + beta,
-2 * (aminus1 + aplus1 * coso),
aplus1 + aminus1TimesCoso - beta } };
}
template <typename NumericType>
std::array<NumericType, 6> ArrayCoefficients<NumericType>::makeHighShelf (double sampleRate,
NumericType cutOffFrequency,
NumericType Q,
NumericType gainFactor)
{
jassert (sampleRate > 0);
jassert (cutOffFrequency > 0 && cutOffFrequency <= static_cast<NumericType> (sampleRate * 0.5));
jassert (Q > 0);
auto A = jmax (static_cast<NumericType> (0.0), std::sqrt (gainFactor));
auto aminus1 = A - 1;
auto aplus1 = A + 1;
auto omega = (2 * MathConstants<NumericType>::pi * jmax (cutOffFrequency, static_cast<NumericType> (2.0))) / static_cast<NumericType> (sampleRate);
auto coso = std::cos (omega);
auto beta = std::sin (omega) * std::sqrt (A) / Q;
auto aminus1TimesCoso = aminus1 * coso;
return { { A * (aplus1 + aminus1TimesCoso + beta),
A * -2 * (aminus1 + aplus1 * coso),
A * (aplus1 + aminus1TimesCoso - beta),
aplus1 - aminus1TimesCoso + beta,
2 * (aminus1 - aplus1 * coso),
aplus1 - aminus1TimesCoso - beta } };
}
template <typename NumericType>
std::array<NumericType, 6> ArrayCoefficients<NumericType>::makePeakFilter (double sampleRate,
NumericType frequency,
NumericType Q,
NumericType gainFactor)
{
jassert (sampleRate > 0);
jassert (frequency > 0 && frequency <= static_cast<NumericType> (sampleRate * 0.5));
jassert (Q > 0);
jassert (gainFactor > 0);
auto A = jmax (static_cast<NumericType> (0.0), std::sqrt (gainFactor));
auto omega = (2 * MathConstants<NumericType>::pi * jmax (frequency, static_cast<NumericType> (2.0))) / static_cast<NumericType> (sampleRate);
auto alpha = std::sin (omega) / (Q * 2);
auto c2 = -2 * std::cos (omega);
auto alphaTimesA = alpha * A;
auto alphaOverA = alpha / A;
return { { 1 + alphaTimesA, c2, 1 - alphaTimesA, 1 + alphaOverA, c2, 1 - alphaOverA } };
}
template struct ArrayCoefficients<float>;
template struct ArrayCoefficients<double>;
//==============================================================================
template <typename NumericType>
Coefficients<NumericType>::Coefficients()
{
assign ({ NumericType(), NumericType(), NumericType(),
NumericType(), NumericType(), NumericType() });
}
template <typename NumericType>
Coefficients<NumericType>::Coefficients (NumericType b0, NumericType b1,
NumericType a0, NumericType a1)
{
assign ({ b0, b1,
a0, a1 });
}
template <typename NumericType>
Coefficients<NumericType>::Coefficients (NumericType b0, NumericType b1, NumericType b2,
NumericType a0, NumericType a1, NumericType a2)
{
assign ({ b0, b1, b2,
a0, a1, a2 });
}
template <typename NumericType>
Coefficients<NumericType>::Coefficients (NumericType b0, NumericType b1, NumericType b2, NumericType b3,
NumericType a0, NumericType a1, NumericType a2, NumericType a3)
{
assign ({ b0, b1, b2, b3,
a0, a1, a2, a3 });
}
template <typename NumericType>
typename Coefficients<NumericType>::Ptr Coefficients<NumericType>::makeFirstOrderLowPass (double sampleRate,
NumericType frequency)
{
return *new Coefficients (ArrayCoeffs::makeFirstOrderLowPass (sampleRate, frequency));
}
template <typename NumericType>
typename Coefficients<NumericType>::Ptr Coefficients<NumericType>::makeFirstOrderHighPass (double sampleRate,
NumericType frequency)
{
return *new Coefficients (ArrayCoeffs::makeFirstOrderHighPass (sampleRate, frequency));
}
template <typename NumericType>
typename Coefficients<NumericType>::Ptr Coefficients<NumericType>::makeFirstOrderAllPass (double sampleRate,
NumericType frequency)
{
return *new Coefficients (ArrayCoeffs::makeFirstOrderAllPass (sampleRate, frequency));
}
template <typename NumericType>
typename Coefficients<NumericType>::Ptr Coefficients<NumericType>::makeLowPass (double sampleRate,
NumericType frequency)
{
return *new Coefficients (ArrayCoeffs::makeLowPass (sampleRate, frequency));
}
template <typename NumericType>
typename Coefficients<NumericType>::Ptr Coefficients<NumericType>::makeLowPass (double sampleRate,
NumericType frequency,
NumericType Q)
{
return *new Coefficients (ArrayCoeffs::makeLowPass (sampleRate, frequency, Q));
}
template <typename NumericType>
typename Coefficients<NumericType>::Ptr Coefficients<NumericType>::makeHighPass (double sampleRate,
NumericType frequency)
{
return *new Coefficients (ArrayCoeffs::makeHighPass (sampleRate, frequency));
}
template <typename NumericType>
typename Coefficients<NumericType>::Ptr Coefficients<NumericType>::makeHighPass (double sampleRate,
NumericType frequency,
NumericType Q)
{
return *new Coefficients (ArrayCoeffs::makeHighPass (sampleRate, frequency, Q));
}
template <typename NumericType>
typename Coefficients<NumericType>::Ptr Coefficients<NumericType>::makeBandPass (double sampleRate,
NumericType frequency)
{
return *new Coefficients (ArrayCoeffs::makeBandPass (sampleRate, frequency));
}
template <typename NumericType>
typename Coefficients<NumericType>::Ptr Coefficients<NumericType>::makeBandPass (double sampleRate,
NumericType frequency,
NumericType Q)
{
return *new Coefficients (ArrayCoeffs::makeBandPass (sampleRate, frequency, Q));
}
template <typename NumericType>
typename Coefficients<NumericType>::Ptr Coefficients<NumericType>::makeNotch (double sampleRate,
NumericType frequency)
{
return *new Coefficients (ArrayCoeffs::makeNotch (sampleRate, frequency));
}
template <typename NumericType>
typename Coefficients<NumericType>::Ptr Coefficients<NumericType>::makeNotch (double sampleRate,
NumericType frequency,
NumericType Q)
{
return *new Coefficients (ArrayCoeffs::makeNotch (sampleRate, frequency, Q));
}
template <typename NumericType>
typename Coefficients<NumericType>::Ptr Coefficients<NumericType>::makeAllPass (double sampleRate,
NumericType frequency)
{
return *new Coefficients (ArrayCoeffs::makeAllPass (sampleRate, frequency));
}
template <typename NumericType>
typename Coefficients<NumericType>::Ptr Coefficients<NumericType>::makeAllPass (double sampleRate,
NumericType frequency,
NumericType Q)
{
return *new Coefficients (ArrayCoeffs::makeAllPass (sampleRate, frequency, Q));
}
template <typename NumericType>
typename Coefficients<NumericType>::Ptr Coefficients<NumericType>::makeLowShelf (double sampleRate,
NumericType cutOffFrequency,
NumericType Q,
NumericType gainFactor)
{
return *new Coefficients (ArrayCoeffs::makeLowShelf (sampleRate, cutOffFrequency, Q, gainFactor));
}
template <typename NumericType>
typename Coefficients<NumericType>::Ptr Coefficients<NumericType>::makeHighShelf (double sampleRate,
NumericType cutOffFrequency,
NumericType Q,
NumericType gainFactor)
{
return *new Coefficients (ArrayCoeffs::makeHighShelf (sampleRate, cutOffFrequency, Q, gainFactor));
}
template <typename NumericType>
typename Coefficients<NumericType>::Ptr Coefficients<NumericType>::makePeakFilter (double sampleRate,
NumericType frequency,
NumericType Q,
NumericType gainFactor)
{
return *new Coefficients (ArrayCoeffs::makePeakFilter (sampleRate, frequency, Q, gainFactor));
}
template <typename NumericType>
size_t Coefficients<NumericType>::getFilterOrder() const noexcept
{
return (static_cast<size_t> (coefficients.size()) - 1) / 2;
}
template <typename NumericType>
double Coefficients<NumericType>::getMagnitudeForFrequency (double frequency, double sampleRate) const noexcept
{
constexpr Complex<double> j (0, 1);
const auto order = getFilterOrder();
const auto* coefs = coefficients.begin();
jassert (frequency >= 0 && frequency <= sampleRate * 0.5);
Complex<double> numerator = 0.0, denominator = 0.0, factor = 1.0;
Complex<double> jw = std::exp (-MathConstants<double>::twoPi * frequency * j / sampleRate);
for (size_t n = 0; n <= order; ++n)
{
numerator += static_cast<double> (coefs[n]) * factor;
factor *= jw;
}
denominator = 1.0;
factor = jw;
for (size_t n = order + 1; n <= 2 * order; ++n)
{
denominator += static_cast<double> (coefs[n]) * factor;
factor *= jw;
}
return std::abs (numerator / denominator);
}
template <typename NumericType>
void Coefficients<NumericType>::getMagnitudeForFrequencyArray (const double* frequencies, double* magnitudes,
size_t numSamples, double sampleRate) const noexcept
{
constexpr Complex<double> j (0, 1);
const auto order = getFilterOrder();
const auto* coefs = coefficients.begin();
jassert (order >= 0);
for (size_t i = 0; i < numSamples; ++i)
{
jassert (frequencies[i] >= 0 && frequencies[i] <= sampleRate * 0.5);
Complex<double> numerator = 0.0, denominator = 0.0, factor = 1.0;
Complex<double> jw = std::exp (-MathConstants<double>::twoPi * frequencies[i] * j / sampleRate);
for (size_t n = 0; n <= order; ++n)
{
numerator += static_cast<double> (coefs[n]) * factor;
factor *= jw;
}
denominator = 1.0;
factor = jw;
for (size_t n = order + 1; n <= 2 * order; ++n)
{
denominator += static_cast<double> (coefs[n]) * factor;
factor *= jw;
}
magnitudes[i] = std::abs(numerator / denominator);
}
}
template <typename NumericType>
double Coefficients<NumericType>::getPhaseForFrequency (double frequency, double sampleRate) const noexcept
{
constexpr Complex<double> j (0, 1);
const auto order = getFilterOrder();
const auto* coefs = coefficients.begin();
jassert (frequency >= 0 && frequency <= sampleRate * 0.5);
Complex<double> numerator = 0.0, denominator = 0.0, factor = 1.0;
Complex<double> jw = std::exp (-MathConstants<double>::twoPi * frequency * j / sampleRate);
for (size_t n = 0; n <= order; ++n)
{
numerator += static_cast<double> (coefs[n]) * factor;
factor *= jw;
}
denominator = 1.0;
factor = jw;
for (size_t n = order + 1; n <= 2 * order; ++n)
{
denominator += static_cast<double> (coefs[n]) * factor;
factor *= jw;
}
return std::arg (numerator / denominator);
}
template <typename NumericType>
void Coefficients<NumericType>::getPhaseForFrequencyArray (double* frequencies, double* phases,
size_t numSamples, double sampleRate) const noexcept
{
jassert (sampleRate > 0);
constexpr Complex<double> j (0, 1);
const auto order = getFilterOrder();
const auto* coefs = coefficients.begin();
auto invSampleRate = 1 / sampleRate;
jassert (order >= 0);
for (size_t i = 0; i < numSamples; ++i)
{
jassert (frequencies[i] >= 0 && frequencies[i] <= sampleRate * 0.5);
Complex<double> numerator = 0.0, denominator = 0.0, factor = 1.0;
Complex<double> jw = std::exp (-MathConstants<double>::twoPi * frequencies[i] * j * invSampleRate);
for (size_t n = 0; n <= order; ++n)
{
numerator += static_cast<double> (coefs[n]) * factor;
factor *= jw;
}
denominator = 1.0;
factor = jw;
for (size_t n = order + 1; n <= 2 * order; ++n)
{
denominator += static_cast<double> (coefs[n]) * factor;
factor *= jw;
}
phases[i] = std::arg (numerator / denominator);
}
}
template struct Coefficients<float>;
template struct Coefficients<double>;
} // namespace IIR
} // namespace dsp
} // namespace juce

View File

@ -0,0 +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"

View File

@ -0,0 +1,245 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
By using JUCE, you agree to the terms of both the JUCE 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-privacy-policy
Or: You may also use this code under the terms of the GPL v3 (see
www.gnu.org/licenses).
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
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

View File

@ -0,0 +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

View File

@ -0,0 +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

View File

@ -0,0 +1,770 @@
/*
==============================================================================
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
{
/** Abstract class for the provided oversampling stages used internally in
the Oversampling class.
*/
template <typename SampleType>
struct Oversampling<SampleType>::OversamplingStage
{
OversamplingStage (size_t numChans, size_t newFactor) : numChannels (numChans), factor (newFactor) {}
virtual ~OversamplingStage() {}
//==============================================================================
virtual SampleType getLatencyInSamples() const = 0;
virtual void initProcessing (size_t maximumNumberOfSamplesBeforeOversampling)
{
buffer.setSize (static_cast<int> (numChannels),
static_cast<int> (maximumNumberOfSamplesBeforeOversampling * factor),
false, false, true);
}
virtual void reset()
{
buffer.clear();
}
AudioBlock<SampleType> getProcessedSamples (size_t numSamples)
{
return AudioBlock<SampleType> (buffer).getSubBlock (0, numSamples);
}
virtual void processSamplesUp (const AudioBlock<const SampleType>&) = 0;
virtual void processSamplesDown (AudioBlock<SampleType>&) = 0;
AudioBuffer<SampleType> buffer;
size_t numChannels, factor;
};
//==============================================================================
/** Dummy oversampling stage class which simply copies and pastes the input
signal, which could be equivalent to a "one time" oversampling processing.
*/
template <typename SampleType>
struct OversamplingDummy : public Oversampling<SampleType>::OversamplingStage
{
using ParentType = typename Oversampling<SampleType>::OversamplingStage;
OversamplingDummy (size_t numChans) : ParentType (numChans, 1) {}
//==============================================================================
SampleType getLatencyInSamples() const override
{
return 0;
}
void processSamplesUp (const AudioBlock<const SampleType>& inputBlock) override
{
jassert (inputBlock.getNumChannels() <= static_cast<size_t> (ParentType::buffer.getNumChannels()));
jassert (inputBlock.getNumSamples() * ParentType::factor <= static_cast<size_t> (ParentType::buffer.getNumSamples()));
for (size_t channel = 0; channel < inputBlock.getNumChannels(); ++channel)
ParentType::buffer.copyFrom (static_cast<int> (channel), 0,
inputBlock.getChannelPointer (channel), static_cast<int> (inputBlock.getNumSamples()));
}
void processSamplesDown (AudioBlock<SampleType>& outputBlock) override
{
jassert (outputBlock.getNumChannels() <= static_cast<size_t> (ParentType::buffer.getNumChannels()));
jassert (outputBlock.getNumSamples() * ParentType::factor <= static_cast<size_t> (ParentType::buffer.getNumSamples()));
outputBlock.copyFrom (ParentType::getProcessedSamples (outputBlock.getNumSamples()));
}
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OversamplingDummy)
};
//==============================================================================
/** Oversampling stage class performing 2 times oversampling using the Filter
Design FIR Equiripple method. The resulting filter is linear phase,
symmetric, and has every two samples but the middle one equal to zero,
leading to specific processing optimizations.
*/
template <typename SampleType>
struct Oversampling2TimesEquirippleFIR : public Oversampling<SampleType>::OversamplingStage
{
using ParentType = typename Oversampling<SampleType>::OversamplingStage;
Oversampling2TimesEquirippleFIR (size_t numChans,
SampleType normalisedTransitionWidthUp,
SampleType stopbandAmplitudedBUp,
SampleType normalisedTransitionWidthDown,
SampleType stopbandAmplitudedBDown)
: ParentType (numChans, 2)
{
coefficientsUp = *FilterDesign<SampleType>::designFIRLowpassHalfBandEquirippleMethod (normalisedTransitionWidthUp, stopbandAmplitudedBUp);
coefficientsDown = *FilterDesign<SampleType>::designFIRLowpassHalfBandEquirippleMethod (normalisedTransitionWidthDown, stopbandAmplitudedBDown);
auto N = coefficientsUp.getFilterOrder() + 1;
stateUp.setSize (static_cast<int> (this->numChannels), static_cast<int> (N));
N = coefficientsDown.getFilterOrder() + 1;
auto Ndiv2 = N / 2;
auto Ndiv4 = Ndiv2 / 2;
stateDown.setSize (static_cast<int> (this->numChannels), static_cast<int> (N));
stateDown2.setSize (static_cast<int> (this->numChannels), static_cast<int> (Ndiv4 + 1));
position.resize (static_cast<int> (this->numChannels));
}
//==============================================================================
SampleType getLatencyInSamples() const override
{
return static_cast<SampleType> (coefficientsUp.getFilterOrder() + coefficientsDown.getFilterOrder()) * 0.5f;
}
void reset() override
{
ParentType::reset();
stateUp.clear();
stateDown.clear();
stateDown2.clear();
position.fill (0);
}
void processSamplesUp (const AudioBlock<const SampleType>& inputBlock) override
{
jassert (inputBlock.getNumChannels() <= static_cast<size_t> (ParentType::buffer.getNumChannels()));
jassert (inputBlock.getNumSamples() * ParentType::factor <= static_cast<size_t> (ParentType::buffer.getNumSamples()));
// Initialization
auto fir = coefficientsUp.getRawCoefficients();
auto N = coefficientsUp.getFilterOrder() + 1;
auto Ndiv2 = N / 2;
auto numSamples = inputBlock.getNumSamples();
// Processing
for (size_t channel = 0; channel < inputBlock.getNumChannels(); ++channel)
{
auto bufferSamples = ParentType::buffer.getWritePointer (static_cast<int> (channel));
auto buf = stateUp.getWritePointer (static_cast<int> (channel));
auto samples = inputBlock.getChannelPointer (channel);
for (size_t i = 0; i < numSamples; ++i)
{
// Input
buf[N - 1] = 2 * samples[i];
// Convolution
auto out = static_cast<SampleType> (0.0);
for (size_t k = 0; k < Ndiv2; k += 2)
out += (buf[k] + buf[N - k - 1]) * fir[k];
// Outputs
bufferSamples[i << 1] = out;
bufferSamples[(i << 1) + 1] = buf[Ndiv2 + 1] * fir[Ndiv2];
// Shift data
for (size_t k = 0; k < N - 2; k += 2)
buf[k] = buf[k + 2];
}
}
}
void processSamplesDown (AudioBlock<SampleType>& outputBlock) override
{
jassert (outputBlock.getNumChannels() <= static_cast<size_t> (ParentType::buffer.getNumChannels()));
jassert (outputBlock.getNumSamples() * ParentType::factor <= static_cast<size_t> (ParentType::buffer.getNumSamples()));
// Initialization
auto fir = coefficientsDown.getRawCoefficients();
auto N = coefficientsDown.getFilterOrder() + 1;
auto Ndiv2 = N / 2;
auto Ndiv4 = Ndiv2 / 2;
auto numSamples = outputBlock.getNumSamples();
// Processing
for (size_t channel = 0; channel < outputBlock.getNumChannels(); ++channel)
{
auto bufferSamples = ParentType::buffer.getWritePointer (static_cast<int> (channel));
auto buf = stateDown.getWritePointer (static_cast<int> (channel));
auto buf2 = stateDown2.getWritePointer (static_cast<int> (channel));
auto samples = outputBlock.getChannelPointer (channel);
auto pos = position.getUnchecked (static_cast<int> (channel));
for (size_t i = 0; i < numSamples; ++i)
{
// Input
buf[N - 1] = bufferSamples[i << 1];
// Convolution
auto out = static_cast<SampleType> (0.0);
for (size_t k = 0; k < Ndiv2; k += 2)
out += (buf[k] + buf[N - k - 1]) * fir[k];
// Output
out += buf2[pos] * fir[Ndiv2];
buf2[pos] = bufferSamples[(i << 1) + 1];
samples[i] = out;
// Shift data
for (size_t k = 0; k < N - 2; ++k)
buf[k] = buf[k + 2];
// Circular buffer
pos = (pos == 0 ? Ndiv4 : pos - 1);
}
position.setUnchecked (static_cast<int> (channel), pos);
}
}
private:
//==============================================================================
FIR::Coefficients<SampleType> coefficientsUp, coefficientsDown;
AudioBuffer<SampleType> stateUp, stateDown, stateDown2;
Array<size_t> position;
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Oversampling2TimesEquirippleFIR)
};
//==============================================================================
/** Oversampling stage class performing 2 times oversampling using the Filter
Design IIR Polyphase Allpass Cascaded method. The resulting filter is minimum
phase, and provided with a method to get the exact resulting latency.
*/
template <typename SampleType>
struct Oversampling2TimesPolyphaseIIR : public Oversampling<SampleType>::OversamplingStage
{
using ParentType = typename Oversampling<SampleType>::OversamplingStage;
Oversampling2TimesPolyphaseIIR (size_t numChans,
SampleType normalisedTransitionWidthUp,
SampleType stopbandAmplitudedBUp,
SampleType normalisedTransitionWidthDown,
SampleType stopbandAmplitudedBDown)
: ParentType (numChans, 2)
{
auto structureUp = FilterDesign<SampleType>::designIIRLowpassHalfBandPolyphaseAllpassMethod (normalisedTransitionWidthUp, stopbandAmplitudedBUp);
auto coeffsUp = getCoefficients (structureUp);
latency = static_cast<SampleType> (-(coeffsUp.getPhaseForFrequency (0.0001, 1.0)) / (0.0001 * MathConstants<double>::twoPi));
auto structureDown = FilterDesign<SampleType>::designIIRLowpassHalfBandPolyphaseAllpassMethod (normalisedTransitionWidthDown, stopbandAmplitudedBDown);
auto coeffsDown = getCoefficients (structureDown);
latency += static_cast<SampleType> (-(coeffsDown.getPhaseForFrequency (0.0001, 1.0)) / (0.0001 * MathConstants<double>::twoPi));
for (auto i = 0; i < structureUp.directPath.size(); ++i)
coefficientsUp.add (structureUp.directPath.getObjectPointer (i)->coefficients[0]);
for (auto i = 1; i < structureUp.delayedPath.size(); ++i)
coefficientsUp.add (structureUp.delayedPath.getObjectPointer (i)->coefficients[0]);
for (auto i = 0; i < structureDown.directPath.size(); ++i)
coefficientsDown.add (structureDown.directPath.getObjectPointer (i)->coefficients[0]);
for (auto i = 1; i < structureDown.delayedPath.size(); ++i)
coefficientsDown.add (structureDown.delayedPath.getObjectPointer (i)->coefficients[0]);
v1Up.setSize (static_cast<int> (this->numChannels), coefficientsUp.size());
v1Down.setSize (static_cast<int> (this->numChannels), coefficientsDown.size());
delayDown.resize (static_cast<int> (this->numChannels));
}
//==============================================================================
SampleType getLatencyInSamples() const override
{
return latency;
}
void reset() override
{
ParentType::reset();
v1Up.clear();
v1Down.clear();
delayDown.fill (0);
}
void processSamplesUp (const AudioBlock<const SampleType>& inputBlock) override
{
jassert (inputBlock.getNumChannels() <= static_cast<size_t> (ParentType::buffer.getNumChannels()));
jassert (inputBlock.getNumSamples() * ParentType::factor <= static_cast<size_t> (ParentType::buffer.getNumSamples()));
// Initialization
auto coeffs = coefficientsUp.getRawDataPointer();
auto numStages = coefficientsUp.size();
auto delayedStages = numStages / 2;
auto directStages = numStages - delayedStages;
auto numSamples = inputBlock.getNumSamples();
// Processing
for (size_t channel = 0; channel < inputBlock.getNumChannels(); ++channel)
{
auto bufferSamples = ParentType::buffer.getWritePointer (static_cast<int> (channel));
auto lv1 = v1Up.getWritePointer (static_cast<int> (channel));
auto samples = inputBlock.getChannelPointer (channel);
for (size_t i = 0; i < numSamples; ++i)
{
// Direct path cascaded allpass filters
auto input = samples[i];
for (auto n = 0; n < directStages; ++n)
{
auto alpha = coeffs[n];
auto output = alpha * input + lv1[n];
lv1[n] = input - alpha * output;
input = output;
}
// Output
bufferSamples[i << 1] = input;
// Delayed path cascaded allpass filters
input = samples[i];
for (auto n = directStages; n < numStages; ++n)
{
auto alpha = coeffs[n];
auto output = alpha * input + lv1[n];
lv1[n] = input - alpha * output;
input = output;
}
// Output
bufferSamples[(i << 1) + 1] = input;
}
}
#if JUCE_DSP_ENABLE_SNAP_TO_ZERO
snapToZero (true);
#endif
}
void processSamplesDown (AudioBlock<SampleType>& outputBlock) override
{
jassert (outputBlock.getNumChannels() <= static_cast<size_t> (ParentType::buffer.getNumChannels()));
jassert (outputBlock.getNumSamples() * ParentType::factor <= static_cast<size_t> (ParentType::buffer.getNumSamples()));
// Initialization
auto coeffs = coefficientsDown.getRawDataPointer();
auto numStages = coefficientsDown.size();
auto delayedStages = numStages / 2;
auto directStages = numStages - delayedStages;
auto numSamples = outputBlock.getNumSamples();
// Processing
for (size_t channel = 0; channel < outputBlock.getNumChannels(); ++channel)
{
auto bufferSamples = ParentType::buffer.getWritePointer (static_cast<int> (channel));
auto lv1 = v1Down.getWritePointer (static_cast<int> (channel));
auto samples = outputBlock.getChannelPointer (channel);
auto delay = delayDown.getUnchecked (static_cast<int> (channel));
for (size_t i = 0; i < numSamples; ++i)
{
// Direct path cascaded allpass filters
auto input = bufferSamples[i << 1];
for (auto n = 0; n < directStages; ++n)
{
auto alpha = coeffs[n];
auto output = alpha * input + lv1[n];
lv1[n] = input - alpha * output;
input = output;
}
auto directOut = input;
// Delayed path cascaded allpass filters
input = bufferSamples[(i << 1) + 1];
for (auto n = directStages; n < numStages; ++n)
{
auto alpha = coeffs[n];
auto output = alpha * input + lv1[n];
lv1[n] = input - alpha * output;
input = output;
}
// Output
samples[i] = (delay + directOut) * static_cast<SampleType> (0.5);
delay = input;
}
delayDown.setUnchecked (static_cast<int> (channel), delay);
}
#if JUCE_DSP_ENABLE_SNAP_TO_ZERO
snapToZero (false);
#endif
}
void snapToZero (bool snapUpProcessing)
{
if (snapUpProcessing)
{
for (auto channel = 0; channel < ParentType::buffer.getNumChannels(); ++channel)
{
auto lv1 = v1Up.getWritePointer (channel);
auto numStages = coefficientsUp.size();
for (auto n = 0; n < numStages; ++n)
util::snapToZero (lv1[n]);
}
}
else
{
for (auto channel = 0; channel < ParentType::buffer.getNumChannels(); ++channel)
{
auto lv1 = v1Down.getWritePointer (channel);
auto numStages = coefficientsDown.size();
for (auto n = 0; n < numStages; ++n)
util::snapToZero (lv1[n]);
}
}
}
private:
//==============================================================================
/** This function calculates the equivalent high order IIR filter of a given
polyphase cascaded allpass filters structure.
*/
IIR::Coefficients<SampleType> getCoefficients (typename FilterDesign<SampleType>::IIRPolyphaseAllpassStructure& structure) const
{
constexpr auto one = static_cast<SampleType> (1.0);
Polynomial<SampleType> numerator1 ({ one }), denominator1 ({ one }),
numerator2 ({ one }), denominator2 ({ one });
for (auto* i : structure.directPath)
{
auto coeffs = i->getRawCoefficients();
if (i->getFilterOrder() == 1)
{
numerator1 = numerator1 .getProductWith (Polynomial<SampleType> ({ coeffs[0], coeffs[1] }));
denominator1 = denominator1.getProductWith (Polynomial<SampleType> ({ one, coeffs[2] }));
}
else
{
numerator1 = numerator1 .getProductWith (Polynomial<SampleType> ({ coeffs[0], coeffs[1], coeffs[2] }));
denominator1 = denominator1.getProductWith (Polynomial<SampleType> ({ one, coeffs[3], coeffs[4] }));
}
}
for (auto* i : structure.delayedPath)
{
auto coeffs = i->getRawCoefficients();
if (i->getFilterOrder() == 1)
{
numerator2 = numerator2 .getProductWith (Polynomial<SampleType> ({ coeffs[0], coeffs[1] }));
denominator2 = denominator2.getProductWith (Polynomial<SampleType> ({ one, coeffs[2] }));
}
else
{
numerator2 = numerator2 .getProductWith (Polynomial<SampleType> ({ coeffs[0], coeffs[1], coeffs[2] }));
denominator2 = denominator2.getProductWith (Polynomial<SampleType> ({ one, coeffs[3], coeffs[4] }));
}
}
auto numeratorf1 = numerator1.getProductWith (denominator2);
auto numeratorf2 = numerator2.getProductWith (denominator1);
auto numerator = numeratorf1.getSumWith (numeratorf2);
auto denominator = denominator1.getProductWith (denominator2);
IIR::Coefficients<SampleType> coeffs;
coeffs.coefficients.clear();
auto inversion = one / denominator[0];
for (int i = 0; i <= numerator.getOrder(); ++i)
coeffs.coefficients.add (numerator[i] * inversion);
for (int i = 1; i <= denominator.getOrder(); ++i)
coeffs.coefficients.add (denominator[i] * inversion);
return coeffs;
}
//==============================================================================
Array<SampleType> coefficientsUp, coefficientsDown;
SampleType latency;
AudioBuffer<SampleType> v1Up, v1Down;
Array<SampleType> delayDown;
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Oversampling2TimesPolyphaseIIR)
};
//==============================================================================
template <typename SampleType>
Oversampling<SampleType>::Oversampling (size_t newNumChannels)
: numChannels (newNumChannels)
{
jassert (numChannels > 0);
addDummyOversamplingStage();
}
template <typename SampleType>
Oversampling<SampleType>::Oversampling (size_t newNumChannels, size_t newFactor,
FilterType newType, bool isMaximumQuality,
bool useIntegerLatency)
: numChannels (newNumChannels), shouldUseIntegerLatency (useIntegerLatency)
{
jassert (isPositiveAndBelow (newFactor, 5) && numChannels > 0);
if (newFactor == 0)
{
addDummyOversamplingStage();
}
else if (newType == FilterType::filterHalfBandPolyphaseIIR)
{
for (size_t n = 0; n < newFactor; ++n)
{
auto twUp = (isMaximumQuality ? 0.10f : 0.12f) * (n == 0 ? 0.5f : 1.0f);
auto twDown = (isMaximumQuality ? 0.12f : 0.15f) * (n == 0 ? 0.5f : 1.0f);
auto gaindBStartUp = (isMaximumQuality ? -90.0f : -70.0f);
auto gaindBStartDown = (isMaximumQuality ? -75.0f : -60.0f);
auto gaindBFactorUp = (isMaximumQuality ? 10.0f : 8.0f);
auto gaindBFactorDown = (isMaximumQuality ? 10.0f : 8.0f);
addOversamplingStage (FilterType::filterHalfBandPolyphaseIIR,
twUp, gaindBStartUp + gaindBFactorUp * (float) n,
twDown, gaindBStartDown + gaindBFactorDown * (float) n);
}
}
else if (newType == FilterType::filterHalfBandFIREquiripple)
{
for (size_t n = 0; n < newFactor; ++n)
{
auto twUp = (isMaximumQuality ? 0.10f : 0.12f) * (n == 0 ? 0.5f : 1.0f);
auto twDown = (isMaximumQuality ? 0.12f : 0.15f) * (n == 0 ? 0.5f : 1.0f);
auto gaindBStartUp = (isMaximumQuality ? -90.0f : -70.0f);
auto gaindBStartDown = (isMaximumQuality ? -75.0f : -60.0f);
auto gaindBFactorUp = (isMaximumQuality ? 10.0f : 8.0f);
auto gaindBFactorDown = (isMaximumQuality ? 10.0f : 8.0f);
addOversamplingStage (FilterType::filterHalfBandFIREquiripple,
twUp, gaindBStartUp + gaindBFactorUp * (float) n,
twDown, gaindBStartDown + gaindBFactorDown * (float) n);
}
}
}
template <typename SampleType>
Oversampling<SampleType>::~Oversampling()
{
stages.clear();
}
//==============================================================================
template <typename SampleType>
void Oversampling<SampleType>::addDummyOversamplingStage()
{
stages.add (new OversamplingDummy<SampleType> (numChannels));
}
template <typename SampleType>
void Oversampling<SampleType>::addOversamplingStage (FilterType type,
float normalisedTransitionWidthUp,
float stopbandAmplitudedBUp,
float normalisedTransitionWidthDown,
float stopbandAmplitudedBDown)
{
if (type == FilterType::filterHalfBandPolyphaseIIR)
{
stages.add (new Oversampling2TimesPolyphaseIIR<SampleType> (numChannels,
normalisedTransitionWidthUp, stopbandAmplitudedBUp,
normalisedTransitionWidthDown, stopbandAmplitudedBDown));
}
else
{
stages.add (new Oversampling2TimesEquirippleFIR<SampleType> (numChannels,
normalisedTransitionWidthUp, stopbandAmplitudedBUp,
normalisedTransitionWidthDown, stopbandAmplitudedBDown));
}
factorOversampling *= 2;
}
template <typename SampleType>
void Oversampling<SampleType>::clearOversamplingStages()
{
stages.clear();
factorOversampling = 1u;
}
//==============================================================================
template <typename SampleType>
void Oversampling<SampleType>::setUsingIntegerLatency (bool useIntegerLatency) noexcept
{
shouldUseIntegerLatency = useIntegerLatency;
}
template <typename SampleType>
SampleType Oversampling<SampleType>::getLatencyInSamples() const noexcept
{
auto latency = getUncompensatedLatency();
return shouldUseIntegerLatency ? latency + fractionalDelay : latency;
}
template <typename SampleType>
SampleType Oversampling<SampleType>::getUncompensatedLatency() const noexcept
{
auto latency = static_cast<SampleType> (0);
size_t order = 1;
for (auto* stage : stages)
{
order *= stage->factor;
latency += stage->getLatencyInSamples() / static_cast<SampleType> (order);
}
return latency;
}
template <typename SampleType>
size_t Oversampling<SampleType>::getOversamplingFactor() const noexcept
{
return factorOversampling;
}
//==============================================================================
template <typename SampleType>
void Oversampling<SampleType>::initProcessing (size_t maximumNumberOfSamplesBeforeOversampling)
{
jassert (! stages.isEmpty());
auto currentNumSamples = maximumNumberOfSamplesBeforeOversampling;
for (auto* stage : stages)
{
stage->initProcessing (currentNumSamples);
currentNumSamples *= stage->factor;
}
ProcessSpec spec = { 0.0, (uint32) maximumNumberOfSamplesBeforeOversampling, (uint32) numChannels };
delay.prepare (spec);
updateDelayLine();
isReady = true;
reset();
}
template <typename SampleType>
void Oversampling<SampleType>::reset() noexcept
{
jassert (! stages.isEmpty());
if (isReady)
for (auto* stage : stages)
stage->reset();
delay.reset();
}
template <typename SampleType>
AudioBlock<SampleType> Oversampling<SampleType>::processSamplesUp (const AudioBlock<const SampleType>& inputBlock) noexcept
{
jassert (! stages.isEmpty());
if (! isReady)
return {};
auto* firstStage = stages.getUnchecked (0);
firstStage->processSamplesUp (inputBlock);
auto block = firstStage->getProcessedSamples (inputBlock.getNumSamples() * firstStage->factor);
for (int i = 1; i < stages.size(); ++i)
{
stages[i]->processSamplesUp (block);
block = stages[i]->getProcessedSamples (block.getNumSamples() * stages[i]->factor);
}
return block;
}
template <typename SampleType>
void Oversampling<SampleType>::processSamplesDown (AudioBlock<SampleType>& outputBlock) noexcept
{
jassert (! stages.isEmpty());
if (! isReady)
return;
auto currentNumSamples = outputBlock.getNumSamples();
for (int n = 0; n < stages.size() - 1; ++n)
currentNumSamples *= stages.getUnchecked(n)->factor;
for (int n = stages.size() - 1; n > 0; --n)
{
auto& stage = *stages.getUnchecked(n);
auto audioBlock = stages.getUnchecked (n - 1)->getProcessedSamples (currentNumSamples);
stage.processSamplesDown (audioBlock);
currentNumSamples /= stage.factor;
}
stages.getFirst()->processSamplesDown (outputBlock);
if (shouldUseIntegerLatency && fractionalDelay > static_cast<SampleType> (0.0))
{
auto context = ProcessContextReplacing<SampleType> (outputBlock);
delay.process (context);
}
}
template <typename SampleType>
void Oversampling<SampleType>::updateDelayLine()
{
auto latency = getUncompensatedLatency();
fractionalDelay = static_cast<SampleType> (1.0) - (latency - std::floor (latency));
if (fractionalDelay == static_cast<SampleType> (1.0))
fractionalDelay = static_cast<SampleType> (0.0);
else if (fractionalDelay < static_cast<SampleType> (0.618))
fractionalDelay += static_cast<SampleType> (1.0);
delay.setDelay (fractionalDelay);
}
template class Oversampling<float>;
template class Oversampling<double>;
} // namespace dsp
} // namespace juce

View File

@ -0,0 +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

View File

@ -0,0 +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

View File

@ -0,0 +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

View File

@ -0,0 +1,177 @@
/*
==============================================================================
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

View File

@ -0,0 +1,175 @@
/*
==============================================================================
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

View File

@ -0,0 +1,141 @@
/*
==============================================================================
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

View File

@ -0,0 +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

View File

@ -0,0 +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

View File

@ -0,0 +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

View File

@ -0,0 +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

View File

@ -0,0 +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