Skip to content

Conversation

@Warchamp7
Copy link
Member

Description

Moves a bunch of logic out of OBSBasic into a central AudioMixer class, refactors a bunch of code to clean up, and adds a pile of new functionality to the mixer.

Adds a new MenuCheckbox widget for having a checkbox toggle inside a QMenu that does not close the menu after clicking.

The primary addition in this PR is a new audio monitoring toggle button in the mixer itself. Other features have also been added such as the ability to show Hidden sources, Inactive sources, adjust how they get sorted, and an easier way to toggle the layout.

image

PR also depends on #12734 for icon coloring.

Motivation and Context

With the fixes made as part of #12175, it is now far more reasonable for us to expose audio monitoring more directly to users. It is often very desirable to be able to toggle on monitoring for things like Media Sources or some Video Devices such as capture cards.

I elected to also clean up the Audio Mixer code a bunch while working on this feature addition, and add some other additional features and UI adjustments.

There is more that I want to do and clean up still but I'm trying to keep this PR at least moderately reasonable.

How Has This Been Tested?

Tested muting and monitoring on multiple audio sources both via the Mixer dock, the Advanced Audio Properties window, as well as a separate Audio Mixer window utilizing the same widget, to test that things are properly encapsulated and not kept in any local widget state.

Types of changes

  • New feature (non-breaking change which adds functionality)
  • Code cleanup (non-breaking change which makes code smaller or more readable)

Checklist:

  • My code has been run through clang-format.
  • I have read the contributing document.
  • My code is not on the master branch.
  • The code has been tested.
  • All commit messages are properly formatted and commits squashed where appropriate.
  • I have included updates to all appropriate documentation.

@Warchamp7 Warchamp7 added Enhancement Improvement to existing functionality Seeking Testers Build artifacts on CI New Feature New feature or plugin UI/UX Anything to do with changes or additions to UI/UX elements. labels Oct 15, 2025
@Warchamp7 Warchamp7 force-pushed the audio-mixer-updates branch 8 times, most recently from 7e94625 to e9f1da9 Compare October 16, 2025 01:15
@gbdlin
Copy link

gbdlin commented Oct 18, 2025

Small request for this redesign, if it's feasible and easy enough: can you add one more checkbox to the menu to show sources that are inactive, but present on the previewed screen? Of course it only makes sense in the studio mode, but it would be a great help when you need to adjust levels of a source that will become active with next transition, but without having to find it in the list of all available sources.

@Warchamp7 Warchamp7 force-pushed the audio-mixer-updates branch 8 times, most recently from 895cced to 95167b7 Compare October 29, 2025 02:49
@Warchamp7
Copy link
Member Author

Small request for this redesign, if it's feasible and easy enough: can you add one more checkbox to the menu to show sources that are inactive, but present on the previewed screen? Of course it only makes sense in the studio mode, but it would be a great help when you need to adjust levels of a source that will become active with next transition, but without having to find it in the list of all available sources.

This was in fact not straightforward but I added it as default behaviour

image

@nyanpasu64
Copy link

This PR causes ugly pixelated rendering of VolumeMeter text on Linux at 125% DPI scale (and probably Windows as well).

Screenshot_20251101_011534-OBS 32 0 2-7-g1e346fdf9 - Profile: Untitled - Scenes: Untitled

I think you're rendering the text off-screen (backgroundCache?) and blitting on-screen. Unfortunately this may not be easily fixable, because Qt 6 lays out widgets in integer virtual pixels (unless you set a rounding policy, which may cause layout errors when changing DPI at runtime), and you don't know how many physical pixels the QImage/Pixmap will appear on-screen due to position-dependent rounding.

Looking at the QPixmap docs, I found https://doc.qt.io/qt-6/qpainter.html#drawing-high-resolution-versions-of-pixmaps-and-images:

High resolution versions of pixmaps have a device pixel ratio value larger than 1 (see QImageReader, QPixmap::devicePixelRatio()). Should it match the value of the underlying QPaintDevice, it is drawn directly onto the device with no additional transformation applied.

One interesting possible strategy is "multiply size by DPI, round to nearest pixel, then set the QPixmap DPR to the screen, then blit to the widget". If you can convince Qt to not scale it at all (haven't tested), you could get exact rendering, possibly with one pixel cut off or missing at the edges :(

Still waiting for Qt to add https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserverEntry/devicePixelContentBoxSize 🦗 VS Code faced a similar problem with their terminal at xtermjs/xterm.js#2662 (comment) and xtermjs/xterm.js#3926

@Warchamp7
Copy link
Member Author

This PR causes ugly pixelated rendering of VolumeMeter text on Linux at 125% DPI scale (and probably Windows as well).

Thanks for catching this. I'm travelling this week but hopefully the fix will be as simple as adjusting the Pixmap size to be

size() * devicePixelRatioF()) instead of just size()

@Warchamp7 Warchamp7 marked this pull request as ready for review December 4, 2025 17:02
@Warchamp7 Warchamp7 force-pushed the audio-mixer-updates branch from 81ec88b to 68e30cd Compare December 4, 2025 17:04
@noah9444
Copy link

noah9444 commented Dec 4, 2025

I’m using HTML in the source names, but it’s no longer being reflected in the audio mixer.

Example:
<font color="#ffaaff">test</font>

This PR
mixer-broken

Version 32.0.2
mixer-32

I learned how to add color to source names from this video:
https://www.youtube.com/watch?v=nh0dhJR8P3g

Copy link
Member

@gxalpha gxalpha left a comment

Choose a reason for hiding this comment

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

things I noticed when scrolling through. Definitely make sure to revert the obs-browser changes.

@Warchamp7
Copy link
Member Author

things I noticed when scrolling through. Definitely make sure to revert the obs-browser changes.

God I hate that rebasing does that

@Warchamp7 Warchamp7 force-pushed the audio-mixer-updates branch from 68e30cd to b29ffee Compare December 5, 2025 18:59
Copy link
Member

@PatTheMav PatTheMav left a comment

Choose a reason for hiding this comment

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

First round of review, will continue over the next few days.

@Warchamp7 Warchamp7 force-pushed the audio-mixer-updates branch 5 times, most recently from c2a7677 to 5f23356 Compare December 6, 2025 17:41
@Warchamp7 Warchamp7 force-pushed the audio-mixer-updates branch 3 times, most recently from 8cf2d01 to 76fd9ae Compare December 18, 2025 01:36
@Warchamp7 Warchamp7 moved this from Backlog to Ready For Review in OBS Studio 32.1 PR Considerations Jan 6, 2026
@Warchamp7 Warchamp7 force-pushed the audio-mixer-updates branch from f8e40bd to de49c0c Compare January 6, 2026 22:53
@Warchamp7 Warchamp7 force-pushed the audio-mixer-updates branch from de49c0c to 99e9a12 Compare January 6, 2026 22:56
Copy link
Member

@PatTheMav PatTheMav left a comment

Choose a reason for hiding this comment

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

During testing I found that there are two ways to toggle the display of hidden sources: Either via the Options menu or via the hidden source counter.

That itself is a bit of a UX smell, because there should be a single, canonical, way to do this, not multiple ways. But because there are multiple ways, this creates a bidirectional state binding between both of these controls and at least in my tests, toggling the display of hidden sources via the counter label does not also uncheck the checkbox in the "Options" submenu, even though it should.

So either this binding needs to be made truly bidirectional, or the duplication in functionality is resolved.


Also the on_actionMixerToolbarMenu_triggered function never seems to run in my tests, even though I get the context menu both by clicking on the "Options" button or right-clicking into the mixer area. How is this menu triggered?

Similarly debugHideControls exists, but doesn't seem to be hooked up to anything?

}
};

QMetaObject::invokeMethod(App(), "Exec", Qt::QueuedConnection, Q_ARG(VoidFunc, msgBox));
Copy link
Member

Choose a reason for hiding this comment

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

Does this execute exec recursively, effectively creating 2 nested event loops just for a message box, or does Exec do something else (hence the capitalisation)?

In general, use of exec is a big code smell in Qt (they discourage its use explicitly) and we should move away from it rather than double down on its use.


obs_fader_attach_source(obs_fader, source);

/* Call volume changed once to init the slider position and label */
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
/* Call volume changed once to init the slider position and label */
// Call volume changed once to init the slider position and label

buttonLayout->setContentsMargins(0, 0, 0, 0);
buttonLayout->setSpacing(0);

//buttonLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding));
Copy link
Member

Choose a reason for hiding this comment

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

Refactor or remove.

obs_source_set_muted(source, mute);

if (!mute && unassigned) {
// muteButton->setChecked(true);
Copy link
Member

Choose a reason for hiding this comment

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

Refactor or remove.


if (!mute && unassigned) {
// muteButton->setChecked(true);
/* Show notice about the source no being assigned to any tracks */
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
/* Show notice about the source no being assigned to any tracks */
// Show notice about the source no being assigned to any tracks

Comment on lines +87 to +88
float levelTotal;
float levelCount;
Copy link
Member

Choose a reason for hiding this comment

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

According to clang these two are unused and should be removed (dead code).

return;
}

auto getPreviewSources = [this](obs_scene_t *, obs_sceneitem_t *item) /* -- */
Copy link
Member

Choose a reason for hiding this comment

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

What's the purpose of the comment at the end of the line?


using getPreviewSources_t = decltype(getPreviewSources);

auto previewEnum = [](obs_scene_t *scene, obs_sceneitem_t *item, void *data) -> bool /* -- */
Copy link
Member

Choose a reason for hiding this comment

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

What's the purpose of the comment at the end of the line?


void AudioMixer::obsSourceActivated(void *data, calldata_t *params)
{
obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source");
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source");
obs_source_t *source = static_cast<obs_source_t *>(calldata_ptr(params, "source"));

Don't use unsafe C-style casts in C++ code, always use C++ cast operations.

Applies to all C-style casts in this file.


#include "moc_AudioMixer.cpp"

static constexpr int GLOBAL_SOURCE_TOTAL = 6;
Copy link
Member

Choose a reason for hiding this comment

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

The static qualifier doesn't make much sense here: The variable is defined in the global scope, which gives it static storage duration by default, but it's also qualified as constexpr which makes it implicitly const and thus has internal linkage by default.

See also https://www.learncpp.com/cpp-tutorial/scope-duration-and-linkage-summary/.


void VolumeControl::renameSource()
{
QAction *action = reinterpret_cast<QAction *>(sender());
Copy link
Member

Choose a reason for hiding this comment

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

I'm afraid that I kinda know the answer already, but: Is it really necessary for the VolumeControl class to take care of all these arcana related to renaming a source? Isn't this code implemented somewhere else already (e.g. the source dock), meaning that if something changes about the process to rename a source that we need to remember that the VolumeControl also renames sources and thus needs to be updated as well?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Enhancement Improvement to existing functionality New Feature New feature or plugin Seeking Testers Build artifacts on CI UI/UX Anything to do with changes or additions to UI/UX elements.

Projects

Status: Ready For Review

Development

Successfully merging this pull request may close these issues.

7 participants