From 8b146a60de6c2b2f9616ae2c1f017ffba0fe3be5 Mon Sep 17 00:00:00 2001 From: Miguel Sousa Date: Fri, 29 May 2026 16:35:14 +0100 Subject: [PATCH] Implement Unmute-all and Unsolo-all buttons to mixer This commit adds a button to unmute all muted channels in the mixer and another to unsolo all soloed channels in the mixer. Removing the need to do these actions to each channel one by one. It also adds a shortcut to unmute all (ctrl+shift+m) and a shortcut to unsolo all (ctrl+shift+l) following the same logic as the buttons. Closes: musescore#22767 Co-authored-by: Joana Guia --- src/app/configs/data/shortcuts.xml | 10 ++ src/app/configs/data/shortcuts_azerty.xml | 10 ++ src/app/configs/data/shortcuts_mac.xml | 10 ++ .../internal/playbackconfiguration.cpp | 2 + src/playback/internal/playbackcontroller.cpp | 39 ++++++ src/playback/internal/playbackcontroller.h | 2 + src/playback/internal/playbackuiactions.cpp | 14 +++ src/playback/internal/playbackuiactions.h | 1 + src/playback/playbacktypes.h | 2 + .../qml/MuseScore/Playback/CMakeLists.txt | 1 + .../qml/MuseScore/Playback/MixerPanel.qml | 35 ++++-- .../internal/MixerUnMuteAndUnSoloSection.qml | 111 ++++++++++++++++++ .../Playback/mixerpanelcontextmenumodel.cpp | 10 ++ .../Playback/mixerpanelcontextmenumodel.h | 3 + .../MuseScore/Playback/mixerpanelmodel.cpp | 14 +++ tools/codestyle/tidy_file.sh | 4 +- 16 files changed, 256 insertions(+), 12 deletions(-) mode change 100644 => 100755 src/playback/internal/playbackcontroller.cpp create mode 100644 src/playback/qml/MuseScore/Playback/internal/MixerUnMuteAndUnSoloSection.qml diff --git a/src/app/configs/data/shortcuts.xml b/src/app/configs/data/shortcuts.xml index f3e9ee6d557e0..0182ecc05c6a6 100644 --- a/src/app/configs/data/shortcuts.xml +++ b/src/app/configs/data/shortcuts.xml @@ -819,6 +819,16 @@ notation-select-all Ctrl+A + + unmute-all + Ctrl+Shift+U + 0 + + + unsolo-all + Ctrl+Shift+L + 0 + system-break Return diff --git a/src/app/configs/data/shortcuts_azerty.xml b/src/app/configs/data/shortcuts_azerty.xml index 20d115574481e..6410864603879 100644 --- a/src/app/configs/data/shortcuts_azerty.xml +++ b/src/app/configs/data/shortcuts_azerty.xml @@ -860,6 +860,16 @@ notation-select-all Ctrl+A + + unmute-all + Ctrl+Shift+U + 0 + + + unsolo-all + Ctrl+Shift+L + 0 + system-break Return diff --git a/src/app/configs/data/shortcuts_mac.xml b/src/app/configs/data/shortcuts_mac.xml index 3acba86c3dde0..2cf4212f22c98 100644 --- a/src/app/configs/data/shortcuts_mac.xml +++ b/src/app/configs/data/shortcuts_mac.xml @@ -819,6 +819,16 @@ notation-select-all Ctrl+A + + unmute-all + Ctrl+Shift+U + 0 + + + unsolo-all + Ctrl+Shift+L + 0 + system-break Return diff --git a/src/playback/internal/playbackconfiguration.cpp b/src/playback/internal/playbackconfiguration.cpp index d408abd40a520..4f52f21561229 100644 --- a/src/playback/internal/playbackconfiguration.cpp +++ b/src/playback/internal/playbackconfiguration.cpp @@ -51,6 +51,7 @@ static const Settings::Key MIXER_VOLUME_SECTION_VISIBLE_KEY(moduleName, "playbac static const Settings::Key MIXER_FADER_SECTION_VISIBLE_KEY(moduleName, "playback/mixer/faderSectionVisible"); static const Settings::Key MIXER_MUTE_AND_SOLO_SECTION_VISIBLE_KEY(moduleName, "playback/mixer/muteAndSoloSectionVisible"); static const Settings::Key MIXER_TITLE_SECTION_VISIBLE_KEY(moduleName, "playback/mixer/titleSectionVisible"); +static const Settings::Key MIXER_UNMUTE_AND_UNSOLO_SECTION_VISIBLE_KEY(moduleName, "playback/mixer/unMuteAndUnSoloSectionVisible"); static const Settings::Key MIXER_RESET_SOUND_FLAGS_WHEN_CHANGE_SOUND_WARNING(moduleName, "playback/mixer/needToShowAboutResetSoundFlagsWhwnChangeSoundWarning"); @@ -78,6 +79,7 @@ static Settings::Key mixerSectionVisibleKey(MixerSectionType sectionType) case MixerSectionType::Fader: return MIXER_FADER_SECTION_VISIBLE_KEY; case MixerSectionType::MuteAndSolo: return MIXER_MUTE_AND_SOLO_SECTION_VISIBLE_KEY; case MixerSectionType::Title: return MIXER_TITLE_SECTION_VISIBLE_KEY; + case MixerSectionType::UnMuteAndUnSolo: return MIXER_UNMUTE_AND_UNSOLO_SECTION_VISIBLE_KEY; case MixerSectionType::Unknown: break; } diff --git a/src/playback/internal/playbackcontroller.cpp b/src/playback/internal/playbackcontroller.cpp old mode 100644 new mode 100755 index 54dd5ba15c4f4..fe00a269016bc --- a/src/playback/internal/playbackcontroller.cpp +++ b/src/playback/internal/playbackcontroller.cpp @@ -69,6 +69,8 @@ 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 UNMUTE_ALL_CODE("unmute-all"); +static const ActionCode UNSOLO_ALL_CODE("unsolo-all"); static AudioOutputParams makeReverbOutputParams() { @@ -127,6 +129,8 @@ 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, UNMUTE_ALL_CODE, this, &PlaybackController::unmuteAll); + dispatcher()->reg(this, UNSOLO_ALL_CODE, this, &PlaybackController::unsoloAll); m_onlineSoundsController->regActions(); @@ -956,6 +960,41 @@ void PlaybackController::reloadPlaybackCache() } } +void PlaybackController::unmuteAll() +{ + InstrumentTrackIdSet existingTrackIdSet = notationPlayback()->existingTrackIdSet(); + + for (const InstrumentTrackId& instrumentTrackId : existingTrackIdSet) { + if (instrumentTrackId == notationPlayback()->metronomeTrackId()) { + continue; + } + + INotationSoloMuteState::SoloMuteState newState = m_notation->soloMuteState()->trackSoloMuteState(instrumentTrackId); + if (newState.mute == true) { + newState.mute = false; + + setTrackSoloMuteState(instrumentTrackId, newState); + } + } + + updateSoloMuteStates(); +} + +void PlaybackController::unsoloAll() +{ + InstrumentTrackIdSet existingTrackIdSet = notationPlayback()->existingTrackIdSet(); + + for (const InstrumentTrackId& instrumentTrackId : existingTrackIdSet) { + INotationSoloMuteState::SoloMuteState newState = trackSoloMuteState(instrumentTrackId); + if (newState.solo == true) { + newState.solo = false; + setTrackSoloMuteState(instrumentTrackId, newState); + } + } + + updateSoloMuteStates(); +} + void PlaybackController::openPlaybackSetupDialog() { interactive()->open("musescore://playback/soundprofilesdialog"); diff --git a/src/playback/internal/playbackcontroller.h b/src/playback/internal/playbackcontroller.h index bfd8184f19519..3fd302623831d 100644 --- a/src/playback/internal/playbackcontroller.h +++ b/src/playback/internal/playbackcontroller.h @@ -190,6 +190,8 @@ class PlaybackController : public IPlaybackController, public muse::actions::Act void setMidiUseWrittenPitch(bool useWrittenPitch); void toggleLoopPlayback(); void toggleHearPlaybackWhenEditing(); + void unmuteAll(); + void unsoloAll(); void reloadPlaybackCache(); diff --git a/src/playback/internal/playbackuiactions.cpp b/src/playback/internal/playbackuiactions.cpp index 9899b2679d86b..96080c2487a6d 100644 --- a/src/playback/internal/playbackuiactions.cpp +++ b/src/playback/internal/playbackuiactions.cpp @@ -201,6 +201,19 @@ const UiActionList PlaybackUiActions::s_diagnosticActions = { ) }; +const UiActionList PlaybackUiActions::s_mixerActions = { + UiAction("unmute-all", + mu::context::UiCtxAny, + mu::context::CTX_ANY, + TranslatableString("action", "Unmute All Channels") + ), + UiAction("unsolo-all", + mu::context::UiCtxAny, + mu::context::CTX_ANY, + TranslatableString("action", "Unsolo All Channels") + ) +}; + const UiActionList PlaybackUiActions::s_onlineSoundsActions = { UiAction(CLEAR_ONLINE_SOUNDS_CACHE_CODE, mu::context::UiCtxAny, @@ -264,6 +277,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..f68be84094a41 100644 --- a/src/playback/internal/playbackuiactions.h +++ b/src/playback/internal/playbackuiactions.h @@ -64,6 +64,7 @@ class PlaybackUiActions : public muse::ui::IUiActionsModule, public muse::async: static const muse::ui::UiActionList s_loopBoundaryActions; static const muse::ui::UiActionList s_diagnosticActions; static const muse::ui::UiActionList s_onlineSoundsActions; + static const muse::ui::UiActionList s_mixerActions; std::shared_ptr m_controller; muse::async::Channel m_actionEnabledChanged; diff --git a/src/playback/playbacktypes.h b/src/playback/playbacktypes.h index f1e37dfa95b11..6c6d606f0f596 100644 --- a/src/playback/playbacktypes.h +++ b/src/playback/playbacktypes.h @@ -44,6 +44,7 @@ enum class MixerSectionType { Volume, Fader, MuteAndSolo, + UnMuteAndUnSolo, Title }; @@ -57,6 +58,7 @@ inline QList allMixerSectionTypes() MixerSectionType::Volume, MixerSectionType::Fader, MixerSectionType::MuteAndSolo, + MixerSectionType::UnMuteAndUnSolo, MixerSectionType::Title }; diff --git a/src/playback/qml/MuseScore/Playback/CMakeLists.txt b/src/playback/qml/MuseScore/Playback/CMakeLists.txt index 279583f143e4d..1ca62395c88f1 100644 --- a/src/playback/qml/MuseScore/Playback/CMakeLists.txt +++ b/src/playback/qml/MuseScore/Playback/CMakeLists.txt @@ -74,6 +74,7 @@ qt_add_qml_module(playback_qml internal/SoundFlag/ParamsGridView.qml internal/VolumePressureMeter.qml internal/VolumeSlider.qml + internal/MixerUnMuteAndUnSoloSection.qml MixerPanel.qml NotationRegionsBeingProcessedView.qml OnlineSoundsStatusView.qml diff --git a/src/playback/qml/MuseScore/Playback/MixerPanel.qml b/src/playback/qml/MuseScore/Playback/MixerPanel.qml index 0cbe7bccad710..bff081dac9a19 100644 --- a/src/playback/qml/MuseScore/Playback/MixerPanel.qml +++ b/src/playback/qml/MuseScore/Playback/MixerPanel.qml @@ -305,21 +305,34 @@ ColumnLayout { } } - MixerMuteAndSoloSection { - id: muteAndSoloSection + Item { + width: contentColumn.width + height: muteAndSoloSection.height - visible: contextMenuModel.muteAndSoloSectionVisible - headerVisible: contextMenuModel.labelsSectionVisible - headerWidth: prv.headerWidth - channelItemWidth: prv.channelItemWidth + MixerMuteAndSoloSection { + id: muteAndSoloSection - model: mixerPanelModel + visible: contextMenuModel.muteAndSoloSectionVisible + headerVisible: contextMenuModel.labelsSectionVisible + headerWidth: prv.headerWidth + channelItemWidth: prv.channelItemWidth - navigationRowStart: 600 - needReadChannelName: prv.isPanelActivated + model: mixerPanelModel + navigationRowStart: 600 + needReadChannelName: prv.isPanelActivated - onNavigateControlIndexChanged: function(index) { - prv.setNavigateControlIndex(index) + onNavigateControlIndexChanged: function(index) { + prv.setNavigateControlIndex(index) + } + } + + MixerUnMuteAndUnSoloSection { + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + visible: contextMenuModel.unMuteAndUnSoloSectionVisible + headerVisible: contextMenuModel.labelsSectionVisible + headerWidth: prv.headerWidth + model: mixerPanelModel } } diff --git a/src/playback/qml/MuseScore/Playback/internal/MixerUnMuteAndUnSoloSection.qml b/src/playback/qml/MuseScore/Playback/internal/MixerUnMuteAndUnSoloSection.qml new file mode 100644 index 0000000000000..6d2e4791c4180 --- /dev/null +++ b/src/playback/qml/MuseScore/Playback/internal/MixerUnMuteAndUnSoloSection.qml @@ -0,0 +1,111 @@ +/* + * SPDX-License-Identifier: GPL-3.0-only + * MuseScore-Studio-CLA-applies + * + * MuseScore Studio + * Music Composition & Notation + * + * Copyright (C) 2021 MuseScore Limited + * + * 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 . + */ + +pragma ComponentBehavior: Bound + +import QtQuick + +import Muse.Ui +import Muse.UiComponents +import MuseScore.Playback + +Item { + id: root + + property var model: undefined + property int headerWidth: 98 + property bool headerVisible: true + property alias unMuteButton: unMuteAllButton + property alias unSoloButton: unSoloAllButton + + height: 28 + width: headerWidth + visible: headerVisible + + Row { + anchors.centerIn: parent + spacing: 6 + + FlatToggleButton { + id: unMuteAllButton + + height: 20 + width: 20 + + icon: IconCode.MUTE + + enabled: parent.enabled + visible: parent.visible + + navigation.name: "UnMuteAllButton" + navigation.panel: root.channelItem.panel + navigation.row: root.navigationRowStart + navigation.accessible.name: root.accessibleName + " " + qsTrc("playback", "Unmute All") + navigation.onActiveChanged: { + if (navigation.active) { + root.navigateControlIndexChanged({row: navigation.row, column: navigation.column}) + } + } + + onToggled: { + for (let i = 0; i < model.rowCount(); i++) { + let item = model.get(i); + + if(item.channelItem.type === MixerChannelItem.Metronome){ + continue + } + + item.channelItem.muted = false + } + } + } + + FlatToggleButton { + id: unSoloAllButton + + height: 20 + width: 20 + + icon: IconCode.SOLO + + enabled: parent.enabled + visible: parent.visible + + navigation.name: "UnSoloAllButton" + navigation.panel: root.channelItem.panel + navigation.row: root.navigationRowStart + 1 + navigation.accessible.name: root.accessibleName + " " + qsTrc("playback", "Unsolo All") + navigation.onActiveChanged: { + if (navigation.active) { + root.navigateControlIndexChanged({row: navigation.row, column: navigation.column}) + } + } + + onToggled: { + for (let i = 0; i < model.rowCount(); i++) { + let item = model.get(i); + item.channelItem.solo = false + } + } + } + } +} diff --git a/src/playback/qml/MuseScore/Playback/mixerpanelcontextmenumodel.cpp b/src/playback/qml/MuseScore/Playback/mixerpanelcontextmenumodel.cpp index 0c5b11b107b7c..fe2ff0653620a 100644 --- a/src/playback/qml/MuseScore/Playback/mixerpanelcontextmenumodel.cpp +++ b/src/playback/qml/MuseScore/Playback/mixerpanelcontextmenumodel.cpp @@ -49,6 +49,7 @@ static TranslatableString mixerSectionTitle(MixerSectionType type) case MixerSectionType::Fader: return TranslatableString("playback", "Fader"); case MixerSectionType::MuteAndSolo: return TranslatableString("playback", "Mute and solo"); case MixerSectionType::Title: return TranslatableString("playback", "Name"); + case MixerSectionType::UnMuteAndUnSolo: return TranslatableString("playback", "Unmute and Unsolo"); case MixerSectionType::Unknown: break; } @@ -121,6 +122,11 @@ bool MixerPanelContextMenuModel::titleSectionVisible() const return isSectionVisible(MixerSectionType::Title); } +bool MixerPanelContextMenuModel::unMuteAndUnSoloSectionVisible() const +{ + return isSectionVisible(MixerSectionType::UnMuteAndUnSolo); +} + void MixerPanelContextMenuModel::load() { AbstractMenuModel::load(); @@ -149,6 +155,7 @@ void MixerPanelContextMenuModel::load() buildSectionVisibleItem(MixerSectionType::Labels), buildSectionVisibleItem(MixerSectionType::Sound), buildSectionVisibleItem(MixerSectionType::AudioFX), + buildSectionVisibleItem(MixerSectionType::UnMuteAndUnSolo), }; for (aux_channel_idx_t idx = 0; idx < AUX_CHANNEL_NUM; ++idx) { @@ -318,6 +325,9 @@ void MixerPanelContextMenuModel::emitMixerSectionVisibilityChanged(MixerSectionT case MixerSectionType::Title: emit titleSectionVisibleChanged(); break; + case MixerSectionType::UnMuteAndUnSolo: + emit unMuteAndUnSoloSectionVisibleChanged(); + break; case MixerSectionType::Unknown: break; } diff --git a/src/playback/qml/MuseScore/Playback/mixerpanelcontextmenumodel.h b/src/playback/qml/MuseScore/Playback/mixerpanelcontextmenumodel.h index 34b18edce3e59..4b0e2a9a47edd 100644 --- a/src/playback/qml/MuseScore/Playback/mixerpanelcontextmenumodel.h +++ b/src/playback/qml/MuseScore/Playback/mixerpanelcontextmenumodel.h @@ -44,6 +44,7 @@ class MixerPanelContextMenuModel : public muse::uicomponents::AbstractMenuModel, Q_PROPERTY(bool faderSectionVisible READ faderSectionVisible NOTIFY faderSectionVisibleChanged) Q_PROPERTY(bool muteAndSoloSectionVisible READ muteAndSoloSectionVisible NOTIFY muteAndSoloSectionVisibleChanged) Q_PROPERTY(bool titleSectionVisible READ titleSectionVisible NOTIFY titleSectionVisibleChanged) + Q_PROPERTY(bool unMuteAndUnSoloSectionVisible READ unMuteAndUnSoloSectionVisible NOTIFY unMuteAndUnSoloSectionVisibleChanged) QML_ELEMENT @@ -62,6 +63,7 @@ class MixerPanelContextMenuModel : public muse::uicomponents::AbstractMenuModel, bool faderSectionVisible() const; bool muteAndSoloSectionVisible() const; bool titleSectionVisible() const; + bool unMuteAndUnSoloSectionVisible() const; Q_INVOKABLE void load() override; @@ -75,6 +77,7 @@ class MixerPanelContextMenuModel : public muse::uicomponents::AbstractMenuModel, void faderSectionVisibleChanged(); void muteAndSoloSectionVisibleChanged(); void titleSectionVisibleChanged(); + void unMuteAndUnSoloSectionVisibleChanged(); private: bool isSectionVisible(MixerSectionType sectionType) const; diff --git a/src/playback/qml/MuseScore/Playback/mixerpanelmodel.cpp b/src/playback/qml/MuseScore/Playback/mixerpanelmodel.cpp index 721954da8ed6d..1ee2decb2ffe1 100644 --- a/src/playback/qml/MuseScore/Playback/mixerpanelmodel.cpp +++ b/src/playback/qml/MuseScore/Playback/mixerpanelmodel.cpp @@ -263,6 +263,20 @@ void MixerPanelModel::clear() void MixerPanelModel::setupConnections() { + currentProject()->masterNotation()->notation()->soloMuteState()->trackSoloMuteStateChanged().onReceive( + this, [this](const InstrumentTrackId& instrumentTrackId, + const notation::INotationSoloMuteState::SoloMuteState& newState) { + const IPlaybackController::InstrumentTrackIdMap& trackIdMap = controller()->instrumentTrackIdMap(); + auto it = trackIdMap.find(instrumentTrackId); + if (it == trackIdMap.end()) { + return; + } + + if (MixerChannelItem* item = findChannelItem(it->second)) { + item->loadSoloMuteState(newState); + } + }); + audioSettings()->auxSoloMuteStateChanged().onReceive( this, [this](const aux_channel_idx_t index, notation::INotationSoloMuteState::SoloMuteState newSoloMuteState) { diff --git a/tools/codestyle/tidy_file.sh b/tools/codestyle/tidy_file.sh index 66ec22b7259d3..419559a3afee8 100755 --- a/tools/codestyle/tidy_file.sh +++ b/tools/codestyle/tidy_file.sh @@ -15,11 +15,13 @@ trap 'echo >&2 "$0: Error $?, line ${LINENO}, args: $*"; exit ${FAIL_FAST}' ERR # the git index when multiple instances of this script are run in parallel. HERE="${BASH_SOURCE%/*}" # path to dir that contains this script +REPO_ROOT="${HERE}/../.." +UNCRUSTIFY_CONFIG="${REPO_ROOT}/muse/tools/codestyle/uncrustify_muse.cfg" # lives in framework submodule function uncrustify_file() { local file="$1" lang="$2" status - uncrustify -c "${HERE}/uncrustify_musescore.cfg" --no-backup -l "${lang}" -f "${file}" + uncrustify -c "${UNCRUSTIFY_CONFIG}" --no-backup -l "${lang}" -f "${file}" status=$? rm -f "${file}.uncrustify" # remove possible temporary file return ${status}