Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add SlewDistortion effect and oversampling support #7641

Open
wants to merge 17 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion plugins/SlewDistortion/SlewDistortion.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,612 +59,612 @@
}


#ifdef __SSE2__
#ifdef JNOAINHPIAHJP__SSE2__
LostRobotMusic marked this conversation as resolved.
Show resolved Hide resolved
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 int distType1 = m_slewdistortionControls.m_distType1Model.value();
const int distType2 = 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].process_sample(m_overOuts[0].data(), buf[f][0]);
m_upsampler[1].process_sample(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 = _mm_and_ps(in, _mm_castsi128_ps(_mm_set1_epi32(0x7FFFFFFF)));

// 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(&this->m_inEnv[0]);
__m128 slewOut = _mm_load_ps(&this->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, fast_log_sse(inEnv));
// clamp to [-87.0f, 87.0f] since fast_exp_sse breaks outside of those bounds
__m128 clampedScaledLog = _mm_max_ps(_mm_min_ps(scaledLog, _mm_set1_ps(87.0f)), _mm_set1_ps(-87.0f));
__m128 slewMult = fast_exp_sse(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 - 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)
{
int 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 0:// 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 1: // Tanh => 2 / (1 + exp(-2x)) - 1
{
// clamp to [-87.0f, 87.0f] since fast_exp_sse 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(87.0f)), _mm_set1_ps(-87.0f));
__m128 expResult = fast_exp_sse(clampedInput);
distOutFull = _mm_sub_ps(_mm_div_ps(_mm_set1_ps(2.0f), _mm_add_ps(one, expResult)), one);
break;
}
case 2: // 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 3: // 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 4: // Sinusoidal => sin(x)
{
// SSE2 sine approximation I created
__m128 pi = _mm_set1_ps(3.14159265358979323846f);
__m128 piOverTwo = _mm_set1_ps(1.57079632679489661923f);
__m128 tau = _mm_set1_ps(6.28318530717958647692f);

__m128 distMinusPiOverTwo = _mm_sub_ps(distInFull, piOverTwo);
__m128 divByTwoPi = _mm_div_ps(distMinusPiOverTwo, tau);

// SSE2 floor replacement
__m128 trunc = _mm_cvtepi32_ps(_mm_cvttps_epi32(divByTwoPi));
__m128 floorDivByTwoPi = _mm_sub_ps(trunc, _mm_and_ps(_mm_cmplt_ps(divByTwoPi, trunc), one));

// 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(_mm_andnot_ps(_mm_set1_ps(-0.0f), _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 5: // 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 trunc = _mm_cvtepi32_ps(_mm_cvttps_epi32(divByFour));
__m128 correction = _mm_and_ps(_mm_cmplt_ps(divByFour, trunc), one);
__m128 floorOverFour = _mm_sub_ps(trunc, correction);

distOutFull = _mm_sub_ps(_mm_andnot_ps(_mm_set1_ps(-0.0f), _mm_sub_ps(_mm_sub_ps(
distInMinusOne, _mm_mul_ps(floorOverFour, four)), _mm_set1_ps(2.0f))), one);
break;
}
case 6: // Full-wave Rectify => |x|
{
distOutFull = _mm_and_ps(distInFull, _mm_castsi128_ps(_mm_set1_epi32(0x7FFFFFFF)));
break;
}
case 7: // Smooth Rectify => 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 8: // Half-wave Rectify => max(0, x)
{
distOutFull = _mm_max_ps(_mm_setzero_ps(), distInFull);
break;
}
case 9: // 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 integer
__m128 signMask = _mm_cmplt_ps(scaledVal, zero);
__m128 half = _mm_set1_ps(0.5f);
__m128 addVal = _mm_or_ps(_mm_andnot_ps(signMask, half), _mm_and_ps(signMask, _mm_sub_ps(zero, half)));
__m128 rounded = _mm_cvtepi32_ps(_mm_cvttps_epi32(_mm_add_ps(scaledVal, addVal)));

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((__m64*)&distOutArr[0], distOutFull);
}
else
{
// for elements 2 and 3
_mm_storeh_pi((__m64*)&distOutArr[2], distOutFull);
}
}
}

