mirror of
https://github.com/LMMS/lmms.git
synced 2026-05-09 23:35:33 -04:00
* Remove fpp_t, switch f_cnt_t to std::uint64_t, change uses of fpp_t to use f_cnt_t instead * Switch release_frame in Lb302 to use f_cnt_t * Switch ch_cnt_t to uint16_t * Update src/core/NotePlayHandle.cpp Co-authored-by: Dalton Messmer <messmer.dalton@gmail.com> * Update src/core/NotePlayHandle.cpp Co-authored-by: Dalton Messmer <messmer.dalton@gmail.com> --------- Co-authored-by: Dalton Messmer <messmer.dalton@gmail.com>
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 f_cnt_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 (f_cnt_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 f_cnt_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 (f_cnt_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
|