From 1f5ef70d2cb3d116938179c15655dfdc400da382 Mon Sep 17 00:00:00 2001 From: Johannes Lorenz Date: Sat, 5 Apr 2014 08:15:35 +0200 Subject: [PATCH 1/2] Logscales temporary commit. --- include/AutomatableModel.h | 32 ++++++- include/ControllerConnection.h | 2 +- include/LadspaBase.h | 5 ++ plugins/LadspaEffect/LadspaEffect.cpp | 11 +-- src/core/AutomatableModel.cpp | 122 +++++++++++++++++++++----- src/core/LadspaControl.cpp | 8 ++ 6 files changed, 151 insertions(+), 29 deletions(-) diff --git a/include/AutomatableModel.h b/include/AutomatableModel.h index 242b50595..4bca72891 100644 --- a/include/AutomatableModel.h +++ b/include/AutomatableModel.h @@ -60,13 +60,19 @@ class ControllerConnection; - +#include class EXPORT AutomatableModel : public Model, public JournallingObject { Q_OBJECT public: typedef QVector AutoModelVector; + enum ScaleType + { + Linear, + Logarithmic + }; + enum DataType { Float, @@ -175,6 +181,14 @@ public: } void setRange( const float min, const float max, const float step = 1 ); + void setScaleType( ScaleType sc ) { + printf("Settings scale to: %d\n", (int)sc); + m_scaleType = sc; + } + void setScaleLogarithmic( bool set_to_true = true ) + { + setScaleType(set_to_true ? Logarithmic : Linear); + } void setStep( const float step ); @@ -193,8 +207,14 @@ public: void unlinkAllModels(); - /*! \brief Saves settings (value, automation links and controller connections) of AutomatableModel into - specified DOM element using as attribute/node name */ + /** + * @brief Saves settings (value, automation links and controller connections) of AutomatableModel into + * specified DOM element using as attribute/node name + * @param doc TODO + * @param element Where this option shall be saved. + * Depending on the model, this can be done in an attribute or in a subnode. + * @param name Name to store this model as. + */ virtual void saveSettings( QDomDocument& doc, QDomElement& element, const QString& name ); /*! \brief Loads settings (value, automation links and controller connections) of AutomatableModel from @@ -248,8 +268,13 @@ private: void linkModel( AutomatableModel* model ); void unlinkModel( AutomatableModel* model ); + //! rounds @a value to @a where if it is close to it + //! @param value will be modified to rounded value + template void round_at(T &value, const T &where) const; + DataType m_dataType; + ScaleType m_scaleType; //! scale type, linear by default float m_value; float m_initValue; float m_minValue; @@ -267,6 +292,7 @@ private: bool m_hasLinkedModels; + //! NULL if not appended to controller, otherwise connection info ControllerConnection* m_controllerConnection; diff --git a/include/ControllerConnection.h b/include/ControllerConnection.h index 440960fca..648c1711a 100644 --- a/include/ControllerConnection.h +++ b/include/ControllerConnection.h @@ -100,7 +100,7 @@ protected: //virtual controllerDialog * createDialog( QWidget * _parent ); Controller * m_controller; QString m_targetName; - int m_controllerId; + int m_controllerId; bool m_ownsController; diff --git a/include/LadspaBase.h b/include/LadspaBase.h index 4b77083a7..723d8251f 100644 --- a/include/LadspaBase.h +++ b/include/LadspaBase.h @@ -51,6 +51,8 @@ typedef enum BufferData NONE } buffer_data_t; +//! This struct is used to hold port descriptions internally +//! which where received from the ladspa plugin typedef struct PortDescription { QString name; @@ -64,6 +66,9 @@ typedef struct PortDescription LADSPA_Data min; LADSPA_Data def; LADSPA_Data value; + //! This is true iff ladspa suggests logscale + //! Note however that the model can still decide to use a linear scale + bool suggests_logscale; LADSPA_Data * buffer; LadspaControl * control; } port_desc_t; diff --git a/plugins/LadspaEffect/LadspaEffect.cpp b/plugins/LadspaEffect/LadspaEffect.cpp index 2bd082f7c..1dd5072d0 100644 --- a/plugins/LadspaEffect/LadspaEffect.cpp +++ b/plugins/LadspaEffect/LadspaEffect.cpp @@ -305,12 +305,12 @@ void LadspaEffect::pluginInstantiation() // Determine the port's category. if( manager->isPortAudio( m_key, port ) ) { - // Nasty manual memory management--was having difficulty - // with some prepackaged plugins that were segfaulting - // during cleanup. It was easier to troubleshoot with the - // memory management all taking place in one file. + // Nasty manual memory management--was having difficulty + // with some prepackaged plugins that were segfaulting + // during cleanup. It was easier to troubleshoot with the + // memory management all taking place in one file. p->buffer = - new LADSPA_Data[engine::mixer()->framesPerPeriod()]; + new LADSPA_Data[engine::mixer()->framesPerPeriod()]; if( p->name.toUpper().contains( "IN" ) && manager->isPortInput( m_key, port ) ) @@ -430,6 +430,7 @@ void LadspaEffect::pluginInstantiation() p->value = p->def; + p->suggests_logscale = manager->isLogarithmic( m_key, port ); ports.append( p ); diff --git a/src/core/AutomatableModel.cpp b/src/core/AutomatableModel.cpp index 70cfb9631..ae4db5911 100644 --- a/src/core/AutomatableModel.cpp +++ b/src/core/AutomatableModel.cpp @@ -39,6 +39,7 @@ AutomatableModel::AutomatableModel( DataType type, Model* parent, const QString & displayName, bool defaultConstructed ) : Model( parent, displayName, defaultConstructed ), m_dataType( type ), + m_scaleType( Linear ), m_value( val ), m_initValue( val ), m_minValue( min ), @@ -85,21 +86,30 @@ bool AutomatableModel::isAutomated() const void AutomatableModel::saveSettings( QDomDocument& doc, QDomElement& element, const QString& name ) { + bool automated_or_controlled = false; + if( isAutomated() ) { + // automation needs tuple of data (name, id, value) + // => it must be appended as a node QDomElement me = doc.createElement( name ); me.setAttribute( "id", id() ); me.setAttribute( "value", m_value ); element.appendChild( me ); + + automated_or_controlled = true; } else { + // non automation => can be saved as attribute element.setAttribute( name, m_value ); } if( m_controllerConnection ) { QDomElement controllerElement; + + // get "connection" element (and create it if needed) QDomNode node = element.namedItem( "connection" ); if( node.isElement() ) { @@ -115,6 +125,15 @@ void AutomatableModel::saveSettings( QDomDocument& doc, QDomElement& element, co m_controllerConnection->saveSettings( doc, element ); controllerElement.appendChild( element ); + + automated_or_controlled = true; + } + + if(automated_or_controlled && (m_scaleType != Linear)) + { // note: if we have more scale types than two, make + // a mapper function enums <-> string + if(m_scaleType == Logarithmic) + element.setAttribute("scale_type", "log"); } } @@ -123,6 +142,15 @@ void AutomatableModel::saveSettings( QDomDocument& doc, QDomElement& element, co void AutomatableModel::loadSettings( const QDomElement& element, const QString& name ) { + // read scale type and overwrite default scale type + if(element.hasAttribute("scale_type")) // wrong in most cases + { + if(element.attribute("scale_type") == "log") + setScaleType(Logarithmic); + } + else + setScaleType(Linear); + // compat code QDomNode node = element.namedItem( AutomationPattern::classNodeName() ); if( node.isElement() ) @@ -141,9 +169,12 @@ void AutomatableModel::loadSettings( const QDomElement& element, const QString& } return; } + // logscales were not existing at this point of time + // so they can be ignored } QDomNode connectionNode = element.namedItem( "connection" ); + // reads controller connection if( connectionNode.isElement() ) { QDomNode thisConnection = connectionNode.toElement().namedItem( name ); @@ -154,14 +185,20 @@ void AutomatableModel::loadSettings( const QDomElement& element, const QString& //m_controllerConnection->setTargetName( displayName() ); } } - + + // models can be stored as elements (port00) or attributes (port10): + // + // + // + // element => there is automation data node = element.namedItem( name ); - if( node.isElement() ) - { - changeID( node.toElement().attribute( "id" ).toInt() ); - setValue( node.toElement().attribute( "value" ).toFloat() ); - } - else if( element.hasAttribute( name ) ) + if( node.isElement() ) + { + changeID( node.toElement().attribute( "id" ).toInt() ); + setValue( node.toElement().attribute( "value" ).toFloat() ); + } + else if( element.hasAttribute( name ) ) + // attribute => read the element's value from the attribute list { setInitValue( element.attribute( name ).toFloat() ); } @@ -207,12 +244,34 @@ void AutomatableModel::setValue( const float value ) +//! @brief Scales @value from linear to logarithmic. +//! Value should be within [0,1] +template T log_to_linear_scale(T min, T max, T value) +// we get min and max from the class => TODO +{ + printf("scale: in: %f, out: %f\n",value, exp((log(max)-log(min)) * value + log(min))); + return exp((log(max)-log(min)) * value + log(min)); +} + + + + void AutomatableModel::setAutomatedValue( const float value ) { ++m_setValueDepth; const float oldValue = m_value; - m_value = fittedValue( value ); + const float scaled_value = + (m_scaleType == Linear) + ? value + : log_to_linear_scale( + minValue(), maxValue(), + // fit value into [0,1]: + (value - minValue()) / maxValue() + ); + + m_value = fittedValue( scaled_value ); + if( oldValue != m_value ) { // notify linked models @@ -223,6 +282,8 @@ void AutomatableModel::setAutomatedValue( const float value ) !(*it)->fittedValue( m_value ) != (*it)->m_value ) { + // @TOBY: don't take m_value, but better: value, + // otherwise, we convert to log twice? (*it)->setAutomatedValue( m_value ); } } @@ -271,6 +332,19 @@ void AutomatableModel::setStep( const float step ) +template +void AutomatableModel::round_at(T& value, const T& where) const +{ + if( qAbs( value - where ) + < typeInfo::minEps() * qAbs( m_step ) ) + { + value = where; + } +} + + + + float AutomatableModel::fittedValue( float value ) const { value = tLimit( value, m_minValue, m_maxValue ); @@ -280,17 +354,9 @@ float AutomatableModel::fittedValue( float value ) const value = nearbyintf( value / m_step ) * m_step; } - // correct rounding error at the border - if( qAbs( value - m_maxValue ) < typeInfo::minEps() * qAbs( m_step ) ) - { - value = m_maxValue; - } - - // correct rounding error if value = 0 - if( qAbs( value ) < typeInfo::minEps() * qAbs( m_step ) ) - { - value = 0; - } + round_at(value, m_maxValue, m_step); + round_at(value, m_minValue, m_step); + round_at(value, 0, m_step); if( value < m_minValue ) { @@ -384,11 +450,27 @@ void AutomatableModel::setControllerConnection( ControllerConnection* c ) + float AutomatableModel::controllerValue( int frameOffset ) const { if( m_controllerConnection ) { - const float v = minValue() + ( range() * controllerConnection()->currentValue( frameOffset ) ); + float v; + switch(m_scaleType) + { + case Linear: + v = minValue() + ( range() * controllerConnection()->currentValue( frameOffset ) ); + break; + case Logarithmic: + v = log_to_linear_scale(minValue(), maxValue(), + controllerConnection()->currentValue( frameOffset )); + break; + default: + qFatal("AutomatableModel::controllerValue(int)" + "lacks implementation for a scale type"); + v = 0; // suppress warning... + break; + } if( typeInfo::isEqual( m_step, 1 ) ) { return qRound( v ); diff --git a/src/core/LadspaControl.cpp b/src/core/LadspaControl.cpp index 5f469611c..1b9e69393 100644 --- a/src/core/LadspaControl.cpp +++ b/src/core/LadspaControl.cpp @@ -54,6 +54,8 @@ LadspaControl::LadspaControl( Model * _parent, port_desc_t * _port, { m_toggledModel.setValue( true ); } + // TODO: careful: we must prevent saved scales + m_toggledModel.setScaleLogarithmic(m_port->suggests_logscale); break; case INTEGER: @@ -65,6 +67,8 @@ LadspaControl::LadspaControl( Model * _parent, port_desc_t * _port, static_cast( m_port->def ) ); connect( &m_knobModel, SIGNAL( dataChanged() ), this, SLOT( knobChanged() ) ); + // TODO: careful: we must prevent saved scales + m_knobModel.setScaleLogarithmic(m_port->suggests_logscale); break; case FLOATING: @@ -76,6 +80,8 @@ LadspaControl::LadspaControl( Model * _parent, port_desc_t * _port, m_knobModel.setInitValue( m_port->def ); connect( &m_knobModel, SIGNAL( dataChanged() ), this, SLOT( knobChanged() ) ); + // TODO: careful: we must prevent saved scales + m_knobModel.setScaleLogarithmic(m_port->suggests_logscale); break; case TIME: @@ -85,6 +91,8 @@ LadspaControl::LadspaControl( Model * _parent, port_desc_t * _port, m_tempoSyncKnobModel.setInitValue( m_port->def ); connect( &m_tempoSyncKnobModel, SIGNAL( dataChanged() ), this, SLOT( tempoKnobChanged() ) ); + // TODO: careful: we must prevent saved scales + m_tempoSyncKnobModel.setScaleLogarithmic(m_port->suggests_logscale); break; default: From 554323dcb6e90c6f176c61ca3a53a0f607240165 Mon Sep 17 00:00:00 2001 From: Johannes Lorenz Date: Sat, 5 Apr 2014 09:05:22 +0200 Subject: [PATCH 2/2] Current logscales fix. --- include/AutomatableModel.h | 9 +++++- src/core/AutomatableModel.cpp | 56 ++++++++++++++++++++++------------- 2 files changed, 44 insertions(+), 21 deletions(-) diff --git a/include/AutomatableModel.h b/include/AutomatableModel.h index 4bca72891..931022ab8 100644 --- a/include/AutomatableModel.h +++ b/include/AutomatableModel.h @@ -182,7 +182,6 @@ public: void setRange( const float min, const float max, const float step = 1 ); void setScaleType( ScaleType sc ) { - printf("Settings scale to: %d\n", (int)sc); m_scaleType = sc; } void setScaleLogarithmic( bool set_to_true = true ) @@ -251,6 +250,10 @@ public slots: protected: + //! returns a value which is in range between min() and + //! max() and aligned according to the step size (step size 0.05 -> value + //! 0.12345 becomes 0.10 etc.). You should always call it at the end after + //! doing your own calculations. float fittedValue( float value ) const; @@ -268,6 +271,10 @@ private: void linkModel( AutomatableModel* model ); void unlinkModel( AutomatableModel* model ); + //! @brief Scales @value from linear to logarithmic. + //! Value should be within [0,1] + template T log_to_linear_scale(T value) const; + //! rounds @a value to @a where if it is close to it //! @param value will be modified to rounded value template void round_at(T &value, const T &where) const; diff --git a/src/core/AutomatableModel.cpp b/src/core/AutomatableModel.cpp index ae4db5911..e00b3ce5f 100644 --- a/src/core/AutomatableModel.cpp +++ b/src/core/AutomatableModel.cpp @@ -246,16 +246,46 @@ void AutomatableModel::setValue( const float value ) //! @brief Scales @value from linear to logarithmic. //! Value should be within [0,1] +//! @todo This should be moved into a maths header template T log_to_linear_scale(T min, T max, T value) -// we get min and max from the class => TODO { - printf("scale: in: %f, out: %f\n",value, exp((log(max)-log(min)) * value + log(min))); return exp((log(max)-log(min)) * value + log(min)); } +template T AutomatableModel::log_to_linear_scale(T value) const +{ + return ::log_to_linear_scale(minValue(), maxValue(), value); +} + + + + +//! @todo: this should be moved into a maths header +template +void round_at(T& value, const T& where, const T& step_size) +{ + if( qAbs( value - where ) + < typeInfo::minEps() * qAbs( step_size ) ) + { + value = where; + } +} + + + + +template +void AutomatableModel::round_at(T& value, const T& where) const +{ + ::round_at(value, where, m_step); +} + + + + void AutomatableModel::setAutomatedValue( const float value ) { ++m_setValueDepth; @@ -265,7 +295,6 @@ void AutomatableModel::setAutomatedValue( const float value ) (m_scaleType == Linear) ? value : log_to_linear_scale( - minValue(), maxValue(), // fit value into [0,1]: (value - minValue()) / maxValue() ); @@ -332,19 +361,6 @@ void AutomatableModel::setStep( const float step ) -template -void AutomatableModel::round_at(T& value, const T& where) const -{ - if( qAbs( value - where ) - < typeInfo::minEps() * qAbs( m_step ) ) - { - value = where; - } -} - - - - float AutomatableModel::fittedValue( float value ) const { value = tLimit( value, m_minValue, m_maxValue ); @@ -354,9 +370,9 @@ float AutomatableModel::fittedValue( float value ) const value = nearbyintf( value / m_step ) * m_step; } - round_at(value, m_maxValue, m_step); - round_at(value, m_minValue, m_step); - round_at(value, 0, m_step); + round_at(value, m_maxValue); + round_at(value, m_minValue); + round_at(value, 0.0f); if( value < m_minValue ) { @@ -462,7 +478,7 @@ float AutomatableModel::controllerValue( int frameOffset ) const v = minValue() + ( range() * controllerConnection()->currentValue( frameOffset ) ); break; case Logarithmic: - v = log_to_linear_scale(minValue(), maxValue(), + v = log_to_linear_scale( controllerConnection()->currentValue( frameOffset )); break; default: