Skip to content

Fix Mixer volume/pan and Solo state resetting after Solo toggle + playback - #34676

Open
tharos-devs wants to merge 1 commit into
musescore:mainfrom
tharos-devs:fix/mixer-volume-solo-reset
Open

Fix Mixer volume/pan and Solo state resetting after Solo toggle + playback#34676
tharos-devs wants to merge 1 commit into
musescore:mainfrom
tharos-devs:fix/mixer-volume-solo-reset

Conversation

@tharos-devs

Copy link
Copy Markdown

Resolves: #34673

Problem

Two related regressions in the Mixer, both triggered by the same reproduction steps:

  1. Manual volume/pan resets to 0 dB. If you manually lower an instrument's volume (or change its pan) in the Mixer, then enable Solo on any track and play back, the manually-set value silently reverts to 0 dB (unity gain) — even without ever touching an automation curve.
  2. The Solo button itself reverts to unchecked. Enable Solo on a track, play back a few measures, stop, and reopen (or just re-check) the Mixer: the Solo checkbox is unchecked again, even though solo/mute playback behavior and the underlying notation state are unaffected.

Steps to reproduce (both bugs)

  1. Open the Mixer.
  2. (For bug 1) Lower an instrument's volume, e.g. to -6 dB.
  3. Enable Solo on a track.
  4. Start playback for a measure or two, then stop.
  5. Look at the Mixer again: the volume has reset to 0 dB, and/or the Solo button is unchecked.

Bug 2 reproduces on its own too — no volume change is needed, just Solo + playback.

Root causes

These are two independent bugs in the same area, both stemming from the same architectural mistake: reading a field from an AudioOutputParams snapshot that isn't actually the authoritative source for that field.

Bug 1 — stale automated-control-params cache overrides manual volume/pan

PlaybackController::trackControlParams() caches the last computed ControlParams per track in m_automatedControlParamsCache, so that repeated calls (e.g. from updateSoloMuteStates(), with rebuildVolume = rebuildPan = false) don't need to rebuild the automation envelope each time. The problem: the cached volume/balance were reapplied unconditionally, whether or not that track actually has an active automation curve.

