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) 2021 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 : #include "notationplayback.h"
23 :
24 : #include <cmath>
25 :
26 : #include "engraving/dom/chordrest.h"
27 : #include "engraving/dom/factory.h"
28 : #include "engraving/dom/instrument.h"
29 : #include "engraving/dom/linkedobjects.h"
30 : #include "engraving/dom/masterscore.h"
31 : #include "engraving/dom/measure.h"
32 : #include "engraving/dom/page.h"
33 : #include "engraving/dom/part.h"
34 : #include "engraving/dom/repeatlist.h"
35 : #include "engraving/dom/segment.h"
36 : #include "engraving/dom/soundflag.h"
37 : #include "engraving/dom/staff.h"
38 : #include "engraving/dom/stafftext.h"
39 : #include "engraving/dom/tempotimeline.h"
40 : #include "engraving/dom/utils.h"
41 :
42 : #include "notationerrors.h"
43 :
44 : #include "log.h"
45 :
46 : using namespace mu;
47 : using namespace mu::notation;
48 : using namespace mu::engraving;
49 : using namespace muse;
50 : using namespace muse::midi;
51 : using namespace muse::async;
52 :
53 : static constexpr double PLAYBACK_TAIL_SECS = 3;
54 :
55 0 : NotationPlayback::NotationPlayback(IGetScore* getScore,
56 : muse::async::Channel<muse::RectF> notationChanged,
57 0 : const modularity::ContextPtr& iocCtx)
58 0 : : muse::Contextable(iocCtx), m_getScore(getScore), m_notationChanged(notationChanged), m_playbackModel(iocCtx)
59 : {
60 0 : m_notationChanged.onReceive(this, [this](const muse::RectF&) {
61 0 : updateLoopBoundaries();
62 0 : });
63 0 : }
64 :
65 0 : mu::engraving::Score* NotationPlayback::score() const
66 : {
67 0 : return m_getScore->score();
68 : }
69 :
70 0 : void NotationPlayback::init()
71 : {
72 0 : IF_ASSERT_FAILED(score()) {
73 0 : return;
74 : }
75 :
76 0 : m_playbackModel.setPlayRepeats(configuration()->isPlayRepeatsEnabled());
77 0 : m_playbackModel.setPlayChordSymbols(configuration()->isPlayChordSymbolsEnabled());
78 0 : m_playbackModel.setUseScoreDynamicsForOffstreamPlayback(configuration()->playPreviewNotesWithScoreDynamics());
79 0 : m_playbackModel.setIsMetronomeEnabled(configuration()->isMetronomeEnabled());
80 :
81 0 : m_playbackModel.load(score());
82 :
83 0 : updateTotalPlayTime();
84 0 : m_playbackModel.tracksDataChanged().onReceive(this, [this](const InstrumentTrackIdSet&) {
85 0 : updateTotalPlayTime();
86 0 : });
87 :
88 0 : configuration()->isPlayRepeatsChanged().onNotify(this, [this]() {
89 0 : bool expandRepeats = configuration()->isPlayRepeatsEnabled();
90 0 : if (expandRepeats != m_playbackModel.isPlayRepeatsEnabled()) {
91 0 : m_playbackModel.setPlayRepeats(expandRepeats);
92 0 : m_playbackModel.reload();
93 : }
94 0 : });
95 :
96 0 : configuration()->isPlayChordSymbolsChanged().onNotify(this, [this]() {
97 0 : bool playChordSymbols = configuration()->isPlayChordSymbolsEnabled();
98 0 : if (playChordSymbols != m_playbackModel.isPlayChordSymbolsEnabled()) {
99 0 : m_playbackModel.setPlayChordSymbols(playChordSymbols);
100 0 : m_playbackModel.reload();
101 : }
102 0 : });
103 :
104 0 : configuration()->playPreviewNotesWithScoreDynamicsChanged().onNotify(this, [this]() {
105 0 : bool useScoreDynamics = configuration()->playPreviewNotesWithScoreDynamics();
106 0 : if (useScoreDynamics != m_playbackModel.useScoreDynamicsForOffstreamPlayback()) {
107 0 : m_playbackModel.setUseScoreDynamicsForOffstreamPlayback(useScoreDynamics);
108 : }
109 0 : });
110 :
111 0 : configuration()->isMetronomeEnabledChanged().onNotify(this, [this]() {
112 0 : bool metronomeEnabled = configuration()->isMetronomeEnabled();
113 0 : if (metronomeEnabled != m_playbackModel.isMetronomeEnabled()) {
114 0 : m_playbackModel.setIsMetronomeEnabled(metronomeEnabled);
115 : }
116 0 : });
117 :
118 0 : score()->loopBoundaryTickChanged().onReceive(this, [this](LoopBoundaryType, unsigned) {
119 0 : updateLoopBoundaries();
120 0 : });
121 : }
122 :
123 0 : void NotationPlayback::reload()
124 : {
125 0 : m_playbackModel.reload();
126 0 : }
127 :
128 0 : void NotationPlayback::setSendEventsOnScoreChange(const InstrumentTrackId& trackId, bool send)
129 : {
130 0 : m_playbackModel.setSendEventsOnScoreChange(trackId, send);
131 0 : }
132 :
133 0 : void NotationPlayback::sendEventsForChangedTracks()
134 : {
135 0 : m_playbackModel.sendEventsForChangedTracks();
136 0 : }
137 :
138 0 : muse::async::Channel<InstrumentTrackIdSet> NotationPlayback::tracksDataChanged() const
139 : {
140 0 : return m_playbackModel.tracksDataChanged();
141 : }
142 :
143 0 : const engraving::InstrumentTrackId& NotationPlayback::metronomeTrackId() const
144 : {
145 0 : return m_playbackModel.metronomeTrackId();
146 : }
147 :
148 0 : engraving::InstrumentTrackId NotationPlayback::chordSymbolsTrackId(const ID& partId) const
149 : {
150 0 : return m_playbackModel.chordSymbolsTrackId(partId);
151 : }
152 :
153 0 : bool NotationPlayback::isChordSymbolsTrack(const engraving::InstrumentTrackId& trackId) const
154 : {
155 0 : return m_playbackModel.isChordSymbolsTrack(trackId);
156 : }
157 :
158 0 : const muse::mpe::PlaybackData& NotationPlayback::trackPlaybackData(const engraving::InstrumentTrackId& trackId) const
159 : {
160 0 : return m_playbackModel.resolveTrackPlaybackData(trackId);
161 : }
162 :
163 0 : void NotationPlayback::triggerEventsForItems(const std::vector<const EngravingItem*>& items, muse::mpe::duration_t duration,
164 : bool flushSound)
165 : {
166 0 : m_playbackModel.triggerEventsForItems(items, duration, flushSound);
167 0 : }
168 :
169 0 : void NotationPlayback::triggerMetronome(muse::midi::tick_t tick)
170 : {
171 0 : m_playbackModel.triggerMetronome(tick);
172 0 : }
173 :
174 0 : void NotationPlayback::triggerCountIn(muse::midi::tick_t tick, muse::secs_t& countInDuration)
175 : {
176 0 : muse::mpe::duration_t durationInMicrosecs = 0;
177 0 : m_playbackModel.triggerCountIn(tick, durationInMicrosecs);
178 0 : countInDuration = muse::usecs_to_secs(durationInMicrosecs);
179 0 : }
180 :
181 0 : void NotationPlayback::triggerControllers(const muse::mpe::ControllerChangeEventList& list, staff_idx_t staffIdx, int tick)
182 : {
183 0 : if (list.empty()) {
184 0 : return;
185 : }
186 :
187 0 : const Staff* staff = score()->staff(staffIdx);
188 0 : if (!staff) {
189 0 : return;
190 : }
191 :
192 0 : const Part* part = staff->part();
193 : const InstrumentTrackId trackId {
194 : part->id(),
195 0 : part->instrumentId(Fraction::fromTicks(tick))
196 0 : };
197 :
198 : const mpe::PlaybackEventsMap events {
199 0 : { 0, mpe::PlaybackEventList(list.begin(), list.end()) }
200 0 : };
201 :
202 0 : mpe::PlaybackData& data = m_playbackModel.resolveTrackPlaybackData(trackId);
203 0 : data.offStream.send(events, false /*flushOffstream*/);
204 0 : }
205 :
206 0 : InstrumentTrackIdSet NotationPlayback::existingTrackIdSet() const
207 : {
208 0 : return m_playbackModel.existingTrackIdSet();
209 : }
210 :
211 0 : muse::async::Channel<InstrumentTrackId> NotationPlayback::trackAdded() const
212 : {
213 0 : return m_playbackModel.trackAdded();
214 : }
215 :
216 0 : muse::async::Channel<InstrumentTrackId> NotationPlayback::trackRemoved() const
217 : {
218 0 : return m_playbackModel.trackRemoved();
219 : }
220 :
221 0 : void NotationPlayback::updateLoopBoundaries()
222 : {
223 0 : LoopBoundaries newBoundaries;
224 0 : newBoundaries.loopInTick = score()->loopInTick();
225 0 : newBoundaries.loopOutTick = score()->loopOutTick();
226 0 : newBoundaries.enabled = m_loopBoundaries.enabled;
227 :
228 0 : if (m_loopBoundaries != newBoundaries) {
229 0 : m_loopBoundaries = newBoundaries;
230 0 : m_loopBoundariesChanged.notify();
231 : }
232 0 : }
233 :
234 0 : void NotationPlayback::updateTotalPlayTime()
235 : {
236 0 : const mu::engraving::Score* score = m_getScore->score();
237 0 : if (!score) {
238 0 : return;
239 : }
240 :
241 0 : const bool expandRepeats = m_playbackModel.isPlayRepeatsEnabled();
242 0 : const int lastTick = score->repeatList(expandRepeats).ticks();
243 0 : const TempoTimeline& timeline = score->tempoTimeline(expandRepeats);
244 0 : audio::secs_t newPlayTime = timeline.utick2utime(lastTick);
245 0 : newPlayTime += PLAYBACK_TAIL_SECS;
246 :
247 0 : if (m_totalPlayTime == newPlayTime) {
248 0 : return;
249 : }
250 :
251 0 : m_totalPlayTime = newPlayTime;
252 0 : m_totalPlayTimeChanged.send(m_totalPlayTime);
253 : }
254 :
255 0 : muse::audio::secs_t NotationPlayback::totalPlayTime() const
256 : {
257 0 : return m_totalPlayTime;
258 : }
259 :
260 0 : muse::async::Channel<muse::audio::secs_t> NotationPlayback::totalPlayTimeChanged() const
261 : {
262 0 : return m_totalPlayTimeChanged;
263 : }
264 :
265 0 : muse::audio::secs_t NotationPlayback::playedTickToSec(tick_t tick) const
266 : {
267 0 : const mu::engraving::Score* sc = score();
268 0 : if (!sc) {
269 0 : return 0;
270 : }
271 :
272 0 : return sc->tempoTimeline(m_playbackModel.isPlayRepeatsEnabled()).utick2utime(tick);
273 : }
274 :
275 0 : tick_t NotationPlayback::secToPlayedTick(muse::audio::secs_t sec) const
276 : {
277 0 : const mu::engraving::Score* sc = score();
278 0 : if (!sc) {
279 0 : return 0;
280 : }
281 :
282 0 : return sc->tempoTimeline(m_playbackModel.isPlayRepeatsEnabled()).utime2utick(sec);
283 : }
284 :
285 0 : tick_t NotationPlayback::secToTick(muse::audio::secs_t sec) const
286 : {
287 0 : const mu::engraving::Score* sc = score();
288 0 : if (!sc) {
289 0 : return 0;
290 : }
291 :
292 0 : const bool expandRepeats = m_playbackModel.isPlayRepeatsEnabled();
293 0 : const tick_t utick = sc->tempoTimeline(expandRepeats).utime2utick(sec);
294 :
295 : // The flattened timeline's domain is already raw tick - only the expanded one needs converting back
296 0 : return expandRepeats ? sc->expandedRepeatList().utick2tick(utick) : utick;
297 : }
298 :
299 0 : RetVal<muse::midi::tick_t> NotationPlayback::playPositionTickByRawTick(muse::midi::tick_t tick) const
300 : {
301 0 : if (!score()) {
302 0 : return make_ret(Err::Undefined);
303 : }
304 :
305 0 : muse::midi::tick_t playbackTick = score()->repeatList(m_playbackModel.isPlayRepeatsEnabled()).tick2utick(tick);
306 :
307 0 : return RetVal<muse::midi::tick_t>::make_ok(std::move(playbackTick));
308 : }
309 :
310 0 : RetVal<muse::midi::tick_t> NotationPlayback::playPositionTickByElement(const EngravingItem* element) const
311 : {
312 0 : IF_ASSERT_FAILED(element) {
313 0 : return make_ret(Err::Undefined);
314 : }
315 :
316 0 : if (!score()) {
317 0 : return make_ret(Err::Undefined);
318 : }
319 :
320 0 : return playPositionTickByRawTick(element->tick().ticks());
321 : }
322 :
323 0 : void NotationPlayback::addLoopBoundary(LoopBoundaryType boundaryType, tick_t tick)
324 : {
325 0 : const Measure* first = score()->firstMeasure();
326 0 : const Measure* last = score()->lastMeasure();
327 0 : IF_ASSERT_FAILED(first && last) {
328 0 : return;
329 : }
330 :
331 0 : if (tick == BoundaryTick::FirstScoreTick) {
332 0 : tick = first->tick().ticks();
333 0 : } else if (tick == BoundaryTick::LastScoreTick) {
334 0 : tick = last->endTick().ticks();
335 : }
336 :
337 0 : switch (boundaryType) {
338 0 : case LoopBoundaryType::LoopIn:
339 0 : addLoopIn(tick);
340 0 : break;
341 0 : case LoopBoundaryType::LoopOut:
342 0 : addLoopOut(tick);
343 0 : break;
344 0 : case LoopBoundaryType::Unknown:
345 0 : break;
346 : }
347 : }
348 :
349 0 : void NotationPlayback::addLoopIn(int _tick)
350 : {
351 0 : Fraction tick = Fraction::fromTicks(_tick);
352 :
353 0 : if (_tick == BoundaryTick::SelectedNoteTick) {
354 0 : tick = score()->pos();
355 : }
356 :
357 0 : if (tick >= score()->loopOutTick()) { // If In pos >= Out pos, reset Out pos to end of score
358 0 : score()->setLoopOutTick(score()->lastMeasure()->endTick());
359 : }
360 :
361 0 : score()->setLoopInTick(tick);
362 0 : }
363 :
364 0 : void NotationPlayback::addLoopOut(int _tick)
365 : {
366 0 : Fraction tick = Fraction::fromTicks(_tick);
367 :
368 0 : if (_tick == BoundaryTick::SelectedNoteTick) {
369 0 : tick = score()->pos() + score()->inputState().ticks();
370 : }
371 :
372 0 : if (tick <= score()->loopInTick()) { // If Out pos <= In pos, reset In pos to beginning of score
373 0 : score()->setLoopInTick(Fraction(0, 1));
374 : } else {
375 0 : if (tick > score()->lastMeasure()->endTick()) {
376 0 : tick = score()->lastMeasure()->endTick();
377 : }
378 : }
379 :
380 0 : score()->setLoopOutTick(tick);
381 0 : }
382 :
383 0 : void NotationPlayback::setLoopBoundariesEnabled(bool enabled)
384 : {
385 0 : if (m_loopBoundaries.enabled == enabled) {
386 0 : return;
387 : }
388 :
389 0 : m_loopBoundaries.enabled = enabled;
390 0 : m_loopBoundariesChanged.notify();
391 0 : m_loopEnabledChanged.send(enabled);
392 : }
393 :
394 0 : bool NotationPlayback::isLoopEnabled() const
395 : {
396 0 : return !m_loopBoundaries.isNull() && m_loopBoundaries.enabled;
397 : }
398 :
399 0 : Channel<bool> NotationPlayback::loopEnabledChanged() const
400 : {
401 0 : return m_loopEnabledChanged;
402 : }
403 :
404 0 : const LoopBoundaries& NotationPlayback::loopBoundaries() const
405 : {
406 0 : return m_loopBoundaries;
407 : }
408 :
409 0 : Notification NotationPlayback::loopBoundariesChanged() const
410 : {
411 0 : return m_loopBoundariesChanged;
412 : }
413 :
414 0 : const Tempo& NotationPlayback::multipliedTempo(tick_t tick) const
415 : {
416 0 : if (!score()) {
417 : static Tempo empty;
418 0 : return empty;
419 : }
420 :
421 0 : m_currentTempo.valueBpm = static_cast<int>(std::round(score()->multipliedTempo(Fraction::fromTicks(tick)).toBPM().val));
422 :
423 0 : return m_currentTempo;
424 : }
425 :
426 0 : MeasureBeat NotationPlayback::beat(tick_t tick) const
427 : {
428 0 : return mu::engraving::findBeat(m_getScore->score(), tick);
429 : }
430 :
431 0 : tick_t NotationPlayback::beatToRawTick(int measureIndex, int beatIndex) const
432 : {
433 0 : return score() ? score()->sigmap()->bar2tick(measureIndex, beatIndex) : 0;
434 : }
435 :
436 0 : double NotationPlayback::tempoMultiplier() const
437 : {
438 0 : return score() ? score()->tempoTimeline().tempoMultiplier().val : 1.0;
439 : }
440 :
441 0 : void NotationPlayback::setTempoMultiplier(double multiplier)
442 : {
443 0 : Score* score = this->score();
444 0 : if (!score) {
445 0 : return;
446 : }
447 :
448 0 : if (!score->masterScore()->setTempoMultiplier(multiplier)) {
449 0 : return;
450 : }
451 :
452 0 : score->masterScore()->updateRepeatListTempo();
453 :
454 0 : m_playbackModel.reload();
455 : }
456 :
457 0 : void NotationPlayback::addSoundFlags(const std::vector<StaffText*>& staffTextList)
458 : {
459 0 : TRACEFUNC;
460 :
461 0 : if (staffTextList.empty()) {
462 0 : return;
463 : }
464 :
465 0 : bool added = false;
466 :
467 0 : for (StaffText* staffText : staffTextList) {
468 0 : added |= doAddSoundFlag(staffText);
469 : }
470 :
471 0 : if (added) {
472 0 : score()->update();
473 0 : m_notationChanged.send(muse::RectF());
474 : }
475 0 : }
476 :
477 0 : bool NotationPlayback::doAddSoundFlag(StaffText* staffText)
478 : {
479 0 : IF_ASSERT_FAILED(staffText) {
480 0 : return false;
481 : }
482 :
483 0 : if (staffText->hasSoundFlag()) {
484 0 : return false;
485 : }
486 :
487 0 : SoundFlag* soundFlag = Factory::createSoundFlag(staffText);
488 0 : staffText->add(soundFlag);
489 :
490 0 : const LinkedObjects* links = staffText->links();
491 0 : if (!links) {
492 0 : return true;
493 : }
494 :
495 0 : for (EngravingObject* obj : *links) {
496 0 : if (obj && obj->isStaffText() && obj != staffText) {
497 0 : toStaffText(obj)->add(soundFlag->linkedClone());
498 : }
499 : }
500 :
501 0 : return true;
502 : }
503 :
504 0 : void NotationPlayback::removeSoundFlags(const InstrumentTrackIdSet& trackIdSet)
505 : {
506 0 : TRACEFUNC;
507 :
508 0 : std::vector<StaffText*> staffTextList = collectStaffText(trackIdSet, true /*withSoundFlags*/);
509 0 : if (staffTextList.empty()) {
510 0 : return;
511 : }
512 :
513 0 : for (StaffText* staffText : staffTextList) {
514 0 : if (!staffText->hasSoundFlag()) {
515 0 : continue;
516 : }
517 :
518 0 : staffText->remove(staffText->soundFlag());
519 :
520 0 : const LinkedObjects* links = staffText->links();
521 0 : if (!links) {
522 0 : continue;
523 : }
524 :
525 0 : for (EngravingObject* obj : *links) {
526 0 : if (obj && obj->isStaffText() && obj != staffText) {
527 0 : StaffText* linkedStaffText = toStaffText(obj);
528 0 : if (!linkedStaffText->hasSoundFlag()) {
529 0 : continue;
530 : }
531 :
532 0 : linkedStaffText->remove(linkedStaffText->soundFlag());
533 : }
534 : }
535 : }
536 :
537 0 : score()->update();
538 :
539 0 : m_playbackModel.reload();
540 0 : m_notationChanged.send(muse::RectF());
541 0 : }
542 :
543 0 : bool NotationPlayback::hasSoundFlags(const engraving::InstrumentTrackIdSet& trackIdSet)
544 : {
545 0 : TRACEFUNC;
546 :
547 0 : for (const InstrumentTrackId& trackId : trackIdSet) {
548 0 : if (m_playbackModel.hasSoundFlags(trackId)) {
549 0 : return true;
550 : }
551 : }
552 :
553 0 : return false;
554 0 : }
555 :
556 0 : std::vector<StaffText*> NotationPlayback::collectStaffText(const InstrumentTrackIdSet& trackIdSet, bool withSoundFlags) const
557 : {
558 0 : TRACEFUNC;
559 :
560 0 : std::vector<StaffText*> result;
561 :
562 0 : if (trackIdSet.empty()) {
563 0 : return result;
564 : }
565 :
566 0 : const Score* score = this->score();
567 0 : IF_ASSERT_FAILED(score) {
568 0 : return result;
569 : }
570 :
571 0 : const Measure* fm = score->firstMeasure();
572 0 : if (!fm) {
573 0 : return result;
574 : }
575 :
576 0 : for (const Segment* seg = fm->first(SegmentType::ChordRest); seg; seg = seg->next1(SegmentType::ChordRest)) {
577 0 : for (EngravingItem* annotation : seg->annotations()) {
578 0 : if (!annotation || !annotation->isStaffText()) {
579 0 : continue;
580 : }
581 :
582 0 : StaffText* staffText = toStaffText(annotation);
583 0 : bool hasSoundFlag = staffText->hasSoundFlag();
584 :
585 0 : if (withSoundFlags && !hasSoundFlag) {
586 0 : continue;
587 : }
588 :
589 0 : if (!withSoundFlags && hasSoundFlag) {
590 0 : continue;
591 : }
592 :
593 0 : InstrumentTrackId trackId = mu::engraving::makeInstrumentTrackId(annotation);
594 0 : if (muse::contains(trackIdSet, trackId)) {
595 0 : result.push_back(staffText);
596 : }
597 0 : }
598 : }
599 :
600 0 : return result;
601 0 : }
|