LCOV - code coverage report
Current view: top level - src/engraving/playback - playbackmodel.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 71.1 % 651 463
Test Date: 2026-11-05 04:07:02

            Line data    Source code
       1              : /*
       2              :  * SPDX-License-Identifier: GPL-3.0-only
       3              :  * MuseScore-Studio-CLA-applies
       4              :  *
       5              :  * MuseScore Studio
       6              :  * Music Composition & Notation
       7              :  *
       8              :  * Copyright (C) 2025 MuseScore Limited and others
       9              :  *
      10              :  * This program is free software: you can redistribute it and/or modify
      11              :  * it under the terms of the GNU General Public License version 3 as
      12              :  * published by the Free Software Foundation.
      13              :  *
      14              :  * This program is distributed in the hope that it will be useful,
      15              :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      16              :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      17              :  * GNU General Public License for more details.
      18              :  *
      19              :  * You should have received a copy of the GNU General Public License
      20              :  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
      21              :  */
      22              : 
      23              : #include "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              : }
        

Generated by: LCOV version 2.0-1