diff --git a/src/app/configs/data/shortcuts.xml b/src/app/configs/data/shortcuts.xml
index e47c53467b07e..98f6163721e05 100644
--- a/src/app/configs/data/shortcuts.xml
+++ b/src/app/configs/data/shortcuts.xml
@@ -819,6 +819,11 @@
notation-select-all
Ctrl+A
+
+ reset-mixer
+ Ctrl+Shift+R
+ 0
+
system-break
Return
diff --git a/src/playback/internal/playbackcontroller.cpp b/src/playback/internal/playbackcontroller.cpp
index 54dd5ba15c4f4..a56fc8eb345b7 100644
--- a/src/playback/internal/playbackcontroller.cpp
+++ b/src/playback/internal/playbackcontroller.cpp
@@ -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()
{
@@ -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);
m_onlineSoundsController->regActions();
@@ -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(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(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();
+}
+
+muse::async::Notification PlaybackController::mixerResetRequested() const
+{
+ return m_mixerResetRequested;
+}
+
void PlaybackController::openPlaybackSetupDialog()
{
interactive()->open("musescore://playback/soundprofilesdialog");
diff --git a/src/playback/internal/playbackcontroller.h b/src/playback/internal/playbackcontroller.h
index bfd8184f19519..02484ef07a5e8 100644
--- a/src/playback/internal/playbackcontroller.h
+++ b/src/playback/internal/playbackcontroller.h
@@ -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;
@@ -215,6 +217,8 @@ class PlaybackController : public IPlaybackController, public muse::actions::Act
void updateSoloMuteStates();
void updateAuxMuteStates();
+ void resetMixerToDefaults();
+
using TrackAddFinished = std::function;
void addTrack(const engraving::InstrumentTrackId& instrumentTrackId, const TrackAddFinished& onFinished);
@@ -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 m_currentPlaybackPositionChanged;
muse::async::Channel m_actionCheckedChanged;
diff --git a/src/playback/internal/playbackuiactions.cpp b/src/playback/internal/playbackuiactions.cpp
index 9899b2679d86b..ac9b90e7af9ea 100644
--- a/src/playback/internal/playbackuiactions.cpp
+++ b/src/playback/internal/playbackuiactions.cpp
@@ -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,
@@ -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;
diff --git a/src/playback/internal/playbackuiactions.h b/src/playback/internal/playbackuiactions.h
index b07dc35042dbd..af1aa2f26fefa 100644
--- a/src/playback/internal/playbackuiactions.h
+++ b/src/playback/internal/playbackuiactions.h
@@ -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 m_controller;
diff --git a/src/playback/iplaybackcontroller.h b/src/playback/iplaybackcontroller.h
index b31d8ec35460e..1f9f38a941c41 100644
--- a/src/playback/iplaybackcontroller.h
+++ b/src/playback/iplaybackcontroller.h
@@ -120,5 +120,7 @@ class IPlaybackController : MODULE_CONTEXT_INTERFACE
virtual const std::map& onlineSounds() const = 0;
virtual muse::async::Notification onlineSoundsChanged() const = 0;
virtual muse::Progress onlineSoundsProcessingProgress() const = 0;
+
+ virtual muse::async::Notification mixerResetRequested() const = 0;
};
}
diff --git a/src/playback/qml/MuseScore/Playback/CMakeLists.txt b/src/playback/qml/MuseScore/Playback/CMakeLists.txt
index 279583f143e4d..8f60178b01bf1 100644
--- a/src/playback/qml/MuseScore/Playback/CMakeLists.txt
+++ b/src/playback/qml/MuseScore/Playback/CMakeLists.txt
@@ -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
diff --git a/src/playback/qml/MuseScore/Playback/MixerPanel.qml b/src/playback/qml/MuseScore/Playback/MixerPanel.qml
index 0cbe7bccad710..fc875f5c89aa0 100644
--- a/src/playback/qml/MuseScore/Playback/MixerPanel.qml
+++ b/src/playback/qml/MuseScore/Playback/MixerPanel.qml
@@ -42,6 +42,10 @@ ColumnLayout {
property Component toolbarComponent: MixerPanelToolbar {
navigation.section: root.navigationSection
navigation.order: root.contentNavigationPanelOrderStart
+
+ onResetMixerRequested: {
+ root.resetMixer()
+ }
}
signal resizeRequested(var newWidth, var newHeight)
@@ -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
diff --git a/src/playback/qml/MuseScore/Playback/internal/MixerPanelToolbar.qml b/src/playback/qml/MuseScore/Playback/internal/MixerPanelToolbar.qml
index ef9cea7a341c9..b4910dbfc74ee 100644
--- a/src/playback/qml/MuseScore/Playback/internal/MixerPanelToolbar.qml
+++ b/src/playback/qml/MuseScore/Playback/internal/MixerPanelToolbar.qml
@@ -30,6 +30,8 @@ Item {
property alias navigation: navPanel
+ signal resetMixerRequested()
+
anchors.fill: parent
NavigationPanel {
@@ -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 {
diff --git a/src/playback/qml/MuseScore/Playback/internal/ResetMixerButton.qml b/src/playback/qml/MuseScore/Playback/internal/ResetMixerButton.qml
new file mode 100644
index 0000000000000..887d896014b75
--- /dev/null
+++ b/src/playback/qml/MuseScore/Playback/internal/ResetMixerButton.qml
@@ -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 .
+ */
+
+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()
+ }
+ }
+ }
+ }
+ }
+}
+
diff --git a/src/playback/qml/MuseScore/Playback/mixerchannelitem.cpp b/src/playback/qml/MuseScore/Playback/mixerchannelitem.cpp
index 37f595426e632..ac23305f23ba1 100644
--- a/src/playback/qml/MuseScore/Playback/mixerchannelitem.cpp
+++ b/src/playback/qml/MuseScore/Playback/mixerchannelitem.cpp
@@ -312,6 +312,15 @@ void MixerChannelItem::loadAuxSendItems(const AuxSendsParams& auxSends)
m_outParams.auxSends = auxSends;
+ for (size_t i = 0; i < auxSends.size(); ++i) {
+ const auto index = static_cast(i);
+ auto it = m_auxSendItems.find(index);
+ if (it != m_auxSendItems.end()) {
+ it.value()->setIsActive(auxSends[i].active);
+ it.value()->setAudioSignalPercentage(static_cast(auxSends[i].signalAmount * 100.f));
+ }
+ }
+
configuration()->isAuxSendVisibleChanged().onReceive(this, [this](aux_channel_idx_t index, bool visible) {
if (visible) {
IF_ASSERT_FAILED(index < m_outParams.auxSends.size()) {
diff --git a/src/playback/qml/MuseScore/Playback/mixerpanelmodel.cpp b/src/playback/qml/MuseScore/Playback/mixerpanelmodel.cpp
index 721954da8ed6d..ac44023044765 100644
--- a/src/playback/qml/MuseScore/Playback/mixerpanelmodel.cpp
+++ b/src/playback/qml/MuseScore/Playback/mixerpanelmodel.cpp
@@ -66,6 +66,22 @@ void MixerPanelModel::init()
removeItem(trackId);
});
+ controller()->mixerResetRequested().onNotify(this, [this]() {
+ for (MixerChannelItem* item : m_mixerChannelList) {
+ const InstrumentTrackId& instrumentTrackId = item->instrumentTrackId();
+ if (!instrumentTrackId.isValid()) {
+ continue;
+ }
+ AudioOutputParams outParams = audioSettings()->trackOutputParams(instrumentTrackId);
+ loadOutputParams(item, std::move(outParams));
+ }
+
+ if (m_masterChannelItem) {
+ AudioOutputParams masterParams = audioSettings()->masterAudioOutputParams();
+ loadOutputParams(m_masterChannelItem, std::move(masterParams));
+ }
+ });
+
reload();
}
diff --git a/src/playback/tests/mocks/playbackcontrollermock.h b/src/playback/tests/mocks/playbackcontrollermock.h
index 0172b1899d141..8f1e6968a8240 100644
--- a/src/playback/tests/mocks/playbackcontrollermock.h
+++ b/src/playback/tests/mocks/playbackcontrollermock.h
@@ -86,6 +86,8 @@ class PlaybackControllerMock : public IPlaybackController
MOCK_METHOD(double, tempoMultiplier, (), (const, override));
MOCK_METHOD(void, setTempoMultiplier, (double), (override));
+ MOCK_METHOD(muse::async::Notification, mixerResetRequested, (), (const, override));
+
MOCK_METHOD(muse::Progress, loadingProgress, (), (const, override));
MOCK_METHOD(void, applyProfile, (const SoundProfileName&), (override));