LCOV - code coverage report
Current view: top level - src/engraving/playback/renderers - tremolorenderer.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 89.8 % 127 114
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 "tremolorenderer.h"
      24              : 
      25              : #include "dom/chord.h"
      26              : #include "dom/tremolosinglechord.h"
      27              : #include "dom/tremolotwochord.h"
      28              : 
      29              : #include "playback/metaparsers/notearticulationsparser.h"
      30              : #include "playback/utils/expressionutils.h"
      31              : #include "playback/utils/repeatutils.h"
      32              : 
      33              : #include "noterenderer.h"
      34              : 
      35              : using namespace mu::engraving;
      36              : using namespace muse;
      37              : using namespace muse::mpe;
      38              : 
      39           19 : static bool containEqualTremolo(const Chord* c1, const Chord* c2)
      40              : {
      41           19 :     if (c1->tremoloSingleChord() && c2->tremoloSingleChord()) {
      42           18 :         return c1->tremoloSingleChord()->tremoloType() == c2->tremoloSingleChord()->tremoloType();
      43              :     }
      44              : 
      45            1 :     if (c1->tremoloTwoChord() && c2->tremoloTwoChord()) {
      46            0 :         return c1->tremoloTwoChord()->tremoloType() == c2->tremoloTwoChord()->tremoloType();
      47              :     }
      48              : 
      49            1 :     return false;
      50              : }
      51              : 
      52           35 : static timestamp_t tremoloTimestampFrom(const Note* note, const RenderingContext& ctx)
      53              : {
      54           35 :     const Note* originalNote = note;
      55           70 :     std::unordered_set<const Note*> notes { note };
      56           35 :     int positionTickOffset = ctx.positionTickOffset;
      57              : 
      58           35 :     if (note->incomingPartialTie()) {
      59            2 :         const PartiallyTiedNoteInfo outgoingNoteInfo = findOutgoingNoteInPreviousRepeat(note, ctx);
      60            2 :         if (outgoingNoteInfo.isValid() && containEqualTremolo(outgoingNoteInfo.note->chord(), note->chord())) {
      61            1 :             note = outgoingNoteInfo.note;
      62            1 :             positionTickOffset = outgoingNoteInfo.repeat->utick - outgoingNoteInfo.repeat->tick;
      63              :         }
      64              :     }
      65              : 
      66           43 :     while (note && note->tieBackNonPartial() && note->tieBackNonPartial()->playSpanner()) {
      67            9 :         const Note* prevNote = note->tieBackNonPartial()->startNote();
      68            9 :         const Chord* prevChord = prevNote ? prevNote->chord() : nullptr;
      69              : 
      70            9 :         if (!prevChord || !containEqualTremolo(prevChord, note->chord())) {
      71            1 :             break;
      72              :         }
      73              : 
      74            8 :         if (muse::contains(notes, prevNote)) {
      75            0 :             break; // prevents infinite loop
      76              :         }
      77              : 
      78            8 :         note = prevNote;
      79            8 :         notes.insert(note);
      80              :     }
      81              : 
      82           35 :     if (note && originalNote != note) {
      83            6 :         return timestampFromTicks(ctx.score, note->tick().ticks() + positionTickOffset);
      84              :     }
      85              : 
      86           29 :     return ctx.nominalTimestamp;
      87           35 : }
      88              : 
      89           35 : static duration_t tremoloDuration(const Note* note, const timestamp_t tremoloTimestampFrom, const RenderingContext& ctx)
      90              : {
      91           70 :     std::unordered_set<const Note*> notes { note };
      92           35 :     int positionTickOffset = ctx.positionTickOffset;
      93              : 
      94           35 :     if (note->outgoingPartialTie()) {
      95            2 :         const PartiallyTiedNoteInfo incomingNoteInfo = findIncomingNoteInNextRepeat(note, ctx);
      96            2 :         if (incomingNoteInfo.isValid() && containEqualTremolo(incomingNoteInfo.note->chord(), note->chord())) {
      97            1 :             note = incomingNoteInfo.note;
      98            1 :             positionTickOffset = incomingNoteInfo.repeat->utick - incomingNoteInfo.repeat->tick;
      99              :         }
     100              :     }
     101              : 
     102           43 :     while (note && note->tieForNonPartial() && note->tieForNonPartial()->playSpanner()) {
     103            8 :         const Note* nextNote = note->tieForNonPartial()->endNote();
     104            8 :         const Chord* nextChord = nextNote ? nextNote->chord() : nullptr;
     105              : 
     106            8 :         if (!nextChord || !containEqualTremolo(nextChord, note->chord())) {
     107            0 :             break;
     108              :         }
     109              : 
     110            8 :         if (muse::contains(notes, nextNote)) {
     111            0 :             break; // prevents infinite loop
     112              :         }
     113              : 
     114            8 :         note = nextNote;
     115            8 :         notes.insert(note);
     116              :     }
     117              : 
     118           35 :     if (note) {
     119           35 :         const int tickTo = note->chord()->endTick().ticks();
     120           35 :         const timestamp_t tremoloTimestampTo = timestampFromTicks(ctx.score, tickTo + positionTickOffset);
     121           35 :         return tremoloTimestampTo - tremoloTimestampFrom;
     122              :     }
     123              : 
     124            0 :     return ctx.nominalDuration;
     125           35 : }
     126              : 
     127           60 : const ArticulationTypeSet& TremoloRenderer::supportedTypes()
     128              : {
     129              :     static const mpe::ArticulationTypeSet types = {
     130              :         mpe::ArticulationType::Tremolo8th, mpe::ArticulationType::Tremolo16th,
     131              :         mpe::ArticulationType::Tremolo32nd, mpe::ArticulationType::Tremolo64th,
     132              :         mpe::ArticulationType::TremoloBuzz,
     133           62 :     };
     134              : 
     135           60 :     return types;
     136              : }
     137              : 
     138           28 : void TremoloRenderer::doRender(const EngravingItem* item, const mpe::ArticulationType preferredType,
     139              :                                const RenderingContext& ctx,
     140              :                                mpe::PlaybackEventList& result)
     141              : {
     142           28 :     const Chord* chord = item_cast<const Chord*>(item);
     143           28 :     IF_ASSERT_FAILED(chord) {
     144            1 :         return;
     145              :     }
     146              : 
     147              :     struct TremoloAdapter {
     148              :         const TremoloSingleChord* single = nullptr;
     149              :         const TremoloTwoChord* two = nullptr;
     150              : 
     151           28 :         TremoloAdapter(const Chord* ch)
     152           28 :             : single(ch->tremoloSingleChord()), two(ch->tremoloTwoChord()) {}
     153              : 
     154           28 :         bool hasTremolo() const { return single != nullptr || two != nullptr; }
     155              : 
     156           28 :         int lines() const { return single ? single->lines() : (two ? two->lines() : 0); }
     157              :     };
     158              : 
     159           28 :     TremoloAdapter tremolo = TremoloAdapter(chord);
     160           28 :     IF_ASSERT_FAILED(tremolo.hasTremolo()) {
     161            0 :         return;
     162              :     }
     163              : 
     164              :     // TODO: We need a member like articulationData.overallDurationTicks (ticks rather than duration),
     165              :     // so that we are not duplicating this calculation (see TremoloTwoMetaParser::doParse)
     166              :     //const ArticulationAppliedData& articulationData = context.commonArticulations.at(preferredType);
     167           28 :     int overallDurationTicks = ctx.nominalDurationTicks;
     168           28 :     if (tremolo.two && tremolo.two->chord1() && tremolo.two->chord2()) {
     169            1 :         overallDurationTicks = tremolo.two->chord1()->actualTicks().ticks() + tremolo.two->chord2()->actualTicks().ticks();
     170              :     }
     171              : 
     172           28 :     int stepDurationTicks = 0;
     173           28 :     if (preferredType == ArticulationType::TremoloBuzz) {
     174            0 :         stepDurationTicks = overallDurationTicks;
     175              :     } else {
     176           28 :         stepDurationTicks = TremoloRenderer::stepDurationTicks(chord, tremolo.lines());
     177              :     }
     178              : 
     179           28 :     if (stepDurationTicks <= 0) {
     180            0 :         LOGE() << "Unable to render unsupported tremolo type";
     181            0 :         return;
     182              :     }
     183              : 
     184              :     // ... and use that here
     185           28 :     const int stepsCount = std::round(overallDurationTicks / (float)stepDurationTicks);
     186           28 :     if (stepsCount == 0) {
     187            0 :         return;
     188              :     }
     189              : 
     190           28 :     stepDurationTicks = overallDurationTicks / stepsCount;
     191              : 
     192           28 :     TremoloTimeCache tremoloTimeCache;
     193              : 
     194           28 :     if (tremolo.two) {
     195            1 :         const Chord* firstTremoloChord = tremolo.two->chord1();
     196            1 :         const Chord* secondTremoloChord = tremolo.two->chord2();
     197              : 
     198            1 :         IF_ASSERT_FAILED(firstTremoloChord && secondTremoloChord) {
     199            0 :             return;
     200              :         }
     201              : 
     202           17 :         for (int i = 0; i < stepsCount; ++i) {
     203           16 :             const Chord* currentChord = firstTremoloChord;
     204              : 
     205           16 :             if (i % 2 != 0) {
     206            8 :                 currentChord = secondTremoloChord;
     207              :             }
     208              : 
     209           16 :             buildAndAppendEvents(currentChord, preferredType, stepDurationTicks, ctx.nominalPositionStartTick + i * stepDurationTicks,
     210              :                                  ctx, tremoloTimeCache, result);
     211              :         }
     212              : 
     213            1 :         return;
     214              :     }
     215              : 
     216          371 :     for (int i = 0; i < stepsCount; ++i) {
     217          344 :         buildAndAppendEvents(chord, preferredType, stepDurationTicks, ctx.nominalPositionStartTick + i * stepDurationTicks,
     218              :                              ctx, tremoloTimeCache, result);
     219              :     }
     220           28 : }
     221              : 
     222           28 : int TremoloRenderer::stepDurationTicks(const Chord* chord, int tremoloLines)
     223              : {
     224           28 :     int ticks = Constants::DIVISION / (1 << (chord->beams() + tremoloLines));
     225           28 :     if (ticks <= 0) {
     226            0 :         return 1;
     227              :     }
     228           28 :     return ticks * chord->timeStretchFactor();
     229              : }
     230              : 
     231          360 : void TremoloRenderer::buildAndAppendEvents(const Chord* chord, const ArticulationType type,
     232              :                                            const int stepDurationTicks,
     233              :                                            const int startTick, const RenderingContext& ctx,
     234              :                                            TremoloTimeCache& tremoloCache,
     235              :                                            mpe::PlaybackEventList& result)
     236              : {
     237          760 :     for (const Note* note : chord->notes()) {
     238          400 :         if (!NoteRenderer::shouldRender(note, ctx, ctx.commonArticulations)) {
     239            0 :             continue;
     240              :         }
     241              : 
     242          400 :         auto noteTnD = timestampAndDurationFromStartAndDurationTicks(
     243          400 :             ctx.score, startTick, stepDurationTicks, ctx.positionTickOffset);
     244              : 
     245          400 :         NominalNoteCtx noteCtx(note, ctx);
     246          400 :         noteCtx.duration = noteTnD.duration;
     247          400 :         noteCtx.timestamp = noteTnD.timestamp;
     248              : 
     249          400 :         int utick = timestampToTick(ctx.score, noteCtx.timestamp);
     250          400 :         noteCtx.dynamicLevel = ctx.playbackCtx->appliableDynamicLevel(note->track(), utick);
     251              : 
     252          400 :         NoteArticulationsParser::buildNoteArticulationMap(note, ctx, noteCtx.articulations);
     253              : 
     254          400 :         const TimestampAndDuration& tremoloTnD = tremoloTimeAndDuration(note, ctx, tremoloCache);
     255          400 :         muse::mpe::ArticulationAppliedData& articulationData = noteCtx.articulations.at(type);
     256          400 :         articulationData.meta.timestamp = tremoloTnD.timestamp;
     257          400 :         articulationData.meta.overallDuration = tremoloTnD.duration;
     258              : 
     259          400 :         updateArticulationBoundaries(type, noteCtx.timestamp, noteCtx.duration, noteCtx.articulations);
     260              : 
     261          400 :         result.emplace_back(buildNoteEvent(noteCtx));
     262          400 :     }
     263          760 : }
     264              : 
     265          400 : const TimestampAndDuration& TremoloRenderer::tremoloTimeAndDuration(const Note* note, const RenderingContext& ctx,
     266              :                                                                     TremoloTimeCache& cache)
     267              : {
     268          400 :     auto cacheIt = cache.find(note);
     269          400 :     if (cacheIt != cache.end()) {
     270          365 :         return cacheIt->second;
     271              :     }
     272              : 
     273           35 :     TimestampAndDuration& tnd = cache[note];
     274           35 :     tnd.timestamp = tremoloTimestampFrom(note, ctx);
     275           35 :     tnd.duration = tremoloDuration(note, tnd.timestamp, ctx);
     276              : 
     277           35 :     return tnd;
     278              : }
        

Generated by: LCOV version 2.0-1