LCOV - code coverage report
Current view: top level - src/engraving/playback/renderers - gracechordcontext.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 96.7 % 60 58
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) 2024 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 "gracechordcontext.h"
      24              : 
      25              : #include "playback/utils/expressionutils.h"
      26              : 
      27              : using namespace mu::engraving;
      28              : using namespace muse;
      29              : using namespace muse::mpe;
      30              : 
      31           14 : static duration_t graceNotesTotalDuration(const std::vector<Chord*>& graceChords, const BeatsPerSecond& bps)
      32              : {
      33           14 :     duration_t result = 0;
      34              : 
      35           33 :     for (const Chord* graceChord : graceChords) {
      36           19 :         result += durationFromTempoAndTicks(bps.val, graceChord->durationTypeTicks().ticks());
      37              :     }
      38              : 
      39           14 :     return result;
      40              : }
      41              : 
      42           14 : static duration_t graceNotesMaxAvailableDuration(const ArticulationType type, const RenderingContext& ctx,
      43              :                                                  const size_t graceNotesCount)
      44              : {
      45           14 :     const duration_t halvedDuration = 0.5 * ctx.nominalDuration;
      46           14 :     const duration_t twoThirdsDuration = (2 * ctx.nominalDuration) / 3;
      47              : 
      48           14 :     if ((type == ArticulationType::PostAppoggiatura
      49            8 :          || (type == ArticulationType::PreAppoggiatura && graceNotesCount == 1))) {
      50           11 :         if (ctx.timeSignatureFraction.isCompound() && ctx.nominalDurationTicks > QUAVER_TICKS) {
      51            0 :             return twoThirdsDuration;
      52              :         } else {
      53           11 :             return halvedDuration;
      54              :         }
      55              :     }
      56              : 
      57            3 :     const duration_t minAcciacaturaDuration = durationFromTempoAndTicks(ctx.beatsPerSecond.val, DEMISEMIQUAVER_TICKS / 2);
      58            3 :     return std::min(minAcciacaturaDuration * static_cast<duration_t>(graceNotesCount), halvedDuration);
      59              : }
      60              : 
      61           14 : static timestamp_t graceNotesStartTimestamp(const mpe::ArticulationType type, const mpe::duration_t availableDuration,
      62              :                                             const mpe::timestamp_t nominalTimestamp)
      63              : {
      64           14 :     if (type == ArticulationType::PostAppoggiatura) {
      65            6 :         return nominalTimestamp - availableDuration;
      66              :     }
      67              : 
      68            8 :     return nominalTimestamp;
      69              : }
      70              : 
      71           14 : static timestamp_t principalNotesStartTimestamp(const mpe::ArticulationType type, const mpe::duration_t graceNotesDuration,
      72              :                                                 const mpe::timestamp_t nominalTimestamp)
      73              : {
      74           14 :     if (type == ArticulationType::PreAppoggiatura
      75            9 :         || type == ArticulationType::Acciaccatura) {
      76            8 :         return nominalTimestamp + graceNotesDuration;
      77              :     }
      78              : 
      79            6 :     return nominalTimestamp;
      80              : }
      81              : 
      82           33 : static RenderingContext buildGraceRenderingCtx(const RenderingContext& baseCtx,
      83              :                                                const timestamp_t timestamp, const duration_t duration)
      84              : {
      85           33 :     RenderingContext result(baseCtx);
      86           33 :     result.nominalTimestamp = timestamp;
      87           33 :     result.nominalDuration = duration;
      88           33 :     result.nominalPositionStartTick = timestampToTick(result.score, timestamp) - result.positionTickOffset;
      89           33 :     result.nominalPositionEndTick = timestampToTick(result.score, timestamp + duration) - result.positionTickOffset;
      90           33 :     result.nominalDurationTicks = result.nominalPositionEndTick - result.nominalPositionStartTick;
      91              : 
      92           33 :     return result;
      93            0 : }
      94              : 
      95           14 : GraceChordCtx GraceChordCtx::buildCtx(const Chord* chord, const mpe::ArticulationType type, const RenderingContext& ctx)
      96              : {
      97           14 :     std::vector<Chord*> graceChords;
      98              : 
      99           14 :     const bool isPlacedBeforePrincipal = isGraceNotePlacedBeforePrincipalNote(type);
     100           14 :     if (isPlacedBeforePrincipal) {
     101            8 :         graceChords = chord->graceNotesBefore(true /*filterUnplayable*/);
     102              :     } else {
     103            6 :         graceChords = chord->graceNotesAfter(true /*filterUnplayable*/);
     104              :     }
     105              : 
     106           14 :     const duration_t availableGraceNotesDuration = graceNotesMaxAvailableDuration(type, ctx, graceChords.size());
     107           14 :     const duration_t accumulatedGraceNotesDuration = graceNotesTotalDuration(graceChords, ctx.beatsPerSecond);
     108           14 :     const duration_t actualGraceNotesDuration = std::min(availableGraceNotesDuration, accumulatedGraceNotesDuration);
     109           14 :     const timestamp_t principalChordTimestamp = principalNotesStartTimestamp(type, actualGraceNotesDuration, ctx.nominalTimestamp);
     110           14 :     const duration_t principalChordDuration = ctx.nominalDuration - actualGraceNotesDuration;
     111              : 
     112           14 :     GraceChordCtx result { buildGraceRenderingCtx(ctx, principalChordTimestamp, principalChordDuration), {} };
     113           14 :     result.principalChordCtx.commonArticulations.erase(type);
     114              : 
     115           14 :     const double graceNotesDurationFactor = double(actualGraceNotesDuration) / accumulatedGraceNotesDuration;
     116           14 :     const timestamp_t graceNotesTimeOffset = isPlacedBeforePrincipal ? 0 : ctx.nominalDuration;
     117              : 
     118           14 :     timestamp_t graceChordTimestamp = graceNotesStartTimestamp(type, actualGraceNotesDuration,
     119           14 :                                                                ctx.nominalTimestamp + graceNotesTimeOffset);
     120              : 
     121           33 :     for (const Chord* graceChord : graceChords) {
     122           19 :         const int durationTicks = graceChord->durationTypeTicks().ticks();
     123           19 :         const duration_t duration = muse::RealRound(
     124           19 :             graceNotesDurationFactor * durationFromTempoAndTicks(ctx.beatsPerSecond.val, durationTicks), 0);
     125              : 
     126           19 :         RenderingContext graceNoteCtx = buildGraceRenderingCtx(ctx, graceChordTimestamp, duration);
     127           19 :         result.graceChordCtxList.emplace_back(std::make_pair(graceChord, graceNoteCtx));
     128              : 
     129           19 :         graceChordTimestamp += duration;
     130           19 :     }
     131              : 
     132           14 :     return result;
     133           14 : }
        

Generated by: LCOV version 2.0-1