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