diff --git a/include/AutomatableModel.h b/include/AutomatableModel.h index 242b50595..4a1e6d2b5 100644 --- a/include/AutomatableModel.h +++ b/include/AutomatableModel.h @@ -60,13 +60,18 @@ class ControllerConnection; - class EXPORT AutomatableModel : public Model, public JournallingObject { Q_OBJECT public: typedef QVector AutoModelVector; + enum ScaleType + { + Linear, + Logarithmic + }; + enum DataType { Float, @@ -175,6 +180,13 @@ public: } void setRange( const float min, const float max, const float step = 1 ); + void setScaleType( ScaleType sc ) { + m_scaleType = sc; + } + void setScaleLogarithmic( bool setToTrue = true ) + { + setScaleType( setToTrue ? Logarithmic : Linear ); + } void setStep( const float step ); @@ -193,8 +205,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 @@ -231,6 +249,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; @@ -248,8 +270,17 @@ private: void linkModel( AutomatableModel* model ); void unlinkModel( AutomatableModel* model ); + //! @brief Scales @value from linear to logarithmic. + //! Value should be within [0,1] + template T logToLinearScale( 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 roundAt( 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 +298,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/include/TempoSyncKnob.h b/include/TempoSyncKnob.h index 964d77cf4..0f6a9063c 100644 --- a/include/TempoSyncKnob.h +++ b/include/TempoSyncKnob.h @@ -38,7 +38,7 @@ class EXPORT TempoSyncKnob : public knob { Q_OBJECT public: - TempoSyncKnob( int knobNum, QWidget* parent = NULL, const QString& name = QString() ); + TempoSyncKnob( knobTypes knobNum, QWidget* parent = NULL, const QString& name = QString() ); virtual ~TempoSyncKnob(); const QString & syncDescription(); diff --git a/include/knob.h b/include/knob.h index 81141220d..6b738911f 100644 --- a/include/knob.h +++ b/include/knob.h @@ -46,6 +46,8 @@ enum knobTypes class EXPORT knob : public QWidget, public FloatModelView { Q_OBJECT + Q_ENUMS( knobTypes ) + Q_PROPERTY(float innerRadius READ innerRadius WRITE setInnerRadius) Q_PROPERTY(float outerRadius READ outerRadius WRITE setOuterRadius) @@ -57,11 +59,18 @@ class EXPORT knob : public QWidget, public FloatModelView // Unfortunately, the gradient syntax doesn't create our gradient // correctly so we need to do this: Q_PROPERTY(QColor outerColor READ outerColor WRITE setOuterColor) + Q_PROPERTY(QColor lineColor READ lineColor WRITE setlineColor) + Q_PROPERTY(QColor arcColor READ arcColor WRITE setarcColor) mapPropertyFromModel(bool,isVolumeKnob,setVolumeKnob,m_volumeKnob); mapPropertyFromModel(float,volumeRatio,setVolumeRatio,m_volumeRatio); + Q_PROPERTY(knobTypes knobNum READ knobNum WRITE setknobNum) + + void init( const QString & _name ); //!< to be called by ctors + public: - knob( int _knob_num, QWidget * _parent = NULL, const QString & _name = QString() ); + knob( knobTypes _knob_num, QWidget * _parent = NULL, const QString & _name = QString() ); + knob( QWidget * _parent = NULL, const QString & _name = QString() ); //!< default ctor virtual ~knob(); // TODO: remove @@ -82,6 +91,9 @@ public: float outerRadius() const; void setOuterRadius( float _r ); + knobTypes knobNum() const; + void setknobNum( knobTypes _k ); + QPointF centerPoint() const; float centerPointX() const; void setCenterPointX( float _c ); @@ -93,6 +105,10 @@ public: QColor outerColor() const; void setOuterColor( const QColor & _c ); + QColor lineColor() const; + void setlineColor( const QColor & _c ); + QColor arcColor() const; + void setarcColor( const QColor & _c ); signals: @@ -146,7 +162,6 @@ private: static textFloat * s_textFloat; - int m_knobNum; QString m_label; QPixmap * m_knobPixmap; @@ -167,7 +182,11 @@ private: float m_innerRadius; float m_outerRadius; float m_lineWidth; - QColor * m_outerColor; + QColor m_outerColor; + QColor m_lineColor; //!< unused yet + QColor m_arcColor; //!< unused yet + + knobTypes m_knobNum; } ; 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..a647b3451 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 automatedOrControlled = 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 ); + + automatedOrControlled = 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,16 @@ void AutomatableModel::saveSettings( QDomDocument& doc, QDomElement& element, co m_controllerConnection->saveSettings( doc, element ); controllerElement.appendChild( element ); + + automatedOrControlled = true; + } + + if( automatedOrControlled && ( 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 +143,16 @@ 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 +171,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 +187,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 +246,63 @@ 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 logToLinearScale(T min, T max, T value) +{ + return exp( ( log(max) - log(min) ) * value + log(min) ); +} + + + + +template T AutomatableModel::logToLinearScale(T value) const +{ + return ::logToLinearScale( minValue(), maxValue(), value ); +} + + + + +//! @todo: this should be moved into a maths header +template +void roundAt( T& value, const T& where, const T& step_size ) +{ + if( qAbs( value - where ) + < typeInfo::minEps() * qAbs( step_size ) ) + { + value = where; + } +} + + + + +template +void AutomatableModel::roundAt( T& value, const T& where ) const +{ + ::roundAt(value, where, m_step); +} + + + + 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 + : logToLinearScale( + // fits value into [0,1]: + (value - minValue()) / maxValue() + ); + + m_value = fittedValue( scaled_value ); + if( oldValue != m_value ) { // notify linked models @@ -223,6 +313,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 ); } } @@ -280,17 +372,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; - } + roundAt( value, m_maxValue ); + roundAt( value, m_minValue ); + roundAt( value, 0.0f ); if( value < m_minValue ) { @@ -384,11 +468,26 @@ void AutomatableModel::setControllerConnection( ControllerConnection* c ) + float AutomatableModel::controllerValue( int frameOffset ) const { if( m_controllerConnection ) { - const float v = minValue() + ( range() * controllerConnection()->currentValue( frameOffset ) ); + float v = 0; + switch(m_scaleType) + { + case Linear: + v = minValue() + ( range() * controllerConnection()->currentValue( frameOffset ) ); + break; + case Logarithmic: + v = logToLinearScale( + controllerConnection()->currentValue( frameOffset )); + break; + default: + qFatal("AutomatableModel::controllerValue(int)" + "lacks implementation for a scale type"); + break; + } if( typeInfo::isEqual( m_step, 1 ) ) { return qRound( v ); diff --git a/src/core/LadspaControl.cpp b/src/core/LadspaControl.cpp index 5f469611c..8d31f55d6 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: diff --git a/src/gui/widgets/knob.cpp b/src/gui/widgets/knob.cpp index fc33abea3..8d8637eae 100644 --- a/src/gui/widgets/knob.cpp +++ b/src/gui/widgets/knob.cpp @@ -55,17 +55,37 @@ textFloat * knob::s_textFloat = NULL; -knob::knob( int _knob_num, QWidget * _parent, const QString & _name ) : - QWidget( _parent ), - FloatModelView( new FloatModel( 0, 0, 0, 1, NULL, _name, true ), this ), - m_knobNum( _knob_num ), - m_label( "" ), - m_knobPixmap( NULL ), - m_volumeKnob( false ), - m_volumeRatio( 100.0, 0.0, 1000000.0 ), - m_buttonPressed( false ), - m_angle( -10 ), - m_outerColor( NULL ) +//! @todo: in C++11, we can use delegating ctors +#define DEFAULT_KNOB_INITIALIZER_LIST \ + QWidget( _parent ), \ + FloatModelView( new FloatModel( 0, 0, 0, 1, NULL, _name, true ), this ), \ + m_label( "" ), \ + m_knobPixmap( NULL ), \ + m_volumeKnob( false ), \ + m_volumeRatio( 100.0, 0.0, 1000000.0 ), \ + m_buttonPressed( false ), \ + m_angle( -10 ) + +knob::knob( knobTypes _knob_num, QWidget * _parent, const QString & _name ) : + DEFAULT_KNOB_INITIALIZER_LIST, + m_knobNum( _knob_num ) +{ + init( _name ); +} + +knob::knob( QWidget * _parent, const QString & _name ) : + DEFAULT_KNOB_INITIALIZER_LIST, + m_knobNum( knobBright_26 ) +{ + init( _name ); +} + +#undef DEFAULT_KNOB_INITIALIZER_LIST + + + + +void knob::init( const QString & _name ) { if( s_textFloat == NULL ) { @@ -162,6 +182,23 @@ void knob::setOuterRadius( float _r ) + +knobTypes knob::knobNum() const +{ + return m_knobNum; +} + + + + +void knob::setknobNum( knobTypes _k ) +{ + m_knobNum = _k; +} + + + + QPointF knob::centerPoint() const { return m_centerPoint; @@ -213,28 +250,42 @@ void knob::setLineWidth( float _w ) QColor knob::outerColor() const { - if( m_outerColor ) - { - return *m_outerColor; - } - else - { - return QColor(); - } + return m_outerColor; } void knob::setOuterColor( const QColor & _c ) { - if( m_outerColor ) - { - *m_outerColor = _c; - } - else - { - m_outerColor = new QColor( _c ); - } + m_outerColor = _c; +} + + + +QColor knob::lineColor() const +{ + return m_lineColor; +} + + + +void knob::setlineColor( const QColor & _c ) +{ + m_lineColor = _c; +} + + + +QColor knob::arcColor() const +{ + return m_arcColor; +} + + + +void knob::setarcColor( const QColor & _c ) +{ + m_arcColor = _c; } @@ -290,11 +341,11 @@ void knob::drawKnob( QPainter * _p ) p.setRenderHint( QPainter::Antialiasing ); // Perhaps this can move to setOuterRadius() - if( m_outerColor ) + if( m_outerColor.isValid() ) { QRadialGradient gradient( centerPoint(), outerRadius() ); - gradient.setColorAt(0.4, _p->pen().brush().color() ); - gradient.setColorAt(1, *m_outerColor ); + gradient.setColorAt( 0.4, _p->pen().brush().color() ); + gradient.setColorAt( 1, m_outerColor ); p.setPen( QPen( gradient, lineWidth(), Qt::SolidLine, Qt::RoundCap ) ); @@ -380,6 +431,8 @@ void knob::drawKnob( QPainter * _p ) p.drawLine( calculateLine( mid, radius-2, 2 ) ); break; } + case knobStyled: + break; } p.drawArc( mid.x() - arcRectSize/2, 1, arcRectSize, arcRectSize, (90-centerAngle)*16, -16*(m_angle-centerAngle) ); diff --git a/src/gui/widgets/tempo_sync_knob.cpp b/src/gui/widgets/tempo_sync_knob.cpp index 4e178ee67..331fea908 100644 --- a/src/gui/widgets/tempo_sync_knob.cpp +++ b/src/gui/widgets/tempo_sync_knob.cpp @@ -36,7 +36,7 @@ -TempoSyncKnob::TempoSyncKnob( int _knob_num, QWidget * _parent, +TempoSyncKnob::TempoSyncKnob( knobTypes _knob_num, QWidget * _parent, const QString & _name ) : knob( _knob_num, _parent, _name ), m_tempoSyncIcon( embed::getIconPixmap( "tempo_sync" ) ),