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 "noterenderer.h"
24 :
25 : #include "dom/arpeggio.h"
26 : #include "dom/note.h"
27 : #include "dom/staff.h"
28 : #include "dom/swing.h"
29 : #include "dom/tremolosinglechord.h"
30 : #include "dom/tremolotwochord.h"
31 :
32 : #include "glissandosrenderer.h"
33 :
34 : #include "playback/metaparsers/chordarticulationsparser.h"
35 : #include "playback/metaparsers/notearticulationsparser.h"
36 :
37 : #include "playback/utils/repeatutils.h"
38 :
39 : using namespace mu::engraving;
40 : using namespace muse;
41 : using namespace muse::mpe;
42 :
43 1550 : bool NoteRenderer::shouldRender(const Note* note, const RenderingContext& ctx, const muse::mpe::ArticulationMap& articulations)
44 : {
45 1550 : if (!note->play()) {
46 0 : return false;
47 : }
48 :
49 1550 : const Tie* tie = note->tieBack();
50 :
51 1550 : if (tie && tie->playSpanner()) {
52 155 : const Note* startNote = tie->startNote();
53 155 : const Note* endNote = tie->endNote();
54 155 : if (!startNote || !endNote) {
55 0 : return false;
56 : }
57 :
58 155 : if (tie->isPartialTie()) {
59 : // Play the partially tied note if there is no outgoing note in the previous repeat
60 25 : if (!findOutgoingNoteInPreviousRepeat(note, ctx).isValid()) {
61 11 : return true;
62 : }
63 : }
64 :
65 144 : const Chord* startChord = startNote->chord();
66 144 : const Chord* endChord = endNote->chord();
67 :
68 : // Helper function to check if tremolo should play
69 228 : auto shouldTremoloPlay = [](const Chord* chord) -> bool {
70 228 : if (chord->tremoloType() == TremoloType::INVALID_TREMOLO) {
71 149 : return false;
72 : }
73 :
74 79 : const TremoloSingleChord* singleTremolo = chord->tremoloSingleChord();
75 79 : if (singleTremolo) {
76 79 : return singleTremolo->playTremolo();
77 : }
78 :
79 0 : const TremoloTwoChord* twoTremolo = chord->tremoloTwoChord();
80 0 : if (twoTremolo) {
81 0 : return twoTremolo->playTremolo();
82 : }
83 :
84 0 : return false;
85 : };
86 :
87 : // Only render tied notes if tremolo is actually enabled for playback
88 144 : if (shouldTremoloPlay(startChord) || shouldTremoloPlay(endChord)) {
89 77 : return true;
90 : }
91 :
92 67 : if (startChord->arpeggio() && endChord->arpeggio()) {
93 4 : return false;
94 : }
95 :
96 : //!Note Checking whether the tied note has any multi-note articulation attached
97 : //! If so, we can't ignore such note
98 124 : for (const auto& pair : articulations) {
99 66 : if (muse::mpe::isMultiNoteArticulation(pair.first) && !muse::mpe::isRangedArticulation(pair.first)) {
100 5 : return true;
101 : }
102 : }
103 :
104 116 : const auto& intervals = startChord->score()->spannerMap().findOverlapping(startChord->tick().ticks(),
105 58 : startChord->endTick().ticks(),
106 : /*excludeCollisions*/ true);
107 72 : for (const auto& interval : intervals) {
108 15 : const Spanner* sp = interval.value;
109 15 : if (sp->isTrill() && sp->playSpanner() && sp->endElement() == startChord) {
110 1 : return true;
111 : }
112 : }
113 :
114 57 : return false;
115 : }
116 :
117 1395 : return true;
118 : }
119 :
120 1087 : void NoteRenderer::render(const Note* note, const RenderingContext& ctx, mpe::PlaybackEventList& result)
121 : {
122 1087 : IF_ASSERT_FAILED(note) {
123 32 : return;
124 : }
125 :
126 1087 : NominalNoteCtx noteCtx = buildNominalNoteCtx(note, ctx);
127 1087 : if (!shouldRender(note, ctx, noteCtx.articulations)) {
128 25 : return;
129 : }
130 :
131 1062 : const Tie* tieFor = note->tieFor();
132 1062 : if (tieFor && tieFor->playSpanner()) {
133 35 : if (tieFor->isPartialTie()) {
134 4 : renderPartialTie(note, noteCtx);
135 31 : } else if (!tieFor->isLaissezVib()) {
136 31 : renderNormalTie(note, noteCtx);
137 : }
138 : }
139 :
140 1062 : applySwingIfNeed(note, noteCtx);
141 :
142 1062 : if (noteCtx.articulations.contains(ArticulationType::DiscreteGlissando)) {
143 4 : GlissandosRenderer::renderDiscreteGlissando(note, noteCtx, result);
144 4 : return;
145 : }
146 :
147 1058 : if (noteCtx.articulations.contains(ArticulationType::ContinuousGlissando)) {
148 3 : GlissandosRenderer::renderContinuousGlissando(note, noteCtx, result);
149 3 : return;
150 : }
151 :
152 1055 : mpe::NoteEvent ev = buildNoteEvent(noteCtx);
153 :
154 1055 : if (ev.arrangementCtx().actualTimestamp >= 0) {
155 1055 : result.emplace_back(std::move(ev));
156 : } else {
157 0 : ArrangementContext arrCtx = ev.arrangementCtx();
158 0 : arrCtx.actualDuration = arrCtx.actualDuration + arrCtx.actualTimestamp;
159 0 : arrCtx.actualTimestamp = 0;
160 :
161 0 : PitchContext pitchCtx = ev.pitchCtx();
162 0 : ExpressionContext expCtx = ev.expressionCtx();
163 :
164 0 : result.emplace_back(mpe::NoteEvent(std::move(arrCtx), std::move(pitchCtx), std::move(expCtx)));
165 0 : }
166 2142 : }
167 :
168 7 : void NoteRenderer::renderPartialTie(const Note* outgoingNote, NominalNoteCtx& outgoingNoteCtx)
169 : {
170 7 : const RenderingContext& outgoingChordCtx = outgoingNoteCtx.chordCtx;
171 7 : const PartiallyTiedNoteInfo incomingNoteInfo = findIncomingNoteInNextRepeat(outgoingNote, outgoingChordCtx);
172 7 : if (!incomingNoteInfo.isValid()) {
173 2 : return;
174 : }
175 :
176 5 : const int incomingNotePositionTickOffset = incomingNoteInfo.repeat->utick - incomingNoteInfo.repeat->tick;
177 15 : RenderingContext incomingChordCtx = buildRenderingCtx(incomingNoteInfo.note->chord(), incomingNotePositionTickOffset,
178 10 : outgoingChordCtx.profile, outgoingChordCtx.playbackCtx);
179 :
180 5 : ChordArticulationsParser::buildChordArticulationMap(incomingNoteInfo.note->chord(), incomingChordCtx,
181 : incomingChordCtx.commonArticulations);
182 :
183 5 : NominalNoteCtx incomingNoteCtx = buildNominalNoteCtx(incomingNoteInfo.note, incomingChordCtx);
184 :
185 5 : if (shouldRender(incomingNoteInfo.note, incomingChordCtx, incomingNoteCtx.articulations)) {
186 0 : return;
187 : }
188 :
189 5 : const Tie* tieFor = incomingNoteInfo.note->tieFor();
190 5 : if (tieFor && tieFor->playSpanner() && !tieFor->isLaissezVib()) {
191 0 : renderNormalTie(incomingNoteInfo.note, incomingNoteCtx);
192 : }
193 :
194 5 : addTiedNote(incomingNoteCtx, outgoingNoteCtx);
195 5 : updateArticulationBoundaries(outgoingNoteCtx.timestamp, outgoingNoteCtx.duration, outgoingNoteCtx.articulations);
196 5 : }
197 :
198 31 : void NoteRenderer::renderNormalTie(const Note* firstNote, NominalNoteCtx& firstNoteCtx)
199 : {
200 62 : std::unordered_set<const Note*> renderedNotes { firstNote };
201 :
202 31 : const RenderingContext& firstChordCtx = firstNoteCtx.chordCtx;
203 31 : const Tie* currTie = firstNote->tieFor();
204 :
205 62 : while (currTie && currTie->playSpanner()) {
206 38 : const Note* currNote = currTie->endNote();
207 38 : if (!currNote || !currNote->play()) {
208 7 : break;
209 : }
210 :
211 38 : if (muse::contains(renderedNotes, currNote)) {
212 0 : break; // prevents infinite loop
213 : }
214 :
215 38 : if (!notesInSameRepeat(firstChordCtx.score, firstNote, currNote, firstChordCtx.positionTickOffset)) {
216 4 : const TieJumpPointList* jumpPoints = firstNote->tieJumpPoints();
217 4 : if (jumpPoints && !jumpPoints->empty()) {
218 3 : renderPartialTie(firstNote, firstNoteCtx);
219 : }
220 4 : break;
221 : }
222 :
223 34 : const Chord* chord = currNote->chord();
224 34 : if (!chord) {
225 0 : break;
226 : }
227 :
228 68 : RenderingContext currChordCtx = buildRenderingCtx(chord, firstChordCtx.positionTickOffset,
229 34 : firstChordCtx.profile, firstChordCtx.playbackCtx);
230 34 : ChordArticulationsParser::buildChordArticulationMap(chord, currChordCtx, currChordCtx.commonArticulations);
231 :
232 34 : const NominalNoteCtx currNoteCtx = buildNominalNoteCtx(currNote, currChordCtx);
233 34 : if (shouldRender(currNote, currChordCtx, currNoteCtx.articulations)) {
234 3 : if (currNoteCtx.articulations.contains(ArticulationType::DiscreteGlissando)) {
235 1 : firstNoteCtx.duration += GlissandosRenderer::discreteGlissandoStepDuration(currNote, currNoteCtx.duration);
236 : }
237 :
238 3 : break;
239 : }
240 :
241 31 : addTiedNote(currNoteCtx, firstNoteCtx);
242 :
243 31 : currTie = currNote->tieFor();
244 31 : renderedNotes.insert(currNote);
245 37 : }
246 :
247 31 : if (firstNoteCtx.articulations.size() > 1) {
248 4 : firstNoteCtx.articulations.erase(mpe::ArticulationType::Standard);
249 : }
250 :
251 31 : updateArticulationBoundaries(firstNoteCtx.timestamp, firstNoteCtx.duration, firstNoteCtx.articulations);
252 31 : }
253 :
254 36 : void NoteRenderer::addTiedNote(const NominalNoteCtx& tiedNoteCtx, NominalNoteCtx& firstNoteCtx)
255 : {
256 36 : if (tiedNoteCtx.articulations.size() == 1) {
257 33 : if (tiedNoteCtx.articulations.begin()->first == mpe::ArticulationType::Standard) {
258 25 : firstNoteCtx.duration += tiedNoteCtx.duration;
259 25 : return;
260 : }
261 : }
262 :
263 11 : const float avgDurationFactor = percentageToFactor(tiedNoteCtx.articulations.averageDurationFactor());
264 11 : firstNoteCtx.duration += tiedNoteCtx.duration * avgDurationFactor;
265 :
266 : // Ignore these articulations so we won't re-apply them to the total duration
267 : static const ArticulationTypeSet ARTICULATION_TO_IGNORE_TYPES {
268 : ArticulationType::Staccato,
269 : ArticulationType::Staccatissimo,
270 13 : };
271 :
272 25 : for (const auto& pair : tiedNoteCtx.articulations) {
273 14 : if (!muse::contains(ARTICULATION_TO_IGNORE_TYPES, pair.first)) {
274 14 : firstNoteCtx.articulations.insert(pair);
275 : }
276 : }
277 : }
278 :
279 36 : void NoteRenderer::updateArticulationBoundaries(const timestamp_t noteTimestamp, const duration_t noteDuration,
280 : ArticulationMap& articulations)
281 : {
282 36 : const timestamp_t noteTimestampTo = noteTimestamp + noteDuration;
283 36 : IF_ASSERT_FAILED(noteTimestampTo > 0) {
284 0 : return;
285 : }
286 :
287 75 : for (const auto& pair : articulations) {
288 39 : const ArticulationAppliedData& articulation = pair.second;
289 :
290 39 : const duration_percentage_t occupiedFrom = mpe::occupiedPercentage(articulation.meta.timestamp,
291 : noteTimestampTo);
292 39 : const duration_percentage_t occupiedTo = mpe::occupiedPercentage(articulation.meta.timestamp + articulation.meta.overallDuration,
293 : noteTimestampTo);
294 :
295 39 : articulations.updateOccupiedRange(pair.first, occupiedFrom, occupiedTo);
296 : }
297 : }
298 :
299 1062 : void NoteRenderer::applySwingIfNeed(const Note* note, NominalNoteCtx& noteCtx)
300 : {
301 1062 : const Chord* chord = note->chord();
302 1062 : if (!chord || chord->tuplet()) {
303 1062 : return;
304 : }
305 :
306 1062 : const SwingParameters swing = chord->staff()->swing(chord->tick());
307 1062 : if (!swing.isOn()) {
308 1062 : return;
309 : }
310 :
311 : //! NOTE: Swing must be applied to the "raw" note duration, but not to the additional duration (e.g, from a tied note)
312 0 : const Swing::ChordDurationAdjustment swingDurationAdjustment = Swing::applySwing(chord, swing);
313 0 : const duration_t additionalDuration = noteCtx.duration - noteCtx.chordCtx.nominalDuration;
314 0 : noteCtx.timestamp = noteCtx.timestamp + noteCtx.chordCtx.nominalDuration * swingDurationAdjustment.remainingDurationMultiplier;
315 0 : noteCtx.duration = noteCtx.chordCtx.nominalDuration * swingDurationAdjustment.durationMultiplier + additionalDuration;
316 : }
317 :
318 1126 : NominalNoteCtx NoteRenderer::buildNominalNoteCtx(const Note* note, const RenderingContext& ctx)
319 : {
320 1126 : NominalNoteCtx noteCtx(note, ctx);
321 1126 : NoteArticulationsParser::buildNoteArticulationMap(note, ctx, noteCtx.articulations);
322 :
323 1126 : return noteCtx;
324 0 : }
|