Add SlewDistortion effect and oversampling support (#7641)
This commit is contained in:
76
include/Draggable.h
Normal file
76
include/Draggable.h
Normal file
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Draggable.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_GUI_DRAGGABLE_H
|
||||
#define LMMS_GUI_DRAGGABLE_H
|
||||
|
||||
#include "FloatModelEditorBase.h"
|
||||
|
||||
namespace lmms::gui
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief A pixmap that can be dragged from one location to another to control a FloatModel
|
||||
*/
|
||||
class LMMS_EXPORT Draggable : public FloatModelEditorBase
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
using FloatModelEditorBase::DirectionOfManipulation;
|
||||
|
||||
/**
|
||||
* @param directionOfManipulation Direction the user can drag the control
|
||||
* @param floatModel Pointer to the FloatModel this draggable modifies
|
||||
* @param pixmap The pixmap shown for this handle
|
||||
* @param pointA Starting pixel position (Y if vertical, X if horizontal)
|
||||
* @param pointB Ending pixel position (Y if vertical, X if horizontal)
|
||||
* @param parent Parent QWidget
|
||||
*/
|
||||
Draggable(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 // LMMS_GUI_DRAGGABLE_H
|
||||
@@ -85,13 +85,6 @@ protected:
|
||||
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:
|
||||
bool m_buttonPressed;
|
||||
|
||||
DirectionOfManipulation m_directionOfManipulation;
|
||||
|
||||
private slots:
|
||||
virtual void enterValue();
|
||||
void friendlyUpdate();
|
||||
void toggleScale();
|
||||
};
|
||||
|
||||
} // namespace lmms::gui
|
||||
|
||||
249
include/OversamplingHelpers.h
Executable file
249
include/OversamplingHelpers.h
Executable file
@@ -0,0 +1,249 @@
|
||||
/*
|
||||
* OversamplingHelpers.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_OVERSAMPLING_HELPERS_H
|
||||
#define LMMS_OVERSAMPLING_HELPERS_H
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
|
||||
#include <hiir/PolyphaseIir2Designer.h>
|
||||
|
||||
#ifdef __SSE2__
|
||||
#include <hiir/Downsampler2xSse.h>
|
||||
#include <hiir/Upsampler2xSse.h>
|
||||
#else
|
||||
#include <hiir/Downsampler2xFpu.h>
|
||||
#include <hiir/Upsampler2xFpu.h>
|
||||
#endif
|
||||
|
||||
|
||||
inline constexpr float HIIR_DEFAULT_PASSBAND = 19600;
|
||||
inline 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[s_firstCoefCount];
|
||||
hiir::PolyphaseIir2Designer::compute_coefs_spec_order_tbw(coefsFirst, s_firstCoefCount, bw);
|
||||
m_upsampleFirst.set_coefs(coefsFirst);
|
||||
m_upsampleFirst.clear_buffers();
|
||||
bw = (bw + 0.5f) * 0.5f;
|
||||
|
||||
// Stage 2
|
||||
double coefsSecond[s_secondCoefCount];
|
||||
hiir::PolyphaseIir2Designer::compute_coefs_spec_order_tbw(coefsSecond, s_secondCoefCount, bw);
|
||||
m_upsampleSecond.set_coefs(coefsSecond);
|
||||
m_upsampleSecond.clear_buffers();
|
||||
bw = (bw + 0.5f) * 0.5f;
|
||||
|
||||
// Remaining stages
|
||||
for (int i = 0; i < m_stages - 2; ++i)
|
||||
{
|
||||
double coefsRest[s_restCoefCount];
|
||||
hiir::PolyphaseIir2Designer::compute_coefs_spec_order_tbw(coefsRest, s_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();
|
||||
}
|
||||
|
||||
// expects `2 ^ m_stages` elements for the output
|
||||
void processSample(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:
|
||||
static constexpr int s_firstCoefCount = MaxCoefs;
|
||||
static constexpr int s_secondCoefCount = std::max(MaxCoefs / 2, 2);
|
||||
static constexpr int s_restCoefCount = std::max(MaxCoefs / 4, 2);
|
||||
#ifdef __SSE2__
|
||||
hiir::Upsampler2xSse<s_firstCoefCount> m_upsampleFirst;
|
||||
hiir::Upsampler2xSse<s_secondCoefCount> m_upsampleSecond;
|
||||
std::array<hiir::Upsampler2xSse<s_restCoefCount>, MaxStages - 2> m_upsampleRest;
|
||||
#else
|
||||
hiir::Upsampler2xFpu<s_firstCoefCount> m_upsampleFirst;
|
||||
hiir::Upsampler2xFpu<s_secondCoefCount> m_upsampleSecond;
|
||||
std::array<hiir::Upsampler2xFpu<s_restCoefCount>, MaxStages - 2> m_upsampleRest;
|
||||
#endif
|
||||
|
||||
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[s_firstCoefCount];
|
||||
hiir::PolyphaseIir2Designer::compute_coefs_spec_order_tbw(coefsFirst, s_firstCoefCount, bw);
|
||||
m_downsampleFirst.set_coefs(coefsFirst);
|
||||
m_downsampleFirst.clear_buffers();
|
||||
bw = (bw + 0.5f) * 0.5f;
|
||||
|
||||
// Stage 2
|
||||
double coefsSecond[s_secondCoefCount];
|
||||
hiir::PolyphaseIir2Designer::compute_coefs_spec_order_tbw(coefsSecond, s_secondCoefCount, bw);
|
||||
m_downsampleSecond.set_coefs(coefsSecond);
|
||||
m_downsampleSecond.clear_buffers();
|
||||
bw = (bw + 0.5f) * 0.5f;
|
||||
|
||||
// Remaining stages
|
||||
for (int i = 0; i < m_stages - 2; ++i)
|
||||
{
|
||||
double coefsRest[s_restCoefCount];
|
||||
hiir::PolyphaseIir2Designer::compute_coefs_spec_order_tbw(coefsRest, s_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();
|
||||
}
|
||||
|
||||
// expects `2 ^ m_stages` elements for the input
|
||||
float processSample(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:
|
||||
static constexpr int s_firstCoefCount = MaxCoefs;
|
||||
static constexpr int s_secondCoefCount = std::max(MaxCoefs / 2, 2);
|
||||
static constexpr int s_restCoefCount = std::max(MaxCoefs / 4, 2);
|
||||
#ifdef __SSE2__
|
||||
hiir::Downsampler2xSse<s_firstCoefCount> m_downsampleFirst;
|
||||
hiir::Downsampler2xSse<s_secondCoefCount> m_downsampleSecond;
|
||||
std::array<hiir::Downsampler2xSse<s_restCoefCount>, MaxStages - 2> m_downsampleRest;
|
||||
#else
|
||||
hiir::Downsampler2xFpu<s_firstCoefCount> m_downsampleFirst;
|
||||
hiir::Downsampler2xFpu<s_secondCoefCount> m_downsampleSecond;
|
||||
std::array<hiir::Downsampler2xFpu<s_restCoefCount>, MaxStages - 2> m_downsampleRest;
|
||||
#endif
|
||||
|
||||
int m_stages = 0;
|
||||
float m_sampleRate = 44100;
|
||||
float m_passband = HIIR_DEFAULT_PASSBAND;
|
||||
};
|
||||
|
||||
|
||||
} // namespace lmms
|
||||
|
||||
#endif // LMMS_OVERSAMPLING_HELPERS_H
|
||||
|
||||
@@ -36,6 +36,10 @@
|
||||
|
||||
#include "lmms_constants.h"
|
||||
|
||||
#ifdef __SSE2__
|
||||
#include <emmintrin.h>
|
||||
#endif
|
||||
|
||||
namespace lmms
|
||||
{
|
||||
|
||||
@@ -280,6 +284,77 @@ private:
|
||||
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]
|
||||
inline __m128 fastExp(__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]
|
||||
inline __m128 fastLog(__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;
|
||||
}
|
||||
|
||||
inline __m128 sse2Abs(__m128 x)
|
||||
{
|
||||
return _mm_and_ps(x, _mm_castsi128_ps(_mm_set1_epi32(0x7fffffff)));// clear sign bit
|
||||
}
|
||||
|
||||
inline __m128 sse2Floor(__m128 x)
|
||||
{
|
||||
__m128 t = _mm_cvtepi32_ps(_mm_cvttps_epi32(x)); // trunc toward 0
|
||||
__m128 needs_correction = _mm_cmplt_ps(x, t); // checks if x < trunc
|
||||
return _mm_sub_ps(t, _mm_and_ps(needs_correction, _mm_set1_ps(1.0f)));
|
||||
}
|
||||
|
||||
inline __m128 sse2Round(__m128 x)
|
||||
{
|
||||
__m128 sign_mask = _mm_cmplt_ps(x, _mm_setzero_ps());// checks if x < 0
|
||||
__m128 bias_pos = _mm_set1_ps(0.5f);
|
||||
__m128 bias_neg = _mm_set1_ps(-0.5f);
|
||||
__m128 bias = _mm_or_ps(_mm_and_ps(sign_mask, bias_neg), _mm_andnot_ps(sign_mask, bias_pos));
|
||||
__m128 y = _mm_add_ps(x, bias);
|
||||
return _mm_cvtepi32_ps(_mm_cvttps_epi32(y));
|
||||
}
|
||||
|
||||
#endif // __SSE2__
|
||||
|
||||
} // namespace lmms
|
||||
|
||||
#endif // LMMS_MATH_H
|
||||
|
||||
Reference in New Issue
Block a user