diff --git a/cmake/modules/PluginList.cmake b/cmake/modules/PluginList.cmake index 8dc1f72f6..55d457982 100644 --- a/cmake/modules/PluginList.cmake +++ b/cmake/modules/PluginList.cmake @@ -34,6 +34,7 @@ SET(LMMS_PLUGIN_LIST CrossoverEQ Delay Dispersion + Disintegrator DualFilter DynamicsProcessor Eq diff --git a/plugins/Disintegrator/CMakeLists.txt b/plugins/Disintegrator/CMakeLists.txt new file mode 100644 index 000000000..7642f8138 --- /dev/null +++ b/plugins/Disintegrator/CMakeLists.txt @@ -0,0 +1,3 @@ +INCLUDE(BuildPlugin) + +BUILD_PLUGIN(disintegrator Disintegrator.cpp DisintegratorControls.cpp DisintegratorControlDialog.cpp MOCFILES DisintegratorControls.h DisintegratorControlDialog.h EMBEDDED_RESOURCES artwork.png logo.png) diff --git a/plugins/Disintegrator/Disintegrator.cpp b/plugins/Disintegrator/Disintegrator.cpp new file mode 100644 index 000000000..0eb3d1ae4 --- /dev/null +++ b/plugins/Disintegrator/Disintegrator.cpp @@ -0,0 +1,240 @@ +/* + * Disintegrator.cpp + * + * Copyright (c) 2019 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 "Disintegrator.h" + +#include "embed.h" +#include "lmms_math.h" +#include "plugin_export.h" + +extern "C" +{ + +Plugin::Descriptor PLUGIN_EXPORT disintegrator_plugin_descriptor = +{ + STRINGIFY(PLUGIN_NAME), + "Disintegrator", + QT_TRANSLATE_NOOP("pluginBrowser", "A delay modulation effect for very aggressive digital distortion."), + "Lost Robot ", + 0x0100, + Plugin::Effect, + new PluginPixmapLoader("logo"), + NULL, + NULL +} ; + +} + + + +DisintegratorEffect::DisintegratorEffect(Model* parent, const Descriptor::SubPluginFeatures::Key* key) : + Effect(&disintegrator_plugin_descriptor, parent, key), + m_disintegratorControls(this), + m_needsUpdate(true) +{ + sampleRateChanged(); +} + + + +void DisintegratorEffect::sampleRateChanged() +{ + m_sampleRate = Engine::mixer()->processingSampleRate(); + m_2PiOverSR = F_2PI / m_sampleRate; + m_lp = new BasicFilters<2>(Engine::mixer()->processingSampleRate()); + m_hp = new BasicFilters<2>(Engine::mixer()->processingSampleRate()); + m_lp->setFilterType(0); + m_hp->setFilterType(1); + m_needsUpdate = true; + + m_bufferSize = m_sampleRate * 0.01f + 1.f; + for (int i = 0; i < 2; ++i) + { + m_inBuf[i].resize(m_bufferSize); + } +} + + + +bool DisintegratorEffect::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 ValueBuffer * amountBuf = m_disintegratorControls.m_amountModel.valueBuffer(); + const ValueBuffer * typeBuf = m_disintegratorControls.m_typeModel.valueBuffer(); + const ValueBuffer * freqBuf = m_disintegratorControls.m_lowCutModel.valueBuffer(); + + // Update filters + if(m_needsUpdate || m_disintegratorControls.m_highCutModel.isValueChanged()) + { + m_lp->calcFilterCoeffs(m_disintegratorControls.m_highCutModel.value(), 0.5); + + } + if(m_needsUpdate || m_disintegratorControls.m_lowCutModel.isValueChanged()) + { + m_hp->calcFilterCoeffs(m_disintegratorControls.m_lowCutModel.value(), 0.5); + } + m_needsUpdate = false; + + for (fpp_t f = 0; f < frames; ++f) + { + const float amount = (amountBuf ? amountBuf->value(f) : m_disintegratorControls.m_amountModel.value()) * 0.001f * m_sampleRate; + const int type = typeBuf ? typeBuf->value(f) : m_disintegratorControls.m_typeModel.value(); + const float freq = freqBuf ? freqBuf->value(f) : m_disintegratorControls.m_freqModel.value(); + + sample_t s[2] = {buf[f][0], buf[f][1]}; + + // Increment buffer read point + ++m_inBufLoc; + if (m_inBufLoc >= m_bufferSize) + { + m_inBufLoc = 0; + } + + // Write dry input to buffer + m_inBuf[0][m_inBufLoc] = s[0]; + m_inBuf[1][m_inBufLoc] = s[1]; + + float newInBufLoc[2] = {0, 0}; + float newInBufLocFrac[2] = {0, 0}; + + // Generate white noise or sine wave, apply filters, subtract the + // result from the buffer read point and store in a variable. + sampleFrame delayModInput = {0, 0}; + switch (type) + { + case 0://Mono Noise + { + delayModInput[0] = fast_rand() / (float)FAST_RAND_MAX * 2.f - 1.f; + delayModInput[1] = delayModInput[0]; + break; + } + case 1://Stereo Noise + { + delayModInput[0] = fast_rand() / (float)FAST_RAND_MAX * 2.f - 1.f; + delayModInput[1] = fast_rand() / (float)FAST_RAND_MAX * 2.f - 1.f; + break; + } + case 2://Sine Wave + { + m_sineLoc = fmod(m_sineLoc + (freq * m_2PiOverSR), F_2PI); + delayModInput[0] = sin(m_sineLoc); + delayModInput[1] = delayModInput[0]; + break; + } + case 3:// Self-Modulation + { + delayModInput[0] = qBound(-1.f, s[0], 1.f); + delayModInput[1] = qBound(-1.f, s[1], 1.f); + break; + } + } + + for (int i = 0; i < 2; ++i) + { + newInBufLoc[i] = delayModInput[i]; + + if (type != 2)// Sine mode doesn't use filters + { + newInBufLoc[i] = m_hp->update(newInBufLoc[i], i); + newInBufLoc[i] = m_lp->update(newInBufLoc[i], i); + } + + newInBufLoc[i] = (newInBufLoc[i] + 1) * 0.5f; + + newInBufLoc[i] = realfmod(m_inBufLoc - newInBufLoc[i] * amount, m_bufferSize); + + // Distance between samples + newInBufLocFrac[i] = fmod(newInBufLoc[i], 1); + } + + + + for (int i = 0; i < 2; ++i) + { + if (newInBufLocFrac[i] == 0) + { + s[i] = m_inBuf[i][newInBufLoc[i]]; + } + else + { + if (newInBufLoc[i] < m_bufferSize - 1) + { + s[i] = m_inBuf[i][floor(newInBufLoc[i])] * (1 - newInBufLocFrac[i]) + m_inBuf[i][ceil(newInBufLoc[i])] * newInBufLocFrac[i]; + } + else// For when the interpolation wraps around to the beginning of the buffer + { + s[i] = m_inBuf[i][m_bufferSize - 1] * (1 - newInBufLocFrac[i]) + m_inBuf[i][0] * newInBufLocFrac[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][1]; + } + + checkGate(outSum / frames); + + return isRunning(); +} + + + +// Handles negative values properly, unlike fmod. +inline float DisintegratorEffect::realfmod(float k, float n) +{ + float r = fmod(k, n); + return r < 0 ? r + n : r; +} + + + +void DisintegratorEffect::clearFilterHistories() +{ + m_lp->clearHistory(); + m_hp->clearHistory(); +} + + + +extern "C" +{ + +// necessary for getting instance out of shared lib +PLUGIN_EXPORT Plugin * lmms_plugin_main(Model* parent, void* data) +{ + return new DisintegratorEffect(parent, static_cast(data)); +} + +} + diff --git a/plugins/Disintegrator/Disintegrator.h b/plugins/Disintegrator/Disintegrator.h new file mode 100644 index 000000000..5a79420b9 --- /dev/null +++ b/plugins/Disintegrator/Disintegrator.h @@ -0,0 +1,74 @@ +/* + * Disintegrator.h + * + * Copyright (c) 2019 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 DISINTEGRATOR_H +#define DISINTEGRATOR_H + +#include "DisintegratorControls.h" + +#include "BasicFilters.h" +#include "Effect.h" +#include "ValueBuffer.h" + + +class DisintegratorEffect : public Effect +{ +public: + DisintegratorEffect(Model* parent, const Descriptor::SubPluginFeatures::Key* key); + bool processAudioBuffer(sampleFrame* buf, const fpp_t frames) override; + + EffectControls* controls() override + { + return &m_disintegratorControls; + } + + void sampleRateChanged(); + + inline float realfmod(float k, float n); + + void clearFilterHistories(); + +private: + DisintegratorControls m_disintegratorControls; + + std::vector m_inBuf[2]; + int m_inBufLoc = 0; + + float m_sineLoc = 0; + + BasicFilters<2> * m_lp; + BasicFilters<2> * m_hp; + bool m_needsUpdate; + + float m_sampleRate; + float m_2PiOverSR; + float m_sampleRateMult; + int m_bufferSize = 500; + + friend class DisintegratorControls; + +} ; + +#endif diff --git a/plugins/Disintegrator/DisintegratorControlDialog.cpp b/plugins/Disintegrator/DisintegratorControlDialog.cpp new file mode 100644 index 000000000..a75e838d6 --- /dev/null +++ b/plugins/Disintegrator/DisintegratorControlDialog.cpp @@ -0,0 +1,97 @@ +/* + * DisintegratorControlDialog.cpp + * + * Copyright (c) 2019 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 + +#include "DisintegratorControlDialog.h" +#include "DisintegratorControls.h" + +#include "embed.h" +#include "gui_templates.h" + + + +DisintegratorControlDialog::DisintegratorControlDialog(DisintegratorControls* controls) : + EffectControlDialog(controls), + m_controls(controls) +{ + setAutoFillBackground(true); + QPalette pal; + pal.setBrush(backgroundRole(), PLUGIN_NAME::getIconPixmap("artwork")); + setPalette(pal); + setFixedSize(179, 97); + + m_lowCutKnob = new Knob(knobBright_26, this); + m_lowCutKnob -> move(70, 10); + m_lowCutKnob->setModel(&m_controls->m_lowCutModel); + m_lowCutKnob->setLabel(tr("LOW CUT")); + m_lowCutKnob->setHintText(tr("Low Cut:"), " Hz"); + + m_highCutKnob = new Knob(knobBright_26, this); + m_highCutKnob -> move(125, 10); + m_highCutKnob->setModel(&m_controls->m_highCutModel); + m_highCutKnob->setLabel(tr("HIGH CUT")); + m_highCutKnob->setHintText(tr("High Cut:"), " Hz"); + + m_amountKnob = new Knob(knobBright_26, this); + m_amountKnob -> move(15, 10); + m_amountKnob -> setVolumeKnob(true); + m_amountKnob->setModel(&m_controls->m_amountModel); + m_amountKnob->setLabel(tr("AMOUNT")); + m_amountKnob->setHintText(tr("Amount:"), " ms"); + + m_typeBox = new ComboBox(this); + m_typeBox->setGeometry(1000, 5, 149, 22); + m_typeBox->setFont(pointSize<8>(m_typeBox->font())); + m_typeBox->move(14, 65); + m_typeBox->setModel(&m_controls->m_typeModel); + + m_freqKnob = new Knob(knobBright_26, this); + m_freqKnob -> move(132, 10); + m_freqKnob->setModel(&m_controls->m_freqModel); + m_freqKnob->setLabel(tr("FREQ")); + m_freqKnob->setHintText(tr("Frequency:"), " Hz"); + + connect(&m_controls->m_typeModel, SIGNAL(dataChanged()), this, SLOT(updateKnobVisibility())); + emit m_controls->m_typeModel.dataChanged();// Needed to update knobs when view is opened +} + + +/* Switches between the lowcut/highcut and +frequency knobs depending on the modulation type. */ +void DisintegratorControlDialog::updateKnobVisibility() +{ + if (m_controls->m_typeModel.value() == 2)// Sine Mode + { + m_lowCutKnob->hide(); + m_highCutKnob->hide(); + m_freqKnob->show(); + } + else + { + m_lowCutKnob->show(); + m_highCutKnob->show(); + m_freqKnob->hide(); + } +} diff --git a/plugins/Disintegrator/DisintegratorControlDialog.h b/plugins/Disintegrator/DisintegratorControlDialog.h new file mode 100644 index 000000000..9ce4bc667 --- /dev/null +++ b/plugins/Disintegrator/DisintegratorControlDialog.h @@ -0,0 +1,56 @@ +/* + * DisintegratorControlDialog.h + * + * Copyright (c) 2019 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 DISINTEGRATOR_CONTROL_DIALOG_H +#define DISINTEGRATOR_CONTROL_DIALOG_H + +#include "ComboBox.h" +#include "EffectControlDialog.h" +#include "Knob.h" + + +class DisintegratorControls; + + +class DisintegratorControlDialog : public EffectControlDialog +{ + Q_OBJECT +public: + DisintegratorControlDialog(DisintegratorControls* controls); + DisintegratorControls * m_controls; + +private slots: + void updateKnobVisibility(); + +private: + Knob * m_lowCutKnob; + Knob * m_highCutKnob; + Knob * m_amountKnob; + ComboBox * m_typeBox; + Knob * m_freqKnob; + + friend class DisintegratorControls; +}; + +#endif diff --git a/plugins/Disintegrator/DisintegratorControls.cpp b/plugins/Disintegrator/DisintegratorControls.cpp new file mode 100644 index 000000000..4cd54b5e7 --- /dev/null +++ b/plugins/Disintegrator/DisintegratorControls.cpp @@ -0,0 +1,87 @@ +/* + * DisintegratorControls.cpp + * + * Copyright (c) 2019 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 + +#include "DisintegratorControls.h" +#include "Disintegrator.h" + +#include "Engine.h" +#include "Song.h" + + +DisintegratorControls::DisintegratorControls(DisintegratorEffect* effect) : + EffectControls(effect), + m_effect(effect), + m_lowCutModel(20.0f, 20.0f, 20000.0f, 0.01f, this, tr("Low Cut Frequency")), + m_highCutModel(20000.0f, 20.0f, 20000.0f, 0.01f, this, tr("High Cut Frequency")), + m_amountModel(1.6f, 0.0f, 10.0f, 0.001f, this, tr("Amount")), + m_typeModel(this, tr("Type")), + m_freqModel(100.0f, 1.0f, 21050.0f, 0.01f, this, tr("Frequency")) +{ + // All of these are much easier to tweak when logarithmic + m_lowCutModel.setScaleLogarithmic(true); + m_highCutModel.setScaleLogarithmic(true); + m_amountModel.setScaleLogarithmic(true); + m_freqModel.setScaleLogarithmic(true); + + m_typeModel.addItem(tr("Mono Noise")); + m_typeModel.addItem(tr("Stereo Noise")); + m_typeModel.addItem(tr("Sine Wave")); + m_typeModel.addItem(tr("Self-Modulation")); + + connect(Engine::mixer(), SIGNAL(sampleRateChanged()), this, SLOT(sampleRateChanged())); +} + + +void DisintegratorControls::saveSettings(QDomDocument& doc, QDomElement& _this) +{ + m_lowCutModel.saveSettings(doc, _this, "lowCut"); + m_highCutModel.saveSettings(doc, _this, "highCut"); + m_amountModel.saveSettings(doc, _this, "amount"); + m_typeModel.saveSettings(doc, _this, "type"); + m_freqModel.saveSettings(doc, _this, "freq"); +} + + + +void DisintegratorControls::loadSettings(const QDomElement& _this) +{ + m_lowCutModel.loadSettings(_this, "lowCut"); + m_highCutModel.loadSettings(_this, "highCut"); + m_amountModel.loadSettings(_this, "amount"); + m_typeModel.loadSettings(_this, "type"); + m_freqModel.loadSettings(_this, "freq"); + + m_effect->m_needsUpdate = true; + m_effect->clearFilterHistories(); +} + + + +void DisintegratorControls::sampleRateChanged() +{ + m_effect->sampleRateChanged(); +} diff --git a/plugins/Disintegrator/DisintegratorControls.h b/plugins/Disintegrator/DisintegratorControls.h new file mode 100644 index 000000000..0be62c570 --- /dev/null +++ b/plugins/Disintegrator/DisintegratorControls.h @@ -0,0 +1,77 @@ +/* + * DisintegratorControls.h + * + * Copyright (c) 2019 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 DISINTEGRATOR_CONTROLS_H +#define DISINTEGRATOR_CONTROLS_H + +#include "DisintegratorControlDialog.h" + +#include "ComboBox.h" +#include "EffectControls.h" +#include "Knob.h" + + +class DisintegratorEffect; + + +class DisintegratorControls : public EffectControls +{ + Q_OBJECT +public: + DisintegratorControls(DisintegratorEffect* effect); + + void saveSettings(QDomDocument & _doc, QDomElement & _parent) override; + void loadSettings(const QDomElement & _this) override; + inline QString nodeName() const override + { + return "DisintegratorControls"; + } + + int controlCount() override + { + return 5; + } + + EffectControlDialog* createView() override + { + return new DisintegratorControlDialog(this); + } + +private slots: + void sampleRateChanged(); + +private: + DisintegratorEffect* m_effect; + + FloatModel m_lowCutModel; + FloatModel m_highCutModel; + FloatModel m_amountModel; + ComboBoxModel m_typeModel; + FloatModel m_freqModel; + + friend class DisintegratorControlDialog; + friend class DisintegratorEffect; +} ; + +#endif diff --git a/plugins/Disintegrator/artwork.png b/plugins/Disintegrator/artwork.png new file mode 100644 index 000000000..5598b32db Binary files /dev/null and b/plugins/Disintegrator/artwork.png differ diff --git a/plugins/Disintegrator/logo.png b/plugins/Disintegrator/logo.png new file mode 100644 index 000000000..9340da708 Binary files /dev/null and b/plugins/Disintegrator/logo.png differ