__m128 distOut = _mm_load_ps(&distOutArr[0]);

// (1 - warp) * distOut + 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 = _mm_and_ps(biasedIn, _mm_castsi128_ps(_mm_set1_epi32(0x7FFFFFFF)));
__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(&this->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(&this->m_outEnv[0]);
__m128 absOut = _mm_and_ps(distOutMinusDC, _mm_castsi128_ps(_mm_set1_epi32(0x7FFFFFFF)));

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 = _mm_and_ps(outFinal, _mm_castsi128_ps(_mm_set1_epi32(0x7FFFFFFF)));
_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(&this->m_inEnv[0], inEnv);
_mm_store_ps(&this->m_slewOut[0], slewOut);
_mm_store_ps(&this->m_dcOffset[0], dcOffset);
_mm_store_ps(&this->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].process_sample(m_overOuts[0].data());
s[1] = m_downsampler[1].process_sample(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 int distType1 = m_slewdistortionControls.m_distType1Model.value();
const int distType2 = 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};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm curious why these variables are stored in arrays of length 4, if the first two and last two are the same. Is it to match the way SSE2 works with vectors of 4 floats at a time?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. This obviously isn't the way you'd want to do things normally, but I was writing the code with the intention of converting it over to SSE2 from day 1. This non-SSE2 code won't ever run for over 99.99% of people, so it's best to essentially treat it as a giant comment, so you can see what the SSE2 code is achieving if you don't understand a certain part of it.

const std::array<float, 4> slewUp = {slewUp1, slewUp1, slewUp2, slewUp2};
const std::array<float, 4> slewDown = {slewDown1, slewDown1, slewDown2, slewDown2};
const std::array<int, 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].process_sample(m_overOuts[0].data(), buf[f][0]);
m_upsampler[1].process_sample(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 - copysign(warp[i] / crush[i], biasedIn)) / (1.f - warp[i]);
float distOut;
switch(distType[i]) {
case 0: {// hard clip
distOut = std::clamp(distIn, -1.f, 1.f);
break;
}
case 1: {// tanh
distOut = 2.f / (1.f + std::exp(-2.f * distIn)) - 1;
break;
}
case 2: {// fast soft clip 1
const float temp = std::clamp(distIn, -2.f, 2.f);
distOut = temp / (1 + 0.25f * temp * temp);
break;
}
case 3: {// fast soft clip 2
const float temp = std::clamp(distIn, -1.5f, 1.5f);
distOut = temp - (4.f / 27.f) * temp * temp * temp;
break;
}
case 4: { // sinusodal
// using a polynomial approximation so it matches with the SSE2 code
// x - x^3 / 6 + x^5 / 120
float modInput = std::fmod(distIn - F_PI * 0.5f, 2.f * F_PI);
if (modInput < 0) {modInput += 2.f * F_PI;}
const float x = std::abs(modInput - F_PI) - F_PI * 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 5: {// foldback distortion
distOut = std::abs(std::abs(std::fmod(distIn - 1.f, 4.f)) - 2.f) - 1.f;
break;
}
case 6: {// rectify
distOut = std::abs(distIn);
break;
}
case 7: // smooth rectify
{
distOut = std::sqrt(distIn * distIn + 0.04f) - 0.2f;
break;
}
case 8: // half-wave rectify
{
distOut = std::max(0.0f, distIn);
break;
}
case 9: // 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]) + 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 *= linearInterpolate(1.f, m_inEnv[i] / m_outEnv[i], dynamics[i]);

out[i] = linearInterpolate(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].process_sample(m_overOuts[0].data());
s[1] = m_downsampler[1].process_sample(m_overOuts[1].data());
}
else
{
s[0] = m_overOuts[0][0];
s[1] = m_overOuts[1][0];
}

Check notice on line 667 in plugins/SlewDistortion/SlewDistortion.cpp

View check run for this annotation

codefactor.io / CodeFactor

plugins/SlewDistortion/SlewDistortion.cpp#L64-L667

Complex Method

buf[f][0] = d * buf[f][0] + w * s[0];
buf[f][1] = d * buf[f][1] + w * s[1];
Expand Down
4 changes: 2 additions & 2 deletions plugins/SlewDistortion/SlewDistortionControlDialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -82,12 +82,12 @@
};

