Implement Stem VU Meter Control Objects#15887
Conversation
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
|
Those vu-meters are really nice, I never thought about them. |
Thanks @Eve00000 . :)
You can refer to my reply to |
|
The current Mixxx VU meter is somewhere between a paek meter and a VU meter, |
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? |
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. |
|
Bumping for someone to test with a controller to ensure this works correctly. |
|
I can try to have a look over the weekend. Thanks for your contribution! |
acolombier
left a comment
There was a problem hiding this comment.
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: volumeSliderScreencast.From.2026-01-25.19-30-52.mp4
| // 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); |
There was a problem hiding this comment.
Is this FIXME still relevant?
There was a problem hiding this comment.
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.
Thanks! I haven't been working with QML yet as I am not that familiar with QML specifically (I am learning on the side). |
acolombier
left a comment
There was a problem hiding this comment.
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
JoergAtGithub
left a comment
There was a problem hiding this comment.
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(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 |
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.
|
Thank you! |
Description
This PR implements individual VU Meter Control Objects (COs) for Stems.
Key Changes
m_stemVuMetervector to storeEngineVuMeterinstances for each stem.EngineDeckconstructor with group names like[ChannelX_StemY].m_stemVuMeter[i]->process()intoEngineDeck::processStemto analyze the post-fader stem audio.stemcontrolobjecttest.cpp(to verify COs are created and emit values during playback.)Related Issues
Fixes #13699
Verification
Screen.Recording.2026-01-20.at.5.05.53.PM-2.mp4
m_pStem1VuMeter->get() > 0.0when active).