Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/app/configs/data/shortcuts.xml
Original file line number Diff line number Diff line change
Expand Up @@ -819,6 +819,11 @@
<key>notation-select-all</key>
<seq>Ctrl+A</seq>
</SC>
<SC>
<key>reset-mixer</key>
<seq>Ctrl+Shift+R</seq>
<autorepeat>0</autorepeat>
</SC>
<SC>
<key>system-break</key>
<seq>Return</seq>
Expand Down
80 changes: 80 additions & 0 deletions src/playback/internal/playbackcontroller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ static const ActionCode REPEAT_CODE("repeat");
static const ActionCode PLAY_CHORD_SYMBOLS_CODE("play-chord-symbols");
static const ActionCode PLAYBACK_SETUP("playback-setup");
static const ActionCode TOGGLE_HEAR_PLAYBACK_WHEN_EDITING_CODE("toggle-hear-playback-when-editing");
static const ActionCode RESET_MIXER_CODE("reset-mixer");

static AudioOutputParams makeReverbOutputParams()
{
Expand Down Expand Up @@ -127,6 +128,7 @@ void PlaybackController::init()
dispatcher()->reg(this, PLAYBACK_SETUP, this, &PlaybackController::openPlaybackSetupDialog);
dispatcher()->reg(this, TOGGLE_HEAR_PLAYBACK_WHEN_EDITING_CODE, this, &PlaybackController::toggleHearPlaybackWhenEditing);
dispatcher()->reg(this, "playback-reload-cache", this, &PlaybackController::reloadPlaybackCache);
dispatcher()->reg(this, RESET_MIXER_CODE, this, &PlaybackController::resetMixerToDefaults);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Route the shortcut through the same confirmation gate.

Line 131 binds reset-mixer directly to resetMixerToDefaults(). With the new Ctrl+Shift+R shortcut, that makes the destructive reset execute immediately, while the confirmation in this PR exists only in the button/QML flow. The shortcut needs the same guard or it can wipe mixer state in one keystroke.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/playback/internal/playbackcontroller.cpp` at line 131, The shortcut
registration currently calls resetMixerToDefaults directly via
dispatcher()->reg(..., RESET_MIXER_CODE, ...,
&PlaybackController::resetMixerToDefaults); change it to route through the same
confirmation gate used by the button/QML flow: bind RESET_MIXER_CODE to a new or
existing handler (e.g., PlaybackController::requestResetMixerConfirmation or the
exact method the QML button uses) that shows the confirmation dialog and only
calls PlaybackController::resetMixerToDefaults after the user confirms; ensure
the dispatcher()->reg uses that confirmation handler instead of
resetMixerToDefaults so the keyboard shortcut cannot bypass confirmation.


m_onlineSoundsController->regActions();

Expand Down Expand Up @@ -956,6 +958,84 @@ void PlaybackController::reloadPlaybackCache()
}
}

void PlaybackController::resetMixerToDefaults()
{
IF_ASSERT_FAILED(audioSettings() && notationPlayback()) {
return;
}

InstrumentTrackIdSet existingTrackIdSet = notationPlayback()->existingTrackIdSet();

for (const InstrumentTrackId& instrumentTrackId : existingTrackIdSet) {
if (!muse::contains(m_instrumentTrackIdMap, instrumentTrackId)) {
continue;
}

AudioOutputParams outParams = trackOutputParams(instrumentTrackId);
const bool wasMuted = outParams.muted;
const bool wasForceMute = outParams.forceMute;

outParams.volume = 0.f;
outParams.balance = 0.f;

const AudioInputParams inParams = audioSettings()->trackInputParams(instrumentTrackId);
const AudioSourceType sourceType = inParams.isValid() ? inParams.type() : AudioSourceType::Fluid;
const muse::String instrumentSoundId = inParams.resourceMeta.attributeVal(PLAYBACK_SETUP_DATA_ATTRIBUTE);

const bool isMetronome = instrumentTrackId == notationPlayback()->metronomeTrackId();

if (isMetronome) {
outParams.muted = wasMuted;
} else {
const auto soloMuteState = trackSoloMuteState(instrumentTrackId);
outParams.solo = soloMuteState.solo;
outParams.muted = soloMuteState.mute;
}
outParams.forceMute = wasForceMute;

if (!isMetronome) {
if (outParams.auxSends.empty()) {
const auto& auxMap = m_auxTrackIdMap;
for (aux_channel_idx_t idx = 0; idx < static_cast<aux_channel_idx_t>(auxMap.size()); ++idx) {
gain_t signalAmount = configuration()->defaultAuxSendValue(idx, sourceType, instrumentSoundId);
outParams.auxSends.emplace_back(AuxSendParams { signalAmount, true });
}
} else {
for (aux_channel_idx_t idx = 0; idx < static_cast<aux_channel_idx_t>(outParams.auxSends.size()); ++idx) {
gain_t signalAmount = configuration()->defaultAuxSendValue(idx, sourceType, instrumentSoundId);
outParams.auxSends[idx] = AuxSendParams { signalAmount, true };
}
}
}

audioSettings()->setTrackOutputParams(instrumentTrackId, outParams);

audio::TrackId trackId = m_instrumentTrackIdMap.at(instrumentTrackId);
playback()->setControlParams(trackId, outParams.control());
playback()->setAuxSendsParams(trackId, outParams.auxSends);
}

AudioOutputParams masterParams = audioSettings()->masterAudioOutputParams();
const bool masterWasMuted = masterParams.muted;
const bool masterWasForceMute = masterParams.forceMute;

masterParams.volume = 0.f;
masterParams.balance = 0.f;

masterParams.muted = masterWasMuted;
masterParams.forceMute = masterWasForceMute;

audioSettings()->setMasterAudioOutputParams(masterParams);
playback()->setMasterControlParams(masterParams.control());

m_mixerResetRequested.notify();
}
Comment on lines +961 to +1032

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reset the aux/reverb outputs, not just track sends.

Lines 996-1029 rebuild per-track sends and master control params, but they never restore audioSettings()->auxOutputParams(...) or push default aux-track params back into playback. As written, edits made on the aux/reverb channels themselves survive the reset, so this does not fully return the mixer to the "New-Score" defaults described in the PR objective.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/playback/internal/playbackcontroller.cpp` around lines 961 - 1033, The
resetMixerToDefaults must also restore aux/reverb channel outputs; update
PlaybackController::resetMixerToDefaults to iterate m_auxTrackIdMap (or the aux
IDs from audioSettings()/m_auxTrackIdMap), build default AuxOutputParams (reset
volume/balance/mute/forceMute and set default send/return values via
configuration() or the same defaults used for tracks), call
audioSettings()->setAuxOutputParams(auxInstrumentTrackId, auxParams) for each
aux and push those to playback via playback()->setAuxControlParams(auxTrackId,
auxParams.control()) (and any equivalent playback API to set aux return/reverb
params). Ensure you reference AuxOutputParams, m_auxTrackIdMap,
audioSettings()->auxOutputParams / setAuxOutputParams, configuration() defaults,
and playback()->setAuxControlParams when making the changes.


muse::async::Notification PlaybackController::mixerResetRequested() const
{
return m_mixerResetRequested;
}

void PlaybackController::openPlaybackSetupDialog()
{
interactive()->open("musescore://playback/soundprofilesdialog");
Expand Down
5 changes: 5 additions & 0 deletions src/playback/internal/playbackcontroller.h
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,8 @@ class PlaybackController : public IPlaybackController, public muse::actions::Act
muse::async::Notification onlineSoundsChanged() const override;
muse::Progress onlineSoundsProcessingProgress() const override;

muse::async::Notification mixerResetRequested() const override;

private:
muse::audio::IPlayerPtr currentPlayer() const;

Expand Down Expand Up @@ -215,6 +217,8 @@ class PlaybackController : public IPlaybackController, public muse::actions::Act
void updateSoloMuteStates();
void updateAuxMuteStates();

void resetMixerToDefaults();

using TrackAddFinished = std::function<void ()>;

void addTrack(const engraving::InstrumentTrackId& instrumentTrackId, const TrackAddFinished& onFinished);
Expand All @@ -239,6 +243,7 @@ class PlaybackController : public IPlaybackController, public muse::actions::Act
muse::async::Notification m_isPlayingChanged;
muse::async::Notification m_totalPlayTimeChanged;
muse::async::Notification m_currentTempoChanged;
muse::async::Notification m_mixerResetRequested;
muse::async::Channel<muse::audio::secs_t, muse::midi::tick_t> m_currentPlaybackPositionChanged;
muse::async::Channel<muse::actions::ActionCode> m_actionCheckedChanged;

Expand Down
9 changes: 9 additions & 0 deletions src/playback/internal/playbackuiactions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,14 @@ const UiActionList PlaybackUiActions::s_diagnosticActions = {
)
};

const UiActionList PlaybackUiActions::s_mixerActions = {
UiAction("reset-mixer",
mu::context::UiCtxAny,
mu::context::CTX_ANY,
TranslatableString("action", "Reset mixer to default settings")
)
};

const UiActionList PlaybackUiActions::s_onlineSoundsActions = {
UiAction(CLEAR_ONLINE_SOUNDS_CACHE_CODE,
mu::context::UiCtxAny,
Expand Down Expand Up @@ -264,6 +272,7 @@ const UiActionList& PlaybackUiActions::actionsList() const
alist.insert(alist.end(), s_settingsActions.cbegin(), s_settingsActions.cend());
alist.insert(alist.end(), s_loopBoundaryActions.cbegin(), s_loopBoundaryActions.cend());
alist.insert(alist.end(), s_diagnosticActions.cbegin(), s_diagnosticActions.cend());
alist.insert(alist.end(), s_mixerActions.cbegin(), s_mixerActions.cend());
alist.insert(alist.end(), s_onlineSoundsActions.cbegin(), s_onlineSoundsActions.cend());
}
return alist;
Expand Down
1 change: 1 addition & 0 deletions src/playback/internal/playbackuiactions.h
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ class PlaybackUiActions : public muse::ui::IUiActionsModule, public muse::async:
static const muse::ui::UiActionList s_settingsActions;
static const muse::ui::UiActionList s_loopBoundaryActions;
static const muse::ui::UiActionList s_diagnosticActions;
static const muse::ui::UiActionList s_mixerActions;
static const muse::ui::UiActionList s_onlineSoundsActions;

std::shared_ptr<PlaybackController> m_controller;
Expand Down
2 changes: 2 additions & 0 deletions src/playback/iplaybackcontroller.h
Original file line number Diff line number Diff line change
Expand Up @@ -120,5 +120,7 @@ class IPlaybackController : MODULE_CONTEXT_INTERFACE
virtual const std::map<muse::audio::TrackId, muse::audio::AudioResourceMeta>& onlineSounds() const = 0;
virtual muse::async::Notification onlineSoundsChanged() const = 0;
virtual muse::Progress onlineSoundsProcessingProgress() const = 0;

virtual muse::async::Notification mixerResetRequested() const = 0;
};
}
1 change: 1 addition & 0 deletions src/playback/qml/MuseScore/Playback/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ qt_add_qml_module(playback_qml
internal/PlaybackSpeedPopup.qml
internal/PlaybackSpeedSlider.qml
internal/PlaybackToolBarActions.qml
internal/ResetMixerButton.qml
internal/SoundFlag/MuseSoundsParams.qml
internal/SoundFlag/ParamsGridView.qml
internal/VolumePressureMeter.qml
Expand Down
23 changes: 23 additions & 0 deletions src/playback/qml/MuseScore/Playback/MixerPanel.qml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ ColumnLayout {
property Component toolbarComponent: MixerPanelToolbar {
navigation.section: root.navigationSection
navigation.order: root.contentNavigationPanelOrderStart

onResetMixerRequested: {
root.resetMixer()
}
Comment on lines +46 to +48

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Route reset through the controller action, not a local QML reset implementation.

Line 46 and Line 47 send the button flow to root.resetMixer(), and Lines 96-113 hardcode aux defaults (isActive = true, audioSignalPercentage = 30). This can diverge from the canonical reset behavior used by the "reset-mixer" action/shortcut, where defaults are computed in controller logic per track/source. Please wire the toolbar reset to the same "reset-mixer" action path and remove the local reset logic so both entry points produce identical defaults.

Also applies to: 96-113

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/playback/qml/MuseScore/Playback/MixerPanel.qml` around lines 46 - 48, The
toolbar's onResetMixerRequested currently calls the local root.resetMixer() and
the file contains a local hardcoded reset implementation (aux defaults in the
block around the function used at lines 96-113); instead, remove the local reset
logic and route the toolbar click to the canonical controller action
"reset-mixer" so both entry points use the same controller-computed defaults.
Concretely: replace the root.resetMixer() call path from onResetMixerRequested
with invoking the controller's "reset-mixer" action (or emitting the same signal
the shortcut uses) and delete the local aux-default assignments (isActive =
true, audioSignalPercentage = 30) so defaults are computed only by the
controller reset implementation.

}

signal resizeRequested(var newWidth, var newHeight)
Expand Down Expand Up @@ -89,6 +93,25 @@ ColumnLayout {
}
}

function resetMixer() {
for (let i = 0; i < mixerPanelModel.count; i++) {
let item = mixerPanelModel.get(i)
if (!item || !item.channelItem) {
continue
}

item.channelItem.volumeLevel = 0
item.channelItem.balance = 0

let auxList = item.channelItem.auxSendItemList
for (let j = 0; j < auxList.length; j++) {
let aux = auxList[j]
aux.isActive = true
aux.audioSignalPercentage = 30
}
}
}

MixerPanelModel {
id: mixerPanelModel

Expand Down
13 changes: 13 additions & 0 deletions src/playback/qml/MuseScore/Playback/internal/MixerPanelToolbar.qml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ Item {

property alias navigation: navPanel

signal resetMixerRequested()

anchors.fill: parent

NavigationPanel {
Expand All @@ -38,6 +40,17 @@ Item {
enabled: root.enabled && root.visible
}

ResetMixerButton {
id: resetMixerButton

anchors.fill: parent
navigationPanel: navPanel

onResetMixerRequested: {
root.resetMixerRequested()
}
}

// TODO: https://github.com/musescore/MuseScore/issues/16722
/*
FlatButton {
Expand Down
147 changes: 147 additions & 0 deletions src/playback/qml/MuseScore/Playback/internal/ResetMixerButton.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
/*
* SPDX-License-Identifier: GPL-3.0-only
* MuseScore-CLA-applies
*
* MuseScore Studio
* Music Composition & Notation
*
* Copyright (C) 2021 MuseScore Limited and others
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

import QtQuick
import QtQuick.Layouts

import Muse.Ui
import Muse.UiComponents

Item {
id: root

property NavigationPanel navigationPanel: null

signal resetMixerRequested()

anchors.fill: parent

FlatButton {
id: resetMixerButton

anchors.right: parent.right
anchors.rightMargin: 6
anchors.verticalCenter: parent.verticalCenter

width: 90

text: qsTrc("playback", "Reset Mixer")
transparent: false
accentButton: false

navigation.name: "Reset mixer"
navigation.panel: root.navigationPanel
navigation.row: 0

onClicked: {
resetVolumesConfirmPopup.toggleOpened()
}
}

StyledPopupView {
id: resetVolumesConfirmPopup

anchorItem: root

contentWidth: 360
contentHeight: popupContentColumn.implicitHeight

placementPolicies: PopupView.PreferBelow | PopupView.PreferAbove
closePolicies: PopupView.CloseOnEscape | PopupView.CloseOnPressOutsideParent

ColumnLayout {
id: popupContentColumn

width: 360
spacing: 12

RowLayout {
Layout.fillWidth: true

StyledTextLabel {
Layout.fillWidth: true
font: ui.theme.bodyBoldFont
text: qsTrc("playback", "Confirm mixer reset")
horizontalAlignment: Text.AlignLeft
}

FlatButton {
icon: IconCode.CLOSE_X_ROUNDED
transparent: true
accentButton: false

navigation.name: qsTrc("global", "Close")
navigation.panel: root.navigationPanel
navigation.row: 1

onClicked: {
resetVolumesConfirmPopup.close()
}
}
}

StyledTextLabel {
Layout.fillWidth: true
text: qsTrc("playback", "This will reset the mixer to default settings. Are you sure you want to continue?")
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignLeft
}

RowLayout {
Layout.fillWidth: true
spacing: 8

Item {
Layout.fillWidth: true
}

FlatButton {
text: qsTrc("global", "Cancel")
transparent: true
accentButton: false

navigation.panel: root.navigationPanel
navigation.row: 2

onClicked: {
resetVolumesConfirmPopup.close()
}
}

FlatButton {
text: qsTrc("global", "Confirm")
transparent: false
accentButton: true

navigation.panel: root.navigationPanel
navigation.row: 3

onClicked: {
resetVolumesConfirmPopup.close()
root.resetMixerRequested()
}
}
}
}
}
}

Loading
Loading