diff --git a/include/PluginPortConfig.h b/include/PluginPortConfig.h new file mode 100644 index 00000000000..c6fcc1d0195 --- /dev/null +++ b/include/PluginPortConfig.h @@ -0,0 +1,139 @@ +/* + * PluginPortConfig.h - Specifies how to route audio channels + * in and out of a plugin. + * + * Copyright (c) 2024 Dalton Messmer + * + * 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_PLUGIN_PORT_CONFIG_H +#define LMMS_PLUGIN_PORT_CONFIG_H + +#include + +#include "ComboBoxModel.h" +#include "lmms_export.h" +#include "SerializingObject.h" + +class QWidget; + +namespace lmms +{ + +namespace gui +{ + +class ComboBox; + +} // namespace gui + +//! Configure channel routing for a plugin's mono/stereo in/out ports +class LMMS_EXPORT PluginPortConfig + : public QObject + , public SerializingObject +{ + Q_OBJECT + +public: + enum class Config + { + None = -1, + MonoMix = 0, // mono ports only + LeftOnly = 1, // mono ports only + RightOnly = 2, // mono ports only + Stereo = 3 + }; + + enum class MonoPluginType + { + None, + Input, + Output, + Both + }; + + PluginPortConfig(Model* parent = nullptr); + PluginPortConfig(int inCount, int outCount, Model* parent = nullptr); + + /** + * Getters + */ + auto portCountIn() const -> int { return m_portCountIn; } + auto portCountOut() const -> int { return m_portCountOut; } + + auto portConfigIn() const -> Config + { + switch (m_portCountIn) + { + default: [[fallthrough]]; + case 0: return Config::None; + case 1: return static_cast(m_config.value()); + case 2: return Config::Stereo; + } + } + + auto portConfigOut() const -> Config + { + switch (m_portCountOut) + { + default: [[fallthrough]]; + case 0: return Config::None; + case 1: return static_cast(m_config.value()); + case 2: return Config::Stereo; + } + } + + auto hasMonoPort() const -> bool; + auto monoPluginType() const -> MonoPluginType; + auto model() -> ComboBoxModel* { return &m_config; } + + /** + * Setters + */ + void setPortCounts(int inCount, int outCount); + void setPortCountIn(int inCount); + void setPortCountOut(int outCount); + auto setPortConfig(Config config) -> bool; + + /** + * SerializingObject implementation + */ + void saveSettings(QDomDocument& doc, QDomElement& elem) override; + void loadSettings(const QDomElement& elem) override; + auto nodeName() const -> QString override { return "port_config"; } + + auto instantiateView(QWidget* parent = nullptr) -> gui::ComboBox*; + +signals: + void portsChanged(); + +private: + void updateOptions(); + + int m_portCountIn = DEFAULT_CHANNELS; + int m_portCountOut = DEFAULT_CHANNELS; + + //! Value is 0..2, which represents { MonoMix, LeftOnly, RightOnly } for non-Stereo plugins + ComboBoxModel m_config; +}; + +} // namespace lmms + +#endif // LMMS_PLUGIN_PORT_CONFIG_H diff --git a/include/RemotePlugin.h b/include/RemotePlugin.h index c5fcd7dd2c3..2d3d0b1f992 100644 --- a/include/RemotePlugin.h +++ b/include/RemotePlugin.h @@ -26,6 +26,8 @@ #define LMMS_REMOTE_PLUGIN_H #include "RemotePluginBase.h" + +#include "PluginPortConfig.h" #include "SharedMemory.h" #if (QT_VERSION >= QT_VERSION_CHECK(5,14,0)) @@ -42,7 +44,7 @@ class ProcessWatcher : public QThread { Q_OBJECT public: - ProcessWatcher( RemotePlugin * ); + explicit ProcessWatcher(RemotePlugin* plugin); ~ProcessWatcher() override = default; void stop() @@ -69,7 +71,7 @@ class LMMS_EXPORT RemotePlugin : public QObject, public RemotePluginBase { Q_OBJECT public: - RemotePlugin(); + explicit RemotePlugin(Model* parent = nullptr); ~RemotePlugin() override; inline bool isRunning() @@ -140,6 +142,11 @@ class LMMS_EXPORT RemotePlugin : public QObject, public RemotePluginBase m_commMutex.unlock(); } + PluginPortConfig& portConfig() + { + return m_portConfig; + } + public slots: virtual void showUI(); virtual void hideUI(); @@ -172,8 +179,7 @@ public slots: SharedMemory m_audioBuffer; std::size_t m_audioBufferSize; - int m_inputCount; - int m_outputCount; + PluginPortConfig m_portConfig; #ifndef SYNC_WITH_SHM_FIFO int m_server; diff --git a/include/RemotePluginClient.h b/include/RemotePluginClient.h index 77eef68f035..71056725b5c 100644 --- a/include/RemotePluginClient.h +++ b/include/RemotePluginClient.h @@ -58,8 +58,7 @@ class RemotePluginClient : public RemotePluginBase bool processMessage( const message & _m ) override; - virtual void process( const sampleFrame * _in_buf, - sampleFrame * _out_buf ) = 0; + virtual void process(float* _in_buf, float* _out_buf) = 0; virtual void processMidiEvent( const MidiEvent&, const f_cnt_t /* _offset */ ) { @@ -342,9 +341,8 @@ void RemotePluginClient::doProcessing() { if (m_audioBuffer) { - process( (sampleFrame *)( m_inputCount > 0 ? m_audioBuffer.get() : nullptr ), - (sampleFrame *)( m_audioBuffer.get() + - ( m_inputCount*m_bufferSize ) ) ); + process(m_inputCount > 0 ? m_audioBuffer.get() : nullptr, + m_audioBuffer.get() + m_inputCount * m_bufferSize); } else { diff --git a/plugins/Vestige/Vestige.cpp b/plugins/Vestige/Vestige.cpp index 1de713960ba..9377cbed81e 100644 --- a/plugins/Vestige/Vestige.cpp +++ b/plugins/Vestige/Vestige.cpp @@ -41,6 +41,7 @@ #include "AudioEngine.h" +#include "ComboBox.h" #include "ConfigManager.h" #include "CustomTextKnob.h" #include "Engine.h" @@ -363,7 +364,7 @@ void VestigeInstrument::loadFile( const QString & _file ) } m_pluginMutex.lock(); - m_plugin = new VstInstrumentPlugin( m_pluginDLL ); + m_plugin = new VstInstrumentPlugin{m_pluginDLL, this}; if( m_plugin->failed() ) { m_pluginMutex.unlock(); @@ -482,10 +483,17 @@ gui::PluginView * VestigeInstrument::instantiateView( QWidget * _parent ) } +PluginPortConfig* VestigeInstrument::portConfig() +{ + if (!m_plugin) { return nullptr; } + return &m_plugin->portConfig(); +} + + namespace gui { -VestigeInstrumentView::VestigeInstrumentView( Instrument * _instrument, +VestigeInstrumentView::VestigeInstrumentView( VestigeInstrument * _instrument, QWidget * _parent ) : InstrumentViewFixedSize( _instrument, _parent ), lastPosInMenu (0) @@ -602,7 +610,7 @@ VestigeInstrumentView::VestigeInstrumentView( Instrument * _instrument, SLOT( noteOffAll() ) ); setAcceptDrops( true ); - _instrument2 = _instrument; + m_instrument2 = _instrument; _parent2 = _parent; } @@ -610,7 +618,7 @@ VestigeInstrumentView::VestigeInstrumentView( Instrument * _instrument, void VestigeInstrumentView::managePlugin( void ) { if ( m_vi->m_plugin != nullptr && m_vi->m_subWindow == nullptr ) { - m_vi->p_subWindow = new ManageVestigeInstrumentView( _instrument2, _parent2, m_vi); + m_vi->p_subWindow = new ManageVestigeInstrumentView( m_instrument2, _parent2, m_vi); } else if (m_vi->m_subWindow != nullptr) { if (m_vi->m_subWindow->widget()->isVisible() == false ) { m_vi->m_scrollArea->show(); @@ -912,8 +920,8 @@ void VestigeInstrumentView::paintEvent( QPaintEvent * ) -ManageVestigeInstrumentView::ManageVestigeInstrumentView( Instrument * _instrument, - QWidget * _parent, VestigeInstrument * m_vi2 ) : +ManageVestigeInstrumentView::ManageVestigeInstrumentView( VestigeInstrument * _instrument, + QWidget * _parent, VestigeInstrument * _vi2 ) : InstrumentViewFixedSize( _instrument, _parent ) { #if QT_VERSION < 0x50C00 @@ -927,7 +935,7 @@ ManageVestigeInstrumentView::ManageVestigeInstrumentView( Instrument * _instrume #endif - m_vi = m_vi2; + m_vi = _vi2; m_vi->m_scrollArea = new QScrollArea( this ); widget = new QWidget(this); l = new QGridLayout( this ); @@ -960,13 +968,12 @@ ManageVestigeInstrumentView::ManageVestigeInstrumentView( Instrument * _instrume l->addWidget( m_displayAutomatedOnly, 0, 1, 1, 2, Qt::AlignLeft ); - - m_closeButton = new QPushButton( tr( " Close " ), widget ); - connect( m_closeButton, SIGNAL( clicked() ), this, - SLOT( closeWindow() ) ); - - l->addWidget( m_closeButton, 0, 2, 1, 7, Qt::AlignLeft ); - + if (m_vi->portConfig()->hasMonoPort()) + { + m_portConfig = m_vi->portConfig()->instantiateView(this); + m_portConfig->setFixedSize(108, gui::ComboBox::DEFAULT_HEIGHT); + l->addWidget(m_portConfig, 0, 2, 1, 3, Qt::AlignLeft); + } for( int i = 0; i < 10; i++ ) { diff --git a/plugins/Vestige/Vestige.h b/plugins/Vestige/Vestige.h index 529893ba087..30ef1cc1458 100644 --- a/plugins/Vestige/Vestige.h +++ b/plugins/Vestige/Vestige.h @@ -42,7 +42,9 @@ class QGridLayout; namespace lmms { +class ComboBox; class FloatModel; +class PluginPortConfig; class VstPlugin; namespace gui @@ -74,6 +76,8 @@ class VestigeInstrument : public Instrument virtual gui::PluginView* instantiateView( QWidget * _parent ); + PluginPortConfig* portConfig(); + protected slots: void setParameter( lmms::Model * action ); void handleConfigChange( QString cls, QString attr, QString value ); @@ -107,7 +111,7 @@ class ManageVestigeInstrumentView : public InstrumentViewFixedSize { Q_OBJECT public: - ManageVestigeInstrumentView( Instrument * _instrument, QWidget * _parent, VestigeInstrument * m_vi2 ); + ManageVestigeInstrumentView( VestigeInstrument * _instrument, QWidget * _parent, VestigeInstrument * _vi2 ); virtual ~ManageVestigeInstrumentView(); @@ -132,7 +136,7 @@ protected slots: QGridLayout * l; QPushButton * m_syncButton; QPushButton * m_displayAutomatedOnly; - QPushButton * m_closeButton; + ComboBox* m_portConfig; CustomTextKnob ** vstKnobs; } ; @@ -142,7 +146,7 @@ class VestigeInstrumentView : public InstrumentViewFixedSize { Q_OBJECT public: - VestigeInstrumentView( Instrument * _instrument, QWidget * _parent ); + VestigeInstrumentView( VestigeInstrument * _instrument, QWidget * _parent ); virtual ~VestigeInstrumentView() = default; @@ -182,7 +186,7 @@ protected slots: PixmapButton * m_managePluginButton; PixmapButton * m_savePresetButton; - Instrument * _instrument2; + VestigeInstrument* m_instrument2; QWidget * _parent2; } ; diff --git a/plugins/VstBase/RemoteVstPlugin.cpp b/plugins/VstBase/RemoteVstPlugin.cpp index 0ec60bea4fe..a5733643b35 100644 --- a/plugins/VstBase/RemoteVstPlugin.cpp +++ b/plugins/VstBase/RemoteVstPlugin.cpp @@ -191,7 +191,7 @@ class RemoteVstPlugin : public RemotePluginClient void hideEditor(); void destroyEditor(); - virtual void process( const sampleFrame * _in, sampleFrame * _out ); + void process(float* _in, float* _out) override; virtual void processMidiEvent( const MidiEvent& event, const f_cnt_t offset ); @@ -1027,7 +1027,7 @@ bool RemoteVstPlugin::load( const std::string & _plugin_file ) -void RemoteVstPlugin::process( const sampleFrame * _in, sampleFrame * _out ) +void RemoteVstPlugin::process(float* _in, float* _out) { // first we gonna post all MIDI-events we enqueued so far if( m_midiEvents.size() ) @@ -1076,14 +1076,15 @@ void RemoteVstPlugin::process( const sampleFrame * _in, sampleFrame * _out ) return; } + // NOTE: VST in/out channels are always provided split: in[0..frames] (left), in[frames..2*frames] (right) for( int i = 0; i < inputCount(); ++i ) { - m_inputs[i] = &((float *) _in)[i * bufferSize()]; + m_inputs[i] = &_in[i * bufferSize()]; } for( int i = 0; i < outputCount(); ++i ) { - m_outputs[i] = &((float *) _out)[i * bufferSize()]; + m_outputs[i] = &_out[i * bufferSize()]; memset( m_outputs[i], 0, bufferSize() * sizeof( float ) ); } diff --git a/plugins/VstBase/VstPlugin.cpp b/plugins/VstBase/VstPlugin.cpp index 0361d4c25d0..abd22de34af 100644 --- a/plugins/VstBase/VstPlugin.cpp +++ b/plugins/VstBase/VstPlugin.cpp @@ -121,14 +121,15 @@ enum class ExecutableType Unknown, Win32, Win64, Linux64, }; -VstPlugin::VstPlugin( const QString & _plugin ) : - m_plugin( PathUtil::toAbsolute(_plugin) ), - m_pluginWindowID( 0 ), - m_embedMethod( (gui::getGUI() != nullptr) - ? ConfigManager::inst()->vstEmbedMethod() - : "headless" ), - m_version( 0 ), - m_currentProgram() +VstPlugin::VstPlugin(const QString& plugin, Model* parent) + : RemotePlugin{parent} + , m_plugin{PathUtil::toAbsolute(plugin)} + , m_pluginWindowID{0} + , m_embedMethod{(gui::getGUI() != nullptr) + ? ConfigManager::inst()->vstEmbedMethod() + : "headless"} + , m_version{0} + , m_currentProgram{-1} { setSplittedChannels( true ); @@ -264,6 +265,8 @@ void VstPlugin::loadSettings( const QDomElement & _this ) } setParameterDump( dump ); } + + portConfig().loadSettings(_this); } @@ -307,6 +310,7 @@ void VstPlugin::saveSettings( QDomDocument & _doc, QDomElement & _this ) } _this.setAttribute( "program", currentProgram() ); + portConfig().saveSettings(_doc, _this); } void VstPlugin::toggleUI() diff --git a/plugins/VstBase/VstPlugin.h b/plugins/VstBase/VstPlugin.h index 03e732970e2..04a044b1da9 100644 --- a/plugins/VstBase/VstPlugin.h +++ b/plugins/VstBase/VstPlugin.h @@ -44,7 +44,7 @@ class VSTBASE_EXPORT VstPlugin : public RemotePlugin, public JournallingObject { Q_OBJECT public: - VstPlugin( const QString & _plugin ); + explicit VstPlugin(const QString& plugin, Model* parent = nullptr); ~VstPlugin() override; void tryLoad( const QString &remoteVstPluginExecutable ); diff --git a/plugins/VstEffect/VstEffect.cpp b/plugins/VstEffect/VstEffect.cpp index bdbdea8060c..f61623f9987 100644 --- a/plugins/VstEffect/VstEffect.cpp +++ b/plugins/VstEffect/VstEffect.cpp @@ -133,7 +133,7 @@ void VstEffect::openPlugin( const QString & _plugin ) } QMutexLocker ml( &m_pluginMutex ); Q_UNUSED( ml ); - m_plugin = QSharedPointer(new VstPlugin( _plugin )); + m_plugin = QSharedPointer(new VstPlugin{_plugin, this}); if( m_plugin->failed() ) { m_plugin.clear(); diff --git a/plugins/VstEffect/VstEffectControlDialog.cpp b/plugins/VstEffect/VstEffectControlDialog.cpp index 0fb4913a338..81576ed80c9 100644 --- a/plugins/VstEffect/VstEffectControlDialog.cpp +++ b/plugins/VstEffect/VstEffectControlDialog.cpp @@ -22,20 +22,20 @@ * */ +#include "VstEffectControlDialog.h" + +#include #include #include #include +#include -#include "VstEffectControlDialog.h" +#include "embed.h" +#include "gui_templates.h" +#include "PixmapButton.h" #include "VstEffect.h" #include "VstPlugin.h" -#include "PixmapButton.h" -#include "embed.h" - -#include "gui_templates.h" -#include -#include namespace lmms::gui { diff --git a/plugins/VstEffect/VstEffectControls.cpp b/plugins/VstEffect/VstEffectControls.cpp index c9eb4923451..3e29c6bd8fe 100644 --- a/plugins/VstEffect/VstEffectControls.cpp +++ b/plugins/VstEffect/VstEffectControls.cpp @@ -30,6 +30,7 @@ #include #include "embed.h" +#include "ComboBox.h" #include "CustomTextKnob.h" #include "VstEffectControls.h" #include "VstEffectControlDialog.h" @@ -165,6 +166,13 @@ gui::EffectControlDialog* VstEffectControls::createView() +PluginPortConfig* VstEffectControls::portConfig() +{ + if (!m_effect->m_plugin) { return nullptr; } + return &m_effect->m_plugin->portConfig(); +} + + void VstEffectControls::managePlugin() { @@ -356,13 +364,12 @@ ManageVSTEffectView::ManageVSTEffectView( VstEffect * _eff, VstEffectControls * l->addWidget( m_displayAutomatedOnly, 0, 1, 1, 2, Qt::AlignLeft ); - - m_closeButton = new QPushButton( tr( " Close " ), widget ); - connect( m_closeButton, SIGNAL( clicked() ), this, - SLOT( closeWindow() ) ); - - l->addWidget( m_closeButton, 0, 2, 1, 7, Qt::AlignLeft ); - + if (m_vi->portConfig()->hasMonoPort()) + { + m_portConfig = m_vi->portConfig()->instantiateView(widget); + m_portConfig->setFixedSize(108, gui::ComboBox::DEFAULT_HEIGHT); + l->addWidget(m_portConfig, 0, 2, 1, 3, Qt::AlignLeft); + } for( int i = 0; i < 10; i++ ) { diff --git a/plugins/VstEffect/VstEffectControls.h b/plugins/VstEffect/VstEffectControls.h index e2bea36e88c..6f21fa102dd 100644 --- a/plugins/VstEffect/VstEffectControls.h +++ b/plugins/VstEffect/VstEffectControls.h @@ -39,11 +39,12 @@ class QScrollArea; namespace lmms { - +class PluginPortConfig; class VstEffect; namespace gui { +class ComboBox; class CustomTextKnob; class ManageVSTEffectView; class VstEffectControlDialog; @@ -68,6 +69,7 @@ class VstEffectControls : public EffectControls gui::EffectControlDialog* createView() override; + PluginPortConfig* portConfig(); protected slots: void updateMenu(); @@ -138,7 +140,7 @@ protected slots: QPushButton * m_syncButton; QPushButton * m_displayAutomatedOnly; - QPushButton * m_closeButton; + ComboBox* m_portConfig; CustomTextKnob ** vstKnobs; } ; diff --git a/plugins/ZynAddSubFx/RemoteZynAddSubFx.cpp b/plugins/ZynAddSubFx/RemoteZynAddSubFx.cpp index c4d6b71a16b..c3c467910a7 100644 --- a/plugins/ZynAddSubFx/RemoteZynAddSubFx.cpp +++ b/plugins/ZynAddSubFx/RemoteZynAddSubFx.cpp @@ -141,9 +141,9 @@ class RemoteZynAddSubFx : public RemotePluginClient, public LocalZynAddSubFx } - void process( const sampleFrame * _in, sampleFrame * _out ) override + void process(float* _in, float* _out) override { - LocalZynAddSubFx::processAudio( _out ); + LocalZynAddSubFx::processAudio( (sampleFrame*)_out ); } void guiLoop(); diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 9ebe2c35533..6a29980ee92 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -59,6 +59,7 @@ set(LMMS_SRCS core/Plugin.cpp core/PluginIssue.cpp core/PluginFactory.cpp + core/PluginPortConfig.cpp core/PresetPreviewPlayHandle.cpp core/ProjectJournal.cpp core/ProjectRenderer.cpp diff --git a/src/core/PluginPortConfig.cpp b/src/core/PluginPortConfig.cpp new file mode 100644 index 00000000000..83ed9a2ffc3 --- /dev/null +++ b/src/core/PluginPortConfig.cpp @@ -0,0 +1,255 @@ +/* + * PluginPortConfig.cpp - Specifies how to route audio channels + * in and out of a plugin. + * + * Copyright (c) 2024 Dalton Messmer + * + * 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 "PluginPortConfig.h" + +#include +#include +#include +#include + +#include "ComboBox.h" +#include "Model.h" + +namespace lmms +{ + +PluginPortConfig::PluginPortConfig(Model* parent) + : QObject{parent} + , m_config{parent, tr("L/R channel configuration")} +{ +} + +PluginPortConfig::PluginPortConfig(int inCount, int outCount, Model* parent) + : QObject{parent} + , m_portCountIn{inCount} + , m_portCountOut{outCount} + , m_config{parent, tr("L/R channel configuration")} +{ +} + +auto PluginPortConfig::hasMonoPort() const -> bool +{ + return m_portCountIn == 1 || m_portCountOut == 1; +} + +auto PluginPortConfig::monoPluginType() const -> MonoPluginType +{ + if (m_portCountIn == 1) + { + if (m_portCountOut == 1) + { + return MonoPluginType::Both; + } + return MonoPluginType::Input; + } + else if (m_portCountOut == 1) + { + return MonoPluginType::Output; + } + return MonoPluginType::None; +} + +void PluginPortConfig::setPortCounts(int inCount, int outCount) +{ + if (inCount < 0 || inCount > DEFAULT_CHANNELS) + { + throw std::invalid_argument{"Invalid input count"}; + } + + if (outCount < 0 || outCount > DEFAULT_CHANNELS) + { + throw std::invalid_argument{"Invalid output count"}; + } + + if (inCount == 0 && outCount == 0) + { + throw std::invalid_argument{"At least one port count must be non-zero"}; + } + + if (m_portCountIn == inCount && m_portCountOut == outCount) + { + // No action needed + return; + } + + m_portCountIn = inCount; + m_portCountOut = outCount; + + updateOptions(); + + emit portsChanged(); +} + +void PluginPortConfig::setPortCountIn(int inCount) +{ + setPortCounts(inCount, m_portCountOut); +} + +void PluginPortConfig::setPortCountOut(int outCount) +{ + setPortCounts(m_portCountIn, outCount); +} + +auto PluginPortConfig::setPortConfig(Config config) -> bool +{ + assert(config != Config::None); + if (m_portCountIn != 1 && m_portCountOut != 1) + { + if (config != Config::Stereo) { return false; } + m_config.setValue(0); + } + else + { + if (config == Config::Stereo) { return false; } + m_config.setValue(static_cast(config)); + } + + return true; +} + +void PluginPortConfig::saveSettings(QDomDocument& doc, QDomElement& elem) +{ + // Only plugins with a mono in/out need to be saved + //if (m_portCountIn != 1 && m_portCountOut != 1) { return; } + + elem.setAttribute("in", m_portCountIn); // probably not needed, but just in case + elem.setAttribute("out", m_portCountOut); // ditto + m_config.saveSettings(doc, elem, "config"); +} + +void PluginPortConfig::loadSettings(const QDomElement& elem) +{ + // TODO: Assert port counts are what was expected? + //const auto portCountIn = elem.attribute("in", "0").toInt(); + //const auto portCountOut = elem.attribute("out", "0").toInt(); + m_config.loadSettings(elem, "config"); +} + +auto PluginPortConfig::instantiateView(QWidget* parent) -> gui::ComboBox* +{ + auto view = new gui::ComboBox{parent}; + + QString inputType; + switch (m_portCountIn) + { + case 0: break; + case 1: inputType += tr("mono in"); break; + case 2: inputType += tr("stereo in"); break; + default: break; + } + + QString outputType; + switch (m_portCountOut) + { + case 0: break; + case 1: outputType += tr("mono out"); break; + case 2: outputType += tr("stereo out"); break; + default: break; + } + + QString pluginType; + if (inputType.isEmpty()) { pluginType = outputType; } + else if (outputType.isEmpty()) { pluginType = inputType; } + else { pluginType = tr("%1, %2").arg(inputType, outputType); } + + view->setToolTip(tr("L/R channel config for %1 plugin").arg(pluginType)); + view->setModel(model()); + + return view; +} + +void PluginPortConfig::updateOptions() +{ + m_config.clear(); + + const auto monoType = monoPluginType(); + if (monoType == MonoPluginType::None) + { + m_config.addItem(tr("Stereo")); + return; + } + + const auto hasInputPort = m_portCountIn != 0; + const auto hasOutputPort = m_portCountOut != 0; + + // 1. Mono mix + QString itemText; + switch (monoType) + { + case MonoPluginType::Input: + itemText = tr("Downmix to mono"); break; + case MonoPluginType::Output: + itemText = tr("Upmix to stereo"); break; + case MonoPluginType::Both: + itemText = tr("Mono mix"); break; + default: break; + } + m_config.addItem(itemText); + + // 2. Left only + itemText = QString{}; + switch (monoType) + { + case MonoPluginType::Input: + itemText = hasOutputPort + ? tr("L in (R bypass)") + : tr("Left in"); + break; + case MonoPluginType::Output: + itemText = hasInputPort + ? tr("L out (R bypass)") + : tr("Left only"); + break; + case MonoPluginType::Both: + itemText = tr("L only (R bypass)"); + break; + default: break; + } + m_config.addItem(itemText); + + // 3. Right only + itemText = QString{}; + switch (monoType) + { + case MonoPluginType::Input: + itemText = hasOutputPort + ? tr("R in (L bypass)") + : tr("Right in"); + break; + case MonoPluginType::Output: + itemText = hasInputPort + ? tr("R out (L bypass)") + : tr("Right only"); + break; + case MonoPluginType::Both: + itemText = tr("R only (L bypass)"); + break; + default: break; + } + m_config.addItem(itemText); +} + +} // namespace lmms diff --git a/src/core/RemotePlugin.cpp b/src/core/RemotePlugin.cpp index b46c547da62..8cc954d495a 100644 --- a/src/core/RemotePlugin.cpp +++ b/src/core/RemotePlugin.cpp @@ -77,10 +77,10 @@ namespace lmms // simple helper thread monitoring our RemotePlugin - if process terminates // unexpectedly invalidate plugin so LMMS doesn't lock up -ProcessWatcher::ProcessWatcher( RemotePlugin * _p ) : - QThread(), - m_plugin( _p ), - m_quit( false ) +ProcessWatcher::ProcessWatcher(RemotePlugin* plugin) + : QThread{} + , m_plugin{plugin} + , m_quit{false} { } @@ -130,22 +130,21 @@ void ProcessWatcher::run() -RemotePlugin::RemotePlugin() : - QObject(), +RemotePlugin::RemotePlugin(Model* parent) + : QObject{} #ifdef SYNC_WITH_SHM_FIFO - RemotePluginBase( new shmFifo(), new shmFifo() ), + , RemotePluginBase{new shmFifo(), new shmFifo()} #else - RemotePluginBase(), + , RemotePluginBase{} #endif - m_failed( true ), - m_watcher( this ), + , m_failed{true} + , m_watcher{this} #if (QT_VERSION < QT_VERSION_CHECK(5,14,0)) - m_commMutex(QMutex::Recursive), + , m_commMutex{QMutex::Recursive} #endif - m_splitChannels( false ), - m_audioBufferSize( 0 ), - m_inputCount( DEFAULT_CHANNELS ), - m_outputCount( DEFAULT_CHANNELS ) + , m_splitChannels{false} + , m_audioBufferSize{0} + , m_portConfig{parent} { #ifndef SYNC_WITH_SHM_FIFO struct sockaddr_un sa; @@ -359,29 +358,58 @@ bool RemotePlugin::process( const sampleFrame * _in_buf, sampleFrame * _out_buf memset( m_audioBuffer.get(), 0, m_audioBufferSize ); - ch_cnt_t inputs = std::min(m_inputCount, DEFAULT_CHANNELS); + const ch_cnt_t inputsReal = m_portConfig.portCountIn(); + const ch_cnt_t inputsClamped = std::min(inputsReal, DEFAULT_CHANNELS); - if( _in_buf != nullptr && inputs > 0 ) + if( _in_buf != nullptr && inputsClamped > 0 ) { if( m_splitChannels ) { - for( ch_cnt_t ch = 0; ch < inputs; ++ch ) + // NOTE: VST plugins always use split channels + switch (m_portConfig.portConfigIn()) { - for( fpp_t frame = 0; frame < frames; ++frame ) - { - m_audioBuffer[ch * frames + frame] = - _in_buf[frame][ch]; - } + case PluginPortConfig::Config::MonoMix: + for (fpp_t frame = 0; frame < frames; ++frame) + { + // mix stereo to mono for mono plugin input + m_audioBuffer[frame] = (_in_buf[frame][0] + _in_buf[frame][1]) / 2; + } + break; + case PluginPortConfig::Config::LeftOnly: + for (fpp_t frame = 0; frame < frames; ++frame) + { + m_audioBuffer[frame] = _in_buf[frame][0]; + _out_buf[frame][1] = _in_buf[frame][1]; // right bypass + } + break; + case PluginPortConfig::Config::RightOnly: + for (fpp_t frame = 0; frame < frames; ++frame) + { + _out_buf[frame][0] = _in_buf[frame][0]; // left bypass + m_audioBuffer[frame] = _in_buf[frame][1]; + } + break; + case PluginPortConfig::Config::Stereo: + assert(inputsReal == 2); + for (ch_cnt_t ch = 0; ch < inputsClamped; ++ch) + { + for (fpp_t frame = 0; frame < frames; ++frame) + { + m_audioBuffer[ch * frames + frame] = _in_buf[frame][ch]; + } + } + break; + default: throw std::runtime_error{"Invalid input port config"}; } } - else if( inputs == DEFAULT_CHANNELS ) + else if (inputsClamped == DEFAULT_CHANNELS) { memcpy( m_audioBuffer.get(), _in_buf, frames * BYTES_PER_FRAME ); } else { - auto o = (sampleFrame*)m_audioBuffer.get(); - for( ch_cnt_t ch = 0; ch < inputs; ++ch ) + auto o = reinterpret_cast(m_audioBuffer.get()); + for( ch_cnt_t ch = 0; ch < inputsClamped; ++ch ) { for( fpp_t frame = 0; frame < frames; ++frame ) { @@ -394,7 +422,7 @@ bool RemotePlugin::process( const sampleFrame * _in_buf, sampleFrame * _out_buf lock(); sendMessage( IdStartProcessing ); - if( m_failed || _out_buf == nullptr || m_outputCount == 0 ) + if (m_failed || _out_buf == nullptr || m_portConfig.portCountOut() == 0) { unlock(); return false; @@ -403,32 +431,56 @@ bool RemotePlugin::process( const sampleFrame * _in_buf, sampleFrame * _out_buf waitForMessage( IdProcessingDone ); unlock(); - const ch_cnt_t outputs = std::min(m_outputCount, - DEFAULT_CHANNELS); + const ch_cnt_t outputsReal = m_portConfig.portCountOut(); + const ch_cnt_t outputsClamped = std::min(outputsReal, DEFAULT_CHANNELS); + if( m_splitChannels ) { - for( ch_cnt_t ch = 0; ch < outputs; ++ch ) + // NOTE: VST plugins always use split channels + switch (m_portConfig.portConfigOut()) { - for( fpp_t frame = 0; frame < frames; ++frame ) - { - _out_buf[frame][ch] = m_audioBuffer[( m_inputCount+ch )* - frames + frame]; - } + case PluginPortConfig::Config::MonoMix: + for (fpp_t frame = 0; frame < frames; ++frame) + { + _out_buf[frame][0] = _out_buf[frame][1] = m_audioBuffer[inputsReal * frames + frame]; + } + break; + case PluginPortConfig::Config::LeftOnly: + for (fpp_t frame = 0; frame < frames; ++frame) + { + _out_buf[frame][0] = m_audioBuffer[inputsReal * frames + frame]; + } + break; + case PluginPortConfig::Config::RightOnly: + for (fpp_t frame = 0; frame < frames; ++frame) + { + _out_buf[frame][1] = m_audioBuffer[inputsReal * frames + frame]; + } + break; + case PluginPortConfig::Config::Stereo: + for (ch_cnt_t ch = 0; ch < outputsClamped; ++ch) + { + for (fpp_t frame = 0; frame < frames; ++frame) + { + _out_buf[frame][ch] = m_audioBuffer[(inputsReal + ch) * frames + frame]; + } + } + break; + default: throw std::runtime_error{"Invalid output port config"}; } } - else if( outputs == DEFAULT_CHANNELS ) + else if (outputsClamped == DEFAULT_CHANNELS) { - memcpy( _out_buf, m_audioBuffer.get() + m_inputCount * frames, + memcpy( _out_buf, m_audioBuffer.get() + inputsReal * frames, frames * BYTES_PER_FRAME ); } else { - auto o = (sampleFrame*)(m_audioBuffer.get() + m_inputCount * frames); + auto o = reinterpret_cast(m_audioBuffer.get() + inputsReal * frames); // clear buffer, if plugin didn't fill up both channels BufferManager::clear( _out_buf, frames ); - for (ch_cnt_t ch = 0; ch < - std::min(DEFAULT_CHANNELS, outputs); ++ch) + for (ch_cnt_t ch = 0; ch < outputsClamped; ++ch) { for( fpp_t frame = 0; frame < frames; ++frame ) { @@ -476,7 +528,8 @@ void RemotePlugin::hideUI() void RemotePlugin::resizeSharedProcessingMemory() { - const size_t s = (m_inputCount + m_outputCount) * Engine::audioEngine()->framesPerPeriod(); + const size_t s = (m_portConfig.portCountIn() + m_portConfig.portCountOut()) + * Engine::audioEngine()->framesPerPeriod(); try { m_audioBuffer.create(QUuid::createUuid().toString().toStdString(), s); @@ -544,18 +597,17 @@ bool RemotePlugin::processMessage( const message & _m ) break; case IdChangeInputCount: - m_inputCount = _m.getInt( 0 ); + m_portConfig.setPortCountIn(_m.getInt(0)); resizeSharedProcessingMemory(); break; case IdChangeOutputCount: - m_outputCount = _m.getInt( 0 ); + m_portConfig.setPortCountOut(_m.getInt(0)); resizeSharedProcessingMemory(); break; case IdChangeInputOutputCount: - m_inputCount = _m.getInt( 0 ); - m_outputCount = _m.getInt( 1 ); + m_portConfig.setPortCounts(_m.getInt(0), _m.getInt(1)); resizeSharedProcessingMemory(); break;