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 000000000..17e3b9a11 Binary files /dev/null and b/plugins/Dispersion/artwork.png differ diff --git a/plugins/Dispersion/dc_active.png b/plugins/Dispersion/dc_active.png new file mode 100644 index 000000000..d9c8c9378 Binary files /dev/null and b/plugins/Dispersion/dc_active.png differ diff --git a/plugins/Dispersion/dc_inactive.png b/plugins/Dispersion/dc_inactive.png new file mode 100644 index 000000000..9a0ee0693 Binary files /dev/null and b/plugins/Dispersion/dc_inactive.png differ diff --git a/plugins/Dispersion/logo.png b/plugins/Dispersion/logo.png new file mode 100644 index 000000000..9340da708 Binary files /dev/null and b/plugins/Dispersion/logo.png differ