From 3212bd8ffc173d646a286cec44e7187351734a1e Mon Sep 17 00:00:00 2001 From: szeli1 <143485814+szeli1@users.noreply.github.com> Date: Wed, 9 Apr 2025 22:18:59 +0200 Subject: [PATCH 01/30] ModelView_adding_error_message_for_model_nullptr --- include/ModelView.h | 15 +++++++++++++++ src/gui/ModelView.cpp | 13 +++++++++++++ 2 files changed, 28 insertions(+) diff --git a/include/ModelView.h b/include/ModelView.h index 786a6812c21..ed0e4ce2f78 100644 --- a/include/ModelView.h +++ b/include/ModelView.h @@ -26,6 +26,7 @@ #define LMMS_GUI_MODEL_VIEW_H #include + #include "Model.h" namespace lmms::gui @@ -53,12 +54,14 @@ class LMMS_EXPORT ModelView template T* castModel() { + assertModel(); return dynamic_cast( model() ); } template const T* castModel() const { + assertModel(); return dynamic_cast( model() ); } @@ -78,6 +81,18 @@ class LMMS_EXPORT ModelView private: + template + void assertModel() const + { + if (model()) { return; } + + QString type = typeid(T).name(); + execAssertFailedDialog(type); + Q_ASSERT(false); + } + void execAssertFailedDialog(const QString& type) const; + + QWidget* m_widget; QPointer m_model; diff --git a/src/gui/ModelView.cpp b/src/gui/ModelView.cpp index b2536fd9c8c..ce283c41004 100644 --- a/src/gui/ModelView.cpp +++ b/src/gui/ModelView.cpp @@ -22,6 +22,8 @@ * */ + +#include #include #include "ModelView.h" @@ -83,6 +85,17 @@ void ModelView::unsetModel() setModel(new Model(nullptr, QString(), true)); } +void ModelView::execAssertFailedDialog(const QString& type) const +{ + QMessageBox errorDialog; + errorDialog.setText("ModelView error, model() == nullptr, type: " + type); + errorDialog.setWindowTitle("ModelView error"); + errorDialog.setIcon(QMessageBox::Warning); + errorDialog.addButton(QMessageBox::Ok); + + errorDialog.exec(); +} + From 247e348c1f5088b2ff546ece00979cd0846d5707 Mon Sep 17 00:00:00 2001 From: szeli1 <143485814+szeli1@users.noreply.github.com> Date: Thu, 10 Apr 2025 20:11:40 +0200 Subject: [PATCH 02/30] ModelView_adding_assert_to_model_also --- include/ModelView.h | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/include/ModelView.h b/include/ModelView.h index ed0e4ce2f78..5280fd4689e 100644 --- a/include/ModelView.h +++ b/include/ModelView.h @@ -43,11 +43,13 @@ class LMMS_EXPORT ModelView Model* model() { + assertModel(); return m_model; } const Model* model() const { + assertModel(); return m_model; } @@ -55,14 +57,19 @@ class LMMS_EXPORT ModelView T* castModel() { assertModel(); - return dynamic_cast( model() ); + return dynamic_cast(m_model.data()); } template const T* castModel() const { assertModel(); - return dynamic_cast( model() ); + return dynamic_cast(m_model.data()); + } + + bool isModelValid() const + { + return m_model != nullptr; } @@ -84,7 +91,7 @@ class LMMS_EXPORT ModelView template void assertModel() const { - if (model()) { return; } + if (m_model.data()) { return; } QString type = typeid(T).name(); execAssertFailedDialog(type); From 4ba7cbb940e5121ff52c3043f1ce8e3ad73cd25b Mon Sep 17 00:00:00 2001 From: szeli1 <143485814+szeli1@users.noreply.github.com> Date: Fri, 9 May 2025 14:42:13 +0200 Subject: [PATCH 03/30] AutomatableModel_refactoring_linking_part_1 --- include/AutomatableModel.h | 33 ++-- src/core/AutomatableModel.cpp | 288 +++++++++++++++++++++++----------- 2 files changed, 218 insertions(+), 103 deletions(-) diff --git a/include/AutomatableModel.h b/include/AutomatableModel.h index 15285e17ab3..bc3de763b4a 100644 --- a/include/AutomatableModel.h +++ b/include/AutomatableModel.h @@ -212,8 +212,8 @@ class LMMS_EXPORT AutomatableModel : public Model, public JournallingObject void setInitValue( const float value ); - void setAutomatedValue( const float value ); - void setValue( const float value ); + void setAutomatedValue(float value); + void setValue(float value, bool isAutomated = false); void incValue( int steps ) { @@ -252,7 +252,6 @@ class LMMS_EXPORT AutomatableModel : public Model, public JournallingObject //! link @p m1 and @p m2, let @p m1 take the values of @p m2 static void linkModels( AutomatableModel* m1, AutomatableModel* m2 ); - static void unlinkModels( AutomatableModel* m1, AutomatableModel* m2 ); void unlinkAllModels(); @@ -279,7 +278,7 @@ class LMMS_EXPORT AutomatableModel : public Model, public JournallingObject bool hasLinkedModels() const { - return !m_linkedModels.empty(); + return m_nextLink != nullptr || m_firstLink != nullptr; } // a way to track changed values in the model and avoid using signals/slots - useful for speed-critical code. @@ -312,7 +311,7 @@ class LMMS_EXPORT AutomatableModel : public Model, public JournallingObject s_periodCounter = 0; } - bool useControllerValue() + bool useControllerValue() const { return m_useControllerValue; } @@ -340,6 +339,7 @@ public slots: private: + void setValueInternal(float value, bool isAutomated); // dynamicCast implementation template struct DCastVisitor : public ModelVisitor @@ -368,9 +368,16 @@ public slots: loadSettings( element, "value" ); } - void linkModel( AutomatableModel* model ); - void unlinkModel( AutomatableModel* model ); - + void linkModel(AutomatableModel* model); + void unlinkModel(); + AutomatableModel* getBaseLink(); + const AutomatableModel* getBaseLink() const; + //! links an other model link chain to this + //! can rearrange the current chain if `this` is passed in + void rebaseLinkToThis(AutomatableModel* model); + void rebaseLinkThis(); + + //! @brief Scales @value from linear to logarithmic. //! Value should be within [0,1] template T logToLinearScale( T value ) const; @@ -391,15 +398,15 @@ public slots: bool m_valueChanged; - // currently unused? - float m_oldValue; - int m_setValueDepth; - // used to determine if step size should be applied strictly (ie. always) // or only when value set from gui (default) bool m_hasStrictStepSize; - AutoModelVector m_linkedModels; + //! an `AutomatableModel` either has a linked parent or a array of linked models + //AutoModelVector m_linkedModels; + //! linked `AutomatableModels` will act as a linked list + AutomatableModel* m_nextLink; + AutomatableModel* m_firstLink; //! NULL if not appended to controller, otherwise connection info diff --git a/src/core/AutomatableModel.cpp b/src/core/AutomatableModel.cpp index e006be651a4..ac72a94df6c 100644 --- a/src/core/AutomatableModel.cpp +++ b/src/core/AutomatableModel.cpp @@ -51,8 +51,9 @@ AutomatableModel::AutomatableModel( m_range( max - min ), m_centerValue( m_minValue ), m_valueChanged( false ), - m_setValueDepth( 0 ), m_hasStrictStepSize( false ), + m_nextLink(nullptr), + m_firstLink(nullptr), m_controllerConnection( nullptr ), m_valueBuffer( static_cast( Engine::audioEngine()->framesPerPeriod() ) ), m_lastUpdatedPeriod( -1 ), @@ -69,11 +70,15 @@ AutomatableModel::AutomatableModel( AutomatableModel::~AutomatableModel() { - while( m_linkedModels.empty() == false ) + // unlink this from anything else + unlinkAllModels(); + /* TODO remove code + while (m_linkedModels.empty() == false) { m_linkedModels.back()->unlinkModel(this); m_linkedModels.erase( m_linkedModels.end() - 1 ); } + */ if( m_controllerConnection ) { @@ -292,41 +297,51 @@ void AutomatableModel::loadSettings( const QDomElement& element, const QString& -void AutomatableModel::setValue( const float value ) +void AutomatableModel::setValue(float value, bool isAutomated) { - m_oldValue = m_value; - ++m_setValueDepth; - const float old_val = m_value; + float newValue = fittedValue(value); + if (newValue == m_value) { emit dataUnchanged(); return; } - m_value = fittedValue( value ); - if( old_val != m_value ) + if (getBaseLink() != this) { - // add changes to history so user can undo it - addJournalCheckPoint(); + // if this is not the first model in the link chain + // move to the first model + getBaseLink()->setValue(value); + return; + } - // notify linked models - for (const auto& linkedModel : m_linkedModels) - { - if (linkedModel->m_setValueDepth < 1 && linkedModel->fittedValue(value) != linkedModel->m_value) - { - bool journalling = linkedModel->testAndSetJournalling(isJournalling()); - linkedModel->setValue(value); - linkedModel->setJournalling(journalling); - } - } + // if this is the first model in the link chain + if (isAutomated == false) { addJournalCheckPoint(); } + AutomatableModel* next = this; + while (true) + { + next->setValueInternal(value, isAutomated); + next = next->m_nextLink; + if (next == nullptr) { break; } + } +} + +void AutomatableModel::setValueInternal(float value, bool isAutomated) +{ + qDebug("setValue: to id: %d, %f, %f", id(), value, fittedValue(value)); + float oldValue = m_value; + if (isAutomated) { m_value = fittedValue(scaledValue(value)); } + else { m_value = fittedValue(value); } + + if (oldValue != m_value) + { m_valueChanged = true; emit dataChanged(); } - else + else if (isAutomated == false) { + // emit only if not automated and not changed emit dataUnchanged(); } - --m_setValueDepth; } - template T AutomatableModel::logToLinearScale( T value ) const { return castValue( lmms::logToLinearScale( minValue(), maxValue(), static_cast( value ) ) ); @@ -360,34 +375,6 @@ void AutomatableModel::roundAt( T& value, const T& where ) const -void AutomatableModel::setAutomatedValue( const float value ) -{ - setUseControllerValue(false); - - m_oldValue = m_value; - ++m_setValueDepth; - const float oldValue = m_value; - - const float scaled_value = scaledValue( value ); - - m_value = fittedValue( scaled_value ); - - if( oldValue != m_value ) - { - // notify linked models - for (const auto& linkedModel : m_linkedModels) - { - if (!(linkedModel->controllerConnection()) && linkedModel->m_setValueDepth < 1 && - linkedModel->fittedValue(m_value) != linkedModel->m_value) - { - linkedModel->setAutomatedValue(value); - } - } - m_valueChanged = true; - emit dataChanged(); - } - --m_setValueDepth; -} @@ -458,78 +445,196 @@ float AutomatableModel::fittedValue( float value ) const -void AutomatableModel::linkModel( AutomatableModel* model ) +void AutomatableModel::linkModel(AutomatableModel* model) { - auto containsModel = std::find(m_linkedModels.begin(), m_linkedModels.end(), model) != m_linkedModels.end(); - if (!containsModel && model != this) + qDebug("linkModel: in id: %d", id()); + assert(m_firstLink != this); + if (model == nullptr || model->m_firstLink == this) { return; } + qDebug("linkModel: to id: %d", model->id()); + + if (m_firstLink != nullptr) { - m_linkedModels.push_back( model ); - - if( !model->hasLinkedModels() ) - { - QObject::connect( this, SIGNAL(dataChanged()), - model, SIGNAL(dataChanged()), Qt::DirectConnection ); - } + // if this is not the first model in the link chain + // move to the first model + m_firstLink->linkModel(model); + return; } + + // if this is the first model in the link chain + rebaseLinkToThis(model); + + QObject::connect(this, SIGNAL(dataChanged()), + model, SIGNAL(dataChanged()), Qt::DirectConnection); } +void AutomatableModel::unlinkModel() +{ + qDebug("unlinkModel: in id: %d", id()); + if (hasLinkedModels() == false) { return; } + if (this == getBaseLink()) + { + qDebug("unlinkModel 0"); + // if this is a base, rebase on the next link, so this can be removed + if (m_nextLink != nullptr) { m_nextLink->rebaseLinkThis(); } + else { return; } + } + qDebug("unlinkModel 1"); + AutomatableModel* next = m_firstLink; + while (true) + { + if (next->m_nextLink == nullptr) { break; } + if (next->m_nextLink == this) + { + // step over this model + next->m_nextLink = m_nextLink; + // unlink + m_firstLink = nullptr; + m_nextLink = nullptr; + break; + } + next = next->m_nextLink; + } + qDebug("unlinkModel 2"); -void AutomatableModel::unlinkModel( AutomatableModel* model ) -{ - auto it = std::find(m_linkedModels.begin(), m_linkedModels.end(), model); - if( it != m_linkedModels.end() ) + qDebug("After unlink"); + next = next->getBaseLink(); + size_t counter = 0; + while (true) { - m_linkedModels.erase( it ); + qDebug("unlinkModel: after [%ld] id: %d", counter, next->id()); + if (next->m_nextLink == nullptr) { break; } + next = next->m_nextLink; + counter++; } + qDebug("unlinkModel 3"); } +AutomatableModel* AutomatableModel::getBaseLink() +{ + return m_firstLink == nullptr ? this : m_firstLink; +} +const AutomatableModel* AutomatableModel::getBaseLink() const +{ + return m_firstLink == nullptr ? this : m_firstLink; +} +void AutomatableModel::rebaseLinkToThis(AutomatableModel* model) +{ + if (model == nullptr) { return; } + qDebug("rebaseLinkToThis 1"); + AutomatableModel* otherBase = model->getBaseLink(); + AutomatableModel* thisBase = getBaseLink(); + if (otherBase == thisBase) { return; } + qDebug("rebaseLinkToThis 2"); + AutomatableModel* next = otherBase; + while (true) + { + // rebase other link chain + next->m_firstLink = thisBase; + if (next->m_nextLink == nullptr) { break; } + next = next->m_nextLink; + } + qDebug("rebaseLinkToThis 3"); + // attach the end points + next->m_nextLink = thisBase->m_nextLink; + thisBase->m_nextLink = otherBase; + // Before: this_base - this_next - this_next AND other_base - other_next - other_next + // After: this_base - other_base - other_next - other_next - this_next - this_next + qDebug("After rebase to this"); + next = thisBase; + size_t counter = 0; + while (true) + { + qDebug("rebaseLinkToThis: after [%ld] id: %d", counter, next->id()); + if (next->m_nextLink == nullptr) { break; } + next = next->m_nextLink; + counter++; + } + qDebug("rebaseLinkToThis 4"); +} -void AutomatableModel::linkModels( AutomatableModel* model1, AutomatableModel* model2 ) +void AutomatableModel::rebaseLinkThis() { - auto model1ContainsModel2 = std::find(model1->m_linkedModels.begin(), model1->m_linkedModels.end(), model2) != model1->m_linkedModels.end(); - if (!model1ContainsModel2 && model1 != model2) + AutomatableModel* thisBase = getBaseLink(); + if (this == thisBase) { return; } + qDebug("rebaseLinkThis 0"); + + AutomatableModel* next = thisBase; + while (true) { - // copy data - model1->m_value = model2->m_value; - if (model1->valueBuffer() && model2->valueBuffer()) + // rebase other models + next->m_firstLink = this; + if (next->m_nextLink == nullptr) { break; } + if (next->m_nextLink == this) { - std::copy_n(model2->valueBuffer()->data(), - model1->valueBuffer()->length(), - model1->valueBuffer()->data()); + // step over this model + next->m_nextLink = m_nextLink; + if (next->m_nextLink == nullptr) { break; } } - // send dataChanged() before linking (because linking will - // connect the two dataChanged() signals) - emit model1->dataChanged(); - // finally: link the models - model1->linkModel( model2 ); - model2->linkModel( model1 ); + next = next->m_nextLink; + } + next->m_nextLink = nullptr; // just in case + m_nextLink = thisBase; + m_firstLink = nullptr; + // Before: this_base - this_next - this_model - this_next + // After: this_model - this_base - this_next - this_next + + qDebug("rebaseLinkThis 1"); + next = this; + size_t counter = 0; + while (true) + { + qDebug("rebaseLinkThis: after [%ld] id: %d", counter, next->id()); + if (next->m_nextLink == nullptr) { break; } + next = next->m_nextLink; + counter++; } + qDebug("rebaseLinkThis 2"); } -void AutomatableModel::unlinkModels( AutomatableModel* model1, AutomatableModel* model2 ) + +void AutomatableModel::linkModels( AutomatableModel* model1, AutomatableModel* model2 ) { - model1->unlinkModel( model2 ); - model2->unlinkModel( model1 ); + // copy data + qDebug("linkModels copying..."); + model1->m_value = model2->m_value; + if (model1->valueBuffer() && model2->valueBuffer()) + { + std::copy_n(model2->valueBuffer()->data(), + model1->valueBuffer()->length(), + model1->valueBuffer()->data()); + } + // send dataChanged() before linking (because linking will + // connect the two dataChanged() signals) + emit model1->dataChanged(); + // finally: link the models + model1->linkModel(model2); } + + + void AutomatableModel::unlinkAllModels() { + qDebug("unlinkAllModels unlinking..."); + unlinkModel(); + /* for( AutomatableModel* model : m_linkedModels ) { unlinkModels( this, model ); } + */ } @@ -577,13 +682,14 @@ float AutomatableModel::controllerValue( int frameOffset ) const return v; } - AutomatableModel* lm = m_linkedModels.front(); + const AutomatableModel* lm = getBaseLink(); + if (lm == this) { return m_value; } if (lm->controllerConnection() && lm->useControllerValue()) { - return fittedValue( lm->controllerValue( frameOffset ) ); + return fittedValue(lm->controllerValue(frameOffset)); } - return fittedValue( lm->m_value ); + return fittedValue(lm->m_value); } @@ -637,7 +743,7 @@ ValueBuffer * AutomatableModel::valueBuffer() AutomatableModel* lm = nullptr; if (hasLinkedModels()) { - lm = m_linkedModels.front(); + lm = m_firstLink; // TODO } if (lm && lm->controllerConnection() && lm->useControllerValue() && lm->controllerConnection()->getController()->isSampleExact()) @@ -655,6 +761,7 @@ ValueBuffer * AutomatableModel::valueBuffer() } } + /* TODO if( m_oldValue != val ) { m_valueBuffer.interpolate( m_oldValue, val ); @@ -663,6 +770,7 @@ ValueBuffer * AutomatableModel::valueBuffer() m_hasSampleExactData = true; return &m_valueBuffer; } + */ // if we have no sample-exact source for a ValueBuffer, return NULL to signify that no data is available at the moment // in which case the recipient knows to use the static value() instead @@ -690,7 +798,7 @@ void AutomatableModel::setInitValue( const float value ) m_initValue = fittedValue( value ); bool journalling = testAndSetJournalling( false ); setValue( value ); - m_oldValue = m_value; + //m_oldValue = m_value; TODO setJournalling( journalling ); emit initValueChanged( value ); } @@ -816,4 +924,4 @@ QString BoolModel::displayValue( const float val ) const } -} // namespace lmms \ No newline at end of file +} // namespace lmms From 74500d8532943187ffe03d0e1e147fb2af7d0bf2 Mon Sep 17 00:00:00 2001 From: szeli1 <143485814+szeli1@users.noreply.github.com> Date: Fri, 9 May 2025 14:44:20 +0200 Subject: [PATCH 04/30] LapsdaControl_updating_functions --- src/core/LadspaControl.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/core/LadspaControl.cpp b/src/core/LadspaControl.cpp index 3282a0c7a0e..9691500f997 100644 --- a/src/core/LadspaControl.cpp +++ b/src/core/LadspaControl.cpp @@ -342,16 +342,18 @@ void LadspaControl::unlinkControls( LadspaControl * _control ) switch( m_port->data_type ) { case BufferDataType::Toggled: - BoolModel::unlinkModels( &m_toggledModel, _control->toggledModel() ); + m_toggledModel.unlinkAllModels(); + _control->toggledModel()->unlinkAllModels(); break; case BufferDataType::Integer: case BufferDataType::Enum: case BufferDataType::Floating: - FloatModel::unlinkModels( &m_knobModel, _control->knobModel() ); + m_knobModel.unlinkAllModels(); + _control->knobModel()->unlinkAllModels(); break; case BufferDataType::Time: - TempoSyncKnobModel::unlinkModels( &m_tempoSyncKnobModel, - _control->tempoSyncKnobModel() ); + m_tempoSyncKnobModel.unlinkAllModels(); + _control->tempoSyncKnobModel()->unlinkAllModels(); break; default: break; From dba2a5a6156b5526a6b1ce2860de2655a745b3bf Mon Sep 17 00:00:00 2001 From: szeli1 <143485814+szeli1@users.noreply.github.com> Date: Fri, 9 May 2025 14:44:37 +0200 Subject: [PATCH 05/30] Song_updating_automation_functions --- src/core/Song.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/core/Song.cpp b/src/core/Song.cpp index ea60e349bb6..36efec34135 100644 --- a/src/core/Song.cpp +++ b/src/core/Song.cpp @@ -419,7 +419,8 @@ void Song::processAutomations(const TrackList &tracklist, TimePos timeStart, fpp { if (! recordedModels.contains(it.key())) { - it.key()->setAutomatedValue(it.value()); + //it.key()->setAutomatedValue(it.value()); + it.key()->setValue(it.value(), true); } else if (!it.key()->useControllerValue()) { From 57c24b8348cb4afdc7945dcf2fbfa13d67e07bad Mon Sep 17 00:00:00 2001 From: szeli1 <143485814+szeli1@users.noreply.github.com> Date: Fri, 9 May 2025 17:17:19 +0200 Subject: [PATCH 06/30] AutomatableModel_refactoring_part_2 --- include/AutomatableModel.h | 24 +-- src/core/AutomatableModel.cpp | 286 ++++++++++++---------------------- 2 files changed, 114 insertions(+), 196 deletions(-) diff --git a/include/AutomatableModel.h b/include/AutomatableModel.h index bc3de763b4a..0faf7a42c4d 100644 --- a/include/AutomatableModel.h +++ b/include/AutomatableModel.h @@ -171,6 +171,7 @@ class LMMS_EXPORT AutomatableModel : public Model, public JournallingObject } float controllerValue( int frameOffset ) const; + float controllerValueInternal(int frameOffset) const; //! @brief Function that returns sample-exact data as a ValueBuffer //! @return pointer to model's valueBuffer when s.ex.data exists, NULL otherwise @@ -276,9 +277,9 @@ class LMMS_EXPORT AutomatableModel : public Model, public JournallingObject virtual QString displayValue( const float val ) const = 0; - bool hasLinkedModels() const + constexpr bool hasLinkedModels() const { - return m_nextLink != nullptr || m_firstLink != nullptr; + return m_nextLink != nullptr; } // a way to track changed values in the model and avoid using signals/slots - useful for speed-critical code. @@ -370,12 +371,12 @@ public slots: void linkModel(AutomatableModel* model); void unlinkModel(); - AutomatableModel* getBaseLink(); - const AutomatableModel* getBaseLink() const; - //! links an other model link chain to this - //! can rearrange the current chain if `this` is passed in - void rebaseLinkToThis(AutomatableModel* model); - void rebaseLinkThis(); + //! linking is stored in a linked list ring + //! @return the model that's `m_nextLink` is `this` + AutomatableModel* getLastLinkedModel() const; + //! @return true if the `model` is in the linked list + bool isModelLinked(AutomatableModel* model) const; + size_t countLinks() const; //! @brief Scales @value from linear to logarithmic. @@ -397,16 +398,15 @@ public slots: float m_centerValue; bool m_valueChanged; + float m_oldValue; //!< used for interpolation // used to determine if step size should be applied strictly (ie. always) // or only when value set from gui (default) bool m_hasStrictStepSize; - //! an `AutomatableModel` either has a linked parent or a array of linked models - //AutoModelVector m_linkedModels; - //! linked `AutomatableModels` will act as a linked list + //! an `AutomatableModel` can be linked together with others in a linked list + //! the list had no end, the last model is connected to the first forming a ring AutomatableModel* m_nextLink; - AutomatableModel* m_firstLink; //! NULL if not appended to controller, otherwise connection info diff --git a/src/core/AutomatableModel.cpp b/src/core/AutomatableModel.cpp index ac72a94df6c..ce400c52641 100644 --- a/src/core/AutomatableModel.cpp +++ b/src/core/AutomatableModel.cpp @@ -51,9 +51,9 @@ AutomatableModel::AutomatableModel( m_range( max - min ), m_centerValue( m_minValue ), m_valueChanged( false ), + m_oldValue(val), m_hasStrictStepSize( false ), m_nextLink(nullptr), - m_firstLink(nullptr), m_controllerConnection( nullptr ), m_valueBuffer( static_cast( Engine::audioEngine()->framesPerPeriod() ) ), m_lastUpdatedPeriod( -1 ), @@ -71,14 +71,7 @@ AutomatableModel::AutomatableModel( AutomatableModel::~AutomatableModel() { // unlink this from anything else - unlinkAllModels(); - /* TODO remove code - while (m_linkedModels.empty() == false) - { - m_linkedModels.back()->unlinkModel(this); - m_linkedModels.erase( m_linkedModels.end() - 1 ); - } - */ + unlinkModel(); if( m_controllerConnection ) { @@ -302,28 +295,23 @@ void AutomatableModel::setValue(float value, bool isAutomated) float newValue = fittedValue(value); if (newValue == m_value) { emit dataUnchanged(); return; } - if (getBaseLink() != this) - { - // if this is not the first model in the link chain - // move to the first model - getBaseLink()->setValue(value); - return; - } - - // if this is the first model in the link chain if (isAutomated == false) { addJournalCheckPoint(); } - AutomatableModel* next = this; - while (true) + // set value for this and the other linked widgets + if (hasLinkedModels()) { - next->setValueInternal(value, isAutomated); - next = next->m_nextLink; - if (next == nullptr) { break; } + AutomatableModel* next = this; + while (true) + { + next->setValueInternal(value, isAutomated); + next = next->m_nextLink; + if (next == this) { break; } + } } + else { setValueInternal(value, isAutomated); } } void AutomatableModel::setValueInternal(float value, bool isAutomated) { - qDebug("setValue: to id: %d, %f, %f", id(), value, fittedValue(value)); float oldValue = m_value; if (isAutomated) { m_value = fittedValue(scaledValue(value)); } else { m_value = fittedValue(value); } @@ -447,21 +435,19 @@ float AutomatableModel::fittedValue( float value ) const void AutomatableModel::linkModel(AutomatableModel* model) { - qDebug("linkModel: in id: %d", id()); - assert(m_firstLink != this); - if (model == nullptr || model->m_firstLink == this) { return; } - qDebug("linkModel: to id: %d", model->id()); + if (model == nullptr || model == this || isModelLinked(model)) { return; } - if (m_firstLink != nullptr) + AutomatableModel* otherEnd = model->getLastLinkedModel(); + if (otherEnd == nullptr) { - // if this is not the first model in the link chain - // move to the first model - m_firstLink->linkModel(model); - return; + model->m_nextLink = m_nextLink == nullptr ? this : m_nextLink; + m_nextLink = model; + } + else + { + otherEnd->m_nextLink = m_nextLink == nullptr ? this : m_nextLink; + m_nextLink = model; } - - // if this is the first model in the link chain - rebaseLinkToThis(model); QObject::connect(this, SIGNAL(dataChanged()), model, SIGNAL(dataChanged()), Qt::DirectConnection); @@ -469,132 +455,52 @@ void AutomatableModel::linkModel(AutomatableModel* model) void AutomatableModel::unlinkModel() { - qDebug("unlinkModel: in id: %d", id()); - if (hasLinkedModels() == false) { return; } - if (this == getBaseLink()) - { - qDebug("unlinkModel 0"); - // if this is a base, rebase on the next link, so this can be removed - if (m_nextLink != nullptr) { m_nextLink->rebaseLinkThis(); } - else { return; } - } - qDebug("unlinkModel 1"); - - AutomatableModel* next = m_firstLink; - while (true) - { - if (next->m_nextLink == nullptr) { break; } - if (next->m_nextLink == this) - { - // step over this model - next->m_nextLink = m_nextLink; - // unlink - m_firstLink = nullptr; - m_nextLink = nullptr; - break; - } - next = next->m_nextLink; - } - qDebug("unlinkModel 2"); - - qDebug("After unlink"); - next = next->getBaseLink(); - size_t counter = 0; - while (true) - { - qDebug("unlinkModel: after [%ld] id: %d", counter, next->id()); - if (next->m_nextLink == nullptr) { break; } - next = next->m_nextLink; - counter++; - } - qDebug("unlinkModel 3"); -} - -AutomatableModel* AutomatableModel::getBaseLink() -{ - return m_firstLink == nullptr ? this : m_firstLink; -} -const AutomatableModel* AutomatableModel::getBaseLink() const -{ - return m_firstLink == nullptr ? this : m_firstLink; + AutomatableModel* end = getLastLinkedModel(); + assert(end != nullptr); + end->m_nextLink = end == m_nextLink ? nullptr : m_nextLink; + m_nextLink = nullptr; } -void AutomatableModel::rebaseLinkToThis(AutomatableModel* model) +AutomatableModel* AutomatableModel::getLastLinkedModel() const { - if (model == nullptr) { return; } - - qDebug("rebaseLinkToThis 1"); - AutomatableModel* otherBase = model->getBaseLink(); - AutomatableModel* thisBase = getBaseLink(); - if (otherBase == thisBase) { return; } - qDebug("rebaseLinkToThis 2"); - - AutomatableModel* next = otherBase; + if (hasLinkedModels() == false) { return nullptr; } + AutomatableModel* output = m_nextLink; while (true) { - // rebase other link chain - next->m_firstLink = thisBase; - if (next->m_nextLink == nullptr) { break; } - next = next->m_nextLink; + if (output->m_nextLink == this) { break; } + output = output->m_nextLink; } - qDebug("rebaseLinkToThis 3"); - // attach the end points - next->m_nextLink = thisBase->m_nextLink; - thisBase->m_nextLink = otherBase; - // Before: this_base - this_next - this_next AND other_base - other_next - other_next - // After: this_base - other_base - other_next - other_next - this_next - this_next - qDebug("After rebase to this"); - next = thisBase; - size_t counter = 0; - while (true) - { - qDebug("rebaseLinkToThis: after [%ld] id: %d", counter, next->id()); - if (next->m_nextLink == nullptr) { break; } - next = next->m_nextLink; - counter++; - } - qDebug("rebaseLinkToThis 4"); + return output; } -void AutomatableModel::rebaseLinkThis() +bool AutomatableModel::isModelLinked(AutomatableModel* model) const { - AutomatableModel* thisBase = getBaseLink(); - if (this == thisBase) { return; } - qDebug("rebaseLinkThis 0"); - - AutomatableModel* next = thisBase; + if (hasLinkedModels() == false || model == nullptr || model == this) { return false; } + bool output = false; + AutomatableModel* next = m_nextLink; while (true) { - // rebase other models - next->m_firstLink = this; - if (next->m_nextLink == nullptr) { break; } - if (next->m_nextLink == this) - { - // step over this model - next->m_nextLink = m_nextLink; - if (next->m_nextLink == nullptr) { break; } - } + if (next->m_nextLink == this) { break; } + if (next->m_nextLink == model) { output = true; break; } next = next->m_nextLink; } - next->m_nextLink = nullptr; // just in case - m_nextLink = thisBase; - m_firstLink = nullptr; - // Before: this_base - this_next - this_model - this_next - // After: this_model - this_base - this_next - this_next + return output; +} - qDebug("rebaseLinkThis 1"); - next = this; - size_t counter = 0; +size_t AutomatableModel::countLinks() const +{ + if (hasLinkedModels() == false) { return 0; } + size_t output = 2; + AutomatableModel* next = m_nextLink; while (true) { - qDebug("rebaseLinkThis: after [%ld] id: %d", counter, next->id()); - if (next->m_nextLink == nullptr) { break; } + if (next->m_nextLink == this) { break; } next = next->m_nextLink; - counter++; + output++; } - qDebug("rebaseLinkThis 2"); + return output; } @@ -604,18 +510,15 @@ void AutomatableModel::rebaseLinkThis() void AutomatableModel::linkModels( AutomatableModel* model1, AutomatableModel* model2 ) { // copy data - qDebug("linkModels copying..."); - model1->m_value = model2->m_value; + model1->setValue(model2->m_value); if (model1->valueBuffer() && model2->valueBuffer()) { std::copy_n(model2->valueBuffer()->data(), model1->valueBuffer()->length(), model1->valueBuffer()->data()); + emit model1->dataChanged(); } - // send dataChanged() before linking (because linking will - // connect the two dataChanged() signals) - emit model1->dataChanged(); - // finally: link the models + // link the models model1->linkModel(model2); } @@ -625,9 +528,9 @@ void AutomatableModel::linkModels( AutomatableModel* model1, AutomatableModel* m + void AutomatableModel::unlinkAllModels() { - qDebug("unlinkAllModels unlinking..."); unlinkModel(); /* for( AutomatableModel* model : m_linkedModels ) @@ -658,11 +561,35 @@ void AutomatableModel::setControllerConnection( ControllerConnection* c ) float AutomatableModel::controllerValue( int frameOffset ) const { - if( m_controllerConnection ) + float output = m_value; + if (hasLinkedModels()) { - float v = 0; - switch(m_scaleType) + const AutomatableModel* next = this; + while (true) { + if (next->controllerConnection() != nullptr && next->useControllerValue()) + { + output = next->fittedValue(controllerValueInternal(frameOffset)); + break; + } + next = next->m_nextLink; + if (next == this) { break; } + } + } + else + { + output = fittedValue(controllerValueInternal(frameOffset)); + } + return output; +} + +float AutomatableModel::controllerValueInternal(int frameOffset) const +{ + if (m_controllerConnection == nullptr) { return m_value; } + + float v = 0; + switch(m_scaleType) + { case ScaleType::Linear: v = minValue() + ( range() * controllerConnection()->currentValue( frameOffset ) ); break; @@ -674,25 +601,14 @@ float AutomatableModel::controllerValue( int frameOffset ) const qFatal("AutomatableModel::controllerValue(int)" "lacks implementation for a scale type"); break; - } - if (approximatelyEqual(m_step, 1) && m_hasStrictStepSize) - { - return std::round(v); - } - return v; } - - const AutomatableModel* lm = getBaseLink(); - if (lm == this) { return m_value; } - if (lm->controllerConnection() && lm->useControllerValue()) + if (approximatelyEqual(m_step, 1) && m_hasStrictStepSize) { - return fittedValue(lm->controllerValue(frameOffset)); + return std::round(v); } - - return fittedValue(lm->m_value); + return v; } - ValueBuffer * AutomatableModel::valueBuffer() { QMutexLocker m( &m_valueBufferMutex ); @@ -740,29 +656,32 @@ ValueBuffer * AutomatableModel::valueBuffer() if (!m_controllerConnection) { - AutomatableModel* lm = nullptr; if (hasLinkedModels()) { - lm = m_firstLink; // TODO - } - if (lm && lm->controllerConnection() && lm->useControllerValue() && - lm->controllerConnection()->getController()->isSampleExact()) - { - auto vb = lm->valueBuffer(); - float * values = vb->values(); - float * nvalues = m_valueBuffer.values(); - for (int i = 0; i < vb->length(); i++) + AutomatableModel* next = this; + while (true) { - nvalues[i] = fittedValue(values[i]); + if (next->controllerConnection() && next->useControllerValue() && + next->controllerConnection()->getController()->isSampleExact()) + { + auto vb = next->valueBuffer(); + float * values = vb->values(); + float * nvalues = m_valueBuffer.values(); + for (int i = 0; i < vb->length(); i++) + { + nvalues[i] = fittedValue(values[i]); + } + m_lastUpdatedPeriod = s_periodCounter; + m_hasSampleExactData = true; + return &m_valueBuffer; + } + next = next->m_nextLink; + if (next == this) { break; } } - m_lastUpdatedPeriod = s_periodCounter; - m_hasSampleExactData = true; - return &m_valueBuffer; } } - /* TODO - if( m_oldValue != val ) + if (m_oldValue != val) { m_valueBuffer.interpolate( m_oldValue, val ); m_oldValue = val; @@ -770,7 +689,6 @@ ValueBuffer * AutomatableModel::valueBuffer() m_hasSampleExactData = true; return &m_valueBuffer; } - */ // if we have no sample-exact source for a ValueBuffer, return NULL to signify that no data is available at the moment // in which case the recipient knows to use the static value() instead @@ -798,7 +716,7 @@ void AutomatableModel::setInitValue( const float value ) m_initValue = fittedValue( value ); bool journalling = testAndSetJournalling( false ); setValue( value ); - //m_oldValue = m_value; TODO + m_oldValue = m_value; setJournalling( journalling ); emit initValueChanged( value ); } From eff1d2ad89af52876522609f453cf5117f65fe62 Mon Sep 17 00:00:00 2001 From: szeli1 <143485814+szeli1@users.noreply.github.com> Date: Fri, 9 May 2025 17:19:45 +0200 Subject: [PATCH 07/30] AutomatableModel_moving_funcion_to_private --- include/AutomatableModel.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/AutomatableModel.h b/include/AutomatableModel.h index 0faf7a42c4d..0c090cdd277 100644 --- a/include/AutomatableModel.h +++ b/include/AutomatableModel.h @@ -171,7 +171,6 @@ class LMMS_EXPORT AutomatableModel : public Model, public JournallingObject } float controllerValue( int frameOffset ) const; - float controllerValueInternal(int frameOffset) const; //! @brief Function that returns sample-exact data as a ValueBuffer //! @return pointer to model's valueBuffer when s.ex.data exists, NULL otherwise @@ -341,6 +340,7 @@ public slots: private: void setValueInternal(float value, bool isAutomated); + float controllerValueInternal(int frameOffset) const; // dynamicCast implementation template struct DCastVisitor : public ModelVisitor From ffaddb11f91b1c58a91b0c79118875a4a8ec3974 Mon Sep 17 00:00:00 2001 From: szeli1 <143485814+szeli1@users.noreply.github.com> Date: Fri, 9 May 2025 17:21:47 +0200 Subject: [PATCH 08/30] ModelView_reveting_changes --- include/ModelView.h | 26 ++------------------------ src/gui/ModelView.cpp | 13 ------------- 2 files changed, 2 insertions(+), 37 deletions(-) diff --git a/include/ModelView.h b/include/ModelView.h index 5280fd4689e..786a6812c21 100644 --- a/include/ModelView.h +++ b/include/ModelView.h @@ -26,7 +26,6 @@ #define LMMS_GUI_MODEL_VIEW_H #include - #include "Model.h" namespace lmms::gui @@ -43,33 +42,24 @@ class LMMS_EXPORT ModelView Model* model() { - assertModel(); return m_model; } const Model* model() const { - assertModel(); return m_model; } template T* castModel() { - assertModel(); - return dynamic_cast(m_model.data()); + return dynamic_cast( model() ); } template const T* castModel() const { - assertModel(); - return dynamic_cast(m_model.data()); - } - - bool isModelValid() const - { - return m_model != nullptr; + return dynamic_cast( model() ); } @@ -88,18 +78,6 @@ class LMMS_EXPORT ModelView private: - template - void assertModel() const - { - if (m_model.data()) { return; } - - QString type = typeid(T).name(); - execAssertFailedDialog(type); - Q_ASSERT(false); - } - void execAssertFailedDialog(const QString& type) const; - - QWidget* m_widget; QPointer m_model; diff --git a/src/gui/ModelView.cpp b/src/gui/ModelView.cpp index ce283c41004..b2536fd9c8c 100644 --- a/src/gui/ModelView.cpp +++ b/src/gui/ModelView.cpp @@ -22,8 +22,6 @@ * */ - -#include #include #include "ModelView.h" @@ -85,17 +83,6 @@ void ModelView::unsetModel() setModel(new Model(nullptr, QString(), true)); } -void ModelView::execAssertFailedDialog(const QString& type) const -{ - QMessageBox errorDialog; - errorDialog.setText("ModelView error, model() == nullptr, type: " + type); - errorDialog.setWindowTitle("ModelView error"); - errorDialog.setIcon(QMessageBox::Warning); - errorDialog.addButton(QMessageBox::Ok); - - errorDialog.exec(); -} - From f4678e9f110c6a39bc667b54212453bfcd41eade Mon Sep 17 00:00:00 2001 From: szeli1 <143485814+szeli1@users.noreply.github.com> Date: Fri, 9 May 2025 17:49:29 +0200 Subject: [PATCH 09/30] AutomatableModel_removing_unused_code --- include/AutomatableModel.h | 4 +--- src/core/AutomatableModel.cpp | 6 ------ 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/include/AutomatableModel.h b/include/AutomatableModel.h index 0c090cdd277..372b34cbf83 100644 --- a/include/AutomatableModel.h +++ b/include/AutomatableModel.h @@ -78,8 +78,6 @@ class LMMS_EXPORT AutomatableModel : public Model, public JournallingObject { Q_OBJECT public: - using AutoModelVector = std::vector; - enum class ScaleType { Linear, @@ -405,7 +403,7 @@ public slots: bool m_hasStrictStepSize; //! an `AutomatableModel` can be linked together with others in a linked list - //! the list had no end, the last model is connected to the first forming a ring + //! the list has no end, the last model is connected to the first forming a ring AutomatableModel* m_nextLink; diff --git a/src/core/AutomatableModel.cpp b/src/core/AutomatableModel.cpp index ce400c52641..90683d493b2 100644 --- a/src/core/AutomatableModel.cpp +++ b/src/core/AutomatableModel.cpp @@ -532,12 +532,6 @@ void AutomatableModel::linkModels( AutomatableModel* model1, AutomatableModel* m void AutomatableModel::unlinkAllModels() { unlinkModel(); - /* - for( AutomatableModel* model : m_linkedModels ) - { - unlinkModels( this, model ); - } - */ } From 368d8bfbbdc541444cce9fe311dda2524fe65a68 Mon Sep 17 00:00:00 2001 From: szeli1 <143485814+szeli1@users.noreply.github.com> Date: Fri, 9 May 2025 17:49:38 +0200 Subject: [PATCH 10/30] Song_removing_unused_code --- src/core/Song.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/core/Song.cpp b/src/core/Song.cpp index 36efec34135..8d0ff285cc0 100644 --- a/src/core/Song.cpp +++ b/src/core/Song.cpp @@ -419,7 +419,6 @@ void Song::processAutomations(const TrackList &tracklist, TimePos timeStart, fpp { if (! recordedModels.contains(it.key())) { - //it.key()->setAutomatedValue(it.value()); it.key()->setValue(it.value(), true); } else if (!it.key()->useControllerValue()) From 23c99df2ae9ed1dd0f11ff8c5732f45673dd5fc4 Mon Sep 17 00:00:00 2001 From: szeli1 <143485814+szeli1@users.noreply.github.com> Date: Fri, 9 May 2025 18:02:16 +0200 Subject: [PATCH 11/30] Vestige_replace_setAutomatedValue --- plugins/Vestige/Vestige.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/Vestige/Vestige.cpp b/plugins/Vestige/Vestige.cpp index e372924005b..e3255d5b2ff 100644 --- a/plugins/Vestige/Vestige.cpp +++ b/plugins/Vestige/Vestige.cpp @@ -1070,7 +1070,7 @@ void ManageVestigeInstrumentView::syncPlugin( void ) std::snprintf(paramStr.data(), paramStr.size(), "param%d", i); s_dumpValues = dump[paramStr.data()].split(":"); float f_value = LocaleHelper::toFloat(s_dumpValues.at(2)); - m_vi->knobFModel[ i ]->setAutomatedValue( f_value ); + m_vi->knobFModel[ i ]->setValue(f_value, true); m_vi->knobFModel[ i ]->setInitValue( f_value ); } } From 6bb898257aa62518075df29e5ecc47b121074676 Mon Sep 17 00:00:00 2001 From: szeli1 <143485814+szeli1@users.noreply.github.com> Date: Fri, 9 May 2025 18:02:33 +0200 Subject: [PATCH 12/30] VstEffectControls_replace_setAutomatedValue --- plugins/VstEffect/VstEffectControls.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/VstEffect/VstEffectControls.cpp b/plugins/VstEffect/VstEffectControls.cpp index ef8bd38d077..de35a9d6754 100644 --- a/plugins/VstEffect/VstEffectControls.cpp +++ b/plugins/VstEffect/VstEffectControls.cpp @@ -463,7 +463,7 @@ void ManageVSTEffectView::syncPlugin() std::snprintf(paramStr.data(), paramStr.size(), "param%d", i); s_dumpValues = dump[paramStr.data()].split(":"); float f_value = LocaleHelper::toFloat(s_dumpValues.at(2)); - m_vi2->knobFModel[ i ]->setAutomatedValue( f_value ); + m_vi2->knobFModel[ i ]->setValue(f_value, true); m_vi2->knobFModel[ i ]->setInitValue( f_value ); } } From c5f9a0400df5a855cc0834d1ce497209f04adcca Mon Sep 17 00:00:00 2001 From: szeli1 <143485814+szeli1@users.noreply.github.com> Date: Fri, 9 May 2025 21:54:24 +0200 Subject: [PATCH 13/30] AutomatableModel_fixing_issues --- include/AutomatableModel.h | 24 ++++++------------------ src/core/AutomatableModel.cpp | 34 +--------------------------------- 2 files changed, 7 insertions(+), 51 deletions(-) diff --git a/include/AutomatableModel.h b/include/AutomatableModel.h index 372b34cbf83..b8423c226d9 100644 --- a/include/AutomatableModel.h +++ b/include/AutomatableModel.h @@ -149,22 +149,12 @@ class LMMS_EXPORT AutomatableModel : public Model, public JournallingObject template inline T value( int frameOffset = 0 ) const { - if (m_controllerConnection) + if (m_controllerConnection && m_useControllerValue) { - if (!m_useControllerValue) - { - return castValue(m_value); - } - else - { - return castValue(controllerValue(frameOffset)); - } + // workaround to update linked models + AutomatableModel* thisModel = const_cast(this); + thisModel->setValue(controllerValue(frameOffset)); } - else if (hasLinkedModels()) - { - return castValue( controllerValue( frameOffset ) ); - } - return castValue( m_value ); } @@ -250,8 +240,9 @@ class LMMS_EXPORT AutomatableModel : public Model, public JournallingObject //! link @p m1 and @p m2, let @p m1 take the values of @p m2 static void linkModels( AutomatableModel* m1, AutomatableModel* m2 ); - void unlinkAllModels(); + //! @return 0 if not connected, never 1, 2 if connected to 1 model + size_t countLinks() const; /** * @brief Saves settings (value, automation links and controller connections) of AutomatableModel into @@ -338,7 +329,6 @@ public slots: private: void setValueInternal(float value, bool isAutomated); - float controllerValueInternal(int frameOffset) const; // dynamicCast implementation template struct DCastVisitor : public ModelVisitor @@ -374,8 +364,6 @@ public slots: AutomatableModel* getLastLinkedModel() const; //! @return true if the `model` is in the linked list bool isModelLinked(AutomatableModel* model) const; - size_t countLinks() const; - //! @brief Scales @value from linear to logarithmic. //! Value should be within [0,1] diff --git a/src/core/AutomatableModel.cpp b/src/core/AutomatableModel.cpp index 90683d493b2..caa0a8ee954 100644 --- a/src/core/AutomatableModel.cpp +++ b/src/core/AutomatableModel.cpp @@ -321,11 +321,6 @@ void AutomatableModel::setValueInternal(float value, bool isAutomated) m_valueChanged = true; emit dataChanged(); } - else if (isAutomated == false) - { - // emit only if not automated and not changed - emit dataUnchanged(); - } } @@ -436,7 +431,7 @@ float AutomatableModel::fittedValue( float value ) const void AutomatableModel::linkModel(AutomatableModel* model) { if (model == nullptr || model == this || isModelLinked(model)) { return; } - + AutomatableModel* otherEnd = model->getLastLinkedModel(); if (otherEnd == nullptr) { @@ -448,9 +443,6 @@ void AutomatableModel::linkModel(AutomatableModel* model) otherEnd->m_nextLink = m_nextLink == nullptr ? this : m_nextLink; m_nextLink = model; } - - QObject::connect(this, SIGNAL(dataChanged()), - model, SIGNAL(dataChanged()), Qt::DirectConnection); } void AutomatableModel::unlinkModel() @@ -554,30 +546,6 @@ void AutomatableModel::setControllerConnection( ControllerConnection* c ) float AutomatableModel::controllerValue( int frameOffset ) const -{ - float output = m_value; - if (hasLinkedModels()) - { - const AutomatableModel* next = this; - while (true) - { - if (next->controllerConnection() != nullptr && next->useControllerValue()) - { - output = next->fittedValue(controllerValueInternal(frameOffset)); - break; - } - next = next->m_nextLink; - if (next == this) { break; } - } - } - else - { - output = fittedValue(controllerValueInternal(frameOffset)); - } - return output; -} - -float AutomatableModel::controllerValueInternal(int frameOffset) const { if (m_controllerConnection == nullptr) { return m_value; } From e008275d8a3b907f857d39f9b2df40be7ee41ce0 Mon Sep 17 00:00:00 2001 From: szeli1 <143485814+szeli1@users.noreply.github.com> Date: Fri, 9 May 2025 21:54:59 +0200 Subject: [PATCH 14/30] AutomatableModelTest_updating_test --- tests/src/core/AutomatableModelTest.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/tests/src/core/AutomatableModelTest.cpp b/tests/src/core/AutomatableModelTest.cpp index 6e8a28116d8..4fa81369a42 100644 --- a/tests/src/core/AutomatableModelTest.cpp +++ b/tests/src/core/AutomatableModelTest.cpp @@ -78,7 +78,7 @@ private slots: // tests { using namespace lmms; - BoolModel m1(false), m2(false); + BoolModel m1(true), m2(false); QObject::connect(&m1, SIGNAL(dataChanged()), this, SLOT(onM1Changed())); @@ -89,11 +89,13 @@ private slots: // tests AutomatableModel::linkModels(&m1, &m1); QVERIFY(!m1Changed); // cannot link to itself QVERIFY(!m2Changed); + QVERIFY(m1.countLinks() == 0); resetChanged(); AutomatableModel::linkModels(&m1, &m2); - QVERIFY(m1Changed); // since m1 takes the value of m2 + QVERIFY(m1.value() == m2.value()); // since m1 takes the value of m2 QVERIFY(!m2Changed); // the second model is the source + QVERIFY(m1.countLinks() == 2); resetChanged(); AutomatableModel::linkModels(&m1, &m2); @@ -102,17 +104,17 @@ private slots: // tests resetChanged(); BoolModel m3(false); - m1.setValue(1.f); - m2.setValue(1.f); + m1.setValue(1.f); // true + m2.setValue(1.f); // true AutomatableModel::linkModels(&m1, &m2); QVERIFY(m1.value()); QVERIFY(m2.value()); QVERIFY(!m3.value()); AutomatableModel::linkModels(&m2, &m3); // drag m3, drop on m2 // m2 should take m3's (0) value - // due to a bug(?), this does not happen - QVERIFY(m2.value()); + QVERIFY(m2.value() == m3.value()); QVERIFY(!m3.value()); + QVERIFY(m1.countLinks() == 3); } }; From db3b11a3e1dde3ae51bf86902ea74386bd4f8457 Mon Sep 17 00:00:00 2001 From: szeli1 <143485814+szeli1@users.noreply.github.com> Date: Fri, 23 May 2025 17:46:08 +0200 Subject: [PATCH 15/30] AutomatatbleModel adding comments and assert --- src/core/AutomatableModel.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/core/AutomatableModel.cpp b/src/core/AutomatableModel.cpp index caa0a8ee954..0ca66d19a3c 100644 --- a/src/core/AutomatableModel.cpp +++ b/src/core/AutomatableModel.cpp @@ -433,16 +433,20 @@ void AutomatableModel::linkModel(AutomatableModel* model) if (model == nullptr || model == this || isModelLinked(model)) { return; } AutomatableModel* otherEnd = model->getLastLinkedModel(); + AutomatableModel* next = m_nextLink == nullptr ? this : m_nextLink; if (otherEnd == nullptr) { - model->m_nextLink = m_nextLink == nullptr ? this : m_nextLink; - m_nextLink = model; + // linking other start to our next + model->m_nextLink = next; } else { - otherEnd->m_nextLink = m_nextLink == nullptr ? this : m_nextLink; - m_nextLink = model; + // linking other end to our next + otherEnd->m_nextLink = next; } + // linking this to other start + m_nextLink = model; + assert(m_nextLink != this); // if you change the code, be careful to not link to this } void AutomatableModel::unlinkModel() From 675993a9e064479878f09e7348812e7aa404dba0 Mon Sep 17 00:00:00 2001 From: szeli1 <143485814+szeli1@users.noreply.github.com> Date: Sat, 24 May 2025 19:14:57 +0200 Subject: [PATCH 16/30] edit_unlink adding new svg --- data/themes/classic/edit_unlink.svg | 121 ++++++++++++++++++++++++++++ data/themes/default/edit_unlink.svg | 121 ++++++++++++++++++++++++++++ 2 files changed, 242 insertions(+) create mode 100644 data/themes/classic/edit_unlink.svg create mode 100644 data/themes/default/edit_unlink.svg diff --git a/data/themes/classic/edit_unlink.svg b/data/themes/classic/edit_unlink.svg new file mode 100644 index 00000000000..444ddf649f6 --- /dev/null +++ b/data/themes/classic/edit_unlink.svg @@ -0,0 +1,121 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/themes/default/edit_unlink.svg b/data/themes/default/edit_unlink.svg new file mode 100644 index 00000000000..444ddf649f6 --- /dev/null +++ b/data/themes/default/edit_unlink.svg @@ -0,0 +1,121 @@ + + + + + + + + + + + + + + + + + + + + + + + + + From f1f0d0418d89c59c5e8fd4fbd08c6d34744e43c9 Mon Sep 17 00:00:00 2001 From: szeli1 <143485814+szeli1@users.noreply.github.com> Date: Sat, 24 May 2025 19:15:24 +0200 Subject: [PATCH 17/30] AutomatableModelView fixing unlink picture --- src/gui/AutomatableModelView.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/AutomatableModelView.cpp b/src/gui/AutomatableModelView.cpp index 2faf74064a5..1b03ea071a9 100644 --- a/src/gui/AutomatableModelView.cpp +++ b/src/gui/AutomatableModelView.cpp @@ -98,7 +98,7 @@ void AutomatableModelView::addDefaultActions( QMenu* menu ) if( model->hasLinkedModels() ) { - menu->addAction( embed::getIconPixmap( "edit-delete" ), + menu->addAction( embed::getIconPixmap( "edit_unlink" ), AutomatableModel::tr( "Remove all linked controls" ), amvSlots, SLOT(unlinkAllModels())); menu->addSeparator(); From 8ab1bd134af06798678f313c5a26ab8f1bbd391c Mon Sep 17 00:00:00 2001 From: szeli1 <143485814+szeli1@users.noreply.github.com> Date: Wed, 4 Jun 2025 16:32:09 +0200 Subject: [PATCH 18/30] AutomatableModel removing unused function --- include/AutomatableModel.h | 1 - 1 file changed, 1 deletion(-) diff --git a/include/AutomatableModel.h b/include/AutomatableModel.h index b8423c226d9..1f824c047e1 100644 --- a/include/AutomatableModel.h +++ b/include/AutomatableModel.h @@ -200,7 +200,6 @@ class LMMS_EXPORT AutomatableModel : public Model, public JournallingObject void setInitValue( const float value ); - void setAutomatedValue(float value); void setValue(float value, bool isAutomated = false); void incValue( int steps ) From 37400d48168674b3fa1bcc9d5a70c5438c3477e9 Mon Sep 17 00:00:00 2001 From: szeli1 <143485814+szeli1@users.noreply.github.com> Date: Mon, 9 Jun 2025 18:36:32 +0200 Subject: [PATCH 19/30] edit_unlink replacing with better image --- data/themes/classic/edit_unlink.svg | 137 ++++++++++------------------ data/themes/default/edit_unlink.svg | 137 ++++++++++------------------ 2 files changed, 92 insertions(+), 182 deletions(-) diff --git a/data/themes/classic/edit_unlink.svg b/data/themes/classic/edit_unlink.svg index 444ddf649f6..15526dea44a 100644 --- a/data/themes/classic/edit_unlink.svg +++ b/data/themes/classic/edit_unlink.svg @@ -6,116 +6,71 @@ height="20" viewBox="0 0 5.2916665 5.2916666" version="1.1" - id="svg5" - inkscape:version="1.2.2 (b0a8486541, 2022-12-01)" - sodipodi:docname="edit_unlink.svg" + id="svg1" + inkscape:export-filename="chain400.png" + inkscape:export-xdpi="1920" + inkscape:export-ydpi="1920" + inkscape:version="1.4.1 (93de688d07, 2025-03-30)" + sodipodi:docname="chain peki 2.svg" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" - xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg"> + inkscape:current-layer="layer1"> + + - - - - - - - - - - - + id="defs1" /> + style="opacity:1;baseline-shift:baseline;display:inline;overflow:visible;vector-effect:none;fill:#ffffff;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.3;enable-background:accumulate;stop-color:#000000;stop-opacity:1" + d="m 2.9882813,2.9882813 a 0.2645835,0.2645835 0 0 0 0,0.3730468 l 1.5878906,1.5878906 a 0.2645835,0.2645835 0 0 0 0.3730468,0 0.2645835,0.2645835 0 0 0 0,-0.3730468 L 3.3613281,2.9882813 a 0.2645835,0.2645835 0 0 0 -0.3730468,0 z" + id="path13" + inkscape:label="down" /> - + style="opacity:1;baseline-shift:baseline;display:inline;overflow:visible;vector-effect:none;fill:#ffffff;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.3;enable-background:accumulate;stop-color:#000000;stop-opacity:1" + d="m 0.34179688,0.34179688 a 0.2645835,0.2645835 0 0 0 0,0.37499999 L 1.9296875,2.3046875 a 0.2645835,0.2645835 0 0 0 0.375,0 0.2645835,0.2645835 0 0 0 0,-0.375 L 0.71679687,0.34179688 a 0.2645835,0.2645835 0 0 0 -0.37499999,0 z" + id="path15" + inkscape:label="up" /> - + id="path11" + style="baseline-shift:baseline;display:inline;overflow:visible;fill:#ffffff;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.3;enable-background:accumulate;stop-color:#000000;stop-opacity:1;opacity:1;stroke:#ffffff;stroke-opacity:1;stroke-width:0.13229167;stroke-dasharray:none" + inkscape:label="cen" + d="M 2.1171834 0.79271647 A 0.26460981 0.26460981 0 0 0 1.929598 0.87126465 L 1.7285767 1.072286 L 2.102714 1.4464233 L 2.2267375 1.3223999 L 2.5373128 1.3223999 L 3.0189372 1.8050578 L 3.3925578 1.4314372 L 2.8318685 0.87126465 A 0.26460981 0.26460981 0 0 0 2.6463501 0.79271647 L 2.1171834 0.79271647 z M 1.4474569 1.3534058 L 1.3534058 1.4474569 L 1.7280599 1.822111 L 1.822111 1.7280599 L 1.4474569 1.3534058 z M 1.072286 1.7285767 L 0.87126465 1.929598 A 0.26460981 0.26460981 0 0 0 0.79271647 2.1171834 L 0.79271647 2.6463501 A 0.26460981 0.26460981 0 0 0 0.87126465 2.8318685 L 1.4314372 3.3925578 L 1.8050578 3.0189372 L 1.3223999 2.5373128 L 1.3223999 2.2282878 L 1.4469401 2.1032308 L 1.072286 1.7285767 z M 3.8602295 1.8996257 L 3.4866089 2.2732463 L 3.96875 2.7559041 L 3.96875 3.0644124 L 3.843693 3.1894694 L 4.2178304 3.5636068 L 4.4198853 3.3615519 A 0.26460981 0.26460981 0 0 0 4.4979167 3.1760335 L 4.4979167 2.6463501 A 0.26460981 0.26460981 0 0 0 4.4198853 2.4587646 L 3.8602295 1.8996257 z M 3.5625732 3.4705892 L 3.4685221 3.5646403 L 3.8426595 3.9387777 L 3.9367106 3.8447266 L 3.5625732 3.4705892 z M 2.2732463 3.4866089 L 1.8996257 3.8602295 L 2.4587646 4.4198853 A 0.26460981 0.26460981 0 0 0 2.6463501 4.4979167 L 3.1760335 4.4979167 A 0.26460981 0.26460981 0 0 0 3.3615519 4.4198853 L 3.5615397 4.2198975 L 3.1874023 3.8457601 L 3.0644124 3.96875 L 2.7559041 3.96875 L 2.2732463 3.4866089 z " /> diff --git a/data/themes/default/edit_unlink.svg b/data/themes/default/edit_unlink.svg index 444ddf649f6..15526dea44a 100644 --- a/data/themes/default/edit_unlink.svg +++ b/data/themes/default/edit_unlink.svg @@ -6,116 +6,71 @@ height="20" viewBox="0 0 5.2916665 5.2916666" version="1.1" - id="svg5" - inkscape:version="1.2.2 (b0a8486541, 2022-12-01)" - sodipodi:docname="edit_unlink.svg" + id="svg1" + inkscape:export-filename="chain400.png" + inkscape:export-xdpi="1920" + inkscape:export-ydpi="1920" + inkscape:version="1.4.1 (93de688d07, 2025-03-30)" + sodipodi:docname="chain peki 2.svg" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" - xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg"> + inkscape:current-layer="layer1"> + + - - - - - - - - - - - + id="defs1" /> + style="opacity:1;baseline-shift:baseline;display:inline;overflow:visible;vector-effect:none;fill:#ffffff;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.3;enable-background:accumulate;stop-color:#000000;stop-opacity:1" + d="m 2.9882813,2.9882813 a 0.2645835,0.2645835 0 0 0 0,0.3730468 l 1.5878906,1.5878906 a 0.2645835,0.2645835 0 0 0 0.3730468,0 0.2645835,0.2645835 0 0 0 0,-0.3730468 L 3.3613281,2.9882813 a 0.2645835,0.2645835 0 0 0 -0.3730468,0 z" + id="path13" + inkscape:label="down" /> - + style="opacity:1;baseline-shift:baseline;display:inline;overflow:visible;vector-effect:none;fill:#ffffff;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.3;enable-background:accumulate;stop-color:#000000;stop-opacity:1" + d="m 0.34179688,0.34179688 a 0.2645835,0.2645835 0 0 0 0,0.37499999 L 1.9296875,2.3046875 a 0.2645835,0.2645835 0 0 0 0.375,0 0.2645835,0.2645835 0 0 0 0,-0.375 L 0.71679687,0.34179688 a 0.2645835,0.2645835 0 0 0 -0.37499999,0 z" + id="path15" + inkscape:label="up" /> - + id="path11" + style="baseline-shift:baseline;display:inline;overflow:visible;fill:#ffffff;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4.3;enable-background:accumulate;stop-color:#000000;stop-opacity:1;opacity:1;stroke:#ffffff;stroke-opacity:1;stroke-width:0.13229167;stroke-dasharray:none" + inkscape:label="cen" + d="M 2.1171834 0.79271647 A 0.26460981 0.26460981 0 0 0 1.929598 0.87126465 L 1.7285767 1.072286 L 2.102714 1.4464233 L 2.2267375 1.3223999 L 2.5373128 1.3223999 L 3.0189372 1.8050578 L 3.3925578 1.4314372 L 2.8318685 0.87126465 A 0.26460981 0.26460981 0 0 0 2.6463501 0.79271647 L 2.1171834 0.79271647 z M 1.4474569 1.3534058 L 1.3534058 1.4474569 L 1.7280599 1.822111 L 1.822111 1.7280599 L 1.4474569 1.3534058 z M 1.072286 1.7285767 L 0.87126465 1.929598 A 0.26460981 0.26460981 0 0 0 0.79271647 2.1171834 L 0.79271647 2.6463501 A 0.26460981 0.26460981 0 0 0 0.87126465 2.8318685 L 1.4314372 3.3925578 L 1.8050578 3.0189372 L 1.3223999 2.5373128 L 1.3223999 2.2282878 L 1.4469401 2.1032308 L 1.072286 1.7285767 z M 3.8602295 1.8996257 L 3.4866089 2.2732463 L 3.96875 2.7559041 L 3.96875 3.0644124 L 3.843693 3.1894694 L 4.2178304 3.5636068 L 4.4198853 3.3615519 A 0.26460981 0.26460981 0 0 0 4.4979167 3.1760335 L 4.4979167 2.6463501 A 0.26460981 0.26460981 0 0 0 4.4198853 2.4587646 L 3.8602295 1.8996257 z M 3.5625732 3.4705892 L 3.4685221 3.5646403 L 3.8426595 3.9387777 L 3.9367106 3.8447266 L 3.5625732 3.4705892 z M 2.2732463 3.4866089 L 1.8996257 3.8602295 L 2.4587646 4.4198853 A 0.26460981 0.26460981 0 0 0 2.6463501 4.4979167 L 3.1760335 4.4979167 A 0.26460981 0.26460981 0 0 0 3.3615519 4.4198853 L 3.5615397 4.2198975 L 3.1874023 3.8457601 L 3.0644124 3.96875 L 2.7559041 3.96875 L 2.2732463 3.4866089 z " /> From 33fa2524e9350df089699b8974fb595a75206097 Mon Sep 17 00:00:00 2001 From: szeli1 <143485814+szeli1@users.noreply.github.com> Date: Mon, 9 Jun 2025 18:51:30 +0200 Subject: [PATCH 20/30] MULTIPLE FILES applying style suggestions --- plugins/Vestige/Vestige.cpp | 4 ++-- plugins/VstEffect/VstEffectControls.cpp | 4 ++-- src/core/AutomatableModel.cpp | 6 +++--- src/gui/AutomatableModelView.cpp | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/plugins/Vestige/Vestige.cpp b/plugins/Vestige/Vestige.cpp index e3255d5b2ff..24f76efb17f 100644 --- a/plugins/Vestige/Vestige.cpp +++ b/plugins/Vestige/Vestige.cpp @@ -1070,8 +1070,8 @@ void ManageVestigeInstrumentView::syncPlugin( void ) std::snprintf(paramStr.data(), paramStr.size(), "param%d", i); s_dumpValues = dump[paramStr.data()].split(":"); float f_value = LocaleHelper::toFloat(s_dumpValues.at(2)); - m_vi->knobFModel[ i ]->setValue(f_value, true); - m_vi->knobFModel[ i ]->setInitValue( f_value ); + m_vi->knobFModel[i]->setValue(f_value, true); + m_vi->knobFModel[i]->setInitValue(f_value); } } syncParameterText(); diff --git a/plugins/VstEffect/VstEffectControls.cpp b/plugins/VstEffect/VstEffectControls.cpp index de35a9d6754..86f8626fa05 100644 --- a/plugins/VstEffect/VstEffectControls.cpp +++ b/plugins/VstEffect/VstEffectControls.cpp @@ -463,8 +463,8 @@ void ManageVSTEffectView::syncPlugin() std::snprintf(paramStr.data(), paramStr.size(), "param%d", i); s_dumpValues = dump[paramStr.data()].split(":"); float f_value = LocaleHelper::toFloat(s_dumpValues.at(2)); - m_vi2->knobFModel[ i ]->setValue(f_value, true); - m_vi2->knobFModel[ i ]->setInitValue( f_value ); + m_vi2->knobFModel[i]->setValue(f_value, true); + m_vi2->knobFModel[i]->setInitValue(f_value); } } syncParameterText(); diff --git a/src/core/AutomatableModel.cpp b/src/core/AutomatableModel.cpp index 0ca66d19a3c..4fd7b24bd4f 100644 --- a/src/core/AutomatableModel.cpp +++ b/src/core/AutomatableModel.cpp @@ -73,7 +73,7 @@ AutomatableModel::~AutomatableModel() // unlink this from anything else unlinkModel(); - if( m_controllerConnection ) + if (m_controllerConnection) { delete m_controllerConnection; } @@ -554,7 +554,7 @@ float AutomatableModel::controllerValue( int frameOffset ) const if (m_controllerConnection == nullptr) { return m_value; } float v = 0; - switch(m_scaleType) + switch (m_scaleType) { case ScaleType::Linear: v = minValue() + ( range() * controllerConnection()->currentValue( frameOffset ) ); @@ -649,7 +649,7 @@ ValueBuffer * AutomatableModel::valueBuffer() if (m_oldValue != val) { - m_valueBuffer.interpolate( m_oldValue, val ); + m_valueBuffer.interpolate(m_oldValue, val); m_oldValue = val; m_lastUpdatedPeriod = s_periodCounter; m_hasSampleExactData = true; diff --git a/src/gui/AutomatableModelView.cpp b/src/gui/AutomatableModelView.cpp index 1b03ea071a9..ea91afbab42 100644 --- a/src/gui/AutomatableModelView.cpp +++ b/src/gui/AutomatableModelView.cpp @@ -98,8 +98,8 @@ void AutomatableModelView::addDefaultActions( QMenu* menu ) if( model->hasLinkedModels() ) { - menu->addAction( embed::getIconPixmap( "edit_unlink" ), - AutomatableModel::tr( "Remove all linked controls" ), + menu->addAction(embed::getIconPixmap("edit_unlink"), + AutomatableModel::tr("Remove all linked controls"), amvSlots, SLOT(unlinkAllModels())); menu->addSeparator(); } From a8a5b3e596c52916261b6e4e934b000a7b08b40f Mon Sep 17 00:00:00 2001 From: szeli1 <143485814+szeli1@users.noreply.github.com> Date: Fri, 27 Jun 2025 15:41:28 +0200 Subject: [PATCH 21/30] AutomatableModel updating while loops --- include/AutomatableModel.h | 2 +- src/core/AutomatableModel.cpp | 26 +++++++++++--------------- 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/include/AutomatableModel.h b/include/AutomatableModel.h index 1f824c047e1..922f144a57e 100644 --- a/include/AutomatableModel.h +++ b/include/AutomatableModel.h @@ -264,7 +264,7 @@ class LMMS_EXPORT AutomatableModel : public Model, public JournallingObject virtual QString displayValue( const float val ) const = 0; - constexpr bool hasLinkedModels() const + bool hasLinkedModels() const { return m_nextLink != nullptr; } diff --git a/src/core/AutomatableModel.cpp b/src/core/AutomatableModel.cpp index 4fd7b24bd4f..f14a89c2341 100644 --- a/src/core/AutomatableModel.cpp +++ b/src/core/AutomatableModel.cpp @@ -296,18 +296,18 @@ void AutomatableModel::setValue(float value, bool isAutomated) if (newValue == m_value) { emit dataUnchanged(); return; } if (isAutomated == false) { addJournalCheckPoint(); } + + setValueInternal(value, isAutomated); // set value for this and the other linked widgets if (hasLinkedModels()) { - AutomatableModel* next = this; - while (true) + AutomatableModel* next = m_nextLink; + while (next != this) { next->setValueInternal(value, isAutomated); next = next->m_nextLink; - if (next == this) { break; } } } - else { setValueInternal(value, isAutomated); } } void AutomatableModel::setValueInternal(float value, bool isAutomated) @@ -463,9 +463,8 @@ AutomatableModel* AutomatableModel::getLastLinkedModel() const { if (hasLinkedModels() == false) { return nullptr; } AutomatableModel* output = m_nextLink; - while (true) + while (output->m_nextLink != this) { - if (output->m_nextLink == this) { break; } output = output->m_nextLink; } return output; @@ -474,27 +473,24 @@ AutomatableModel* AutomatableModel::getLastLinkedModel() const bool AutomatableModel::isModelLinked(AutomatableModel* model) const { if (hasLinkedModels() == false || model == nullptr || model == this) { return false; } - bool output = false; AutomatableModel* next = m_nextLink; - while (true) + while (next != this) { - if (next->m_nextLink == this) { break; } - if (next->m_nextLink == model) { output = true; break; } + if (next == model) { return true; } next = next->m_nextLink; } - return output; + return false; } size_t AutomatableModel::countLinks() const { if (hasLinkedModels() == false) { return 0; } - size_t output = 2; + size_t output = 1; AutomatableModel* next = m_nextLink; - while (true) + while (next != this) { - if (next->m_nextLink == this) { break; } - next = next->m_nextLink; output++; + next = next->m_nextLink; } return output; } From 40c231deb728172e26ac2aa57c172f27796ee54e Mon Sep 17 00:00:00 2001 From: szeli1 <143485814+szeli1@users.noreply.github.com> Date: Sun, 29 Jun 2025 21:30:28 +0200 Subject: [PATCH 22/30] AutomatableModel fixing value buffers causing crash --- include/AutomatableModel.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/AutomatableModel.h b/include/AutomatableModel.h index 922f144a57e..1204a2efbb3 100644 --- a/include/AutomatableModel.h +++ b/include/AutomatableModel.h @@ -153,7 +153,7 @@ class LMMS_EXPORT AutomatableModel : public Model, public JournallingObject { // workaround to update linked models AutomatableModel* thisModel = const_cast(this); - thisModel->setValue(controllerValue(frameOffset)); + thisModel->setValue(controllerValue(frameOffset), true); } return castValue( m_value ); } From f7c1d79e2815ace854223ec34c1c0d352e723e20 Mon Sep 17 00:00:00 2001 From: allejok96 Date: Mon, 7 Jul 2025 17:08:07 +0200 Subject: [PATCH 23/30] Remove const_cast and simiplify linking functions --- include/AutomatableModel.h | 43 +++--- include/AutomatableModelView.h | 1 - src/core/AutomatableModel.cpp | 158 ++++++++--------------- src/core/LadspaControl.cpp | 16 +-- src/core/LinkedModelGroups.cpp | 2 +- src/core/Song.cpp | 19 ++- src/gui/AutomatableModelView.cpp | 9 +- src/gui/widgets/FloatModelEditorBase.cpp | 19 +-- tests/src/core/AutomatableModelTest.cpp | 14 +- 9 files changed, 120 insertions(+), 161 deletions(-) diff --git a/include/AutomatableModel.h b/include/AutomatableModel.h index 1204a2efbb3..fbf24114e69 100644 --- a/include/AutomatableModel.h +++ b/include/AutomatableModel.h @@ -149,12 +149,21 @@ class LMMS_EXPORT AutomatableModel : public Model, public JournallingObject template inline T value( int frameOffset = 0 ) const { - if (m_controllerConnection && m_useControllerValue) + if (m_useControllerValue) { - // workaround to update linked models - AutomatableModel* thisModel = const_cast(this); - thisModel->setValue(controllerValue(frameOffset), true); + if (m_controllerConnection) + { + return castValue(controllerValue(frameOffset)); + } + for (auto next = m_nextLink; next != this; next = next->m_nextLink) + { + if (next->controllerConnection() && next->useControllerValue()) + { + return castValue(fittedValue(next->controllerValue(frameOffset))); + } + } } + return castValue( m_value ); } @@ -200,7 +209,7 @@ class LMMS_EXPORT AutomatableModel : public Model, public JournallingObject void setInitValue( const float value ); - void setValue(float value, bool isAutomated = false); + void setValue(const float value, const bool isAutomated = false); void incValue( int steps ) { @@ -237,10 +246,9 @@ class LMMS_EXPORT AutomatableModel : public Model, public JournallingObject m_centerValue = centerVal; } - //! link @p m1 and @p m2, let @p m1 take the values of @p m2 - static void linkModels( AutomatableModel* m1, AutomatableModel* m2 ); - void unlinkAllModels(); - //! @return 0 if not connected, never 1, 2 if connected to 1 model + //! link this to @p model, copying the value from @p model + void linkToModel(AutomatableModel* model); + //! @return number of other models linked to this size_t countLinks() const; /** @@ -264,9 +272,9 @@ class LMMS_EXPORT AutomatableModel : public Model, public JournallingObject virtual QString displayValue( const float val ) const = 0; - bool hasLinkedModels() const + bool isLinked() const { - return m_nextLink != nullptr; + return m_nextLink != this; } // a way to track changed values in the model and avoid using signals/slots - useful for speed-critical code. @@ -306,6 +314,7 @@ class LMMS_EXPORT AutomatableModel : public Model, public JournallingObject public slots: virtual void reset(); + void unlink(); void unlinkControllerConnection(); void setUseControllerValue(bool b = true); @@ -327,7 +336,6 @@ public slots: private: - void setValueInternal(float value, bool isAutomated); // dynamicCast implementation template struct DCastVisitor : public ModelVisitor @@ -356,13 +364,14 @@ public slots: loadSettings( element, "value" ); } - void linkModel(AutomatableModel* model); - void unlinkModel(); + void setValueInternal(const float value); + //! linking is stored in a linked list ring - //! @return the model that's `m_nextLink` is `this` + //! @return the model whose `m_nextLink` is `this`, + //! or `this` if there are no linked models AutomatableModel* getLastLinkedModel() const; //! @return true if the `model` is in the linked list - bool isModelLinked(AutomatableModel* model) const; + bool isLinkedToModel(AutomatableModel* model) const; //! @brief Scales @value from linear to logarithmic. //! Value should be within [0,1] @@ -383,7 +392,7 @@ public slots: float m_centerValue; bool m_valueChanged; - float m_oldValue; //!< used for interpolation + float m_oldValue; //!< used by valueBuffer for interpolation // used to determine if step size should be applied strictly (ie. always) // or only when value set from gui (default) diff --git a/include/AutomatableModelView.h b/include/AutomatableModelView.h index 12b2e4d4919..2d4a88ac085 100644 --- a/include/AutomatableModelView.h +++ b/include/AutomatableModelView.h @@ -98,7 +98,6 @@ public slots: void execConnectionDialog(); void removeConnection(); void editSongGlobalAutomation(); - void unlinkAllModels(); void removeSongGlobalAutomation(); private slots: diff --git a/src/core/AutomatableModel.cpp b/src/core/AutomatableModel.cpp index f14a89c2341..0a9a4cef659 100644 --- a/src/core/AutomatableModel.cpp +++ b/src/core/AutomatableModel.cpp @@ -53,7 +53,7 @@ AutomatableModel::AutomatableModel( m_valueChanged( false ), m_oldValue(val), m_hasStrictStepSize( false ), - m_nextLink(nullptr), + m_nextLink(this), m_controllerConnection( nullptr ), m_valueBuffer( static_cast( Engine::audioEngine()->framesPerPeriod() ) ), m_lastUpdatedPeriod( -1 ), @@ -70,8 +70,7 @@ AutomatableModel::AutomatableModel( AutomatableModel::~AutomatableModel() { - // unlink this from anything else - unlinkModel(); + unlink(); if (m_controllerConnection) { @@ -290,33 +289,34 @@ void AutomatableModel::loadSettings( const QDomElement& element, const QString& -void AutomatableModel::setValue(float value, bool isAutomated) +void AutomatableModel::setValue(const float value, const bool isAutomated) { - float newValue = fittedValue(value); - if (newValue == m_value) { emit dataUnchanged(); return; } + if (fittedValue(value) == m_value) + { + // TODO check why we need this signal, is there a better solution? + if (!isAutomated) { emit dataUnchanged(); } + return; + } - if (isAutomated == false) { addJournalCheckPoint(); } + if (!isAutomated) { addJournalCheckPoint(); } - setValueInternal(value, isAutomated); - // set value for this and the other linked widgets - if (hasLinkedModels()) + // set value for this and the other linked models + setValueInternal(value); + for (auto model = m_nextLink; model != this; model = model->m_nextLink) { - AutomatableModel* next = m_nextLink; - while (next != this) - { - next->setValueInternal(value, isAutomated); - next = next->m_nextLink; - } + model->setValueInternal(value); } } -void AutomatableModel::setValueInternal(float value, bool isAutomated) + + + +void AutomatableModel::setValueInternal(const float value) { - float oldValue = m_value; - if (isAutomated) { m_value = fittedValue(scaledValue(value)); } - else { m_value = fittedValue(value); } + m_oldValue = m_value; + m_value = fittedValue(value); - if (oldValue != m_value) + if (m_oldValue != m_value) { m_valueChanged = true; emit dataChanged(); @@ -427,70 +427,33 @@ float AutomatableModel::fittedValue( float value ) const - -void AutomatableModel::linkModel(AutomatableModel* model) +AutomatableModel* AutomatableModel::getLastLinkedModel() const { - if (model == nullptr || model == this || isModelLinked(model)) { return; } - - AutomatableModel* otherEnd = model->getLastLinkedModel(); - AutomatableModel* next = m_nextLink == nullptr ? this : m_nextLink; - if (otherEnd == nullptr) + for (auto model = m_nextLink; ; model = model->m_nextLink) { - // linking other start to our next - model->m_nextLink = next; + // The last model in the circual reference links back to this + if (model->m_nextLink == this) { return model; } } - else - { - // linking other end to our next - otherEnd->m_nextLink = next; - } - // linking this to other start - m_nextLink = model; - assert(m_nextLink != this); // if you change the code, be careful to not link to this } -void AutomatableModel::unlinkModel() -{ - if (hasLinkedModels() == false) { return; } - AutomatableModel* end = getLastLinkedModel(); - assert(end != nullptr); - end->m_nextLink = end == m_nextLink ? nullptr : m_nextLink; - m_nextLink = nullptr; -} -AutomatableModel* AutomatableModel::getLastLinkedModel() const -{ - if (hasLinkedModels() == false) { return nullptr; } - AutomatableModel* output = m_nextLink; - while (output->m_nextLink != this) - { - output = output->m_nextLink; - } - return output; -} -bool AutomatableModel::isModelLinked(AutomatableModel* model) const +bool AutomatableModel::isLinkedToModel(AutomatableModel* model) const { - if (hasLinkedModels() == false || model == nullptr || model == this) { return false; } - AutomatableModel* next = m_nextLink; - while (next != this) + for (auto next = m_nextLink; ; next = next->m_nextLink) { if (next == model) { return true; } - next = next->m_nextLink; + if (next == this) { return false; } } - return false; } size_t AutomatableModel::countLinks() const { - if (hasLinkedModels() == false) { return 0; } - size_t output = 1; - AutomatableModel* next = m_nextLink; - while (next != this) + size_t output = 0; + for (auto model = m_nextLink; model != this; model = model->m_nextLink) { output++; - next = next->m_nextLink; } return output; } @@ -498,32 +461,33 @@ size_t AutomatableModel::countLinks() const - -void AutomatableModel::linkModels( AutomatableModel* model1, AutomatableModel* model2 ) +void AutomatableModel::linkToModel(AutomatableModel* other) { - // copy data - model1->setValue(model2->m_value); - if (model1->valueBuffer() && model2->valueBuffer()) + if (isLinkedToModel(other)) { return; } + + // copy data from other to this + setValue(other->m_value); + if (valueBuffer() && other->valueBuffer()) { - std::copy_n(model2->valueBuffer()->data(), - model1->valueBuffer()->length(), - model1->valueBuffer()->data()); - emit model1->dataChanged(); + std::copy_n(other->valueBuffer()->data(), + valueBuffer()->length(), + valueBuffer()->data()); + emit dataChanged(); } // link the models - model1->linkModel(model2); + AutomatableModel* thisEnd = getLastLinkedModel(); + AutomatableModel* otherEnd = other->getLastLinkedModel(); + thisEnd->m_nextLink = other; + otherEnd->m_nextLink = this; } - - - - -void AutomatableModel::unlinkAllModels() +void AutomatableModel::unlink() { - unlinkModel(); + getLastLinkedModel()->m_nextLink = m_nextLink; + m_nextLink = this; } @@ -547,8 +511,6 @@ void AutomatableModel::setControllerConnection( ControllerConnection* c ) float AutomatableModel::controllerValue( int frameOffset ) const { - if (m_controllerConnection == nullptr) { return m_value; } - float v = 0; switch (m_scaleType) { @@ -584,6 +546,7 @@ ValueBuffer * AutomatableModel::valueBuffer() float val = m_value; // make sure our m_value doesn't change midway + // Get value buffer from our controller if (m_controllerConnection && m_useControllerValue && m_controllerConnection->getController()->isSampleExact()) { auto vb = m_controllerConnection->valueBuffer(); @@ -616,13 +579,11 @@ ValueBuffer * AutomatableModel::valueBuffer() } } - if (!m_controllerConnection) + // Get value buffer from one of the linked models' controller + if (m_useControllerValue) { - if (hasLinkedModels()) + for (auto next = m_nextLink; next != this; next = next->m_nextLink) { - AutomatableModel* next = this; - while (true) - { if (next->controllerConnection() && next->useControllerValue() && next->controllerConnection()->getController()->isSampleExact()) { @@ -637,12 +598,10 @@ ValueBuffer * AutomatableModel::valueBuffer() m_hasSampleExactData = true; return &m_valueBuffer; } - next = next->m_nextLink; - if (next == this) { break; } - } } } + // Populate value buffer by interpolatating between the old and new value if (m_oldValue != val) { m_valueBuffer.interpolate(m_oldValue, val); @@ -754,16 +713,9 @@ float AutomatableModel::globalAutomationValueAt( const TimePos& time ) void AutomatableModel::setUseControllerValue(bool b) { - if (b) - { - m_useControllerValue = true; - emit dataChanged(); - } - else if (m_controllerConnection && m_useControllerValue) - { - m_useControllerValue = false; - emit dataChanged(); - } + if (m_useControllerValue == b) { return; } + m_useControllerValue = b; + if (m_controllerConnection) { emit dataChanged(); } } float FloatModel::getRoundedValue() const diff --git a/src/core/LadspaControl.cpp b/src/core/LadspaControl.cpp index 9691500f997..4227f7c4fb2 100644 --- a/src/core/LadspaControl.cpp +++ b/src/core/LadspaControl.cpp @@ -291,16 +291,15 @@ void LadspaControl::linkControls( LadspaControl * _control ) switch( m_port->data_type ) { case BufferDataType::Toggled: - BoolModel::linkModels( &m_toggledModel, _control->toggledModel() ); + _control->toggledModel()->linkToModel(&m_toggledModel); break; case BufferDataType::Integer: case BufferDataType::Enum: case BufferDataType::Floating: - FloatModel::linkModels( &m_knobModel, _control->knobModel() ); + _control->knobModel()->linkToModel(&m_knobModel); break; case BufferDataType::Time: - TempoSyncKnobModel::linkModels( &m_tempoSyncKnobModel, - _control->tempoSyncKnobModel() ); + _control->tempoSyncKnobModel()->linkToModel(&m_tempoSyncKnobModel); break; default: break; @@ -342,18 +341,15 @@ void LadspaControl::unlinkControls( LadspaControl * _control ) switch( m_port->data_type ) { case BufferDataType::Toggled: - m_toggledModel.unlinkAllModels(); - _control->toggledModel()->unlinkAllModels(); + _control->toggledModel()->unlink(); break; case BufferDataType::Integer: case BufferDataType::Enum: case BufferDataType::Floating: - m_knobModel.unlinkAllModels(); - _control->knobModel()->unlinkAllModels(); + _control->knobModel()->unlink(); break; case BufferDataType::Time: - m_tempoSyncKnobModel.unlinkAllModels(); - _control->tempoSyncKnobModel()->unlinkAllModels(); + _control->tempoSyncKnobModel()->unlink(); break; default: break; diff --git a/src/core/LinkedModelGroups.cpp b/src/core/LinkedModelGroups.cpp index c52bce43310..841e4f154ff 100644 --- a/src/core/LinkedModelGroups.cpp +++ b/src/core/LinkedModelGroups.cpp @@ -46,7 +46,7 @@ void LinkedModelGroup::linkControls(LinkedModelGroup *other) { auto itr2 = other->m_models.find(id); Q_ASSERT(itr2 != other->m_models.end()); - AutomatableModel::linkModels(inf.m_model, itr2->second.m_model); + itr2->second.m_model->linkToModel(inf.m_model); }); } diff --git a/src/core/Song.cpp b/src/core/Song.cpp index 8d0ff285cc0..722b4702fe0 100644 --- a/src/core/Song.cpp +++ b/src/core/Song.cpp @@ -407,7 +407,7 @@ void Song::processAutomations(const TrackList &tracklist, TimePos timeStart, fpp for (auto it = m_oldAutomatedValues.begin(); it != m_oldAutomatedValues.end(); it++) { AutomatableModel * am = it.key(); - if (am->controllerConnection() && !values.contains(am)) + if (!values.contains(am)) { am->setUseControllerValue(true); } @@ -417,13 +417,18 @@ void Song::processAutomations(const TrackList &tracklist, TimePos timeStart, fpp // Apply values for (auto it = values.begin(); it != values.end(); it++) { - if (! recordedModels.contains(it.key())) - { - it.key()->setValue(it.value(), true); - } - else if (!it.key()->useControllerValue()) + AutomatableModel* model = it.key(); + bool isRecording = recordedModels.contains(model); + model->setUseControllerValue(isRecording); + + if (!isRecording) { - it.key()->setUseControllerValue(true); + /* TODO + * Remove scaleValue() from here when automation editor's + * Y axis can be set to logarithmic, and automation clips store + * the actual values, and not the invertedScaledValue. + */ + model->setValue(model->scaledValue(it.value()), true); } } } diff --git a/src/gui/AutomatableModelView.cpp b/src/gui/AutomatableModelView.cpp index ea91afbab42..ef40da8158b 100644 --- a/src/gui/AutomatableModelView.cpp +++ b/src/gui/AutomatableModelView.cpp @@ -96,11 +96,11 @@ void AutomatableModelView::addDefaultActions( QMenu* menu ) menu->addSeparator(); - if( model->hasLinkedModels() ) + if (model->isLinked()) { menu->addAction(embed::getIconPixmap("edit_unlink"), AutomatableModel::tr("Remove all linked controls"), - amvSlots, SLOT(unlinkAllModels())); + model, SLOT(unlink())); menu->addSeparator(); } @@ -276,11 +276,6 @@ void AutomatableModelViewSlots::removeSongGlobalAutomation() } -void AutomatableModelViewSlots::unlinkAllModels() -{ - m_amv->modelUntyped()->unlinkAllModels(); -} - void AutomatableModelViewSlots::copyToClipboard() { // For copyString() and MimeType enum class diff --git a/src/gui/widgets/FloatModelEditorBase.cpp b/src/gui/widgets/FloatModelEditorBase.cpp index 3c7fe93c752..a0b3e4b3495 100644 --- a/src/gui/widgets/FloatModelEditorBase.cpp +++ b/src/gui/widgets/FloatModelEditorBase.cpp @@ -146,8 +146,7 @@ void FloatModelEditorBase::dropEvent(QDropEvent * de) auto mod = dynamic_cast(Engine::projectJournal()->journallingObject(val.toInt())); if (mod != nullptr) { - AutomatableModel::linkModels(model(), mod); - mod->setValue(model()->value()); + model()->linkToModel(mod); } } } @@ -418,12 +417,16 @@ void FloatModelEditorBase::enterValue() void FloatModelEditorBase::friendlyUpdate() { - if (model() && (model()->controllerConnection() == nullptr || - model()->controllerConnection()->getController()->frequentUpdates() == false || - Controller::runningFrames() % (256*4) == 0)) - { - update(); - } + if (model() == nullptr) { return; } + + // If the controller changes constantly, only repaint every 1024th frame + if (model()->useControllerValue() + && model()->controllerConnection() + && model()->controllerConnection()->getController()->frequentUpdates() + && Controller::runningFrames() % (256*4) != 0) + { return; } + + update(); } diff --git a/tests/src/core/AutomatableModelTest.cpp b/tests/src/core/AutomatableModelTest.cpp index 4fa81369a42..fdcc4f42f74 100644 --- a/tests/src/core/AutomatableModelTest.cpp +++ b/tests/src/core/AutomatableModelTest.cpp @@ -86,19 +86,19 @@ private slots: // tests this, SLOT(onM2Changed())); resetChanged(); - AutomatableModel::linkModels(&m1, &m1); + m1.linkToModel(&m1); QVERIFY(!m1Changed); // cannot link to itself QVERIFY(!m2Changed); QVERIFY(m1.countLinks() == 0); resetChanged(); - AutomatableModel::linkModels(&m1, &m2); + m1.linkToModel(&m2); QVERIFY(m1.value() == m2.value()); // since m1 takes the value of m2 QVERIFY(!m2Changed); // the second model is the source - QVERIFY(m1.countLinks() == 2); + QVERIFY(m1.countLinks() == 1); resetChanged(); - AutomatableModel::linkModels(&m1, &m2); + m1.linkToModel(&m2); QVERIFY(!m1Changed); // it's already linked QVERIFY(!m2Changed); @@ -106,15 +106,15 @@ private slots: // tests BoolModel m3(false); m1.setValue(1.f); // true m2.setValue(1.f); // true - AutomatableModel::linkModels(&m1, &m2); + m1.linkToModel(&m2); QVERIFY(m1.value()); QVERIFY(m2.value()); QVERIFY(!m3.value()); - AutomatableModel::linkModels(&m2, &m3); // drag m3, drop on m2 + m2.linkToModel(&m3); // drag m3, drop on m2 // m2 should take m3's (0) value QVERIFY(m2.value() == m3.value()); QVERIFY(!m3.value()); - QVERIFY(m1.countLinks() == 3); + QVERIFY(m1.countLinks() == 2); } }; From 761b385766760f8dacfa124dddf29e1b7fa46c55 Mon Sep 17 00:00:00 2001 From: allejok96 Date: Sun, 13 Jul 2025 15:48:13 +0200 Subject: [PATCH 24/30] Clarify for, add assert, revert setUseControllerValue, remove comments --- include/AutomatableModel.h | 1 - src/core/AutomatableModel.cpp | 20 +++++++++++++++----- tests/src/core/AutomatableModelTest.cpp | 4 ++-- 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/include/AutomatableModel.h b/include/AutomatableModel.h index fbf24114e69..2ace29aa458 100644 --- a/include/AutomatableModel.h +++ b/include/AutomatableModel.h @@ -163,7 +163,6 @@ class LMMS_EXPORT AutomatableModel : public Model, public JournallingObject } } } - return castValue( m_value ); } diff --git a/src/core/AutomatableModel.cpp b/src/core/AutomatableModel.cpp index 0a9a4cef659..c34b26e0d31 100644 --- a/src/core/AutomatableModel.cpp +++ b/src/core/AutomatableModel.cpp @@ -441,11 +441,12 @@ AutomatableModel* AutomatableModel::getLastLinkedModel() const bool AutomatableModel::isLinkedToModel(AutomatableModel* model) const { - for (auto next = m_nextLink; ; next = next->m_nextLink) + if (model == this) { return true; } + for (auto next = m_nextLink; next != this; next = next->m_nextLink) { if (next == model) { return true; } - if (next == this) { return false; } } + return false; } size_t AutomatableModel::countLinks() const @@ -511,6 +512,8 @@ void AutomatableModel::setControllerConnection( ControllerConnection* c ) float AutomatableModel::controllerValue( int frameOffset ) const { + assert(m_controllerConnection != nullptr); + float v = 0; switch (m_scaleType) { @@ -713,9 +716,16 @@ float AutomatableModel::globalAutomationValueAt( const TimePos& time ) void AutomatableModel::setUseControllerValue(bool b) { - if (m_useControllerValue == b) { return; } - m_useControllerValue = b; - if (m_controllerConnection) { emit dataChanged(); } + if (b) + { + m_useControllerValue = true; + emit dataChanged(); + } + else if (m_controllerConnection && m_useControllerValue) + { + m_useControllerValue = false; + emit dataChanged(); + } } float FloatModel::getRoundedValue() const diff --git a/tests/src/core/AutomatableModelTest.cpp b/tests/src/core/AutomatableModelTest.cpp index fdcc4f42f74..0af6550025c 100644 --- a/tests/src/core/AutomatableModelTest.cpp +++ b/tests/src/core/AutomatableModelTest.cpp @@ -104,8 +104,8 @@ private slots: // tests resetChanged(); BoolModel m3(false); - m1.setValue(1.f); // true - m2.setValue(1.f); // true + m1.setValue(1.f); + m2.setValue(1.f); m1.linkToModel(&m2); QVERIFY(m1.value()); QVERIFY(m2.value()); From e89ba7d76121f55dd9b58ce054e72668ff5e884e Mon Sep 17 00:00:00 2001 From: allejok96 Date: Fri, 18 Jul 2025 23:11:46 +0200 Subject: [PATCH 25/30] Add comments about design flaws --- include/AutomatableModel.h | 6 ++++++ src/core/AutomatableModel.cpp | 8 ++++++++ 2 files changed, 14 insertions(+) diff --git a/include/AutomatableModel.h b/include/AutomatableModel.h index 2ace29aa458..629b33cd299 100644 --- a/include/AutomatableModel.h +++ b/include/AutomatableModel.h @@ -149,6 +149,12 @@ class LMMS_EXPORT AutomatableModel : public Model, public JournallingObject template inline T value( int frameOffset = 0 ) const { + // TODO + // The `m_value` should only be updated whenever the Controller value changes, + // instead of the Model calling `controller->currentValue()` every time. + // This becomes even worse in the case of linked Models, where it has to + // loop through the list of all links. + if (m_useControllerValue) { if (m_controllerConnection) diff --git a/src/core/AutomatableModel.cpp b/src/core/AutomatableModel.cpp index c34b26e0d31..e5ae0c439b0 100644 --- a/src/core/AutomatableModel.cpp +++ b/src/core/AutomatableModel.cpp @@ -549,6 +549,10 @@ ValueBuffer * AutomatableModel::valueBuffer() float val = m_value; // make sure our m_value doesn't change midway + // TODO + // Let the Controller set the value of connected Models, + // instead of each Model checking the Controller value every time. + // Get value buffer from our controller if (m_controllerConnection && m_useControllerValue && m_controllerConnection->getController()->isSampleExact()) { @@ -604,6 +608,10 @@ ValueBuffer * AutomatableModel::valueBuffer() } } + // Note: if there are linked models but no controller, `setValue()` will have + // updated `m_value` and `m_oldValue` across all linked models, so the `valueBuffer()` + // will be the same (even if it is calculated separatly for each model). + // Populate value buffer by interpolatating between the old and new value if (m_oldValue != val) { From 2680d4c2b0acd1caea15d0e25ef3cb8b61a03948 Mon Sep 17 00:00:00 2001 From: szeli1 <143485814+szeli1@users.noreply.github.com> Date: Sat, 25 Oct 2025 19:18:11 +0200 Subject: [PATCH 26/30] AutomatableModel style fixes --- src/core/AutomatableModel.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/core/AutomatableModel.cpp b/src/core/AutomatableModel.cpp index 7025c1cd6e6..92e536a7a68 100644 --- a/src/core/AutomatableModel.cpp +++ b/src/core/AutomatableModel.cpp @@ -433,7 +433,7 @@ AutomatableModel* AutomatableModel::getLastLinkedModel() const { for (auto model = m_nextLink; ; model = model->m_nextLink) { - // The last model in the circual reference links back to this + // The last model in the circular reference links back to this if (model->m_nextLink == this) { return model; } } } @@ -597,8 +597,8 @@ ValueBuffer * AutomatableModel::valueBuffer() next->controllerConnection()->getController()->isSampleExact()) { auto vb = next->valueBuffer(); - float * values = vb->values(); - float * nvalues = m_valueBuffer.values(); + float* values = vb->values(); + float* nvalues = m_valueBuffer.values(); for (int i = 0; i < vb->length(); i++) { nvalues[i] = fittedValue(values[i]); From 3f2e78e9fe600b651f2497212241652ae773ab7a Mon Sep 17 00:00:00 2001 From: szeli1 <143485814+szeli1@users.noreply.github.com> Date: Sat, 6 Dec 2025 18:45:53 +0100 Subject: [PATCH 27/30] FloatModelEditorBase fixing format --- src/gui/widgets/FloatModelEditorBase.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/widgets/FloatModelEditorBase.cpp b/src/gui/widgets/FloatModelEditorBase.cpp index d733ea5e177..a492bad0d1b 100644 --- a/src/gui/widgets/FloatModelEditorBase.cpp +++ b/src/gui/widgets/FloatModelEditorBase.cpp @@ -423,7 +423,7 @@ void FloatModelEditorBase::friendlyUpdate() if (model()->useControllerValue() && model()->controllerConnection() && model()->controllerConnection()->getController()->frequentUpdates() - && Controller::runningFrames() % (256*4) != 0) + && Controller::runningFrames() % (256 * 4) != 0) { return; } update(); From 0558a6b6a1405be342352e7ed22ca5bd79f2da1b Mon Sep 17 00:00:00 2001 From: szeli1 <143485814+szeli1@users.noreply.github.com> Date: Sat, 6 Dec 2025 18:51:15 +0100 Subject: [PATCH 28/30] LinkedModelGroups fixing link order --- src/core/LinkedModelGroups.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/LinkedModelGroups.cpp b/src/core/LinkedModelGroups.cpp index 841e4f154ff..b0ce60faf33 100644 --- a/src/core/LinkedModelGroups.cpp +++ b/src/core/LinkedModelGroups.cpp @@ -46,7 +46,7 @@ void LinkedModelGroup::linkControls(LinkedModelGroup *other) { auto itr2 = other->m_models.find(id); Q_ASSERT(itr2 != other->m_models.end()); - itr2->second.m_model->linkToModel(inf.m_model); + itr->second.m_model->linkToModel(inf2.m_model); }); } From c53f98237b70b053aaaf313b7d4392c12cd33d10 Mon Sep 17 00:00:00 2001 From: szeli1 <143485814+szeli1@users.noreply.github.com> Date: Sat, 6 Dec 2025 19:20:02 +0100 Subject: [PATCH 29/30] LinkedModelGroups fixing bug --- src/core/LinkedModelGroups.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/LinkedModelGroups.cpp b/src/core/LinkedModelGroups.cpp index b0ce60faf33..c4a889b4d26 100644 --- a/src/core/LinkedModelGroups.cpp +++ b/src/core/LinkedModelGroups.cpp @@ -46,7 +46,7 @@ void LinkedModelGroup::linkControls(LinkedModelGroup *other) { auto itr2 = other->m_models.find(id); Q_ASSERT(itr2 != other->m_models.end()); - itr->second.m_model->linkToModel(inf2.m_model); + inf->second.m_model->linkToModel(itr2.m_model); }); } From 6cfa40c3a8f8cb5ced450cf1343e23e6695fd371 Mon Sep 17 00:00:00 2001 From: szeli1 <143485814+szeli1@users.noreply.github.com> Date: Sat, 6 Dec 2025 23:01:58 +0100 Subject: [PATCH 30/30] LinkedModelGroups fixing bug 2 --- src/core/LinkedModelGroups.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/LinkedModelGroups.cpp b/src/core/LinkedModelGroups.cpp index c4a889b4d26..3778d4d80da 100644 --- a/src/core/LinkedModelGroups.cpp +++ b/src/core/LinkedModelGroups.cpp @@ -46,7 +46,7 @@ void LinkedModelGroup::linkControls(LinkedModelGroup *other) { auto itr2 = other->m_models.find(id); Q_ASSERT(itr2 != other->m_models.end()); - inf->second.m_model->linkToModel(itr2.m_model); + inf.m_model->linkToModel(itr2->second.m_model); }); }