Created
May 31, 2022 18:47
-
-
Save Xenakios/2012c6ea3cfba12dec1ba93aa214cb72 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#pragma once | |
/* | |
============================================================================== | |
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. | |
============================================================================== | |
*/ | |
#include "JuceHeader.h" | |
namespace xenakios | |
{ | |
namespace dsp | |
{ | |
/** | |
A simple compressor with standard threshold, ratio, attack time and release time | |
controls. | |
Modified by Xenakios 31st May 2022 to support sidechain input | |
@tags{DSP} | |
*/ | |
template <typename SampleType> | |
class Compressor | |
{ | |
public: | |
//============================================================================== | |
/** Constructor. */ | |
Compressor(); | |
//============================================================================== | |
/** Sets the threshold in dB of the compressor.*/ | |
void setThreshold(SampleType newThreshold); | |
/** Sets the ratio of the compressor (must be higher or equal to 1).*/ | |
void setRatio(SampleType newRatio); | |
/** Sets the attack time in milliseconds of the compressor.*/ | |
void setAttack(SampleType newAttack); | |
/** Sets the release time in milliseconds of the compressor.*/ | |
void setRelease(SampleType newRelease); | |
//============================================================================== | |
/** Initialises the processor. */ | |
void prepare(const juce::dsp::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 | |
{ | |
// for clarity, deal with the sidechained and non-sidechained cases completely separately here, but code | |
// could of course be deduplicated a bit later | |
if (!usesSideChain) | |
{ | |
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.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]); | |
} | |
} | |
else | |
{ | |
const auto& inputBlock = context.getInputBlock(); | |
auto& outputBlock = context.getOutputBlock(); | |
const auto numInChannels = inputBlock.getNumChannels(); | |
// num in and out channels seems to be identical, so have to do this hack here... | |
const auto numOutChannels = outputBlock.getNumChannels() / 2; | |
const auto numSamples = outputBlock.getNumSamples(); | |
jassert(inputBlock.getNumSamples() == numSamples); | |
/* | |
// ignore dealing with bypass for now, since we don't have enough output channels | |
if (context.isBypassed) | |
{ | |
outputBlock.copyFrom(inputBlock); | |
return; | |
} | |
*/ | |
for (size_t channel = 0; channel < numOutChannels; ++channel) | |
{ | |
auto* inputSamples = inputBlock.getChannelPointer(channel); | |
auto* scSamples = inputBlock.getChannelPointer(channel + 2); | |
auto* outputSamples = outputBlock.getChannelPointer(channel); | |
for (size_t i = 0; i < numSamples; ++i) | |
outputSamples[i] = processSampleWithSideChain((int)channel, inputSamples[i], scSamples[i]); | |
} | |
} | |
} | |
/** Performs the processing operation on a single sample at a time. */ | |
SampleType processSample(int channel, SampleType inputValue); | |
SampleType processSampleWithSideChain(int channel, SampleType inputValue, SampleType sideChainValue) | |
{ | |
// Ballistics filter with peak rectifier | |
auto env = envelopeFilter.processSample(channel, sideChainValue); | |
// VCA | |
auto gain = (env < threshold) ? static_cast<SampleType> (1.0) | |
: std::pow(env * thresholdInverse, ratioInverse - static_cast<SampleType> (1.0)); | |
// Output | |
return gain * inputValue; | |
} | |
void setSidechainEnabled(bool b) | |
{ | |
usesSideChain = b; | |
} | |
bool getSidechainEnabled() const | |
{ | |
return usesSideChain; | |
} | |
private: | |
//============================================================================== | |
void update(); | |
//============================================================================== | |
SampleType threshold, thresholdInverse, ratioInverse; | |
juce::dsp::BallisticsFilter<SampleType> envelopeFilter; | |
bool usesSideChain = false; | |
double sampleRate = 44100.0; | |
SampleType thresholddB = 0.0, ratio = 1.0, attackTime = 1.0, releaseTime = 100.0; | |
}; | |
template class Compressor<float>; | |
template class Compressor<double>; | |
// namespace dsp | |
//============================================================================== | |
template <typename SampleType> | |
Compressor<SampleType>::Compressor() | |
{ | |
update(); | |
} | |
//============================================================================== | |
template <typename SampleType> | |
void Compressor<SampleType>::setThreshold(SampleType newThreshold) | |
{ | |
thresholddB = newThreshold; | |
update(); | |
} | |
template <typename SampleType> | |
void Compressor<SampleType>::setRatio(SampleType newRatio) | |
{ | |
jassert(newRatio >= static_cast<SampleType> (1.0)); | |
ratio = newRatio; | |
update(); | |
} | |
template <typename SampleType> | |
void Compressor<SampleType>::setAttack(SampleType newAttack) | |
{ | |
attackTime = newAttack; | |
update(); | |
} | |
template <typename SampleType> | |
void Compressor<SampleType>::setRelease(SampleType newRelease) | |
{ | |
releaseTime = newRelease; | |
update(); | |
} | |
//============================================================================== | |
template <typename SampleType> | |
void Compressor<SampleType>::prepare(const juce::dsp::ProcessSpec& spec) | |
{ | |
jassert(spec.sampleRate > 0); | |
jassert(spec.numChannels > 0); | |
sampleRate = spec.sampleRate; | |
// This needlessly sets up double the number of ballistics filter channels when using sidechaining, but that | |
// only uses a bit of memory, we never actually process samples through the extra ones | |
// optimize if seems important enough to bother with... | |
envelopeFilter.prepare(spec); | |
update(); | |
reset(); | |
} | |
template <typename SampleType> | |
void Compressor<SampleType>::reset() | |
{ | |
envelopeFilter.reset(); | |
} | |
//============================================================================== | |
template <typename SampleType> | |
SampleType Compressor<SampleType>::processSample(int channel, SampleType inputValue) | |
{ | |
// Ballistics filter with peak rectifier | |
auto env = envelopeFilter.processSample(channel, inputValue); | |
// VCA | |
auto gain = (env < threshold) ? static_cast<SampleType> (1.0) | |
: std::pow(env * thresholdInverse, ratioInverse - static_cast<SampleType> (1.0)); | |
// Output | |
return gain * inputValue; | |
} | |
template <typename SampleType> | |
void Compressor<SampleType>::update() | |
{ | |
threshold = juce::Decibels::decibelsToGain(thresholddB, static_cast<SampleType> (-200.0)); | |
thresholdInverse = static_cast<SampleType> (1.0) / threshold; | |
ratioInverse = static_cast<SampleType> (1.0) / ratio; | |
envelopeFilter.setAttackTime(attackTime); | |
envelopeFilter.setReleaseTime(releaseTime); | |
} | |
} | |
} // namespace xenakios |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment