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));