From 2e572caa58a0fecf521865a0330687b8b7462285 Mon Sep 17 00:00:00 2001 From: Lost Robot <34612565+LostRobotMusic@users.noreply.github.com> Date: Wed, 5 Apr 2023 21:08:24 -0700 Subject: [PATCH] Add Dispersion effect (#6683) --- cmake/modules/PluginList.cmake | 1 + plugins/Dispersion/CMakeLists.txt | 3 + plugins/Dispersion/Dispersion.cpp | 162 ++++++++++++++++++ plugins/Dispersion/Dispersion.h | 78 +++++++++ .../Dispersion/DispersionControlDialog.cpp | 82 +++++++++ plugins/Dispersion/DispersionControlDialog.h | 52 ++++++ plugins/Dispersion/DispersionControls.cpp | 73 ++++++++ plugins/Dispersion/DispersionControls.h | 81 +++++++++ plugins/Dispersion/artwork.png | Bin 0 -> 8757 bytes plugins/Dispersion/dc_active.png | Bin 0 -> 9043 bytes plugins/Dispersion/dc_inactive.png | Bin 0 -> 8948 bytes plugins/Dispersion/logo.png | Bin 0 -> 774 bytes 12 files changed, 532 insertions(+) create mode 100644 plugins/Dispersion/CMakeLists.txt create mode 100644 plugins/Dispersion/Dispersion.cpp create mode 100644 plugins/Dispersion/Dispersion.h create mode 100644 plugins/Dispersion/DispersionControlDialog.cpp create mode 100644 plugins/Dispersion/DispersionControlDialog.h create mode 100644 plugins/Dispersion/DispersionControls.cpp create mode 100644 plugins/Dispersion/DispersionControls.h create mode 100644 plugins/Dispersion/artwork.png create mode 100644 plugins/Dispersion/dc_active.png create mode 100644 plugins/Dispersion/dc_inactive.png create mode 100644 plugins/Dispersion/logo.png diff --git a/cmake/modules/PluginList.cmake b/cmake/modules/PluginList.cmake index fe98a64b4..6b2c7519a 100644 --- a/cmake/modules/PluginList.cmake +++ b/cmake/modules/PluginList.cmake @@ -33,6 +33,7 @@ SET(LMMS_PLUGIN_LIST Compressor CrossoverEQ Delay + Dispersion DualFilter DynamicsProcessor Eq diff --git a/plugins/Dispersion/CMakeLists.txt b/plugins/Dispersion/CMakeLists.txt new file mode 100644 index 000000000..a40e04b80 --- /dev/null +++ b/plugins/Dispersion/CMakeLists.txt @@ -0,0 +1,3 @@ +INCLUDE(BuildPlugin) + +BUILD_PLUGIN(dispersion Dispersion.cpp DispersionControls.cpp DispersionControlDialog.cpp MOCFILES DispersionControls.h DispersionControlDialog.h EMBEDDED_RESOURCES *.png) diff --git a/plugins/Dispersion/Dispersion.cpp b/plugins/Dispersion/Dispersion.cpp new file mode 100644 index 000000000..9b98877e5 --- /dev/null +++ b/plugins/Dispersion/Dispersion.cpp @@ -0,0 +1,162 @@ +/* + * Dispersion.cpp + * + * Copyright (c) 2023 Lost Robot + * + * 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 "Dispersion.h" + +#include "embed.h" +#include "plugin_export.h" + +namespace lmms +{ + +extern "C" +{ + +Plugin::Descriptor PLUGIN_EXPORT dispersion_plugin_descriptor = +{ + LMMS_STRINGIFY(PLUGIN_NAME), + "Dispersion", + QT_TRANSLATE_NOOP("PluginBrowser", "An all-pass filter allowing for extremely high orders."), + "Lost Robot ", + 0x0100, + Plugin::Effect, + new PluginPixmapLoader("logo"), + nullptr, + nullptr +}; + +} + + +DispersionEffect::DispersionEffect(Model* parent, const Descriptor::SubPluginFeatures::Key* key) : + Effect(&dispersion_plugin_descriptor, parent, key), + m_dispersionControls(this), + m_sampleRate(Engine::audioEngine()->processingSampleRate()), + m_amountVal(0) +{ +} + + +bool DispersionEffect::processAudioBuffer(sampleFrame* buf, const fpp_t frames) +{ + if (!isEnabled() || !isRunning()) + { + return false; + } + + double outSum = 0.0; + const float d = dryLevel(); + const float w = wetLevel(); + + const int amount = m_dispersionControls.m_amountModel.value(); + const float freq = m_dispersionControls.m_freqModel.value(); + const float reso = m_dispersionControls.m_resoModel.value(); + float feedback = m_dispersionControls.m_feedbackModel.value(); + const bool dc = m_dispersionControls.m_dcModel.value(); + + // All-pass coefficient calculation + const float w0 = (F_2PI / m_sampleRate) * freq; + const float a0 = 1 + (std::sin(w0) / (reso * 2.f)); + float apCoeff1 = (1 - (a0 - 1)) / a0; + float apCoeff2 = (-2 * std::cos(w0)) / a0; + + float dcCoeff = 0.001 * (44100.f / m_sampleRate); + + if (amount != m_amountVal) + { + if (amount < m_amountVal) + { + // Flush filter buffers when they're no longer in use + for (int i = amount * 2; i < m_amountVal * 2; ++i) + { + m_state.x0[i] = m_state.x1[i] = m_state.y0[i] = m_state.y1[i] = 0; + } + } + m_amountVal = amount; + } + + if (amount == 0) + { + feedback = 0; + m_feedbackVal[0] = m_feedbackVal[1] = 0; + } + + for (fpp_t f = 0; f < frames; ++f) + { + std::array s = { buf[f][0] + m_feedbackVal[0], buf[f][1] + m_feedbackVal[1] }; + + runDispersionAP(m_amountVal, apCoeff1, apCoeff2, s); + m_feedbackVal[0] = s[0] * feedback; + m_feedbackVal[1] = s[1] * feedback; + + if (dc) + { + // DC offset removal + for (int i = 0; i < 2; ++i) + { + m_integrator[i] = m_integrator[i] * (1.f - dcCoeff) + s[i] * dcCoeff; + s[i] -= m_integrator[i]; + } + } + + buf[f][0] = d * buf[f][0] + w * s[0]; + buf[f][1] = d * buf[f][1] + w * s[1]; + outSum += buf[f][0] * buf[f][0] + buf[f][1] * buf[f][1]; + } + + checkGate(outSum / frames); + return isRunning(); +} + + +void DispersionEffect::runDispersionAP(const int filtNum, const float apCoeff1, const float apCoeff2, std::array &put) +{ + for (int i = 0; i < filtNum * 2; ++i) + { + const int channel = i % 2; + const sample_t currentInput = put[channel]; + const sample_t filterOutput = apCoeff1 * (currentInput - m_state.y1[i]) + + apCoeff2 * (m_state.x0[i] - m_state.y0[i]) + m_state.x1[i]; + m_state.x1[i] = m_state.x0[i]; + m_state.x0[i] = currentInput; + m_state.y1[i] = m_state.y0[i]; + m_state.y0[i] = filterOutput; + + put[channel] = filterOutput; + } +} + + +extern "C" +{ + +// necessary for getting instance out of shared lib +PLUGIN_EXPORT Plugin * lmms_plugin_main(Model* parent, void* data) +{ + return new DispersionEffect(parent, static_cast(data)); +} + +} + +} // namespace lmms diff --git a/plugins/Dispersion/Dispersion.h b/plugins/Dispersion/Dispersion.h new file mode 100644 index 000000000..9e2014baf --- /dev/null +++ b/plugins/Dispersion/Dispersion.h @@ -0,0 +1,78 @@ +/* + * Dispersion.h + * + * Copyright (c) 2023 Lost Robot + * + * 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_DISPERSION_H +#define LMMS_DISPERSION_H + +#include "DispersionControls.h" +#include "Effect.h" + +#include "lmms_math.h" + +namespace lmms +{ + +constexpr inline int MAX_DISPERSION_FILTERS = 999; + +class DispersionEffect : public Effect +{ +public: + DispersionEffect(Model* parent, const Descriptor::SubPluginFeatures::Key* key); + ~DispersionEffect() override = default; + bool processAudioBuffer(sampleFrame* buf, const fpp_t frames) override; + + EffectControls* controls() override + { + return &m_dispersionControls; + } + + void runDispersionAP(const int filtNum, const float apCoeff1, const float apCoeff2, std::array &put); + +private: + DispersionControls m_dispersionControls; + + float m_sampleRate; + + int m_amountVal; + + using Filter = std::array; + struct FilterState { + Filter x0{}; + Filter x1{}; + Filter y0{}; + Filter y1{}; + }; + FilterState m_state = {}; + + std::array m_feedbackVal{}; + std::array m_integrator{}; + + friend class DispersionControls; +}; + + +} // namespace lmms + +#endif // LMMS_DISPERSION_H diff --git a/plugins/Dispersion/DispersionControlDialog.cpp b/plugins/Dispersion/DispersionControlDialog.cpp new file mode 100644 index 000000000..b9f04baa2 --- /dev/null +++ b/plugins/Dispersion/DispersionControlDialog.cpp @@ -0,0 +1,82 @@ +/* + * DispersionControlDialog.cpp + * + * Copyright (c) 2023 Lost Robot + * + * 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 "DispersionControlDialog.h" +#include "DispersionControls.h" + +#include "embed.h" +#include "Knob.h" +#include "LcdSpinBox.h" +#include "PixmapButton.h" + + +namespace lmms::gui +{ + + +DispersionControlDialog::DispersionControlDialog(DispersionControls* controls) : + EffectControlDialog(controls) +{ + setAutoFillBackground(true); + QPalette pal; + pal.setBrush(backgroundRole(), PLUGIN_NAME::getIconPixmap("artwork")); + setPalette(pal); + setFixedSize(207, 50); + + LcdSpinBox * m_amountBox = new LcdSpinBox(3, this, "Amount"); + m_amountBox->setModel(&controls->m_amountModel); + m_amountBox->move(5, 10); + m_amountBox->setLabel(tr("AMOUNT")); + m_amountBox->setToolTip(tr("Number of all-pass filters")); + + Knob * freqKnob = new Knob(knobBright_26, this); + freqKnob->move(59, 8); + freqKnob->setModel(&controls->m_freqModel); + freqKnob->setLabel(tr("FREQ")); + freqKnob->setHintText(tr("Frequency:") , " Hz"); + + Knob * resoKnob = new Knob(knobBright_26, this); + resoKnob->move(99, 8); + resoKnob->setModel(&controls->m_resoModel); + resoKnob->setLabel(tr("RESO")); + resoKnob->setHintText(tr("Resonance:") , " octaves"); + + Knob * feedbackKnob = new Knob(knobBright_26, this); + feedbackKnob->move(139, 8); + feedbackKnob->setModel(&controls->m_feedbackModel); + feedbackKnob->setLabel(tr("FEED")); + feedbackKnob->setHintText(tr("Feedback:") , ""); + + PixmapButton * dcButton = new PixmapButton(this, tr("DC Offset Removal")); + dcButton->move(176, 16); + dcButton->setActiveGraphic(PLUGIN_NAME::getIconPixmap("dc_active")); + dcButton->setInactiveGraphic(PLUGIN_NAME::getIconPixmap("dc_inactive")); + dcButton->setCheckable(true); + dcButton->setModel(&controls->m_dcModel); + dcButton->setToolTip(tr("Remove DC Offset")); +} + + +} // namespace lmms::gui diff --git a/plugins/Dispersion/DispersionControlDialog.h b/plugins/Dispersion/DispersionControlDialog.h new file mode 100644 index 000000000..0d55678bd --- /dev/null +++ b/plugins/Dispersion/DispersionControlDialog.h @@ -0,0 +1,52 @@ +/* + * DispersionControlDialog.h + * + * Copyright (c) 2023 Lost Robot + * + * 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_DISPERSION_CONTROL_DIALOG_H +#define LMMS_GUI_DISPERSION_CONTROL_DIALOG_H + +#include "EffectControlDialog.h" + +namespace lmms +{ + +class DispersionControls; + + +namespace gui +{ + +class DispersionControlDialog : public EffectControlDialog +{ + Q_OBJECT +public: + DispersionControlDialog(DispersionControls* controls); + ~DispersionControlDialog() override = default; +}; + + +} // namespace gui + +} // namespace lmms + +#endif // LMMS_GUI_DISPERSION_CONTROL_DIALOG_H diff --git a/plugins/Dispersion/DispersionControls.cpp b/plugins/Dispersion/DispersionControls.cpp new file mode 100644 index 000000000..771ffb89d --- /dev/null +++ b/plugins/Dispersion/DispersionControls.cpp @@ -0,0 +1,73 @@ +/* + * DispersionControls.cpp + * + * Copyright (c) 2023 Lost Robot + * + * 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 "DispersionControls.h" +#include "Dispersion.h" + +#include + +namespace lmms +{ + +DispersionControls::DispersionControls(DispersionEffect* effect) : + EffectControls(effect), + m_effect(effect), + m_amountModel(0, 0, MAX_DISPERSION_FILTERS, this, tr("Amount")), + m_freqModel(200, 20, 20000, 0.001, this, tr("Frequency")), + m_resoModel(0.707, 0.01, 8, 0.0001, this, tr("Resonance")), + m_feedbackModel(0.f, -1.f, 1.f, 0.0001, this, tr("Feedback")), + m_dcModel(false, this, tr("DC Offset Removal")) +{ + m_freqModel.setScaleLogarithmic(true); + m_resoModel.setScaleLogarithmic(true); +} + + + +void DispersionControls::loadSettings(const QDomElement& parent) +{ + m_amountModel.loadSettings(parent, "amount"); + m_freqModel.loadSettings(parent, "freq"); + m_resoModel.loadSettings(parent, "reso"); + m_feedbackModel.loadSettings(parent, "feedback"); + m_dcModel.loadSettings(parent, "dc"); +} + + + + +void DispersionControls::saveSettings(QDomDocument& doc, QDomElement& parent) +{ + m_amountModel.saveSettings(doc, parent, "amount"); + m_freqModel.saveSettings(doc, parent, "freq"); + m_resoModel.saveSettings(doc, parent, "reso"); + m_feedbackModel.saveSettings(doc, parent, "feedback"); + m_dcModel.saveSettings(doc, parent, "dc"); +} + + +} // namespace lmms + + diff --git a/plugins/Dispersion/DispersionControls.h b/plugins/Dispersion/DispersionControls.h new file mode 100644 index 000000000..e815e1115 --- /dev/null +++ b/plugins/Dispersion/DispersionControls.h @@ -0,0 +1,81 @@ +/* + * DispersionControls.h + * + * Copyright (c) 2023 Lost Robot + * + * 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_DISPERSION_CONTROLS_H +#define LMMS_DISPERSION_CONTROLS_H + +#include "DispersionControlDialog.h" +#include "EffectControls.h" + +namespace lmms +{ + +class DispersionEffect; + +namespace gui +{ +class DispersionControlDialog; +} + + +class DispersionControls : public EffectControls +{ + Q_OBJECT +public: + DispersionControls(DispersionEffect* effect); + ~DispersionControls() override = default; + + void saveSettings(QDomDocument & doc, QDomElement & parent) override; + void loadSettings(const QDomElement & parent) override; + inline QString nodeName() const override + { + return "DispersionControls"; + } + + int controlCount() override + { + return 5; + } + + gui::EffectControlDialog* createView() override + { + return new gui::DispersionControlDialog(this); + } + +private: + DispersionEffect* m_effect; + IntModel m_amountModel; + FloatModel m_freqModel; + FloatModel m_resoModel; + FloatModel m_feedbackModel; + BoolModel m_dcModel; + + friend class gui::DispersionControlDialog; + friend class DispersionEffect; +}; + + +} // namespace lmms + +#endif // LMMS_DISPERSION_CONTROLS_H diff --git a/plugins/Dispersion/artwork.png b/plugins/Dispersion/artwork.png new file mode 100644 index 0000000000000000000000000000000000000000..17e3b9a118973f04f70c8a813f0c4667a7eb6f5d GIT binary patch literal 8757 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 F{s*w2i^Ko` literal 0 HcmV?d00001 diff --git a/plugins/Dispersion/dc_active.png b/plugins/Dispersion/dc_active.png new file mode 100644 index 0000000000000000000000000000000000000000..d9c8c93784521b5310d745be52a6a876d1782627 GIT binary patch literal 9043 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 O08G7I{{I0n?sE$O literal 0 HcmV?d00001 diff --git a/plugins/Dispersion/dc_inactive.png b/plugins/Dispersion/dc_inactive.png new file mode 100644 index 0000000000000000000000000000000000000000..9a0ee069399c47b10bdfc291a79e573eccf98591 GIT binary patch literal 8948 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 HfAfC;^8!HH literal 0 HcmV?d00001 diff --git a/plugins/Dispersion/logo.png b/plugins/Dispersion/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..9340da708dd79ed97111eb535f51b81a91d6a15b GIT binary patch literal 774 zcmV+h1Nr=kP)7WEc)VQ)zLm`B#lSD% z0Wg;#IH?7|3b0p&fj_`2;A{cm@zx+(Ge?s$@EN$6LtMjg>;+(boCbaXw;ja=b6mQ?gRp67Yf18(18niCN0v&eY^{Cr&#;#IcF{ks?!* z&o!_q>9xbSw`QytRI!M?zP_o#K-8ExJaV?vi znPt?~0KhTu23U+Gy8^Tw;^SzWSet9n