Skip to content

Implement Stem VU Meter Control Objects#15887

Merged
acolombier merged 3 commits into
mixxxdj:mainfrom
xARSENICx:vu-meter-cos
Jan 27, 2026
Merged

Implement Stem VU Meter Control Objects#15887
acolombier merged 3 commits into
mixxxdj:mainfrom
xARSENICx:vu-meter-cos

Conversation

@xARSENICx
Copy link
Copy Markdown
Contributor

Description

This PR implements individual VU Meter Control Objects (COs) for Stems.

Key Changes

  • EngineDeck: Added m_stemVuMeter vector to storeEngineVuMeter instances for each stem.
  • Initialization: Configured stem VU meters in EngineDeckconstructor with group names like [ChannelX_StemY].
  • Processing: Wired m_stemVuMeter[i]->process() into EngineDeck::processStem to analyze the post-fader stem audio.
  • Tests: Added regression tests in stemcontrolobjecttest.cpp(to verify COs are created and emit values during playback.)

Related Issues

Fixes #13699

Verification

  • Added temporary widget to Tango skin and verified VU meter responds to audio. (Reverted to original state in the PR)
Screen.Recording.2026-01-20.at.5.05.53.PM-2.mp4
  • Verified Mute button stops the VU meter visual (functionally working).
  • Unit tests pass (m_pStem1VuMeter->get() > 0.0 when active).

Adds m_stemVuMeter to EngineDeck to calculate and emit VU meter values
for each stem.
Adds unit tests in StemControlFixture (VuMeter) to verify connectivity.
Manual verification confirmed visual VU meter response in Tango skin
(skin changes reverted).

Fixes mixxxdj#13699
@Eve00000
Copy link
Copy Markdown
Contributor

Those vu-meters are really nice, I never thought about them.
How accurate are they?

Comment thread src/engine/channels/enginedeck.cpp
@xARSENICx
Copy link
Copy Markdown
Contributor Author

xARSENICx commented Jan 21, 2026

Those vu-meters are really nice, I never thought about them.

Thanks @Eve00000 . :)

How accurate are they?

You can refer to my reply to @JoergAtGithub above. The Stem COs just inherit the behaviour of a vu-meter. So, all the math involved in attack/decay of a vu-meter govern the Stem COs too. If you want I can give you a quick heads-up as to how they behave as I went through the implementation thoroughly for creation of Stem COs

@daschuer
Copy link
Copy Markdown
Member

The current Mixxx VU meter is somewhere between a paek meter and a VU meter,
The are currently designed to look good. Not to be extra precise.
They suites for DJ-ing, but do not compare to pro broadcasting gear.
See:
#7888

@xARSENICx
Copy link
Copy Markdown
Contributor Author

xARSENICx commented Jan 21, 2026

The current Mixxx VU meter is somewhere between a paek meter and a VU meter,

The are currently designed to look good. Not to be extra precise.

That is true. Thanks for the referencing issue, I wasn't aware of it's existence although I did come across this behaviour while going through the implementation. Correct me if I am wrong, but with an actual VU meter, the attack/decay time is in ~300 ms which would create an apparent lag in the LED indicator while the mixxx VU just sits right around ~10ms.

So for our stem COs, shipping with mixxx VU seems good enough to me. Wdyt?

@JoergAtGithub
Copy link
Copy Markdown
Member

So for our stem COs, shipping with mixxx VU seems good enough to me. Wdyt?

Yes, as all VU-Meters should behave the same. There are several different scaling standards for such meters. But implementing these is out of scope here.

@xARSENICx
Copy link
Copy Markdown
Contributor Author

Bumping for someone to test with a controller to ensure this works correctly.

@acolombier
Copy link
Copy Markdown
Member

I can try to have a look over the weekend. Thanks for your contribution!

Copy link
Copy Markdown
Member

@acolombier acolombier left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested and works as expected!

Tried QML as well with the following patch

diff --git a/res/qml/Deck/HotcueAndStem.qml b/res/qml/Deck/HotcueAndStem.qml
index 881c42af54..ee8b949c0d 100644
--- a/res/qml/Deck/HotcueAndStem.qml
+++ b/res/qml/Deck/HotcueAndStem.qml
@@ -401,23 +401,23 @@ Item {
                                 top: parent.top
                             }
 
-                            // Skin.VuMeter {
-                            //     x: 15
-                            //     y: (parent.height - height) / 2
-                            //     width: 4
-                            //     height: parent.height - 40
-                            //     group: root.group
-                            //     key: "vu_meter_left"
-                            // }
+                            Skin.VuMeter {
+                                x: 11
+                                y: (parent.height - height) / 2
+                                width: 3
+                                height: parent.height - 10
+                                group: stem.group
+                                key: "vu_meter_left"
+                            }
 
