mirror of
https://github.com/LMMS/lmms.git
synced 2026-01-19 11:58:16 -05:00
* Centralize standard LMMS plugin logo Previously, every single plugin would have its own copy of this image. Now, these plugins pull it from the theme. I'm not sure it's valuable for this to be themeable, but AFAIK it's the place all the other non-plugin resources are stored, so that's where it goes for now.
721 lines
29 KiB
C++
Executable File
721 lines
29 KiB
C++
Executable File
/*
|
|
* SlewDistortion.cpp
|
|
*
|
|
* Copyright (c) 2025 Lost Robot <r94231/at/gmail/dot/com>
|
|
*
|
|
* This file is part of LMMS - https://lmms.io
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2 of the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public
|
|
* License along with this program (see COPYING); if not, write to the
|
|
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
|
* Boston, MA 02110-1301 USA.
|
|
*
|
|
*/
|
|
|
|
#include "SlewDistortion.h"
|
|
|
|
#include "embed.h"
|
|
#include "plugin_export.h"
|
|
|
|
namespace lmms
|
|
{
|
|
|
|
extern "C"
|
|
{
|
|
Plugin::Descriptor PLUGIN_EXPORT slewdistortion_plugin_descriptor =
|
|
{
|
|
LMMS_STRINGIFY(PLUGIN_NAME),
|
|
"Slew Distortion",
|
|
QT_TRANSLATE_NOOP("PluginBrowser", "A 2-band distortion and slew rate limiter plugin."),
|
|
"Lost Robot <r94231/at/gmail/dot/com>",
|
|
0x0100,
|
|
Plugin::Type::Effect,
|
|
new PixmapLoader("lmms-plugin-logo"),
|
|
nullptr,
|
|
nullptr,
|
|
};
|
|
}
|
|
|
|
|
|
SlewDistortion::SlewDistortion(Model* parent, const Descriptor::SubPluginFeatures::Key* key) :
|
|
Effect(&slewdistortion_plugin_descriptor, parent, key),
|
|
m_sampleRate(Engine::audioEngine()->outputSampleRate()),
|
|
m_lp(m_sampleRate),
|
|
m_hp(m_sampleRate),
|
|
m_slewdistortionControls(this)
|
|
{
|
|
connect(Engine::audioEngine(), &AudioEngine::sampleRateChanged, this, &SlewDistortion::changeSampleRate);
|
|
changeSampleRate();
|
|
}
|
|
|
|
|
|
#ifdef __SSE2__
|
|
Effect::ProcessStatus SlewDistortion::processImpl(SampleFrame* buf, const fpp_t frames)
|
|
{
|
|
const float d = dryLevel();
|
|
const float w = wetLevel();
|
|
|
|
const int oversampling = m_slewdistortionControls.m_oversamplingModel.value();
|
|
const int oversampleVal = 1 << oversampling;
|
|
if (oversampleVal != m_oldOversampleVal)
|
|
{
|
|
m_oldOversampleVal = oversampleVal;
|
|
changeSampleRate();
|
|
}
|
|
|
|
const SlewDistortionType distType1 = static_cast<SlewDistortionType>(m_slewdistortionControls.m_distType1Model.value());
|
|
const SlewDistortionType distType2 = static_cast<SlewDistortionType>(m_slewdistortionControls.m_distType2Model.value());
|
|
const float drive1 = dbfsToAmp(m_slewdistortionControls.m_drive1Model.value());
|
|
const float drive2 = dbfsToAmp(m_slewdistortionControls.m_drive2Model.value());
|
|
const float slewUp1 = dbfsToAmp(m_slewdistortionControls.m_slewUp1Model.value()) / oversampleVal;
|
|
const float slewUp2 = dbfsToAmp(m_slewdistortionControls.m_slewUp2Model.value()) / oversampleVal;
|
|
const float slewDown1 = dbfsToAmp(m_slewdistortionControls.m_slewDown1Model.value()) / oversampleVal;
|
|
const float slewDown2 = dbfsToAmp(m_slewdistortionControls.m_slewDown2Model.value()) / oversampleVal;
|
|
const float bias1 = m_slewdistortionControls.m_bias1Model.value();
|
|
const float bias2 = m_slewdistortionControls.m_bias2Model.value();
|
|
const float warp1 = m_slewdistortionControls.m_warp1Model.value();
|
|
const float warp2 = m_slewdistortionControls.m_warp2Model.value();
|
|
const float crush1 = dbfsToAmp(m_slewdistortionControls.m_crush1Model.value());
|
|
const float crush2 = dbfsToAmp(m_slewdistortionControls.m_crush2Model.value());
|
|
const float attack1 = msToCoeff(m_slewdistortionControls.m_attack1Model.value());
|
|
const float attack2 = msToCoeff(m_slewdistortionControls.m_attack2Model.value());
|
|
const float attackInv1 = 1.f - attack1;
|
|
const float attackInv2 = 1.f - attack2;
|
|
const float release1 = msToCoeff(m_slewdistortionControls.m_release1Model.value());
|
|
const float release2 = msToCoeff(m_slewdistortionControls.m_release2Model.value());
|
|
const float releaseInv1 = 1.f - release1;
|
|
const float releaseInv2 = 1.f - release2;
|
|
const float dynamics1 = m_slewdistortionControls.m_dynamics1Model.value();
|
|
const float dynamics2 = m_slewdistortionControls.m_dynamics2Model.value();
|
|
const float dynamicSlew1 = m_slewdistortionControls.m_dynamicSlew1Model.value();
|
|
const float dynamicSlew2 = m_slewdistortionControls.m_dynamicSlew2Model.value();
|
|
const float outVol1 = dbfsToAmp(m_slewdistortionControls.m_outVol1Model.value());
|
|
const float outVol2 = dbfsToAmp(m_slewdistortionControls.m_outVol2Model.value());
|
|
const float split = m_slewdistortionControls.m_splitModel.value();
|
|
const bool dcRemove = m_slewdistortionControls.m_dcRemoveModel.value();
|
|
const bool multiband = m_slewdistortionControls.m_multibandModel.value();
|
|
const float mix1 = m_slewdistortionControls.m_mix1Model.value();
|
|
const float mix2 = m_slewdistortionControls.m_mix2Model.value();
|
|
const bool slewLink1 = m_slewdistortionControls.m_slewLink1Model.value();
|
|
const bool slewLink2 = m_slewdistortionControls.m_slewLink2Model.value();
|
|
|
|
const __m128 drive = _mm_set_ps(drive2, drive2, drive1, drive1);
|
|
const __m128 slewUp = _mm_set_ps(slewUp2, slewUp2, slewUp1, slewUp1);
|
|
const __m128 slewDown = _mm_set_ps(slewDown2, slewDown2, slewDown1, slewDown1);
|
|
const __m128 warp = _mm_set_ps(warp2, warp2, warp1, warp1);
|
|
const __m128 crush = _mm_set_ps(crush2, crush2, crush1, crush1);
|
|
const __m128 outVol = _mm_set_ps(outVol2, outVol2, outVol1, outVol1);
|
|
const __m128 attack = _mm_set_ps(attack2, attack2, attack1, attack1);
|
|
const __m128 attackInv = _mm_set_ps(attackInv2, attackInv2, attackInv1, attackInv1);
|
|
const __m128 release = _mm_set_ps(release2, release2, release1, release1);
|
|
const __m128 releaseInv = _mm_set_ps(releaseInv2, releaseInv2, releaseInv1, releaseInv1);
|
|
const __m128 dynamics = _mm_set_ps(dynamics2, dynamics2, dynamics1, dynamics1);
|
|
const __m128 dynamicSlew = _mm_set_ps(dynamicSlew2, dynamicSlew2, dynamicSlew1, dynamicSlew1);
|
|
const __m128 mix = _mm_set_ps(mix2, mix2, mix1, mix1);
|
|
const __m128 minFloor = _mm_set1_ps(SLEW_DISTORTION_MIN_FLOOR);
|
|
const int link1Mask = -static_cast<int>(slewLink1);
|
|
const int link2Mask = -static_cast<int>(slewLink2);
|
|
const __m128 slewLinkMask = _mm_castsi128_ps(_mm_set_epi32(link2Mask, link2Mask, link1Mask, link1Mask));
|
|
|
|
const __m128 zero = _mm_setzero_ps();
|
|
const __m128 one = _mm_set1_ps(1.0f);
|
|
|
|
if (m_slewdistortionControls.m_splitModel.isValueChanged())
|
|
{
|
|
m_lp.setLowpass(split);
|
|
m_hp.setHighpass(split);
|
|
}
|
|
|
|
for (fpp_t f = 0; f < frames; ++f)
|
|
{
|
|
// interpolate bias to remove crackling when moving the parameter
|
|
m_trueBias1 = m_biasInterpCoef * m_trueBias1 + (1.f - m_biasInterpCoef) * bias1;
|
|
m_trueBias2 = m_biasInterpCoef * m_trueBias2 + (1.f - m_biasInterpCoef) * bias2;
|
|
const __m128 bias = _mm_set_ps(m_trueBias2, m_trueBias2, m_trueBias1, m_trueBias1);
|
|
|
|
if (oversampleVal > 1)
|
|
{
|
|
m_upsampler[0].processSample(m_overOuts[0].data(), buf[f][0]);
|
|
m_upsampler[1].processSample(m_overOuts[1].data(), buf[f][1]);
|
|
}
|
|
else
|
|
{
|
|
m_overOuts[0][0] = buf[f][0];
|
|
m_overOuts[1][0] = buf[f][1];
|
|
}
|
|
|
|
for (int overSamp = 0; overSamp < oversampleVal; ++overSamp)
|
|
{
|
|
alignas(16) std::array<float, 4> inArr = {0};
|
|
if (multiband)
|
|
{
|
|
inArr[0] = m_hp.update(m_overOuts[0][overSamp], 0);
|
|
inArr[1] = m_hp.update(m_overOuts[1][overSamp], 1);
|
|
inArr[2] = m_lp.update(m_overOuts[0][overSamp], 0);
|
|
inArr[3] = m_lp.update(m_overOuts[1][overSamp], 1);
|
|
}
|
|
else
|
|
{
|
|
inArr[0] = m_overOuts[0][overSamp];
|
|
inArr[1] = m_overOuts[1][overSamp];
|
|
inArr[2] = 0;
|
|
inArr[3] = 0;
|
|
}
|
|
|
|
__m128 in = _mm_load_ps(&inArr[0]);
|
|
__m128 absIn = sse2Abs(in);
|
|
|
|
// store volume for display
|
|
_mm_store_ps(&m_inPeakDisplay[0], _mm_max_ps(_mm_load_ps(&m_inPeakDisplay[0]), _mm_mul_ps(absIn, drive)));
|
|
|
|
__m128 inEnv = _mm_load_ps(&m_inEnv[0]);
|
|
__m128 slewOut = _mm_load_ps(&m_slewOut[0]);
|
|
|
|
// apply attack and release to envelope follower
|
|
__m128 cmp = _mm_cmpgt_ps(absIn, inEnv);
|
|
__m128 envRise = _mm_add_ps(_mm_mul_ps(inEnv, attack), _mm_mul_ps(absIn, attackInv));
|
|
__m128 envFall = _mm_add_ps(_mm_mul_ps(inEnv, release), _mm_mul_ps(absIn, releaseInv));
|
|
inEnv = _mm_or_ps(_mm_and_ps(cmp, envRise), _mm_andnot_ps(cmp, envFall));
|
|
inEnv = _mm_max_ps(inEnv, minFloor);
|
|
|
|
// this is the input signal's slew rate
|
|
__m128 rate = _mm_sub_ps(in, slewOut);
|
|
|
|
__m128 scaledLog = _mm_mul_ps(dynamicSlew, fastLog(inEnv));
|
|
// clamp to [-80.0f, 80.0f] since float std::exp breaks outside of those bounds
|
|
__m128 clampedScaledLog = _mm_max_ps(_mm_min_ps(scaledLog, _mm_set1_ps(80.0f)), _mm_set1_ps(-80.0f));
|
|
__m128 slewMult = fastExp(clampedScaledLog);
|
|
|
|
// determine whether we should use the slew up or slew down parameter
|
|
__m128 finalMask = _mm_or_ps(_mm_cmpge_ps(rate, zero), slewLinkMask);
|
|
__m128 finalSlew = _mm_or_ps(_mm_and_ps(finalMask, _mm_mul_ps(slewUp, slewMult)),
|
|
_mm_andnot_ps(finalMask, _mm_mul_ps(slewDown, slewMult)));
|
|
|
|
__m128 clampedRate = _mm_max_ps(_mm_sub_ps(zero, finalSlew), _mm_min_ps(rate, finalSlew));
|
|
slewOut = _mm_add_ps(slewOut, clampedRate);
|
|
|
|
// apply drive and bias
|
|
__m128 biasedIn = _mm_add_ps(_mm_mul_ps(slewOut, drive), bias);
|
|
|
|
// apply warp and crush
|
|
// distIn = (biasedIn - std::copysign(warp[i] / crush[i], biasedIn)) / (1.f - warp[i]);
|
|
__m128 signBiasedIn = _mm_and_ps(biasedIn, _mm_castsi128_ps(_mm_set1_epi32(0x80000000)));
|
|
__m128 warpOverCrush = _mm_div_ps(warp, crush);
|
|
__m128 copysignWarpOverCrush = _mm_or_ps(warpOverCrush, signBiasedIn);
|
|
__m128 distIn = _mm_div_ps(_mm_sub_ps(biasedIn, copysignWarpOverCrush), _mm_sub_ps(one, warp));
|
|
|
|
alignas(16) std::array<float, 4> distInArr;
|
|
_mm_store_ps(&distInArr[0], distIn);
|
|
alignas(16) std::array<float, 4> distOutArr;
|
|
|
|
// if both bands have the same distortion type, we can process all four channels simultaneously
|
|
// otherwise we have to do two at a time
|
|
int loopCount = (distType1 == distType2 || !multiband) ? 1 : 2;
|
|
|
|
for (int pair = 0; pair < loopCount; ++pair)
|
|
{
|
|
SlewDistortionType currentDistType = (pair == 0) ? distType1 : distType2;
|
|
|
|
__m128 distInFull = _mm_load_ps(&distInArr[0]);
|
|
__m128 distOutFull;
|
|
|
|
// switch-case applies the distortion to the full set of 4 values
|
|
switch (currentDistType)
|
|
{
|
|
case SlewDistortionType::HardClip:// Hard Clip => clamp(x, -1, 1)
|
|
{
|
|
__m128 minVal = _mm_set1_ps(-1.0f);
|
|
__m128 maxVal = one;
|
|
distOutFull = _mm_max_ps(_mm_min_ps(distInFull, maxVal), minVal);
|
|
break;
|
|
}
|
|
case SlewDistortionType::Tanh: // Tanh => 2 / (1 + exp(-2x)) - 1
|
|
{
|
|
// clamp to [-80.0f, 80.0f] since float std::exp breaks outside of those bounds
|
|
__m128 clampedInput = _mm_max_ps(_mm_min_ps(_mm_mul_ps(_mm_set1_ps(-2.0f),
|
|
distInFull), _mm_set1_ps(80.0f)), _mm_set1_ps(-80.0f));
|
|
__m128 expResult = fastExp(clampedInput);
|
|
distOutFull = _mm_sub_ps(_mm_div_ps(_mm_set1_ps(2.0f), _mm_add_ps(one, expResult)), one);
|
|
break;
|
|
}
|
|
case SlewDistortionType::FastSoftClip1: // Fast Soft Clip 1 => x / (1 + x^2 / 4)
|
|
{
|
|
__m128 temp = _mm_max_ps(_mm_min_ps(distInFull, _mm_set1_ps(2.f)), _mm_set1_ps(-2.f));// clamp
|
|
distOutFull = _mm_div_ps(temp, _mm_add_ps(one,
|
|
_mm_mul_ps(_mm_set1_ps(0.25f), _mm_mul_ps(temp, temp))));
|
|
break;
|
|
}
|
|
case SlewDistortionType::FastSoftClip2: // Fast Soft Clip 2 => x - (4/27) * x^3
|
|
{
|
|
__m128 temp = _mm_max_ps(_mm_min_ps(distInFull, _mm_set1_ps(1.5f)), _mm_set1_ps(-1.5f));// clamp
|
|
distOutFull = _mm_sub_ps(temp, _mm_mul_ps(_mm_set1_ps(4.f / 27.f),
|
|
_mm_mul_ps(_mm_mul_ps(temp, temp), temp)));
|
|
break;
|
|
}
|
|
case SlewDistortionType::Sinusoidal: // Sinusoidal => sin(x)
|
|
{
|
|
// SSE2 sine approximation I created
|
|
__m128 pi = _mm_set1_ps(std::numbers::pi_v<float>);
|
|
__m128 piOverTwo = _mm_set1_ps(std::numbers::pi_v<float> * 0.5f);
|
|
__m128 tau = _mm_set1_ps(std::numbers::pi_v<float> * 2.f);
|
|
|
|
__m128 distMinusPiOverTwo = _mm_sub_ps(distInFull, piOverTwo);
|
|
__m128 divByTwoPi = _mm_div_ps(distMinusPiOverTwo, tau);
|
|
|
|
// SSE2 floor replacement
|
|
__m128 floorDivByTwoPi = sse2Floor(divByTwoPi);
|
|
|
|
// x mod 2pi = x - floor(x / 2pi) * 2pi
|
|
__m128 floorMulTwoPi = _mm_mul_ps(floorDivByTwoPi, tau);
|
|
__m128 modInput = _mm_sub_ps(distMinusPiOverTwo, floorMulTwoPi);
|
|
|
|
// abs(in - pi) - pi/2
|
|
__m128 x = _mm_sub_ps(sse2Abs(_mm_sub_ps(modInput, pi)), piOverTwo);
|
|
|
|
// polynomial sine approximation
|
|
// sin(x) ≈ x - x^3 / 6 + x^5 / 120
|
|
__m128 x2 = _mm_mul_ps(x, x);
|
|
__m128 x3 = _mm_mul_ps(x2, x);
|
|
__m128 x5 = _mm_mul_ps(x3, x2);
|
|
__m128 sinApprox = _mm_sub_ps(x, _mm_mul_ps(x3, _mm_set1_ps(1.0f / 6.0f)));
|
|
distOutFull = _mm_add_ps(sinApprox, _mm_mul_ps(x5, _mm_set1_ps(1.0f / 120.0f)));
|
|
break;
|
|
}
|
|
case SlewDistortionType::Foldback: // Foldback => |(|x - 1| mod 4) - 2| - 1 = |2 - |(x - 1) - 4 * floor((x - 1) / 4)|| - 1
|
|
{
|
|
__m128 four = _mm_set1_ps(4.0f);
|
|
__m128 distInMinusOne = _mm_sub_ps(distInFull, one);
|
|
__m128 divByFour = _mm_div_ps(distInMinusOne, four);
|
|
|
|
// floor
|
|
__m128 floorOverFour = sse2Floor(divByFour);
|
|
|
|
distOutFull = _mm_sub_ps(sse2Abs(_mm_sub_ps(_mm_sub_ps(
|
|
distInMinusOne, _mm_mul_ps(floorOverFour, four)), _mm_set1_ps(2.0f))), one);
|
|
break;
|
|
}
|
|
case SlewDistortionType::FullRectify: // |x|
|
|
{
|
|
distOutFull = sse2Abs(distInFull);
|
|
break;
|
|
}
|
|
case SlewDistortionType::SmoothRectify: // sqrt(x^2 + 0.04) - 0.2
|
|
{
|
|
distOutFull = _mm_sub_ps(_mm_sqrt_ps(_mm_add_ps(_mm_mul_ps(distInFull, distInFull),
|
|
_mm_set1_ps(0.04f))), _mm_set1_ps(0.2f));
|
|
break;
|
|
}
|
|
case SlewDistortionType::HalfRectify: // max(0, x)
|
|
{
|
|
distOutFull = _mm_max_ps(_mm_setzero_ps(), distInFull);
|
|
break;
|
|
}
|
|
case SlewDistortionType::Bitcrush: // round(x / drive * scale) / scale
|
|
{
|
|
// scale = 16 / drive
|
|
__m128 scale = _mm_div_ps(_mm_set1_ps(16.f), drive);
|
|
__m128 scaledVal = _mm_mul_ps(_mm_div_ps(distInFull, drive), scale);
|
|
|
|
// round to nearest, half away from zero
|
|
__m128 rounded = sse2Round(scaledVal);
|
|
|
|
distOutFull = _mm_div_ps(rounded, scale);
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
distOutFull = distInFull;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (loopCount == 1)// we can store all four simultaneously
|
|
{
|
|
_mm_store_ps(&distOutArr[0], distOutFull);
|
|
break;
|
|
}
|
|
else// need to store two at a time
|
|
{
|
|
if (pair == 0)
|
|
{
|
|
// for elements 0 and 1
|
|
_mm_storel_pi(reinterpret_cast<__m64*>(&distOutArr[0]), distOutFull);
|
|
}
|
|
else
|
|
{
|
|
// for elements 2 and 3
|
|
_mm_storeh_pi(reinterpret_cast<__m64*>(&distOutArr[2]), distOutFull);
|
|
}
|
|
}
|
|
}
|
|
|
|
__m128 distOut = _mm_load_ps(&distOutArr[0]);
|
|
|
|
// (1 - warp) * distOut + std::copysign(warp, biasedIn)
|
|
__m128 distOutScaled = _mm_add_ps(_mm_mul_ps(distOut, _mm_sub_ps(one, warp)), _mm_or_ps(warp, signBiasedIn));
|
|
|
|
// if (abs(biasedIn) < warp / crush) {distOut = biasedIn * crush;}
|
|
__m128 absBiasedIn = sse2Abs(biasedIn);
|
|
__m128 condition = _mm_cmplt_ps(absBiasedIn, _mm_div_ps(warp, crush));
|
|
__m128 biasedInCrush = _mm_mul_ps(biasedIn, crush);
|
|
|
|
distOut = _mm_or_ps(_mm_and_ps(condition, biasedInCrush), _mm_andnot_ps(condition, distOutScaled));
|
|
|
|
// DC offset calculation
|
|
__m128 dcOffset = _mm_load_ps(&m_dcOffset[0]);
|
|
__m128 dcCoeff = _mm_set1_ps(m_dcCoeff);
|
|
dcOffset = _mm_add_ps(_mm_mul_ps(dcOffset, dcCoeff), _mm_mul_ps(distOut, _mm_sub_ps(one, dcCoeff)));
|
|
|
|
__m128 distOutMinusDC = _mm_sub_ps(distOut, dcOffset);
|
|
|
|
// even with DC offset removal disabled, we should still apply it for the envelope follower
|
|
__m128 outEnv = _mm_load_ps(&m_outEnv[0]);
|
|
__m128 absOut = sse2Abs(distOutMinusDC);
|
|
|
|
cmp = _mm_cmpgt_ps(absOut, outEnv);
|
|
__m128 outEnvRise = _mm_add_ps(_mm_mul_ps(outEnv, attack), _mm_mul_ps(absOut, attackInv));
|
|
__m128 outEnvFall = _mm_add_ps(_mm_mul_ps(outEnv, release), _mm_mul_ps(absOut, releaseInv));
|
|
outEnv = _mm_max_ps(_mm_or_ps(_mm_and_ps(cmp, outEnvRise), _mm_andnot_ps(cmp, outEnvFall)), minFloor);
|
|
|
|
// remove DC
|
|
__m128 finalDistOut = (dcRemove) ? distOutMinusDC : distOut;
|
|
|
|
// crossfade between a multiplier of 1 and (inEnv/outEnv) for dynamics feature
|
|
__m128 distDyn = _mm_mul_ps(finalDistOut, _mm_add_ps(one,
|
|
_mm_mul_ps(_mm_sub_ps(_mm_div_ps(inEnv, outEnv), one), dynamics)));
|
|
|
|
// apply mix
|
|
__m128 outFinal = _mm_mul_ps(_mm_add_ps(in, _mm_mul_ps(mix, _mm_sub_ps(distDyn, in))), outVol);
|
|
|
|
// store volume for display
|
|
__m128 outAbs = sse2Abs(outFinal);
|
|
_mm_store_ps(&m_outPeakDisplay[0], _mm_max_ps(_mm_load_ps(&m_outPeakDisplay[0]), outAbs));
|
|
|
|
// write updated stuff back into member variables
|
|
_mm_store_ps(&m_inEnv[0], inEnv);
|
|
_mm_store_ps(&m_slewOut[0], slewOut);
|
|
_mm_store_ps(&m_dcOffset[0], dcOffset);
|
|
_mm_store_ps(&m_outEnv[0], outEnv);
|
|
|
|
alignas(16) std::array<float, 4> outArr;
|
|
_mm_store_ps(&outArr[0], outFinal);
|
|
|
|
m_overOuts[0][overSamp] = outArr[0] + outArr[2];
|
|
m_overOuts[1][overSamp] = outArr[1] + outArr[3];
|
|
}
|
|
|
|
std::array<float, 2> s;
|
|
if (oversampleVal > 1)
|
|
{
|
|
s[0] = m_downsampler[0].processSample(m_overOuts[0].data());
|
|
s[1] = m_downsampler[1].processSample(m_overOuts[1].data());
|
|
}
|
|
else
|
|
{
|
|
s[0] = m_overOuts[0][0];
|
|
s[1] = m_overOuts[1][0];
|
|
}
|
|
|
|
buf[f][0] = d * buf[f][0] + w * s[0];
|
|
buf[f][1] = d * buf[f][1] + w * s[1];
|
|
}
|
|
|
|
return ProcessStatus::ContinueIfNotQuiet;
|
|
}
|
|
|
|
|
|
|
|
#else
|
|
Effect::ProcessStatus SlewDistortion::processImpl(SampleFrame* buf, const fpp_t frames)
|
|
{
|
|
const float d = dryLevel();
|
|
const float w = wetLevel();
|
|
|
|
const int oversampling = m_slewdistortionControls.m_oversamplingModel.value();
|
|
const int oversampleVal = 1 << oversampling;
|
|
if (oversampleVal != m_oldOversampleVal)
|
|
{
|
|
m_oldOversampleVal = oversampleVal;
|
|
changeSampleRate();
|
|
}
|
|
|
|
const SlewDistortionType distType1 = static_cast<SlewDistortionType>(m_slewdistortionControls.m_distType1Model.value());
|
|
const SlewDistortionType distType2 = static_cast<SlewDistortionType>(m_slewdistortionControls.m_distType2Model.value());
|
|
const float drive1 = dbfsToAmp(m_slewdistortionControls.m_drive1Model.value());
|
|
const float drive2 = dbfsToAmp(m_slewdistortionControls.m_drive2Model.value());
|
|
const float slewUp1 = dbfsToAmp(m_slewdistortionControls.m_slewUp1Model.value()) / oversampleVal;
|
|
const float slewUp2 = dbfsToAmp(m_slewdistortionControls.m_slewUp2Model.value()) / oversampleVal;
|
|
const float slewDown1 = dbfsToAmp(m_slewdistortionControls.m_slewDown1Model.value()) / oversampleVal;
|
|
const float slewDown2 = dbfsToAmp(m_slewdistortionControls.m_slewDown2Model.value()) / oversampleVal;
|
|
const float bias1 = m_slewdistortionControls.m_bias1Model.value();
|
|
const float bias2 = m_slewdistortionControls.m_bias2Model.value();
|
|
const float warp1 = m_slewdistortionControls.m_warp1Model.value();
|
|
const float warp2 = m_slewdistortionControls.m_warp2Model.value();
|
|
const float crush1 = dbfsToAmp(m_slewdistortionControls.m_crush1Model.value());
|
|
const float crush2 = dbfsToAmp(m_slewdistortionControls.m_crush2Model.value());
|
|
const float attack1 = msToCoeff(m_slewdistortionControls.m_attack1Model.value());
|
|
const float attack2 = msToCoeff(m_slewdistortionControls.m_attack2Model.value());
|
|
const float attackInv1 = 1.f - attack1;
|
|
const float attackInv2 = 1.f - attack2;
|
|
const float release1 = msToCoeff(m_slewdistortionControls.m_release1Model.value());
|
|
const float release2 = msToCoeff(m_slewdistortionControls.m_release2Model.value());
|
|
const float releaseInv1 = 1.f - release1;
|
|
const float releaseInv2 = 1.f - release2;
|
|
const float dynamics1 = m_slewdistortionControls.m_dynamics1Model.value();
|
|
const float dynamics2 = m_slewdistortionControls.m_dynamics2Model.value();
|
|
const float dynamicSlew1 = m_slewdistortionControls.m_dynamicSlew1Model.value();
|
|
const float dynamicSlew2 = m_slewdistortionControls.m_dynamicSlew2Model.value();
|
|
const float outVol1 = dbfsToAmp(m_slewdistortionControls.m_outVol1Model.value());
|
|
const float outVol2 = dbfsToAmp(m_slewdistortionControls.m_outVol2Model.value());
|
|
const float split = m_slewdistortionControls.m_splitModel.value();
|
|
const bool dcRemove = m_slewdistortionControls.m_dcRemoveModel.value();
|
|
const bool multiband = m_slewdistortionControls.m_multibandModel.value();
|
|
const float mix1 = m_slewdistortionControls.m_mix1Model.value();
|
|
const float mix2 = m_slewdistortionControls.m_mix2Model.value();
|
|
const bool slewLink1 = m_slewdistortionControls.m_slewLink1Model.value();
|
|
const bool slewLink2 = m_slewdistortionControls.m_slewLink2Model.value();
|
|
|
|
std::array<float, 4> in = {0};
|
|
std::array<float, 4> out = {0};
|
|
const std::array<float, 4> drive = {drive1, drive1, drive2, drive2};
|
|
const std::array<float, 4> slewUp = {slewUp1, slewUp1, slewUp2, slewUp2};
|
|
const std::array<float, 4> slewDown = {slewDown1, slewDown1, slewDown2, slewDown2};
|
|
const std::array<SlewDistortionType, 4> distType = {distType1, distType1, distType2, distType2};
|
|
const std::array<float, 4> warp = {warp1, warp1, warp2, warp2};
|
|
const std::array<float, 4> crush = {crush1, crush1, crush2, crush2};
|
|
const std::array<float, 4> outVol = {outVol1, outVol1, outVol2, outVol2};
|
|
const std::array<float, 4> attack = {attack1, attack1, attack2, attack2};
|
|
const std::array<float, 4> attackInv = {attackInv1, attackInv1, attackInv2, attackInv2};
|
|
const std::array<float, 4> release = {release1, release1, release2, release2};
|
|
const std::array<float, 4> releaseInv = {releaseInv1, releaseInv1, releaseInv2, releaseInv2};
|
|
const std::array<float, 4> dynamics = {dynamics1, dynamics1, dynamics2, dynamics2};
|
|
const std::array<float, 4> dynamicSlew = {dynamicSlew1, dynamicSlew1, dynamicSlew2, dynamicSlew2};
|
|
const std::array<float, 4> mix = {mix1, mix1, mix2, mix2};
|
|
const std::array<bool, 4> slewLink = {slewLink1, slewLink1, slewLink2, slewLink2};
|
|
|
|
if (m_slewdistortionControls.m_splitModel.isValueChanged())
|
|
{
|
|
m_lp.setLowpass(split);
|
|
m_hp.setHighpass(split);
|
|
}
|
|
|
|
for (fpp_t f = 0; f < frames; ++f)
|
|
{
|
|
// interpolate bias to remove crackling when moving the parameter
|
|
m_trueBias1 = m_biasInterpCoef * m_trueBias1 + (1.f - m_biasInterpCoef) * bias1;
|
|
m_trueBias2 = m_biasInterpCoef * m_trueBias2 + (1.f - m_biasInterpCoef) * bias2;
|
|
const std::array<float, 4> bias = {m_trueBias1, m_trueBias1, m_trueBias2, m_trueBias2};
|
|
|
|
if (oversampleVal > 1)
|
|
{
|
|
m_upsampler[0].processSample(m_overOuts[0].data(), buf[f][0]);
|
|
m_upsampler[1].processSample(m_overOuts[1].data(), buf[f][1]);
|
|
}
|
|
else
|
|
{
|
|
m_overOuts[0][0] = buf[f][0];
|
|
m_overOuts[1][0] = buf[f][1];
|
|
}
|
|
|
|
for (int overSamp = 0; overSamp < oversampleVal; ++overSamp)
|
|
{
|
|
if (multiband)
|
|
{
|
|
in[0] = m_hp.update(m_overOuts[0][overSamp], 0);
|
|
in[1] = m_hp.update(m_overOuts[1][overSamp], 1);
|
|
in[2] = m_lp.update(m_overOuts[0][overSamp], 0);
|
|
in[3] = m_lp.update(m_overOuts[1][overSamp], 1);
|
|
}
|
|
else
|
|
{
|
|
in[0] = m_overOuts[0][overSamp];
|
|
in[1] = m_overOuts[1][overSamp];
|
|
in[2] = 0;
|
|
in[3] = 0;
|
|
}
|
|
|
|
m_inPeakDisplay[0] = std::max(m_inPeakDisplay[0], std::abs(in[0] * drive[0]));
|
|
m_inPeakDisplay[1] = std::max(m_inPeakDisplay[1], std::abs(in[1] * drive[1]));
|
|
m_inPeakDisplay[2] = std::max(m_inPeakDisplay[2], std::abs(in[2] * drive[2]));
|
|
m_inPeakDisplay[3] = std::max(m_inPeakDisplay[3], std::abs(in[3] * drive[3]));
|
|
|
|
for (int i = 0; i < 4 - !multiband * 2; ++i) {
|
|
const float absIn = std::abs(in[i]);
|
|
m_inEnv[i] = absIn > m_inEnv[i] ? m_inEnv[i] * attack[i] + absIn * attackInv[i] : m_inEnv[i] * release[i] + absIn * releaseInv[i];
|
|
m_inEnv[i] = std::max(m_inEnv[i], SLEW_DISTORTION_MIN_FLOOR);
|
|
|
|
float rate = in[i] - m_slewOut[i];
|
|
float slewMult = dynamicSlew[i] ? std::pow(m_inEnv[i], dynamicSlew[i]) : 1.f;
|
|
const float trueSlew = ((rate >= 0 || slewLink[i]) ? slewUp[i] : slewDown[i]) * slewMult;
|
|
rate = std::clamp(rate, -trueSlew, trueSlew);
|
|
m_slewOut[i] = m_slewOut[i] + rate;
|
|
|
|
float biasedIn = m_slewOut[i] * drive[i] + bias[i];
|
|
float distIn = (biasedIn - std::copysign(warp[i] / crush[i], biasedIn)) / (1.f - warp[i]);
|
|
float distOut;
|
|
switch (static_cast<SlewDistortionType>(distType[i]))
|
|
{
|
|
case SlewDistortionType::HardClip: {
|
|
distOut = std::clamp(distIn, -1.f, 1.f);
|
|
break;
|
|
}
|
|
case SlewDistortionType::Tanh: {
|
|
const float temp = std::clamp(distIn, -40.f, 40.f);
|
|
distOut = 2.f / (1.f + std::exp(-2.f * temp)) - 1;
|
|
break;
|
|
}
|
|
case SlewDistortionType::FastSoftClip1: {
|
|
const float temp = std::clamp(distIn, -2.f, 2.f);
|
|
distOut = temp / (1 + 0.25f * temp * temp);
|
|
break;
|
|
}
|
|
case SlewDistortionType::FastSoftClip2: {
|
|
const float temp = std::clamp(distIn, -1.5f, 1.5f);
|
|
distOut = temp - (4.f / 27.f) * temp * temp * temp;
|
|
break;
|
|
}
|
|
case SlewDistortionType::Sinusoidal: {
|
|
// using a polynomial approximation so it matches with the SSE2 code
|
|
// x - x^3 / 6 + x^5 / 120
|
|
float modInput = std::fmod(distIn - std::numbers::pi_v<float> * 0.5f, 2.f * std::numbers::pi_v<float>);
|
|
if (modInput < 0) {modInput += 2.f * std::numbers::pi_v<float>;}
|
|
const float x = std::abs(modInput - std::numbers::pi_v<float>) - std::numbers::pi_v<float> * 0.5f;
|
|
const float x2 = x * x;
|
|
const float x3 = x2 * x;
|
|
const float x5 = x3 * x2;
|
|
distOut = x - (x3 / 6.0f) + (x5 / 120.0f);
|
|
break;
|
|
}
|
|
case SlewDistortionType::Foldback: {
|
|
distOut = std::abs(std::abs(std::fmod(distIn - 1.f, 4.f)) - 2.f) - 1.f;
|
|
break;
|
|
}
|
|
case SlewDistortionType::FullRectify: {
|
|
distOut = std::abs(distIn);
|
|
break;
|
|
}
|
|
case SlewDistortionType::SmoothRectify:
|
|
{
|
|
distOut = std::sqrt(distIn * distIn + 0.04f) - 0.2f;
|
|
break;
|
|
}
|
|
case SlewDistortionType::HalfRectify:
|
|
{
|
|
distOut = std::max(0.0f, distIn);
|
|
break;
|
|
}
|
|
case SlewDistortionType::Bitcrush:
|
|
{
|
|
const float scale = 16 / drive[i];
|
|
distOut = std::round(distIn / drive[i] * scale) / scale;
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
distOut = distIn;
|
|
}
|
|
}
|
|
distOut = distOut * (1.f - warp[i]) + std::copysign(warp[i], biasedIn);
|
|
if (std::abs(biasedIn) < warp[i] / crush[i]) {distOut = biasedIn * crush[i];}
|
|
|
|
m_dcOffset[i] = m_dcOffset[i] * m_dcCoeff + distOut * (1.f - m_dcCoeff);
|
|
|
|
// even with DC offset removal disabled, we should still apply it for the envelope follower
|
|
const float absOut = std::abs(distOut - m_dcOffset[i]);
|
|
m_outEnv[i] = absOut > m_outEnv[i] ? m_outEnv[i] * attack[i] + absOut * attackInv[i] : m_outEnv[i] * release[i] + absOut * releaseInv[i];
|
|
m_outEnv[i] = std::max(m_outEnv[i], SLEW_DISTORTION_MIN_FLOOR);
|
|
|
|
if (dcRemove) { distOut -= m_dcOffset[i]; }
|
|
|
|
distOut *= std::lerp(1.f, m_inEnv[i] / m_outEnv[i], dynamics[i]);
|
|
|
|
out[i] = std::lerp(in[i], distOut, mix[i]) * outVol[i];
|
|
}
|
|
|
|
m_outPeakDisplay[0] = std::max(m_outPeakDisplay[0], std::abs(out[0]));
|
|
m_outPeakDisplay[1] = std::max(m_outPeakDisplay[1], std::abs(out[1]));
|
|
m_outPeakDisplay[2] = std::max(m_outPeakDisplay[2], std::abs(out[2]));
|
|
m_outPeakDisplay[3] = std::max(m_outPeakDisplay[3], std::abs(out[3]));
|
|
|
|
m_overOuts[0][overSamp] = out[0] + out[2];
|
|
m_overOuts[1][overSamp] = out[1] + out[3];
|
|
}
|
|
|
|
std::array<float, 2> s;
|
|
if (oversampleVal > 1)
|
|
{
|
|
s[0] = m_downsampler[0].processSample(m_overOuts[0].data());
|
|
s[1] = m_downsampler[1].processSample(m_overOuts[1].data());
|
|
}
|
|
else
|
|
{
|
|
s[0] = m_overOuts[0][0];
|
|
s[1] = m_overOuts[1][0];
|
|
}
|
|
|
|
buf[f][0] = d * buf[f][0] + w * s[0];
|
|
buf[f][1] = d * buf[f][1] + w * s[1];
|
|
}
|
|
|
|
return ProcessStatus::ContinueIfNotQuiet;
|
|
}
|
|
#endif
|
|
|
|
void SlewDistortion::changeSampleRate()
|
|
{
|
|
m_sampleRate = Engine::audioEngine()->outputSampleRate();
|
|
const int oversampleStages = m_slewdistortionControls.m_oversamplingModel.value();
|
|
const int oversampleVal = 1 << oversampleStages;
|
|
float sampleRateOver = m_sampleRate * oversampleVal;
|
|
|
|
for (int i = 0; i < 2; ++i)
|
|
{
|
|
m_upsampler[i].setup(oversampleStages, m_sampleRate);
|
|
m_downsampler[i].setup(oversampleStages, m_sampleRate);
|
|
}
|
|
|
|
m_lp.setSampleRate(sampleRateOver);
|
|
m_lp.setLowpass(m_slewdistortionControls.m_splitModel.value());
|
|
m_lp.clearHistory();
|
|
|
|
m_hp.setSampleRate(sampleRateOver);
|
|
m_hp.setHighpass(m_slewdistortionControls.m_splitModel.value());
|
|
m_hp.clearHistory();
|
|
|
|
m_coeffPrecalc = -1.f / (sampleRateOver * 0.001f);
|
|
|
|
m_dcCoeff = std::exp(-2.f * std::numbers::pi_v<float> * SLEW_DISTORTION_DC_FREQ / sampleRateOver);
|
|
|
|
std::fill(std::begin(m_inPeakDisplay), std::end(m_inPeakDisplay), 0.0f);
|
|
std::fill(std::begin(m_slewOut), std::end(m_slewOut), 0.0f);
|
|
std::fill(std::begin(m_dcOffset), std::end(m_dcOffset), 0.0f);
|
|
std::fill(std::begin(m_inEnv), std::end(m_inEnv), 0.0f);
|
|
std::fill(std::begin(m_outEnv), std::end(m_outEnv), 0.0f);
|
|
std::fill(std::begin(m_outPeakDisplay), std::end(m_outPeakDisplay), 0.0f);
|
|
for (auto& subArray : m_overOuts) {std::fill(subArray.begin(), subArray.end(), 0.0f);}
|
|
|
|
m_biasInterpCoef = std::exp(-1 / (0.01f * m_sampleRate));
|
|
}
|
|
|
|
|
|
extern "C"
|
|
{
|
|
// necessary for getting instance out of shared lib
|
|
PLUGIN_EXPORT Plugin* lmms_plugin_main(Model* parent, void* data)
|
|
{
|
|
return new SlewDistortion(parent, static_cast<const Plugin::Descriptor::SubPluginFeatures::Key*>(data));
|
|
}
|
|
}
|
|
|
|
} // namespace lmms
|