LCOV - code coverage report
Current view: top level - src/playback/internal - playbackcontroller.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 4.0 % 1158 46
Test Date: 2026-11-05 04:07:02

            Line data    Source code
       1              : /*
       2              :  * SPDX-License-Identifier: GPL-3.0-only
       3              :  * MuseScore-Studio-CLA-applies
       4              :  *
       5              :  * MuseScore Studio
       6              :  * Music Composition & Notation
       7              :  *
       8              :  * Copyright (C) 2025 MuseScore Limited and others
       9              :  *
      10              :  * This program is free software: you can redistribute it and/or modify
      11              :  * it under the terms of the GNU General Public License version 3 as
      12              :  * published by the Free Software Foundation.
      13              :  *
      14              :  * This program is distributed in the hope that it will be useful,
      15              :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      16              :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      17              :  * GNU General Public License for more details.
      18              :  *
      19              :  * You should have received a copy of the GNU General Public License
      20              :  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
      21              :  */
      22              : 
      23              : #include "playbackcontroller.h"
      24              : 
      25              : #include "async/notifylist.h"
      26              : #include "containers.h"
      27              : #include "modularity/ioc.h"
      28              : #include "log.h"
      29              : #include "types/ret.h"
      30              : 
      31              : #include "audio/common/audioutils.h"
      32              : #include "audio/devtools/inputlag.h"
      33              : 
      34              : #include "engraving/dom/factory.h"
      35              : #include "engraving/dom/masterscore.h"
      36              : #include "engraving/dom/stafftext.h"
      37              : #include "engraving/dom/utils.h"
      38              : 
      39              : #include "notation/iexcerptnotation.h" // IWYU pragma: keep
      40              : #include "notation/imasternotation.h"
      41              : #include "notation/inotationautomation.h"
      42              : #include "notation/inotationinteraction.h"
      43              : #include "notation/inotationnoteinput.h" // IWYU pragma: keep
      44              : #include "notation/inotationparts.h"
      45              : #include "notation/inotationselection.h"
      46              : 
      47              : #include "project/inotationproject.h"
      48              : 
      49              : #include "../playbacktypes.h"
      50              : #include "onlinesoundscontroller.h"
      51              : 
      52              : using namespace muse;
      53              : using namespace muse::async;
      54              : using namespace muse::audio;
      55              : using namespace muse::midi;
      56              : using namespace mu::engraving;
      57              : using namespace mu::notation;
      58              : using namespace mu::playback;
      59              : using namespace mu::project;
      60              : 
      61              : static const muse::Uri NOTATION_REVIEW_PAGE_URI("musescore://notation/review");
      62              : 
      63            0 : static AudioOutputParams makeReverbOutputParams()
      64              : {
      65            0 :     AudioFxParams reverbParams;
      66            0 :     reverbParams.resourceMeta = makeReverbMeta();
      67            0 :     reverbParams.categories.insert(AudioFxCategory::FxReverb);
      68            0 :     reverbParams.chainOrder = 0;
      69            0 :     reverbParams.active = true;
      70              : 
      71            0 :     AudioOutputParams result;
      72            0 :     result.fxChain.emplace(reverbParams.chainOrder, std::move(reverbParams));
      73              : 
      74            0 :     return result;
      75            0 : }
      76              : 
      77            0 : static std::string resolveAuxTrackTitle(aux_channel_idx_t index, const AudioOutputParams& params, bool considerFx = true)
      78              : {
      79            0 :     if (considerFx && params.fxChain.size() == 1) {
      80            0 :         const AudioResourceMeta& meta = params.fxChain.cbegin()->second.resourceMeta;
      81            0 :         if (meta.id == MUSE_REVERB_ID) {
      82            0 :             return muse::trc("playback", "Reverb");
      83              :         }
      84              : 
      85            0 :         return meta.id;
      86              :     }
      87              : 
      88            0 :     return muse::mtrc("playback", "Aux %1").arg(index + 1).toStdString();
      89              : }
      90              : 
      91            9 : PlaybackController::PlaybackController(const muse::modularity::ContextPtr& iocCtx)
      92           81 :     : muse::Contextable(iocCtx), m_onlineSoundsController(std::make_unique<OnlineSoundsController>(iocCtx))
      93              : {
      94           81 : }
      95              : 
      96            9 : PlaybackController::~PlaybackController() = default;
      97              : 
      98            9 : void PlaybackController::init()
      99              : {
     100            9 :     m_onlineSoundsController->regActions();
     101              : 
     102            9 :     projectSession()->currentNotationChanged().onNotify(this, [this]() {
     103           12 :         onNotationChanged();
     104              : 
     105           12 :         setupPlaybackIfNeed();
     106           12 :     });
     107              : 
     108            9 :     projectSession()->projectChanged().onNotify(this, [this]() {
     109           11 :         if (m_isPlaybackInited) {
     110            0 :             resetPlayback();
     111              :         }
     112              : 
     113           11 :         m_needSetupPlayback = projectSession()->project() != nullptr;
     114              : 
     115           11 :         if (m_needSetupPlayback) {
     116            8 :             m_loadingProgress.start();
     117              :         }
     118           11 :     });
     119              : 
     120            9 :     playback()->init().onResolve(this, [this](const Ret& ret) {
     121            9 :         if (!ret) {
     122            0 :             LOGE() << "failed to init the audio context: " << ret.toString();
     123            0 :             return;
     124              :         }
     125              : 
     126            9 :         setupPlaybackIfNeed();
     127              :     });
     128              : 
     129            9 :     m_totalPlayTimeChanged.onNotify(this, [this]() {
     130            0 :         updateCurrentTempo();
     131              : 
     132            0 :         updateLoop();
     133              : 
     134              :         // Tempo/repeat/duration changes shift what secs_t a given tick maps to,
     135              :         // which invalidates any Volume/Pan automation envelope already sent to the engine
     136            0 :         resendAutomatedControlParams();
     137            0 :     });
     138              : 
     139            9 :     m_measureInputLag = configuration()->shouldMeasureInputLag();
     140            9 : }
     141              : 
     142           21 : void PlaybackController::setupPlaybackIfNeed()
     143              : {
     144           21 :     if (!m_needSetupPlayback || !playback()->isInited()) {
     145           14 :         return;
     146              :     }
     147              : 
     148            7 :     m_needSetupPlayback = false;
     149              : 
     150            7 :     setupPlayback();
     151              : }
     152              : 
     153            2 : void PlaybackController::deinit()
     154              : {
     155            2 :     m_needSetupPlayback = false;
     156              : 
     157            2 :     if (m_isPlaybackInited) {
     158            0 :         resetPlayback();
     159              :     }
     160              : 
     161            2 :     if (playback()->isInited()) {
     162            1 :         playback()->deinit();
     163              :     }
     164            2 : }
     165              : 
     166            0 : void PlaybackController::updateCurrentTempo()
     167              : {
     168            0 :     if (!notationPlayback()) {
     169            0 :         return;
     170              :     }
     171              : 
     172            0 :     const Tempo& newTempo = notationPlayback()->multipliedTempo(m_currentTick);
     173              : 
     174            0 :     if (newTempo == m_currentTempo) {
     175            0 :         return;
     176              :     }
     177              : 
     178            0 :     m_currentTempo = newTempo;
     179            0 :     m_currentTempoChanged.notify();
     180              : }
     181              : 
     182            0 : bool PlaybackController::isPlayAllowed() const
     183              : {
     184            0 :     if (!m_isPlaybackInited) {
     185            0 :         return false;
     186              :     }
     187              : 
     188            0 :     if (!m_notation) {
     189            0 :         return false;
     190              :     }
     191              : 
     192            0 :     if (!m_masterNotation) {
     193            0 :         return false;
     194              :     }
     195              : 
     196            0 :     const MasterScore* masterScore = m_masterNotation->masterScore();
     197            0 :     if (!masterScore) {
     198            0 :         return false;
     199              :     }
     200              : 
     201            0 :     if (!masterScore->firstMeasure()) {
     202            0 :         return false;
     203              :     }
     204              : 
     205            0 :     if (!m_notation->hasVisibleParts()) {
     206            0 :         return false;
     207              :     }
     208              : 
     209            0 :     if (!isLoaded()) {
     210            0 :         return false;
     211              :     }
     212              : 
     213            0 :     return true;
     214              : }
     215              : 
     216            0 : Channel<bool> PlaybackController::isPlayAllowedChanged() const
     217              : {
     218            0 :     return m_isPlayAllowedChanged;
     219              : }
     220              : 
     221            0 : bool PlaybackController::isPlaying() const
     222              : {
     223            0 :     if (!currentPlayer()) {
     224            0 :         return false;
     225              :     }
     226            0 :     return currentPlayer()->playbackStatus() == PlaybackStatus::Running;
     227              : }
     228              : 
     229            0 : Channel<bool> PlaybackController::isPlayingChanged() const
     230              : {
     231            0 :     return m_isPlayingChanged;
     232              : }
     233              : 
     234            0 : muse::audio::secs_t PlaybackController::playbackPosition() const
     235              : {
     236            0 :     if (!currentPlayer()) {
     237            0 :         return 0;
     238              :     }
     239            0 :     return currentPlayer()->playbackPosition();
     240              : }
     241              : 
     242            0 : muse::async::Channel<muse::audio::secs_t> PlaybackController::playbackPositionChanged() const
     243              : {
     244            0 :     return m_playbackPositionChanged;
     245              : }
     246              : 
     247            0 : bool PlaybackController::isPaused() const
     248              : {
     249            0 :     if (!currentPlayer()) {
     250            0 :         return false;
     251              :     }
     252            0 :     return currentPlayer()->playbackStatus() == PlaybackStatus::Paused;
     253              : }
     254              : 
     255            0 : bool PlaybackController::isLoaded() const
     256              : {
     257            0 :     return m_loadingTrackCount == 0;
     258              : }
     259              : 
     260            0 : bool PlaybackController::isLoopEnabled() const
     261              : {
     262            0 :     return notationPlayback() && notationPlayback()->isLoopEnabled();
     263              : }
     264              : 
     265            0 : Channel<bool> PlaybackController::loopEnabledChanged() const
     266              : {
     267            0 :     return m_loopEnabledChanged;
     268              : }
     269              : 
     270            0 : bool PlaybackController::loopBoundariesSet() const
     271              : {
     272            0 :     return notationPlayback() && !notationPlayback()->loopBoundaries().isNull();
     273              : }
     274              : 
     275            0 : void PlaybackController::seekRawTick(const midi::tick_t tick, const bool flushSound)
     276              : {
     277            0 :     if (m_currentTick == tick) {
     278            0 :         return;
     279              :     }
     280              : 
     281            0 :     RetVal<midi::tick_t> playedTick = notationPlayback()->playPositionTickByRawTick(tick);
     282            0 :     if (!playedTick.ret) {
     283            0 :         return;
     284              :     }
     285              : 
     286            0 :     seek(playedTickToSecs(playedTick.val), flushSound);
     287            0 : }
     288              : 
     289            0 : void PlaybackController::seek(const audio::secs_t secs, const bool flushSound)
     290              : {
     291            0 :     IF_ASSERT_FAILED(currentPlayer()) {
     292            0 :         return;
     293              :     }
     294              : 
     295            0 :     currentPlayer()->seek(secs, flushSound);
     296              : }
     297              : 
     298            0 : bool PlaybackController::isPlaybackInited() const
     299              : {
     300            0 :     return m_isPlaybackInited;
     301              : }
     302              : 
     303            0 : muse::async::Channel<bool> PlaybackController::playbackInitedChanged() const
     304              : {
     305            0 :     return m_playbackInited;
     306              : }
     307              : 
     308            0 : const IPlaybackController::InstrumentTrackIdMap& PlaybackController::instrumentTrackIdMap() const
     309              : {
     310            0 :     return m_instrumentTrackIdMap;
     311              : }
     312              : 
     313            0 : const IPlaybackController::AuxTrackIdMap& PlaybackController::auxTrackIdMap() const
     314              : {
     315            0 :     return m_auxTrackIdMap;
     316              : }
     317              : 
     318            0 : Channel<TrackId> PlaybackController::trackAdded() const
     319              : {
     320            0 :     return m_trackAdded;
     321              : }
     322              : 
     323            0 : Channel<TrackId> PlaybackController::trackRemoved() const
     324              : {
     325            0 :     return m_trackRemoved;
     326              : }
     327              : 
     328            0 : std::string PlaybackController::auxChannelName(aux_channel_idx_t index) const
     329              : {
     330            0 :     return resolveAuxTrackTitle(index, audioSettings()->auxOutputParams(index));
     331              : }
     332              : 
     333            0 : Channel<aux_channel_idx_t, std::string> PlaybackController::auxChannelNameChanged() const
     334              : {
     335            0 :     return m_auxChannelNameChanged;
     336              : }
     337              : 
     338            0 : Promise<SoundPresetList> PlaybackController::availableSoundPresets(const InstrumentTrackId& instrumentTrackId) const
     339              : {
     340            0 :     auto it = m_instrumentTrackIdMap.find(instrumentTrackId);
     341            0 :     if (it == m_instrumentTrackIdMap.end()) {
     342            0 :         return Promise<SoundPresetList>([](auto, auto reject) {
     343            0 :             return reject(static_cast<int>(Ret::Code::UnknownError), "invalid instrumentTrackId");
     344            0 :         });
     345              :     }
     346              : 
     347            0 :     const AudioInputParams& params = audioSettings()->trackInputParams(instrumentTrackId);
     348            0 :     return playback()->availableSoundPresets(params.resourceMeta);
     349              : }
     350              : 
     351            0 : const PlaybackController::SoloMuteState& PlaybackController::trackSoloMuteState(const InstrumentTrackId& trackId) const
     352              : {
     353            0 :     return m_notation->soloMuteState()->trackSoloMuteState(trackId);
     354              : }
     355              : 
     356            0 : void PlaybackController::setTrackSoloMuteState(const InstrumentTrackId& trackId, const SoloMuteState& state)
     357              : {
     358            0 :     if (trackId == notationPlayback()->metronomeTrackId()) {
     359            0 :         if (state.mute != notationConfiguration()->isMetronomeEnabled()) {
     360            0 :             return;
     361              :         }
     362              : 
     363            0 :         toggleMetronome();
     364            0 :         return;
     365              :     }
     366              : 
     367            0 :     m_notation->soloMuteState()->setTrackSoloMuteState(trackId, state);
     368              : }
     369              : 
     370            0 : void PlaybackController::playElements(const std::vector<const notation::EngravingItem*>& elements, const PlayParams& params, bool isMidi)
     371              : {
     372            0 :     IF_ASSERT_FAILED(notationPlayback()) {
     373            0 :         return;
     374              :     }
     375              : 
     376            0 :     if ((!configuration()->playNotesWhenEditing()) || (isMidi && !configuration()->playNotesOnMidiInput())) {
     377            0 :         return;
     378              :     }
     379              : 
     380            0 :     if (m_measureInputLag) {
     381            0 :         START_INPUT_LAG_TIMER;
     382              :     }
     383              : 
     384            0 :     std::vector<const notation::EngravingItem*> elementsForPlaying;
     385            0 :     elementsForPlaying.reserve(elements.size());
     386              : 
     387            0 :     bool playChordWhenEditing = configuration()->playChordWhenEditing();
     388            0 :     bool playHarmonyWhenEditing = configuration()->playHarmonyWhenEditing();
     389              : 
     390            0 :     for (const EngravingItem* element : elements) {
     391            0 :         IF_ASSERT_FAILED(element) {
     392            0 :             continue;
     393              :         }
     394              : 
     395            0 :         if (!element->isPlayable()) {
     396            0 :             continue;
     397              :         }
     398              : 
     399            0 :         if (element->isChord() && !playChordWhenEditing) {
     400            0 :             continue;
     401              :         }
     402              : 
     403            0 :         if (element->isHarmony() && !playHarmonyWhenEditing) {
     404            0 :             continue;
     405              :         }
     406              : 
     407            0 :         elementsForPlaying.push_back(element);
     408              :     }
     409              : 
     410            0 :     const mpe::duration_t duration = params.duration.has_value() ? params.duration.value()
     411            0 :                                      : notationConfiguration()->notePlayDurationMilliseconds() * 1000;
     412              : 
     413            0 :     notationPlayback()->triggerEventsForItems(elementsForPlaying, duration, params.flushSound);
     414            0 : }
     415              : 
     416            0 : void PlaybackController::playNotes(const NoteValList& notes, staff_idx_t staffIdx, const Segment* segment, const PlayParams& params)
     417              : {
     418            0 :     Segment* seg = const_cast<Segment*>(segment);
     419            0 :     Chord* chord = engraving::Factory::createChord(seg);
     420              : 
     421            0 :     std::vector<const EngravingItem*> elements;
     422            0 :     elements.reserve(notes.size());
     423              : 
     424            0 :     for (const NoteVal& nval : notes) {
     425            0 :         Note* note = engraving::Factory::createNote(chord);
     426            0 :         note->setStaffIdx(staffIdx);
     427            0 :         note->setNval(nval);
     428            0 :         elements.push_back(note);
     429              :     }
     430              : 
     431            0 :     playElements(elements, params);
     432              : 
     433            0 :     delete chord;
     434            0 :     DeleteAll(elements);
     435            0 : }
     436              : 
     437            0 : void PlaybackController::playMetronome(int tick)
     438              : {
     439            0 :     notationPlayback()->triggerMetronome(tick);
     440            0 : }
     441              : 
     442            0 : void PlaybackController::triggerControllers(const muse::mpe::ControllerChangeEventList& list, staff_idx_t staffIdx, int tick)
     443              : {
     444            0 :     notationPlayback()->triggerControllers(list, staffIdx, tick);
     445            0 : }
     446              : 
     447            0 : void PlaybackController::seekElement(const notation::EngravingItem* element, bool flushSound)
     448              : {
     449            0 :     IF_ASSERT_FAILED(notationPlayback()) {
     450            0 :         return;
     451              :     }
     452              : 
     453            0 :     if (!element) {
     454            0 :         return;
     455              :     }
     456              : 
     457            0 :     RetVal<midi::tick_t> tick = notationPlayback()->playPositionTickByElement(element);
     458            0 :     if (!tick.ret) {
     459            0 :         return;
     460              :     }
     461              : 
     462            0 :     seek(playedTickToSecs(tick.val), flushSound);
     463            0 : }
     464              : 
     465            0 : void PlaybackController::seekBeat(int measureIndex, int beatIndex, bool flushSound)
     466              : {
     467            0 :     secs_t targetSecs = beatToSecs(measureIndex, beatIndex);
     468            0 :     seek(targetSecs, flushSound);
     469            0 : }
     470              : 
     471            0 : void PlaybackController::seekRangeSelection()
     472              : {
     473            0 :     if (!selection()->isRange()) {
     474            0 :         return;
     475              :     }
     476              : 
     477            0 :     midi::tick_t startTick = selectionRange()->startTick().ticks();
     478              : 
     479            0 :     seekRawTick(startTick);
     480              : }
     481              : 
     482            0 : void PlaybackController::onAudioResourceChanged(const TrackId trackId, const InstrumentTrackId& instrumentTrackId,
     483              :                                                 const AudioResourceMeta& oldMeta, const AudioResourceMeta& newMeta)
     484              : {
     485            0 :     INotationPlaybackPtr notationPlayback = this->notationPlayback();
     486            0 :     if (!notationPlayback) {
     487            0 :         return;
     488              :     }
     489              : 
     490            0 :     if (oldMeta.type == newMeta.type && oldMeta.id == newMeta.id) {
     491            0 :         return;
     492              :     }
     493              : 
     494            0 :     if (shouldLoadDrumset(instrumentTrackId, oldMeta, newMeta)) {
     495            0 :         m_drumsetLoader.loadDrumset(m_notation, instrumentTrackId, newMeta);
     496              :     }
     497              : 
     498            0 :     notationPlayback->removeSoundFlags({ instrumentTrackId });
     499              : 
     500            0 :     if (audio::isOnlineAudioResource(newMeta)) {
     501            0 :         m_onlineSoundsController->addOnlineTrack(trackId, newMeta);
     502            0 :         tours()->onEvent(u"online_sounds_added");
     503            0 :         notationPlayback->setSendEventsOnScoreChange(instrumentTrackId, true);
     504            0 :     } else if (audio::isOnlineAudioResource(oldMeta)) {
     505            0 :         m_onlineSoundsController->removeOnlineTrack(trackId);
     506            0 :         notationPlayback->setSendEventsOnScoreChange(instrumentTrackId, false);
     507              :     }
     508            0 : }
     509              : 
     510            0 : bool PlaybackController::shouldLoadDrumset(const engraving::InstrumentTrackId& instrumentTrackId,
     511              :                                            const AudioResourceMeta& oldMeta, const AudioResourceMeta& newMeta) const
     512              : {
     513            0 :     if (oldMeta.type == newMeta.type && oldMeta.id == newMeta.id) {
     514            0 :         return false;
     515              :     }
     516              : 
     517            0 :     const Part* part = masterNotationParts()->part(instrumentTrackId.partId);
     518            0 :     const Instrument* instrument = part ? part->instrumentById(instrumentTrackId.instrumentId) : nullptr;
     519            0 :     if (!instrument || !instrument->useDrumset()) {
     520            0 :         return false;
     521              :     }
     522              : 
     523            0 :     return isResourceType(oldMeta, AudioResourceType::MuseSamplerSoundPack)
     524            0 :            || isResourceType(newMeta, AudioResourceType::MuseSamplerSoundPack);
     525              : }
     526              : 
     527            0 : void PlaybackController::addSoundFlagsIfNeed(const std::vector<EngravingItem*>& selection)
     528              : {
     529            0 :     if (selection.empty()) {
     530            0 :         return;
     531              :     }
     532              : 
     533            0 :     std::vector<StaffText*> staffTextList;
     534              : 
     535            0 :     for (EngravingItem* item : selection) {
     536            0 :         if (!item || !item->isStaffText()) {
     537            0 :             continue;
     538              :         }
     539              : 
     540            0 :         InstrumentTrackId trackId = mu::engraving::makeInstrumentTrackId(item);
     541            0 :         const AudioInputParams& params = audioSettings()->trackInputParams(trackId);
     542              : 
     543            0 :         bool supportsSoundFlags = params.type() == AudioSourceType::MuseSampler;
     544            0 :         if (supportsSoundFlags) {
     545            0 :             staffTextList.push_back(toStaffText(item));
     546              :         }
     547            0 :     }
     548              : 
     549            0 :     if (!staffTextList.empty()) {
     550            0 :         notationPlayback()->addSoundFlags(staffTextList);
     551              :     }
     552            0 : }
     553              : 
     554            0 : muse::audio::IPlayerPtr PlaybackController::currentPlayer() const
     555              : {
     556            0 :     return m_player;
     557              : }
     558              : 
     559            7 : INotationPlaybackPtr PlaybackController::notationPlayback() const
     560              : {
     561            7 :     return m_masterNotation ? m_masterNotation->playback() : nullptr;
     562              : }
     563              : 
     564            0 : INotationPartsPtr PlaybackController::masterNotationParts() const
     565              : {
     566            0 :     return m_masterNotation ? m_masterNotation->parts() : nullptr;
     567              : }
     568              : 
     569            0 : INotationSelectionPtr PlaybackController::selection() const
     570              : {
     571            0 :     return m_notation ? m_notation->interaction()->selection() : nullptr;
     572              : }
     573              : 
     574            0 : INotationSelectionRangePtr PlaybackController::selectionRange() const
     575              : {
     576            0 :     INotationSelectionPtr selection = this->selection();
     577            0 :     return selection ? selection->range() : nullptr;
     578            0 : }
     579              : 
     580            0 : INotationInteractionPtr PlaybackController::interaction() const
     581              : {
     582            0 :     return m_notation ? m_notation->interaction() : nullptr;
     583              : }
     584              : 
     585            0 : uint64_t PlaybackController::notationPlaybackKey() const
     586              : {
     587            0 :     return reinterpret_cast<uint64_t>(notationPlayback().get());
     588              : }
     589              : 
     590           12 : void PlaybackController::onNotationChanged()
     591              : {
     592           12 :     setNotation(projectSession()->currentNotation());
     593           12 : }
     594              : 
     595            0 : void PlaybackController::onPartChanged(const Part* part)
     596              : {
     597            0 :     if (!m_notation->hasVisibleParts()) {
     598            0 :         doPause();
     599              :     }
     600            0 :     m_isPlayAllowedChanged.send(isPlayAllowed());
     601              : 
     602            0 :     if (!configuration()->muteHiddenInstruments()) {
     603            0 :         return;
     604              :     }
     605              : 
     606            0 :     for (const InstrumentTrackId& instrumentTrackId : part->instrumentTrackIdList()) {
     607            0 :         auto soloMuteState = trackSoloMuteState(instrumentTrackId);
     608            0 :         soloMuteState.mute = !part->show();
     609            0 :         setTrackSoloMuteState(instrumentTrackId, soloMuteState);
     610            0 :     }
     611              : 
     612            0 :     if (part->hasChordSymbol()) {
     613            0 :         InstrumentTrackId chordSymbolsTrackId = notationPlayback()->chordSymbolsTrackId(part->id());
     614            0 :         auto chordsSoloMuteState = trackSoloMuteState(chordSymbolsTrackId);
     615            0 :         chordsSoloMuteState.mute = !part->show();
     616            0 :         setTrackSoloMuteState(chordSymbolsTrackId, chordsSoloMuteState);
     617            0 :     }
     618              : }
     619              : 
     620            0 : void PlaybackController::onSelectionChanged()
     621              : {
     622            0 :     const INotationSelectionPtr selection = this->selection();
     623            0 :     if (!selection || !m_player) {
     624            0 :         return;
     625              :     }
     626              : 
     627            0 :     bool selectionTypeChanged = m_isRangeSelection && !selection->isRange();
     628            0 :     m_isRangeSelection = selection->isRange();
     629              : 
     630            0 :     if (!m_isRangeSelection) {
     631            0 :         if (selectionTypeChanged) {
     632            0 :             updateLoop();
     633            0 :             updateSoloMuteStates();
     634              :         }
     635              : 
     636            0 :         addSoundFlagsIfNeed(selection->elements());
     637            0 :         return;
     638              :     }
     639              : 
     640            0 :     m_player->resetLoop();
     641              : 
     642            0 :     seekRangeSelection();
     643            0 :     updateSoloMuteStates();
     644            0 : }
     645              : 
     646            0 : muse::Ret PlaybackController::togglePlay()
     647              : {
     648            0 :     if (isPlaying()) {
     649            0 :         return pause();
     650              :     }
     651              : 
     652            0 :     if (isPaused()) {
     653            0 :         doResume();
     654            0 :         return make_ok();
     655              :     }
     656              : 
     657            0 :     return play();
     658              : }
     659              : 
     660            0 : muse::Ret PlaybackController::play(bool showErrors)
     661              : {
     662            0 :     if (!isPlayAllowed()) {
     663            0 :         LOGW() << "playback not allowed";
     664            0 :         return make_ret(Ret::Code::NotSupported);
     665              :     }
     666              : 
     667            0 :     IF_ASSERT_FAILED(currentPlayer()) {
     668            0 :         return make_ret(Ret::Code::InternalError);
     669              :     }
     670              : 
     671            0 :     if (showErrors && m_onlineSoundsController->shouldShowOnlineSoundsProcessingError(isPlaying())) {
     672            0 :         m_onlineSoundsController->showOnlineSoundsProcessingError([this]() { play(false /*showErrors*/); });
     673            0 :         return make_ret(Ret::Code::NotSupported);
     674              :     }
     675              : 
     676            0 :     interaction()->endEditElement();
     677            0 :     interaction()->noteInput()->endNoteInput();
     678              : 
     679            0 :     if (isPaused()) {
     680            0 :         notationPlayback()->sendEventsForChangedTracks();
     681              : 
     682            0 :         secs_t pos = currentPlayer()->playbackPosition();
     683            0 :         secs_t endSecs = totalPlayTime();
     684            0 :         if (pos == endSecs) {
     685            0 :             secs_t startSecs = playbackStartSecs();
     686            0 :             seek(startSecs);
     687              :         }
     688              : 
     689            0 :         doResume();
     690              :     } else {
     691            0 :         notationPlayback()->sendEventsForChangedTracks();
     692              : 
     693            0 :         doPlay();
     694              :     }
     695              : 
     696            0 :     return make_ok();
     697              : }
     698              : 
     699            0 : muse::Ret PlaybackController::pause(bool select)
     700              : {
     701            0 :     if (isPlaying()) {
     702            0 :         doPause(select);
     703              :     }
     704              : 
     705            0 :     return make_ok();
     706              : }
     707              : 
     708            0 : muse::Ret PlaybackController::stop()
     709              : {
     710            0 :     doStop();
     711            0 :     return make_ok();
     712              : }
     713              : 
     714            0 : muse::Ret PlaybackController::rewind(muse::secs_t secs)
     715              : {
     716            0 :     if (!isPlayAllowed()) {
     717            0 :         LOGW() << "playback not allowed";
     718            0 :         return make_ret(Ret::Code::NotSupported);
     719              :     }
     720              : 
     721            0 :     doRewind(secs);
     722            0 :     return make_ok();
     723              : }
     724              : 
     725            0 : muse::Ret PlaybackController::playFromSelection(bool showErrors)
     726              : {
     727            0 :     if (!isPlayAllowed()) {
     728            0 :         LOGW() << "playback not allowed";
     729            0 :         return make_ret(Ret::Code::NotSupported);
     730              :     }
     731              : 
     732            0 :     if (showErrors && m_onlineSoundsController->shouldShowOnlineSoundsProcessingError(isPlaying())) {
     733            0 :         m_onlineSoundsController->showOnlineSoundsProcessingError([this]() { playFromSelection(false /*showErrors*/); });
     734            0 :         return make_ret(Ret::Code::NotSupported);
     735              :     }
     736              : 
     737            0 :     int startTick = 0;
     738              : 
     739            0 :     if (!selection()->isNone()) {
     740            0 :         startTick = INT_MAX;
     741            0 :         for (const EngravingItem* item : selection()->elements()) {
     742            0 :             startTick = std::min(startTick, item->tick().ticks());
     743              :         }
     744              :     } else {
     745              :         // Selection is none - fall back to last element hit...
     746            0 :         if (const EngravingItem* lastElementHit = selection()->lastElementHit()) {
     747            0 :             startTick = lastElementHit->tick().ticks();
     748              :         }
     749              :     }
     750              : 
     751            0 :     const LoopBoundaries& loop = notationPlayback()->loopBoundaries();
     752            0 :     if (loop.enabled) {
     753            0 :         if (startTick < loop.loopInTick.ticks() || startTick > loop.loopOutTick.ticks()) {
     754            0 :             startTick = loop.loopInTick.ticks();
     755              :         }
     756              :     }
     757              : 
     758            0 :     const RetVal<midi::tick_t> retval = notationPlayback()->playPositionTickByRawTick(startTick);
     759            0 :     if (!retval.ret) {
     760            0 :         return retval.ret;
     761              :     }
     762              : 
     763            0 :     seek(playedTickToSecs(retval.val));
     764              : 
     765            0 :     if (isPaused()) {
     766            0 :         doResume();
     767            0 :     } else if (!isPlaying()) {
     768            0 :         doPlay();
     769              :     }
     770              : 
     771            0 :     return make_ok();
     772            0 : }
     773              : 
     774            0 : void PlaybackController::doPlay()
     775              : {
     776            0 :     IF_ASSERT_FAILED(currentPlayer()) {
     777            0 :         return;
     778              :     }
     779              : 
     780            0 :     if (isLoopEnabled()) {
     781            0 :         secs_t startSecs = playbackStartSecs();
     782            0 :         seek(startSecs);
     783              :     }
     784              : 
     785            0 :     currentPlayer()->prepareToPlay().onResolve(this, [this](const Ret& ret) {
     786            0 :         if (!currentPlayer()) {
     787            0 :             return;
     788              :         }
     789              : 
     790            0 :         if (!ret) {
     791            0 :             LOGE() << ret.toString();
     792              :         }
     793              : 
     794            0 :         secs_t delay = 0.;
     795            0 :         if (notationConfiguration()->isCountInEnabled()) {
     796            0 :             notationPlayback()->triggerCountIn(m_currentTick, delay);
     797              :         }
     798              : 
     799            0 :         currentPlayer()->play(delay);
     800              :     });
     801              : }
     802              : 
     803            0 : void PlaybackController::doRewind(secs_t newPosition)
     804              : {
     805            0 :     secs_t startSecs = playbackStartSecs();
     806            0 :     secs_t endSecs = totalPlayTime();
     807            0 :     newPosition = std::clamp(newPosition, startSecs, endSecs);
     808              : 
     809            0 :     seek(newPosition);
     810            0 : }
     811              : 
     812            0 : void PlaybackController::doPause(bool select)
     813              : {
     814            0 :     IF_ASSERT_FAILED(currentPlayer()) {
     815            0 :         return;
     816              :     }
     817              : 
     818            0 :     if (isPaused()) {
     819            0 :         return;
     820              :     }
     821              : 
     822            0 :     currentPlayer()->pause();
     823              : 
     824            0 :     if (select && m_notation) {
     825            0 :         const Fraction playPositionFrac = Fraction::fromTicks(m_currentTick);
     826            0 :         interaction()->findAndSelectChordRest(playPositionFrac);
     827              :     }
     828              : }
     829              : 
     830            0 : void PlaybackController::doStop()
     831              : {
     832            0 :     IF_ASSERT_FAILED(currentPlayer()) {
     833            0 :         return;
     834              :     }
     835              : 
     836            0 :     currentPlayer()->stop();
     837              : }
     838              : 
     839            0 : void PlaybackController::doResume()
     840              : {
     841            0 :     IF_ASSERT_FAILED(currentPlayer()) {
     842            0 :         return;
     843              :     }
     844              : 
     845            0 :     currentPlayer()->prepareToPlay().onResolve(this, [this](const Ret& ret) {
     846            0 :         if (!currentPlayer()) {
     847            0 :             return;
     848              :         }
     849              : 
     850            0 :         if (!ret) {
     851            0 :             LOGE() << ret.toString();
     852              :         }
     853              : 
     854            0 :         secs_t delay = 0.;
     855            0 :         if (notationConfiguration()->isCountInEnabled()) {
     856            0 :             notationPlayback()->triggerCountIn(m_currentTick, delay);
     857              :         }
     858              : 
     859            0 :         currentPlayer()->resume(delay);
     860              :     });
     861              : }
     862              : 
     863            0 : void PlaybackController::onPlaybackStatusChanged()
     864              : {
     865            0 :     const bool playing = isPlaying();
     866            0 :     m_isPlayingChanged.send(playing);
     867              : 
     868            0 :     if (!notationPlayback()) {
     869            0 :         return;
     870              :     }
     871              : 
     872            0 :     const auto& onlineSounds = m_onlineSoundsController->onlineSounds();
     873              : 
     874            0 :     for (const auto& pair : m_instrumentTrackIdMap) {
     875            0 :         bool shouldSendOnScoreChange = playing || muse::contains(onlineSounds, pair.second);
     876            0 :         notationPlayback()->setSendEventsOnScoreChange(pair.first, shouldSendOnScoreChange);
     877              :     }
     878              : }
     879              : 
     880            0 : secs_t PlaybackController::playbackStartSecs() const
     881              : {
     882            0 :     if (!m_notation) {
     883            0 :         return 0;
     884              :     }
     885              : 
     886            0 :     const LoopBoundaries& loop = notationPlayback()->loopBoundaries();
     887            0 :     if (loop.enabled) {
     888              :         // Convert from raw ticks (visual tick != playback tick due to repeats etc)
     889            0 :         RetVal<tick_t> startTick = notationPlayback()->playPositionTickByRawTick(loop.loopInTick.ticks());
     890            0 :         if (!startTick.ret) {
     891            0 :             return 0;
     892              :         }
     893            0 :         return playedTickToSecs(startTick.val);
     894            0 :     }
     895              : 
     896            0 :     return 0;
     897              : }
     898              : 
     899            0 : InstrumentTrackIdSet PlaybackController::instrumentTrackIdSetForRangePlayback() const
     900              : {
     901            0 :     std::vector<const Part*> selectedParts = selectionRange()->selectedParts();
     902            0 :     Fraction startTick = selectionRange()->startTick();
     903            0 :     int startTicks = startTick.ticks();
     904              : 
     905            0 :     InstrumentTrackIdSet result;
     906              : 
     907            0 :     for (const Part* part : selectedParts) {
     908            0 :         if (const Instrument* startInstrument = part->instrument(startTick)) {
     909            0 :             result.insert({ part->id(), startInstrument->id() });
     910              :         }
     911              : 
     912            0 :         for (auto [tick, instrument] : part->instruments()) {
     913            0 :             if (tick > startTicks) {
     914            0 :                 result.insert({ part->id(), instrument->id() });
     915              :             }
     916              :         }
     917              : 
     918            0 :         if (part->hasChordSymbol()) {
     919            0 :             result.insert(notationPlayback()->chordSymbolsTrackId(part->id()));
     920              :         }
     921              :     }
     922              : 
     923            0 :     return result;
     924            0 : }
     925              : 
     926            0 : muse::Ret PlaybackController::togglePlayRepeats()
     927              : {
     928            0 :     bool playRepeatsEnabled = notationConfiguration()->isPlayRepeatsEnabled();
     929            0 :     notationConfiguration()->setIsPlayRepeatsEnabled(!playRepeatsEnabled);
     930            0 :     return make_ok();
     931              : }
     932              : 
     933            0 : muse::Ret PlaybackController::togglePlayChordSymbols()
     934              : {
     935            0 :     bool playChordSymbolsEnabled = notationConfiguration()->isPlayChordSymbolsEnabled();
     936            0 :     notationConfiguration()->setIsPlayChordSymbolsEnabled(!playChordSymbolsEnabled);
     937              : 
     938            0 :     for (auto it = m_instrumentTrackIdMap.cbegin(); it != m_instrumentTrackIdMap.cend(); ++it) {
     939            0 :         if (notationPlayback()->isChordSymbolsTrack(it->first)) {
     940            0 :             setTrackActivity(it->first, !playChordSymbolsEnabled);
     941              :         }
     942              :     }
     943            0 :     return make_ok();
     944              : }
     945              : 
     946            0 : muse::Ret PlaybackController::toggleAutomaticallyPan()
     947              : {
     948            0 :     bool panEnabled = notationConfiguration()->isAutomaticallyPanEnabled();
     949            0 :     notationConfiguration()->setIsAutomaticallyPanEnabled(!panEnabled);
     950            0 :     return make_ok();
     951              : }
     952              : 
     953            0 : muse::Ret PlaybackController::toggleMetronome()
     954              : {
     955            0 :     bool metronomeEnabled = notationConfiguration()->isMetronomeEnabled();
     956            0 :     bool countInEnabled = notationConfiguration()->isCountInEnabled();
     957              : 
     958            0 :     notationConfiguration()->setIsMetronomeEnabled(!metronomeEnabled);
     959              : 
     960            0 :     setTrackActivity(notationPlayback()->metronomeTrackId(), !metronomeEnabled || countInEnabled);
     961              : 
     962            0 :     return make_ok();
     963              : }
     964              : 
     965            0 : muse::Ret PlaybackController::toggleCountIn()
     966              : {
     967            0 :     bool metronomeEnabled = notationConfiguration()->isMetronomeEnabled();
     968            0 :     bool countInEnabled = notationConfiguration()->isCountInEnabled();
     969              : 
     970            0 :     notationConfiguration()->setIsCountInEnabled(!countInEnabled);
     971              : 
     972            0 :     setTrackActivity(notationPlayback()->metronomeTrackId(), metronomeEnabled || !countInEnabled);
     973              : 
     974            0 :     return make_ok();
     975              : }
     976              : 
     977            0 : muse::Ret PlaybackController::toggleMidiInput()
     978              : {
     979            0 :     bool wasMidiInputEnabled = notationConfiguration()->isMidiInputEnabled();
     980            0 :     notationConfiguration()->setIsMidiInputEnabled(!wasMidiInputEnabled);
     981            0 :     return make_ok();
     982              : }
     983              : 
     984            0 : muse::Ret PlaybackController::setMidiUseWrittenPitch(bool useWrittenPitch)
     985              : {
     986            0 :     notationConfiguration()->setMidiUseWrittenPitch(useWrittenPitch);
     987            0 :     return make_ok();
     988              : }
     989              : 
     990            0 : muse::Ret PlaybackController::toggleLoopPlayback()
     991              : {
     992            0 :     if (isLoopEnabled()) {
     993            0 :         disableLoop();
     994            0 :         return make_ok();
     995              :     }
     996              : 
     997            0 :     if (loopBoundariesSet() && !selection()->isRange()) {
     998            0 :         enableLoop();
     999            0 :         return make_ok();
    1000              :     }
    1001              : 
    1002            0 :     int loopInTick = 0;
    1003            0 :     int loopOutTick = 0;
    1004              : 
    1005            0 :     if (!selection()->isNone()) {
    1006            0 :         loopInTick = selectionRange()->startTick().ticks();
    1007            0 :         loopOutTick = selectionRange()->endTick().ticks();
    1008              :     }
    1009              : 
    1010            0 :     if (loopInTick <= 0) {
    1011            0 :         loopInTick = INotationPlayback::FirstScoreTick;
    1012              :     }
    1013              : 
    1014            0 :     if (loopOutTick <= 0) {
    1015            0 :         loopOutTick = INotationPlayback::LastScoreTick;
    1016              :     }
    1017              : 
    1018            0 :     addLoopBoundaryToTick(LoopBoundaryType::LoopIn, loopInTick);
    1019            0 :     addLoopBoundaryToTick(LoopBoundaryType::LoopOut, loopOutTick);
    1020              : 
    1021            0 :     return make_ok();
    1022              : }
    1023              : 
    1024            0 : muse::Ret PlaybackController::toggleHearPlaybackWhenEditing()
    1025              : {
    1026            0 :     bool wasPlayNotesWhenEditing = configuration()->playNotesWhenEditing();
    1027            0 :     configuration()->setPlayNotesWhenEditing(!wasPlayNotesWhenEditing);
    1028            0 :     return make_ok();
    1029              : }
    1030              : 
    1031            0 : muse::Ret PlaybackController::reloadPlaybackCache()
    1032              : {
    1033            0 :     INotationPlaybackPtr nPlayback = notationPlayback();
    1034            0 :     if (nPlayback) {
    1035            0 :         nPlayback->reload();
    1036              :     }
    1037              : 
    1038            0 :     return make_ok();
    1039            0 : }
    1040              : 
    1041            0 : muse::Ret PlaybackController::addLoopBoundary(LoopBoundaryType type)
    1042              : {
    1043            0 :     if (isPlaying()) {
    1044            0 :         addLoopBoundaryToTick(type, m_currentTick);
    1045              :     } else {
    1046            0 :         addLoopBoundaryToTick(type, INotationPlayback::SelectedNoteTick);
    1047              :     }
    1048              : 
    1049            0 :     return make_ok();
    1050              : }
    1051              : 
    1052            0 : void PlaybackController::addLoopBoundaryToTick(LoopBoundaryType type, int tick)
    1053              : {
    1054            0 :     if (notationPlayback()) {
    1055            0 :         notationPlayback()->addLoopBoundary(type, tick);
    1056            0 :         enableLoop();
    1057              :     }
    1058            0 : }
    1059              : 
    1060            0 : void PlaybackController::updateLoop()
    1061              : {
    1062            0 :     if (!notationPlayback() || !currentPlayer()) {
    1063            0 :         return;
    1064              :     }
    1065              : 
    1066            0 :     const LoopBoundaries& boundaries = notationPlayback()->loopBoundaries();
    1067              : 
    1068            0 :     if (!boundaries.enabled) {
    1069            0 :         disableLoop();
    1070            0 :         return;
    1071              :     }
    1072              : 
    1073              :     // Convert from raw ticks (visual tick != playback tick due to repeats etc)
    1074            0 :     RetVal<tick_t> playbackTickFrom = notationPlayback()->playPositionTickByRawTick(boundaries.loopInTick.ticks());
    1075            0 :     RetVal<tick_t> playbackTickTo = notationPlayback()->playPositionTickByRawTick(boundaries.loopOutTick.ticks());
    1076            0 :     if (!playbackTickFrom.ret || !playbackTickTo.ret) {
    1077            0 :         return;
    1078              :     }
    1079              : 
    1080            0 :     secs_t fromSecs = playedTickToSecs(playbackTickFrom.val);
    1081            0 :     secs_t toSecs = playedTickToSecs(playbackTickTo.val);
    1082            0 :     currentPlayer()->setLoop(fromSecs, toSecs);
    1083              : 
    1084            0 :     enableLoop();
    1085            0 : }
    1086              : 
    1087            0 : void PlaybackController::enableLoop()
    1088              : {
    1089            0 :     if (notationPlayback()) {
    1090            0 :         notationPlayback()->setLoopBoundariesEnabled(true);
    1091              :     }
    1092            0 : }
    1093              : 
    1094            0 : void PlaybackController::disableLoop()
    1095              : {
    1096            0 :     IF_ASSERT_FAILED(notationPlayback() && currentPlayer()) {
    1097            0 :         return;
    1098              :     }
    1099              : 
    1100            0 :     currentPlayer()->resetLoop();
    1101            0 :     notationPlayback()->setLoopBoundariesEnabled(false);
    1102              : }
    1103              : 
    1104            0 : mu::project::IProjectAudioSettingsPtr PlaybackController::audioSettings() const
    1105              : {
    1106            0 :     INotationProjectPtr project = projectSession()->project();
    1107            0 :     IF_ASSERT_FAILED(project) {
    1108            0 :         return nullptr;
    1109              :     }
    1110              : 
    1111            0 :     return project->audioSettings();
    1112            0 : }
    1113              : 
    1114            0 : void PlaybackController::resetPlayback()
    1115              : {
    1116            0 :     auto player = m_player;
    1117              : 
    1118            0 :     m_player = nullptr;
    1119            0 :     m_currentTick = 0;
    1120              : 
    1121            0 :     if (player) {
    1122            0 :         player->playbackPositionChanged().disconnect(this);
    1123            0 :         player->playbackStatusChanged().disconnect(this);
    1124              : 
    1125            0 :         player->stop();
    1126            0 :         player->seek(0);
    1127            0 :         m_playbackPositionChanged.send(0);
    1128            0 :         m_isPlayingChanged.send(false);
    1129              :     }
    1130              : 
    1131            0 :     playback()->clearSources();
    1132            0 :     playback()->sourceParamsChanged().disconnect(this);
    1133            0 :     playback()->controlParamsChanged().disconnect(this);
    1134            0 :     playback()->fxChainParamsChanged().disconnect(this);
    1135            0 :     playback()->auxSendsParamsChanged().disconnect(this);
    1136            0 :     playback()->clearAllFx();
    1137            0 :     playback()->masterControlParamsChanged().disconnect(this);
    1138            0 :     playback()->masterFxChainParamsChanged().disconnect(this);
    1139            0 :     playback()->masterAuxSendsParamsChanged().disconnect(this);
    1140            0 :     playback()->clearMasterOutputParams();
    1141              : 
    1142            0 :     m_seqAsyncReceiver.async_disconnectAll();
    1143              : 
    1144            0 :     m_instrumentTrackIdMap.clear();
    1145            0 :     m_auxTrackIdMap.clear();
    1146              : 
    1147            0 :     m_isRangeSelection = false;
    1148              : 
    1149            0 :     m_isPlaybackInited = false;
    1150            0 :     m_playbackInited.send(m_isPlaybackInited);
    1151              : 
    1152            0 :     m_onlineSoundsController->reset();
    1153            0 : }
    1154              : 
    1155            0 : void PlaybackController::addTrack(const InstrumentTrackId& instrumentTrackId, bool projectHadNoAudioSettings,
    1156              :                                   const TrackAddFinished& onFinished)
    1157              : {
    1158            0 :     if (notationPlayback()->metronomeTrackId() == instrumentTrackId) {
    1159            0 :         doAddTrack(instrumentTrackId, muse::trc("playback", "Metronome"), projectHadNoAudioSettings, onFinished);
    1160            0 :         return;
    1161              :     }
    1162              : 
    1163            0 :     const Part* part = masterNotationParts()->part(instrumentTrackId.partId);
    1164            0 :     if (!part) {
    1165            0 :         return;
    1166              :     }
    1167              : 
    1168            0 :     if (notationPlayback()->isChordSymbolsTrack(instrumentTrackId)) {
    1169            0 :         const std::string trackName = muse::trc("playback", "Chords") + "." + part->partName().toStdString();
    1170            0 :         doAddTrack(instrumentTrackId, trackName, projectHadNoAudioSettings, onFinished);
    1171            0 :         return;
    1172            0 :     }
    1173              : 
    1174            0 :     const muse::String primaryInstrId = part->instrument()->id();
    1175            0 :     if (instrumentTrackId.instrumentId == primaryInstrId) {
    1176            0 :         const std::string trackName = part->partName().toStdString();
    1177            0 :         doAddTrack(instrumentTrackId, trackName, projectHadNoAudioSettings, onFinished);
    1178            0 :         return;
    1179            0 :     }
    1180              : 
    1181            0 :     const Instrument* instrument = part->instrumentById(instrumentTrackId.instrumentId);
    1182            0 :     if (instrument != nullptr) {
    1183            0 :         std::string trackName = "(" + instrument->trackName().toStdString() + ")";
    1184            0 :         doAddTrack(instrumentTrackId, trackName, projectHadNoAudioSettings, onFinished);
    1185            0 :     }
    1186            0 : }
    1187              : 
    1188            0 : void PlaybackController::doAddTrack(const InstrumentTrackId& instrumentTrackId, const std::string& title,
    1189              :                                     bool projectHadNoAudioSettings, const TrackAddFinished& onFinished)
    1190              : {
    1191            0 :     IF_ASSERT_FAILED(notationPlayback() && playback()) {
    1192            0 :         return;
    1193              :     }
    1194              : 
    1195            0 :     if (!instrumentTrackId.isValid()) {
    1196            0 :         return;
    1197              :     }
    1198              : 
    1199            0 :     mpe::PlaybackData playbackData = notationPlayback()->trackPlaybackData(instrumentTrackId);
    1200            0 :     if (!playbackData.isValid()) {
    1201            0 :         return;
    1202              :     }
    1203              : 
    1204            0 :     AudioInputParams inParams = audioSettings()->trackInputParams(instrumentTrackId);
    1205            0 :     AudioOutputParams originParams = trackOutputParams(instrumentTrackId);
    1206            0 :     AudioResourceMeta originMeta = inParams.resourceMeta;
    1207              : 
    1208            0 :     bool isMetronome = notationPlayback()->metronomeTrackId() == instrumentTrackId;
    1209              : 
    1210            0 :     if (!inParams.isValid()) {
    1211            0 :         if (isMetronome) {
    1212            0 :             const SoundProfile& profile = profilesRepo()->profile(configuration()->basicSoundProfileName());
    1213            0 :             inParams = { profile.findResource(playbackData.setupData), {} };
    1214              :         } else {
    1215            0 :             const SoundProfile& profile = profilesRepo()->profile(audioSettings()->activeSoundProfile());
    1216            0 :             inParams = { profile.findResource(playbackData.setupData), {} };
    1217              :         }
    1218              :     }
    1219              : 
    1220            0 :     if (!isMetronome && originParams.auxSends.empty()) {
    1221            0 :         const muse::String& instrumentSoundId = inParams.resourceMeta.attributeVal(PLAYBACK_SETUP_DATA_ATTRIBUTE);
    1222            0 :         AudioSourceType sourceType = inParams.isValid() ? inParams.type() : AudioSourceType::Fluid;
    1223              : 
    1224            0 :         for (aux_channel_idx_t idx = 0; idx < AUX_CHANNEL_NUM; ++idx) {
    1225            0 :             gain_t signalAmount = configuration()->defaultAuxSendValue(idx, sourceType, instrumentSoundId);
    1226            0 :             originParams.auxSends.emplace_back(AuxSendParams { signalAmount, true });
    1227              :         }
    1228              :     }
    1229              : 
    1230            0 :     uint64_t playbackKey = notationPlaybackKey();
    1231              : 
    1232            0 :     TrackParams trackParams;
    1233            0 :     trackParams.source = inParams;
    1234            0 :     trackParams.fxChain = originParams.fxChain;
    1235            0 :     trackParams.auxSends = originParams.auxSends;
    1236            0 :     trackParams.control = trackControlParams(instrumentTrackId, originParams);
    1237              : 
    1238              :     struct {
    1239              :         uint64_t playbackKey;
    1240              :         InstrumentTrackId instrumentTrackId;
    1241              :         bool projectHadNoAudioSettings;
    1242              :         AudioOutputParams originParams;
    1243              :         TrackAddFinished onFinished;
    1244              :         AudioResourceMeta originMeta;
    1245            0 :     } tctx = { playbackKey, instrumentTrackId, projectHadNoAudioSettings, originParams, onFinished,  originMeta };
    1246              : 
    1247            0 :     playback()->addTrack(title, std::move(playbackData), trackParams)
    1248            0 :     .onResolve(this, [this, tctx](const TrackId trackId, const TrackParams& appliedParams) {
    1249              :         //! NOTE It may be that while we were adding a track, the notation was already closed (or opened another)
    1250              :         //! This situation can be if the notation was opened and immediately closed.
    1251            0 :         if (notationPlaybackKey() != tctx.playbackKey) {
    1252            0 :             return;
    1253              :         }
    1254              : 
    1255              :         // context variables
    1256            0 :         const InstrumentTrackId instrumentTrackId = tctx.instrumentTrackId;
    1257            0 :         const bool projectHadNoAudioSettings = tctx.projectHadNoAudioSettings;
    1258            0 :         const AudioOutputParams& originParams = tctx.originParams;
    1259            0 :         const TrackAddFinished onFinished = tctx.onFinished;
    1260            0 :         const AudioResourceMeta& originMeta = tctx.originMeta;
    1261              : 
    1262            0 :         m_instrumentTrackIdMap.insert({ instrumentTrackId, trackId });
    1263              : 
    1264              :         const bool trackNewlyAdded = projectHadNoAudioSettings
    1265            0 :                                      || !audioSettings()->trackHasExistingOutputParams(instrumentTrackId);
    1266              : 
    1267            0 :         auto appliedOutParams = originParams;
    1268            0 :         appliedOutParams.fxChain = appliedParams.fxChain;
    1269              : 
    1270            0 :         audioSettings()->setTrackInputParams(instrumentTrackId, appliedParams.source, !projectHadNoAudioSettings);
    1271            0 :         audioSettings()->setTrackOutputParams(instrumentTrackId, appliedOutParams, !projectHadNoAudioSettings);
    1272              : 
    1273            0 :         updateSoloMuteStates();
    1274              : 
    1275            0 :         onFinished();
    1276              : 
    1277            0 :         m_trackAdded.send(trackId);
    1278              : 
    1279            0 :         if (trackNewlyAdded) {
    1280            0 :             onTrackNewlyAdded(instrumentTrackId);
    1281              :         }
    1282              : 
    1283            0 :         if (shouldLoadDrumset(instrumentTrackId, originMeta, appliedParams.source.resourceMeta)) {
    1284            0 :             m_drumsetLoader.loadDrumset(m_notation, instrumentTrackId, appliedParams.source.resourceMeta);
    1285              :         }
    1286              : 
    1287            0 :         if (muse::audio::isOnlineAudioResource(appliedParams.source.resourceMeta)) {
    1288            0 :             m_onlineSoundsController->addOnlineTrack(trackId, appliedParams.source.resourceMeta);
    1289              : 
    1290            0 :             if (notationPlayback()) {
    1291            0 :                 notationPlayback()->setSendEventsOnScoreChange(instrumentTrackId, true);
    1292              :             }
    1293              :         }
    1294            0 :     })
    1295            0 :     .onReject(this, [instrumentTrackId, onFinished](int code, const std::string& msg) {
    1296            0 :         LOGE() << "can't add a new track, code: [" << code << "] " << msg;
    1297              : 
    1298            0 :         onFinished();
    1299            0 :     });
    1300              : 
    1301            0 :     m_loadingTrackCount++;
    1302            0 : }
    1303              : 
    1304            0 : void PlaybackController::addAuxTrack(aux_channel_idx_t index, bool projectHadNoAudioSettings, const TrackAddFinished& onFinished)
    1305              : {
    1306            0 :     IF_ASSERT_FAILED(notationPlayback() && playback()) {
    1307            0 :         return;
    1308              :     }
    1309              : 
    1310            0 :     AudioOutputParams originParams;
    1311              : 
    1312            0 :     if (audioSettings()->containsAuxOutputParams(index)) {
    1313            0 :         originParams = audioSettings()->auxOutputParams(index);
    1314            0 :     } else if (index == REVERB_CHANNEL_IDX) {
    1315            0 :         originParams = makeReverbOutputParams();
    1316              :     }
    1317              : 
    1318            0 :     TrackParams trackParams;
    1319            0 :     trackParams.source = {};
    1320            0 :     trackParams.fxChain = originParams.fxChain;
    1321            0 :     trackParams.auxSends = originParams.auxSends;
    1322            0 :     trackParams.control = originParams.control();
    1323              : 
    1324            0 :     std::string title = resolveAuxTrackTitle(index, originParams, false);
    1325            0 :     uint64_t playbackKey = notationPlaybackKey();
    1326              : 
    1327              :     struct {
    1328              :         uint64_t playbackKey;
    1329              :         aux_channel_idx_t index;
    1330              :         bool projectHadNoAudioSettings;
    1331              :         AudioOutputParams originParams;
    1332              :         TrackAddFinished onFinished;
    1333            0 :     } tctx = { playbackKey, index, projectHadNoAudioSettings, originParams, onFinished };
    1334              : 
    1335            0 :     playback()->addAuxTrack(title, trackParams)
    1336            0 :     .onResolve(this, [this, tctx](const TrackId trackId, const TrackParams& appliedParams) {
    1337              :         //! NOTE It may be that while we were adding a track, the notation was already closed (or opened another)
    1338              :         //! This situation can be if the notation was opened and immediately closed.
    1339            0 :         if (notationPlaybackKey() != tctx.playbackKey) {
    1340            0 :             return;
    1341              :         }
    1342              : 
    1343              :         // context variables
    1344            0 :         const aux_channel_idx_t index = tctx.index;
    1345            0 :         const bool projectHadNoAudioSettings = tctx.projectHadNoAudioSettings;
    1346            0 :         const AudioOutputParams& originParams = tctx.originParams;
    1347            0 :         const TrackAddFinished onFinished = tctx.onFinished;
    1348              : 
    1349            0 :         m_auxTrackIdMap.insert({ index, trackId });
    1350              : 
    1351            0 :         auto appliedOutParams = originParams;
    1352            0 :         appliedOutParams.fxChain = appliedParams.fxChain;
    1353              : 
    1354            0 :         audioSettings()->setAuxOutputParams(index, appliedOutParams, !projectHadNoAudioSettings);
    1355              : 
    1356            0 :         updateSoloMuteStates();
    1357            0 :         onFinished();
    1358              : 
    1359            0 :         m_trackAdded.send(trackId);
    1360            0 :     })
    1361            0 :     .onReject(this, [onFinished](int code, const std::string& msg) {
    1362            0 :         LOGE() << "can't add a new aux track, code: [" << code << "] " << msg;
    1363              : 
    1364            0 :         onFinished();
    1365            0 :     });
    1366              : 
    1367            0 :     m_loadingTrackCount++;
    1368            0 : }
    1369              : 
    1370            0 : void PlaybackController::setTrackActivity(const engraving::InstrumentTrackId& instrumentTrackId, const bool isActive)
    1371              : {
    1372            0 :     IF_ASSERT_FAILED(audioSettings() && playback()) {
    1373            0 :         return;
    1374              :     }
    1375              : 
    1376            0 :     AudioOutputParams outParams = audioSettings()->trackOutputParams(instrumentTrackId);
    1377            0 :     ControlParams control = trackControlParams(instrumentTrackId, outParams, /*rebuildVolume*/ false, /*rebuildPan*/ false);
    1378              : 
    1379            0 :     control.muted = !isActive;
    1380              : 
    1381            0 :     audio::TrackId trackId = m_instrumentTrackIdMap[instrumentTrackId];
    1382            0 :     playback()->setControlParams(trackId, control);
    1383            0 : }
    1384              : 
    1385            0 : AudioOutputParams PlaybackController::trackOutputParams(const InstrumentTrackId& instrumentTrackId) const
    1386              : {
    1387            0 :     IF_ASSERT_FAILED(audioSettings() && notationConfiguration() && notationPlayback()) {
    1388            0 :         return {};
    1389              :     }
    1390              : 
    1391            0 :     AudioOutputParams result = audioSettings()->trackOutputParams(instrumentTrackId);
    1392              : 
    1393            0 :     if (instrumentTrackId == notationPlayback()->metronomeTrackId()) {
    1394            0 :         result.muted = !notationConfiguration()->isMetronomeEnabled() && !notationConfiguration()->isCountInEnabled();
    1395            0 :         return result;
    1396              :     }
    1397              : 
    1398            0 :     if (notationPlayback()->isChordSymbolsTrack(instrumentTrackId)) {
    1399            0 :         result.muted = !notationConfiguration()->isPlayChordSymbolsEnabled();
    1400              :     }
    1401              : 
    1402            0 :     return result;
    1403            0 : }
    1404              : 
    1405            0 : ControlParams PlaybackController::trackControlParams(const InstrumentTrackId& instrumentTrackId,
    1406              :                                                      const AudioOutputParams& outParams,
    1407              :                                                      bool rebuildVolume, bool rebuildPan)
    1408              : {
    1409            0 :     ControlParams control = outParams.control();
    1410            0 :     const auto it = m_automatedControlParamsCache.find(instrumentTrackId);
    1411            0 :     if (it != m_automatedControlParamsCache.end()) {
    1412            0 :         control.volume = it->second.volume;
    1413            0 :         control.balance = it->second.balance;
    1414              :     }
    1415              : 
    1416            0 :     if (!rebuildVolume && !rebuildPan) {
    1417            0 :         return control;
    1418              :     }
    1419              : 
    1420            0 :     const INotationAutomationPtr automation = m_masterNotation ? m_masterNotation->automation() : nullptr;
    1421            0 :     const AutomationDataConstPtr automationData = automation ? automation->automationData() : nullptr;
    1422            0 :     if (!automationData || !instrumentTrackId.isValid()) {
    1423            0 :         m_automatedControlParamsCache.erase(instrumentTrackId);
    1424            0 :         return outParams.control();
    1425              :     }
    1426              : 
    1427            0 :     auto buildAutomationEnvelope = [this](const AutomationCurve& curve) {
    1428            0 :         muse::audio::AutomationEnvelope envelope;
    1429            0 :         for (const auto& [tick, point] : curve) {
    1430            0 :             envelope.insert({ playedTickToSecs(tick), point.value });
    1431              :         }
    1432            0 :         return envelope;
    1433            0 :     };
    1434              : 
    1435            0 :     if (rebuildVolume) {
    1436            0 :         const AutomationCurve& volumeCurve = automationData->curve(AutomationCurveKey::instrument(AutomationType::Volume,
    1437              :                                                                                                   instrumentTrackId));
    1438            0 :         control.volume = !volumeCurve.empty() ? AutomatableValue<volume_db_t>(buildAutomationEnvelope(volumeCurve))
    1439            0 :                          : AutomatableValue<volume_db_t>(outParams.volume);
    1440              :     }
    1441              : 
    1442            0 :     if (rebuildPan) {
    1443            0 :         const AutomationCurve& panCurve = automationData->curve(AutomationCurveKey::instrument(AutomationType::Pan, instrumentTrackId));
    1444            0 :         control.balance = !panCurve.empty() ? AutomatableValue<balance_t>(buildAutomationEnvelope(panCurve))
    1445            0 :                           : AutomatableValue<balance_t>(outParams.balance);
    1446              :     }
    1447              : 
    1448            0 :     m_automatedControlParamsCache[instrumentTrackId] = control;
    1449              : 
    1450            0 :     return control;
    1451            0 : }
    1452              : 
    1453            0 : void PlaybackController::onAutomationDataChanged(const AutomationChanges& changes)
    1454              : {
    1455            0 :     if (changes.isFullReset) {
    1456            0 :         resendAutomatedControlParams();
    1457            0 :         return;
    1458              :     }
    1459              : 
    1460            0 :     InstrumentTrackIdSet volumeTrackIds;
    1461            0 :     InstrumentTrackIdSet panTrackIds;
    1462              : 
    1463            0 :     for (const AutomationCurveKey& key : changes.affectedKeys) {
    1464            0 :         const std::optional<InstrumentTrackId> trackId = key.trackId();
    1465            0 :         if (!trackId) {
    1466            0 :             continue;
    1467              :         }
    1468              : 
    1469            0 :         if (key.type == AutomationType::Volume) {
    1470            0 :             volumeTrackIds.insert(*trackId);
    1471            0 :         } else if (key.type == AutomationType::Pan) {
    1472            0 :             panTrackIds.insert(*trackId);
    1473              :         }
    1474            0 :     }
    1475              : 
    1476            0 :     if (volumeTrackIds.empty() && panTrackIds.empty()) {
    1477            0 :         return;
    1478              :     }
    1479              : 
    1480            0 :     resendAutomatedControlParams(volumeTrackIds, panTrackIds);
    1481            0 : }
    1482              : 
    1483            0 : void PlaybackController::resendAutomatedControlParams(std::optional<InstrumentTrackIdSet> volumeTrackIds,
    1484              :                                                       std::optional<InstrumentTrackIdSet> panTrackIds)
    1485              : {
    1486            0 :     if (!playback()) {
    1487            0 :         return;
    1488              :     }
    1489              : 
    1490            0 :     for (const auto& pair : m_instrumentTrackIdMap) {
    1491            0 :         const bool rebuildVolume = !volumeTrackIds || muse::contains(*volumeTrackIds, pair.first);
    1492            0 :         const bool rebuildPan = !panTrackIds || muse::contains(*panTrackIds, pair.first);
    1493              : 
    1494            0 :         if (!rebuildVolume && !rebuildPan) {
    1495            0 :             continue;
    1496              :         }
    1497              : 
    1498            0 :         const AudioOutputParams outParams = trackOutputParams(pair.first);
    1499            0 :         playback()->setControlParams(pair.second, trackControlParams(pair.first, outParams, rebuildVolume, rebuildPan));
    1500            0 :     }
    1501              : }
    1502              : 
    1503            0 : void PlaybackController::removeTrack(const InstrumentTrackId& instrumentTrackId)
    1504              : {
    1505            0 :     IF_ASSERT_FAILED(notationPlayback() && playback()) {
    1506            0 :         return;
    1507              :     }
    1508              : 
    1509            0 :     auto search = m_instrumentTrackIdMap.find(instrumentTrackId);
    1510              : 
    1511            0 :     if (search == m_instrumentTrackIdMap.end()) {
    1512            0 :         return;
    1513              :     }
    1514              : 
    1515            0 :     playback()->removeTrack(search->second);
    1516            0 :     audioSettings()->removeTrackParams(instrumentTrackId);
    1517              : 
    1518            0 :     m_masterNotation->notation()->soloMuteState()->removeTrackSoloMuteState(instrumentTrackId);
    1519            0 :     for (const IExcerptNotationPtr& excerpt : m_masterNotation->excerpts()) {
    1520            0 :         if (const INotationPtr& notation = excerpt->notation()) {
    1521            0 :             notation->soloMuteState()->removeTrackSoloMuteState(instrumentTrackId);
    1522            0 :         }
    1523              :     }
    1524              : 
    1525            0 :     m_onlineSoundsController->removeOnlineTrack(search->second);
    1526              : 
    1527            0 :     m_trackRemoved.send(search->second);
    1528            0 :     m_instrumentTrackIdMap.erase(instrumentTrackId);
    1529            0 :     m_automatedControlParamsCache.erase(instrumentTrackId);
    1530              : }
    1531              : 
    1532            0 : void PlaybackController::onTrackNewlyAdded(const InstrumentTrackId& instrumentTrackId)
    1533              : {
    1534            0 :     for (const IExcerptNotationPtr& excerpt : m_masterNotation->excerpts()) {
    1535            0 :         if (const INotationPtr& notation = excerpt->notation()) {
    1536            0 :             if (notation == m_notation || notation->soloMuteState()->trackSoloMuteStateExists(instrumentTrackId)) {
    1537            0 :                 continue;
    1538              :             }
    1539              : 
    1540            0 :             const Part* part = notation->parts()->part(instrumentTrackId.partId);
    1541            0 :             const bool shouldMute = !part || !part->show();
    1542              : 
    1543            0 :             const INotationSoloMuteState::SoloMuteState soloMuteState = { shouldMute, /*solo*/ false };
    1544            0 :             notation->soloMuteState()->setTrackSoloMuteState(instrumentTrackId, soloMuteState);
    1545            0 :         }
    1546              :     }
    1547            0 : }
    1548              : 
    1549            7 : void PlaybackController::setupPlayback()
    1550              : {
    1551            7 :     playback()->removeAllTracks();
    1552              : 
    1553            7 :     m_player = playback()->player();
    1554              : 
    1555            7 :     if (!notationPlayback()) {
    1556            7 :         return;
    1557              :     }
    1558              : 
    1559            0 :     const AudioOutputParams& masterOutputParams = audioSettings()->masterAudioOutputParams();
    1560            0 :     playback()->setMasterFxChainParams(masterOutputParams.fxChain);
    1561            0 :     playback()->setMasterAuxSendsParams(masterOutputParams.auxSends);
    1562            0 :     playback()->setMasterControlParams(masterOutputParams.control());
    1563              : 
    1564            0 :     subscribeOnAudioParamsChanges();
    1565            0 :     setupTracks();
    1566            0 :     setupPlayer();
    1567              : 
    1568            0 :     m_isPlaybackInited = true;
    1569            0 :     m_playbackInited.send(m_isPlaybackInited);
    1570              : }
    1571              : 
    1572            0 : void PlaybackController::subscribeOnAudioParamsChanges()
    1573              : {
    1574            0 :     playback()->masterControlParamsChanged().onReceive(this, [this](const ControlParams& params) {
    1575            0 :         AudioOutputParams outParams = audioSettings()->masterAudioOutputParams();
    1576            0 :         outParams.setControl(params);
    1577            0 :         audioSettings()->setMasterAudioOutputParams(outParams);
    1578            0 :     });
    1579              : 
    1580            0 :     playback()->masterFxChainParamsChanged().onReceive(this, [this](const AudioFxChain& params) {
    1581            0 :         AudioOutputParams outParams = audioSettings()->masterAudioOutputParams();
    1582            0 :         outParams.fxChain = params;
    1583            0 :         audioSettings()->setMasterAudioOutputParams(outParams);
    1584            0 :     });
    1585              : 
    1586            0 :     playback()->masterAuxSendsParamsChanged().onReceive(this, [this](const AuxSendsParams& params) {
    1587            0 :         AudioOutputParams outParams = audioSettings()->masterAudioOutputParams();
    1588            0 :         outParams.auxSends = params;
    1589            0 :         audioSettings()->setMasterAudioOutputParams(outParams);
    1590            0 :     });
    1591              : 
    1592            0 :     playback()->sourceParamsChanged().onReceive(this, [this](const TrackId trackId, const AudioInputParams& params) {
    1593            0 :         auto search = std::find_if(m_instrumentTrackIdMap.begin(), m_instrumentTrackIdMap.end(), [trackId](const auto& pair) {
    1594            0 :             return pair.second == trackId;
    1595              :         });
    1596              : 
    1597            0 :         if (search != m_instrumentTrackIdMap.end()) {
    1598            0 :             const AudioResourceMeta& oldMeta = audioSettings()->trackInputParams(search->first).resourceMeta;
    1599            0 :             onAudioResourceChanged(trackId, search->first, oldMeta, params.resourceMeta);
    1600              : 
    1601            0 :             audioSettings()->setTrackInputParams(search->first, params);
    1602              :         }
    1603            0 :     });
    1604              : 
    1605            0 :     playback()->fxChainParamsChanged().onReceive(this, [this](const TrackId trackId, const AudioFxChain& params) {
    1606            0 :         auto instrumentIt = std::find_if(m_instrumentTrackIdMap.begin(), m_instrumentTrackIdMap.end(), [trackId](const auto& pair) {
    1607            0 :             return pair.second == trackId;
    1608              :         });
    1609              : 
    1610            0 :         if (instrumentIt != m_instrumentTrackIdMap.end()) {
    1611            0 :             AudioOutputParams outParams = audioSettings()->trackOutputParams(instrumentIt->first);
    1612            0 :             outParams.fxChain = params;
    1613            0 :             audioSettings()->setTrackOutputParams(instrumentIt->first, outParams);
    1614            0 :             return;
    1615            0 :         }
    1616              : 
    1617            0 :         auto auxIt = std::find_if(m_auxTrackIdMap.begin(), m_auxTrackIdMap.end(), [trackId](const auto& pair) {
    1618            0 :             return pair.second == trackId;
    1619              :         });
    1620              : 
    1621            0 :         if (auxIt != m_auxTrackIdMap.end()) {
    1622            0 :             aux_channel_idx_t auxIdx = auxIt->first;
    1623            0 :             std::string oldName = resolveAuxTrackTitle(auxIdx, audioSettings()->auxOutputParams(auxIdx));
    1624            0 :             AudioOutputParams outParams = audioSettings()->auxOutputParams(auxIdx);
    1625            0 :             outParams.fxChain = params;
    1626            0 :             std::string newName = resolveAuxTrackTitle(auxIdx, outParams);
    1627              : 
    1628            0 :             audioSettings()->setAuxOutputParams(auxIdx, outParams);
    1629              : 
    1630            0 :             if (oldName != newName) {
    1631            0 :                 m_auxChannelNameChanged.send(auxIdx, newName);
    1632              :             }
    1633            0 :         }
    1634              :     });
    1635              : 
    1636            0 :     playback()->controlParamsChanged().onReceive(this, [this](const TrackId trackId, const ControlParams& params) {
    1637            0 :         auto instrumentIt = std::find_if(m_instrumentTrackIdMap.begin(), m_instrumentTrackIdMap.end(), [trackId](const auto& pair) {
    1638            0 :             return pair.second == trackId;
    1639              :         });
    1640              : 
    1641            0 :         if (instrumentIt != m_instrumentTrackIdMap.end()) {
    1642            0 :             AudioOutputParams outParams = audioSettings()->trackOutputParams(instrumentIt->first);
    1643            0 :             outParams.setControl(params);
    1644            0 :             audioSettings()->setTrackOutputParams(instrumentIt->first, outParams);
    1645            0 :             return;
    1646            0 :         }
    1647              : 
    1648            0 :         auto auxIt = std::find_if(m_auxTrackIdMap.begin(), m_auxTrackIdMap.end(), [trackId](const auto& pair) {
    1649            0 :             return pair.second == trackId;
    1650              :         });
    1651              : 
    1652            0 :         if (auxIt != m_auxTrackIdMap.end()) {
    1653            0 :             aux_channel_idx_t auxIdx = auxIt->first;
    1654            0 :             AudioOutputParams outParams = audioSettings()->auxOutputParams(auxIdx);
    1655            0 :             outParams.setControl(params);
    1656            0 :             audioSettings()->setAuxOutputParams(auxIdx, outParams);
    1657            0 :         }
    1658              :     });
    1659              : 
    1660            0 :     playback()->auxSendsParamsChanged().onReceive(this, [this](const TrackId trackId, const AuxSendsParams& params) {
    1661            0 :         auto instrumentIt = std::find_if(m_instrumentTrackIdMap.begin(), m_instrumentTrackIdMap.end(), [trackId](const auto& pair) {
    1662            0 :             return pair.second == trackId;
    1663              :         });
    1664              : 
    1665            0 :         if (instrumentIt != m_instrumentTrackIdMap.end()) {
    1666            0 :             AudioOutputParams outParams = audioSettings()->trackOutputParams(instrumentIt->first);
    1667            0 :             outParams.auxSends = params;
    1668            0 :             audioSettings()->setTrackOutputParams(instrumentIt->first, outParams);
    1669            0 :             return;
    1670            0 :         }
    1671              : 
    1672            0 :         auto auxIt = std::find_if(m_auxTrackIdMap.begin(), m_auxTrackIdMap.end(), [trackId](const auto& pair) {
    1673            0 :             return pair.second == trackId;
    1674              :         });
    1675              : 
    1676            0 :         if (auxIt != m_auxTrackIdMap.end()) {
    1677            0 :             aux_channel_idx_t auxIdx = auxIt->first;
    1678            0 :             AudioOutputParams outParams = audioSettings()->auxOutputParams(auxIdx);
    1679            0 :             outParams.auxSends = params;
    1680            0 :             audioSettings()->setAuxOutputParams(auxIdx, outParams);
    1681            0 :         }
    1682              :     });
    1683            0 : }
    1684              : 
    1685            0 : void PlaybackController::setupTracks()
    1686              : {
    1687            0 :     m_instrumentTrackIdMap.clear();
    1688              : 
    1689            0 :     if (!masterNotationParts()) {
    1690            0 :         return;
    1691              :     }
    1692              : 
    1693            0 :     const bool projectHadNoAudioSettings = !audioSettings()->hasAnyAudioSettings();
    1694              : 
    1695            0 :     m_loadingTrackCount = 0;
    1696              : 
    1697            0 :     InstrumentTrackIdSet trackIdSet = notationPlayback()->existingTrackIdSet();
    1698            0 :     size_t trackCount = trackIdSet.size() + AUX_CHANNEL_NUM;
    1699            0 :     std::string title = muse::trc("playback", "Loading audio samples");
    1700              : 
    1701            0 :     auto onAddFinished = [this, trackCount, title]() {
    1702            0 :         m_loadingTrackCount--;
    1703              : 
    1704            0 :         size_t current = trackCount - m_loadingTrackCount;
    1705            0 :         m_loadingProgress.progress(current, trackCount, title);
    1706              : 
    1707            0 :         if (m_loadingTrackCount == 0) {
    1708            0 :             m_loadingProgress.finish(muse::make_ok());
    1709            0 :             m_isPlayAllowedChanged.send(isPlayAllowed());
    1710              :         }
    1711            0 :     };
    1712              : 
    1713            0 :     for (const InstrumentTrackId& trackId : trackIdSet) {
    1714            0 :         addTrack(trackId, projectHadNoAudioSettings, onAddFinished);
    1715              :     }
    1716              : 
    1717            0 :     for (aux_channel_idx_t idx = 0; idx < AUX_CHANNEL_NUM; ++idx) {
    1718            0 :         addAuxTrack(idx, projectHadNoAudioSettings, onAddFinished);
    1719              :     }
    1720              : 
    1721            0 :     m_loadingProgress.progress(0, trackCount, title);
    1722              : 
    1723            0 :     notationPlayback()->trackAdded().onReceive(this, [this, onAddFinished](const InstrumentTrackId& instrumentTrackId) {
    1724            0 :         addTrack(instrumentTrackId, false /*projectHadNoAudioSettings*/, onAddFinished);
    1725            0 :     });
    1726              : 
    1727            0 :     notationPlayback()->trackRemoved().onReceive(this, [this](const InstrumentTrackId& instrumentTrackId) {
    1728            0 :         removeTrack(instrumentTrackId);
    1729            0 :     });
    1730              : 
    1731            0 :     NotifyList<const Part*> partList = masterNotationParts()->partList();
    1732              : 
    1733              :     //! HACK - ideally we would use "this" (PlaybackController) instead of m_seqAsyncReceiver for the following
    1734              :     //! subscription, but we've already subscribed to onItemChanged for a different reason in setNotation...
    1735            0 :     partList.onItemChanged(&m_seqAsyncReceiver, [this](const Part*) {
    1736            0 :         updateSoloMuteStates();
    1737            0 :     });
    1738              : 
    1739            0 :     audioSettings()->auxSoloMuteStateChanged().onReceive(
    1740            0 :         this, [this](aux_channel_idx_t, const notation::INotationSoloMuteState::SoloMuteState&) {
    1741            0 :         updateSoloMuteStates();
    1742            0 :     });
    1743              : 
    1744            0 :     m_isPlayAllowedChanged.send(isPlayAllowed());
    1745            0 : }
    1746              : 
    1747            0 : void PlaybackController::setupPlayer()
    1748              : {
    1749            0 :     currentPlayer()->playbackPositionChanged().onReceive(this, [this](const audio::secs_t pos) {
    1750            0 :         m_playbackPositionChanged.send(pos);
    1751              : 
    1752            0 :         m_currentTick = notationPlayback()->secToTick(pos);
    1753              : 
    1754            0 :         updateCurrentTempo();
    1755              : 
    1756            0 :         secs_t endSecs = totalPlayTime();
    1757            0 :         if (pos + muse::msecs_to_secs(1) >= endSecs) {
    1758            0 :             doStop();
    1759              :         }
    1760            0 :     });
    1761              : 
    1762            0 :     currentPlayer()->playbackStatusChanged().onReceive(this, [this](PlaybackStatus) {
    1763            0 :         onPlaybackStatusChanged();
    1764            0 :     });
    1765              : 
    1766            0 :     currentPlayer()->setDuration(notationPlayback()->totalPlayTime());
    1767              : 
    1768            0 :     notationPlayback()->totalPlayTimeChanged().onReceive(this, [this](const audio::secs_t totalPlaybackTime) {
    1769            0 :         currentPlayer()->setDuration(totalPlaybackTime);
    1770            0 :         m_totalPlayTimeChanged.notify();
    1771            0 :     });
    1772            0 : }
    1773              : 
    1774            0 : void PlaybackController::updateSoloMuteStates()
    1775              : {
    1776            0 :     if (!audioSettings() || !playback() || !m_notation) {
    1777            0 :         return;
    1778              :     }
    1779              : 
    1780            0 :     TRACEFUNC;
    1781              : 
    1782            0 :     InstrumentTrackIdSet existingTrackIdSet = notationPlayback()->existingTrackIdSet();
    1783            0 :     bool hasSolo = false;
    1784              : 
    1785            0 :     for (const InstrumentTrackId& instrumentTrackId : existingTrackIdSet) {
    1786            0 :         if (instrumentTrackId == notationPlayback()->metronomeTrackId()) {
    1787            0 :             continue;
    1788              :         }
    1789            0 :         if (m_notation->soloMuteState()->trackSoloMuteState(instrumentTrackId).solo) {
    1790            0 :             hasSolo = true;
    1791            0 :             break;
    1792              :         }
    1793              :     }
    1794              : 
    1795            0 :     InstrumentTrackIdSet allowedInstrumentTrackIdSet = instrumentTrackIdSetForRangePlayback();
    1796            0 :     bool isRangePlaybackMode = !m_isExportingAudio && selection()->isRange() && !allowedInstrumentTrackIdSet.empty();
    1797              : 
    1798            0 :     for (const InstrumentTrackId& instrumentTrackId : existingTrackIdSet) {
    1799            0 :         if (!muse::contains(m_instrumentTrackIdMap, instrumentTrackId)) {
    1800            0 :             continue;
    1801              :         }
    1802              : 
    1803            0 :         if (instrumentTrackId == notationPlayback()->metronomeTrackId()) {
    1804            0 :             continue;
    1805              :         }
    1806              : 
    1807              :         // 1. Recall the solo-mute state for this notation
    1808            0 :         const auto& soloMuteState = m_notation->soloMuteState()->trackSoloMuteState(instrumentTrackId);
    1809              : 
    1810              :         // 2. Evaluate "force mute" (disabling the mute button)
    1811            0 :         bool shouldForceMute = hasSolo && !soloMuteState.solo;
    1812            0 :         if (notationPlayback()->isChordSymbolsTrack(instrumentTrackId) && !shouldForceMute) {
    1813            0 :             shouldForceMute = !notationConfiguration()->isPlayChordSymbolsEnabled();
    1814              :         }
    1815              : 
    1816            0 :         if (isRangePlaybackMode && !shouldForceMute) {
    1817            0 :             shouldForceMute = !muse::contains(allowedInstrumentTrackIdSet, instrumentTrackId);
    1818              :         }
    1819              : 
    1820              :         // 3. Update params for playback / mixer
    1821            0 :         AudioOutputParams params = trackOutputParams(instrumentTrackId);
    1822            0 :         params.solo = soloMuteState.solo;
    1823            0 :         params.muted = soloMuteState.mute || shouldForceMute;
    1824            0 :         params.forceMute = shouldForceMute;
    1825              : 
    1826            0 :         audio::TrackId trackId = m_instrumentTrackIdMap.at(instrumentTrackId);
    1827            0 :         playback()->setControlParams(trackId, trackControlParams(instrumentTrackId, params, /*rebuildVolume*/ false, /*rebuildPan*/ false));
    1828            0 :     }
    1829              : 
    1830            0 :     updateAuxMuteStates();
    1831            0 : }
    1832              : 
    1833            0 : void PlaybackController::updateAuxMuteStates()
    1834              : {
    1835            0 :     for (const auto& pair : m_auxTrackIdMap) {
    1836            0 :         auto soloMuteState = audioSettings()->auxSoloMuteState(pair.first);
    1837              : 
    1838            0 :         AudioOutputParams params = audioSettings()->auxOutputParams(pair.first);
    1839            0 :         if (params.muted == soloMuteState.mute) {
    1840            0 :             continue;
    1841              :         }
    1842              : 
    1843            0 :         params.muted = soloMuteState.mute;
    1844            0 :         playback()->setControlParams(pair.second, params.control());
    1845            0 :     }
    1846            0 : }
    1847              : 
    1848            0 : secs_t PlaybackController::totalPlayTime() const
    1849              : {
    1850            0 :     auto np = notationPlayback();
    1851            0 :     return np ? np->totalPlayTime() : secs_t { 0.0 };
    1852            0 : }
    1853              : 
    1854            0 : Notification PlaybackController::totalPlayTimeChanged() const
    1855              : {
    1856            0 :     return m_totalPlayTimeChanged;
    1857              : }
    1858              : 
    1859            0 : const Tempo& PlaybackController::currentTempo() const
    1860              : {
    1861            0 :     return m_currentTempo;
    1862              : }
    1863              : 
    1864            0 : Notification PlaybackController::currentTempoChanged() const
    1865              : {
    1866            0 :     return m_currentTempoChanged;
    1867              : }
    1868              : 
    1869            0 : MeasureBeat PlaybackController::currentBeat() const
    1870              : {
    1871            0 :     return notationPlayback() ? notationPlayback()->beat(m_currentTick) : MeasureBeat();
    1872              : }
    1873              : 
    1874            0 : secs_t PlaybackController::beatToSecs(int measureIndex, int beatIndex) const
    1875              : {
    1876            0 :     if (!notationPlayback()) {
    1877            0 :         return 0;
    1878              :     }
    1879              : 
    1880            0 :     muse::midi::tick_t rawTick = notationPlayback()->beatToRawTick(measureIndex, beatIndex);
    1881            0 :     muse::midi::tick_t playedTick = notationPlayback()->playPositionTickByRawTick(rawTick).val;
    1882              : 
    1883            0 :     return playedTickToSecs(playedTick);
    1884              : }
    1885              : 
    1886            0 : double PlaybackController::tempoMultiplier() const
    1887              : {
    1888            0 :     return notationPlayback() ? notationPlayback()->tempoMultiplier() : 1.0;
    1889              : }
    1890              : 
    1891            0 : void PlaybackController::setTempoMultiplier(double multiplier)
    1892              : {
    1893            0 :     if (!notationPlayback()) {
    1894            0 :         return;
    1895              :     }
    1896              : 
    1897            0 :     tick_t tick = m_currentTick;
    1898            0 :     bool playing = isPlaying();
    1899              : 
    1900            0 :     if (playing) {
    1901            0 :         doPause();
    1902              :     }
    1903              : 
    1904            0 :     notationPlayback()->setTempoMultiplier(multiplier);
    1905            0 :     seekRawTick(tick);
    1906            0 :     updateLoop();
    1907              : 
    1908            0 :     if (playing) {
    1909            0 :         doResume();
    1910              :     }
    1911              : }
    1912              : 
    1913            0 : muse::Progress PlaybackController::loadingProgress() const
    1914              : {
    1915            0 :     return m_loadingProgress;
    1916              : }
    1917              : 
    1918            0 : void PlaybackController::applyProfile(const SoundProfileName& profileName)
    1919              : {
    1920            0 :     project::IProjectAudioSettingsPtr audioSettingsPtr = audioSettings();
    1921              : 
    1922            0 :     IF_ASSERT_FAILED(audioSettingsPtr) {
    1923            0 :         return;
    1924              :     }
    1925              : 
    1926            0 :     const SoundProfile& profile = profilesRepo()->profile(profileName);
    1927            0 :     if (!profile.isValid()) {
    1928            0 :         return;
    1929              :     }
    1930              : 
    1931            0 :     notationPlayback()->removeSoundFlags(notationPlayback()->existingTrackIdSet());
    1932              : 
    1933            0 :     const InstrumentTrackId& metronomeTrackId = notationPlayback()->metronomeTrackId();
    1934              : 
    1935            0 :     for (const auto& pair : m_instrumentTrackIdMap) {
    1936            0 :         if (pair.first == metronomeTrackId) {
    1937            0 :             continue;
    1938              :         }
    1939              : 
    1940            0 :         const mpe::PlaybackData& playbackData = notationPlayback()->trackPlaybackData(pair.first);
    1941            0 :         AudioSourceParams newSourceParams { profile.findResource(playbackData.setupData), {} };
    1942              : 
    1943            0 :         playback()->setSourceParams(pair.second, newSourceParams);
    1944            0 :     }
    1945              : 
    1946            0 :     audioSettingsPtr->setActiveSoundProfile(profileName);
    1947            0 : }
    1948              : 
    1949           12 : void PlaybackController::setNotation(notation::INotationPtr notation)
    1950              : {
    1951           12 :     if (m_notation == notation) {
    1952           12 :         return;
    1953              :     }
    1954              : 
    1955            0 :     if (m_notation) {
    1956            0 :         INotationPartsPtr notationParts = m_notation->parts();
    1957            0 :         NotifyList<const Part*> partList = notationParts->partList();
    1958            0 :         partList.disconnect(this);
    1959              : 
    1960            0 :         m_notation->interaction()->selectionChanged().disconnect(this);
    1961            0 :         m_notation->interaction()->textEditingEnded().disconnect(this);
    1962            0 :         m_notation->soloMuteState()->trackSoloMuteStateChanged().disconnect(this);
    1963            0 :     }
    1964              : 
    1965            0 :     m_notation = notation;
    1966              : 
    1967            0 :     m_isPlayAllowedChanged.send(isPlayAllowed());
    1968              : 
    1969            0 :     if (!m_notation) {
    1970            0 :         setMasterNotation(nullptr);
    1971            0 :         return;
    1972              :     }
    1973              : 
    1974            0 :     setMasterNotation(m_notation->masterNotation());
    1975              : 
    1976            0 :     if (!m_notation->hasVisibleParts()) {
    1977            0 :         doPause();
    1978              :     }
    1979              : 
    1980            0 :     updateSoloMuteStates();
    1981              : 
    1982            0 :     NotifyList<const Part*> partList = m_notation->parts()->partList();
    1983              : 
    1984            0 :     partList.onItemAdded(this, [this](const Part* part) {
    1985            0 :         onPartChanged(part);
    1986            0 :     });
    1987              : 
    1988            0 :     partList.onItemChanged(this, [this](const Part* part) {
    1989            0 :         onPartChanged(part);
    1990            0 :     });
    1991              : 
    1992            0 :     m_notation->interaction()->selectionChanged().onNotify(this, [this]() {
    1993            0 :         onSelectionChanged();
    1994            0 :     });
    1995              : 
    1996            0 :     m_notation->interaction()->textEditingEnded().onReceive(this, [this](engraving::TextBase* text) {
    1997            0 :         if (text && text->isHarmony()) {
    1998            0 :             playElements({ text });
    1999              :         }
    2000            0 :     });
    2001              : 
    2002            0 :     m_notation->soloMuteState()->trackSoloMuteStateChanged().onReceive(
    2003            0 :         this, [this](const InstrumentTrackId&, const notation::INotationSoloMuteState::SoloMuteState&) {
    2004            0 :         updateSoloMuteStates();
    2005            0 :     });
    2006            0 : }
    2007              : 
    2008            0 : void PlaybackController::setMasterNotation(notation::IMasterNotationPtr masterNotation)
    2009              : {
    2010            0 :     if (m_masterNotation == masterNotation) {
    2011            0 :         return;
    2012              :     }
    2013              : 
    2014            0 :     if (m_masterNotation) {
    2015            0 :         m_masterNotation->hasPartsChanged().disconnect(this);
    2016            0 :         m_masterNotation->playback()->loopBoundariesChanged().disconnect(this);
    2017            0 :         m_masterNotation->playback()->loopEnabledChanged().disconnect(this);
    2018              : 
    2019            0 :         if (const AutomationDataConstPtr automationData = m_masterNotation->automation()->automationData()) {
    2020            0 :             automationData->changed().disconnect(this);
    2021            0 :         }
    2022              :     }
    2023              : 
    2024            0 :     m_masterNotation = masterNotation;
    2025              : 
    2026            0 :     m_totalPlayTimeChanged.notify();
    2027              : 
    2028            0 :     if (!m_masterNotation) {
    2029            0 :         return;
    2030              :     }
    2031              : 
    2032            0 :     m_masterNotation->hasPartsChanged().onNotify(this, [this]() {
    2033            0 :         m_isPlayAllowedChanged.send(isPlayAllowed());
    2034            0 :     });
    2035              : 
    2036            0 :     m_masterNotation->playback()->loopBoundariesChanged().onNotify(this, [this]() {
    2037            0 :         updateLoop();
    2038            0 :     });
    2039              : 
    2040            0 :     m_masterNotation->playback()->loopEnabledChanged().onReceive(this, [this](bool value) {
    2041            0 :         m_loopEnabledChanged.send(value);
    2042            0 :     });
    2043              : 
    2044            0 :     if (const AutomationDataConstPtr automationData = m_masterNotation->automation()->automationData()) {
    2045            0 :         automationData->changed().onReceive(this, [this](const AutomationChanges& changes) {
    2046            0 :             onAutomationDataChanged(changes);
    2047            0 :         });
    2048            0 :     }
    2049              : }
    2050              : 
    2051            0 : void PlaybackController::setIsExportingAudio(bool exporting)
    2052              : {
    2053            0 :     if (m_isExportingAudio == exporting) {
    2054            0 :         return;
    2055              :     }
    2056              : 
    2057            0 :     m_isExportingAudio = exporting;
    2058            0 :     updateSoloMuteStates();
    2059              : 
    2060            0 :     if (exporting && notationPlayback()) {
    2061            0 :         notationPlayback()->sendEventsForChangedTracks();
    2062              :     }
    2063              : }
    2064              : 
    2065            0 : const std::map<TrackId, AudioResourceMeta>& PlaybackController::onlineSounds() const
    2066              : {
    2067            0 :     return m_onlineSoundsController->onlineSounds();
    2068              : }
    2069              : 
    2070            0 : muse::async::Notification PlaybackController::onlineSoundsChanged() const
    2071              : {
    2072            0 :     return m_onlineSoundsController->onlineSoundsChanged();
    2073              : }
    2074              : 
    2075            0 : muse::Progress PlaybackController::onlineSoundsProcessingProgress() const
    2076              : {
    2077            0 :     return m_onlineSoundsController->onlineSoundsProcessingProgress();
    2078              : }
    2079              : 
    2080            0 : muse::audio::secs_t PlaybackController::playedTickToSecs(int tick) const
    2081              : {
    2082            0 :     return secs_t(notationPlayback()->playedTickToSec(tick));
    2083              : }
        

Generated by: LCOV version 2.0-1