diff --git a/cmake/modules/PluginList.cmake b/cmake/modules/PluginList.cmake
index 009679533ae..7a6b266cf34 100644
--- a/cmake/modules/PluginList.cmake
+++ b/cmake/modules/PluginList.cmake
@@ -61,6 +61,7 @@ SET(LMMS_PLUGIN_LIST
 	Sf2Player
 	Sfxr
 	Sid
+	SlewDistortion
 	SlicerT
 	SpectrumAnalyzer
 	StereoEnhancer
diff --git a/include/Draggable.h b/include/Draggable.h
new file mode 100644
index 00000000000..d81f5dcf29c
--- /dev/null
+++ b/include/Draggable.h
@@ -0,0 +1,63 @@
+/*
+ * Draggable.h
+ *
+ * Copyright (c) 2022 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.
+ *
+ */
+ 
+#ifndef DRAGGABLE_H
+#define DRAGGABLE_H
+
+#include "FloatModelEditorBase.h"
+
+namespace lmms::gui
+{
+
+class LMMS_EXPORT Draggable : public FloatModelEditorBase
+{
+	Q_OBJECT
+
+public:
+	Draggable(FloatModelEditorBase::DirectionOfManipulation directionOfManipulation,
+		FloatModel* floatModel, const QPixmap &pixmap, int pointA, int pointB, QWidget* parent = nullptr);
+
+	QSize sizeHint() const override;
+	void setPixmap(const QPixmap &pixmap);
+	void setDefaultValPixmap(const QPixmap &pixmap, float value = 0.f);
+
+protected:
+	void paintEvent(QPaintEvent *event) override;
+	void mouseMoveEvent(QMouseEvent *me) override;
+
+protected slots:
+	void handleMovement();
+
+private:
+	QPixmap m_pixmap;
+	QPixmap m_defaultValPixmap;
+	float m_pointA;
+	float m_pointB;
+	float m_defaultValue;
+	bool m_hasDefaultValPixmap;
+};
+
+} // namespace lmms::gui
+
+#endif
diff --git a/include/FloatModelEditorBase.h b/include/FloatModelEditorBase.h
index 72f1450de5a..458459c9fdb 100644
--- a/include/FloatModelEditorBase.h
+++ b/include/FloatModelEditorBase.h
@@ -85,13 +85,6 @@ class LMMS_EXPORT FloatModelEditorBase : public QWidget, public FloatModelView
 	void leaveEvent(QEvent *event) override;
 
 	virtual float getValue(const QPoint & p);
-
-private slots:
-	virtual void enterValue();
-	void friendlyUpdate();
-	void toggleScale();
-
-private:
 	virtual QString displayValue() const;
 
 	void doConnections() override;
@@ -114,6 +107,11 @@ private slots:
 	bool m_buttonPressed;
 
 	DirectionOfManipulation m_directionOfManipulation;
+
+private slots:
+	virtual void enterValue();
+	void friendlyUpdate();
+	void toggleScale();
 };
 
 } // namespace lmms::gui
diff --git a/include/OversamplingHelpers.h b/include/OversamplingHelpers.h
new file mode 100755
index 00000000000..79fa99e195b
--- /dev/null
+++ b/include/OversamplingHelpers.h
@@ -0,0 +1,226 @@
+/*
+ * OversamplingHelpers.h
+ *
+ * Copyright (c) 2023 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.
+ *
+ */
+
+#ifndef OVERSAMPLING_HELPERS_H
+#define OVERSAMPLING_HELPERS_H
+
+#include <algorithm>
+#include <array>
+
+#include "hiir/PolyphaseIir2Designer.h"
+#include "hiir/Downsampler2xFpu.h"
+#include "hiir/Upsampler2xFpu.h"
+
+constexpr float HIIR_DEFAULT_PASSBAND = 19600;
+constexpr int HIIR_DEFAULT_MAX_COEFS = 8;
+
+namespace lmms
+{
+
+template<int MaxStages, int MaxCoefs = 8>
+class Upsampler
+{
+public:
+	void reset()
+	{
+		float bw = 0.5f - m_passband / m_sampleRate;
+
+		// Stage 1
+		double coefsFirst[MaxCoefs];
+		hiir::PolyphaseIir2Designer::compute_coefs_spec_order_tbw(coefsFirst, MaxCoefs, bw);
+		m_upsampleFirst.set_coefs(coefsFirst);
+		m_upsampleFirst.clear_buffers();
+		bw = (bw + 0.5f) * 0.5f;
+
+		// Stage 2
+		constexpr int secondCoefCount = std::max(MaxCoefs / 2, 2);
+		double coefsSecond[secondCoefCount];
+		hiir::PolyphaseIir2Designer::compute_coefs_spec_order_tbw(coefsSecond, secondCoefCount, bw);
+		m_upsampleSecond.set_coefs(coefsSecond);
+		m_upsampleSecond.clear_buffers();
+		bw = (bw + 0.5f) * 0.5f;
+
+		// Remaining stages
+		constexpr int restCoefCount = std::max(MaxCoefs / 4, 2);
+		for (int i = 0; i < m_stages - 2; ++i)
+		{
+			double coefsRest[restCoefCount];
+			hiir::PolyphaseIir2Designer::compute_coefs_spec_order_tbw(coefsRest, restCoefCount, bw);
+			m_upsampleRest[i].set_coefs(coefsRest);
+			m_upsampleRest[i].clear_buffers();
+			bw = (bw + 0.5f) * 0.5f;
+		}
+	}
+
+	void setup(int stages, float sampleRate, float passband = HIIR_DEFAULT_PASSBAND)
+	{
+		assert(stages <= MaxStages);
+		m_stages = stages;
+		m_sampleRate = sampleRate;
+		m_passband = passband;
+		reset();
+	}
+
+	void process_sample(float* outSamples, float inSample)
+	{
+		int total = 1 << m_stages;
+		outSamples[0] = inSample;
+		int gap1 = total / 2;
+
+		if (m_stages >= 1)
+		{
+			m_upsampleFirst.process_sample(outSamples[0], outSamples[gap1], outSamples[0]);
+		}
+
+		if (m_stages >= 2)
+		{
+			int gap2 = gap1 / 2;
+			m_upsampleSecond.process_sample(outSamples[0], outSamples[gap2], outSamples[0]);
+			m_upsampleSecond.process_sample(outSamples[gap1], outSamples[gap1 + gap2], outSamples[gap1]);
+		}
+
+		for (int i = 2; i < m_stages; ++i)
+		{
+			int count = 1 << i;
+			int gap = total / count;
+			for (int j = 0; j < count; ++j)
+			{
+				int temp = j * gap;
+				m_upsampleRest[i - 2].process_sample(outSamples[temp], outSamples[temp + gap / 2], outSamples[temp]);
+			}
+		}
+	}
+
+	int getStages() const { return m_stages; }
+	float getSampleRate() const { return m_sampleRate; }
+	float getPassband() const { return m_passband; }
+
+	void setStages(int stages) { m_stages = stages; reset(); }
+	void setSampleRate(float sampleRate) { m_sampleRate = sampleRate; reset(); }
+	void setPassband(float passband) { m_passband = passband; reset(); }
+
+private:
+	hiir::Upsampler2xFpu<MaxCoefs> m_upsampleFirst;
+	hiir::Upsampler2xFpu<std::max(MaxCoefs / 2, 2)> m_upsampleSecond;
+	std::array<hiir::Upsampler2xFpu<std::max(MaxCoefs / 4, 2)>, MaxStages - 2> m_upsampleRest;
+
+	int m_stages = 0;
+	float m_sampleRate = 44100;
+	float m_passband = HIIR_DEFAULT_PASSBAND;
+};
+
+
+template<int MaxStages, int MaxCoefs = 8>
+class Downsampler
+{
+public:
+	void reset()
+	{
+		float bw = 0.5f - m_passband / m_sampleRate;
+
+		// Stage 1
+		double coefsFirst[MaxCoefs];
+		hiir::PolyphaseIir2Designer::compute_coefs_spec_order_tbw(coefsFirst, MaxCoefs, bw);
+		m_downsampleFirst.set_coefs(coefsFirst);
+		m_downsampleFirst.clear_buffers();
+		bw = (bw + 0.5f) * 0.5f;
+
+		// Stage 2
+		constexpr int secondCoefCount = std::max(MaxCoefs / 2, 2);
+		double coefsSecond[secondCoefCount];
+		hiir::PolyphaseIir2Designer::compute_coefs_spec_order_tbw(coefsSecond, secondCoefCount, bw);
+		m_downsampleSecond.set_coefs(coefsSecond);
+		m_downsampleSecond.clear_buffers();
+		bw = (bw + 0.5f) * 0.5f;
+
+		// Remaining stages
+		constexpr int restCoefCount = std::max(MaxCoefs / 4, 2);
+		for (int i = 0; i < m_stages - 2; ++i)
+		{
+			double coefsRest[restCoefCount];
+			hiir::PolyphaseIir2Designer::compute_coefs_spec_order_tbw(coefsRest, restCoefCount, bw);
+			m_downsampleRest[i].set_coefs(coefsRest);
+			m_downsampleRest[i].clear_buffers();
+			bw = (bw + 0.5f) * 0.5f;
+		}
+	}
+
+	void setup(int stages, float sampleRate, float passband = HIIR_DEFAULT_PASSBAND)
+	{
+		assert(stages <= MaxStages);
+		m_stages = stages;
+		m_sampleRate = sampleRate;
+		m_passband = passband;
+		reset();
+	}
+
+	float process_sample(float* inSamples)
+	{
+		for (int i = m_stages - 1; i >= 2; --i)
+		{
+			for (int j = 0; j < 1 << i; ++j)
+			{
+				inSamples[j] = m_downsampleRest[i - 2].process_sample(&inSamples[j * 2]);
+			}
+		}
+
+		if (m_stages >= 2)
+		{
+			for (int j = 0; j < 2; ++j)
+			{
+				inSamples[j] = m_downsampleSecond.process_sample(&inSamples[j * 2]);
+			}
+		}
+
+		if (m_stages >= 1)
+		{
+			inSamples[0] = m_downsampleFirst.process_sample(&inSamples[0]);
+		}
+
+		return inSamples[0];
+	}
+
+	int getStages() const { return m_stages; }
+	float getSampleRate() const { return m_sampleRate; }
+	float getPassband() const { return m_passband; }
+
+	void setStages(int stages) { m_stages = stages; reset(); }
+	void setSampleRate(float sampleRate) { m_sampleRate = sampleRate; reset(); }
+	void setPassband(float passband) { m_passband = passband; reset(); }
+
+private:
+	hiir::Downsampler2xFpu<MaxCoefs> m_downsampleFirst;
+	hiir::Downsampler2xFpu<std::max(MaxCoefs / 2, 2)> m_downsampleSecond;
+	std::array<hiir::Downsampler2xFpu<std::max(MaxCoefs / 4, 2)>, MaxStages - 2> m_downsampleRest;
+
+	int m_stages = 0;
+	float m_sampleRate = 44100;
+	float m_passband = HIIR_DEFAULT_PASSBAND;
+};
+
+
+} // namespace lmms
+
+#endif
+
diff --git a/include/lmms_math.h b/include/lmms_math.h
index bdadd7ba0c4..d80248221ce 100644
--- a/include/lmms_math.h
+++ b/include/lmms_math.h
@@ -35,6 +35,10 @@
 #include "lmms_constants.h"
 #include "lmmsconfig.h"
 
