From a264bb108c826975ef6d599c024cb0a22fa79ebf Mon Sep 17 00:00:00 2001 From: Brian Matherly Date: Sat, 22 Aug 2026 10:21:15 -0500 Subject: [PATCH 1/3] Fix duck status is not synchronized The status set on the mix transition is earlier than the frame being displayed. Use the status from the frame so the status is synchronized with the preview player --- src/models/multitrackmodel.cpp | 9 +++++ src/widgets/trackpropertieswidget.cpp | 51 ++++++++++++++++----------- src/widgets/trackpropertieswidget.h | 13 ++++--- 3 files changed, 49 insertions(+), 24 deletions(-) diff --git a/src/models/multitrackmodel.cpp b/src/models/multitrackmodel.cpp index f3271993e7..3f039b17b7 100644 --- a/src/models/multitrackmodel.cpp +++ b/src/models/multitrackmodel.cpp @@ -48,6 +48,11 @@ static QByteArray trackAudioLevelPrefix(int mltTrackIndex) return QStringLiteral("meta.track.%1.audio_level.").arg(mltTrackIndex).toLatin1(); } +static QByteArray trackDuckLevelPrefix(int mltTrackIndex) +{ + return QStringLiteral("meta.track.%1.").arg(mltTrackIndex).toLatin1(); +} + static double audioLevelDbFromFilter(Mlt::Filter *filter) { static const char *kAudioLevelLeft = "_audio_level.0"; @@ -812,6 +817,10 @@ void MultitrackModel::syncTrackAudioLevelFilterPrefixes() std::unique_ptr levelFilter( ensureTrackAudioLevelFilter(track.data(), mltTrackIndex)); Q_UNUSED(levelFilter) + + QScopedPointer mix(getTransition("mix", mltTrackIndex)); + if (mix && mix->is_valid()) + mix->set("prefix", trackDuckLevelPrefix(mltTrackIndex).constData()); } } diff --git a/src/widgets/trackpropertieswidget.cpp b/src/widgets/trackpropertieswidget.cpp index 66733f6ff0..41f3d79ce6 100644 --- a/src/widgets/trackpropertieswidget.cpp +++ b/src/widgets/trackpropertieswidget.cpp @@ -20,12 +20,14 @@ #include "commands/timelinecommands.h" #include "mainwindow.h" +#include "sharedframe.h" #include "shotcut_mlt_properties.h" #include "util.h" #include +#include #include -#include +#include static const char *BLEND_PROPERTY_CAIROBLEND = "1"; static const char *BLEND_PROPERTY_QTBLEND = "compositing"; @@ -42,7 +44,7 @@ TrackPropertiesWidget::TrackPropertiesWidget(Mlt::Producer &track, : QWidget(parent) , ui(new Ui::TrackPropertiesWidget) , m_track(track) - , m_duckStatusTimer(new QTimer(this)) + , m_trackIndex(-1) { ui->setupUi(this); Util::setColorsToHighlight(ui->nameLabel); @@ -63,15 +65,6 @@ TrackPropertiesWidget::TrackPropertiesWidget(Mlt::Producer &track, } } - m_duckStatusTimer->setInterval(100); - if (showDucking) { - connect(m_duckStatusTimer, - &QTimer::timeout, - this, - &TrackPropertiesWidget::refreshDuckStatus); - m_duckStatusTimer->start(); - } - if (showBlend) { QScopedPointer transition(getTransition("qtblend")); if (!transition) @@ -165,7 +158,6 @@ TrackPropertiesWidget::TrackPropertiesWidget(Mlt::Producer &track, TrackPropertiesWidget::~TrackPropertiesWidget() { - m_duckStatusTimer->stop(); delete ui; } @@ -192,6 +184,32 @@ void TrackPropertiesWidget::updateDuckStatus(double value) ui->duckStatusValueLabel->setFormat(QString::number(clamped, 'f', 1) + tr(" dB")); } +void TrackPropertiesWidget::showEvent(QShowEvent *event) +{ + QWidget::showEvent(event); + connect(MLT.videoWidget(), + SIGNAL(frameDisplayed(const SharedFrame &)), + this, + SLOT(onShowFrame(const SharedFrame &))); +} + +void TrackPropertiesWidget::hideEvent(QHideEvent *event) +{ + QWidget::hideEvent(event); + disconnect(MLT.videoWidget(), + SIGNAL(frameDisplayed(const SharedFrame &)), + this, + SLOT(onShowFrame(const SharedFrame &))); +} + +void TrackPropertiesWidget::onShowFrame(const SharedFrame &frame) +{ + if (m_trackIndex < 0 || !frame.is_valid()) + return; + const QByteArray key = QStringLiteral("meta.track.%1.duck_level").arg(m_trackIndex).toLatin1(); + updateDuckStatus(frame.get_double(key.constData())); +} + Mlt::Transition *TrackPropertiesWidget::getTransition(const QString &name) { // track.consumer() is the multitrack @@ -206,6 +224,7 @@ Mlt::Transition *TrackPropertiesWidget::getTransition(const QString &name) if (producer->get_producer() == m_track.get_producer()) break; } + m_trackIndex = trackIndex; // Iterate the consumers until found transition by mlt_service and track_b index. while (service && service->is_valid() && mlt_service_tractor_type != service->type()) { @@ -344,11 +363,3 @@ void TrackPropertiesWidget::onDuckFadeOutChanged(double value) ui->duckFadeOutSpinBox->setValue(value); ui->duckFadeOutSpinBox->blockSignals(false); } - -void TrackPropertiesWidget::refreshDuckStatus() -{ - QScopedPointer transition(getTransition("mix")); - if (transition && transition->is_valid()) { - updateDuckStatus(transition->get_double(MIX_PROPERTY_DUCK_LEVEL)); - } -} diff --git a/src/widgets/trackpropertieswidget.h b/src/widgets/trackpropertieswidget.h index 101de09cc5..417331af69 100644 --- a/src/widgets/trackpropertieswidget.h +++ b/src/widgets/trackpropertieswidget.h @@ -22,14 +22,15 @@ #include #include -class QTimer; - namespace Ui { class TrackPropertiesWidget; } namespace Mlt { class Transition; } +class SharedFrame; +class QShowEvent; +class QHideEvent; class TrackPropertiesWidget : public QWidget { @@ -53,7 +54,11 @@ private slots: void onDuckAttenuationChanged(double value); void onDuckFadeInChanged(double value); void onDuckFadeOutChanged(double value); - void refreshDuckStatus(); + void onShowFrame(const SharedFrame &frame); + +protected: + void showEvent(QShowEvent *event) override; + void hideEvent(QHideEvent *event) override; private: Mlt::Transition *getTransition(const QString &name); @@ -62,7 +67,7 @@ private slots: Ui::TrackPropertiesWidget *ui; Mlt::Producer m_track; - QTimer *m_duckStatusTimer; + int m_trackIndex; }; #endif // TRACKPROPERTIESWIDGET_H From 72ff75b4e4bd13c2558f424ef22eed2a233f27b8 Mon Sep 17 00:00:00 2001 From: Brian Matherly Date: Sat, 22 Aug 2026 11:07:36 -0500 Subject: [PATCH 2/3] Fix track audio level does not show ducking Track audio level is calculated by the audio level filter before the mix transition ducking is applied. This change subtracts the track ducking gain reduction --- src/models/multitrackmodel.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/models/multitrackmodel.cpp b/src/models/multitrackmodel.cpp index 3f039b17b7..67c52c6bac 100644 --- a/src/models/multitrackmodel.cpp +++ b/src/models/multitrackmodel.cpp @@ -749,7 +749,13 @@ void MultitrackModel::updateTrackAudioLevels(const SharedFrame &frame) = QStringLiteral("meta.track.%1.audio_level.1").arg(mltTrackIndex).toLatin1(); const double left = frame.get_double(leftKey.constData()); const double right = frame.get_double(rightKey.constData()); - const double audioLevel = audioLevelDbFromLinear(left, right); + double audioLevel = audioLevelDbFromLinear(left, right); + + // audiolevel measures before the mix transition applies ducking, so subtract it here. + const QByteArray duckKey = trackDuckLevelPrefix(mltTrackIndex) + "duck_level"; + const double duckReduction = frame.get_double(duckKey.constData()); + if (duckReduction > 0.0) + audioLevel = qMax(audioLevel - duckReduction, -100.0); setTrackAudioLevel(row, audioLevel); } From 89d019c6edca730994091d3e28fbf821e644a096 Mon Sep 17 00:00:00 2001 From: Brian Matherly Date: Sat, 22 Aug 2026 11:31:19 -0500 Subject: [PATCH 3/3] Do not process frames if ducking is not available on a track Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/widgets/trackpropertieswidget.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/widgets/trackpropertieswidget.cpp b/src/widgets/trackpropertieswidget.cpp index 41f3d79ce6..07b708c6db 100644 --- a/src/widgets/trackpropertieswidget.cpp +++ b/src/widgets/trackpropertieswidget.cpp @@ -187,6 +187,8 @@ void TrackPropertiesWidget::updateDuckStatus(double value) void TrackPropertiesWidget::showEvent(QShowEvent *event) { QWidget::showEvent(event); + if (!ui->duckingHeadingLabel->isVisible()) + return; connect(MLT.videoWidget(), SIGNAL(frameDisplayed(const SharedFrame &)), this, @@ -196,6 +198,8 @@ void TrackPropertiesWidget::showEvent(QShowEvent *event) void TrackPropertiesWidget::hideEvent(QHideEvent *event) { QWidget::hideEvent(event); + if (!ui->duckingHeadingLabel->isVisible()) + return; disconnect(MLT.videoWidget(), SIGNAL(frameDisplayed(const SharedFrame &)), this,