AnalogTapeModel

Physical modelling signal processing for analog tape recording
Log | Files | Refs | Submodules | README | LICENSE

CompressionProcessor.cpp (4510B)


      1 #include "CompressionProcessor.h"
      2 
      3 CompressionProcessor::CompressionProcessor (AudioProcessorValueTreeState& vts)
      4 {
      5     using namespace chowdsp::ParamUtils;
      6     onOff = vts.getRawParameterValue ("comp_onoff");
      7     loadParameterPointer (amountParam, vts, "comp_amt");
      8     loadParameterPointer (attackParam, vts, "comp_attack");
      9     loadParameterPointer (releaseParam, vts, "comp_release");
     10 }
     11 
     12 void CompressionProcessor::createParameterLayout (chowdsp::Parameters& params)
     13 {
     14     using namespace chowdsp::ParamUtils;
     15     emplace_param<chowdsp::BoolParameter> (params, "comp_onoff", "Compression On/Off", false);
     16     createGainDBParameter (params, "comp_amt", "Compression Amount", 0.0f, 9.0f, 0.0f, 3.0f);
     17     createTimeMsParameter (params, "comp_attack", "Compression Attack", createNormalisableRange (0.1f, 50.0f, 10.0f), 5.0f);
     18     createTimeMsParameter (params, "comp_release", "Compression Release", createNormalisableRange (10.0f, 1000.0f, 100.0f), 200.0f);
     19 }
     20 
     21 void CompressionProcessor::prepare (double sr, int samplesPerBlock, int numChannels)
     22 {
     23     oversample = std::make_unique<dsp::Oversampling<float>> (numChannels, 1, dsp::Oversampling<float>::filterHalfBandPolyphaseIIR, true, true);
     24     oversample->initProcessing ((size_t) samplesPerBlock);
     25     auto osFactor = oversample->getOversamplingFactor();
     26     bypass.prepare (samplesPerBlock, numChannels, bypass.toBool (onOff));
     27 
     28     slewLimiter.clear();
     29     dbPlusSmooth.clear();
     30     for (int ch = 0; ch < numChannels; ++ch)
     31     {
     32         slewLimiter.emplace_back();
     33         dbPlusSmooth.emplace_back();
     34 
     35         slewLimiter[ch].prepare ({ sr, (uint32) samplesPerBlock, 1 });
     36         dbPlusSmooth[ch].reset (sr, 0.05);
     37     }
     38 
     39     xDBVec.resize (osFactor * (size_t) samplesPerBlock, 0.0f);
     40     compGainVec.resize (osFactor * (size_t) samplesPerBlock, 0.0f);
     41 }
     42 
     43 template <typename T>
     44 inline T compressionDB (const T& xDB, float dbPlus)
     45 {
     46     using namespace chowdsp::SIMDUtils;
     47     CHOWDSP_USING_XSIMD_STD (log);
     48 
     49     if (dbPlus <= 0.0f)
     50         return (T) dbPlus;
     51 
     52     auto window = 2.0f * dbPlus;
     53     auto belowWin = xDB < -window;
     54     return select (belowWin, (T) dbPlus, log (xDB + window + 1.0f) - dbPlus - xDB);
     55 }
     56 
     57 void CompressionProcessor::processBlock (AudioBuffer<float>& buffer)
     58 {
     59     if (! bypass.processBlockIn (buffer, bypass.toBool (onOff)))
     60         return;
     61 
     62     dsp::AudioBlock<float> block (buffer);
     63     auto osBlock = oversample->processSamplesUp (block);
     64 
     65     const auto numSamples = (int) osBlock.getNumSamples();
     66     for (int ch = 0; ch < buffer.getNumChannels(); ++ch)
     67     {
     68         dbPlusSmooth[ch].setTargetValue (amountParam->getCurrentValue());
     69 
     70         auto* x = osBlock.getChannelPointer ((size_t) ch);
     71         FloatVectorOperations::copy (xDBVec.data(), x, numSamples);
     72         FloatVectorOperations::abs (xDBVec.data(), xDBVec.data(), numSamples);
     73 
     74         constexpr auto inc = xsimd::batch<float>::size;
     75         size_t n = 0;
     76         for (; n < (size_t) numSamples; n += inc)
     77         {
     78             auto xDB = xsimd::load_aligned (&xDBVec[n]);
     79 
     80             xDB = chowdsp::SIMDUtils::gainToDecibels (xDB);
     81             auto compDB = compressionDB (xDB, dbPlusSmooth[ch].skip ((int) inc));
     82             auto compGain = chowdsp::SIMDUtils::decibelsToGain (compDB);
     83 
     84             xsimd::store_aligned (&xDBVec[n], xDB);
     85             xsimd::store_aligned (&compGainVec[n], compGain);
     86         }
     87 
     88         // remaining samples that can't be vectorized
     89         for (; n < (size_t) numSamples; ++n)
     90         {
     91             xDBVec[n] = Decibels::gainToDecibels (xDBVec[n]);
     92             auto compDB = compressionDB (xDBVec[n], dbPlusSmooth[ch].getNextValue());
     93             compGainVec[n] = Decibels::decibelsToGain (compDB);
     94         }
     95 
     96         // since the slew will be applied to the gain, we need to reverse the attack and release parameters!
     97         slewLimiter[ch].setParameters (releaseParam->getCurrentValue(), attackParam->getCurrentValue());
     98         for (size_t k = 0; k < (size_t) numSamples; ++k)
     99             compGainVec[k] = jmin (compGainVec[k], slewLimiter[ch].processSample (compGainVec[k]));
    100 
    101         FloatVectorOperations::multiply (x, compGainVec.data(), numSamples);
    102     }
    103 
    104     oversample->processSamplesDown (block);
    105 
    106     bypass.processBlockOut (buffer, bypass.toBool (onOff));
    107 }
    108 
    109 float CompressionProcessor::getLatencySamples() const noexcept
    110 {
    111     if (oversample == nullptr)
    112         return 0.0f;
    113 
    114     return onOff->load() == 1.0f ? oversample->getLatencyInSamples() // on
    115                                  : 0.0f; // off
    116 }