This cache gets seeded for every track the first time resendAutomatedControlParams() runs (e.g. on project load, since it's wired to totalPlayTimeChanged), even when no automation exists — in that case it just stores a flat snapshot of whatever outParams.volume/balance was at that moment (typically 0 dB / centered pan, the defaults).

Sequence: user changes volume or pan in the Mixer → sent straight to the audio engine via setControlParams, bypassing trackControlParams()/the cache entirely → toggling Solo calls updateSoloMuteStates(), which calls trackControlParams(id, params, /*rebuildVolume*/ false, /*rebuildPan*/ false) → the stale cache entry (from load time) unconditionally overwrites control.volume/control.balance, and that stale value gets sent to the engine, audibly resetting the value.

Fix: only reuse the cached value when the track actually has a non-empty Volume/Pan automation curve right now. Otherwise, control keeps the volume/balance from the live outParams passed in.

Bug 2 — Solo/Mute checkbox reset from a source that never tracks them

MixerChannelItem::loadOutputParams() was also copying solo/muted/forceMute from whatever AudioOutputParams it was given. Several code paths call it with an AudioOutputParams fetched from IProjectAudioSettings (trackOutputParams() / auxOutputParams() / masterAudioOutputParams()) — but solo/mute state is never written into that store. It's owned by INotationSoloMuteState (instrument tracks) or IProjectAudioSettings::auxSoloMuteState() (aux tracks), and forceMute is computed live in PlaybackController::updateSoloMuteStates() and sent directly to the engine — neither ever round-trips through IProjectAudioSettings::AudioOutputParams, so that struct's solo/muted/forceMute fields are always at their default of false.

Sequence: user clicks Solo → MixerChannelItem::setSolo(true) correctly sets the notation-level state and the item's own display state → but the async promise from playback()->params(trackId) used to (re)build the channel item (MixerPanelModel::buildInstrumentChannelItem) resolves afterwards and calls loadOutputParams() with an AudioOutputParams freshly fetched from IProjectAudioSettings — whose solo field is false — clobbering the checkbox back to unchecked. The same pattern affects the fxChainParamsChanged reload path and the aux-channel build path.

Confirmed via targeted LOGD tracing:

MixerChannelItem::loadSoloMuteState | trackId: 2, oldSolo: 0, newSolo: 1   <- correct, from notation state
MixerChannelItem::loadOutputParams  | trackId: 2, oldSolo: 1, newSolo: 0   <- clobbered right after

Fix: loadOutputParams() no longer touches solo/muted/forceMute at all — those fields have their own dedicated, correct update path (loadSoloMuteState(), and updateSoloMuteStates() for the live force-mute computation) and should never be sourced from IProjectAudioSettings.

Additional fix needed to make bug 1's fix effective

Independently of the cache issue, MixerPanelModel's controlParamsChanged handlers (instrument tracks, aux tracks, and master) never persisted manual volume/balance edits back to IProjectAudioSettings — only the live audio engine got the new value via setControlParams. So even after fixing the cache bug above, any later rebuild of a track's output params (e.g. from IProjectAudioSettings::trackOutputParams()) would still read the old, never-updated volume/balance. These handlers now persist volume/balance back to IProjectAudioSettings — deliberately not including solo/muted/forceMute, per the bug 2 root cause above.

Testing

  • Manually built and tested locally (macOS).

  • Reproduced both bugs before the fix, confirmed both are resolved after, using several instruments and multiple simultaneous Solo toggles.

  • Manually re-verified the volume fix; the pan/balance fix follows the exact same code path (both bugs handle volume and balance symmetrically) but wasn't separately re-verified by ear.

  • Verified with debug logging (-d flag) that the Solo state is now correctly preserved through the Mixer's channel-item rebuild path.

  • Ran checkcodestyle.cmake against src/ — no style issues in the changed files.

  • I signed the CLA as tharosd

  • The title of the PR describes the problem it addresses.

  • Each commit's message describes its purpose and effects, and references the issue it resolves. If changes are extensive, there is a sequence of easily reviewable commits.

  • The code in the PR follows the coding rules.

  • I understand all aspects of the code I'm contributing and I'm able to explain it if requested.

  • The code compiles and runs on my machine, preferably after each commit individually. I have manually tested and verified that my changes fulfil their intended purpose.

  • No prior attempts to resolve this problem exist, or if they do, I listed them in my PR description and described how I avoided repeating past mistakes.

  • There are no unnecessary changes.

  • I created a unit test or vtest to verify the changes I made (if applicable).

Manually adjusted volume/balance in the Mixer would revert to 0 dB
after toggling Solo and playing back, and the Solo button itself
could revert to unchecked.

Root causes:
- PlaybackController::trackControlParams() reused a cached automated
  control value (volume/balance) even when no automation curve was
  actually active for the track, so a stale snapshot taken at load
  time would clobber manual Mixer edits on the next solo/mute-driven
  resend.
- MixerPanelModel's controlParamsChanged handlers never persisted
  manual volume/balance edits to IProjectAudioSettings, so any later
  reload of that track's output params would revert to the old value.
- MixerChannelItem::loadOutputParams() also applied solo/muted/
  forceMute from AudioOutputParams fetched via IProjectAudioSettings,
  which never actually tracks those fields (they are owned by
  INotationSoloMuteState / live force-mute computation), so it would
  reset the Solo/Mute checkboxes back to their default of false.

Fixes musescore#34673.
@coderabbitai

coderabbitai Bot commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: e67c9544-985b-4324-8a98-b9782c2349c9

📥 Commits

Reviewing files that changed from the base of the PR and between 5decb27 and 41fb9ae.

📒 Files selected for processing (3)
  • src/playback/internal/playbackcontroller.cpp
  • src/playback/qml/MuseScore/Playback/mixerchannelitem.cpp
  • src/playback/qml/MuseScore/Playback/mixerpanelmodel.cpp

Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review.


📝 Walkthrough

Walkthrough

The changes restrict cached volume and pan restoration to matching automation curves on valid instrument tracks. Mixer output loading no longer changes solo, mute, or force-mute state. Instrument, auxiliary, and master channel volume and balance changes now persist to their respective project audio settings while continuing to update playback controls.

Merge Risk: ⚪ Minimal · up to 41fb9

This change preserves Mixer volume, pan, and Solo state across playback and channel refreshes. No actionable merge-blocking risk remains after normal checks and review.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the Mixer volume, pan, and Solo state reset issues addressed by the pull request.
Description check ✅ Passed The description explains the regressions, root causes, fixes, testing, issue reference, and checklist status in sufficient detail.
Docstring Coverage ✅ Passed Docstring check was indeterminate for this PR — some files could not be analyzed in time. Not blocking.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

Review ran into problems

🔥 Problems

Linked repositories: Public OSS repositories can only analyze public repositories installed in this organization. No linked repositories were analyzed; skipped musescore/muse_framework.git.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Mixer: manual volume resets to 0 dB after enabling Solo and playing back

3 participants