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 : }
|