Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add SlewDistortion effect and oversampling support #7641

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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cmake/modules/PluginList.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ SET(LMMS_PLUGIN_LIST
Sf2Player
Sfxr
Sid
SlewDistortion
SlicerT
SpectrumAnalyzer
StereoEnhancer
Expand Down
63 changes: 63 additions & 0 deletions include/Draggable.h
Original file line number Diff line number Diff line change
@@ -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
12 changes: 5 additions & 7 deletions include/FloatModelEditorBase.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -114,6 +107,11 @@ private slots:
bool m_buttonPressed;

DirectionOfManipulation m_directionOfManipulation;

private slots:
virtual void enterValue();
void friendlyUpdate();
void toggleScale();
};

} // namespace lmms::gui
Expand Down
226 changes: 226 additions & 0 deletions include/OversamplingHelpers.h
Original file line number Diff line number Diff line change
@@ -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

51 changes: 51 additions & 0 deletions include/lmms_math.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@
#include "lmms_constants.h"
#include "lmmsconfig.h"

#ifdef __SSE2__
#include <emmintrin.h>
#endif

namespace lmms
{

Expand Down Expand Up @@ -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
4 changes: 4 additions & 0 deletions plugins/SlewDistortion/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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)
Loading
Loading