-                            // Skin.VuMeter {
-                            //     x: parent.width - width - 15
-                            //     y: (parent.height - height) / 2
-                            //     width: 4
-                            //     height: parent.height - 40
-                            //     group: root.group
-                            //     key: "vu_meter_right"
-                            // }
+                            Skin.VuMeter {
+                                x: parent.width - width - 11
+                                y: (parent.height - height) / 2
+                                width: 3
+                                height: parent.height - 10
+                                group: stem.group
+                                key: "vu_meter_right"
+                            }
 
                             Skin.ControlFader {
                                 id: volumeSlider
Screencast.From.2026-01-25.19-30-52.mp4

Comment thread src/test/stemcontrolobjecttest.cpp Outdated
Comment on lines +373 to +378
// FIXME: This assertion fails in unit test (value stays 1.0) but passed manual verification.
// Likely due to test harness buffer handling or settling time.
// EXPECT_NEAR(m_pStem1VuMeter->get(), 0.0, 0.001);

// Stem 2 should still be playing
// EXPECT_GT(m_pStem2VuMeter->get(), 0.0);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this FIXME still relevant?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, the unit test that I tried to write to test the mute button was not working. (so, by manual testing here I meant the decay fall of vu-meter when a stem is muted in the temporary GUI I made locally- it was working flawlessly.)
Although since then, I did fix this. I will commit the changes.

@xARSENICx
Copy link
Copy Markdown
Contributor Author

Tried QML as well with the following patch

Thanks! I haven't been working with QML yet as I am not that familiar with QML specifically (I am learning on the side).

Copy link
Copy Markdown
Member

@acolombier acolombier left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! Could you please prepare a manual PR with the newly introduce COs?

Here is an example on how to do so: mixxxdj/manual#644

Copy link
Copy Markdown
Member

@JoergAtGithub JoergAtGithub left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The existing vu_meter COs have deprecated names like VuMeter. This PR adds not only the new naming for the stem vu_meter COs, but also the deprecated names.

@xARSENICx
Copy link
Copy Markdown
Contributor Author

The existing vu_meter COs have deprecated names like VuMeter. This PR adds not only the new naming for the stem vu_meter COs, but also the deprecated names.

Will refractoring EngineVuMeter.h and EngineVuMeter.cpp to optionally skip creating the legacy aliases (e.g., VuMeter, VuMeterL/R) via a new constructor argument work? Basically, initializing the Stem VU meters with this flag set to false, so they only expose the new vu_meter controls as requested. Does this approach align with your expectations?

EngineVuMeter(const QString& group, const QString& legacyGroup = QString(), bool createAliases = true);

allowing

EngineVuMeter::EngineVuMeter(const QString& group, const QString& legacyGroup, bool createAliases)
        : m_vuMeter(ConfigKey(group, QStringLiteral("vu_meter"))),
          m_vuMeterLeft(ConfigKey(group, QStringLiteral("vu_meter_left"))),
          m_vuMeterRight(ConfigKey(group, QStringLiteral("vu_meter_right"))),
          m_peakIndicator(ConfigKey(group, QStringLiteral("peak_indicator"))),
          m_peakIndicatorLeft(ConfigKey(group, QStringLiteral("peak_indicator_left"))),
          m_peakIndicatorRight(ConfigKey(group, QStringLiteral("peak_indicator_right"))),
          m_sampleRate(QStringLiteral("[App]"), QStringLiteral("samplerate")) {
    if (createAliases) {
        const QString& aliasGroup = legacyGroup.isEmpty() ? group : legacyGroup;
        m_vuMeter.addAlias(ConfigKey(aliasGroup, QStringLiteral("VuMeter")));
        m_vuMeterLeft.addAlias(ConfigKey(aliasGroup, QStringLiteral("VuMeterL")));
        m_vuMeterRight.addAlias(ConfigKey(aliasGroup, QStringLiteral("VuMeterR")));
        m_peakIndicator.addAlias(ConfigKey(aliasGroup, QStringLiteral("PeakIndicator")));
        m_peakIndicatorLeft.addAlias(ConfigKey(aliasGroup, QStringLiteral("PeakIndicatorL")));
        m_peakIndicatorRight.addAlias(ConfigKey(aliasGroup, QStringLiteral("PeakIndicatorR")));
    }

So, for stems we do

m_stemVuMeter.emplace_back(std::make_unique<EngineVuMeter>(
                getGroupForStem(getGroup(), stemIdx), QString(), false));

This way other stuff relying on EngineVuMeter wouldn't require a re-write either. LMK if this sounds good or am I complicating it.

@JoergAtGithub
Copy link
Copy Markdown
Member

Will refractoring EngineVuMeter.h and EngineVuMeter.cpp to optionally skip creating the legacy aliases (e.g., VuMeter, VuMeterL/R) via a new constructor argument work?

Yes, it will! I would name the argument createLegacyAliases

Avoid creating legacy ControlObject aliases (e.g. VuMeter, VuMeterL) for
Stem VU meters, as requested during review.
- Added createLegacyAliases flag to EngineVuMeter constructor (default true).
- Disabled this flag for Stem VU meters in EngineDeck.
@acolombier acolombier merged commit 4452cd2 into mixxxdj:main Jan 27, 2026
15 checks passed
@acolombier
Copy link
Copy Markdown
Member

Thank you!

@acolombier acolombier modified the milestones: 2.6.0, 2.7-beta Jan 27, 2026
@xARSENICx xARSENICx deleted the vu-meter-cos branch January 27, 2026 23:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add VU-Meter COs for STEMs

5 participants