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 "bendsrenderer.h"
24 :
25 : #include "noterenderer.h"
26 : #include "gracechordcontext.h"
27 :
28 : #include "playback/metaparsers/chordarticulationsparser.h"
29 : #include "playback/utils/expressionutils.h"
30 :
31 : #include "dom/note.h"
32 : #include "dom/guitarbend.h"
33 :
34 : using namespace mu::engraving;
35 : using namespace muse;
36 :
37 : static constexpr bool IGNORE_UNPLAYABLE = false;
38 :
39 36 : static const Chord* principalChord(const Chord* chord)
40 : {
41 36 : if (chord->isGrace()) {
42 8 : const EngravingObject* parent = chord->ownershipParent();
43 8 : if (parent && parent->isChord()) {
44 8 : return toChord(parent);
45 : }
46 : }
47 :
48 28 : return chord;
49 : }
50 :
51 29 : static const GuitarBend* currentBend(const Note* currNote)
52 : {
53 29 : const GuitarBend* diveBack = currNote->diveBack();
54 29 : if (diveBack && diveBack->playSpanner() && diveBack->bendType() == GuitarBendType::SCOOP) {
55 1 : return diveBack;
56 : }
57 :
58 28 : return currNote->bendFor();
59 : }
60 :
61 15 : static const Note* nextNote(const Note* currNote, const GuitarBend* currBend)
62 : {
63 15 : return currBend && currBend->endNote() != currNote ? currBend->endNote() : nullptr;
64 : }
65 :
66 4 : static mpe::pitch_level_t pitchOffset(const GuitarBend* bend)
67 : {
68 4 : return mpe::PITCH_LEVEL_STEP / 2 * bend->bendAmountInQuarterTones();
69 : }
70 :
71 6 : bool BendsRenderer::isMultibendPart(const Note* note)
72 : {
73 6 : if (!note->tieFor() && !note->tieBack()) {
74 4 : return note->bendFor() || note->bendBack();
75 : }
76 :
77 2 : const Note* currNote = note->firstTiedNote(IGNORE_UNPLAYABLE);
78 2 : std::unordered_set<const Note*> visited;
79 :
80 3 : while (currNote && currNote->play()) {
81 3 : if (!visited.insert(currNote).second) {
82 0 : break;
83 : }
84 :
85 3 : if (currNote->bendFor() || note->bendBack()) {
86 2 : return true;
87 : }
88 :
89 1 : const Tie* tieFor = currNote->tieFor();
90 1 : if (!tieFor || !tieFor->playSpanner()) {
91 0 : return false;
92 : }
93 :
94 1 : currNote = tieFor->endNote();
95 : }
96 :
97 0 : return false;
98 2 : }
99 :
100 15 : void BendsRenderer::render(const Note* note, const RenderingContext& ctx, mpe::PlaybackEventList& result)
101 : {
102 15 : IF_ASSERT_FAILED(note) {
103 0 : return;
104 : }
105 :
106 : //! NOTE: ignore the grace note and render only the principal note
107 15 : if (note->isPreBendOrDiveStart()) {
108 1 : return;
109 : }
110 :
111 14 : if (const GuitarBend* bendBack = note->bendBack()) {
112 : static const std::unordered_set<GuitarBendType> SHOULD_RENDER_NOTE_WITH_BEND_BACK {
113 : GuitarBendType::PRE_BEND,
114 : GuitarBendType::PRE_DIVE,
115 : GuitarBendType::SCOOP,
116 8 : };
117 :
118 6 : if (!muse::contains(SHOULD_RENDER_NOTE_WITH_BEND_BACK, bendBack->bendType())) {
119 4 : return;
120 : }
121 : }
122 :
123 10 : if (!NoteRenderer::shouldRender(note, ctx, ctx.commonArticulations)) {
124 0 : return;
125 : }
126 :
127 10 : renderMultibend(note, ctx, result);
128 : }
129 :
130 10 : void BendsRenderer::renderMultibend(const Note* startNote, const RenderingContext& startNoteCtx,
131 : mpe::PlaybackEventList& result)
132 : {
133 10 : const Note* currNote = startNote;
134 10 : const GuitarBend* currBend = nullptr;
135 10 : std::unordered_set<const Note*> visited;
136 :
137 10 : mpe::PlaybackEventList bendEvents;
138 10 : BendTimeFactorMap bendTimeFactorMap;
139 :
140 30 : while (currNote) {
141 29 : if (!visited.insert(currNote).second) {
142 9 : break;
143 : }
144 :
145 29 : RenderingContext currNoteCtx = buildRenderingContext(currNote, startNoteCtx);
146 29 : if (!currNote->tieBack() || !currNote->tieBack()->playSpanner()) {
147 24 : renderNote(currNote, currNoteCtx, bendEvents);
148 : }
149 :
150 29 : currBend = currentBend(currNote);
151 :
152 : // No bend on this note, but it's tied to the next one — check that note as well
153 29 : if (!currBend && currNote->tieFor() && currNote->tieFor()->playSpanner()) {
154 3 : currNote = currNote->tieFor()->endNote();
155 3 : continue;
156 : }
157 :
158 26 : if (!currBend || !currBend->playSpanner() || bendEvents.empty()) {
159 8 : break;
160 : }
161 :
162 18 : if (currBend->bendType() == GuitarBendType::SLIGHT_BEND) {
163 1 : renderSlightBend(currNote, currBend, currNoteCtx, bendEvents);
164 1 : break;
165 : }
166 :
167 17 : if (currBend->bendType() == GuitarBendType::DIP) {
168 2 : renderDip(currNote, currBend, currNoteCtx, bendEvents);
169 :
170 2 : if (currNote->tieFor() && currNote->tieFor()->playSpanner()) {
171 2 : currNote = currNote->tieFor()->endNote();
172 2 : continue;
173 : }
174 0 : break;
175 : }
176 :
177 15 : if (currBend->bendType() == GuitarBendType::SCOOP) {
178 1 : renderScoop(currNote, currBend, currNoteCtx, bendEvents);
179 1 : currBend = currNote->bendFor();
180 1 : currNote = nextNote(currNote, currBend);
181 1 : continue;
182 : }
183 :
184 14 : const mpe::PlaybackEvent& newEvent = bendEvents.back();
185 14 : if (std::holds_alternative<mpe::NoteEvent>(newEvent)) {
186 14 : const mpe::ArrangementContext& arrangementCtx = std::get<mpe::NoteEvent>(newEvent).arrangementCtx();
187 14 : const mpe::timestamp_t timestampTo = arrangementCtx.actualTimestamp + arrangementCtx.actualDuration;
188 14 : bendTimeFactorMap.insert_or_assign(timestampTo, timeFactors(currBend));
189 : }
190 :
191 14 : currNote = nextNote(currNote, currBend);
192 29 : }
193 :
194 10 : if (!bendEvents.empty()) {
195 10 : mpe::NoteEvent event = buildBendEvent(startNote, startNoteCtx, bendEvents, bendTimeFactorMap);
196 10 : result.emplace_back(std::move(event));
197 10 : }
198 10 : }
199 :
200 24 : void BendsRenderer::renderNote(const Note* note, const RenderingContext& ctx, mpe::PlaybackEventList& result)
201 : {
202 24 : for (const auto& pair : ctx.commonArticulations) {
203 7 : if (!muse::contains(GRACE_NOTE_ARTICULATION_TYPES, pair.first)) {
204 0 : continue;
205 : }
206 :
207 : // This note is either a grace note or a principal note for other grace notes
208 7 : const GraceChordCtx graceCtx = GraceChordCtx::buildCtx(principalChord(note->chord()), pair.first, ctx);
209 :
210 7 : if (note->isGrace()) {
211 4 : renderGraceNote(note, graceCtx, result);
212 : } else {
213 3 : NoteRenderer::render(note, graceCtx.principalChordCtx, result);
214 : }
215 :
216 7 : return;
217 7 : }
218 :
219 17 : NoteRenderer::render(note, ctx, result);
220 : }
221 :
222 4 : void BendsRenderer::renderGraceNote(const Note* note, const GraceChordCtx& ctx, mpe::PlaybackEventList& result)
223 : {
224 5 : for (const auto& pair : ctx.graceChordCtxList) {
225 6 : for (const Note* graceNote : pair.first->notes()) {
226 5 : if (note == graceNote) {
227 4 : NoteRenderer::render(note, pair.second, result);
228 4 : return;
229 : }
230 : }
231 : }
232 : }
233 :
234 1 : void BendsRenderer::renderSlightBend(const Note* note, const GuitarBend* bend, const RenderingContext& ctx, mpe::PlaybackEventList& result)
235 : {
236 1 : NominalNoteCtx slightNoteCtx(note, ctx);
237 1 : slightNoteCtx.duration = 0; // aux notes have no duration
238 :
239 1 : const BendTimeFactors factors = timeFactors(bend);
240 :
241 : // Hold the origin pitch
242 1 : if (!RealIsNull(factors.startFactor)) {
243 0 : slightNoteCtx.timestamp = ctx.nominalTimestamp + ctx.nominalDuration * factors.startFactor;
244 0 : result.emplace_back(buildNoteEvent(slightNoteCtx));
245 : }
246 :
247 : // Go up
248 1 : slightNoteCtx.timestamp = ctx.nominalTimestamp + ctx.nominalDuration * factors.endFactor;
249 1 : slightNoteCtx.pitchLevel += pitchOffset(bend);
250 1 : result.emplace_back(buildNoteEvent(slightNoteCtx));
251 2 : }
252 :
253 2 : void BendsRenderer::renderDip(const Note* note, const GuitarBend* bend, const RenderingContext& ctx, mpe::PlaybackEventList& result)
254 : {
255 2 : NominalNoteCtx dipNoteCtx(note, ctx);
256 2 : dipNoteCtx.duration = 0; // aux notes have no duration
257 :
258 2 : const mpe::pitch_level_t originPitchLevel = dipNoteCtx.pitchLevel;
259 2 : const BendTimeFactors factors = timeFactors(bend);
260 :
261 : // Hold the origin pitch
262 2 : dipNoteCtx.timestamp = ctx.nominalTimestamp + ctx.nominalDuration * factors.startFactor;
263 2 : result.emplace_back(buildNoteEvent(dipNoteCtx));
264 :
265 : // Go down / up
266 2 : dipNoteCtx.pitchLevel += pitchOffset(bend);
267 2 : dipNoteCtx.timestamp = ctx.nominalTimestamp + ctx.nominalDuration * factors.targetFactor;
268 2 : result.emplace_back(buildNoteEvent(dipNoteCtx));
269 :
270 : // Go back to the origin pitch
271 2 : if (!RealIsEqual(factors.targetFactor, factors.endFactor)) {
272 2 : dipNoteCtx.timestamp = ctx.nominalTimestamp + ctx.nominalDuration * factors.endFactor;
273 2 : dipNoteCtx.pitchLevel = originPitchLevel;
274 2 : result.emplace_back(buildNoteEvent(dipNoteCtx));
275 : }
276 8 : }
277 :
278 1 : void BendsRenderer::renderScoop(const Note* note, const GuitarBend* bend, const RenderingContext& ctx, mpe::PlaybackEventList& result)
279 : {
280 1 : NominalNoteCtx scoopNoteCtx(note, ctx);
281 1 : scoopNoteCtx.duration = 0; // aux notes have no duration
282 :
283 1 : const mpe::pitch_level_t originPitchLevel = scoopNoteCtx.pitchLevel;
284 1 : const BendTimeFactors factors = timeFactors(bend);
285 :
286 : // Start with a pitch offset
287 1 : scoopNoteCtx.pitchLevel += pitchOffset(bend);
288 1 : result.emplace_back(buildNoteEvent(scoopNoteCtx));
289 :
290 : // Hold it
291 1 : if (!RealIsNull(factors.startFactor)) {
292 0 : scoopNoteCtx.timestamp = ctx.nominalTimestamp + ctx.nominalDuration * factors.startFactor;
293 0 : result.emplace_back(buildNoteEvent(scoopNoteCtx));
294 : }
295 :
296 : // Go back to the origin pitch
297 1 : scoopNoteCtx.timestamp = ctx.nominalTimestamp + ctx.nominalDuration * factors.endFactor;
298 1 : scoopNoteCtx.pitchLevel = originPitchLevel;
299 1 : result.emplace_back(buildNoteEvent(scoopNoteCtx));
300 3 : }
301 :
302 18 : BendsRenderer::BendTimeFactors BendsRenderer::timeFactors(const GuitarBend* bend)
303 : {
304 18 : const float startFactor = std::clamp(bend->startTimeFactor(), 0.f, 1.f);
305 18 : const float endFactor = std::clamp(bend->endTimeFactor(), 0.f, 1.f);
306 :
307 18 : float targetFactor = endFactor;
308 18 : if (bend->targetTimeFactor().has_value()) {
309 2 : targetFactor = std::clamp(bend->targetTimeFactor().value(), 0.f, 1.f);
310 : }
311 :
312 18 : IF_ASSERT_FAILED(RealIsEqualOrLess(startFactor, endFactor)
313 : && RealIsEqualOrLess(startFactor, targetFactor)
314 : && RealIsEqualOrLess(targetFactor, endFactor)) {
315 0 : return BendTimeFactors { 0.f, 1.f, 1.f };
316 : }
317 :
318 18 : return BendTimeFactors { startFactor, targetFactor, endFactor };
319 : }
320 :
321 29 : RenderingContext BendsRenderer::buildRenderingContext(const Note* note, const RenderingContext& initialCtx)
322 : {
323 : // Use the principal chord to build the context
324 29 : const Chord* chord = principalChord(note->chord());
325 :
326 29 : RenderingContext ctx = engraving::buildRenderingCtx(chord, initialCtx.positionTickOffset,
327 29 : initialCtx.profile, initialCtx.playbackCtx,
328 58 : initialCtx.commonArticulations);
329 :
330 29 : ChordArticulationsParser::buildChordArticulationMap(chord, ctx, ctx.commonArticulations);
331 :
332 29 : return ctx;
333 0 : }
334 :
335 10 : mpe::NoteEvent BendsRenderer::buildBendEvent(const Note* startNote, const RenderingContext& startNoteCtx,
336 : const mpe::PlaybackEventList& bendNoteEvents, const BendTimeFactorMap& timeFactorMap)
337 : {
338 10 : NominalNoteCtx noteCtx(startNote, startNoteCtx);
339 :
340 10 : const mpe::NoteEvent& startNoteEvent = std::get<mpe::NoteEvent>(bendNoteEvents.front());
341 10 : noteCtx.articulations = startNoteEvent.expressionCtx().articulations;
342 10 : noteCtx.timestamp = startNoteEvent.arrangementCtx().actualTimestamp;
343 10 : noteCtx.duration = startNoteEvent.arrangementCtx().actualDuration;
344 :
345 10 : PitchOffsets pitchOffsets;
346 10 : pitchOffsets.reserve(bendNoteEvents.size());
347 :
348 10 : auto multibendIt = noteCtx.articulations.find(mpe::ArticulationType::Multibend);
349 10 : if (multibendIt != noteCtx.articulations.end()) {
350 9 : const mpe::ArticulationMeta& meta = multibendIt->second.meta;
351 9 : pitchOffsets.emplace_back(meta.timestamp, 0);
352 : }
353 :
354 33 : for (size_t i = 1; i < bendNoteEvents.size(); ++i) {
355 23 : const mpe::PlaybackEvent& event = bendNoteEvents.at(i);
356 23 : if (!std::holds_alternative<mpe::NoteEvent>(event)) {
357 0 : continue;
358 : }
359 :
360 23 : const mpe::NoteEvent& noteEvent = std::get<mpe::NoteEvent>(event);
361 23 : const mpe::ArrangementContext& arrangementCtx = noteEvent.arrangementCtx();
362 :
363 23 : if (arrangementCtx.actualDuration != 0) {
364 14 : noteCtx.duration = arrangementCtx.actualTimestamp + arrangementCtx.actualDuration - noteCtx.timestamp;
365 : }
366 :
367 23 : const mpe::pitch_level_t offset = noteEvent.pitchCtx().nominalPitchLevel - noteCtx.pitchLevel;
368 23 : pitchOffsets.emplace_back(arrangementCtx.actualTimestamp, offset);
369 : }
370 :
371 10 : if (multibendIt != noteCtx.articulations.end()) {
372 9 : mpe::ArticulationMeta& meta = multibendIt->second.meta;
373 9 : meta.timestamp = noteCtx.timestamp;
374 9 : meta.overallDuration = noteCtx.duration;
375 : }
376 :
377 10 : mpe::PitchCurve curve = buildPitchCurve(noteCtx.timestamp, noteCtx.duration, pitchOffsets, timeFactorMap);
378 10 : mpe::NoteEvent result = buildNoteEvent(noteCtx, curve);
379 :
380 10 : return result;
381 10 : }
382 :
383 10 : mpe::PitchCurve BendsRenderer::buildPitchCurve(mpe::timestamp_t noteTimestamp, mpe::duration_t noteDuration,
384 : const PitchOffsets& pitchOffsets, const BendTimeFactorMap& timeFactorMap)
385 : {
386 32 : auto findFactorsAtTime = [&timeFactorMap](mpe::timestamp_t time) -> const BendTimeFactors& {
387 32 : auto it = muse::findLessOrEqual(timeFactorMap, time);
388 32 : if (it == timeFactorMap.end()) {
389 : static const BendTimeFactors dummy;
390 18 : return dummy;
391 : }
392 :
393 14 : return it->second;
394 10 : };
395 :
396 10 : mpe::PitchCurve result;
397 10 : result.emplace(0, 0);
398 :
399 10 : mpe::percentage_t prevNominalOffsetPrecent = 0;
400 :
401 42 : for (const auto& pair : pitchOffsets) {
402 32 : const BendTimeFactors& factors = findFactorsAtTime(pair.first);
403 32 : const float ratio = static_cast<float>(pair.first - noteTimestamp) / noteDuration;
404 :
405 32 : const mpe::percentage_t nominalOffsetPercent = static_cast<mpe::percentage_t>(ratio * 100.f) * mpe::ONE_PERCENT;
406 32 : const mpe::percentage_t nominalPercentDiff = nominalOffsetPercent - prevNominalOffsetPrecent;
407 :
408 32 : const mpe::percentage_t actualOffsetStartPercent = prevNominalOffsetPrecent + nominalPercentDiff * factors.startFactor;
409 32 : const mpe::percentage_t actualOffsetEndPercent = prevNominalOffsetPrecent + nominalPercentDiff * factors.endFactor;
410 :
411 32 : prevNominalOffsetPrecent = nominalOffsetPercent;
412 :
413 32 : const auto& prevOffset = result.rbegin();
414 32 : result.insert_or_assign(std::min(actualOffsetStartPercent, mpe::HUNDRED_PERCENT), prevOffset->second);
415 32 : result.insert_or_assign(std::min(actualOffsetEndPercent, mpe::HUNDRED_PERCENT), pair.second);
416 : }
417 :
418 10 : return result;
419 0 : }
|