+#ifdef __SSE2__
+#include <emmintrin.h>
+#endif
+
 namespace lmms
 {
 
@@ -266,6 +270,53 @@ class LinearMap
 	T m_b;
 };
 
+#ifdef __SSE2__
+// exp approximation for SSE2: https://stackoverflow.com/a/47025627/5759631
+// Maximum relative error of 1.72863156e-3 on [-87.33654, 88.72283]
+static inline __m128 fast_exp_sse(__m128 x)
+{
+	__m128 f, p, r;
+	__m128i t, j;
+	const __m128 a = _mm_set1_ps (12102203.0f); /* (1 << 23) / log(2) */
+	const __m128i m = _mm_set1_epi32 (0xff800000); /* mask for integer bits */
+	const __m128 ttm23 = _mm_set1_ps (1.1920929e-7f); /* exp2(-23) */
+	const __m128 c0 = _mm_set1_ps (0.3371894346f);
+	const __m128 c1 = _mm_set1_ps (0.657636276f);
+	const __m128 c2 = _mm_set1_ps (1.00172476f);
+
+	t = _mm_cvtps_epi32 (_mm_mul_ps (a, x));
+	j = _mm_and_si128 (t, m);            /* j = (int)(floor (x/log(2))) << 23 */
+	t = _mm_sub_epi32 (t, j);
+	f = _mm_mul_ps (ttm23, _mm_cvtepi32_ps (t)); /* f = (x/log(2)) - floor (x/log(2)) */
+	p = c0;                              /* c0 */
+	p = _mm_mul_ps (p, f);               /* c0 * f */
+	p = _mm_add_ps (p, c1);              /* c0 * f + c1 */
+	p = _mm_mul_ps (p, f);               /* (c0 * f + c1) * f */
+	p = _mm_add_ps (p, c2);              /* p = (c0 * f + c1) * f + c2 ~= 2^f */
+	r = _mm_castsi128_ps (_mm_add_epi32 (j, _mm_castps_si128 (p))); /* r = p * 2^i*/
+	return r;
+}
+
+// Lost Robot's SSE2 adaptation of Kari's vectorized log approximation: https://stackoverflow.com/a/65537754/5759631
+// Maximum relative error of 7.922410e-4 on [1.0279774e-38f, 3.4028235e+38f]
+static inline __m128 fast_log_sse(__m128 a) {
+	__m128i aInt = _mm_castps_si128(a);
+	__m128i e = _mm_sub_epi32(aInt, _mm_set1_epi32(0x3f2aaaab));
+	e = _mm_and_si128(e, _mm_set1_epi32(0xff800000));
+	__m128i subtr = _mm_sub_epi32(aInt, e);
+	__m128 m = _mm_castsi128_ps(subtr);
+	__m128 i = _mm_mul_ps(_mm_cvtepi32_ps(e), _mm_set1_ps(1.19209290e-7f));
+	__m128 f = _mm_sub_ps(m, _mm_set1_ps(1.0f));
+	__m128 s = _mm_mul_ps(f, f);
+	__m128 r = _mm_add_ps(_mm_mul_ps(_mm_set1_ps(0.230836749f), f), _mm_set1_ps(-0.279208571f));
+	__m128 t = _mm_add_ps(_mm_mul_ps(_mm_set1_ps(0.331826031f), f), _mm_set1_ps(-0.498910338f));
+	r = _mm_add_ps(_mm_mul_ps(r, s), t);
+	r = _mm_add_ps(_mm_mul_ps(r, s), f);
+	r = _mm_add_ps(_mm_mul_ps(i, _mm_set1_ps(0.693147182f)), r);
+	return r;
+}
+#endif // __SSE2__
+
 } // namespace lmms
 
 #endif // LMMS_MATH_H
