diff --git a/include/LadspaBase.h b/include/LadspaBase.h index 67b595e2869..be4576f5503 100644 --- a/include/LadspaBase.h +++ b/include/LadspaBase.h @@ -45,6 +45,7 @@ typedef enum BufferRates typedef enum BufferData { TOGGLED, + ENUM, INTEGER, FLOATING, TIME, diff --git a/include/LadspaManager.h b/include/LadspaManager.h index 25d23b61e79..1d055c1d8c5 100644 --- a/include/LadspaManager.h +++ b/include/LadspaManager.h @@ -179,6 +179,12 @@ class EXPORT LadspaManager be described as [-0.1, 3.1]. */ bool isInteger( const ladspa_key_t & _plugin, uint32_t _port ); + /* Indicates that a user interface would probably wish to provide a + stepped control taking only integer values. This is equal to isInteger, + but the number of values is usually small and may be better depicted + with a combo box. */ + bool isEnum( const ladspa_key_t & _plugin, uint32_t _port ); + /* Returns the name of the port. */ QString getPortName( const ladspa_key_t & _plugin, uint32_t _port ); diff --git a/plugins/LadspaEffect/LadspaControlDialog.cpp b/plugins/LadspaEffect/LadspaControlDialog.cpp index f58ccb55b7e..ed16cf9e1c8 100644 --- a/plugins/LadspaEffect/LadspaControlDialog.cpp +++ b/plugins/LadspaEffect/LadspaControlDialog.cpp @@ -112,10 +112,10 @@ void LadspaControlDialog::updateEffectView( LadspaControls * _ctl ) { if( (*it)->port()->proc == proc ) { + buffer_data_t this_port = (*it)->port()->data_type; if( last_port != NONE && - (*it)->port()->data_type == TOGGLED && - !( (*it)->port()->data_type == TOGGLED && - last_port == TOGGLED ) ) + ( this_port == TOGGLED || this_port == ENUM ) && + ( last_port != TOGGLED && last_port != ENUM ) ) { ++row; col = 0; diff --git a/plugins/LadspaEffect/LadspaEffect.cpp b/plugins/LadspaEffect/LadspaEffect.cpp index 0ce66721322..aceea2d4119 100644 --- a/plugins/LadspaEffect/LadspaEffect.cpp +++ b/plugins/LadspaEffect/LadspaEffect.cpp @@ -371,7 +371,11 @@ void LadspaEffect::pluginInstantiation() } p->scale = 1.0f; - if( manager->isPortToggled( m_key, port ) ) + if( manager->isEnum( m_key, port ) ) + { + p->data_type = ENUM; + } + else if( manager->isPortToggled( m_key, port ) ) { p->data_type = TOGGLED; } diff --git a/plugins/LadspaEffect/calf/CMakeLists.txt b/plugins/LadspaEffect/calf/CMakeLists.txt index eaccd87aee8..776752bc5e6 100644 --- a/plugins/LadspaEffect/calf/CMakeLists.txt +++ b/plugins/LadspaEffect/calf/CMakeLists.txt @@ -1,6 +1,8 @@ # Note: # The last version of Calf that was LADSPA-capable is version 0.0.18.2 +SET(CMAKE_CXX_STANDARD 11) + # Parse version info from autoconf FILE(READ veal/configure.ac VERSION_FILE) STRING(REPLACE "[" ";" VERSION_FILE ${VERSION_FILE} ) @@ -12,7 +14,7 @@ FILE(GLOB SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/veal/src/*.cpp") LIST(SORT SOURCES) # Skip files matching pattern -SET(FILE_PATTERNS "ctl;gui;gtk;session;connector;jack;rdf;draw;fluid;preset;lv2;benchmark;win") +SET(FILE_PATTERNS "ctl;gui;gtk;session;connector;jack;rdf;draw;fluid;preset;lv2;benchmark;win;plugin.cpp") FOREACH(_item ${SOURCES}) FOREACH(_pattern ${FILE_PATTERNS}) IF(${_item} MATCHES ${_pattern}) @@ -29,6 +31,10 @@ INCLUDE_DIRECTORIES("${CMAKE_SOURCE_DIR}/include" INSTALL(TARGETS veal LIBRARY DESTINATION "${PLUGIN_DIR}/ladspa") SET_TARGET_PROPERTIES(veal PROPERTIES PREFIX "") + +# Disable OSC messaging, it's not mingw compatible +TARGET_COMPILE_DEFINITIONS(veal PRIVATE DISABLE_OSC=1) + SET(INLINE_FLAGS "") IF("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") SET(INLINE_FLAGS "-finline-functions-called-once -finline-limit=80") @@ -42,4 +48,3 @@ ENDIF() IF(NOT LMMS_BUILD_APPLE AND NOT LMMS_BUILD_OPENBSD) SET_TARGET_PROPERTIES(veal PROPERTIES LINK_FLAGS "${LINK_FLAGS} -shared -Wl,-no-undefined") ENDIF() - diff --git a/plugins/LadspaEffect/calf/config.h.in b/plugins/LadspaEffect/calf/config.h.in index a636660e583..8719ecc4c03 100644 --- a/plugins/LadspaEffect/calf/config.h.in +++ b/plugins/LadspaEffect/calf/config.h.in @@ -1,3 +1,7 @@ #define VERSION "${VERSION}" #define PACKAGE_NAME "veal" #define USE_LADSPA 1 +#define PKGLIBDIR "" + +// Namespace change to avoid conflict with LV2 +#define calf_plugins veal_plugins diff --git a/plugins/LadspaEffect/calf/veal b/plugins/LadspaEffect/calf/veal index 816b4d2b7cc..2841b05a36e 160000 --- a/plugins/LadspaEffect/calf/veal +++ b/plugins/LadspaEffect/calf/veal @@ -1 +1 @@ -Subproject commit 816b4d2b7cc20429faf96863d7e8ba1cda48ace8 +Subproject commit 2841b05a36e53116508a8a862f052e14dad00d67 diff --git a/src/core/DataFile.cpp b/src/core/DataFile.cpp index c66fe0d4981..bf2a43e6e56 100644 --- a/src/core/DataFile.cpp +++ b/src/core/DataFile.cpp @@ -968,6 +968,67 @@ void DataFile::upgrade_1_2_0_rc2_42() } +/** + * Helper function to call a functor for all effect ports' DomElements, + * providing the functor with lists to add and remove DomElements. Helpful for + * patching port values from savefiles. + */ +template +void iterate_ladspa_ports(QDomElement& effect, Ftor& ftor) +{ + // Head back up the DOM to upgrade ports + QDomNodeList ladspacontrols = effect.elementsByTagName( "ladspacontrols" ); + for( int m = 0; !ladspacontrols.item( m ).isNull(); ++m ) + { + QList addList, removeList; + QDomElement ladspacontrol = ladspacontrols.item( m ).toElement(); + for( QDomElement port = ladspacontrol.firstChild().toElement(); + !port.isNull(); port = port.nextSibling().toElement() ) + { + QStringList parts = port.tagName().split("port"); + // Not a "port" + if ( parts.size() < 2 ) + { + continue; + } + int num = parts[1].toInt(); + + // From Qt's docs of QDomNode: + // * copying a QDomNode is OK, they still have the same + // pointer to the "internal" QDomNodePrivate. + // * Also, they are using linked lists, which means + // deleting or appending QDomNode does not invalidate + // any other pointers. + // => Inside ftor, you can (and should) push back the + // QDomElements by value, not references + // => The loops below for adding and removing don't + // invalidate any other QDomElements + ftor(port, num, addList, removeList); + } + + // Add ports marked for adding + for ( QDomElement e : addList ) + { + ladspacontrol.appendChild( e ); + } + // Remove ports marked for removal + for ( QDomElement e : removeList ) + { + ladspacontrol.removeChild( e ); + } + } +} + +// helper function if you need to print a QDomNode +QDebug operator<<(QDebug dbg, const QDomNode& node) +{ + QString s; + QTextStream str(&s, QIODevice::WriteOnly); + node.save(str, 2); + dbg << qPrintable(s); + return dbg; +} + void DataFile::upgrade_1_3_0() { QDomNodeList list = elementsByTagName( "instrument" ); @@ -1009,6 +1070,8 @@ void DataFile::upgrade_1_3_0() QDomNodeList attributes = key.elementsByTagName( "attribute" ); for( int k = 0; !attributes.item( k ).isNull(); ++k ) { + // Effect name changes + QDomElement attribute = attributes.item( k ).toElement(); if( attribute.attribute( "name" ) == "file" && ( attribute.attribute( "value" ) == "calf" || @@ -1016,6 +1079,283 @@ void DataFile::upgrade_1_3_0() { attribute.setAttribute( "value", "veal" ); } + else if( attribute.attribute( "name" ) == "plugin" && + attribute.attribute( "value" ) == "Sidechaincompressor" ) + { + attribute.setAttribute( "value", "SidechainCompressor" ); + } + else if( attribute.attribute( "name" ) == "plugin" && + attribute.attribute( "value" ) == "Sidechaingate" ) + { + attribute.setAttribute( "value", "SidechainGate" ); + } + else if( attribute.attribute( "name" ) == "plugin" && + attribute.attribute( "value" ) == "Multibandcompressor" ) + { + attribute.setAttribute( "value", "MultibandCompressor" ); + } + else if( attribute.attribute( "name" ) == "plugin" && + attribute.attribute( "value" ) == "Multibandgate" ) + { + attribute.setAttribute( "value", "MultibandGate" ); + } + else if( attribute.attribute( "name" ) == "plugin" && + attribute.attribute( "value" ) == "Multibandlimiter" ) + { + attribute.setAttribute( "value", "MultibandLimiter" ); + } + + // Handle port changes + + if( attribute.attribute( "name" ) == "plugin" && + ( attribute.attribute( "value" ) == "MultibandLimiter" || + attribute.attribute( "value" ) == "MultibandCompressor" || + attribute.attribute( "value" ) == "MultibandGate" ) ) + { + auto fn = [&](QDomElement& port, int num, QList&, QList& removeList) + { + // Mark ports for removal + if ( num >= 18 && num <= 23 ) + { + removeList << port; + } + // Bump higher ports up 6 positions + else if ( num >= 24 ) + { + // port01...port010, etc + QString name( "port0" ); + name.append( QString::number( num -6 ) ); + port.setTagName( name ); + } + }; + iterate_ladspa_ports(effect, fn); + } + + if( attribute.attribute( "name" ) == "plugin" && + ( attribute.attribute( "value" ) == "Pulsator" ) ) + { + auto fn = [&](QDomElement& port, int num, QList& addList, QList& removeList) + { + switch(num) + { + case 16: + { + // old freq is now at port 25 + QDomElement portCopy = createElement("port025"); + portCopy.setAttribute("data", port.attribute("data")); + addList << portCopy; + // remove old freq port + removeList << port; + // set the "timing" port to choose port23+2=port25 (timing in Hz) + QDomElement timing = createElement("port022"); + timing.setAttribute("data", 2); + addList << timing; + break; + } + // port 18 (modulation) => 17 + case 17: + port.setTagName("port016"); + break; + case 18: + { + // leave port 18 (offsetr), but add port 17 (offsetl) + QDomElement offsetl = createElement("port017"); + offsetl.setAttribute("data", 0.0f); + addList << offsetl; + // additional: bash port 21 to 1 + QDomElement pulsewidth = createElement("port021"); + pulsewidth.setAttribute("data", 1.0f); + addList << pulsewidth; + break; + } + } + + + }; + iterate_ladspa_ports(effect, fn); + } + + + if( attribute.attribute( "name" ) == "plugin" && + ( attribute.attribute( "value" ) == "VintageDelay" ) ) + { + auto fn = [&](QDomElement& port, int num, QList& addList, QList& ) + { + switch(num) + { + case 4: + { + // BPM is now port028 + port.setTagName("port028"); + // bash timing to BPM + QDomElement timing = createElement("port027"); + timing.setAttribute("data", 0); + addList << timing; + + // port 5 and 6 (in, out gain) need to be bashed to 1: + QDomElement input = createElement("port05"); + input.setAttribute("data", 1.0f); + addList << input; + QDomElement output = createElement("port06"); + output.setAttribute("data", 1.0f); + addList << output; + + break; + } + default: + // all other ports increase by 10 + QString name( "port0" ); + name.append( QString::number( num + 10 ) ); + port.setTagName( name ); + } + + + }; + iterate_ladspa_ports(effect, fn); + } + + if( attribute.attribute( "name" ) == "plugin" && + ( ( attribute.attribute( "value" ) == "Equalizer5Band" ) + || ( attribute.attribute( "value" ) == "Equalizer8Band" ) + || ( attribute.attribute( "value" ) == "Equalizer12Band" ) ) ) + { + // NBand equalizers got 4 q nobs inserted. We need to shift everything else... + // HOWEVER: 5 band eq has only 2 q nobs inserted (no LS/HS filters) + bool band5 = ( attribute.attribute( "value" ) == "Equalizer5Band" ); + auto fn = [&](QDomElement& port, int num, QList& addList, QList& ) + { + if(num == 4) + { + // don't modify port 4, but some other ones: + int zoom_port; + if(attribute.attribute( "value" ) == "Equalizer5Band") + zoom_port = 36; + else if(attribute.attribute( "value" ) == "Equalizer8Band") + zoom_port = 48; + else // 12 band + zoom_port = 64; + // bash zoom to 0.25 + QString name( "port0" ); + name.append( QString::number( zoom_port ) ); + QDomElement timing = createElement(name); + timing.setAttribute("data", 0.25f); + addList << timing; + } + // the following code could be refactored, but I did careful code-reading + // to prevent copy-paste-errors + if(num == 18) + { + // 18 => 19 + port.setTagName("port019"); + // insert port 18 (q) + QDomElement q = createElement("port018"); + q.setAttribute("data", 0.707f); + addList << q; + } + else if(num >= 19 && num <= 20) + { + // num += 1 + QString name( "port0" ); + name.append( QString::number( num + 1 ) ); + port.setTagName( name ); + } + else if(num == 21) + { + // 21 => 23 + port.setTagName("port023"); + // insert port 22 (q) + QDomElement q = createElement("port022"); + q.setAttribute("data", 0.707f); + addList << q; + } + else if(num >= 22 && (num <= 23 || band5)) + { + // num += 2 + QString name( "port0" ); + name.append( QString::number( num + 2 ) ); + port.setTagName( name ); + } + else if(num == 24 && !band5) + { + // 24 => 27 + port.setTagName("port027"); + // insert port 26 (q) + QDomElement q = createElement("port026"); + q.setAttribute("data", 0.707f); + addList << q; + } + else if(num >= 25 && num <= 26 && !band5) + { + // num += 3 + QString name( "port0" ); + name.append( QString::number( num + 3 ) ); + port.setTagName( name ); + } + else if(num == 27 && !band5) + { + // 27 => 31 + port.setTagName("port031"); + // insert port 30 (q) + QDomElement q = createElement("port030"); + q.setAttribute("data", 0.707f); + addList << q; + } + else if(num >= 28 && !band5) + { + // num += 4 + QString name( "port0" ); + name.append( QString::number( num + 4 ) ); + port.setTagName( name ); + } + }; + iterate_ladspa_ports(effect, fn); + } + + if( attribute.attribute( "name" ) == "plugin" && + attribute.attribute( "value" ) == "Saturator" ) + { + auto fn = [&](QDomElement& port, int num, QList&, QList& ) + { + // These ports have been shifted a bit weird... + if( num == 7 ) + { + port.setTagName("port015"); + } + else if(num == 12) + { + port.setTagName("port016"); + } + else if(num == 13) + { + port.setTagName("port017"); + } + else if ( num >= 15 ) + { + QString name( "port0" ); + name.append( QString::number( num + 3 ) ); + port.setTagName( name ); + } + }; + iterate_ladspa_ports(effect, fn); + } + + if( attribute.attribute( "name" ) == "plugin" && + attribute.attribute( "value" ) == "StereoTools" ) + { + auto fn = [&](QDomElement& port, int num, QList&, QList& ) + { + // This effect can not be back-ported due to bugs in the old version, + // or due to different behaviour. We thus port all parameters we can, + // and bash all new parameters (in this case, s.level and m.level) to + // their new defaults (both 1.0f in this case) + + if( num == 23 || num == 25 ) + { + port.setAttribute("data", 1.0f); + } + }; + iterate_ladspa_ports(effect, fn); + } } } } diff --git a/src/core/LadspaControl.cpp b/src/core/LadspaControl.cpp index d2d6e147a61..cf8c6e4680b 100644 --- a/src/core/LadspaControl.cpp +++ b/src/core/LadspaControl.cpp @@ -48,6 +48,8 @@ LadspaControl::LadspaControl( Model * _parent, port_desc_t * _port, switch( m_port->data_type ) { case TOGGLED: + m_toggledModel.setInitValue( + static_cast( m_port->def ) ); connect( &m_toggledModel, SIGNAL( dataChanged() ), this, SLOT( ledChanged() ) ); if( m_port->def == 1.0f ) @@ -59,6 +61,7 @@ LadspaControl::LadspaControl( Model * _parent, port_desc_t * _port, break; case INTEGER: + case ENUM: m_knobModel.setRange( static_cast( m_port->max ), static_cast( m_port->min ), 1 + static_cast( m_port->max - @@ -117,6 +120,7 @@ LADSPA_Data LadspaControl::value() case TOGGLED: return static_cast( m_toggledModel.value() ); case INTEGER: + case ENUM: case FLOATING: return static_cast( m_knobModel.value() ); case TIME: @@ -136,6 +140,7 @@ ValueBuffer * LadspaControl::valueBuffer() { case TOGGLED: case INTEGER: + case ENUM: return NULL; case FLOATING: return m_knobModel.valueBuffer(); @@ -159,6 +164,7 @@ void LadspaControl::setValue( LADSPA_Data _value ) m_toggledModel.setValue( static_cast( _value ) ); break; case INTEGER: + case ENUM: m_knobModel.setValue( static_cast( _value ) ); break; case FLOATING: @@ -193,6 +199,7 @@ void LadspaControl::saveSettings( QDomDocument& doc, m_toggledModel.saveSettings( doc, e, "data" ); break; case INTEGER: + case ENUM: case FLOATING: m_knobModel.saveSettings( doc, e, "data" ); break; @@ -216,35 +223,64 @@ void LadspaControl::loadSettings( const QDomElement& parent, const QString& name QString linkModelName = "link"; QDomElement e = parent.namedItem( name ).toElement(); - // COMPAT < 1.0.0: detect old data format where there's either no dedicated sub - // element or there's a direct sub element with automation link information - if( e.isNull() || e.hasAttribute( "id" ) ) + if(e.isNull()) { - dataModelName = name; - linkModelName = name + "link"; - e = parent; + // the port exists in the current effect, but not in the + // savefile => it's a new port, so load the default value + if( m_link ) + m_linkEnabledModel.setValue(m_linkEnabledModel.initValue()); + switch( m_port->data_type ) + { + case TOGGLED: + m_toggledModel.setValue(m_toggledModel.initValue()); + break; + case INTEGER: + case ENUM: + case FLOATING: + m_knobModel.setValue(m_knobModel.initValue()); + break; + case TIME: + m_tempoSyncKnobModel.setValue(m_tempoSyncKnobModel.initValue()); + break; + default: + printf("LadspaControl::loadSettings BAD BAD BAD\n"); + break; + } } - - if( m_link ) + else { - m_linkEnabledModel.loadSettings( e, linkModelName ); - } - switch( m_port->data_type ) - { - case TOGGLED: - m_toggledModel.loadSettings( e, dataModelName ); - break; - case INTEGER: - case FLOATING: - m_knobModel.loadSettings( e, dataModelName ); - break; - case TIME: - m_tempoSyncKnobModel.loadSettings( e, dataModelName ); - break; - default: - printf("LadspaControl::loadSettings BAD BAD BAD\n"); - break; + // COMPAT < 1.0.0: detect old data format where there's either no dedicated sub + // element or there's a direct sub element with automation link information + if( e.isNull() || e.hasAttribute( "id" ) ) + { + dataModelName = name; + linkModelName = name + "link"; + e = parent; + } + + if( m_link ) + { + m_linkEnabledModel.loadSettings( e, linkModelName ); + } + + switch( m_port->data_type ) + { + case TOGGLED: + m_toggledModel.loadSettings( e, dataModelName ); + break; + case INTEGER: + case ENUM: + case FLOATING: + m_knobModel.loadSettings( e, dataModelName ); + break; + case TIME: + m_tempoSyncKnobModel.loadSettings( e, dataModelName ); + break; + default: + printf("LadspaControl::loadSettings BAD BAD BAD\n"); + break; + } } } @@ -259,6 +295,7 @@ void LadspaControl::linkControls( LadspaControl * _control ) BoolModel::linkModels( &m_toggledModel, _control->toggledModel() ); break; case INTEGER: + case ENUM: case FLOATING: FloatModel::linkModels( &m_knobModel, _control->knobModel() ); break; @@ -309,6 +346,7 @@ void LadspaControl::unlinkControls( LadspaControl * _control ) BoolModel::unlinkModels( &m_toggledModel, _control->toggledModel() ); break; case INTEGER: + case ENUM: case FLOATING: FloatModel::unlinkModels( &m_knobModel, _control->knobModel() ); break; diff --git a/src/core/LadspaManager.cpp b/src/core/LadspaManager.cpp index 4286c34aca0..fc06dfda9ac 100644 --- a/src/core/LadspaManager.cpp +++ b/src/core/LadspaManager.cpp @@ -749,6 +749,31 @@ bool LadspaManager::isInteger( const ladspa_key_t & _plugin, +bool LadspaManager::isEnum( const ladspa_key_t & _plugin, uint32_t _port ) +{ + if( m_ladspaManagerMap.contains( _plugin ) + && _port < getPortCount( _plugin ) ) + { + LADSPA_Descriptor_Function descriptorFunction = + m_ladspaManagerMap[_plugin]->descriptorFunction; + const LADSPA_Descriptor * descriptor = + descriptorFunction( + m_ladspaManagerMap[_plugin]->index ); + LADSPA_PortRangeHintDescriptor hintDescriptor = + descriptor->PortRangeHints[_port].HintDescriptor; + // This is an LMMS extension to ladspa + return( LADSPA_IS_HINT_INTEGER( hintDescriptor ) && + LADSPA_IS_HINT_TOGGLED( hintDescriptor ) ); + } + else + { + return( false ); + } +} + + + + QString LadspaManager::getPortName( const ladspa_key_t & _plugin, uint32_t _port ) { diff --git a/src/gui/widgets/LadspaControlView.cpp b/src/gui/widgets/LadspaControlView.cpp index a0f68ab2c29..b3bc1de3430 100644 --- a/src/gui/widgets/LadspaControlView.cpp +++ b/src/gui/widgets/LadspaControlView.cpp @@ -77,6 +77,7 @@ LadspaControlView::LadspaControlView( QWidget * _parent, } case INTEGER: + case ENUM: case FLOATING: knb = new Knob( knobBright_26, this, m_ctl->port()->name ); break;