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 : }
|