LCOV - code coverage report
Current view: top level - src/engraving/playback - playbackcontext.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 85.6 % 388 332
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) 2021 MuseScore Limited and others
       9              :  *
      10              :  * This program is free software: you can redistribute it and/or modify
      11              :  * it under the terms of the GNU General Public License version 3 as
      12              :  * published by the Free Software Foundation.
      13              :  *
      14              :  * This program is distributed in the hope that it will be useful,
      15              :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      16              :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      17              :  * GNU General Public License for more details.
      18              :  *
      19              :  * You should have received a copy of the GNU General Public License
      20              :  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
      21              :  */
      22              : 
      23              : #include "playbackcontext.h"
      24              : 
      25              : #include <cmath>
      26              : 
      27              : #include "dom/lyrics.h"
      28              : #include "dom/measure.h"
      29              : #include "dom/measurerepeat.h"
      30              : #include "dom/part.h"
      31              : #include "dom/playtechannotation.h"
      32              : #include "dom/repeatlist.h"
      33              : #include "dom/score.h"
      34              : #include "dom/segment.h"
      35              : #include "dom/soundflag.h"
      36              : #include "dom/staff.h"
      37              : #include "dom/stafftext.h"
      38              : 
      39              : #include "engraving/automation/automationdata.h"
      40              : 
      41              : #include "utils/arrangementutils.h"
      42              : #include "utils/expressionutils.h"
      43              : #include "types/constants.h"
      44              : 
      45              : #include "global/containers.h"
      46              : #include "global/realfn.h"
      47              : 
      48              : using namespace mu::engraving;
      49              : using namespace muse;
      50              : using namespace muse::mpe;
      51              : 
      52            9 : static bool soundFlagPlayable(const SoundFlag* flag)
      53              : {
      54            9 :     if (flag && flag->play()) {
      55            9 :         return !flag->soundPresets().empty() || !flag->playingTechnique().empty();
      56              :     }
      57              : 
      58            0 :     return false;
      59              : }
      60              : 
      61           26 : static dynamic_level_t toDynamicLevel(double normalizedValue)
      62              : {
      63           26 :     return static_cast<dynamic_level_t>(std::lround(normalizedValue * MAX_DYNAMIC_LEVEL));
      64              : }
      65              : 
      66              : template<typename ByTrackMap, typename ResultMap, typename MergeFn>
      67          233 : static void collectByTrackRange(const ByTrackMap& byTrackMap, const track_idx_t trackFrom, const track_idx_t trackTo,
      68              :                                 const Score* score, const std::set<track_idx_t>& usedTracks, ResultMap& result, MergeFn merge)
      69              : {
      70          257 :     for (auto it = byTrackMap.lower_bound(trackFrom); it != byTrackMap.end() && it->first < trackTo; ++it) {
      71           24 :         if (!muse::contains(usedTracks, it->first)) {
      72           18 :             continue;
      73              :         }
      74              : 
      75           35 :         for (const auto& pair : it->second) {
      76           29 :             const timestamp_t timestamp = timestampFromTicks(score, pair.first);
      77           29 :             merge(result[timestamp], pair.second);
      78              :         }
      79              :     }
      80          233 : }
      81              : 
      82           79 : PlaybackContext::PlaybackContext(const Score* score)
      83           79 :     : m_score(score)
      84              : {
      85           79 : }
      86              : 
      87         1817 : dynamic_level_t PlaybackContext::appliableDynamicLevel(const track_idx_t trackIdx, const int nominalPositionTick) const
      88              : {
      89         1818 :     TRACEFUNC;
      90              : 
      91         1817 :     const AutomationCurve* curve = dynamicsCurve(trackIdx);
      92         1817 :     if (!curve) {
      93            0 :         return NATURAL_DYNAMIC_LEVEL;
      94              :     }
      95              : 
      96         1817 :     const auto it = muse::findLessOrEqual(*curve, nominalPositionTick);
      97         1817 :     if (it == curve->cend()) {
      98         1791 :         return NATURAL_DYNAMIC_LEVEL;
      99              :     }
     100              : 
     101           26 :     const auto next = std::next(it);
     102           26 :     if (next == curve->cend()) {
     103           10 :         return toDynamicLevel(it->second.value.outValue);
     104              :     }
     105              : 
     106           16 :     const real_t t = static_cast<real_t>(nominalPositionTick - it->first) / static_cast<real_t>(next->first - it->first);
     107           16 :     return toDynamicLevel(muse::mpe::evaluateAt(next->second.value, it->second.value.outValue, t));
     108         1817 : }
     109              : 
     110         1840 : std::pair<mpe::timestamp_t, PlayingTechniqueType> PlaybackContext::playingTechnique(const track_idx_t trackIdx,
     111              :                                                                                     const int nominalPositionTick) const
     112              : {
     113         1840 :     auto mapIt = m_playTechniquesByTrack.find(trackIdx);
     114         1840 :     if (mapIt == m_playTechniquesByTrack.cend()) {
     115         1669 :         return std::make_pair(0, PlayingTechniqueType::Natural);
     116              :     }
     117              : 
     118          171 :     const PlayTechniquesMap& playTechniquesMap = mapIt->second;
     119              : 
     120          171 :     auto it = findLessOrEqual(playTechniquesMap, nominalPositionTick);
     121          171 :     if (it == playTechniquesMap.cend()) {
     122            6 :         return std::make_pair(0, PlayingTechniqueType::Natural);
     123              :     }
     124              : 
     125          165 :     return std::make_pair(timestampFromTicks(m_score, it->first), it->second);
     126              : }
     127              : 
     128           13 : muse::mpe::timestamp_t PlaybackContext::findPlayingTechniqueTimestamp(const track_idx_t trackIdx, PlayingTechniqueType type,
     129              :                                                                       const int startFromTick) const
     130              : {
     131           13 :     auto mapIt = m_playTechniquesByTrack.find(trackIdx);
     132           13 :     if (mapIt == m_playTechniquesByTrack.cend()) {
     133            0 :         return -1;
     134              :     }
     135              : 
     136           13 :     const PlayTechniquesMap& playTechniquesMap = mapIt->second;
     137              : 
     138           13 :     auto it = playTechniquesMap.upper_bound(startFromTick);
     139              : 
     140           13 :     for (; it != playTechniquesMap.end(); ++it) {
     141            5 :         if (it->second == type) {
     142            5 :             return timestampFromTicks(m_score, it->first);
     143              :         }
     144              :     }
     145              : 
     146            8 :     return -1;
     147              : }
     148              : 
     149           76 : std::map<timestamp_t, SoundPresetChangeEventList> PlaybackContext::soundPresets(const track_idx_t trackFrom,
     150              :                                                                                 const track_idx_t trackTo) const
     151              : {
     152           76 :     std::map<timestamp_t, SoundPresetChangeEventList> result;
     153              : 
     154           76 :     collectByTrackRange(m_soundPresetsByTrack, trackFrom, trackTo, m_score, m_usedTracks, result,
     155            0 :                         [](SoundPresetChangeEventList& list, const SoundPresetChangeEventList& events) {
     156            0 :         list.insert(list.end(), events.begin(), events.end());
     157            0 :     });
     158              : 
     159           76 :     return result;
     160            0 : }
     161              : 
     162            2 : SoundPresetChangeEventList PlaybackContext::soundPresets(const track_idx_t trackIdx, const int nominalPositionTick) const
     163              : {
     164            2 :     auto presetsIt = m_soundPresetsByTrack.find(trackIdx);
     165            2 :     if (presetsIt == m_soundPresetsByTrack.end()) {
     166            2 :         return {};
     167              :     }
     168              : 
     169            0 :     const SoundPresetsMap& map = presetsIt->second;
     170            0 :     auto it = muse::findLessOrEqual(map, nominalPositionTick);
     171            0 :     if (it == map.end()) {
     172            0 :         return {};
     173              :     }
     174              : 
     175            0 :     return it->second;
     176              : }
     177              : 
     178           80 : std::map<timestamp_t, TextArticulationEventList> PlaybackContext::textArticulations(const track_idx_t trackFrom,
     179              :                                                                                     const track_idx_t trackTo) const
     180              : {
     181           80 :     std::map<timestamp_t, TextArticulationEventList> result;
     182              : 
     183           80 :     collectByTrackRange(m_textArticulationsByTrack, trackFrom, trackTo, m_score, m_usedTracks, result,
     184           14 :                         [](TextArticulationEventList& list, const TextArticulationEvent& event) {
     185           14 :         list.push_back(event);
     186           14 :     });
     187              : 
     188           80 :     return result;
     189            0 : }
     190              : 
     191           34 : TextArticulationEvent PlaybackContext::textArticulation(const track_idx_t trackIdx, const int nominalPositionTick) const
     192              : {
     193           34 :     auto articulationsIt = m_textArticulationsByTrack.find(trackIdx);
     194           34 :     if (articulationsIt == m_textArticulationsByTrack.end()) {
     195            2 :         return {};
     196              :     }
     197              : 
     198           32 :     const TextArticulationMap& map = articulationsIt->second;
     199           32 :     auto it = muse::findLessOrEqual(map, nominalPositionTick);
     200           32 :     if (it == map.end()) {
     201            8 :         return {};
     202              :     }
     203              : 
     204           24 :     TextArticulationEvent result = it->second;
     205           24 :     result.flags.setFlag(TextArticulationEvent::StartsAtPlaybackPosition, it->first == nominalPositionTick);
     206              : 
     207           24 :     return result;
     208           34 : }
     209              : 
     210           77 : std::map<timestamp_t, SyllableEventList> PlaybackContext::syllables(const track_idx_t trackFrom, const track_idx_t trackTo) const
     211              : {
     212           77 :     std::map<timestamp_t, SyllableEventList> result;
     213              : 
     214           77 :     collectByTrackRange(m_syllablesByTrack, trackFrom, trackTo, m_score, m_usedTracks, result,
     215           15 :                         [](SyllableEventList& list, const SyllableEvent& event) {
     216           15 :         list.push_back(event);
     217           15 :     });
     218              : 
     219           77 :     return result;
     220            0 : }
     221              : 
     222            2 : SyllableEvent PlaybackContext::syllable(const track_idx_t trackIdx, const int nominalPositionTick) const
     223              : {
     224            2 :     auto syllablesIt = m_syllablesByTrack.find(trackIdx);
     225            2 :     if (syllablesIt == m_syllablesByTrack.end()) {
     226            2 :         return {};
     227              :     }
     228              : 
     229            0 :     const SyllableMap& map = syllablesIt->second;
     230            0 :     auto it = muse::findLessOrEqual(map, nominalPositionTick);
     231            0 :     if (it == map.end()) {
     232            0 :         return {};
     233              :     }
     234              : 
     235            0 :     SyllableEvent result = it->second;
     236            0 :     result.flags.setFlag(SyllableEvent::StartsAtPlaybackPosition, it->first == nominalPositionTick);
     237              : 
     238            0 :     return result;
     239            2 : }
     240              : 
     241           83 : DynamicAutomationLayers PlaybackContext::dynamicLevelLayers(const track_idx_t trackFrom, const track_idx_t trackTo) const
     242              : {
     243           84 :     TRACEFUNC;
     244              : 
     245           83 :     if (!m_score || !m_score->automationData()) {
     246            0 :         return {};
     247              :     }
     248              : 
     249           83 :     DynamicAutomationLayers result;
     250           83 :     const AutomationCurve* lastCurve = nullptr;
     251           83 :     DynamicAutomationMap lastLevelMap;
     252              : 
     253          451 :     for (track_idx_t trackIdx = trackFrom; trackIdx < trackTo; ++trackIdx) {
     254          368 :         const AutomationCurve* curve = dynamicsCurve(trackIdx);
     255          368 :         if (!curve || curve->empty()) {
     256          328 :             continue;
     257              :         }
     258              : 
     259           40 :         if (curve != lastCurve) {
     260           12 :             lastLevelMap.clear();
     261           12 :             lastCurve = curve;
     262           12 :             auto hint = lastLevelMap.end();
     263              : 
     264              :             // AutomationCurve ticks are always in expanded utick space, regardless of the Play Repeats setting
     265           12 :             const TempoTimeline& expandedTimeline = m_score->tempoTimeline(/*expandRepeats*/ true);
     266              : 
     267           12 :             if (curve->cbegin()->first > 0) {
     268            1 :                 AutomationPoint naturalPoint;
     269            1 :                 naturalPoint.value.outValue = real_t(NATURAL_DYNAMIC_LEVEL) / real_t(MAX_DYNAMIC_LEVEL);
     270            1 :                 hint = lastLevelMap.insert(hint, { expandedTimeline.utick2utime(0) * 1000000, naturalPoint.value });
     271              :             }
     272              : 
     273           74 :             for (const auto& [tick, point] : *curve) {
     274           62 :                 hint = lastLevelMap.insert(hint, { expandedTimeline.utick2utime(tick) * 1000000, point.value });
     275              :             }
     276              :         }
     277              : 
     278           40 :         result[static_cast<layer_idx_t>(trackIdx)] = lastLevelMap;
     279              :     }
     280              : 
     281           83 :     return result;
     282           83 : }
     283              : 
     284           47 : void PlaybackContext::update(const track_idx_t trackFrom, const track_idx_t trackTo, const int tickFrom, const int tickTo,
     285              :                              bool expandRepeats)
     286              : {
     287           48 :     TRACEFUNC;
     288              : 
     289           47 :     IF_ASSERT_FAILED(m_score) {
     290            0 :         return;
     291              :     }
     292              : 
     293           47 :     IF_ASSERT_FAILED(trackFrom <= trackTo) {
     294            0 :         return;
     295              :     }
     296              : 
     297           47 :     m_dynamicsCurveByTrack.clear();
     298              : 
     299          116 :     for (const RepeatSegment* repeatSegment : m_score->repeatList(expandRepeats)) {
     300           69 :         const int repeatStartTick = repeatSegment->tick;
     301           69 :         const int repeatEndTick = repeatSegment->endTick();
     302              : 
     303           69 :         if (repeatStartTick > tickTo || repeatEndTick <= tickFrom) {
     304            0 :             continue;
     305              :         }
     306              : 
     307           69 :         std::vector<const MeasureRepeat*> measureRepeats;
     308           69 :         int tickPositionOffset = repeatSegment->utick - repeatSegment->tick;
     309              : 
     310          330 :         for (const Measure* measure : repeatSegment->measureList()) {
     311          261 :             const int measureStartTick = measure->tick().ticks();
     312          261 :             const int measureEndTick = measure->endTick().ticks();
     313              : 
     314          261 :             if (measureStartTick > tickTo || measureEndTick <= tickFrom) {
     315           10 :                 continue;
     316              :             }
     317              : 
     318         1614 :             for (const Segment* segment = measure->first(); segment; segment = segment->next()) {
     319         1368 :                 const int segmentTick = segment->tick().ticks();
     320         1368 :                 if (segmentTick > tickTo) {
     321            5 :                     break;
     322              :                 }
     323              : 
     324         1363 :                 if (segmentTick < tickFrom) {
     325           16 :                     continue;
     326              :                 }
     327              : 
     328         1347 :                 int segmentStartTick = segmentTick + tickPositionOffset;
     329              : 
     330         1347 :                 handleSegmentElements(repeatSegment, segment, segmentStartTick, trackFrom, trackTo, measureRepeats);
     331         1347 :                 handleSegmentAnnotations(segment, segmentStartTick, trackFrom, trackTo);
     332              :             }
     333              :         }
     334              : 
     335           69 :         handleMeasureRepeats(measureRepeats, tickPositionOffset);
     336           69 :     }
     337           47 : }
     338              : 
     339           11 : void PlaybackContext::clear(const track_idx_t trackFrom, const track_idx_t trackTo, const int tickFrom, const int tickTo)
     340              : {
     341           55 :     const auto eraseTickRangeByTrack = [trackFrom, trackTo, tickFrom, tickTo](auto& byTrackMap) {
     342           59 :         for (auto it = byTrackMap.lower_bound(trackFrom); it != byTrackMap.end() && it->first < trackTo;) {
     343            4 :             auto& innerMap = it->second;
     344            4 :             innerMap.erase(innerMap.lower_bound(tickFrom), innerMap.upper_bound(tickTo));
     345              : 
     346            4 :             if (innerMap.empty()) {
     347            4 :                 it = byTrackMap.erase(it);
     348              :             } else {
     349            0 :                 ++it;
     350              :             }
     351              :         }
     352           66 :     };
     353              : 
     354           11 :     eraseTickRangeByTrack(m_soundPresetsByTrack);
     355           11 :     eraseTickRangeByTrack(m_textArticulationsByTrack);
     356           11 :     eraseTickRangeByTrack(m_syllablesByTrack);
     357           11 :     eraseTickRangeByTrack(m_playTechniquesByTrack);
     358           11 :     eraseTickRangeByTrack(m_multiVerseLyricsPositionMap);
     359              : 
     360           11 :     muse::remove_if(m_currentVerseNumByChordRest, [trackFrom, trackTo, tickFrom, tickTo](const auto& pair) {
     361            0 :         const track_idx_t track = pair.first->track();
     362            0 :         const int tick = pair.first->tick().ticks();
     363            0 :         return track >= trackFrom && track < trackTo && tick >= tickFrom && tick <= tickTo;
     364              :     });
     365           11 : }
     366              : 
     367            0 : bool PlaybackContext::hasSoundFlags(const track_idx_t trackFrom, const track_idx_t trackTo) const
     368              : {
     369            0 :     auto hasEntryInRange = [trackFrom, trackTo](const auto& byTrackMap) {
     370            0 :         auto it = byTrackMap.lower_bound(trackFrom);
     371            0 :         return it != byTrackMap.end() && it->first < trackTo;
     372            0 :     };
     373              : 
     374            0 :     return hasEntryInRange(m_soundPresetsByTrack) || hasEntryInRange(m_textArticulationsByTrack);
     375              : }
     376              : 
     377           36 : void PlaybackContext::updatePlayTechMap(const Part* part, const PlayTechAnnotation* annotation, const int segmentPositionTick)
     378              : {
     379           36 :     if (!annotation->playPlayTechAnnotation()) {
     380            0 :         return;
     381              :     }
     382           36 :     const PlayingTechniqueType type = annotation->techniqueType();
     383           36 :     if (type == PlayingTechniqueType::Undefined) {
     384            0 :         return;
     385              :     }
     386              : 
     387           34 :     const bool cancelPlayTechniques = (type == PlayingTechniqueType::Natural || type == PlayingTechniqueType::Open)
     388           70 :                                       && !m_textArticulationsByTrack.empty();
     389              : 
     390           36 :     TextArticulationEvent textArticulation;
     391           36 :     textArticulation.text = mpe::ORDINARY_PLAYING_TECHNIQUE_CODE;
     392              : 
     393           36 :     const TrackRange trackRange = part->trackRange();
     394          184 :     for (track_idx_t idx = trackRange.startTrack; idx < trackRange.endTrack; ++idx) {
     395          148 :         m_playTechniquesByTrack[idx][segmentPositionTick] = type;
     396              : 
     397          148 :         if (cancelPlayTechniques) {
     398            8 :             textArticulation.layerIdx = static_cast<layer_idx_t>(idx);
     399            8 :             m_textArticulationsByTrack[idx][segmentPositionTick] = textArticulation;
     400              :         }
     401              :     }
     402           36 : }
     403              : 
     404            8 : void PlaybackContext::updateSoundPresetAndTextArticulationMap(const Part* part, const SoundFlagMap& flagsOnSegment,
     405              :                                                               const int segmentPositionTick)
     406              : {
     407           52 :     auto trackAccepted = [&flagsOnSegment](const SoundFlag* flag, track_idx_t trackIdx) {
     408           52 :         staff_idx_t staffIdx = track2staff(trackIdx);
     409              : 
     410           52 :         if (flag->staffIdx() == staffIdx) {
     411           36 :             return true;
     412              :         }
     413              : 
     414           16 :         if (flag->applyToAllStaves()) {
     415            8 :             return !muse::contains(flagsOnSegment, staffIdx);
     416              :         }
     417              : 
     418            8 :         return false;
     419            8 :     };
     420              : 
     421            8 :     const TrackRange trackRange = part->trackRange();
     422              : 
     423           17 :     for (const auto& pair : flagsOnSegment) {
     424            9 :         const SoundFlag* flag = pair.second;
     425              : 
     426           61 :         for (track_idx_t trackIdx = trackRange.startTrack; trackIdx < trackRange.endTrack; ++trackIdx) {
     427           52 :             if (!trackAccepted(flag, trackIdx)) {
     428           12 :                 continue;
     429              :             }
     430              : 
     431           40 :             SoundPresetChangeEventList presets;
     432              : 
     433           40 :             for (const String& soundPreset : flag->soundPresets()) {
     434            0 :                 if (soundPreset.empty()) {
     435            0 :                     continue;
     436              :                 }
     437              : 
     438            0 :                 SoundPresetChangeEvent event;
     439            0 :                 event.code = soundPreset;
     440            0 :                 event.layerIdx = static_cast<layer_idx_t>(trackIdx);
     441            0 :                 presets.emplace_back(std::move(event));
     442            0 :             }
     443              : 
     444           40 :             if (!presets.empty()) {
     445            0 :                 SoundPresetChangeEventList& target = m_soundPresetsByTrack[trackIdx][segmentPositionTick];
     446            0 :                 target.insert(target.end(), std::make_move_iterator(presets.begin()), std::make_move_iterator(presets.end()));
     447              :             }
     448              : 
     449           40 :             if (!flag->playingTechnique().empty()) {
     450           40 :                 TextArticulationEvent event;
     451           40 :                 event.text = flag->playingTechnique();
     452           40 :                 event.layerIdx = static_cast<layer_idx_t>(trackIdx);
     453           40 :                 m_textArticulationsByTrack[trackIdx][segmentPositionTick] = std::move(event);
     454           40 :             }
     455           40 :         }
     456              : 
     457            9 :         if (flag->playingTechnique() == mpe::ORDINARY_PLAYING_TECHNIQUE_CODE) {
     458            5 :             for (track_idx_t trackIdx = trackRange.startTrack; trackIdx < trackRange.endTrack; ++trackIdx) {
     459            4 :                 m_playTechniquesByTrack[trackIdx][segmentPositionTick] = PlayingTechniqueType::Natural;
     460              :             }
     461              :         }
     462              :     }
     463            8 : }
     464              : 
     465           15 : void PlaybackContext::updateSyllableMap(const TextBase* text, const int segmentPositionTick)
     466              : {
     467           15 :     IF_ASSERT_FAILED(text->isLyrics() || text->isSticking()) {
     468            0 :         return;
     469              :     }
     470              : 
     471           15 :     if (text->empty()) {
     472            0 :         return;
     473              :     }
     474              : 
     475           15 :     SyllableEvent syllable;
     476           15 :     syllable.text = text->plainText();
     477              : 
     478           15 :     if (text->isLyrics()) {
     479           15 :         const Lyrics* lyrics = toLyrics(text);
     480              : 
     481           15 :         switch (lyrics->syllabic()) {
     482            2 :         case LyricsSyllabic::BEGIN:
     483              :         case LyricsSyllabic::MIDDLE:
     484            2 :             syllable.flags.setFlag(SyllableEvent::HyphenedToNext);
     485            2 :             break;
     486           13 :         case LyricsSyllabic::SINGLE:
     487              :         case LyricsSyllabic::END:
     488           13 :             break;
     489              :         }
     490              :     }
     491              : 
     492           15 :     const staff_idx_t staffIdx = text->staffIdx();
     493              : 
     494           75 :     for (voice_idx_t voiceIdx = 0; voiceIdx < VOICES; ++voiceIdx) {
     495           60 :         track_idx_t trackIdx = staff2track(staffIdx, voiceIdx);
     496           60 :         syllable.layerIdx = static_cast<layer_idx_t>(trackIdx);
     497           60 :         m_syllablesByTrack[trackIdx][segmentPositionTick] = syllable;
     498              :     }
     499           15 : }
     500              : 
     501         1347 : void PlaybackContext::handleSegmentAnnotations(const Segment* segment, const int segmentPositionTick,
     502              :                                                const track_idx_t trackFrom, const track_idx_t trackTo)
     503              : {
     504         1347 :     std::unordered_map<const Part*, SoundFlagMap> soundFlagsByPart;
     505              : 
     506         1469 :     for (const EngravingItem* annotation : segment->annotations()) {
     507          122 :         if (!annotation || !annotation->part()) {
     508           49 :             continue;
     509              :         }
     510              : 
     511          122 :         const Part* annotationPart = annotation->part();
     512          122 :         const TrackRange annotationPartTrackRange = annotationPart->trackRange();
     513          122 :         if (annotationPartTrackRange.startTrack >= trackTo || annotationPartTrackRange.endTrack <= trackFrom) {
     514           13 :             continue;
     515              :         }
     516              : 
     517          109 :         if (annotation->isPlayTechAnnotation()) {
     518           36 :             updatePlayTechMap(annotationPart, toPlayTechAnnotation(annotation), segmentPositionTick);
     519           36 :             continue;
     520              :         }
     521              : 
     522           73 :         if (annotation->isSticking()) {
     523            0 :             updateSyllableMap(toTextBase(annotation), segmentPositionTick);
     524            0 :             continue;
     525              :         }
     526              : 
     527           73 :         if (annotation->isStaffText()) {
     528           20 :             if (const SoundFlag* flag = toStaffText(annotation)->soundFlag()) {
     529            9 :                 if (soundFlagPlayable(flag)) {
     530            9 :                     soundFlagsByPart[annotationPart].emplace(flag->staffIdx(), flag);
     531              :                 }
     532              :             }
     533              :         }
     534              :     }
     535              : 
     536         1355 :     for (const auto& pair : soundFlagsByPart) {
     537            8 :         const Part* part = pair.first;
     538            8 :         const SoundFlagMap& flagsOnSegment = pair.second;
     539              : 
     540            8 :         if (!flagsOnSegment.empty()) {
     541            8 :             updateSoundPresetAndTextArticulationMap(part, flagsOnSegment, segmentPositionTick);
     542              :         }
     543              :     }
     544         1347 : }
     545              : 
     546         1347 : void PlaybackContext::handleSegmentElements(const RepeatSegment* repeat, const Segment* segment,
     547              :                                             const int segmentPositionTick,
     548              :                                             const track_idx_t trackFrom, const track_idx_t trackTo,
     549              :                                             std::vector<const MeasureRepeat*>& foundMeasureRepeats)
     550              : {
     551        10127 :     for (track_idx_t track = trackFrom; track < trackTo; ++track) {
     552         8780 :         const EngravingItem* item = segment->element(track);
     553         8780 :         if (!item) {
     554         6561 :             continue;
     555              :         }
     556              : 
     557         2219 :         if (item->isMeasureRepeat()) {
     558           12 :             foundMeasureRepeats.push_back(toMeasureRepeat(item));
     559           12 :             continue;
     560              :         }
     561              : 
     562         2207 :         if (item->isChordRest()) {
     563         1388 :             m_usedTracks.insert(track);
     564              : 
     565         1388 :             const ChordRest* chordRest = toChordRest(item);
     566         1388 :             if (chordRest->lyrics().empty()) {
     567         1370 :                 continue;
     568              :             }
     569              : 
     570           18 :             const Lyrics* lyrics = nullptr;
     571              : 
     572           18 :             auto verseNumIt = m_currentVerseNumByChordRest.find(chordRest);
     573           18 :             if (verseNumIt == m_currentVerseNumByChordRest.end()) {
     574            9 :                 m_currentVerseNumByChordRest[chordRest] = 0;
     575            9 :                 lyrics = chordRest->lyrics(0);
     576            9 :                 if (chordRest->lyrics().size() > 1) {
     577            4 :                     m_multiVerseLyricsPositionMap[track].insert(chordRest->tick().ticks());
     578              :                 }
     579            9 :             } else if (hasOnlyOneLyricsVerse(repeat, track)) {
     580            2 :                 lyrics = chordRest->lyrics(0);
     581              :             } else {
     582            7 :                 verseNumIt->second++;
     583            7 :                 lyrics = chordRest->lyrics(verseNumIt->second);
     584              :             }
     585              : 
     586           18 :             if (lyrics) {
     587           15 :                 updateSyllableMap(lyrics, segmentPositionTick);
     588              :             }
     589              :         }
     590              :     }
     591         1347 : }
     592              : 
     593              : template<typename ItemsMap>
     594           16 : static void copyItemsInRange(ItemsMap& source, const int rangeStartTick, const int rangeEndTick, const int newItemsOffsetTick)
     595              : {
     596           16 :     auto startIt = source.lower_bound(rangeStartTick);
     597           16 :     if (startIt == source.end()) {
     598            0 :         return;
     599              :     }
     600              : 
     601           16 :     auto endIt = source.lower_bound(rangeEndTick);
     602              : 
     603           16 :     ItemsMap newItems;
     604           32 :     for (auto it = startIt; it != endIt; ++it) {
     605           16 :         int tick = it->first + newItemsOffsetTick;
     606           16 :         newItems.insert_or_assign(tick, it->second);
     607              :     }
     608              : 
     609           16 :     source.merge(std::move(newItems));
     610           16 : }
     611              : 
     612              : template<typename ItemsMap>
     613           76 : static void copyItemsInRange(std::map<track_idx_t, ItemsMap>& source, const track_idx_t trackFrom, const track_idx_t trackTo,
     614              :                              const int rangeStartTick, const int rangeEndTick, const int newItemsOffsetTick)
     615              : {
     616           92 :     for (auto it = source.lower_bound(trackFrom); it != source.end() && it->first < trackTo; ++it) {
     617           16 :         copyItemsInRange(it->second, rangeStartTick, rangeEndTick, newItemsOffsetTick);
     618              :     }
     619           76 : }
     620              : 
     621           69 : void PlaybackContext::handleMeasureRepeats(const std::vector<const MeasureRepeat*>& measureRepeats, const int tickPositionOffset)
     622              : {
     623           81 :     for (const MeasureRepeat* mr : measureRepeats) {
     624           12 :         const Part* part = mr->part();
     625           12 :         if (!part) {
     626            0 :             continue;
     627              :         }
     628              : 
     629           12 :         const Measure* currMeasure = mr->firstMeasureOfGroup();
     630           12 :         if (!currMeasure) {
     631            0 :             continue;
     632              :         }
     633              : 
     634           12 :         const Measure* referringMeasure = mr->referringMeasure(currMeasure);
     635           12 :         if (!referringMeasure) {
     636            0 :             continue;
     637              :         }
     638              : 
     639           12 :         int currentMeasureTick = currMeasure->tick().ticks();
     640           12 :         int referringMeasureTick = referringMeasure->tick().ticks();
     641           12 :         int newItemsOffsetTick = currentMeasureTick - referringMeasureTick;
     642              : 
     643           12 :         const TrackRange trackRange = part->trackRange();
     644              : 
     645           24 :         for (int num = 0; num < mr->numMeasures(); ++num) {
     646           19 :             int startTick = referringMeasure->tick().ticks() + tickPositionOffset;
     647           19 :             int endTick = referringMeasure->endTick().ticks() + tickPositionOffset;
     648              : 
     649           19 :             copyItemsInRange(m_soundPresetsByTrack, trackRange.startTrack, trackRange.endTrack, startTick, endTick, newItemsOffsetTick);
     650           19 :             copyItemsInRange(m_textArticulationsByTrack, trackRange.startTrack, trackRange.endTrack, startTick, endTick,
     651              :                              newItemsOffsetTick);
     652           19 :             copyItemsInRange(m_syllablesByTrack, trackRange.startTrack, trackRange.endTrack, startTick, endTick, newItemsOffsetTick);
     653           19 :             copyItemsInRange(m_playTechniquesByTrack, trackRange.startTrack, trackRange.endTrack, startTick, endTick,
     654              :                              newItemsOffsetTick);
     655              : 
     656           19 :             currMeasure = currMeasure->nextMeasure();
     657           19 :             if (!currMeasure) {
     658            5 :                 break;
     659              :             }
     660              : 
     661           14 :             referringMeasure = mr->referringMeasure(currMeasure);
     662           14 :             if (!referringMeasure) {
     663            2 :                 break;
     664              :             }
     665              :         }
     666              :     }
     667           69 : }
     668              : 
     669         2185 : const mu::engraving::AutomationCurve* PlaybackContext::dynamicsCurve(const track_idx_t trackIdx) const
     670              : {
     671         2185 :     auto cacheIt = m_dynamicsCurveByTrack.find(trackIdx);
     672         2185 :     if (cacheIt != m_dynamicsCurveByTrack.end()) {
     673         1771 :         return cacheIt->second;
     674              :     }
     675              : 
     676          414 :     const AutomationDataConstPtr automation = m_score ? m_score->automationData() : nullptr;
     677          414 :     const Staff* staff = automation ? m_score->staff(track2staff(trackIdx)) : nullptr;
     678              : 
     679          414 :     const AutomationCurve* curve = nullptr;
     680          414 :     if (staff) {
     681          414 :         AutomationCurveKey key = AutomationCurveKey::staff(AutomationType::Dynamics, staff->id(), track2voice(trackIdx));
     682          414 :         curve = &automation->curve(key);
     683              : 
     684          414 :         if (curve->empty()) {
     685          412 :             key = key.withoutVoice();
     686          412 :             curve = &automation->curve(key);
     687              :         }
     688          414 :     }
     689              : 
     690          414 :     m_dynamicsCurveByTrack.emplace(trackIdx, curve);
     691          414 :     return curve;
     692          414 : }
     693              : 
     694            9 : bool PlaybackContext::hasOnlyOneLyricsVerse(const RepeatSegment* repeat, const track_idx_t track) const
     695              : {
     696            9 :     if (m_multiVerseLyricsPositionMap.empty()) {
     697            0 :         return true;
     698              :     }
     699              : 
     700            9 :     const auto trackIt = m_multiVerseLyricsPositionMap.find(track);
     701            9 :     if (trackIt == m_multiVerseLyricsPositionMap.cend()) {
     702            0 :         return true;
     703              :     }
     704              : 
     705            9 :     const int startTick = repeat->tick;
     706            9 :     const int endTick = repeat->endTick();
     707            9 :     const auto start = trackIt->second.lower_bound(startTick);
     708            9 :     const auto end = trackIt->second.lower_bound(endTick);
     709              : 
     710            9 :     return start == end;
     711              : }
        

Generated by: LCOV version 2.0-1