ComboBox* distType1Box = new ComboBox(this);
distType1Box->setGeometry(85, 14, 115, 22);
distType1Box->setGeometry(85, 26, 115, 22);
//distType1Box->setFont(pointSize<8>(distType1Box->font()));
distType1Box->setModel(&controls->m_distType1Model);

ComboBox* distType2Box = new ComboBox(this);
distType2Box->setGeometry(85, 135, 115, 22);
distType2Box->setGeometry(85, 147, 115, 22);
//distType2Box->setFont(pointSize<8>(distType2Box->font()));
distType2Box->setModel(&controls->m_distType2Model);

Expand Down Expand Up @@ -186,194 +186,194 @@
update();
}

void SlewDistortionControlDialog::paintEvent(QPaintEvent *event)
{
if (!isVisible())
{
return;
}

QPainter p(this);
p.setRenderHint(QPainter::Antialiasing);

QRect inMeters[] = { {22, 31, 8, 75}, {30, 31, 8, 75}, {22, 152, 8, 75}, {30, 152, 8, 75} };
QRect outMeters[] = { {600, 31, 8, 75}, {608, 31, 8, 75}, {600, 152, 8, 75}, {608, 152, 8, 75} };

float* inPeak = &m_controls->m_effect->m_inPeakDisplay[0];
float* outPeak = &m_controls->m_effect->m_outPeakDisplay[0];

for (int i = 0; i < 4; ++i)
{
m_lastInPeaks[i] = std::max((inPeak[i] != -1.0f) ? inPeak[i] : m_lastInPeaks[i], SLEW_DISTORTION_MIN_FLOOR);
m_lastOutPeaks[i] = std::max((outPeak[i] != -1.0f) ? outPeak[i] : m_lastOutPeaks[i], SLEW_DISTORTION_MIN_FLOOR);
inPeak[i] = outPeak[i] = -1.0f;
}

auto drawInverseMeters = [&p](const QRect meters[], const float values[], QColor coverColor)
{
const float dbfsMin = -24.0f;
const float dbfsMax = 24.0f;

for (int i = 0; i < 4; ++i)
{
float valueDbfs = ampToDbfs(values[i]);

float normalizedValue = (valueDbfs - dbfsMin) / (dbfsMax - dbfsMin);
normalizedValue = std::clamp(normalizedValue, 0.0f, 1.0f);

int coveredHeight = static_cast<int>(meters[i].height() * (1.0f - normalizedValue));
QRect coveredRect(meters[i].left(), meters[i].top(), meters[i].width(), coveredHeight);

p.fillRect(coveredRect, coverColor);
}
};

drawInverseMeters(inMeters, &m_lastInPeaks[0], QColor(10, 10, 10));
drawInverseMeters(outMeters, &m_lastOutPeaks[0], QColor(10, 10, 10));

QRect curveRect1(452, 10, 100, 100);
QRect curveRect2(452, 131, 100, 100);

QPen gridPen(QColor(36, 40, 48));
gridPen.setStyle(Qt::DotLine);
p.setPen(gridPen);

auto drawGrid = [&p](const QRect& rect)
{
for (int i = 1; i < 8; ++i)
{
int x = rect.left() + i * rect.width() / 8 + 1;
p.drawLine(x, rect.top() + 1, x, rect.bottom());

int y = rect.top() + i * rect.height() / 8 + 1;
p.drawLine(rect.left() + 1, y, rect.right(), y);
}
};

drawGrid(curveRect1);
drawGrid(curveRect2);

QPen axisPen(QColor(62, 66, 75));
axisPen.setWidth(2);
p.setPen(axisPen);

auto drawAxes = [&p](const QRect& rect)
{
p.drawLine(rect.center().x() + 2, rect.top() + 1, rect.center().x() + 2, rect.bottom());
p.drawLine(rect.left() + 1, rect.center().y() + 2, rect.right(), rect.center().y() + 2);
};

drawAxes(curveRect1);
drawAxes(curveRect2);

auto drawCurve = [&](const QRect& rect, int band)
{
QVector<QPointF> points;

QPen curvePen(QColor(34, 226, 108));
curvePen.setWidth(2);
p.setPen(curvePen);

const int distType = band == 0 ? m_controls->m_distType1Model.value() : m_controls->m_distType2Model.value();
const float drive = dbfsToAmp(band == 0 ? m_controls->m_drive1Model.value() : m_controls->m_drive2Model.value());
const float bias = band == 0 ? m_controls->m_bias1Model.value() : m_controls->m_bias2Model.value();
const float warp = band == 0 ? m_controls->m_warp1Model.value() : m_controls->m_warp2Model.value();
const float crush = dbfsToAmp(band == 0 ? m_controls->m_crush1Model.value() : m_controls->m_crush2Model.value());

const float halfLineWidth = curvePen.widthF() / 2.0f;
const float amplitudeScale = (rect.height() - curvePen.widthF()) / rect.height();

const int numSteps = curveRect1.width() * 2;
for (int i = 0; i <= numSteps; ++i)
{
float x = -1.0f + 2.0f * i / numSteps;

float biasedIn = x * drive + bias;
float distIn = (biasedIn - copysign(warp / crush, biasedIn)) / (1.0f - warp);
float distOut;

switch (distType)
{
case 0: {// hard clip
distOut = std::clamp(distIn, -1.f, 1.f);
break;
}
case 1: {// tanh
distOut = 2.f / (1.f + std::exp(-2.f * distIn)) - 1;
break;
}
case 2: {// fast soft clip 1
const float temp = std::clamp(distIn, -2.f, 2.f);
distOut = temp / (1 + 0.25f * temp * temp);
break;
}
case 3: {// fast soft clip 2
const float temp = std::clamp(distIn, -1.5f, 1.5f);
distOut = temp - (4.f / 27.f) * temp * temp * temp;
break;
}
case 4: {// sinusoidal
distOut = std::sin(distIn);
break;
}
case 5: {// foldback distortion
distOut = std::abs(std::abs(std::fmod(distIn - 1.f, 4.f)) - 2.f) - 1.f;
break;
}
case 6: {// rectify
distOut = std::abs(distIn);
break;
}
case 7: // half-wave rectify
{
distOut = std::max(0.0f, distIn);
break;
}
case 8: // smooth rectify
{
distOut = std::sqrt(distIn * distIn + 0.04f) - 0.2f;
break;
}
case 9: // bitcrush
{
const float scale = 16 / drive;
distOut = std::round(distIn / drive * scale) / scale;
break;
}
default:
{
distOut = distIn;
}
}

distOut = distOut * (1.0f - warp) + copysign(warp, biasedIn);
if (std::abs(biasedIn) < warp / crush)
{
distOut = biasedIn * crush;
}

distOut *= amplitudeScale;

float px = rect.left() + (x + 1.f) * 0.5f * rect.width();
float py = rect.bottom() - (distOut + 1.f) * 0.5f * rect.height();

py += halfLineWidth;

points.append(QPointF(px, py));
}

QPainterPath path;
path.addPolygon(QPolygonF(points));
p.save();
p.setClipRect(rect);
p.drawPath(path);
p.restore();
};

drawCurve(curveRect1, 0);
drawCurve(curveRect2, 1);
}

Check notice on line 376 in plugins/SlewDistortion/SlewDistortionControlDialog.cpp

View check run for this annotation

codefactor.io / CodeFactor

plugins/SlewDistortion/SlewDistortionControlDialog.cpp#L189-L376

Complex Method
void SlewDistortionControlDialog::showHelpWindow()
{
SlewDistortionHelpView::getInstance()->close();
Expand Down
Binary file modified plugins/SlewDistortion/artwork.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading