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

Generated by: LCOV version 2.0-1