LCOV - code coverage report
Current view: top level - src/engraving/playback/metaparsers - chordarticulationsparser.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 76.1 % 117 89
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 "chordarticulationsparser.h"
      24              : 
      25              : #include "dom/arpeggio.h"
      26              : #include "dom/breath.h"
      27              : #include "dom/chord.h"
      28              : #include "dom/chordline.h"
      29              : #include "dom/score.h"
      30              : #include "dom/segment.h"
      31              : #include "dom/spanner.h"
      32              : #include "dom/tremolosinglechord.h"
      33              : #include "dom/tremolotwochord.h"
      34              : #include "dom/tapping.h"
      35              : 
      36              : #include "playback/utils/arrangementutils.h"
      37              : #include "playback/filters/spannerfilter.h"
      38              : 
      39              : #include "internal/spannersmetaparser.h"
      40              : #include "internal/symbolsmetaparser.h"
      41              : #include "internal/annotationsmetaparser.h"
      42              : #include "internal/tremolometaparser.h"
      43              : #include "internal/arpeggiometaparser.h"
      44              : #include "internal/gracenotesmetaparser.h"
      45              : #include "internal/chordlinemetaparser.h"
      46              : 
      47              : using namespace mu::engraving;
      48              : using namespace muse;
      49              : using namespace muse::mpe;
      50              : 
      51         1139 : void ChordArticulationsParser::buildChordArticulationMap(const Chord* chord, const RenderingContext& ctx, mpe::ArticulationMap& result)
      52              : {
      53         1139 :     if (!chord || !ctx.isValid()) {
      54            0 :         LOGE() << "Unable to render playback events of invalid chord";
      55            0 :         return;
      56              :     }
      57              : 
      58         1139 :     parse(chord, ctx, result);
      59              : 
      60         1226 :     for (const auto& pair : result) {
      61           87 :         if (isSingleNoteArticulation(pair.first)) {
      62           52 :             continue;
      63              :         }
      64              : 
      65           35 :         duration_percentage_t occupiedFrom = occupiedPercentage(ctx.nominalTimestamp - pair.second.meta.timestamp,
      66           35 :                                                                 pair.second.meta.overallDuration);
      67           35 :         duration_percentage_t occupiedTo = occupiedPercentage(ctx.nominalTimestamp + ctx.nominalDuration - pair.second.meta.timestamp,
      68           35 :                                                               pair.second.meta.overallDuration);
      69              : 
      70           35 :         result.updateOccupiedRange(pair.first, occupiedFrom, occupiedTo);
      71              :     }
      72              : 
      73         1139 :     result.preCalculateAverageData();
      74              : }
      75              : 
      76         1139 : void ChordArticulationsParser::doParse(const EngravingItem* item, const RenderingContext& ctx, mpe::ArticulationMap& result)
      77              : {
      78         1139 :     IF_ASSERT_FAILED(item->isChord()) {
      79            0 :         return;
      80              :     }
      81              : 
      82         1139 :     const Chord* chord = toChord(item);
      83              : 
      84         1139 :     parseSpanners(chord, ctx, result);
      85         1139 :     parseAnnotations(chord, ctx, result);
      86         1139 :     parseTremolo(chord, ctx, result);
      87         1139 :     parseArpeggio(chord, ctx, result);
      88         1139 :     parseGraceNotes(chord, ctx, result);
      89         1139 :     parseChordLine(chord, ctx, result);
      90         1139 :     parseArticulationSymbols(chord, ctx, result);
      91         1139 :     parseTapping(chord, ctx, result);
      92         1139 :     parseBreath(chord, ctx, result);
      93              : }
      94              : 
      95         1139 : void ChordArticulationsParser::parseSpanners(const Chord* chord, const RenderingContext& ctx, mpe::ArticulationMap& result)
      96              : {
      97         1139 :     const SpannerMap& spannerMap = ctx.score->spannerMap();
      98         1139 :     if (spannerMap.empty()) {
      99         1048 :         return;
     100              :     }
     101              : 
     102          182 :     auto intervals = spannerMap.findOverlapping(ctx.nominalPositionStartTick,
     103           91 :                                                 ctx.nominalPositionEndTick,
     104           91 :                                                 /*excludeCollisions*/ true);
     105              : 
     106          158 :     for (const auto& interval : intervals) {
     107           67 :         const Spanner* spanner = interval.value;
     108              : 
     109           67 :         if (!SpannersMetaParser::isAbleToParse(spanner)) {
     110           48 :             continue;
     111              :         }
     112              : 
     113           28 :         if (SpannerFilter::isMultiStaffSpanner(spanner)) {
     114           17 :             if (spanner->part() != chord->part()) {
     115            0 :                 continue;
     116              :             }
     117              :         } else {
     118           11 :             if (spanner->staffIdx() != chord->staffIdx()) {
     119            0 :                 continue;
     120              :             }
     121              :         }
     122              : 
     123           28 :         if (!SpannerFilter::isItemPlayable(spanner, ctx)) {
     124            9 :             continue;
     125              :         }
     126              : 
     127           19 :         RenderingContext spannerContext = ctx;
     128           19 :         spannerContext.nominalTimestamp = timestampFromTicks(ctx.score, interval.start + ctx.positionTickOffset);
     129           19 :         spannerContext.nominalPositionStartTick = interval.start;
     130           19 :         spannerContext.nominalDurationTicks = SpannerFilter::spannerActualDurationTicks(spanner, interval.stop - interval.start);
     131           19 :         spannerContext.nominalPositionEndTick = spannerContext.nominalPositionStartTick + spannerContext.nominalDurationTicks;
     132              : 
     133           19 :         SpannersMetaParser::parse(spanner, spannerContext, result);
     134           19 :     }
     135           91 : }
     136              : 
     137         1139 : void ChordArticulationsParser::parseArticulationSymbols(const Chord* chord, const RenderingContext& ctx, mpe::ArticulationMap& result)
     138              : {
     139         1153 :     for (const Articulation* articulation : chord->articulations()) {
     140           14 :         SymbolsMetaParser::parse(articulation, ctx, result);
     141              :     }
     142         1139 : }
     143              : 
     144         1139 : void ChordArticulationsParser::parseAnnotations(const Chord* chord, const RenderingContext& ctx, mpe::ArticulationMap& result)
     145              : {
     146         1185 :     for (const EngravingItem* annotation : chord->segment()->annotations()) {
     147           46 :         if (annotation->staffIdx() != chord->staffIdx()) {
     148            4 :             continue;
     149              :         }
     150              : 
     151           42 :         AnnotationsMetaParser::parse(annotation, ctx, result);
     152              :     }
     153         1139 : }
     154              : 
     155         1139 : void ChordArticulationsParser::parseTremolo(const Chord* chord, const RenderingContext& ctx, mpe::ArticulationMap& result)
     156              : {
     157              :     // single chord
     158              :     {
     159         1139 :         const TremoloSingleChord* tremoloSingle = chord->tremoloSingleChord();
     160         1139 :         if (tremoloSingle && tremoloSingle->playTremolo()) {
     161           28 :             TremoloSingleMetaParser::parse(tremoloSingle, ctx, result);
     162              :         }
     163              :     }
     164              : 
     165              :     // two chord
     166              :     {
     167         1139 :         const TremoloTwoChord* tremoloTwo = chord->tremoloTwoChord();
     168         1139 :         if (tremoloTwo && tremoloTwo->playTremolo()) {
     169            1 :             TremoloTwoMetaParser::parse(tremoloTwo, ctx, result);
     170              :         }
     171              :     }
     172         1139 : }
     173              : 
     174         1139 : void ChordArticulationsParser::parseArpeggio(const Chord* chord, const RenderingContext& ctx, mpe::ArticulationMap& result)
     175              : {
     176         1139 :     const Arpeggio* arpeggio = chord->arpeggio();
     177         1139 :     if (!arpeggio || arpeggio->isChordBracket()) {
     178         1126 :         return;
     179              :     }
     180              : 
     181           13 :     if (chord->notes().empty()) {
     182            0 :         return;
     183              :     }
     184              : 
     185           13 :     ArpeggioMetaParser::parse(arpeggio, ctx, result);
     186              : }
     187              : 
     188         1139 : void ChordArticulationsParser::parseGraceNotes(const Chord* chord, const RenderingContext& ctx, mpe::ArticulationMap& result)
     189              : {
     190         1159 :     for (const Chord* graceChord : chord->graceNotes()) {
     191           20 :         GraceNotesMetaParser::parse(graceChord, ctx, result);
     192              :     }
     193         1139 : }
     194              : 
     195         1139 : void ChordArticulationsParser::parseChordLine(const Chord* chord, const RenderingContext& ctx, mpe::ArticulationMap& result)
     196              : {
     197         1139 :     const ChordLine* chordLine = chord->chordLine();
     198         1139 :     if (!chordLine || !chordLine->playChordLine()) {
     199         1135 :         return;
     200              :     }
     201              : 
     202            4 :     ChordLineMetaParser::parse(chordLine, ctx, result);
     203              : }
     204              : 
     205         1139 : void ChordArticulationsParser::parseTapping(const Chord* chord, const RenderingContext& ctx, mpe::ArticulationMap& result)
     206              : {
     207         1139 :     const Tapping* tapping = chord->tapping();
     208         1139 :     if (!tapping || !tapping->playArticulation()) {
     209         1139 :         return;
     210              :     }
     211              : 
     212            0 :     mpe::ArticulationType type = mpe::ArticulationType::Undefined;
     213              : 
     214            0 :     switch (tapping->hand()) {
     215            0 :     case TappingHand::LEFT:
     216            0 :         type = mpe::ArticulationType::LeftHandTapping;
     217            0 :         break;
     218            0 :     case TappingHand::RIGHT:
     219            0 :         type = mpe::ArticulationType::RightHandTapping;
     220            0 :         break;
     221            0 :     case TappingHand::INVALID:
     222            0 :         break;
     223              :     }
     224              : 
     225            0 :     if (type == mpe::ArticulationType::Undefined) {
     226            0 :         return;
     227              :     }
     228              : 
     229            0 :     const mpe::ArticulationPattern& pattern = ctx.profile->pattern(type);
     230            0 :     if (pattern.empty()) {
     231            0 :         return;
     232              :     }
     233              : 
     234            0 :     appendArticulationData(mpe::ArticulationMeta(type, pattern, ctx.nominalTimestamp, ctx.nominalDuration), result);
     235              : }
     236              : 
     237         1139 : void ChordArticulationsParser::parseBreath(const Chord* chord, const RenderingContext& ctx, mpe::ArticulationMap& result)
     238              : {
     239         1139 :     const mpe::ArticulationPattern& pattern = ctx.profile->pattern(ArticulationType::Breath);
     240         1139 :     if (pattern.empty()) {
     241         1139 :         return;
     242              :     }
     243              : 
     244            0 :     const Breath* breath = chord->hasBreathMark();
     245            0 :     if (!breath) {
     246            0 :         return;
     247              :     }
     248              : 
     249            0 :     const mpe::timestamp_t timestamp = timestampFromTicks(ctx.score, breath->tick().ticks() + ctx.positionTickOffset);
     250            0 :     const mpe::duration_t duration = breath->pause() * 1000000;
     251            0 :     appendArticulationData(mpe::ArticulationMeta(ArticulationType::Breath, pattern, timestamp, duration), result);
     252              : }
        

Generated by: LCOV version 2.0-1