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 "playbackmodel.h"
24 :
25 : #include "dom/fret.h"
26 : #include "dom/harmony.h"
27 : #include "dom/instrument.h"
28 : #include "dom/masterscore.h"
29 : #include "dom/measure.h"
30 : #include "dom/measurerepeat.h"
31 : #include "dom/part.h"
32 : #include "dom/staff.h"
33 : #include "dom/repeatlist.h"
34 : #include "dom/segment.h"
35 : #include "dom/tie.h"
36 : #include "dom/tremolotwochord.h"
37 :
38 : #include "editing/transaction/undoablecommand.h"
39 :
40 : #include "defer.h"
41 : #include "log.h"
42 :
43 : using namespace mu;
44 : using namespace mu::engraving;
45 : using namespace muse::mpe;
46 : using namespace muse::async;
47 :
48 : static const String METRONOME_INSTRUMENT_ID(u"metronome");
49 : static const String CHORD_SYMBOLS_INSTRUMENT_ID(u"chord_symbols");
50 :
51 : const InstrumentTrackId PlaybackModel::METRONOME_TRACK_ID = { 999, METRONOME_INSTRUMENT_ID };
52 :
53 21 : static const Harmony* findChordSymbol(const EngravingItem* item)
54 : {
55 21 : if (item->isHarmony()) {
56 0 : return toHarmony(item);
57 21 : } else if (item->isFretDiagram()) {
58 0 : return toFretDiagram(item)->harmony();
59 : }
60 :
61 21 : return nullptr;
62 : }
63 :
64 6 : static bool isScopedAutomationChange(const ScoreChanges& changes)
65 : {
66 6 : if (!changes.isValidBoundary()) {
67 0 : return false;
68 : }
69 :
70 6 : bool hasAutomationChange = false;
71 :
72 8 : for (const auto& pair : changes.changedObjects) {
73 6 : if (pair.first->type() != ElementType::SCORE) {
74 0 : continue;
75 : }
76 :
77 6 : if (pair.second.size() != 1 || !muse::contains(pair.second, CommandType::EditAutomationPoints)) {
78 4 : return false;
79 : }
80 :
81 2 : hasAutomationChange = true;
82 : }
83 :
84 2 : return hasAutomationChange;
85 : }
86 :
87 23 : void PlaybackModel::load(Score* score)
88 : {
89 24 : TRACEFUNC;
90 :
91 23 : if (!score || score->measures()->empty() || !score->lastMeasure()) {
92 0 : return;
93 : }
94 :
95 23 : m_score = score;
96 23 : m_playbackCtx = std::make_shared<PlaybackContext>(score);
97 :
98 23 : auto changesChannel = score->changesChannel();
99 23 : changesChannel.disconnect(this);
100 :
101 23 : changesChannel.onReceive(this, [this](const ScoreChanges& changes) {
102 7 : if (shouldSkipChanges(changes)) {
103 0 : return;
104 : }
105 :
106 7 : const TickBoundaries tickRange = tickBoundaries(changes);
107 7 : const TrackBoundaries trackRange = trackBoundaries(changes);
108 7 : ChangedTrackIdSet trackChanges;
109 :
110 7 : clearExpiredTracks();
111 7 : m_playbackCtx->clear(trackRange.trackFrom, trackRange.trackTo, tickRange.tickFrom, tickRange.tickTo);
112 7 : clearExpiredEvents(tickRange.tickFrom, tickRange.tickTo, trackRange.trackFrom, trackRange.trackTo, &trackChanges);
113 :
114 7 : const InstrumentTrackIdSet oldTracks = existingTrackIdSet();
115 7 : update(tickRange.tickFrom, tickRange.tickTo, trackRange.trackFrom, trackRange.trackTo, &trackChanges);
116 :
117 7 : notifyAboutChanges(oldTracks, trackChanges);
118 7 : });
119 :
120 23 : update(0, m_score->lastMeasure()->endTick().ticks(), 0, m_score->ntracks());
121 :
122 23 : InstrumentTrackIdSet trackIdSet;
123 23 : trackIdSet.reserve(m_playbackDataMap.size());
124 :
125 92 : for (const auto& pair : m_playbackDataMap) {
126 69 : m_trackAdded.send(pair.first);
127 69 : trackIdSet.insert(pair.first);
128 : }
129 :
130 23 : m_tracksDataChanged.send(trackIdSet);
131 23 : m_changedTrackIdSet.clear();
132 23 : }
133 :
134 1 : void PlaybackModel::reload()
135 : {
136 1 : if (!m_score) {
137 0 : return;
138 : }
139 :
140 2 : TRACEFUNC;
141 :
142 1 : const int trackFrom = 0;
143 1 : const size_t trackTo = m_score->ntracks();
144 :
145 1 : const Measure* lastMeasure = m_score->lastMeasure();
146 :
147 1 : const int tickFrom = 0;
148 1 : const int tickTo = lastMeasure ? lastMeasure->endTick().ticks() : 0;
149 :
150 1 : clearExpiredTracks();
151 1 : m_playbackCtx->clear(trackFrom, trackTo, tickFrom, tickTo);
152 :
153 3 : for (auto& pair : m_playbackDataMap) {
154 2 : pair.second.originEvents.clear();
155 : }
156 :
157 1 : update(tickFrom, tickTo, trackFrom, trackTo);
158 :
159 1 : InstrumentTrackIdSet trackIdSet;
160 1 : trackIdSet.reserve(m_playbackDataMap.size());
161 :
162 3 : for (auto& pair : m_playbackDataMap) {
163 2 : pair.second.mainStream.send(pair.second.originEvents, pair.second.dynamics);
164 2 : trackIdSet.insert(pair.first);
165 : }
166 :
167 1 : m_tracksDataChanged.send(trackIdSet);
168 1 : m_changedTrackIdSet.clear();
169 1 : }
170 :
171 3 : void PlaybackModel::setSendEventsOnScoreChange(const InstrumentTrackId& trackId, bool send)
172 : {
173 3 : m_sendEventsOnScoreChangeMap[trackId] = send;
174 :
175 3 : if (send) {
176 2 : auto it = m_changedTrackIdSet.find(trackId);
177 2 : if (it != m_changedTrackIdSet.end()) {
178 1 : sendEvents(trackId);
179 1 : m_changedTrackIdSet.erase(it);
180 : }
181 : }
182 3 : }
183 :
184 1 : void PlaybackModel::sendEventsForChangedTracks()
185 : {
186 1 : if (m_changedTrackIdSet.empty()) {
187 0 : return;
188 : }
189 :
190 2 : TRACEFUNC;
191 :
192 3 : for (const InstrumentTrackId& trackId : m_changedTrackIdSet) {
193 2 : sendEvents(trackId);
194 : }
195 :
196 1 : m_changedTrackIdSet.clear();
197 1 : }
198 :
199 1 : muse::async::Channel<InstrumentTrackIdSet> PlaybackModel::tracksDataChanged() const
200 : {
201 1 : return m_tracksDataChanged;
202 : }
203 :
204 0 : bool PlaybackModel::isPlayRepeatsEnabled() const
205 : {
206 0 : return m_expandRepeats;
207 : }
208 :
209 1 : void PlaybackModel::setPlayRepeats(const bool isEnabled)
210 : {
211 1 : m_expandRepeats = isEnabled;
212 1 : }
213 :
214 0 : bool PlaybackModel::isPlayChordSymbolsEnabled() const
215 : {
216 0 : return m_playChordSymbols;
217 : }
218 :
219 0 : void PlaybackModel::setPlayChordSymbols(const bool isEnabled)
220 : {
221 0 : m_playChordSymbols = isEnabled;
222 0 : }
223 :
224 0 : bool PlaybackModel::useScoreDynamicsForOffstreamPlayback() const
225 : {
226 0 : return m_useScoreDynamicsForOffstreamPlayback;
227 : }
228 :
229 1 : void PlaybackModel::setUseScoreDynamicsForOffstreamPlayback(bool use)
230 : {
231 1 : m_useScoreDynamicsForOffstreamPlayback = use;
232 1 : }
233 :
234 0 : bool PlaybackModel::isMetronomeEnabled() const
235 : {
236 0 : return m_metronomeEnabled;
237 : }
238 :
239 3 : void PlaybackModel::setIsMetronomeEnabled(const bool isEnabled)
240 : {
241 3 : if (m_metronomeEnabled == isEnabled) {
242 2 : return;
243 : }
244 :
245 1 : m_metronomeEnabled = isEnabled;
246 1 : reloadMetronomeEvents();
247 : }
248 :
249 4 : const InstrumentTrackId& PlaybackModel::metronomeTrackId() const
250 : {
251 4 : return METRONOME_TRACK_ID;
252 : }
253 :
254 33 : InstrumentTrackId PlaybackModel::chordSymbolsTrackId(const ID& partId) const
255 : {
256 33 : return { partId, CHORD_SYMBOLS_INSTRUMENT_ID };
257 33 : }
258 :
259 0 : bool PlaybackModel::isChordSymbolsTrack(const InstrumentTrackId& trackId) const
260 : {
261 0 : return trackId == chordSymbolsTrackId(trackId.partId);
262 : }
263 :
264 0 : bool PlaybackModel::hasSoundFlags(const InstrumentTrackId& trackId) const
265 : {
266 0 : const Part* part = m_score ? m_score->partById(trackId.partId) : nullptr;
267 0 : if (!part) {
268 0 : return false;
269 : }
270 :
271 0 : const TrackRange trackRange = part->trackRange();
272 0 : return m_playbackCtx->hasSoundFlags(trackRange.startTrack, trackRange.endTrack);
273 : }
274 :
275 36 : PlaybackData& PlaybackModel::resolveTrackPlaybackData(const InstrumentTrackId& trackId)
276 : {
277 36 : auto search = m_playbackDataMap.find(trackId);
278 :
279 36 : if (search != m_playbackDataMap.cend()) {
280 36 : return search->second;
281 : }
282 :
283 0 : const Part* part = m_score ? m_score->partById(trackId.partId.toUint64()) : nullptr;
284 :
285 0 : if (!part) {
286 0 : static PlaybackData empty;
287 0 : return empty;
288 : }
289 :
290 0 : const TrackRange trackRange = part->trackRange();
291 0 : update(0, m_score->lastMeasure()->tick().ticks(), trackRange.startTrack, trackRange.endTrack);
292 :
293 0 : return m_playbackDataMap[trackId];
294 : }
295 :
296 31 : PlaybackData& PlaybackModel::resolveTrackPlaybackData(const ID& partId, const String& instrumentId)
297 : {
298 31 : return resolveTrackPlaybackData(idKey(partId, instrumentId));
299 : }
300 :
301 2 : void PlaybackModel::triggerEventsForItems(const std::vector<const EngravingItem*>& items, muse::mpe::duration_t duration, bool flushSound)
302 : {
303 2 : if (items.empty()) {
304 0 : return;
305 : }
306 :
307 2 : InstrumentTrackId trackId = idKey(items);
308 2 : if (!trackId.isValid()) {
309 0 : return;
310 : }
311 :
312 2 : auto trackPlaybackDataIt = m_playbackDataMap.find(trackId);
313 2 : if (trackPlaybackDataIt == m_playbackDataMap.cend()) {
314 0 : return;
315 : }
316 :
317 2 : PlaybackData& trackPlaybackData = trackPlaybackDataIt->second;
318 : ArticulationsProfilePtr profile
319 2 : = profilesRepository() ? profilesRepository()->defaultProfile(trackPlaybackData.setupData.category) : nullptr;
320 2 : if (!profile) {
321 0 : LOGE() << "unsupported instrument family: " << trackId.partId.toUint64();
322 0 : return;
323 : }
324 :
325 2 : const RepeatList& repeats = repeatList();
326 2 : const int firstItemUtick = repeats.tick2utick(items.front()->tick().ticks());
327 2 : const track_idx_t firstItemTrackIdx = items.front()->track();
328 2 : constexpr timestamp_t timestamp = 0;
329 :
330 2 : PlaybackEventsMap result;
331 2 : PlaybackEventList& events = result[timestamp];
332 :
333 2 : SoundPresetChangeEventList soundPresets = m_playbackCtx->soundPresets(firstItemTrackIdx, firstItemUtick);
334 2 : if (!soundPresets.empty()) {
335 0 : events.insert(events.end(), std::make_move_iterator(soundPresets.begin()),
336 : std::make_move_iterator(soundPresets.end()));
337 : }
338 :
339 2 : const TextArticulationEvent textArticulation = m_playbackCtx->textArticulation(firstItemTrackIdx, firstItemUtick);
340 2 : if (!textArticulation.text.empty()) {
341 0 : events.push_back(textArticulation);
342 : }
343 :
344 2 : const SyllableEvent syllable = m_playbackCtx->syllable(firstItemTrackIdx, firstItemUtick);
345 2 : if (!syllable.text.empty()) {
346 0 : events.push_back(syllable);
347 : }
348 :
349 2 : dynamic_level_t dynamicLevel = dynamicLevelFromType(muse::mpe::DynamicType::Natural);
350 :
351 4 : for (const EngravingItem* item : items) {
352 2 : if (m_useScoreDynamicsForOffstreamPlayback) {
353 1 : if (!item->isNote() || toNote(item)->userVelocity() == 0) {
354 1 : const int utick = repeats.tick2utick(item->tick().ticks());
355 1 : dynamicLevel = m_playbackCtx->appliableDynamicLevel(item->track(), utick);
356 : }
357 : }
358 :
359 2 : if (item->isHarmony()) {
360 0 : m_renderer.renderChordSymbol(toHarmony(item), timestamp, duration, dynamicLevel, profile, result);
361 0 : continue;
362 : }
363 :
364 2 : m_renderer.render(item, timestamp, duration, dynamicLevel, m_playbackCtx, profile, result);
365 : }
366 :
367 2 : trackPlaybackData.offStream.send(result, flushSound);
368 2 : }
369 :
370 0 : void PlaybackModel::triggerMetronome(int tick)
371 : {
372 0 : auto trackPlaybackData = m_playbackDataMap.find(METRONOME_TRACK_ID);
373 0 : if (trackPlaybackData == m_playbackDataMap.cend()) {
374 0 : return;
375 : }
376 :
377 0 : const ArticulationsProfilePtr profile = defaultActiculationProfile(METRONOME_TRACK_ID);
378 :
379 0 : PlaybackEventsMap result;
380 0 : m_renderer.renderMetronome(m_score, tick, 0, profile, result);
381 0 : trackPlaybackData->second.offStream.send(result, true /*flushOffstream*/);
382 0 : }
383 :
384 0 : void PlaybackModel::triggerCountIn(int tick, muse::mpe::duration_t& countInDuration)
385 : {
386 0 : auto trackPlaybackData = m_playbackDataMap.find(METRONOME_TRACK_ID);
387 0 : if (trackPlaybackData == m_playbackDataMap.cend()) {
388 0 : return;
389 : }
390 :
391 0 : const ArticulationsProfilePtr profile = defaultActiculationProfile(METRONOME_TRACK_ID);
392 :
393 0 : PlaybackEventsMap result;
394 0 : m_renderer.renderCountIn(m_score, tick, 0, profile, result, countInDuration);
395 0 : trackPlaybackData->second.offStream.send(result, true /*flushOffstream*/);
396 0 : }
397 :
398 7 : InstrumentTrackIdSet PlaybackModel::existingTrackIdSet() const
399 : {
400 7 : InstrumentTrackIdSet result;
401 7 : result.reserve(m_playbackDataMap.size());
402 :
403 54 : for (const auto& pair : m_playbackDataMap) {
404 47 : result.insert(pair.first);
405 : }
406 :
407 7 : return result;
408 0 : }
409 :
410 0 : muse::async::Channel<InstrumentTrackId> PlaybackModel::trackAdded() const
411 : {
412 0 : return m_trackAdded;
413 : }
414 :
415 0 : muse::async::Channel<InstrumentTrackId> PlaybackModel::trackRemoved() const
416 : {
417 0 : return m_trackRemoved;
418 : }
419 :
420 31 : void PlaybackModel::update(const int tickFrom, const int tickTo, const track_idx_t trackFrom, const track_idx_t trackTo,
421 : ChangedTrackIdSet* trackChanges)
422 : {
423 31 : m_score->masterScore()->setExpandRepeats(m_expandRepeats);
424 31 : updateSetupData();
425 31 : updateContext(trackFrom, trackTo, tickFrom, tickTo);
426 31 : updateEvents(tickFrom, tickTo, trackFrom, trackTo, trackChanges);
427 31 : }
428 :
429 31 : void PlaybackModel::updateSetupData()
430 : {
431 31 : EID scoreEID = m_score->eid();
432 31 : if (!scoreEID.isValid()) {
433 20 : scoreEID = m_score->assignNewEID();
434 : }
435 :
436 31 : std::string scoreId = scoreEID.toStdString();
437 :
438 118 : for (const Part* part : m_score->parts()) {
439 174 : for (const auto& pair : part->instruments()) {
440 87 : InstrumentTrackId trackId = idKey(part->id(), pair.second->id());
441 87 : if (!trackId.isValid() || muse::contains(m_playbackDataMap, trackId)) {
442 41 : continue;
443 : }
444 :
445 46 : PlaybackSetupData& setupData = m_playbackDataMap[trackId].setupData;
446 46 : m_setupResolver.resolveSetupData(pair.second, setupData);
447 46 : setupData.scoreId = scoreId;
448 87 : }
449 :
450 87 : if (part->hasChordSymbol()) {
451 0 : InstrumentTrackId trackId = chordSymbolsTrackId(part->id());
452 0 : PlaybackSetupData& setupData = m_playbackDataMap[trackId].setupData;
453 0 : m_setupResolver.resolveChordSymbolsSetupData(part->instrument(), setupData);
454 0 : setupData.scoreId = scoreId;
455 0 : }
456 : }
457 :
458 31 : PlaybackSetupData& metronomeSetupData = m_playbackDataMap[METRONOME_TRACK_ID].setupData;
459 31 : m_setupResolver.resolveMetronomeSetupData(metronomeSetupData);
460 31 : metronomeSetupData.scoreId = scoreId;
461 31 : }
462 :
463 31 : void PlaybackModel::updateContext(const track_idx_t trackFrom, const track_idx_t trackTo, const int tickFrom, const int tickTo)
464 : {
465 31 : m_playbackCtx->update(trackFrom, trackTo, tickFrom, tickTo, m_expandRepeats);
466 :
467 118 : for (const Part* part : m_score->parts()) {
468 87 : const TrackRange trackRange = part->trackRange();
469 87 : if (trackTo <= trackRange.startTrack || trackFrom >= trackRange.endTrack) {
470 11 : continue;
471 : }
472 :
473 76 : InstrumentTrackIdSet trackIds = part->instrumentTrackIdSet();
474 76 : if (part->hasChordSymbol()) {
475 0 : trackIds.insert(chordSymbolsTrackId(part->id()));
476 : }
477 :
478 76 : if (trackIds.empty()) {
479 0 : continue;
480 : }
481 :
482 : const PartData data {
483 152 : m_playbackCtx->dynamicLevelLayers(trackRange.startTrack, trackRange.endTrack),
484 152 : m_playbackCtx->soundPresets(trackRange.startTrack, trackRange.endTrack),
485 152 : m_playbackCtx->textArticulations(trackRange.startTrack, trackRange.endTrack),
486 152 : m_playbackCtx->syllables(trackRange.startTrack, trackRange.endTrack)
487 76 : };
488 :
489 152 : for (const InstrumentTrackId& trackId : trackIds) {
490 76 : applyContextToTrackData(trackId, data);
491 : }
492 76 : }
493 31 : }
494 :
495 76 : void PlaybackModel::applyContextToTrackData(const InstrumentTrackId& trackId, const PartData& data)
496 : {
497 76 : PlaybackData& trackData = m_playbackDataMap[trackId];
498 76 : trackData.dynamics = data.dynamics;
499 :
500 76 : std::set<timestamp_t> newEventTimestamps;
501 :
502 228 : const auto appendEvents = [&trackData, &newEventTimestamps](const auto& events) {
503 228 : for (const auto& pair : events) {
504 0 : PlaybackEventList& list = trackData.originEvents[pair.first];
505 :
506 : //! NOTE: this assumes that the list has already been cleared in clearExpiredEvents
507 : //! Necessary to prevent event duplication (unchanged lists should not be modified)
508 0 : if (list.empty() || muse::contains(newEventTimestamps, pair.first)) {
509 0 : list.insert(list.end(), pair.second.begin(), pair.second.end());
510 0 : newEventTimestamps.insert(pair.first);
511 : }
512 : }
513 304 : };
514 :
515 76 : appendEvents(data.soundPresets);
516 76 : appendEvents(data.textArticulations);
517 76 : appendEvents(data.syllables);
518 76 : }
519 :
520 581 : void PlaybackModel::processSegment(const int tickPositionOffset, const Segment* segment, const std::set<staff_idx_t>& staffIdxSet,
521 : bool isFirstChordRestSegmentOfMeasure, ChangedTrackIdSet* trackChanges)
522 : {
523 602 : for (const EngravingItem* item : segment->annotations()) {
524 21 : if (!item || !item->part()) {
525 21 : continue;
526 : }
527 :
528 21 : const Harmony* chordSymbol = findChordSymbol(item);
529 21 : if (!chordSymbol) {
530 21 : continue;
531 : }
532 :
533 0 : staff_idx_t staffIdx = item->staffIdx();
534 0 : if (staffIdxSet.find(staffIdx) == staffIdxSet.cend()) {
535 0 : continue;
536 : }
537 :
538 0 : InstrumentTrackId trackId = chordSymbolsTrackId(item->part()->id());
539 :
540 0 : ArticulationsProfilePtr profile = defaultActiculationProfile(trackId);
541 0 : if (!profile) {
542 0 : LOGE() << "unsupported instrument family: " << item->part()->id();
543 0 : continue;
544 : }
545 :
546 0 : if (chordSymbol->play()) {
547 0 : m_renderer.renderChordSymbol(chordSymbol, tickPositionOffset, profile, m_playbackCtx,
548 0 : m_playbackDataMap[trackId].originEvents);
549 : }
550 :
551 0 : collectChangesTracks(trackId, trackChanges);
552 0 : }
553 :
554 581 : if (segment->isTimeTickType()) {
555 6 : return; // optimization: search only for annotations
556 : }
557 :
558 4987 : for (const EngravingItem* item : segment->elist()) {
559 4412 : if (!item || !item->isChordRest() || !item->part()) {
560 3446 : continue;
561 : }
562 :
563 1103 : staff_idx_t staffIdx = item->staffIdx();
564 1103 : if (staffIdxSet.find(staffIdx) == staffIdxSet.cend()) {
565 96 : continue;
566 : }
567 :
568 1007 : InstrumentTrackId trackId = idKey(item);
569 :
570 1007 : if (!trackId.isValid()) {
571 0 : continue;
572 : }
573 :
574 1007 : if (isFirstChordRestSegmentOfMeasure) {
575 281 : if (item->isMeasureRepeat()) {
576 16 : const MeasureRepeat* measureRepeat = toMeasureRepeat(item);
577 16 : const Measure* currentMeasure = measureRepeat->measure();
578 :
579 16 : processMeasureRepeat(tickPositionOffset, measureRepeat, currentMeasure, staffIdx, trackChanges);
580 :
581 16 : continue;
582 281 : } else if (item->voice() == 0) {
583 265 : const Measure* currentMeasure = segment->measure();
584 :
585 265 : if (currentMeasure->measureRepeatCount(staffIdx) > 0) {
586 5 : const MeasureRepeat* measureRepeat = currentMeasure->measureRepeatElement(staffIdx);
587 :
588 5 : processMeasureRepeat(tickPositionOffset, measureRepeat, currentMeasure, staffIdx, trackChanges);
589 5 : continue;
590 5 : }
591 : }
592 : }
593 :
594 986 : if (item->isRestFamily()) {
595 20 : continue;
596 : }
597 :
598 966 : ArticulationsProfilePtr profile = defaultActiculationProfile(trackId);
599 966 : if (!profile) {
600 0 : LOGE() << "unsupported instrument family: " << item->part()->id();
601 0 : continue;
602 : }
603 :
604 966 : m_renderer.render(item, tickPositionOffset, profile, m_playbackCtx, m_playbackDataMap[trackId].originEvents);
605 :
606 966 : collectChangesTracks(trackId, trackChanges);
607 1007 : }
608 : }
609 :
610 21 : void PlaybackModel::processMeasureRepeat(const int tickPositionOffset, const MeasureRepeat* measureRepeat, const Measure* currentMeasure,
611 : const staff_idx_t staffIdx, ChangedTrackIdSet* trackChanges)
612 : {
613 21 : if (!measureRepeat || !currentMeasure) {
614 0 : return;
615 : }
616 :
617 21 : const Measure* referringMeasure = measureRepeat->referringMeasure(currentMeasure);
618 21 : if (!referringMeasure) {
619 0 : return;
620 : }
621 :
622 21 : IF_ASSERT_FAILED(referringMeasure != currentMeasure) {
623 0 : return;
624 : }
625 :
626 21 : int currentMeasureTick = currentMeasure->tick().ticks();
627 21 : int referringMeasureTick = referringMeasure->tick().ticks();
628 21 : int repeatPositionTickOffset = currentMeasureTick - referringMeasureTick;
629 21 : int tickFrom = tickPositionOffset + repeatPositionTickOffset;
630 :
631 42 : std::set<staff_idx_t> staffToProcessIdxSet { staffIdx };
632 21 : int chordRestSegmentNum = -1;
633 :
634 134 : for (const Segment* seg = referringMeasure->first(); seg; seg = seg->next()) {
635 113 : if (!seg->isChordRestType() && !seg->isTimeTickType()) {
636 45 : continue;
637 : }
638 :
639 68 : if (seg->isChordRestType()) {
640 68 : chordRestSegmentNum++;
641 : }
642 :
643 68 : processSegment(tickFrom, seg, staffToProcessIdxSet, chordRestSegmentNum == 0, trackChanges);
644 : }
645 21 : }
646 :
647 31 : void PlaybackModel::updateEvents(const int tickFrom, const int tickTo, const track_idx_t trackFrom, const track_idx_t trackTo,
648 : ChangedTrackIdSet* trackChanges)
649 : {
650 32 : TRACEFUNC;
651 :
652 62 : std::set<staff_idx_t> staffToProcessIdxSet = m_score->staffIdxSetFromRange(trackFrom, trackTo, [](const Staff& staff) {
653 85 : return staff.isPrimaryStaff(); // skip linked staves
654 31 : });
655 :
656 31 : const ArticulationsProfilePtr metronomeProfile = defaultActiculationProfile(METRONOME_TRACK_ID);
657 31 : PlaybackEventsMap& metronomeEvents = m_playbackDataMap[METRONOME_TRACK_ID].originEvents;
658 :
659 79 : for (const RepeatSegment* repeatSegment : repeatList()) {
660 48 : int tickPositionOffset = repeatSegment->utick - repeatSegment->tick;
661 48 : int repeatStartTick = repeatSegment->tick;
662 48 : int repeatEndTick = repeatSegment->endTick();
663 :
664 48 : if (repeatStartTick > tickTo || repeatEndTick <= tickFrom) {
665 0 : continue;
666 : }
667 :
668 210 : for (const Measure* measure : repeatSegment->measureList()) {
669 162 : int measureStartTick = measure->tick().ticks();
670 162 : int measureEndTick = measure->endTick().ticks();
671 :
672 162 : if (measureStartTick > tickTo || measureEndTick <= tickFrom) {
673 10 : continue;
674 : }
675 :
676 152 : int chordRestSegmentNum = -1;
677 :
678 981 : for (const Segment* segment = measure->first(); segment; segment = segment->next()) {
679 829 : if (!segment->isChordRestType() && !segment->isTimeTickType()) {
680 293 : continue;
681 : }
682 :
683 536 : int segmentStartTick = segment->tick().ticks();
684 536 : int segmentEndTick = segmentStartTick + segment->ticks().ticks();
685 :
686 536 : if (segmentStartTick > tickTo || segmentEndTick <= tickFrom) {
687 23 : continue;
688 : }
689 :
690 513 : if (segment->isChordRestType()) {
691 507 : chordRestSegmentNum++;
692 : }
693 :
694 513 : processSegment(tickPositionOffset, segment, staffToProcessIdxSet, chordRestSegmentNum == 0, trackChanges);
695 : }
696 :
697 152 : if (m_metronomeEnabled) {
698 148 : m_renderer.renderMetronome(m_score, measure, tickPositionOffset, metronomeProfile, metronomeEvents);
699 148 : collectChangesTracks(METRONOME_TRACK_ID, trackChanges);
700 : }
701 : }
702 : }
703 31 : }
704 :
705 1 : void PlaybackModel::reloadMetronomeEvents()
706 : {
707 2 : TRACEFUNC;
708 :
709 1 : PlaybackData& metronomeData = m_playbackDataMap[METRONOME_TRACK_ID];
710 1 : metronomeData.originEvents.clear();
711 :
712 1 : if (!m_metronomeEnabled) {
713 1 : metronomeData.mainStream.send(metronomeData.originEvents, metronomeData.dynamics);
714 1 : return;
715 : }
716 :
717 0 : const ArticulationsProfilePtr metronomeProfile = defaultActiculationProfile(METRONOME_TRACK_ID);
718 :
719 0 : for (const RepeatSegment* repeatSegment : repeatList()) {
720 0 : int tickPositionOffset = repeatSegment->utick - repeatSegment->tick;
721 :
722 0 : for (const Measure* measure : repeatSegment->measureList()) {
723 0 : m_renderer.renderMetronome(m_score, measure, tickPositionOffset, metronomeProfile, metronomeData.originEvents);
724 : }
725 : }
726 :
727 0 : metronomeData.mainStream.send(metronomeData.originEvents, metronomeData.dynamics);
728 0 : muse::remove(m_changedTrackIdSet, METRONOME_TRACK_ID);
729 1 : }
730 :
731 7 : bool PlaybackModel::hasToReloadTracks(const ScoreChanges& changes) const
732 : {
733 : static const std::unordered_set<ElementType> REQUIRED_TYPES {
734 : ElementType::PART,
735 : ElementType::PLAYTECH_ANNOTATION,
736 : ElementType::CAPO,
737 : ElementType::DYNAMIC,
738 : ElementType::HAIRPIN,
739 : ElementType::HAIRPIN_SEGMENT,
740 : ElementType::HARMONY,
741 : ElementType::STAFF_TEXT,
742 : ElementType::SOUND_FLAG,
743 : ElementType::MEASURE_REPEAT,
744 : ElementType::GUITAR_BEND,
745 : ElementType::GUITAR_BEND_SEGMENT,
746 : ElementType::BREATH,
747 9 : };
748 :
749 15 : for (const ElementType type : changes.changedTypes) {
750 8 : if (muse::contains(REQUIRED_TYPES, type)) {
751 0 : return true;
752 : }
753 : }
754 :
755 7 : if (changes.isValidBoundary()) {
756 7 : const Measure* measureTo = m_score->tick2measure(Fraction::fromTicks(changes.tickTo));
757 7 : if (!measureTo) {
758 0 : return false;
759 : }
760 :
761 7 : if (measureTo->containsMeasureRepeat(changes.staffIdxFrom, changes.staffIdxTo)) {
762 0 : return true;
763 : }
764 :
765 7 : const Measure* nextMeasure = measureTo->nextMeasure();
766 :
767 11 : for (int i = 0; i < MeasureRepeat::MAX_NUM_MEASURES && nextMeasure; ++i) {
768 4 : if (nextMeasure->containsMeasureRepeat(changes.staffIdxFrom, changes.staffIdxTo)) {
769 0 : return true;
770 : }
771 :
772 4 : nextMeasure = nextMeasure->nextMeasure();
773 : }
774 : }
775 :
776 7 : return false;
777 : }
778 :
779 14 : bool PlaybackModel::hasToReloadScore(const ScoreChanges& changes) const
780 : {
781 : static const std::unordered_set<ElementType> REQUIRED_TYPES {
782 : ElementType::SCORE,
783 : ElementType::GRADUAL_TEMPO_CHANGE,
784 : ElementType::GRADUAL_TEMPO_CHANGE_SEGMENT,
785 : ElementType::TEMPO_TEXT,
786 : ElementType::LAYOUT_BREAK,
787 : ElementType::FERMATA,
788 : ElementType::VOLTA,
789 : ElementType::VOLTA_SEGMENT,
790 : ElementType::SYSTEM_TEXT,
791 : ElementType::JUMP,
792 : ElementType::MARKER,
793 : ElementType::BREATH,
794 : ElementType::INSTRUMENT_CHANGE,
795 16 : };
796 :
797 26 : for (const ElementType type : changes.changedTypes) {
798 : // Automation edits on one staff shouldn't reload all tracks
799 16 : if (type == ElementType::SCORE && isScopedAutomationChange(changes)) {
800 2 : continue;
801 : }
802 :
803 14 : if (muse::contains(REQUIRED_TYPES, type)) {
804 4 : return true;
805 : }
806 : }
807 :
808 : static const std::unordered_set<mu::engraving::Pid> REQUIRED_PROPERTIES {
809 : mu::engraving::Pid::REPEAT_START,
810 : mu::engraving::Pid::REPEAT_END,
811 : mu::engraving::Pid::REPEAT_JUMP,
812 : mu::engraving::Pid::REPEAT_COUNT,
813 12 : };
814 :
815 10 : for (const Pid pid: changes.changedPropertyIdSet) {
816 0 : if (muse::contains(REQUIRED_PROPERTIES, pid)) {
817 0 : return true;
818 : }
819 : }
820 :
821 10 : return false;
822 : }
823 :
824 5 : bool PlaybackModel::hasAutomationChange(const ScoreChanges& changes) const
825 : {
826 5 : for (const auto& pair : changes.changedObjects) {
827 1 : if (muse::contains(pair.second, CommandType::EditAutomationPoints)) {
828 1 : return true;
829 : }
830 : }
831 :
832 4 : return false;
833 : }
834 :
835 8 : void PlaybackModel::clearExpiredTracks()
836 : {
837 41 : auto needRemoveTrack = [this](const InstrumentTrackId& trackId) {
838 41 : const Part* part = m_score->partById(trackId.partId.toUint64());
839 :
840 41 : if (!part) {
841 0 : return true;
842 : }
843 :
844 41 : if (trackId.instrumentId == CHORD_SYMBOLS_INSTRUMENT_ID) {
845 0 : return !part->hasChordSymbol();
846 : }
847 :
848 41 : return !part->instruments().contains(trackId.instrumentId);
849 8 : };
850 :
851 8 : auto it = m_playbackDataMap.cbegin();
852 :
853 57 : while (it != m_playbackDataMap.cend()) {
854 49 : if (it->first == METRONOME_TRACK_ID) {
855 8 : ++it;
856 8 : continue;
857 : }
858 :
859 41 : if (needRemoveTrack(it->first)) {
860 0 : m_trackRemoved.send(it->first);
861 0 : it = m_playbackDataMap.erase(it);
862 0 : continue;
863 : }
864 :
865 41 : ++it;
866 : }
867 8 : }
868 :
869 10 : void mu::engraving::PlaybackModel::removeEventsFromRange(const track_idx_t trackFrom, const track_idx_t trackTo,
870 : const timestamp_t timestampFrom, const timestamp_t timestampTo,
871 : ChangedTrackIdSet* trackChanges)
872 : {
873 53 : for (const Part* part : m_score->parts()) {
874 43 : const TrackRange trackRange = part->trackRange();
875 43 : if (trackRange.startTrack > trackTo || trackRange.endTrack <= trackFrom) {
876 10 : continue;
877 : }
878 :
879 66 : for (const InstrumentTrackId& trackId : part->instrumentTrackIdSet()) {
880 33 : removeTrackEvents(trackId, timestampFrom, timestampTo, trackChanges);
881 33 : }
882 :
883 33 : removeTrackEvents(chordSymbolsTrackId(part->id()), timestampFrom, timestampTo, trackChanges);
884 : }
885 10 : }
886 :
887 7 : void PlaybackModel::clearExpiredEvents(const int tickFrom, const int tickTo, const track_idx_t trackFrom, const track_idx_t trackTo,
888 : ChangedTrackIdSet* trackChanges)
889 : {
890 8 : TRACEFUNC;
891 :
892 7 : if (!m_score) {
893 0 : return;
894 : }
895 :
896 7 : const Measure* lastMeasure = m_score->lastMeasure();
897 7 : if (!lastMeasure) {
898 0 : return;
899 : }
900 :
901 7 : if (tickFrom == 0 && lastMeasure->endTick().ticks() == tickTo) {
902 3 : removeEventsFromRange(trackFrom, trackTo, -1 /*timestampFrom*/, -1 /*timestampTo*/, trackChanges);
903 3 : removeTrackEvents(METRONOME_TRACK_ID, -1 /*timestampFrom*/, -1 /*timestampTo*/, trackChanges);
904 3 : return;
905 : }
906 :
907 11 : for (const RepeatSegment* repeatSegment : repeatList()) {
908 7 : const int tickPositionOffset = repeatSegment->utick - repeatSegment->tick;
909 7 : const int repeatStartTick = repeatSegment->tick;
910 7 : const int repeatEndTick = repeatSegment->endTick();
911 :
912 7 : if (repeatStartTick > tickTo || repeatEndTick <= tickFrom) {
913 0 : continue;
914 : }
915 :
916 7 : int removeEventsFromTick = std::max(tickFrom, repeatStartTick);
917 7 : timestamp_t removeEventsFrom = timestampFromTicks(m_score, removeEventsFromTick + tickPositionOffset);
918 :
919 : //! NOTE: the end tick of the current repeat segment == the start tick of the next repeat segment
920 : //! so subtract 1 to avoid removing events belonging to the next segment
921 7 : int removeEventsToTick = std::min(tickTo, repeatEndTick - 1);
922 7 : timestamp_t removeEventsTo = timestampFromTicks(m_score, removeEventsToTick + tickPositionOffset);
923 :
924 7 : removeEventsFromRange(trackFrom, trackTo, removeEventsFrom, removeEventsTo, trackChanges);
925 :
926 7 : if (!m_metronomeEnabled) {
927 0 : continue;
928 : }
929 :
930 29 : for (const Measure* measure : repeatSegment->measureList()) {
931 22 : const int measureStartTick = measure->tick().ticks();
932 22 : const int measureEndTick = measure->endTick().ticks();
933 :
934 22 : if (measureStartTick > tickTo || measureEndTick <= tickFrom) {
935 10 : continue;
936 : }
937 :
938 12 : removeEventsFrom = timestampFromTicks(m_score, measureStartTick + tickPositionOffset);
939 12 : removeEventsTo = timestampFromTicks(m_score, measureEndTick + tickPositionOffset - 1);
940 :
941 12 : removeTrackEvents(METRONOME_TRACK_ID, removeEventsFrom, removeEventsTo, trackChanges);
942 : }
943 : }
944 7 : }
945 :
946 1162 : void PlaybackModel::collectChangesTracks(const InstrumentTrackId& trackId, ChangedTrackIdSet* result)
947 : {
948 1162 : if (!result) {
949 832 : return;
950 : }
951 :
952 330 : result->insert(trackId);
953 : }
954 :
955 7 : void PlaybackModel::notifyAboutChanges(const InstrumentTrackIdSet& oldTracks, const InstrumentTrackIdSet& changedTracks)
956 : {
957 44 : for (const InstrumentTrackId& trackId : changedTracks) {
958 37 : if (muse::value(m_sendEventsOnScoreChangeMap, trackId, false)) {
959 1 : sendEvents(trackId);
960 : } else {
961 36 : m_changedTrackIdSet.insert(trackId);
962 : }
963 : }
964 :
965 54 : for (auto it = m_playbackDataMap.cbegin(); it != m_playbackDataMap.cend(); ++it) {
966 47 : if (!muse::contains(oldTracks, it->first)) {
967 0 : m_trackAdded.send(it->first);
968 : }
969 : }
970 :
971 7 : if (!changedTracks.empty()) {
972 7 : m_tracksDataChanged.send(changedTracks);
973 : }
974 7 : }
975 :
976 4 : void PlaybackModel::sendEvents(const InstrumentTrackId& trackId)
977 : {
978 4 : auto it = m_playbackDataMap.find(trackId);
979 4 : if (it == m_playbackDataMap.cend()) {
980 0 : return;
981 : }
982 :
983 4 : PlaybackData& data = it->second;
984 4 : data.mainStream.send(data.originEvents, data.dynamics);
985 : }
986 :
987 81 : void PlaybackModel::removeTrackEvents(const InstrumentTrackId& trackId, const muse::mpe::timestamp_t timestampFrom,
988 : const muse::mpe::timestamp_t timestampTo, ChangedTrackIdSet* trackChanges)
989 : {
990 81 : IF_ASSERT_FAILED(timestampFrom <= timestampTo) {
991 62 : return;
992 : }
993 :
994 81 : auto search = m_playbackDataMap.find(trackId);
995 81 : if (search == m_playbackDataMap.cend()) {
996 33 : return;
997 : }
998 :
999 48 : PlaybackData& trackPlaybackData = search->second;
1000 48 : const size_t oldSize = trackPlaybackData.originEvents.size();
1001 :
1002 48 : DEFER {
1003 48 : if (oldSize != trackPlaybackData.originEvents.size()) {
1004 48 : collectChangesTracks(trackId, trackChanges);
1005 : }
1006 48 : };
1007 :
1008 48 : if (timestampFrom == -1 && timestampTo == -1) {
1009 29 : search->second.originEvents.clear();
1010 29 : return;
1011 : }
1012 :
1013 19 : PlaybackEventsMap::iterator lowerBound;
1014 :
1015 19 : if (timestampFrom == 0) {
1016 : //!Note Some events might be started RIGHT before the "official" start of the track
1017 : //! Need to make sure that we don't miss those events
1018 2 : lowerBound = trackPlaybackData.originEvents.begin();
1019 : } else {
1020 17 : lowerBound = trackPlaybackData.originEvents.lower_bound(timestampFrom);
1021 : }
1022 :
1023 19 : if (lowerBound == trackPlaybackData.originEvents.end()) {
1024 0 : return;
1025 : }
1026 :
1027 19 : auto upperBound = trackPlaybackData.originEvents.upper_bound(timestampTo);
1028 19 : trackPlaybackData.originEvents.erase(lowerBound, upperBound);
1029 48 : }
1030 :
1031 7 : bool PlaybackModel::shouldSkipChanges(const ScoreChanges& changes) const
1032 : {
1033 7 : if (!changes.isValid() || changes.isTextEditing) {
1034 0 : return true;
1035 : }
1036 :
1037 7 : if (changes.changedObjects.size() != 1) {
1038 4 : return false;
1039 : }
1040 :
1041 3 : const auto it = changes.changedObjects.begin();
1042 3 : if (!it->first->isTextBase()) {
1043 3 : return false;
1044 : }
1045 :
1046 0 : const TextBase* text = toTextBase(it->first);
1047 0 : const bool empty = text->empty();
1048 0 : if (!empty) {
1049 0 : return false;
1050 : }
1051 :
1052 0 : if (text->isHarmony() && m_playChordSymbols) {
1053 0 : const InstrumentTrackId trackId = chordSymbolsTrackId(text->part()->id());
1054 0 : if (!muse::contains(m_playbackDataMap, trackId)) {
1055 0 : return false;
1056 : }
1057 0 : }
1058 :
1059 0 : const std::unordered_set<CommandType>& commands = it->second;
1060 0 : if (muse::contains(commands, CommandType::RemoveElement)) {
1061 0 : return false;
1062 : }
1063 :
1064 0 : return true;
1065 : }
1066 :
1067 7 : PlaybackModel::TrackBoundaries PlaybackModel::trackBoundaries(const ScoreChanges& changes) const
1068 : {
1069 7 : TrackBoundaries result;
1070 :
1071 7 : result.trackFrom = staff2track(changes.staffIdxFrom, 0);
1072 7 : result.trackTo = staff2track(changes.staffIdxTo, VOICES);
1073 :
1074 7 : if (hasToReloadScore(changes) || !changes.isValidBoundary()) {
1075 2 : result.trackFrom = 0;
1076 2 : result.trackTo = m_score->ntracks();
1077 : }
1078 :
1079 7 : return result;
1080 : }
1081 :
1082 7 : PlaybackModel::TickBoundaries PlaybackModel::tickBoundaries(const ScoreChanges& changes) const
1083 : {
1084 7 : TickBoundaries result;
1085 :
1086 7 : result.tickFrom = changes.tickFrom;
1087 7 : result.tickTo = changes.tickTo;
1088 :
1089 7 : if (hasToReloadTracks(changes)
1090 7 : || hasToReloadScore(changes)
1091 5 : || hasAutomationChange(changes)
1092 14 : || !changes.isValidBoundary()) {
1093 3 : const Measure* lastMeasure = m_score->lastMeasure();
1094 3 : result.tickFrom = 0;
1095 3 : result.tickTo = lastMeasure ? lastMeasure->endTick().ticks() : 0;
1096 :
1097 3 : return result;
1098 : }
1099 :
1100 4 : for (const auto& pair : changes.changedObjects) {
1101 0 : if (!pair.first->isEngravingItem()) {
1102 0 : continue;
1103 : }
1104 :
1105 0 : const EngravingItem* item = toEngravingItem(pair.first);
1106 :
1107 0 : if (item->isNote()) {
1108 0 : const Note* note = toNote(item);
1109 0 : const Chord* chord = note->chord();
1110 0 : const TremoloTwoChord* tremoloTwo = chord->tremoloTwoChord();
1111 :
1112 0 : if (tremoloTwo) {
1113 0 : const Chord* startChord = tremoloTwo->chord1();
1114 0 : const Chord* endChord = tremoloTwo->chord2();
1115 :
1116 0 : IF_ASSERT_FAILED(startChord && endChord) {
1117 0 : continue;
1118 : }
1119 :
1120 0 : result.tickFrom = std::min(result.tickFrom, startChord->tick().ticks());
1121 0 : result.tickTo = std::max(result.tickTo, endChord->tick().ticks());
1122 : }
1123 :
1124 0 : applyTiedNotesTickBoundaries(note, result);
1125 0 : } else if (item->isTie()) {
1126 0 : applyTieTickBoundaries(toTie(item), result);
1127 : }
1128 :
1129 0 : const EngravingObject* parent = item->ownershipParent();
1130 0 : if (!parent) {
1131 0 : continue;
1132 : }
1133 :
1134 0 : if (parent->isChord()) {
1135 0 : const Chord* chord = toChord(parent);
1136 :
1137 0 : for (const Note* note : chord->notes()) {
1138 0 : applyTiedNotesTickBoundaries(note, result);
1139 : }
1140 :
1141 0 : for (const Spanner* spanner : chord->startingSpanners()) {
1142 0 : if (spanner->isTrill() && result.tickTo < spanner->tick2().ticks()) {
1143 0 : result.tickTo = spanner->tick2().ticks();
1144 : }
1145 : }
1146 0 : } else if (parent->isNote()) {
1147 0 : applyTiedNotesTickBoundaries(toNote(parent), result);
1148 : }
1149 : }
1150 :
1151 4 : return result;
1152 : }
1153 :
1154 37 : const RepeatList& PlaybackModel::repeatList() const
1155 : {
1156 37 : m_score->masterScore()->setExpandRepeats(m_expandRepeats);
1157 :
1158 37 : return m_score->repeatList();
1159 : }
1160 :
1161 1009 : InstrumentTrackId PlaybackModel::idKey(const EngravingItem* item) const
1162 : {
1163 1009 : if (item->isHarmony()) {
1164 0 : return chordSymbolsTrackId(item->part()->id());
1165 : }
1166 :
1167 1009 : return makeInstrumentTrackId(item);
1168 : }
1169 :
1170 2 : InstrumentTrackId PlaybackModel::idKey(const std::vector<const EngravingItem*>& items) const
1171 : {
1172 2 : InstrumentTrackId result;
1173 :
1174 4 : for (const EngravingItem* item : items) {
1175 2 : InstrumentTrackId itemTrackId = idKey(item);
1176 2 : if (result.isValid() && result != itemTrackId) {
1177 0 : LOGE() << "Triggering events for elements with different tracks";
1178 0 : return InstrumentTrackId();
1179 : }
1180 :
1181 2 : result = itemTrackId;
1182 2 : }
1183 :
1184 2 : return result;
1185 2 : }
1186 :
1187 118 : InstrumentTrackId PlaybackModel::idKey(const ID& partId, const String& instrumentId) const
1188 : {
1189 118 : return { partId, instrumentId };
1190 118 : }
1191 :
1192 997 : muse::mpe::ArticulationsProfilePtr PlaybackModel::defaultActiculationProfile(const InstrumentTrackId& trackId) const
1193 : {
1194 997 : auto it = m_playbackDataMap.find(trackId);
1195 997 : if (it == m_playbackDataMap.cend()) {
1196 0 : return nullptr;
1197 : }
1198 :
1199 997 : if (!profilesRepository()) {
1200 0 : return nullptr;
1201 : }
1202 :
1203 997 : return profilesRepository()->defaultProfile(it->second.setupData.category);
1204 : }
1205 :
1206 0 : void PlaybackModel::applyTiedNotesTickBoundaries(const Note* note, TickBoundaries& tickBoundaries)
1207 : {
1208 : const Tie* tie;
1209 0 : if ((tie = note->tieFor())) {
1210 0 : applyTieTickBoundaries(tie, tickBoundaries);
1211 0 : } else if ((tie = note->tieBack())) {
1212 0 : applyTieTickBoundaries(tie, tickBoundaries);
1213 : }
1214 0 : }
1215 :
1216 0 : void PlaybackModel::applyTieTickBoundaries(const Tie* tie, TickBoundaries& tickBoundaries)
1217 : {
1218 0 : const Note* startNote = tie->startNote();
1219 0 : const Note* endNote = tie->endNote();
1220 0 : if (!startNote || !endNote) {
1221 0 : return;
1222 : }
1223 :
1224 0 : const Note* firstTiedNote = startNote->firstTiedNote();
1225 0 : const Note* lastTiedNote = endNote->lastTiedNote();
1226 0 : IF_ASSERT_FAILED(firstTiedNote && lastTiedNote) {
1227 0 : return;
1228 : }
1229 :
1230 0 : tickBoundaries.tickFrom = std::min(tickBoundaries.tickFrom, firstTiedNote->tick().ticks());
1231 0 : tickBoundaries.tickTo = std::max(tickBoundaries.tickTo, lastTiedNote->tick().ticks());
1232 : }
|