diff --git a/cmake/modules/PluginList.cmake b/cmake/modules/PluginList.cmake index a2871bf99ec..81b35295397 100644 --- a/cmake/modules/PluginList.cmake +++ b/cmake/modules/PluginList.cmake @@ -58,6 +58,7 @@ SET(LMMS_PLUGIN_LIST sfxr sid SpectrumAnalyzer + StereoControl stereo_enhancer stereo_matrix stk diff --git a/plugins/StereoControl/CMakeLists.txt b/plugins/StereoControl/CMakeLists.txt new file mode 100755 index 00000000000..3a5d3dafbe4 --- /dev/null +++ b/plugins/StereoControl/CMakeLists.txt @@ -0,0 +1,3 @@ +INCLUDE(BuildPlugin) + +BUILD_PLUGIN(stereocontrol StereoControl.cpp StereoControlControls.cpp StereoControlControlDialog.cpp MOCFILES StereoControlControls.h StereoControlControlDialog.h EMBEDDED_RESOURCES "${CMAKE_CURRENT_SOURCE_DIR}/*.png") diff --git a/plugins/StereoControl/StereoControl.cpp b/plugins/StereoControl/StereoControl.cpp new file mode 100755 index 00000000000..badae21f65b --- /dev/null +++ b/plugins/StereoControl/StereoControl.cpp @@ -0,0 +1,445 @@ +/* + * StereoControl.cpp + * + * Copyright (c) 2020 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 "StereoControl.h" + +#include "embed.h" +#include "interpolation.h" +#include "lmms_math.h" +#include "plugin_export.h" + +extern "C" +{ + +Plugin::Descriptor PLUGIN_EXPORT stereocontrol_plugin_descriptor = +{ + STRINGIFY(PLUGIN_NAME), + "Stereo Control", + QT_TRANSLATE_NOOP("PluginBrowser", "General utility and stereo processing, including multiple types of panning and stereo enhancement."), + "Lost Robot ", + 0x0100, + Plugin::Effect, + new PluginPixmapLoader("logo"), + NULL, + NULL +}; + +} + + + +StereoControlEffect::StereoControlEffect(Model* parent, const Descriptor::SubPluginFeatures::Key* key) : + Effect(&stereocontrol_plugin_descriptor, parent, key), + m_stereocontrolControls(this) +{ + changeSampleRate(); + + connect(Engine::mixer(), SIGNAL(sampleRateChanged()), this, SLOT(changeSampleRate())); +} + + + +bool StereoControlEffect::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 * gainBuf = m_stereocontrolControls.m_gainModel.valueBuffer(); + const ValueBuffer * stereoizerBuf = m_stereocontrolControls.m_stereoizerModel.valueBuffer(); + const ValueBuffer * widthBuf = m_stereocontrolControls.m_widthModel.valueBuffer(); + const ValueBuffer * panModeBuf = m_stereocontrolControls.m_panModeModel.valueBuffer(); + const ValueBuffer * panBuf = m_stereocontrolControls.m_panModel.valueBuffer(); + const ValueBuffer * monoBuf = m_stereocontrolControls.m_monoModel.valueBuffer(); + const ValueBuffer * dcRemovalBuf = m_stereocontrolControls.m_dcRemovalModel.valueBuffer(); + const ValueBuffer * muteBuf = m_stereocontrolControls.m_muteModel.valueBuffer(); + const ValueBuffer * monoBassBuf = m_stereocontrolControls.m_monoBassModel.valueBuffer(); + const ValueBuffer * auditionBuf = m_stereocontrolControls.m_auditionModel.valueBuffer(); + const ValueBuffer * invertLBuf = m_stereocontrolControls.m_invertLModel.valueBuffer(); + const ValueBuffer * invertRBuf = m_stereocontrolControls.m_invertRModel.valueBuffer(); + const ValueBuffer * soloChannelBuf = m_stereocontrolControls.m_soloChannelModel.valueBuffer(); + const ValueBuffer * monoBassFreqBuf = m_stereocontrolControls.m_monoBassFreqModel.valueBuffer(); + const ValueBuffer * stereoizerLPBuf = m_stereocontrolControls.m_stereoizerLPModel.valueBuffer(); + const ValueBuffer * stereoizerHPBuf = m_stereocontrolControls.m_stereoizerHPModel.valueBuffer(); + const ValueBuffer * panSpectralBuf = m_stereocontrolControls.m_panSpectralModel.valueBuffer(); + const ValueBuffer * panDelayBuf = m_stereocontrolControls.m_panDelayModel.valueBuffer(); + const ValueBuffer * panDualLBuf = m_stereocontrolControls.m_panDualLModel.valueBuffer(); + const ValueBuffer * panDualRBuf = m_stereocontrolControls.m_panDualRModel.valueBuffer(); + + for(fpp_t f = 0; f < frames; ++f) + { + const float gain = dbfsToAmp(gainBuf ? gainBuf->value(f) : m_stereocontrolControls.m_gainModel.value()); + const float stereoizer = (stereoizerBuf ? stereoizerBuf->value(f) : m_stereocontrolControls.m_stereoizerModel.value()) * 0.01f; + const float width = (widthBuf ? widthBuf->value(f) : m_stereocontrolControls.m_widthModel.value()) * 0.01f; + const int panMode = panModeBuf ? panModeBuf->value(f) : m_stereocontrolControls.m_panModeModel.value(); + const float pan = (panBuf ? panBuf->value(f) : m_stereocontrolControls.m_panModel.value()) * 0.01f; + const bool mono = monoBuf ? monoBuf->value(f) : m_stereocontrolControls.m_monoModel.value(); + const bool dcRemoval = dcRemovalBuf ? dcRemovalBuf->value(f) : m_stereocontrolControls.m_dcRemovalModel.value(); + const bool mute = muteBuf ? muteBuf->value(f) : m_stereocontrolControls.m_muteModel.value(); + const bool monoBass = monoBassBuf ? monoBassBuf->value(f) : m_stereocontrolControls.m_monoBassModel.value(); + const bool audition = auditionBuf ? auditionBuf->value(f) : m_stereocontrolControls.m_auditionModel.value(); + const bool invertL = invertLBuf ? invertLBuf->value(f) : m_stereocontrolControls.m_invertLModel.value(); + const bool invertR = invertRBuf ? invertRBuf->value(f) : m_stereocontrolControls.m_invertRModel.value(); + const int soloChannel = soloChannelBuf ? soloChannelBuf->value(f) : m_stereocontrolControls.m_soloChannelModel.value(); + const float monoBassFreq = monoBassFreqBuf ? monoBassFreqBuf->value(f) : m_stereocontrolControls.m_monoBassFreqModel.value(); + const float stereoizerLP = stereoizerLPBuf ? stereoizerLPBuf->value(f) : m_stereocontrolControls.m_stereoizerLPModel.value(); + const float stereoizerHP = stereoizerHPBuf ? stereoizerHPBuf->value(f) : m_stereocontrolControls.m_stereoizerHPModel.value(); + const float panSpectral = (panSpectralBuf ? panSpectralBuf->value(f) : m_stereocontrolControls.m_panSpectralModel.value()) * 0.01f; + const float panDelay = (panDelayBuf ? panDelayBuf->value(f) : m_stereocontrolControls.m_panDelayModel.value()) * 0.01f; + const float panDualL = (panDualLBuf ? panDualLBuf->value(f) : m_stereocontrolControls.m_panDualLModel.value()) * 0.01f; + const float panDualR = (panDualRBuf ? panDualRBuf->value(f) : m_stereocontrolControls.m_panDualRModel.value()) * 0.01f; + + sample_t s[2] = { buf[f][0], buf[f][1] }; + + switch (soloChannel) + { + case 0: + { + break; + } + case 1: + { + s[1] = s[0]; + break; + } + case 2: + { + s[0] = s[1]; + break; + } + } + + if (invertL) + { + s[0] *= -1.f; + } + + if (invertR) + { + s[1] *= -1.f; + } + + // VVV Stereoizer VVV + + /* + The Stereoizer delays the audio slightly, inverts one channel of the delayed signal, and mixes that with the dry signal. + This results in an extremely wide result, even with a mono input. + Also, when collapsed to mono, any previously-mid part of that delayed signal will be cancelled out, so it's very mono-compatible. + + Of course, delaying audio and mixing it with the dry signal creates a comb filter, which is undesirable. With certain inputs and delay + amounts, it'll even result in silence. To help reduce this effect, the delayed signal is sent through some allpass filters, which makes + the delay vary depending on frequency, so the comb filtering is significantly less obvious. + */ + + float allpassFreq = 0; + float delayLength = 0; + allpassFreq = linearInterpolate(370.f, 170.f, stereoizer * 0.5f);// Arbitrary + delayLength = m_sampleRate / allpassFreq; + + // Filter coefficient calculation + const float w0 = (F_2PI / m_sampleRate) * allpassFreq; + const float a0 = 1 + (sin(w0) / (0.707f * 2.f)); + m_filtA = (1 - (a0 - 1)) / a0; + m_filtB = (-2*cos(w0)) / a0; + + float delayResult[2] = {0}; + for (int i = 0; i < 2; ++i) + { + // Read next value from delay buffer + float readLoc = m_delayIndex[i] - delayLength; + if (readLoc < 0) {readLoc += m_delayBufSize;} + float readLocFrac = fraction(readLoc); + if (readLoc < m_delayBufSize - 1) + { + delayResult[i] = linearInterpolate(m_delayBuf[i][floor(readLoc)], m_delayBuf[i][ceil(readLoc)], readLocFrac); + } + else// For when the interpolation wraps around to the beginning of the buffer + { + delayResult[i] = linearInterpolate(m_delayBuf[i][m_delayBufSize - 1], m_delayBuf[i][0], readLocFrac); + } + + // Increment delay ring buffer index + ++m_delayIndex[i]; + if (m_delayIndex[i] >= m_delayBufSize) + { + m_delayIndex[i] -= m_delayBufSize; + } + // Send new value to delay line + m_delayBuf[i][m_delayIndex[i]] = s[i]; + } + + for (int i = 0; i < 2; ++i) + { + float allpassValue = (delayResult[0] + delayResult[1]) * 0.5f; + + for (int j = 0; j < 2; ++j) + { + allpassValue = calcAllpassFilter(allpassValue, m_sampleRate, j, i, m_filtA, m_filtB); + } + + allpassValue *= i ? -1.f : 1.f; + + float lp; + float hp; + float hp2; + multimodeFilter(allpassValue, multimodeCoeff(stereoizerHP), lp, hp, m_stereoizerFilter[0][i]); + multimodeFilter(hp, multimodeCoeff(stereoizerLP), lp, hp2, m_stereoizerFilter[1][i]); + + s[i] += lp * qMin(stereoizer, 1.f); + } + // ^^^ Stereoizer ^^^ + + const float sMid = (s[0] + s[1]) * 0.5f; + const float sSide = (s[0] - s[1]) * 0.5f; + + s[0] = sMid + sSide * width; + s[1] = sMid - sSide * width; + + if (mono) + { + s[0] = (s[0] + s[1]) * 0.5f; + s[1] = s[0]; + } + + if (monoBass) + { + float lp[2]; + float hp[2]; + + for (int i = 0; i < 2; ++i) + { + // We apply two filters because a single filter doesn't have a strong enough slope + multimodeFilter(s[i], multimodeCoeff(monoBassFreq), lp[i], hp[i], m_monoBassFilter[i][0]); + multimodeFilter(s[i], multimodeCoeff(monoBassFreq), lp[i], hp[i], m_monoBassFilter[i][1]); + } + + float lowmono = (lp[0] + lp[1]) * 0.5f; + + if (audition) + { + s[0] = lowmono; + s[1] = lowmono; + } + else + { + s[0] = lowmono - hp[0];// Subtraction is needed due to filter phase shift + s[1] = lowmono - hp[1]; + } + } + + switch (panMode) + { + case 0:// Gain + { + const float lGain = pan > 0 ? 1.f - pan : 1.f; + const float rGain = pan < 0 ? 1.f + pan : 1.f; + s[0] *= lGain; + s[1] *= rGain; + break; + } + case 1:// Stereo + { + const float lGainL = panDualL > 0 ? 1.f - panDualL : 1.f; + const float rGainL = panDualL < 0 ? 1.f + panDualL : 1.f; + const float lGainR = panDualR > 0 ? 1.f - panDualR : 1.f; + const float rGainR = panDualR < 0 ? 1.f + panDualR : 1.f; + const float temp = s[0]; + s[0] = s[0] * lGainL + s[1] * lGainR; + s[1] = temp * rGainL + s[1] * rGainR; + break; + } + case 2:// Haas + { + float haasResult = 0; + float haasDelayVal[2] = {0}; + + if (pan >= 0) + { + haasDelayVal[0] = 0.0008f * m_sampleRate * panDelay * asin(pan); + } + else + { + haasDelayVal[1] = 0.0008f * m_sampleRate * panDelay * asin(-pan); + } + + for (int i = 0; i < 2; ++i) + { + if ((i == 0 && pan > 0) || (i == 1 && pan < 0)) + { + // Read next value from delay buffer + float readLoc = m_haasIndex[i] - haasDelayVal[i]; + if (readLoc < 0) {readLoc += m_haasBufSize;} + float readLocFrac = fraction(readLoc); + if (readLoc < m_haasBufSize - 1) + { + haasResult = linearInterpolate(m_haasBuf[i][floor(readLoc)], m_haasBuf[i][ceil(readLoc)], readLocFrac); + } + else// For when the interpolation wraps around to the beginning of the buffer + { + haasResult = linearInterpolate(m_haasBuf[i][m_haasBufSize - 1], m_haasBuf[i][0], readLocFrac); + } + } + + // Increment delay ring buffer index + ++m_haasIndex[i]; + if (m_haasIndex[i] >= m_haasBufSize) + { + m_haasIndex[i] -= m_haasBufSize; + } + // Send new value to delay line + m_haasBuf[i][m_haasIndex[i]] = s[i]; + } + + if (pan != 0) + { + const float minGain = linearInterpolate(1.f, 0.15f, abs(pan)); + if (pan >= 0) + { + const float lGain = ((1.f - pan) + minGain) / (1.f + minGain); + s[0] = haasResult * lGain; + } + else + { + const float rGain = ((1.f + pan) + minGain) / (1.f + minGain); + s[1] = haasResult * rGain; + } + } + + const int lowChnl = (pan <= 0); + const int highChnl = !lowChnl; + + float lp; + float hp; + + // Filter parameters are semi-arbitrary, but are vaguely based on some random graphs I found here: + // https://goodhertz.co/panpot/ + float temp = -abs(pan) + 1.f; + float filtFreq = 2000.f + 8000.f * temp * temp; + float hpGain = abs(pan) < 0.5 ? 1.f - abs(pan) * 2.f : 0.f; + hpGain = linearInterpolate(1.f, hpGain, panSpectral); + multimodeFilter(s[lowChnl], multimodeCoeff(filtFreq), lp, hp, m_haasSpectralPanFilter[lowChnl]); + + s[lowChnl] = lp + hp * hpGain; + + filtFreq = 2000.f; + hpGain = abs(pan) + 1.f; + hpGain = linearInterpolate(1.f, hpGain, panSpectral); + multimodeFilter(s[highChnl], multimodeCoeff(filtFreq), lp, hp, m_haasSpectralPanFilter[highChnl]); + + s[highChnl] = lp + hp * hpGain; + } + } + + if (dcRemoval) + { + for (int i = 0; i < 2; ++i) + { + m_dcRemovalInfo[i] = m_dcRemovalInfo[i] * (1.f - m_dcCoeff) + s[i] * m_dcCoeff; + s[i] -= m_dcRemovalInfo[i]; + } + } + + if (mute) + { + s[0] = 0; + s[1] = 0; + } + + s[0] *= gain; + s[1] *= gain; + + 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(); +} + + +sample_t StereoControlEffect::calcAllpassFilter(sample_t inSamp, sample_rate_t Fs, int filtNum, int channel, float a, float b) +{ + float filterOutput = a * (inSamp - m_filtY[filtNum][channel][1]) + + b * (m_filtX[filtNum][channel][0] - m_filtY[filtNum][channel][0]) + + m_filtX[filtNum][channel][1]; + + m_filtX[filtNum][channel][1] = m_filtX[filtNum][channel][0]; + m_filtX[filtNum][channel][0] = inSamp; + m_filtY[filtNum][channel][1] = m_filtY[filtNum][channel][0]; + m_filtY[filtNum][channel][0] = filterOutput; + + return filterOutput; +} + + +void StereoControlEffect::multimodeFilter(sample_t in, float g, sample_t &lp, sample_t &hp, sample_t &filtBuf) +{ + lp = in * g + filtBuf; + hp = in - lp; + filtBuf = hp * g * 2.f + filtBuf; +} + +float StereoControlEffect::multimodeCoeff(float freq) +{ + const float g = 1.f / (1.f + 1.f / tan(F_PI * freq / m_sampleRate)); + return g; +} + +void StereoControlEffect::changeSampleRate() +{ + m_sampleRate = Engine::mixer()->processingSampleRate(); + + m_dcCoeff = 44.1f / m_sampleRate; + + m_delayBufSize = int(m_sampleRate / 160.f) + 1.f; + m_delayBuf[0].resize(m_delayBufSize); + m_delayBuf[1].resize(m_delayBufSize); + + m_haasBufSize = int(m_sampleRate * 0.0008f * F_PI) + 1.f; + m_haasBuf[0].resize(m_haasBufSize); + m_haasBuf[1].resize(m_haasBufSize); +} + + + + +extern "C" +{ + +// necessary for getting instance out of shared lib +PLUGIN_EXPORT Plugin * lmms_plugin_main(Model* parent, void* data) +{ + return new StereoControlEffect(parent, static_cast(data)); +} + +} + diff --git a/plugins/StereoControl/StereoControl.h b/plugins/StereoControl/StereoControl.h new file mode 100755 index 00000000000..eede97de5b9 --- /dev/null +++ b/plugins/StereoControl/StereoControl.h @@ -0,0 +1,78 @@ +/* + * StereoControl.h + * + * Copyright (c) 2020 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 STEREOCONTROL_H +#define STEREOCONTROL_H + +#include "StereoControlControls.h" + +#include "Effect.h" +#include "ValueBuffer.h" + +class StereoControlEffect : public Effect +{ +public: + StereoControlEffect(Model* parent, const Descriptor::SubPluginFeatures::Key* key); + bool processAudioBuffer(sampleFrame* buf, const fpp_t frames) override; + + EffectControls* controls() override + { + return &m_stereocontrolControls; + } + +private slots: + void changeSampleRate(); + +private: + inline sample_t calcAllpassFilter(sample_t inSamp, sample_rate_t Fs, int filtNum, int channel, float a, float b); + inline void multimodeFilter(sample_t in, float g, sample_t &lp, sample_t &hp, sample_t &filtBuf); + inline float multimodeCoeff(float freq); + + StereoControlControls m_stereocontrolControls; + + float m_filtX[2][2][2] = {{{0}}};// [filter number][channel][samples back in time] + float m_filtY[2][2][2] = {{{0}}}; + float m_filtA = 0; + float m_filtB = 0; + std::vector m_delayBuf[2]; + int m_delayBufSize = 0; + int m_delayIndex[2] = {0}; + + std::vector m_haasBuf[2]; + int m_haasBufSize = 0; + int m_haasIndex[2] = {0}; + + float m_sampleRate = 0; + + float m_dcCoeff; + float m_dcRemovalInfo[2] = {0}; + + float m_monoBassFilter[2][2] = {{0}}; + float m_stereoizerFilter[2][2] = {{0}}; + + float m_haasSpectralPanFilter[2] = {0}; +}; + +#endif diff --git a/plugins/StereoControl/StereoControlControlDialog.cpp b/plugins/StereoControl/StereoControlControlDialog.cpp new file mode 100755 index 00000000000..e3d5a10ed0f --- /dev/null +++ b/plugins/StereoControl/StereoControlControlDialog.cpp @@ -0,0 +1,231 @@ +/* + * StereoControlControlDialog.cpp + * + * Copyright (c) 2020 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 "StereoControlControlDialog.h" +#include "StereoControlControls.h" + +#include + +#include "embed.h" +#include "gui_templates.h" +#include "ToolTip.h" + + +StereoControlControlDialog::StereoControlControlDialog(StereoControlControls* controls) : + EffectControlDialog(controls), + m_controls(controls) +{ + setAutoFillBackground(true); + QPalette pal; + pal.setBrush(backgroundRole(), PLUGIN_NAME::getIconPixmap("artwork")); + setPalette(pal); + setFixedSize(335, 215); + + m_gainKnob = new Knob(knobBright_26, this); + m_gainKnob->move(15, 126); + m_gainKnob->setModel(&m_controls->m_gainModel); + m_gainKnob->setHintText(tr("Gain:"), " db"); + ToolTip::add(m_gainKnob, tr("Gain")); + + m_stereoizerKnob = new Knob(knobBright_26, this); + m_stereoizerKnob->move(155, 19); + m_stereoizerKnob->setModel(&m_controls->m_stereoizerModel); + m_stereoizerKnob->setHintText(tr("Stereoize:"), "%"); + ToolTip::add(m_stereoizerKnob, tr("Enhance width of any audio")); + + m_widthKnob = new Knob(knobBright_26, this); + m_widthKnob->move(155, 68); + m_widthKnob->setModel(&m_controls->m_widthModel); + m_widthKnob->setHintText(tr("Width:"), "%"); + ToolTip::add(m_widthKnob, tr("Adjust volume of stereo audio")); + + m_monoBassFreqKnob = new Knob(knobBright_26, this); + m_monoBassFreqKnob->move(256, 64); + m_monoBassFreqKnob->setModel(&m_controls->m_monoBassFreqModel); + m_monoBassFreqKnob->setHintText(tr("Mono Bass Frequency:"), " Hz"); + ToolTip::add(m_monoBassFreqKnob, tr("Remove sidebands from audio below this frequency")); + + m_stereoizerLPKnob = new Knob(knobSmall_17, this); + m_stereoizerLPKnob->move(192, 31); + m_stereoizerLPKnob->setModel(&m_controls->m_stereoizerLPModel); + m_stereoizerLPKnob->setHintText(tr("Stereoizer Lowpass:"), " Hz"); + ToolTip::add(m_stereoizerLPKnob, tr("Lowpass delayed signal")); + + m_stereoizerHPKnob = new Knob(knobSmall_17, this); + m_stereoizerHPKnob->move(128, 31); + m_stereoizerHPKnob->setModel(&m_controls->m_stereoizerHPModel); + m_stereoizerHPKnob->setHintText(tr("Stereoizer Highpass:"), " Hz"); + ToolTip::add(m_stereoizerHPKnob, tr("Highpass delayed signal")); + + m_panSpectralKnob = new Knob(knobSmall_17, this); + m_panSpectralKnob->move(234, 171); + m_panSpectralKnob->setModel(&m_controls->m_panSpectralModel); + m_panSpectralKnob->setLabel(tr("Spectral")); + m_panSpectralKnob->setHintText(tr("Spectral Panning:"), "%"); + ToolTip::add(m_panSpectralKnob, tr("Spectral adjustment amount")); + + m_panDelayKnob = new Knob(knobSmall_17, this); + m_panDelayKnob->move(173, 171); + m_panDelayKnob->setModel(&m_controls->m_panDelayModel); + m_panDelayKnob->setLabel(tr("Delay")); + m_panDelayKnob->setHintText(tr("Panning Delay:"), "%"); + ToolTip::add(m_panDelayKnob, tr("Single-channel delay length")); + + m_panDualLKnob = new Knob(knobBright_26, this); + m_panDualLKnob->move(183, 162); + m_panDualLKnob->setModel(&m_controls->m_panDualLModel); + m_panDualLKnob->setLabel(tr("L")); + m_panDualLKnob->setHintText(tr("Left Pan:"), "%"); + ToolTip::add(m_panDualLKnob, tr("Pan left channel")); + + m_panDualRKnob = new Knob(knobBright_26, this); + m_panDualRKnob->move(225, 162); + m_panDualRKnob->setModel(&m_controls->m_panDualRModel); + m_panDualRKnob->setLabel(tr("R")); + m_panDualRKnob->setHintText(tr("Right Pan:"), "%"); + ToolTip::add(m_panDualRKnob, tr("Pan right channel")); + + m_panKnob = new Knob(knobBright_26, this); + m_panKnob->move(204, 161); + m_panKnob->setModel(&m_controls->m_panModel); + m_panKnob->setLabel(tr("PAN")); + m_panKnob->setHintText(tr("Pan:"), "%"); + ToolTip::add(m_panKnob, tr("Pan")); + + m_gainButton = new PixmapButton(this, tr("Gain Panning")); + m_gainButton->move(114, 135); + m_gainButton->setActiveGraphic(PLUGIN_NAME::getIconPixmap("basic_sel")); + m_gainButton->setInactiveGraphic(PLUGIN_NAME::getIconPixmap("basic_unsel")); + ToolTip::add(m_gainButton, tr("Change gain of each channel")); + + m_stereoButton = new PixmapButton(this, tr("Stereo Panning")); + m_stereoButton->move(183, 135); + m_stereoButton->setActiveGraphic(PLUGIN_NAME::getIconPixmap("dual_sel")); + m_stereoButton->setInactiveGraphic(PLUGIN_NAME::getIconPixmap("dual_unsel")); + ToolTip::add(m_stereoButton, tr("Pan one channel into the other")); + + m_haasButton = new PixmapButton(this, tr("Haas Panning")); + m_haasButton->move(252, 135); + m_haasButton->setActiveGraphic(PLUGIN_NAME::getIconPixmap("binaural_sel")); + m_haasButton->setInactiveGraphic(PLUGIN_NAME::getIconPixmap("binaural_unsel")); + ToolTip::add(m_haasButton, tr("Stereo panning with spectral adjustment and single-channel fractional delay")); + + m_panModeGroup = new automatableButtonGroup(this); + m_panModeGroup->addButton(m_gainButton); + m_panModeGroup->addButton(m_stereoButton); + m_panModeGroup->addButton(m_haasButton); + m_panModeGroup->setModel(&m_controls->m_panModeModel); + + m_monoButton = new PixmapButton(this, tr("Mono")); + m_monoButton->move(238, 13); + m_monoButton->setModel(&m_controls->m_monoModel); + m_monoButton->setCheckable(true); + m_monoButton->setActiveGraphic(PLUGIN_NAME::getIconPixmap("mono_sel")); + m_monoButton->setInactiveGraphic(PLUGIN_NAME::getIconPixmap("mono_unsel")); + ToolTip::add(m_monoButton, tr("Converts input to mono")); + + m_dcRemovalButton = new PixmapButton(this, tr("DC Offset Removal")); + m_dcRemovalButton->move(55, 146); + m_dcRemovalButton->setModel(&m_controls->m_dcRemovalModel); + m_dcRemovalButton->setCheckable(true); + m_dcRemovalButton->setActiveGraphic(PLUGIN_NAME::getIconPixmap("dc_sel")); + m_dcRemovalButton->setInactiveGraphic(PLUGIN_NAME::getIconPixmap("dc_unsel")); + ToolTip::add(m_dcRemovalButton, tr("Removes DC offset from the signal")); + + m_muteButton = new PixmapButton(this, tr("Mute")); + m_muteButton->move(11, 181); + m_muteButton->setModel(&m_controls->m_muteModel); + m_muteButton->setCheckable(true); + m_muteButton->setActiveGraphic(PLUGIN_NAME::getIconPixmap("mute_sel")); + m_muteButton->setInactiveGraphic(PLUGIN_NAME::getIconPixmap("mute_unsel")); + ToolTip::add(m_muteButton, tr("Mute audio")); + + m_monoBassButton = new LedCheckBox("", this, tr("Mono Bass"), LedCheckBox::Green); + m_monoBassButton->move(241, 41); + m_monoBassButton->setModel(&m_controls->m_monoBassModel); + m_monoBassButton->setCheckable(true); + ToolTip::add(m_monoBassButton, tr("Mono Bass")); + + m_auditionButton = new PixmapButton(this, tr("Bass Mono audition")); + m_auditionButton->move(305, 55); + m_auditionButton->setModel(&m_controls->m_auditionModel); + m_auditionButton->setCheckable(true); + m_auditionButton->setActiveGraphic(PLUGIN_NAME::getIconPixmap("audition_sel")); + m_auditionButton->setInactiveGraphic(PLUGIN_NAME::getIconPixmap("audition_unsel")); + ToolTip::add(m_auditionButton, tr("Bass Mono Audition")); + + m_invertLButton = new PixmapButton(this, tr("Invert Left Channel")); + m_invertLButton->move(25, 77); + m_invertLButton->setModel(&m_controls->m_invertLModel); + m_invertLButton->setCheckable(true); + m_invertLButton->setActiveGraphic(PLUGIN_NAME::getIconPixmap("invertL_sel")); + m_invertLButton->setInactiveGraphic(PLUGIN_NAME::getIconPixmap("invertL_unsel")); + ToolTip::add(m_invertLButton, tr("Invert Left Channel")); + + m_invertRButton = new PixmapButton(this, tr("Invert Right Channel")); + m_invertRButton->move(55, 77); + m_invertRButton->setModel(&m_controls->m_invertRModel); + m_invertRButton->setCheckable(true); + m_invertRButton->setActiveGraphic(PLUGIN_NAME::getIconPixmap("invertR_sel")); + m_invertRButton->setInactiveGraphic(PLUGIN_NAME::getIconPixmap("invertR_unsel")); + ToolTip::add(m_invertRButton, tr("Invert Right Channel")); + + m_soloChannelBox = new ComboBox(this); + m_soloChannelBox->setGeometry(11, 13, 77, 22); + m_soloChannelBox->setFont(pointSize<8>(m_soloChannelBox->font())); + m_soloChannelBox->setModel(&m_controls->m_soloChannelModel); + ToolTip::add(m_soloChannelBox, tr("Solo a single channel")); + + connect(&m_controls->m_panModeModel, SIGNAL(dataChanged()), this, SLOT(updateKnobVisibility())); + updateKnobVisibility(); +} + + +void StereoControlControlDialog::updateKnobVisibility() +{ + switch (m_controls->m_panModeModel.value()) + { + case 0: + m_panKnob->show(); + m_panDualLKnob->hide(); + m_panDualRKnob->hide(); + m_panSpectralKnob->hide(); + m_panDelayKnob->hide(); + break; + case 1: + m_panKnob->hide(); + m_panDualLKnob->show(); + m_panDualRKnob->show(); + m_panSpectralKnob->hide(); + m_panDelayKnob->hide(); + break; + case 2: + m_panKnob->show(); + m_panDualLKnob->hide(); + m_panDualRKnob->hide(); + m_panSpectralKnob->show(); + m_panDelayKnob->show(); + break; + } +} diff --git a/plugins/StereoControl/StereoControlControlDialog.h b/plugins/StereoControl/StereoControlControlDialog.h new file mode 100755 index 00000000000..72ba8837d80 --- /dev/null +++ b/plugins/StereoControl/StereoControlControlDialog.h @@ -0,0 +1,76 @@ +/* + * StereoControlControlDialog.h + * + * Copyright (c) 2020 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 STEREOCONTROL_CONTROL_DIALOG_H +#define STEREOCONTROL_CONTROL_DIALOG_H + +#include "ComboBox.h" +#include "EffectControlDialog.h" +#include "LedCheckbox.h" +#include "Knob.h" +#include "PixmapButton.h" + + +class StereoControlControls; + + +class StereoControlControlDialog : public EffectControlDialog +{ + Q_OBJECT +public: + StereoControlControlDialog(StereoControlControls* controls); + +private: + StereoControlControls * m_controls; + + Knob * m_gainKnob; + Knob * m_stereoizerKnob; + Knob * m_widthKnob; + Knob * m_monoBassFreqKnob; + Knob * m_stereoizerLPKnob; + Knob * m_stereoizerHPKnob; + Knob * m_panSpectralKnob; + Knob * m_panDelayKnob; + Knob * m_panDualLKnob; + Knob * m_panDualRKnob; + Knob * m_panKnob; + PixmapButton * m_gainButton; + PixmapButton * m_stereoButton; + PixmapButton * m_haasButton; + automatableButtonGroup * m_panModeGroup; + PixmapButton * m_monoButton; + PixmapButton * m_dcRemovalButton; + PixmapButton * m_muteButton; + LedCheckBox * m_monoBassButton; + PixmapButton * m_auditionButton; + PixmapButton * m_invertLButton; + PixmapButton * m_invertRButton; + ComboBox * m_soloChannelBox; + +private slots: + void updateKnobVisibility(); + +}; + +#endif diff --git a/plugins/StereoControl/StereoControlControls.cpp b/plugins/StereoControl/StereoControlControls.cpp new file mode 100755 index 00000000000..01c33ee31c8 --- /dev/null +++ b/plugins/StereoControl/StereoControlControls.cpp @@ -0,0 +1,118 @@ +/* + * StereoControlControls.cpp + * + * Copyright (c) 2020 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 "StereoControlControls.h" +#include "StereoControl.h" + +#include + +#include "Engine.h" +#include "Song.h" + + +StereoControlControls::StereoControlControls(StereoControlEffect* effect) : + EffectControls(effect), + m_gainModel(0.0f, -54.0f, 54.0f, 0.01f, this, tr("gain")), + m_stereoizerModel(0.0f, 0.0f, 200.0f, 0.01f, this, tr("Stereoize")), + m_widthModel(100.0f, 0.0f, 200.0f, 0.01f, this, tr("Width")), + m_panModel(0.0f, -100.0f, 100.0f, 0.01f, this, tr("Pan")), + m_monoBassFreqModel(80.0f, 0.0f, 500.0f, 0.01f, this, tr("Mono Bass Frequency")), + m_stereoizerLPModel(20000.0f, 0.0f, 20000.0f, 0.01f, this, tr("Stereoizer Lowpass")), + m_stereoizerHPModel(0.0f, 0.0f, 20000.0f, 0.01f, this, tr("Stereoizer Highpass")), + m_panSpectralModel(0.0f, 0.0f, 100.0f, 0.01f, this, tr("Spectral Panning")), + m_panDelayModel(100.0f, 0.0f, 200.0f, 0.01f, this, tr("Panning Delay")), + m_panDualLModel(-100.0f, -100.0f, 100.0f, 0.01f, this, tr("Left Pan")), + m_panDualRModel(100.0f, -100.0f, 100.0f, 0.01f, this, tr("Right Pan")), + m_panModeModel(0, 0, 2, this, tr("Panning Mode")), + m_soloChannelModel(this, tr("Solo Channel")), + m_monoModel(false, this, tr("Mono")), + m_dcRemovalModel(false, this, tr("DC Offset Removal")), + m_muteModel(false, this, tr("Mute")), + m_monoBassModel(false, this, tr("Mono Bass")), + m_auditionModel(false, this, tr("Mono Bass Audition")), + m_invertLModel(false, this, tr("Invert Left Channel")), + m_invertRModel(false, this, tr("Invert Right Channel")) +{ + m_monoBassFreqModel.setScaleLogarithmic(true); + m_stereoizerLPModel.setScaleLogarithmic(true); + m_stereoizerHPModel.setScaleLogarithmic(true); + + m_soloChannelModel.addItem(tr("Stereo")); + m_soloChannelModel.addItem(tr("Left")); + m_soloChannelModel.addItem(tr("Right")); +} + + +void StereoControlControls::saveSettings(QDomDocument& doc, QDomElement& elem) +{ + m_gainModel.saveSettings(doc, elem, "gain"); + m_stereoizerModel.saveSettings(doc, elem, "stereoizer"); + m_widthModel.saveSettings(doc, elem, "width"); + m_panModel.saveSettings(doc, elem, "pan"); + m_panModeModel.saveSettings(doc, elem, "panMode"); + m_monoModel.saveSettings(doc, elem, "mono"); + m_dcRemovalModel.saveSettings(doc, elem, "dcRemoval"); + m_muteModel.saveSettings(doc, elem, "mute"); + m_monoBassModel.saveSettings(doc, elem, "monoBass"); + m_auditionModel.saveSettings(doc, elem, "audition"); + m_invertLModel.saveSettings(doc, elem, "invertL"); + m_invertRModel.saveSettings(doc, elem, "invertR"); + m_soloChannelModel.saveSettings(doc, elem, "soloChannel"); + m_monoBassFreqModel.saveSettings(doc, elem, "monoBassFreq"); + m_stereoizerLPModel.saveSettings(doc, elem, "stereoizerLP"); + m_stereoizerHPModel.saveSettings(doc, elem, "stereoizerHP"); + m_panSpectralModel.saveSettings(doc, elem, "panSpectral"); + m_panDelayModel.saveSettings(doc, elem, "panDelay"); + m_panDualLModel.saveSettings(doc, elem, "panDualL"); + m_panDualRModel.saveSettings(doc, elem, "panDualR"); +} + + + +void StereoControlControls::loadSettings(const QDomElement& elem) +{ + m_gainModel.loadSettings(elem, "gain"); + m_stereoizerModel.loadSettings(elem, "stereoizer"); + m_widthModel.loadSettings(elem, "width"); + m_panModel.loadSettings(elem, "pan"); + m_panModeModel.loadSettings(elem, "panMode"); + m_monoModel.loadSettings(elem, "mono"); + m_dcRemovalModel.loadSettings(elem, "dcRemoval"); + m_muteModel.loadSettings(elem, "mute"); + m_monoBassModel.loadSettings(elem, "monoBass"); + m_auditionModel.loadSettings(elem, "audition"); + m_invertLModel.loadSettings(elem, "invertL"); + m_invertRModel.loadSettings(elem, "invertR"); + m_soloChannelModel.loadSettings(elem, "soloChannel"); + m_monoBassFreqModel.loadSettings(elem, "monoBassFreq"); + m_stereoizerLPModel.loadSettings(elem, "stereoizerLP"); + m_stereoizerHPModel.loadSettings(elem, "stereoizerHP"); + m_panSpectralModel.loadSettings(elem, "panSpectral"); + m_panDelayModel.loadSettings(elem, "panDelay"); + m_panDualLModel.loadSettings(elem, "panDualL"); + m_panDualRModel.loadSettings(elem, "panDualR"); +} + + diff --git a/plugins/StereoControl/StereoControlControls.h b/plugins/StereoControl/StereoControlControls.h new file mode 100755 index 00000000000..373e1b03e76 --- /dev/null +++ b/plugins/StereoControl/StereoControlControls.h @@ -0,0 +1,89 @@ +/* + * StereoControlControls.h + * + * Copyright (c) 2020 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 STEREOCONTROL_CONTROLS_H +#define STEREOCONTROL_CONTROLS_H + +#include "EffectControls.h" +#include "StereoControlControlDialog.h" + +#include "ComboBox.h" + + +class StereoControlEffect; + + +class StereoControlControls : public EffectControls +{ + Q_OBJECT +public: + StereoControlControls(StereoControlEffect* effect); + + void saveSettings(QDomDocument & doc, QDomElement & elem) override; + void loadSettings(const QDomElement & elem); + inline QString nodeName() const override + { + return "StereoControlControls"; + } + + int controlCount() override + { + return 20; + } + + EffectControlDialog* createView() override + { + return new StereoControlControlDialog(this); + } + +private: + FloatModel m_gainModel; + FloatModel m_stereoizerModel; + FloatModel m_widthModel; + FloatModel m_panModel; + FloatModel m_monoBassFreqModel; + FloatModel m_stereoizerLPModel; + FloatModel m_stereoizerHPModel; + FloatModel m_panSpectralModel; + FloatModel m_panDelayModel; + FloatModel m_panDualLModel; + FloatModel m_panDualRModel; + + IntModel m_panModeModel; + ComboBoxModel m_soloChannelModel; + + BoolModel m_monoModel; + BoolModel m_dcRemovalModel; + BoolModel m_muteModel; + BoolModel m_monoBassModel; + BoolModel m_auditionModel; + BoolModel m_invertLModel; + BoolModel m_invertRModel; + + friend class StereoControlControlDialog; + friend class StereoControlEffect; + +}; + +#endif diff --git a/plugins/StereoControl/artwork.png b/plugins/StereoControl/artwork.png new file mode 100644 index 00000000000..963e8caba2b Binary files /dev/null and b/plugins/StereoControl/artwork.png differ diff --git a/plugins/StereoControl/audition_sel.png b/plugins/StereoControl/audition_sel.png new file mode 100644 index 00000000000..840801c7559 Binary files /dev/null and b/plugins/StereoControl/audition_sel.png differ diff --git a/plugins/StereoControl/audition_unsel.png b/plugins/StereoControl/audition_unsel.png new file mode 100644 index 00000000000..28160dee381 Binary files /dev/null and b/plugins/StereoControl/audition_unsel.png differ diff --git a/plugins/StereoControl/basic_sel.png b/plugins/StereoControl/basic_sel.png new file mode 100644 index 00000000000..d328c8cb10b Binary files /dev/null and b/plugins/StereoControl/basic_sel.png differ diff --git a/plugins/StereoControl/basic_unsel.png b/plugins/StereoControl/basic_unsel.png new file mode 100644 index 00000000000..580a458fbdf Binary files /dev/null and b/plugins/StereoControl/basic_unsel.png differ diff --git a/plugins/StereoControl/binaural_sel.png b/plugins/StereoControl/binaural_sel.png new file mode 100644 index 00000000000..5e6a4aa7721 Binary files /dev/null and b/plugins/StereoControl/binaural_sel.png differ diff --git a/plugins/StereoControl/binaural_unsel.png b/plugins/StereoControl/binaural_unsel.png new file mode 100644 index 00000000000..2c1e5cbc0e8 Binary files /dev/null and b/plugins/StereoControl/binaural_unsel.png differ diff --git a/plugins/StereoControl/dc_sel.png b/plugins/StereoControl/dc_sel.png new file mode 100644 index 00000000000..f385966298b Binary files /dev/null and b/plugins/StereoControl/dc_sel.png differ diff --git a/plugins/StereoControl/dc_unsel.png b/plugins/StereoControl/dc_unsel.png new file mode 100644 index 00000000000..3fb27da708b Binary files /dev/null and b/plugins/StereoControl/dc_unsel.png differ diff --git a/plugins/StereoControl/dual_sel.png b/plugins/StereoControl/dual_sel.png new file mode 100644 index 00000000000..79d1bcbc09d Binary files /dev/null and b/plugins/StereoControl/dual_sel.png differ diff --git a/plugins/StereoControl/dual_unsel.png b/plugins/StereoControl/dual_unsel.png new file mode 100644 index 00000000000..41a9ed8bee4 Binary files /dev/null and b/plugins/StereoControl/dual_unsel.png differ diff --git a/plugins/StereoControl/invertL_sel.png b/plugins/StereoControl/invertL_sel.png new file mode 100644 index 00000000000..a78ecfef82d Binary files /dev/null and b/plugins/StereoControl/invertL_sel.png differ diff --git a/plugins/StereoControl/invertL_unsel.png b/plugins/StereoControl/invertL_unsel.png new file mode 100644 index 00000000000..77ce34899d3 Binary files /dev/null and b/plugins/StereoControl/invertL_unsel.png differ diff --git a/plugins/StereoControl/invertR_sel.png b/plugins/StereoControl/invertR_sel.png new file mode 100644 index 00000000000..a957f0282bd Binary files /dev/null and b/plugins/StereoControl/invertR_sel.png differ diff --git a/plugins/StereoControl/invertR_unsel.png b/plugins/StereoControl/invertR_unsel.png new file mode 100644 index 00000000000..89fb237a251 Binary files /dev/null and b/plugins/StereoControl/invertR_unsel.png differ diff --git a/plugins/StereoControl/logo.png b/plugins/StereoControl/logo.png new file mode 100755 index 00000000000..9340da708dd Binary files /dev/null and b/plugins/StereoControl/logo.png differ diff --git a/plugins/StereoControl/mono_sel.png b/plugins/StereoControl/mono_sel.png new file mode 100644 index 00000000000..06c2729aee6 Binary files /dev/null and b/plugins/StereoControl/mono_sel.png differ diff --git a/plugins/StereoControl/mono_unsel.png b/plugins/StereoControl/mono_unsel.png new file mode 100644 index 00000000000..66e54407b89 Binary files /dev/null and b/plugins/StereoControl/mono_unsel.png differ diff --git a/plugins/StereoControl/mute_sel.png b/plugins/StereoControl/mute_sel.png new file mode 100644 index 00000000000..b8ab062d15d Binary files /dev/null and b/plugins/StereoControl/mute_sel.png differ diff --git a/plugins/StereoControl/mute_unsel.png b/plugins/StereoControl/mute_unsel.png new file mode 100644 index 00000000000..5b888304c88 Binary files /dev/null and b/plugins/StereoControl/mute_unsel.png differ