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 "ornamentsrenderer.h"
24 :
25 : #include "realfn.h"
26 :
27 : #include "dom/ornament.h"
28 : #include "dom/utils.h"
29 :
30 : #include "playback/metaparsers/notearticulationsparser.h"
31 : #include "playback/utils/expressionutils.h"
32 :
33 : #include "noterenderer.h"
34 :
35 : using namespace mu::engraving;
36 : using namespace muse;
37 : using namespace muse::mpe;
38 :
39 : namespace mu::engraving {
40 : struct IntervalsInfo {
41 : bool intervalBelowIsAuto = false;
42 : bool intervalAboveIsAuto = false;
43 :
44 : Interval intervalBelow;
45 : Interval intervalAbove;
46 : };
47 :
48 : struct DisclosurePattern {
49 : int prefixDurationTicks = 0;
50 : std::vector<mpe::pitch_level_t> prefixPitchOffsets;
51 :
52 : bool isAlterationsRepeatAllowed = false;
53 : std::vector<mpe::pitch_level_t> alterationStepPitchOffsets;
54 :
55 : int suffixDurationTicks = 0;
56 : std::vector<mpe::pitch_level_t> suffixPitchOffsets;
57 :
58 : struct DurationBoundaries {
59 : float lowTempoDurationTicks = 0.f;
60 : float mediumTempoDurationTicks = 0.f;
61 : float highTempoDurationTicks = 0.f;
62 : };
63 :
64 : DurationBoundaries boundaries;
65 :
66 : float subNoteDurationTicks(const double bps) const;
67 : DisclosurePattern buildActualPattern(const Note* note, const IntervalsInfo& intervalsInfo, const double bps) const;
68 :
69 : private:
70 : void updatePitchOffsets(const Note* note, const IntervalsInfo& intervalsInfo, std::vector<mpe::pitch_level_t>& pitchOffsets);
71 : };
72 :
73 14 : static IntervalsInfo makeIntervalsInfo(const OrnamentInterval& below, const OrnamentInterval& above)
74 : {
75 14 : IntervalsInfo result;
76 14 : result.intervalBelowIsAuto = below.type == IntervalType::AUTO;
77 14 : result.intervalAboveIsAuto = above.type == IntervalType::AUTO;
78 14 : result.intervalBelow = Interval::fromOrnamentInterval(below);
79 14 : result.intervalAbove = Interval::fromOrnamentInterval(above);
80 :
81 14 : return result;
82 : }
83 : }
84 :
85 : static const std::unordered_map<ArticulationType, DisclosurePattern> DISCLOSURE_RULES = {
86 : {
87 : ArticulationType::Trill,
88 : {
89 : /*prefixDurationTicks*/ 0,
90 : /*prefixPitchOffsets*/ {},
91 : /*isAlterationsRepeatAllowed*/ true,
92 : /*alterationStepPitchOffsets*/ { 0, PITCH_LEVEL_STEP },
93 : /*suffixDurationTicks*/ 0,
94 : /*suffixPitchOffsets*/ {},
95 : /*minSupportedNoteDurationTicks*/ { QUAVER_TICKS / 10.f, DEMISEMIQUAVER_TICKS,
96 : SEMIQUAVER_TICKS }
97 : }
98 : },
99 : {
100 : ArticulationType::TrillBaroque,
101 : {
102 : /*prefixDurationTicks*/ 0,
103 : /*prefixPitchOffsets*/ {},
104 : /*isAlterationsRepeatAllowed*/ true,
105 : /*alterationStepPitchOffsets*/ { PITCH_LEVEL_STEP, 0 },
106 : /*suffixDurationTicks*/ DEMISEMIQUAVER_TICKS* 2,
107 : /*suffixPitchOffsets*/ { -PITCH_LEVEL_STEP, 0 },
108 : /*minSupportedNoteDurationTicks*/ { QUAVER_TICKS / 10.f, DEMISEMIQUAVER_TICKS,
109 : SEMIQUAVER_TICKS }
110 : }
111 : },
112 : {
113 : ArticulationType::LinePrall,
114 : {
115 : /*prefixDurationTicks*/ DEMISEMIQUAVER_TICKS* 3,
116 : /*prefixPitchOffsets*/ { 2 * PITCH_LEVEL_STEP, 2 * PITCH_LEVEL_STEP, 2 * PITCH_LEVEL_STEP },
117 : /*isAlterationsRepeatAllowed*/ true,
118 : /*alterationStepPitchOffsets*/ { PITCH_LEVEL_STEP, 0 },
119 : /*suffixDurationTicks*/ DEMISEMIQUAVER_TICKS* 2,
120 : /*suffixPitchOffsets*/ { PITCH_LEVEL_STEP, 0 },
121 : /*minSupportedNoteDurationTicks*/ { DEMISEMIQUAVER_TICKS, DEMISEMIQUAVER_TICKS, DEMISEMIQUAVER_TICKS }
122 : }
123 : },
124 : {
125 : ArticulationType::UpPrall,
126 : {
127 : /*prefixDurationTicks*/ SEMIQUAVER_TICKS* 2,
128 : /*prefixPitchOffsets*/ { -PITCH_LEVEL_STEP, 0 },
129 : /*isAlterationsRepeatAllowed*/ true,
130 : /*alterationStepPitchOffsets*/ { PITCH_LEVEL_STEP, 0 },
131 : /*suffixDurationTicks*/ SEMIQUAVER_TICKS* 2,
132 : /*suffixPitchOffsets*/ { PITCH_LEVEL_STEP, 0 },
133 : /*minSupportedNoteDurationTicks*/ { DEMISEMIQUAVER_TICKS, DEMISEMIQUAVER_TICKS, DEMISEMIQUAVER_TICKS }
134 : }
135 : },
136 : {
137 : ArticulationType::UpMordent,
138 : {
139 : /*prefixDurationTicks*/ SEMIQUAVER_TICKS* 2,
140 : /*prefixPitchOffsets*/ { -PITCH_LEVEL_STEP, 0 },
141 : /*isAlterationsRepeatAllowed*/ true,
142 : /*alterationStepPitchOffsets*/ { PITCH_LEVEL_STEP, 0 },
143 : /*suffixDurationTicks*/ SEMIQUAVER_TICKS* 2,
144 : /*suffixPitchOffsets*/ { -PITCH_LEVEL_STEP, 0 },
145 : /*minSupportedNoteDurationTicks*/ { SEMIQUAVER_TICKS, SEMIQUAVER_TICKS, SEMIQUAVER_TICKS }
146 : }
147 : },
148 : {
149 : ArticulationType::UpperMordent,
150 : {
151 : /*prefixDurationTicks*/ DEMISEMIQUAVER_TICKS* 2,
152 : /*prefixPitchOffsets*/ { 0, PITCH_LEVEL_STEP },
153 : /*isAlterationsRepeatAllowed*/ false,
154 : /*alterationStepPitchOffsets*/ { 0 },
155 : /*suffixDurationTicks*/ 0,
156 : /*suffixPitchOffsets*/ {},
157 : /*minSupportedNoteDurationTicks*/ { DEMISEMIQUAVER_TICKS / 2.f, DEMISEMIQUAVER_TICKS, SEMIQUAVER_TICKS }
158 : }
159 : },
160 : {
161 : ArticulationType::LowerMordent,
162 : {
163 : /*prefixDurationTicks*/ DEMISEMIQUAVER_TICKS* 2,
164 : /*prefixPitchOffsets*/ { 0, -PITCH_LEVEL_STEP },
165 : /*isAlterationsRepeatAllowed*/ false,
166 : /*alterationStepPitchOffsets*/ { 0 },
167 : /*suffixDurationTicks*/ 0,
168 : /*suffixPitchOffsets*/ {},
169 : /*minSupportedNoteDurationTicks*/ { DEMISEMIQUAVER_TICKS / 2.f, DEMISEMIQUAVER_TICKS, SEMIQUAVER_TICKS }
170 : }
171 : },
172 : {
173 : ArticulationType::UpperMordentBaroque,
174 : {
175 : /*prefixDurationTicks*/ DEMISEMIQUAVER_TICKS* 3,
176 : /*prefixPitchOffsets*/ { PITCH_LEVEL_STEP, 0, PITCH_LEVEL_STEP },
177 : /*isAlterationsRepeatAllowed*/ false,
178 : /*alterationStepPitchOffsets*/ { 0 },
179 : /*suffixDurationTicks*/ 0,
180 : /*suffixPitchOffsets*/ {},
181 : /*minSupportedNoteDurationTicks*/ { DEMISEMIQUAVER_TICKS / 2.f, DEMISEMIQUAVER_TICKS, SEMIQUAVER_TICKS }
182 : }
183 : },
184 : {
185 : ArticulationType::MordentWithUpperPrefix,
186 : {
187 : /*prefixDurationTicks*/ SEMIQUAVER_TICKS* 4,
188 : /*prefixPitchOffsets*/ { PITCH_LEVEL_STEP, PITCH_LEVEL_STEP, PITCH_LEVEL_STEP, 0 },
189 : /*isAlterationsRepeatAllowed*/ true,
190 : /*alterationStepPitchOffsets*/ { PITCH_LEVEL_STEP, 0 },
191 : /*suffixDurationTicks*/ 0,
192 : /*suffixPitchOffsets*/ {},
193 : /*minSupportedNoteDurationTicks*/ { SEMIQUAVER_TICKS, SEMIQUAVER_TICKS, SEMIQUAVER_TICKS }
194 : }
195 : },
196 : {
197 : ArticulationType::DownMordent,
198 : {
199 : /*prefixDurationTicks*/ SEMIQUAVER_TICKS* 4,
200 : /*prefixPitchOffsets*/ { PITCH_LEVEL_STEP, PITCH_LEVEL_STEP, PITCH_LEVEL_STEP, 0 },
201 : /*isAlterationsRepeatAllowed*/ true,
202 : /*alterationStepPitchOffsets*/ { PITCH_LEVEL_STEP, 0 },
203 : /*suffixDurationTicks*/ SEMIQUAVER_TICKS* 2,
204 : /*suffixPitchOffsets*/ { -PITCH_LEVEL_STEP, 0 },
205 : /*minSupportedNoteDurationTicks*/ { SEMIQUAVER_TICKS, SEMIQUAVER_TICKS, SEMIQUAVER_TICKS }
206 : }
207 : },
208 : {
209 : ArticulationType::PrallUp,
210 : {
211 : /*prefixDurationTicks*/ SEMIQUAVER_TICKS* 2,
212 : /*prefixPitchOffsets*/ { PITCH_LEVEL_STEP, 0 },
213 : /*isAlterationsRepeatAllowed*/ true,
214 : /*alterationStepPitchOffsets*/ { PITCH_LEVEL_STEP, 0 },
215 : /*suffixDurationTicks*/ SEMIQUAVER_TICKS* 2,
216 : /*suffixPitchOffsets*/ { -PITCH_LEVEL_STEP, 0 },
217 : /*minSupportedNoteDurationTicks*/ { SEMIQUAVER_TICKS, SEMIQUAVER_TICKS, SEMIQUAVER_TICKS }
218 : }
219 : },
220 : {
221 : ArticulationType::PrallDown,
222 : {
223 : /*prefixDurationTicks*/ SEMIQUAVER_TICKS* 2,
224 : /*prefixPitchOffsets*/ { PITCH_LEVEL_STEP, 0 },
225 : /*isAlterationsRepeatAllowed*/ true,
226 : /*alterationStepPitchOffsets*/ { PITCH_LEVEL_STEP, 0 },
227 : /*suffixDurationTicks*/ SEMIQUAVER_TICKS* 4,
228 : /*suffixPitchOffsets*/ { -PITCH_LEVEL_STEP, 0, 0, 0 },
229 : /*minSupportedNoteDurationTicks*/ { SEMIQUAVER_TICKS, SEMIQUAVER_TICKS, SEMIQUAVER_TICKS }
230 : }
231 : },
232 : {
233 : ArticulationType::Turn,
234 : {
235 : /*prefixDurationTicks*/ DEMISEMIQUAVER_TICKS* 3,
236 : /*prefixPitchOffsets*/ { PITCH_LEVEL_STEP, 0, -PITCH_LEVEL_STEP },
237 : /*isAlterationsRepeatAllowed*/ false,
238 : /*alterationStepPitchOffsets*/ { 0 },
239 : /*suffixDurationTicks*/ 0,
240 : /*suffixPitchOffsets*/ {},
241 : /*minSupportedNoteDurationTicks*/ { DEMISEMIQUAVER_TICKS / 2.f, DEMISEMIQUAVER_TICKS, SEMIQUAVER_TICKS }
242 : }
243 : },
244 : {
245 : ArticulationType::InvertedTurn,
246 : {
247 : /*prefixDurationTicks*/ DEMISEMIQUAVER_TICKS* 3,
248 : /*prefixPitchOffsets*/ { -PITCH_LEVEL_STEP, 0, PITCH_LEVEL_STEP },
249 : /*isAlterationsRepeatAllowed*/ false,
250 : /*alterationStepPitchOffsets*/ { 0 },
251 : /*suffixDurationTicks*/ 0,
252 : /*suffixPitchOffsets*/ {},
253 : /*minSupportedNoteDurationTicks*/ { DEMISEMIQUAVER_TICKS / 2.f, DEMISEMIQUAVER_TICKS, SEMIQUAVER_TICKS }
254 : }
255 : },
256 : {
257 : ArticulationType::Tremblement,
258 : {
259 : /*prefixDurationTicks*/ SEMIQUAVER_TICKS* 2,
260 : /*prefixPitchOffsets*/ { PITCH_LEVEL_STEP, 0 },
261 : /*isAlterationsRepeatAllowed*/ false,
262 : /*alterationStepPitchOffsets*/ { PITCH_LEVEL_STEP, 0 },
263 : /*suffixDurationTicks*/ 0,
264 : /*suffixPitchOffsets*/ {},
265 : /*minSupportedNoteDurationTicks*/ { SEMIQUAVER_TICKS, SEMIQUAVER_TICKS, SEMIQUAVER_TICKS }
266 : }
267 : },
268 : {
269 : ArticulationType::PrallMordent,
270 : {
271 : /*prefixDurationTicks*/ 0,
272 : /*prefixPitchOffsets*/ {},
273 : /*isAlterationsRepeatAllowed*/ false,
274 : /*alterationStepPitchOffsets*/ { PITCH_LEVEL_STEP, 0, -PITCH_LEVEL_STEP, 0 },
275 : /*suffixDurationTicks*/ 0,
276 : /*suffixPitchOffsets*/ {},
277 : /*minSupportedNoteDurationTicks*/ { SEMIQUAVER_TICKS, SEMIQUAVER_TICKS, SEMIQUAVER_TICKS }
278 : }
279 : },
280 : };
281 :
282 74 : const ArticulationTypeSet& OrnamentsRenderer::supportedTypes()
283 : {
284 : static const ArticulationTypeSet types = {
285 : ArticulationType::Trill, ArticulationType::TrillBaroque,
286 : ArticulationType::LinePrall, ArticulationType::UpPrall,
287 : ArticulationType::UpMordent, ArticulationType::UpperMordent,
288 : ArticulationType::LowerMordent, ArticulationType::MordentWithUpperPrefix,
289 : ArticulationType::DownMordent, ArticulationType::PrallUp,
290 : ArticulationType::PrallDown, ArticulationType::Turn,
291 : ArticulationType::InvertedTurn, ArticulationType::Tremblement,
292 : ArticulationType::PrallMordent, ArticulationType::UpperMordentBaroque,
293 : ArticulationType::LowerMordentBaroque
294 76 : };
295 :
296 74 : return types;
297 : }
298 :
299 14 : void OrnamentsRenderer::doRender(const EngravingItem* item, const ArticulationType preferredType,
300 : const RenderingContext& ctx,
301 : mpe::PlaybackEventList& result)
302 : {
303 14 : const Chord* chord = toChord(item);
304 14 : IF_ASSERT_FAILED(chord) {
305 0 : return;
306 : }
307 :
308 14 : auto search = DISCLOSURE_RULES.find(preferredType);
309 14 : if (search == DISCLOSURE_RULES.end()) {
310 0 : return;
311 : }
312 :
313 14 : IntervalsInfo intervalsInfo;
314 14 : bool isArticulation = false;
315 :
316 14 : if (Ornament* ornament = chord->findOrnament(true)) {
317 14 : intervalsInfo = makeIntervalsInfo(ornament->intervalBelow(), ornament->intervalAbove());
318 14 : isArticulation = muse::contains(chord->articulations(), toArticulation(ornament));
319 : } else {
320 0 : intervalsInfo = makeIntervalsInfo(DEFAULT_ORNAMENT_INTERVAL, DEFAULT_ORNAMENT_INTERVAL);
321 : }
322 :
323 14 : const DisclosurePattern& nominalPattern = search->second;
324 :
325 28 : for (const Note* note : chord->notes()) {
326 14 : if (!NoteRenderer::shouldRender(note, ctx, ctx.commonArticulations)) {
327 0 : continue;
328 : }
329 :
330 14 : RenderingContext ornamentCtx(ctx);
331 14 : if (isArticulation && note->tieFor()) {
332 2 : applyTiedNotesDuration(note, preferredType, ornamentCtx);
333 : }
334 :
335 14 : NominalNoteCtx noteCtx(note, ornamentCtx);
336 14 : NoteArticulationsParser::buildNoteArticulationMap(note, noteCtx.chordCtx, noteCtx.articulations);
337 :
338 14 : convert(preferredType, nominalPattern.buildActualPattern(note, intervalsInfo, ornamentCtx.beatsPerSecond.val),
339 14 : std::move(noteCtx), result);
340 14 : }
341 : }
342 :
343 2 : void OrnamentsRenderer::applyTiedNotesDuration(const Note* note, const ArticulationType ornamentType, RenderingContext& ctx)
344 : {
345 2 : const Note* lastTiedNote = note->lastTiedNote(false);
346 2 : if (!lastTiedNote || lastTiedNote == note) {
347 0 : return;
348 : }
349 :
350 2 : ctx.nominalPositionEndTick = lastTiedNote->chord()->endTick().ticks();
351 2 : ctx.nominalDuration = timestampFromTicks(ctx.score, ctx.nominalPositionEndTick + ctx.positionTickOffset) - ctx.nominalTimestamp;
352 2 : ctx.nominalDurationTicks = ctx.nominalPositionEndTick - ctx.nominalPositionStartTick;
353 :
354 2 : mpe::ArticulationMeta& meta = ctx.commonArticulations.at(ornamentType).meta;
355 2 : meta.overallDuration = ctx.nominalDuration;
356 : }
357 :
358 14 : void OrnamentsRenderer::convert(const ArticulationType type, const DisclosurePattern& pattern, NominalNoteCtx&& noteCtx,
359 : mpe::PlaybackEventList& result)
360 : {
361 14 : if (noteCtx.chordCtx.nominalDurationTicks <= pattern.boundaries.lowTempoDurationTicks) {
362 0 : result.emplace_back(buildNoteEvent(noteCtx));
363 0 : return;
364 : }
365 :
366 : // convert prefix
367 14 : if (!pattern.prefixPitchOffsets.empty()) {
368 5 : createEvents(type, noteCtx, 1, pattern.prefixDurationTicks,
369 5 : noteCtx.chordCtx.nominalDurationTicks, pattern.prefixPitchOffsets, result);
370 : }
371 :
372 : // convert body
373 14 : if (!pattern.alterationStepPitchOffsets.empty()) {
374 14 : int alterationsCount = 1;
375 :
376 14 : if (pattern.isAlterationsRepeatAllowed) {
377 9 : alterationsCount = alterationsNumberByTempo(noteCtx.chordCtx.beatsPerSecond.val,
378 : noteCtx.chordCtx.nominalDurationTicks,
379 : pattern);
380 : }
381 :
382 14 : if (alterationsCount == 0) {
383 1 : result.emplace_back(buildNoteEvent(noteCtx));
384 : } else {
385 13 : createEvents(type, noteCtx, alterationsCount,
386 13 : noteCtx.chordCtx.nominalDurationTicks - pattern.prefixDurationTicks - pattern.suffixDurationTicks,
387 : noteCtx.chordCtx.nominalDurationTicks,
388 13 : pattern.alterationStepPitchOffsets, result);
389 : }
390 : }
391 :
392 : // convert suffix
393 14 : if (!pattern.suffixPitchOffsets.empty()) {
394 1 : createEvents(type, noteCtx, 1,
395 1 : pattern.suffixDurationTicks,
396 : noteCtx.chordCtx.nominalDurationTicks,
397 1 : pattern.suffixPitchOffsets, result);
398 : }
399 1 : }
400 :
401 9 : int OrnamentsRenderer::alterationsNumberByTempo(const double beatsPerSeconds, const int principalNoteDurationTicks,
402 : const DisclosurePattern& pattern)
403 : {
404 9 : float subNotesCount = principalNoteDurationTicks / pattern.subNoteDurationTicks(beatsPerSeconds);
405 :
406 9 : return static_cast<int>(std::max(subNotesCount / 2, 0.f));
407 : }
408 :
409 19 : void OrnamentsRenderer::createEvents(const ArticulationType type, NominalNoteCtx& noteCtx, const int alterationsCount,
410 : const int availableDurationTicks, const int overallDurationTicks,
411 : const std::vector<mpe::pitch_level_t>& pitchOffsets, mpe::PlaybackEventList& result)
412 : {
413 19 : float availableDurationRatio = availableDurationTicks / static_cast<float>(overallDurationTicks);
414 :
415 19 : size_t totalNotesCount = alterationsCount * pitchOffsets.size();
416 19 : float durationStep = (noteCtx.duration * availableDurationRatio) / totalNotesCount;
417 :
418 19 : track_idx_t trackIdx = staff2track(noteCtx.staffIdx, noteCtx.voiceIdx);
419 :
420 142 : for (int alterationStep = 0; alterationStep < alterationsCount; ++alterationStep) {
421 367 : for (size_t alterationSubNoteIdx = 0; alterationSubNoteIdx < pitchOffsets.size(); ++alterationSubNoteIdx) {
422 244 : NominalNoteCtx subNoteCtx(noteCtx);
423 244 : subNoteCtx.duration = durationStep;
424 244 : subNoteCtx.pitchLevel += pitchOffsets.at(alterationSubNoteIdx);
425 :
426 244 : int utick = timestampToTick(subNoteCtx.chordCtx.score, subNoteCtx.timestamp);
427 244 : subNoteCtx.dynamicLevel = noteCtx.chordCtx.playbackCtx->appliableDynamicLevel(trackIdx, utick);
428 :
429 244 : updateArticulationBoundaries(type, subNoteCtx.timestamp,
430 : subNoteCtx.duration, subNoteCtx.articulations);
431 :
432 244 : result.emplace_back(buildNoteEvent(subNoteCtx));
433 :
434 244 : noteCtx.timestamp += durationStep;
435 244 : }
436 : }
437 263 : }
438 :
439 23 : float DisclosurePattern::subNoteDurationTicks(const double bps) const
440 : {
441 23 : if (muse::RealIsEqualOrMore(bps, PRESTISSIMO_BPS_BOUND)) {
442 0 : return boundaries.highTempoDurationTicks;
443 : }
444 :
445 23 : if (muse::RealIsEqualOrMore(bps, MODERATO_BPS_BOUND)) {
446 23 : return boundaries.mediumTempoDurationTicks;
447 : }
448 :
449 0 : return boundaries.lowTempoDurationTicks;
450 : }
451 :
452 14 : DisclosurePattern DisclosurePattern::buildActualPattern(const Note* note, const IntervalsInfo& intervalsInfo, const double bps) const
453 : {
454 14 : DisclosurePattern result = *this;
455 :
456 14 : result.updatePitchOffsets(note, intervalsInfo, result.prefixPitchOffsets);
457 14 : result.updatePitchOffsets(note, intervalsInfo, result.alterationStepPitchOffsets);
458 14 : result.updatePitchOffsets(note, intervalsInfo, result.suffixPitchOffsets);
459 :
460 14 : float subNoteTicks = subNoteDurationTicks(bps);
461 :
462 14 : result.prefixDurationTicks = muse::RealRound(prefixPitchOffsets.size() * subNoteTicks, 0);
463 14 : result.suffixDurationTicks = muse::RealRound(suffixPitchOffsets.size() * subNoteTicks, 0);
464 :
465 14 : return result;
466 0 : }
467 :
468 42 : void DisclosurePattern::updatePitchOffsets(const Note* note, const IntervalsInfo& intervalsInfo,
469 : std::vector<mpe::pitch_level_t>& pitchOffsets)
470 : {
471 80 : for (pitch_level_t& pitchOffset : pitchOffsets) {
472 38 : if (pitchOffset == 0) {
473 20 : continue;
474 : }
475 :
476 18 : semitone_t semitones = 0;
477 :
478 18 : if (pitchOffset < 0) {
479 5 : if (intervalsInfo.intervalBelowIsAuto) {
480 5 : semitones = std::abs(chromaticPitchSteps(note, note, -intervalsInfo.intervalBelow.diatonic));
481 : } else {
482 0 : semitones = intervalsInfo.intervalBelow.chromatic;
483 : }
484 13 : } else if (pitchOffset > 0) {
485 13 : if (intervalsInfo.intervalAboveIsAuto) {
486 13 : semitones = std::abs(chromaticPitchSteps(note, note, intervalsInfo.intervalAbove.diatonic));
487 : } else {
488 0 : semitones = intervalsInfo.intervalAbove.chromatic;
489 : }
490 : }
491 :
492 18 : pitchOffset *= semitones;
493 : }
494 42 : }
|