diff --git a/plugins/SlewDistortion/CMakeLists.txt b/plugins/SlewDistortion/CMakeLists.txt
new file mode 100755
index 00000000000..e5e2f695b6a
--- /dev/null
+++ b/plugins/SlewDistortion/CMakeLists.txt
@@ -0,0 +1,4 @@
+INCLUDE(BuildPlugin)
+
+BUILD_PLUGIN(slewdistortion SlewDistortion.cpp SlewDistortionControls.cpp SlewDistortionControlDialog.cpp MOCFILES SlewDistortion.h SlewDistortionControls.h SlewDistortionControlDialog.h EMBEDDED_RESOURCES *.png)
+TARGET_LINK_LIBRARIES(slewdistortion hiir)
diff --git a/plugins/SlewDistortion/SlewDistortion.cpp b/plugins/SlewDistortion/SlewDistortion.cpp
new file mode 100755
index 00000000000..ab49f4f60fd
--- /dev/null
+++ b/plugins/SlewDistortion/SlewDistortion.cpp
@@ -0,0 +1,723 @@
+/*
+ * 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 PluginPixmapLoader("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(), SIGNAL(sampleRateChanged()), this, SLOT(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 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};
+	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];
+		}
+		
+		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 * F_PI * 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
diff --git a/plugins/SlewDistortion/SlewDistortion.h b/plugins/SlewDistortion/SlewDistortion.h
new file mode 100755
index 00000000000..78567e909bd
--- /dev/null
+++ b/plugins/SlewDistortion/SlewDistortion.h
@@ -0,0 +1,91 @@
+/*
+ * SlewDistortion.h
+ *
+ * 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.
+ *
+ */
+
+#ifndef LMMS_SLEW_DISTORTION_H
+#define LMMS_SLEW_DISTORTION_H
+
+#include "Effect.h"
+#include "SlewDistortionControls.h"
+
+#include "BasicFilters.h"
+#include "lmms_math.h"
+#include "OversamplingHelpers.h"
+
+namespace lmms
+{
+constexpr inline float SLEW_DISTORTION_MIN_FLOOR = 0.0012589f;// -72 dBFS
+constexpr inline float SLEW_DISTORTION_DC_FREQ = 7.f;
+
+class SlewDistortion : public Effect
+{
+	Q_OBJECT
+public:
+	SlewDistortion(Model* parent, const Descriptor::SubPluginFeatures::Key* key);
+	~SlewDistortion() override = default;
+	ProcessStatus processImpl(SampleFrame* buf, const fpp_t frames) override;
+
+	EffectControls* controls() override
+	{
+		return &m_slewdistortionControls;
+	}
+	
+	float msToCoeff(float ms)
+	{
+		return (ms == 0) ? 0 : exp(m_coeffPrecalc / ms);
+	}
+private slots:
+	void changeSampleRate();
+private:
+	alignas(16) std::array<float, 4> m_inPeakDisplay = {0};
+	alignas(16) std::array<float, 4> m_slewOut = {0};
+	alignas(16) std::array<float, 4> m_dcOffset = {0};
+	alignas(16) std::array<float, 4> m_inEnv = {0};
+	alignas(16) std::array<float, 4> m_outEnv = {0};
+	alignas(16) std::array<float, 4> m_outPeakDisplay = {0};
+	alignas(16) std::array<std::array<float, 1 << SLEWDIST_MAX_OVERSAMPLE_STAGES>, 2> m_overOuts = {{0}};
+	
+	float m_sampleRate = 44100.f;
+	
+	int m_oldOversampleVal = -1;
+	float m_coeffPrecalc = 0;
+	float m_dcCoeff = 0;
+	float m_biasInterpCoef = 0;
+	float m_trueBias1 = 0;
+	float m_trueBias2 = 0;
+	
+	std::array<Upsampler<SLEWDIST_MAX_OVERSAMPLE_STAGES>, 2> m_upsampler;
+	std::array<Downsampler<SLEWDIST_MAX_OVERSAMPLE_STAGES>, 2> m_downsampler;
+	
+	StereoLinkwitzRiley m_lp;
+	StereoLinkwitzRiley m_hp;
+	
+	SlewDistortionControls m_slewdistortionControls;
+	
+	friend class SlewDistortionControls;
+	friend class gui::SlewDistortionControlDialog;
+};
+
+} // namespace lmms
+
+#endif // LMMS_SLEW_DISTORTION_H
diff --git a/plugins/SlewDistortion/SlewDistortionControlDialog.cpp b/plugins/SlewDistortion/SlewDistortionControlDialog.cpp
new file mode 100755
index 00000000000..ed06eeb7b97
--- /dev/null
+++ b/plugins/SlewDistortion/SlewDistortionControlDialog.cpp
@@ -0,0 +1,479 @@
+/*
+ * SlewDistortionControlDialog.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 "SlewDistortionControlDialog.h"
+#include "SlewDistortionControls.h"
+#include "SlewDistortion.h"
+
+#include "embed.h"
+#include "Knob.h"
+#include "MainWindow.h"
+#include <QPainter>
+#include "GuiApplication.h"
+#include "PixmapButton.h"
+#include "Draggable.h"
+#include "lmms_math.h"
+
+#include <QPainterPath>
+
+namespace lmms::gui
+{
+
+SlewDistortionControlDialog::SlewDistortionControlDialog(SlewDistortionControls* controls) :
+	EffectControlDialog(controls),
+	m_controls(controls)
+{
+	setAutoFillBackground(true);
+	QPalette pal;
+	pal.setBrush(backgroundRole(), PLUGIN_NAME::getIconPixmap("artwork"));
+	setPalette(pal);
+	setFixedSize(638, 271);
+	
+	auto makeKnob = [this](int x, int y, const QString& hintText, const QString& unit, FloatModel* model, bool smol = false)
+	{
+		Knob* newKnob = new Knob(smol ? KnobType::Small17 : KnobType::Bright26, this);
+		newKnob->move(x, y);
+		newKnob->setModel(model);
+		newKnob->setHintText(hintText, unit);
+		return newKnob;
+	};
+	
+	auto makeToggleButton = [this](int x, int y, const QString& tooltip, const std::string& activeIcon, const std::string& inactiveIcon, BoolModel* model)
+	{
+		PixmapButton* button = new PixmapButton(this, tooltip);
+		button->setActiveGraphic(PLUGIN_NAME::getIconPixmap(activeIcon));
+		button->setInactiveGraphic(PLUGIN_NAME::getIconPixmap(inactiveIcon));
+		button->setToolTip(tooltip);
+		button->move(x, y);
+		button->setCheckable(true);
+		button->setModel(model);
+		return button;
+	};
+	
+	auto makeGroupButton = [this](int x, int y, const QString& tooltip, const std::string& activeIcon, const std::string& inactiveIcon)
+	{
+		PixmapButton* button = new PixmapButton(this, tooltip);
+		button->setActiveGraphic(PLUGIN_NAME::getIconPixmap(activeIcon));
+		button->setInactiveGraphic(PLUGIN_NAME::getIconPixmap(inactiveIcon));
+		button->setToolTip(tooltip);
+		button->move(x, y);
+		return button;
+	};
+
+	ComboBox* distType1Box = new ComboBox(this);
+	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, 147, 115, 22);
+	//distType2Box->setFont(pointSize<8>(distType2Box->font()));
+	distType2Box->setModel(&controls->m_distType2Model);
+	
+	Draggable* drive1Draggable = new Draggable(FloatModelEditorBase::DirectionOfManipulation::Vertical,
+		&controls->m_drive1Model, PLUGIN_NAME::getIconPixmap("handle"), 108, 34, this);
+	drive1Draggable->move(16, drive1Draggable->y());
+	drive1Draggable->setDefaultValPixmap(PLUGIN_NAME::getIconPixmap("handle_zero"));
+	Draggable* drive2Draggable = new Draggable(FloatModelEditorBase::DirectionOfManipulation::Vertical,
+		&controls->m_drive2Model, PLUGIN_NAME::getIconPixmap("handle"), 229, 155, this);
+	drive2Draggable->move(16, drive2Draggable->y());
+	drive2Draggable->setDefaultValPixmap(PLUGIN_NAME::getIconPixmap("handle_zero"));
+	
+	Draggable* bias1Draggable = new Draggable(FloatModelEditorBase::DirectionOfManipulation::Vertical,
+		&controls->m_bias1Model, PLUGIN_NAME::getIconPixmap("handle"), 112, 34, this);
+	bias1Draggable->move(416, bias1Draggable->y());
+	bias1Draggable->setDefaultValPixmap(PLUGIN_NAME::getIconPixmap("handle_zero"));
+	Draggable* bias2Draggable = new Draggable(FloatModelEditorBase::DirectionOfManipulation::Vertical,
+		&controls->m_bias2Model, PLUGIN_NAME::getIconPixmap("handle"), 233, 155, this);
+	bias2Draggable->move(416, bias2Draggable->y());
+	bias2Draggable->setDefaultValPixmap(PLUGIN_NAME::getIconPixmap("handle_zero"));
+	
+	m_slewUp1Knob = makeKnob(96, 65, tr("Slew Up 1:"), "", &controls->m_slewUp1Model);
+	m_slewUp2Knob = makeKnob(96, 186, tr("Slew Up 2:"), "", &controls->m_slewUp2Model);
+	m_slewDown1Knob = makeKnob(163, 65, tr("Slew Down 1:"), "",
+		controls->m_slewLink1Model.value() ? &controls->m_slewUp1Model : &controls->m_slewDown1Model);
+	m_slewDown2Knob = makeKnob(163, 186, tr("Slew Down 2:"), "",
+		controls->m_slewLink2Model.value() ? &controls->m_slewUp2Model : &controls->m_slewDown2Model);
+	makeKnob(329, 26, tr("Warp 1:"), "", &controls->m_warp1Model);
+	makeKnob(329, 147, tr("Warp 2:"), "", &controls->m_warp2Model);
+	makeKnob(371, 26, tr("Crush 1:"), "", &controls->m_crush1Model);
+	makeKnob(371, 147, tr("Crush 2:"), "", &controls->m_crush2Model);
+	makeKnob(225, 65, tr("Attack 1:"), "", &controls->m_attack1Model);
+	makeKnob(225, 186, tr("Attack 2:"), "", &controls->m_attack2Model);
+	makeKnob(267, 65, tr("Release 1:"), "", &controls->m_release1Model);
+	makeKnob(267, 186, tr("Release 2:"), "", &controls->m_release2Model);
+	makeKnob(225, 26, tr("Dynamics 1:"), "", &controls->m_dynamics1Model);
+	makeKnob(225, 147, tr("Dynamics 2:"), "", &controls->m_dynamics2Model);
+	makeKnob(267, 26, tr("Dynamic Slew 1:"), "", &controls->m_dynamicSlew1Model);
+	makeKnob(267, 147, tr("Dynamic Slew 2:"), "", &controls->m_dynamicSlew2Model);
+	
+	Draggable* outVol1Draggable = new Draggable(FloatModelEditorBase::DirectionOfManipulation::Vertical,
+		&controls->m_outVol1Model, PLUGIN_NAME::getIconPixmap("handle"), 108, 34, this);
+	outVol1Draggable->move(594, outVol1Draggable->y());
+	outVol1Draggable->setDefaultValPixmap(PLUGIN_NAME::getIconPixmap("handle_zero"));
+	Draggable* outVol2Draggable = new Draggable(FloatModelEditorBase::DirectionOfManipulation::Vertical,
+		&controls->m_outVol2Model, PLUGIN_NAME::getIconPixmap("handle"), 229, 155, this);
+	outVol2Draggable->move(594, outVol2Draggable->y());
+	outVol2Draggable->setDefaultValPixmap(PLUGIN_NAME::getIconPixmap("handle_zero"));
+	
+	PixmapButton* slewLink1Button = makeToggleButton(132, 70, tr("Slew Link 1"), "link_on", "link_off", &controls->m_slewLink1Model);
+	connect(slewLink1Button, &PixmapButton::clicked, this, [this, controls]{
+		controls->m_slewDown1Model.setValue(controls->m_slewUp1Model.value());
+		m_slewDown1Knob->setModel(controls->m_slewLink1Model.value() ? &controls->m_slewUp1Model : &controls->m_slewDown1Model);
+	}, Qt::DirectConnection);
+	PixmapButton* slewLink2Button = makeToggleButton(132, 191, tr("Slew Link 2"), "link_on", "link_off", &controls->m_slewLink2Model);
+	connect(slewLink2Button, &PixmapButton::clicked, this, [this, controls]{
+		controls->m_slewDown2Model.setValue(controls->m_slewUp2Model.value());
+		m_slewDown2Knob->setModel(controls->m_slewLink2Model.value() ? &controls->m_slewUp2Model : &controls->m_slewDown2Model);
+	}, Qt::DirectConnection);
+	
+	makeToggleButton(9, 248, tr("DC Offset Removal"), "dc_on", "dc_off", &controls->m_dcRemoveModel);
+	makeToggleButton(99, 248, tr("Multiband"), "mb_on", "mb_off", &controls->m_multibandModel);
+
+	makeKnob(190, 249, tr("Split:"), "", &controls->m_splitModel, true);
+	makeKnob(338, 78, tr("Mix 1:"), "", &controls->m_mix1Model);
+	makeKnob(338, 199, tr("Mix 2:"), "", &controls->m_mix2Model);
+	
+	PixmapButton* oversample1xButton = makeGroupButton(454, 248, tr("Disable Oversampling"), "oversample_1x_on", "oversample_1x_off");
+	PixmapButton* oversample2xButton = makeGroupButton(479, 248, tr("2x Oversampling"), "oversample_2x_on", "oversample_2x_off");
+	PixmapButton* oversample4xButton = makeGroupButton(504, 248, tr("4x Oversampling"), "oversample_4x_on", "oversample_4x_off");
+	PixmapButton* oversample8xButton = makeGroupButton(529, 248, tr("8x Oversampling"), "oversample_8x_on", "oversample_8x_off");
+	PixmapButton* oversample16xButton = makeGroupButton(554, 248, tr("16x Oversampling"), "oversample_16x_on", "oversample_16x_off");
+	PixmapButton* oversample32xButton = makeGroupButton(579, 248, tr("32x Oversampling"), "oversample_32x_on", "oversample_32x_off");
+
+	automatableButtonGroup* oversampleGroup = new automatableButtonGroup(this);
+	oversampleGroup->addButton(oversample1xButton);
+	oversampleGroup->addButton(oversample2xButton);
+	oversampleGroup->addButton(oversample4xButton);
+	oversampleGroup->addButton(oversample8xButton);
+	oversampleGroup->addButton(oversample16xButton);
+	oversampleGroup->addButton(oversample32xButton);
+	oversampleGroup->setModel(&controls->m_oversamplingModel);
+	
+	PixmapButton* m_helpBtn = new PixmapButton(this, nullptr);
+	m_helpBtn->move(614, 250);
+	m_helpBtn->setActiveGraphic(PLUGIN_NAME::getIconPixmap("help_on"));
+	m_helpBtn->setInactiveGraphic(PLUGIN_NAME::getIconPixmap("help_off"));
+	m_helpBtn->setToolTip(tr("Open help window"));
+	connect(m_helpBtn, SIGNAL(clicked()), this, SLOT(showHelpWindow()));
+	
+	connect(getGUI()->mainWindow(), SIGNAL(periodicUpdate()), this, SLOT(updateDisplay()));
+}
+
+void SlewDistortionControlDialog::updateDisplay()
+{
+	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);
+}
+
+void SlewDistortionControlDialog::showHelpWindow()
+{
+	SlewDistortionHelpView::getInstance()->close();
+	SlewDistortionHelpView::getInstance()->show();
+}
+
+
+QString SlewDistortionHelpView::s_helpText =
+"<div style='text-align: center;'>"
+"<b>Slew Distortion</b><br><br>"
+"Plugin by Lost Robot<br>"
+"GUI by thismoon<br>"
+"</div>"
+"<h3>Overview:</h3>"
+"Slew Distortion is a multiband slew rate limiter and distortion effect.<br><br>"
+"Slew rate limiting is something I accidentally invented while trying to make a lowpass filter for the first time.<br>"
+"In short, a slew rate limiter limits how quickly the waveform can move from one point to the next.<br>"
+"You'll hear that it has a similar quality to a lowpass filter, in that it does quieten the high frequencies by quite a bit.<br>"
+"However, the intensity of this effect depends heavily on the input signal, and with it comes a rather unique distortion of that signal.<br><br>"
+"In this plugin, the slew rate limiting is followed by waveshaping distortion.<br>"
+"Every distortion type is a pure waveshaping function with no filters or delays of any kind involved.<br>"
+"These distortions will generate new harmonics at exact frequency multiples of the incoming audio.<br><br>"
+"Because the plugin is multiband, you can apply these effects to different frequency ranges independently.<br>"
+"<br><h3>Distortion Types:</h3>"
+"<b>Hard Clip</b> - Aggressively clamps the audio signal to 0 dBFS.<br>"
+"This leaves the signal entirely untouched until it passes the clamping threshold, beyond which all content is clipped out entirely.<br>"
+"<b>Tanh</b> - A very gentle sigmoid distortion.<br>"
+"This waveshape is mathematically smooth and continuous at all derivatives.<br>"
+"It can be pushed significantly harder than most other distortion shapes before it starts generating harsh high frequencies.<br>"
+"<b>Fast Soft Clip 1</b> - A CPU-efficient soft clipping function.<br>"
+"<b>Fast Soft Clip 2</b> - A CPU-efficient cubic soft clipping function.<br>"
+"<b>Sinusoidal</b> - Incredibly smooth wavewrapping distortion.<br>"
+"Unlike all the previous distortion types, loud audio information is not entirely lost or clipped away, and is instead wrapped back down to lower values.<br>"
+"<b>Foldover</b> - A non-smooth wavewrapping alternative.<br>"
+"This leaves the audio values untouched relative to neighboring values,<br>"
+"except at the borders where the waveshape sharply changes directions, generating harsh distortion.<br>"
+"<b>Full-wave Rectify</b> - Flips the bottom half of the waveform to the top half.<br>"
+"The timbre of this commonly sounds similar to shifting the audio upward by one octave.<br>"
+"Unlike all the previous distortion types, this one is asymmetrical by default, meaning it will generate even-multiple harmonics.<br>"
+"<b>Smooth Rectify</b> - An alternative to Full-wave Rectify which has a smooth corner.<br>"
+"<b>Half-wave Rectify</b> - An alternative to Full-wave Rectify which clips all negative audio samples instead of reflecting them upward.<br>"
+"<b>Bitcrush</b> - Bit depth reduction. This distortion type is special-cased to have the Drive change its shape instead of its input amplitude.<br>"
+"<br><h3>Slew:</h3>"
+"This section controls the slew rate limit, the speed at which the incoming waveform's values can change.<br>"
+"<b>Up</b> and <b>Down</b> control the slew rate limit for upward and downward movement, respectively.<br>"
+"The <b>Slew Link</b> button locks the Slew Up and Slew Down parameters to the same value, for convenience.<br>"
+"<br><h3>Dynamics:</h3>"
+"This section uses an envelope follower to track the volume of the incoming audio signal.<br>"
+"<b>Amount</b> - Restores the dynamic range lost from the distortion and slew rate limiting by matching the output volume to the input volume.<br>"
+"<b>Slew</b> - Dynamically changes the slew rate, depending on the input volume.<br>"
+"<b>Attack</b> - How quickly the envelope follower responds to increases in volume (e.g. transients).<br>"
+"<b>Release</b> - How quickly the envelope follower responds to decreases in volume.<br>"
+"<br><h3>Shape:</h3>"
+"This section allows further sculpting of the distortion shape beyond what the distortion types can achieve on their own.<br>"
+"<b>Warp</b> - Causes input values smaller than this value to be unimpacted by the waveshaping.<br>"
+"The distortion shape is properly scaled and shifted to ensure it remains perfectly clean and continuous.<br>"
+"<b>Crush</b> - Increases the volume of audio below the Warp value.<br>"
+"This adds a sharp corner to the waveshaping function, resulting in much more aggressive distortion.<br>"
+"<br><h3>Miscellaneous:</h3>"
+"<b>Mix</b> - Blends between the wet and dry signals for the current band.<br>"
+"Since both the wet and dry signal are after the crossover filter and have oversampling applied,<br>"
+"this parameter is entirely immune to phase issues caused by blending signals.<br>"
+"<b>Bias</b> - Adds DC offset to the input signal before the distortion, causing the waveshaping to be asymmetrical.<br>"
+"This allows every distortion type to generate even-multiple harmonics, including the symmetrical types which usually only generate odd-multiple harmonics.<br>"
+"<b>DC Remover</b> - Removes DC offset (0 Hz audio) from the output signal. You'll almost always want to leave this enabled.<br>"
+"<b>Multiband</b> - Splits the signal into two frequency bands. If disabled, the top band's parameters are applied to the entire audio signal.<br>"
+"<b>Split</b> - The crossover frequency at which the Multiband mode splits the signal into two bands.<br>"
+"<br><h3>Oversampling:</h3>"
+"An audio signal is only capable of storing frequencies below Nyquist, which is half of the sample rate.<br>"
+"If any form of distortion generates new frequencies that are above this Nyquist frequency, they will be reflected (aliased) back downward.<br>"
+"For example, if the distortion generates a harmonic that is 5000 Hz above Nyquist, that frequency will be aliased down to 5000 Hz below Nyquist.<br>"
+"This aliasing is inharmonic, oftentimes sounds unpleasant, and can even contribute to auditory masking within the song.<br><br>"
+"Oversampling helps to resolve this issue by temporarily increasing the sample rate of the signal,<br>"
+"so significantly higher frequencies can be supported before they start aliasing back into the audible range.<br>"
+"Those higher frequencies are then filtered out before decreasing the sample rate back to its original value so they don't alias.<br><br>"
+"This plugin supports up to five stages of oversampling.<br>"
+"Each stage provides an extra 2 octaves of headroom before frequencies alias far enough to become audible.<br>"
+"The number on the button is how much the sample rate is increased by. THE PLUGIN'S CPU USAGE WILL BE INCREASED BY APPROXIMATELY THE SAME AMOUNT.<br>"
+"Even just 2x oversampling can make a massive difference and is oftentimes all you need, but up to 32x oversampling is supported.<br>"
+;
+
+
+
+SlewDistortionHelpView::SlewDistortionHelpView():QTextEdit(s_helpText)
+{
+#if (QT_VERSION < QT_VERSION_CHECK(5,12,0))
+	// Bug workaround: https://codereview.qt-project.org/c/qt/qtbase/+/225348
+	using ::operator|;
+#endif
+	setWindowTitle("Slew Distortion Help");
+	setTextInteractionFlags(Qt::TextSelectableByKeyboard | Qt::TextSelectableByMouse);
+	getGUI()->mainWindow()->addWindowedWidget(this);
+	parentWidget()->setAttribute(Qt::WA_DeleteOnClose, false);
+	parentWidget()->setWindowIcon(PLUGIN_NAME::getIconPixmap("logo"));
+	
+	// No maximize button
+	Qt::WindowFlags flags = parentWidget()->windowFlags();
+	flags &= ~Qt::WindowMaximizeButtonHint;
+	parentWidget()->setWindowFlags(flags);
+}
+
+
+} // namespace lmms::gui
diff --git a/plugins/SlewDistortion/SlewDistortionControlDialog.h b/plugins/SlewDistortion/SlewDistortionControlDialog.h
new file mode 100755
index 00000000000..e5f3a8afd34
--- /dev/null
+++ b/plugins/SlewDistortion/SlewDistortionControlDialog.h
@@ -0,0 +1,85 @@
+/*
+ * SlewDistortionControlDialog.h
+ *
+ * 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.
+ *
+ */
+
+#ifndef SLEW_DISTORTION_CONTROL_DIALOG_H
+#define SLEW_DISTORTION_CONTROL_DIALOG_H
+
+#include "EffectControlDialog.h"
+#include <QTextEdit>
+#include <array>
+
+namespace lmms
+{
+
+class SlewDistortionControls;
+class FloatModel;
+
+namespace gui
+{
+
+class Knob;
+
+class SlewDistortionControlDialog : public EffectControlDialog
+{
+	Q_OBJECT
+public:
+	SlewDistortionControlDialog(SlewDistortionControls* controls);
+	~SlewDistortionControlDialog() override = default;
+	
+	void paintEvent(QPaintEvent *event) override;
+public slots:
+	void updateDisplay();
+	void showHelpWindow();
+private:
+	SlewDistortionControls* m_controls;
+	
+	Knob* m_slewUp1Knob;
+	Knob* m_slewUp2Knob;
+	Knob* m_slewDown1Knob;
+	Knob* m_slewDown2Knob;
+	
+	std::array<float, 4> m_lastInPeaks = {0};
+	std::array<float, 4> m_lastOutPeaks = {0};
+};
+
+class SlewDistortionHelpView : public QTextEdit
+{
+	Q_OBJECT
+public:
+	static SlewDistortionHelpView* getInstance()
+	{
+		static SlewDistortionHelpView instance;
+		return &instance;
+	}
+
+private:
+	SlewDistortionHelpView();
+	static QString s_helpText;
+};
+
+} // namespace gui
+
+} // namespace lmms
+
+#endif
diff --git a/plugins/SlewDistortion/SlewDistortionControls.cpp b/plugins/SlewDistortion/SlewDistortionControls.cpp
new file mode 100755
index 00000000000..2c2f0ab1244
--- /dev/null
+++ b/plugins/SlewDistortion/SlewDistortionControls.cpp
@@ -0,0 +1,183 @@
+/*
+ * SlewDistortionControls.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 <QDomElement>
+
+#include "SlewDistortionControls.h"
+#include "SlewDistortion.h"
+
+namespace lmms
+{
+
+SlewDistortionControls::SlewDistortionControls(SlewDistortion* effect) :
+	EffectControls(effect),
+	m_effect(effect),
+	m_distType1Model(this, tr("Type 1")),
+	m_distType2Model(this, tr("Type 2")),
+	m_drive1Model(0.0f, -24.f, 24.0f, 0.0001f, this, tr("Drive 1")),
+	m_drive2Model(0.0f, -24.f, 24.0f, 0.0001f, this, tr("Drive 2")),
+	m_slewUp1Model(6.0f, -96.f, 6.0f, 0.0001f, this, tr("Slew Up 1")),
+	m_slewUp2Model(6.0f, -96.f, 6.0f, 0.0001f, this, tr("Slew Up 2")),
+	m_slewDown1Model(6.0f, -96.f, 6.0f, 0.0001f, this, tr("Slew Down 1")),
+	m_slewDown2Model(6.0f, -96.f, 6.0f, 0.0001f, this, tr("Slew Down 2")),
+	m_bias1Model(0.0f, -2.0f, 2.0f, 0.0001f, this, tr("Bias 1")),
+	m_bias2Model(0.0f, -2.0f, 2.0f, 0.0001f, this, tr("Bias 2")),
+	m_warp1Model(0.0f, 0.0f, 0.99f, 0.0001f, this, tr("Warp 1")),
+	m_warp2Model(0.0f, 0.0f, 0.99f, 0.0001f, this, tr("Warp 2")),
+	m_crush1Model(0.0f, 0.0f, 24.0f, 0.0001f, this, tr("Crush 1")),
+	m_crush2Model(0.0f, 0.0f, 24.0f, 0.0001f, this, tr("Crush 2")),
+	m_outVol1Model(0.0f, -24.0f, 24.0f, 0.0001f, this, tr("Out Vol 1")),
+	m_outVol2Model(0.0f, -24.0f, 24.0f, 0.0001f, this, tr("Out Vol 2")),
+	m_attack1Model(2.0f, 0.01f, 200.0f, 0.01f, this, tr("Attack 1")),
+	m_attack2Model(2.0f, 0.01f, 200.0f, 0.01f, this, tr("Attack 2")),
+	m_release1Model(20.0f, 0.01f, 800.0f, 0.01f, this, tr("Release 1")),
+	m_release2Model(20.0f, 0.01f, 800.0f, 0.01f, this, tr("Release 2")),
+	m_dynamics1Model(0.0f, 0.0f, 1.0f, 0.0001f, this, tr("Dynamics 1")),
+	m_dynamics2Model(0.0f, 0.0f, 1.0f, 0.0001f, this, tr("Dynamics 2")),
+	m_dynamicSlew1Model(0.0f, -8.0f, 8.0f, 0.0001f, this, tr("Dynamic Slew 1")),
+	m_dynamicSlew2Model(0.0f, -8.0f, 8.0f, 0.0001f, this, tr("Dynamic Slew 2")),
+	m_dcRemoveModel(true, this, tr("DC Offset Remover")),
+	m_multibandModel(false, this, tr("Multiband")),
+	m_oversamplingModel(0, 0, SLEWDIST_MAX_OVERSAMPLE_STAGES, this, tr("Oversample")),
+	m_splitModel(200.0f, 100.0f, 20000.0f, 0.1f, this, tr("Split")),
+	m_mix1Model(1.0f, 0.0f, 1.0f, 0.0001f, this, tr("Mix 1")),
+	m_mix2Model(1.0f, 0.0f, 1.0f, 0.0001f, this, tr("Mix 2")),
+	m_slewLink1Model(true, this, tr("Slew Link 1")),
+	m_slewLink2Model(true, this, tr("Slew Link 2"))
+{
+	m_slewUp1Model.setScaleLogarithmic(true);
+	m_slewUp2Model.setScaleLogarithmic(true);
+	m_slewDown1Model.setScaleLogarithmic(true);
+	m_slewDown2Model.setScaleLogarithmic(true);
+	m_crush1Model.setScaleLogarithmic(true);
+	m_crush2Model.setScaleLogarithmic(true);
+	m_attack1Model.setScaleLogarithmic(true);
+	m_attack2Model.setScaleLogarithmic(true);
+	m_release1Model.setScaleLogarithmic(true);
+	m_release2Model.setScaleLogarithmic(true);
+	m_dynamicSlew1Model.setScaleLogarithmic(true);
+	m_dynamicSlew2Model.setScaleLogarithmic(true);
+	m_splitModel.setScaleLogarithmic(true);
+	
+	m_distType1Model.addItem(tr("Hard Clip"));
+	m_distType1Model.addItem(tr("Tanh"));
+	m_distType1Model.addItem(tr("Fast Soft Clip 1"));
+	m_distType1Model.addItem(tr("Fast Soft Clip 2"));
+	m_distType1Model.addItem(tr("Sinusoidal"));
+	m_distType1Model.addItem(tr("Foldback"));
+	m_distType1Model.addItem(tr("Full Rectify"));
+	m_distType1Model.addItem(tr("Half Rectify"));
+	m_distType1Model.addItem(tr("Smooth Rectify"));
+	m_distType1Model.addItem(tr("Bitcrush"));
+	
+	m_distType2Model.addItem(tr("Hard Clip"));
+	m_distType2Model.addItem(tr("Tanh"));
+	m_distType2Model.addItem(tr("Fast Soft Clip 1"));
+	m_distType2Model.addItem(tr("Fast Soft Clip 2"));
+	m_distType2Model.addItem(tr("Sinusoidal"));
+	m_distType2Model.addItem(tr("Foldback"));
+	m_distType2Model.addItem(tr("Full Rectify"));
+	m_distType2Model.addItem(tr("Half Rectify"));
+	m_distType2Model.addItem(tr("Smooth Rectify"));
+	m_distType2Model.addItem(tr("Bitcrush"));
+}
+
+
+void SlewDistortionControls::loadSettings(const QDomElement& parent)
+{
+	m_distType1Model.loadSettings(parent, "distType1");
+	m_distType2Model.loadSettings(parent, "distType2");
+	m_drive1Model.loadSettings(parent, "drive1");
+	m_drive2Model.loadSettings(parent, "drive2");
+	m_slewUp1Model.loadSettings(parent, "slewUp1");
+	m_slewUp2Model.loadSettings(parent, "slewUp2");
+	m_slewDown1Model.loadSettings(parent, "slewDown1");
+	m_slewDown2Model.loadSettings(parent, "slewDown2");
+	m_bias1Model.loadSettings(parent, "bias1");
+	m_bias2Model.loadSettings(parent, "bias2");
+	m_warp1Model.loadSettings(parent, "warp1");
+	m_warp2Model.loadSettings(parent, "warp2");
+	m_crush1Model.loadSettings(parent, "crush1");
+	m_crush2Model.loadSettings(parent, "crush2");
+	m_outVol1Model.loadSettings(parent, "outVol1");
+	m_outVol2Model.loadSettings(parent, "outVol2");
+	m_attack1Model.loadSettings(parent, "attack1");
+	m_attack2Model.loadSettings(parent, "attack2");
+	m_release1Model.loadSettings(parent, "release1");
+	m_release2Model.loadSettings(parent, "release2");
+	m_dynamics1Model.loadSettings(parent, "dynamics1");
+	m_dynamics2Model.loadSettings(parent, "dynamics2");
+	m_dynamicSlew1Model.loadSettings(parent, "dynamicSlew1");
+	m_dynamicSlew2Model.loadSettings(parent, "dynamicSlew2");
+	m_dcRemoveModel.loadSettings(parent, "dcRemove");
+	m_multibandModel.loadSettings(parent, "multiband");
+	m_oversamplingModel.loadSettings(parent, "oversampling");
+	m_splitModel.loadSettings(parent, "split");
+	m_mix1Model.loadSettings(parent, "mix1");
+	m_mix2Model.loadSettings(parent, "mix2");
+	m_slewLink1Model.loadSettings(parent, "slewLink1");
+	m_slewLink2Model.loadSettings(parent, "slewLink2");
+}
+
+
+
+void SlewDistortionControls::saveSettings(QDomDocument& doc, QDomElement& parent)
+{
+   m_distType1Model.saveSettings(doc, parent, "distType1");
+	m_distType2Model.saveSettings(doc, parent, "distType2");
+	m_drive1Model.saveSettings(doc, parent, "drive1");
+	m_drive2Model.saveSettings(doc, parent, "drive2");
+	m_slewUp1Model.saveSettings(doc, parent, "slewUp1");
+	m_slewUp2Model.saveSettings(doc, parent, "slewUp2");
+	m_slewDown1Model.saveSettings(doc, parent, "slewDown1");
+	m_slewDown2Model.saveSettings(doc, parent, "slewDown2");
+	m_bias1Model.saveSettings(doc, parent, "bias1");
+	m_bias2Model.saveSettings(doc, parent, "bias2");
+	m_warp1Model.saveSettings(doc, parent, "warp1");
+	m_warp2Model.saveSettings(doc, parent, "warp2");
+	m_crush1Model.saveSettings(doc, parent, "crush1");
+	m_crush2Model.saveSettings(doc, parent, "crush2");
+	m_outVol1Model.saveSettings(doc, parent, "outVol1");
+	m_outVol2Model.saveSettings(doc, parent, "outVol2");
+	m_attack1Model.saveSettings(doc, parent, "attack1");
+	m_attack2Model.saveSettings(doc, parent, "attack2");
+	m_release1Model.saveSettings(doc, parent, "release1");
+	m_release2Model.saveSettings(doc, parent, "release2");
+	m_dynamics1Model.saveSettings(doc, parent, "dynamics1");
+	m_dynamics2Model.saveSettings(doc, parent, "dynamics2");
+	m_dynamicSlew1Model.saveSettings(doc, parent, "dynamicSlew1");
+	m_dynamicSlew2Model.saveSettings(doc, parent, "dynamicSlew2");
+	m_dcRemoveModel.saveSettings(doc, parent, "dcRemove");
+	m_multibandModel.saveSettings(doc, parent, "multiband");
+	m_oversamplingModel.saveSettings(doc, parent, "oversampling");
+	m_splitModel.saveSettings(doc, parent, "split");
+	m_mix1Model.saveSettings(doc, parent, "mix1");
+	m_mix2Model.saveSettings(doc, parent, "mix2");
+	m_slewLink1Model.saveSettings(doc, parent, "slewLink1");
+	m_slewLink2Model.saveSettings(doc, parent, "slewLink2");
+}
+
+
+
+} // namespace lmms
diff --git a/plugins/SlewDistortion/SlewDistortionControls.h b/plugins/SlewDistortion/SlewDistortionControls.h
new file mode 100755
index 00000000000..b8c716c533a
--- /dev/null
+++ b/plugins/SlewDistortion/SlewDistortionControls.h
@@ -0,0 +1,105 @@
+/*
+ * SlewDistortionControls.h
+ *
+ * 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.
+ *
+ */
+
+#ifndef SLEW_DISTORTION_CONTROLS_H
+#define SLEW_DISTORTION_CONTROLS_H
+
+#include "EffectControls.h"
+#include "SlewDistortionControlDialog.h"
+#include "ComboBox.h"
+
+namespace lmms
+{
+
+constexpr int SLEWDIST_MAX_OVERSAMPLE_STAGES = 5;
+
+class SlewDistortion;
+
+namespace gui
+{
+class SlewDistortionControlDialog;
+}
+
+class SlewDistortionControls : public EffectControls
+{
+	Q_OBJECT
+public:
+	SlewDistortionControls(SlewDistortion* effect);
+	~SlewDistortionControls() override = default;
+
+	void saveSettings(QDomDocument& doc, QDomElement& parent) override;
+	void loadSettings(const QDomElement& parent) override;
+	inline QString nodeName() const override
+	{
+		return "SlewDistortionControls";
+	}
+	gui::EffectControlDialog* createView() override
+	{
+		return new gui::SlewDistortionControlDialog(this);
+	}
+	int controlCount() override { return 32; }
+
+private:
+	SlewDistortion* m_effect;
+	
+	ComboBoxModel m_distType1Model;
+	ComboBoxModel m_distType2Model;
+	FloatModel m_drive1Model;
+	FloatModel m_drive2Model;
+	FloatModel m_slewUp1Model;
+	FloatModel m_slewUp2Model;
+	FloatModel m_slewDown1Model;
+	FloatModel m_slewDown2Model;
+	FloatModel m_bias1Model;
+	FloatModel m_bias2Model;
+	FloatModel m_warp1Model;
+	FloatModel m_warp2Model;
+	FloatModel m_crush1Model;
+	FloatModel m_crush2Model;
+	FloatModel m_outVol1Model;
+	FloatModel m_outVol2Model;
+	FloatModel m_attack1Model;
+	FloatModel m_attack2Model;
+	FloatModel m_release1Model;
+	FloatModel m_release2Model;
+	FloatModel m_dynamics1Model;
+	FloatModel m_dynamics2Model;
+	FloatModel m_dynamicSlew1Model;
+	FloatModel m_dynamicSlew2Model;
+	BoolModel m_dcRemoveModel;
+	BoolModel m_multibandModel;
+	IntModel m_oversamplingModel;
+	FloatModel m_splitModel;
+	FloatModel m_mix1Model;
+	FloatModel m_mix2Model;
+	BoolModel m_slewLink1Model;
+	BoolModel m_slewLink2Model;
+
+	friend class gui::SlewDistortionControlDialog;
+	friend class SlewDistortion;
+};
+
+} // namespace lmms
+
+#endif
diff --git a/plugins/SlewDistortion/artwork.png b/plugins/SlewDistortion/artwork.png
new file mode 100644
index 00000000000..8f516729c82
Binary files /dev/null and b/plugins/SlewDistortion/artwork.png differ
diff --git a/plugins/SlewDistortion/dc_off.png b/plugins/SlewDistortion/dc_off.png
new file mode 100644
index 00000000000..d2bc7e309f2
Binary files /dev/null and b/plugins/SlewDistortion/dc_off.png differ
diff --git a/plugins/SlewDistortion/dc_on.png b/plugins/SlewDistortion/dc_on.png
new file mode 100644
index 00000000000..c0945bd456f
Binary files /dev/null and b/plugins/SlewDistortion/dc_on.png differ
diff --git a/plugins/SlewDistortion/handle.png b/plugins/SlewDistortion/handle.png
new file mode 100644
index 00000000000..08e68bef3ae
Binary files /dev/null and b/plugins/SlewDistortion/handle.png differ
diff --git a/plugins/SlewDistortion/handle_zero.png b/plugins/SlewDistortion/handle_zero.png
new file mode 100644
index 00000000000..0960a8f441e
Binary files /dev/null and b/plugins/SlewDistortion/handle_zero.png differ
diff --git a/plugins/SlewDistortion/help_off.png b/plugins/SlewDistortion/help_off.png
new file mode 100644
index 00000000000..0a3b927f9da
Binary files /dev/null and b/plugins/SlewDistortion/help_off.png differ
diff --git a/plugins/SlewDistortion/help_on.png b/plugins/SlewDistortion/help_on.png
new file mode 100644
index 00000000000..b9b51d6ed11
Binary files /dev/null and b/plugins/SlewDistortion/help_on.png differ
diff --git a/plugins/SlewDistortion/link_off.png b/plugins/SlewDistortion/link_off.png
new file mode 100644
index 00000000000..8b658abcbb2
Binary files /dev/null and b/plugins/SlewDistortion/link_off.png differ
diff --git a/plugins/SlewDistortion/link_on.png b/plugins/SlewDistortion/link_on.png
new file mode 100644
index 00000000000..c289f16e845
Binary files /dev/null and b/plugins/SlewDistortion/link_on.png differ
diff --git a/plugins/SlewDistortion/logo.png b/plugins/SlewDistortion/logo.png
new file mode 100755
index 00000000000..9340da708dd
Binary files /dev/null and b/plugins/SlewDistortion/logo.png differ
diff --git a/plugins/SlewDistortion/mb_off.png b/plugins/SlewDistortion/mb_off.png
new file mode 100644
index 00000000000..d5f1a4fb03a
Binary files /dev/null and b/plugins/SlewDistortion/mb_off.png differ
diff --git a/plugins/SlewDistortion/mb_on.png b/plugins/SlewDistortion/mb_on.png
new file mode 100644
index 00000000000..4f35fc1c526
Binary files /dev/null and b/plugins/SlewDistortion/mb_on.png differ
diff --git a/plugins/SlewDistortion/oversample_16x_off.png b/plugins/SlewDistortion/oversample_16x_off.png
new file mode 100644
index 00000000000..ad85c1cdf4b
Binary files /dev/null and b/plugins/SlewDistortion/oversample_16x_off.png differ
diff --git a/plugins/SlewDistortion/oversample_16x_on.png b/plugins/SlewDistortion/oversample_16x_on.png
new file mode 100644
index 00000000000..18c440cb7e7
Binary files /dev/null and b/plugins/SlewDistortion/oversample_16x_on.png differ
diff --git a/plugins/SlewDistortion/oversample_1x_off.png b/plugins/SlewDistortion/oversample_1x_off.png
new file mode 100644
index 00000000000..3f7a8408084
Binary files /dev/null and b/plugins/SlewDistortion/oversample_1x_off.png differ
diff --git a/plugins/SlewDistortion/oversample_1x_on.png b/plugins/SlewDistortion/oversample_1x_on.png
new file mode 100644
index 00000000000..a7231eb407d
Binary files /dev/null and b/plugins/SlewDistortion/oversample_1x_on.png differ
diff --git a/plugins/SlewDistortion/oversample_2x_off.png b/plugins/SlewDistortion/oversample_2x_off.png
new file mode 100644
index 00000000000..91eb6321116
Binary files /dev/null and b/plugins/SlewDistortion/oversample_2x_off.png differ
diff --git a/plugins/SlewDistortion/oversample_2x_on.png b/plugins/SlewDistortion/oversample_2x_on.png
new file mode 100644
index 00000000000..e66991d8436
Binary files /dev/null and b/plugins/SlewDistortion/oversample_2x_on.png differ
diff --git a/plugins/SlewDistortion/oversample_32x_off.png b/plugins/SlewDistortion/oversample_32x_off.png
new file mode 100644
index 00000000000..c9ec66b40e3
Binary files /dev/null and b/plugins/SlewDistortion/oversample_32x_off.png differ
diff --git a/plugins/SlewDistortion/oversample_32x_on.png b/plugins/SlewDistortion/oversample_32x_on.png
new file mode 100644
index 00000000000..b16e6cb40ed
Binary files /dev/null and b/plugins/SlewDistortion/oversample_32x_on.png differ
diff --git a/plugins/SlewDistortion/oversample_4x_off.png b/plugins/SlewDistortion/oversample_4x_off.png
new file mode 100644
index 00000000000..a9ad46909f1
Binary files /dev/null and b/plugins/SlewDistortion/oversample_4x_off.png differ
diff --git a/plugins/SlewDistortion/oversample_4x_on.png b/plugins/SlewDistortion/oversample_4x_on.png
new file mode 100644
index 00000000000..95ce8b54e0b
Binary files /dev/null and b/plugins/SlewDistortion/oversample_4x_on.png differ
diff --git a/plugins/SlewDistortion/oversample_8x_off.png b/plugins/SlewDistortion/oversample_8x_off.png
new file mode 100644
index 00000000000..84a91727ce8
Binary files /dev/null and b/plugins/SlewDistortion/oversample_8x_off.png differ
diff --git a/plugins/SlewDistortion/oversample_8x_on.png b/plugins/SlewDistortion/oversample_8x_on.png
new file mode 100644
index 00000000000..bdc12455484
Binary files /dev/null and b/plugins/SlewDistortion/oversample_8x_on.png differ
diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt
index fe4a2c462b2..2c017116d24 100644
--- a/src/gui/CMakeLists.txt
+++ b/src/gui/CMakeLists.txt
@@ -104,6 +104,7 @@ SET(LMMS_SRCS
 	gui/widgets/CaptionMenu.cpp
 	gui/widgets/ComboBox.cpp
 	gui/widgets/CustomTextKnob.cpp
+	gui/widgets/Draggable.cpp
 	gui/widgets/Fader.cpp
 	gui/widgets/FloatModelEditorBase.cpp
 	gui/widgets/Graph.cpp
diff --git a/src/gui/widgets/Draggable.cpp b/src/gui/widgets/Draggable.cpp
new file mode 100644
index 00000000000..e579b94fd63
--- /dev/null
+++ b/src/gui/widgets/Draggable.cpp
@@ -0,0 +1,128 @@
+/*
+ * Draggable.h
+ *
+ * Copyright (c) 2022 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 "Draggable.h"
+#include "SimpleTextFloat.h"
+#include "interpolation.h"
+
+#include <QPixmap>
+#include <QMouseEvent>
+#include <QPainter>
+
+namespace lmms::gui
+{
+
+Draggable::Draggable(FloatModelEditorBase::DirectionOfManipulation directionOfManipulation, 
+	FloatModel* floatModel, const QPixmap &pixmap, int pointA, int pointB, QWidget* parent)
+	: FloatModelEditorBase(directionOfManipulation, parent),
+	  m_pixmap(pixmap),
+	  m_defaultValPixmap(),
+	  m_pointA(pointA),
+	  m_pointB(pointB),
+	  m_defaultValue(0),
+	  m_hasDefaultValPixmap(false)
+{
+	setModel(floatModel);
+	connect(model(), &FloatModel::dataChanged, this, &Draggable::handleMovement);
+	handleMovement();
+}
+
+QSize Draggable::sizeHint() const
+{
+	return m_pixmap.size();
+}
+
+void Draggable::setPixmap(const QPixmap &pixmap)
+{
+	m_pixmap = pixmap;
+	update();
+}
+
+void Draggable::setDefaultValPixmap(const QPixmap &pixmap, float value)
+{
+	m_defaultValPixmap = pixmap;
+	m_defaultValue = value;
+	m_hasDefaultValPixmap = true;
+	update();
+}
+
+void Draggable::paintEvent(QPaintEvent *event)
+{
+	Q_UNUSED(event);
+	QPainter painter(this);
+	
+	if (m_hasDefaultValPixmap && model()->value() == m_defaultValue)
+	{
+		painter.drawPixmap(rect(), m_defaultValPixmap, m_defaultValPixmap.rect());
+	}
+	else
+	{
+		painter.drawPixmap(rect(), m_pixmap, m_pixmap.rect());
+	}
+}
+
+void Draggable::mouseMoveEvent(QMouseEvent *me)
+{
+	QPoint pPos = mapToParent(me->pos());
+
+	if (m_buttonPressed && pPos != m_lastMousePos)
+	{
+		float point = (m_directionOfManipulation == DirectionOfManipulation::Vertical) ? pPos.y() : pPos.x();
+		float progress = (point - m_pointA) / (m_pointB - m_pointA);
+
+		if (progress >= 0 && progress <= 1)
+		{
+			float newVal = progress * (model()->maxValue() - model()->minValue()) + model()->minValue();
+			model()->setValue(newVal);
+		}
+		else if (progress < 0)
+		{
+			model()->setValue(model()->minValue());
+		}
+		else
+		{
+			model()->setValue(model()->maxValue());
+		}
+
+		emit sliderMoved(model()->value());
+		m_lastMousePos = pPos;
+		s_textFloat->setText(displayValue());
+		s_textFloat->moveGlobal(this, QPoint(width() + 2, 0));
+	}
+}
+
+void Draggable::handleMovement()
+{
+	float newCoord = linearInterpolate(m_pointA, m_pointB, (model()->value() - model()->minValue()) / (model()->maxValue() - model()->minValue()));
+	if (m_directionOfManipulation == DirectionOfManipulation::Vertical)
+	{
+		move(x(), newCoord - m_pixmap.height() / 2.f);
+	}
+	else if (m_directionOfManipulation == DirectionOfManipulation::Horizontal)
+	{
+		move(newCoord - m_pixmap.width() / 2.f, y());
+	}
+}
+
+} // namespace lmms::gui