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 : /**
24 : \file
25 : render score into event list
26 : */
27 :
28 : #include "compatmidirender.h"
29 : #include "compatmidirenderinternal.h"
30 :
31 : #include <cmath>
32 :
33 : #include "compat/midi/event.h"
34 : #include "types/constants.h"
35 :
36 : #include "dom/accidental.h"
37 : #include "dom/arpeggio.h"
38 : #include "dom/articulation.h"
39 : #include "dom/bend.h"
40 : #include "dom/chord.h"
41 : #include "dom/durationtype.h"
42 : #include "dom/dynamic.h"
43 : #include "dom/fret.h"
44 : #include "dom/glissando.h"
45 : #include "dom/guitarbend.h"
46 : #include "dom/hairpin.h"
47 : #include "dom/harmony.h"
48 : #include "dom/instrument.h"
49 : #include "dom/letring.h"
50 : #include "dom/masterscore.h"
51 : #include "dom/measure.h"
52 : #include "dom/measurerepeat.h"
53 : #include "dom/note.h"
54 : #include "dom/noteevent.h"
55 : #include "dom/part.h"
56 : #include "dom/repeatlist.h"
57 : #include "dom/score.h"
58 : #include "dom/segment.h"
59 : #include "dom/staff.h"
60 : #include "dom/stafftextbase.h"
61 : #include "dom/swing.h"
62 : #include "dom/tie.h"
63 : #include "dom/trill.h"
64 : #include "dom/utils.h"
65 : #include "dom/vibrato.h"
66 : #include "dom/volta.h"
67 :
68 : #include "log.h"
69 :
70 : namespace mu::engraving {
71 : static PitchWheelSpecs g_wheelSpec;
72 : static constexpr int LET_RING_MAX_TICKS = Constants::DIVISION * 16;
73 : // TODO this should be a (configurable?) constant somewhere
74 : static constexpr Fraction ARTICULATION_CHANGE_TIME_MAX = Fraction(1, 16);
75 : std::unordered_map<String,
76 : CompatMidiRendererInternal::Context::BuiltInArticulation> CompatMidiRendererInternal::Context::
77 : s_builtInArticulationsValues = {
78 : { u"staccatissimo", { 1.0, 30 } },
79 : { u"staccato", { 1.0, 50 } },
80 : { u"portato", { 1.0, 67 } },
81 : { u"tenuto", { 1.0, 100 } },
82 : { u"accent", { 1.2, 100 } },
83 : { u"marcato", { 1.44, 100 } },
84 : { u"sforzato", { 1.69, 100 } },
85 : };
86 :
87 : struct CollectNoteParams {
88 : double velocityMultiplier = 1.;
89 : int tickOffset = 0;
90 : int graceOffsetOn = 0;
91 : int graceOffsetOff = 0;
92 : int endLetRingTick = 0;
93 : int previousChordTicks = -1;
94 : bool letRingNote = false;
95 : MidiInstrumentEffect effect = MidiInstrumentEffect::NONE;
96 : bool callAllSoundOff = false;//NoteOn silence channel
97 : };
98 :
99 : struct PlayNoteParams {
100 : int channel = 0;
101 : int pitch = 0;
102 : int velo = 0;
103 : int onTime = 0;
104 : int offTime = 0;
105 : int offset = 0;
106 : int staffIdx = 0;
107 : MidiInstrumentEffect effect = MidiInstrumentEffect::NONE;
108 : bool callAllSoundOff = false;//NoteOn silence channel
109 : };
110 :
111 : struct VibratoParams {
112 : int lowPitch = 0;
113 : int highPitch = 0;
114 : int period = 0;
115 : };
116 :
117 : struct BendPlaybackInfo {
118 : int startTick = 0;
119 : int endTick = 0;
120 : float startTimeFactor = 0.f;
121 : float endTimeFactor = 1.f;
122 : };
123 :
124 : static uint32_t getChannel(const Instrument* instr, const Note* note, MidiInstrumentEffect effect,
125 : const CompatMidiRendererInternal::Context& context);
126 :
127 : static void fillScoreVelocities(const Score* score, CompatMidiRendererInternal::Context& context);
128 : static void fillHairpinVelocities(const Hairpin* h, std::unordered_map<staff_idx_t, VelocityMap>& velocitiesByTrack);
129 : static void fillVoltaVelocities(const Volta* volta, VelocityMap& veloMap);
130 :
131 : static double chordVelocityMultiplier(const Chord* chord, const CompatMidiRendererInternal::Context& context);
132 : static double velocityMultiplierByInstrument(const Instrument* instrument, const String& articulationName,
133 : const CompatMidiRendererInternal::Context& context);
134 : static int graceBendDuration(const Chord* chord);
135 :
136 : //---------------------------------------------------------
137 : // Converts midi time (noteoff - noteon) to milliseconds
138 : //---------------------------------------------------------
139 0 : int toMilliseconds(float tempo, float midiTime)
140 : {
141 0 : float ticksPerSecond = (float)Constants::DIVISION * tempo;
142 0 : int time = (int)((midiTime / ticksPerSecond) * 1000.0f);
143 0 : if (time > 0x7fff) { //maximum possible value
144 0 : time = 0x7fff;
145 : }
146 0 : return time;
147 : }
148 :
149 : //---------------------------------------------------------
150 : // Detects if a note is a start of a glissando
151 : //---------------------------------------------------------
152 28 : bool isGlissandoFor(const Note* note)
153 : {
154 32 : for (Spanner* spanner : note->spannerFor()) {
155 5 : if (spanner->isGlissando()) {
156 1 : return true;
157 : }
158 : }
159 27 : return false;
160 : }
161 :
162 : //---------------------------------------------------------
163 : // Detects if a note is an end of a glissando
164 : //---------------------------------------------------------
165 18 : bool isGlissandoBack(const Note* note)
166 : {
167 18 : for (Spanner* spanner : note->spannerBack()) {
168 1 : if (spanner->isGlissando()) {
169 1 : return true;
170 : }
171 : }
172 17 : return false;
173 : }
174 :
175 0 : static void collectGlissando(int channel, MidiInstrumentEffect effect,
176 : int onTime, int offTime,
177 : int pitchDelta,
178 : PitchWheelRenderer& pitchWheelRenderer, staff_idx_t staffIdx)
179 : {
180 0 : const float scale = (float)g_wheelSpec.mLimit / g_wheelSpec.mAmplitude;
181 :
182 0 : PitchWheelRenderer::PitchWheelFunction func;
183 0 : func.mStartTick = onTime;
184 0 : func.mEndTick = offTime;
185 :
186 0 : auto linearFunc = [startTick = onTime, endTick = offTime, pitchDelta, scale] (uint32_t tick) {
187 0 : float x = (float)(tick - startTick) / (endTick - startTick);
188 0 : return pitchDelta * x * scale;
189 0 : };
190 0 : func.func = linearFunc;
191 :
192 0 : pitchWheelRenderer.addPitchWheelFunction(func, channel, staffIdx, effect);
193 0 : }
194 :
195 0 : static Fraction getPlayTicksForBend(const Note* note)
196 : {
197 0 : Tie* tie = note->tieFor();
198 0 : if (!tie || !tie->endNote()) {
199 0 : return note->chord()->actualTicks();
200 : }
201 :
202 0 : Fraction stick = note->chord()->tick();
203 0 : Note* nextNote = tie->endNote();
204 0 : while (tie && tie->endNote()) {
205 0 : nextNote = tie->endNote();
206 0 : for (EngravingItem* e : nextNote->el()) {
207 0 : if (e && (e->isBend())) {
208 0 : return nextNote->chord()->tick() - stick;
209 : }
210 : }
211 :
212 0 : tie = nextNote->tieFor();
213 : }
214 :
215 0 : return nextNote->chord()->endTick() - stick;
216 : }
217 :
218 : //---------------------------------------------------------
219 : // playNote
220 : //---------------------------------------------------------
221 503 : static void playNote(EventsHolder& events, const Note* note, PlayNoteParams params, PitchWheelRenderer& pitchWheelRenderer)
222 : {
223 503 : if (!note->play()) {
224 0 : return;
225 : }
226 :
227 503 : if (note->userVelocity() != 0) {
228 0 : params.velo = note->customizeVelocity(params.velo);
229 : }
230 :
231 503 : if (params.callAllSoundOff && params.onTime != 0) {
232 50 : NPlayEvent ev1(ME_CONTROLLER, params.channel, CTRL_ALL_NOTES_OFF, 0);
233 50 : ev1.setEffect(params.effect);
234 50 : events[params.channel].emplace(params.onTime - 1, ev1);
235 : }
236 :
237 503 : NPlayEvent ev(ME_NOTEON, params.channel, params.pitch, params.velo);
238 503 : ev.setOriginatingStaff(params.staffIdx);
239 503 : ev.setTuning(note->tuning());
240 503 : ev.setNote(note);
241 503 : ev.setEffect(params.effect);
242 503 : if (params.offTime > 0 && params.offTime < params.onTime) {
243 0 : return;
244 : }
245 :
246 503 : events[params.channel].emplace(std::max(0, params.onTime - params.offset), ev);
247 503 : Accidental* acc = note->accidental();
248 503 : if (acc) {
249 37 : AccidentalType type = acc->accidentalType();
250 37 : double cents = Accidental::subtype2centOffset(type);
251 37 : if (!muse::RealIsNull(cents)) {
252 0 : double pwValue = cents / 100.0 * (double)g_wheelSpec.mLimit / (double)g_wheelSpec.mAmplitude;
253 0 : PitchWheelRenderer::PitchWheelFunction func;
254 0 : func.mStartTick = params.onTime - params.offset;
255 0 : func.mEndTick = params.offTime - params.offset;
256 0 : auto microtonalPW = [pwValue](uint32_t tick) {
257 : UNUSED(tick);
258 0 : return static_cast<int>(std::round(pwValue));
259 0 : };
260 0 : func.func = microtonalPW;
261 0 : pitchWheelRenderer.addPitchWheelFunction(func, params.channel, params.staffIdx, MidiInstrumentEffect::NONE);
262 0 : }
263 : }
264 : // adds portamento for continuous glissando
265 556 : for (Spanner* spanner : note->spannerFor()) {
266 53 : if (spanner->isGlissando()) {
267 19 : Glissando* glissando = toGlissando(spanner);
268 19 : if (glissando->glissandoStyle() == GlissandoStyle::PORTAMENTO) {
269 0 : Note* nextNote = toNote(spanner->endElement());
270 0 : double pitchDelta = nextNote->ppitch() - params.pitch;
271 0 : int timeDelta = params.offTime - params.onTime;
272 0 : if (pitchDelta != 0 && timeDelta != 0) {
273 0 : collectGlissando(params.channel, params.effect, params.onTime, params.offTime, pitchDelta, pitchWheelRenderer,
274 : glissando->staffIdx());
275 : }
276 : }
277 : }
278 : }
279 :
280 503 : ev.setVelo(0);
281 503 : if (params.offTime != -1) {
282 503 : events[params.channel].emplace(std::max(0, params.offTime - params.offset), ev);
283 : }
284 : }
285 :
286 4 : static void collectVibrato(int channel,
287 : int onTime, int offTime,
288 : const VibratoParams& vibratoParams,
289 : PitchWheelRenderer& pitchWheelRenderer, MidiInstrumentEffect effect, staff_idx_t staffIdx)
290 : {
291 4 : const uint16_t vibratoPeriod = vibratoParams.period;
292 4 : const uint32_t duration = offTime - onTime;
293 4 : const float scale = 2 * (float)g_wheelSpec.mLimit / g_wheelSpec.mAmplitude / 100;
294 :
295 4 : if (duration < vibratoPeriod) {
296 0 : return;
297 : }
298 :
299 4 : const int pillarAmplitude = (vibratoParams.highPitch - vibratoParams.lowPitch);
300 :
301 4 : PitchWheelRenderer::PitchWheelFunction func;
302 4 : func.mStartTick = onTime;
303 4 : func.mEndTick = offTime - duration % vibratoPeriod;//removed last points to make more smooth of the end
304 :
305 4 : int lowPitch = vibratoParams.lowPitch;
306 156 : auto vibratoFunc = [startTick = onTime, pillarAmplitude, vibratoPeriod, lowPitch, scale] (uint32_t tick) {
307 156 : float x = (float)(tick - startTick) / vibratoPeriod;
308 156 : return (pillarAmplitude * 2 / M_PI * asin(sin(2 * M_PI * x)) + lowPitch) * scale;
309 4 : };
310 4 : func.func = vibratoFunc;
311 :
312 4 : pitchWheelRenderer.addPitchWheelFunction(func, channel, staffIdx, effect);
313 4 : }
314 :
315 39 : static void addConstPitchWheel(int startTick, int endTick, float value, PitchWheelRenderer& pitchWheelRenderer, int channel,
316 : staff_idx_t staffIdx,
317 : MidiInstrumentEffect effect)
318 : {
319 39 : const float scale = (float)g_wheelSpec.mLimit / g_wheelSpec.mAmplitude;
320 :
321 39 : PitchWheelRenderer::PitchWheelFunction pitchWheelConstFunc;
322 654 : auto constFunc = [value, scale] (uint32_t tick) {
323 : UNUSED(tick)
324 654 : return value * scale;
325 39 : };
326 :
327 39 : pitchWheelConstFunc.func = constFunc;
328 39 : pitchWheelConstFunc.mStartTick = startTick;
329 39 : pitchWheelConstFunc.mEndTick = endTick;
330 39 : pitchWheelRenderer.addPitchWheelFunction(pitchWheelConstFunc, channel, staffIdx, effect);
331 39 : }
332 :
333 50 : static bool shouldProceedBend(const Note* note)
334 : {
335 50 : const GuitarBend* bendFor = note->bendFor();
336 :
337 50 : if (bendFor->bendType() == GuitarBendType::PRE_DIVE) {
338 4 : return true;
339 : }
340 :
341 46 : const Note* baseNote = bendFor->startNoteOfChain();
342 :
343 46 : const GuitarBend* firstBend = baseNote->bendFor();
344 46 : if (firstBend && firstBend->bendType() == GuitarBendType::PRE_BEND) {
345 3 : const Note* nextNote = firstBend->endNote();
346 3 : if (nextNote) {
347 3 : baseNote = nextNote;
348 : }
349 : }
350 :
351 46 : return baseNote->lastTiedNote(false) == note;
352 : }
353 :
354 46 : static BendPlaybackInfo getBendPlaybackInfo(const GuitarBend* bend, int bendStart, int bendDuration, bool graceBeforeBend)
355 : {
356 46 : BendPlaybackInfo bendInfo;
357 :
358 : // currently ignoring diagram for "grace before" bends
359 46 : if (!graceBeforeBend) {
360 43 : bendInfo.startTimeFactor = bend->startTimeFactor();
361 43 : bendInfo.endTimeFactor = bend->endTimeFactor();
362 : }
363 :
364 46 : bendInfo.startTick = bendStart + bendDuration * bendInfo.startTimeFactor;
365 46 : bendInfo.endTick = bendStart + bendDuration * bendInfo.endTimeFactor;
366 :
367 46 : return bendInfo;
368 : }
369 :
370 58 : static void fillBendDurations(const Note* bendStartNote, const std::unordered_set<const Note*>& currentNotes,
371 : std::unordered_map<const Note*, int>& durations)
372 : {
373 58 : if (!bendStartNote || currentNotes.empty()) {
374 43 : return;
375 : }
376 :
377 15 : int eachBendDuration = bendStartNote->chord()->actualTicks().ticks() / static_cast<int>(currentNotes.size());
378 :
379 35 : for (const Note* note : currentNotes) {
380 20 : durations.insert({ note, eachBendDuration });
381 : }
382 : }
383 :
384 33 : static std::unordered_map<const Note*, int> getGraceNoteBendDurations(const Note* note)
385 : {
386 33 : std::unordered_map<const Note*, int> durations;
387 33 : const Note* bendStartNote = nullptr;
388 33 : std::unordered_set<const Note*> currentNotes;
389 :
390 33 : if (note->bendFor() && note->bendFor()->bendType() == GuitarBendType::SLIGHT_BEND) {
391 3 : return {};
392 : }
393 :
394 30 : while (note->tieFor()) {
395 0 : const Tie* tieFor = note->tieFor();
396 0 : IF_ASSERT_FAILED(tieFor->endNote()) {
397 0 : LOGE() << "cannot find tied note for note on track " << note->track() << ", tick " << note->tick().ticks();
398 0 : return {};
399 : }
400 0 : note = tieFor->endNote();
401 : }
402 :
403 81 : while (note->bendFor() || note->tieForNonPartial()) {
404 51 : const GuitarBend* bendFor = note->bendFor();
405 51 : if (!bendFor) {
406 4 : const Tie* tieFor = note->tieForNonPartial();
407 4 : if (tieFor && tieFor->endNote() && tieFor->endNote() != note) {
408 4 : note = tieFor->endNote();
409 4 : continue;
410 : }
411 0 : break;
412 : }
413 :
414 47 : const Note* endNote = bendFor->endNote();
415 47 : if (!endNote || note == endNote) {
416 0 : LOGE() << "cannot find end bend note for note on track " << note->track() << ", tick " << note->tick().ticks();
417 0 : return {};
418 : }
419 :
420 47 : if (endNote->chord()->isGraceAfter()) {
421 19 : if (currentNotes.empty()) {
422 15 : IF_ASSERT_FAILED(note->chord() == endNote->chord()->ownershipParent()) {
423 0 : LOGE() << "error in filling bends midi data for note on track " << note->track() << ", tick " << note->tick().ticks();
424 0 : return {};
425 : }
426 15 : bendStartNote = note;
427 15 : currentNotes.insert(bendStartNote);
428 : }
429 :
430 19 : if (endNote->bendFor()) {
431 5 : currentNotes.insert(endNote);
432 : }
433 : } else {
434 28 : fillBendDurations(bendStartNote, currentNotes, durations);
435 28 : bendStartNote = nullptr;
436 28 : currentNotes.clear();
437 : }
438 :
439 47 : note = bendFor->endNote();
440 : }
441 :
442 30 : fillBendDurations(bendStartNote, currentNotes, durations);
443 :
444 30 : return durations;
445 33 : }
446 :
447 : /*
448 : * All consecutive tie and bend combinations are processed in a single pass, adding pitch bends where needed to ensure continuity between notes.
449 : *
450 : * When processing the first bend in a series, the duration of any preceding ties is also included,
451 : * allowing for an accurate total duration calculation.
452 : *
453 : * Additional calls (for notes that have already been processed) are filtered out by the function shouldProceedBend(Note*),
454 : * preventing redundant processing.
455 : */
456 50 : static void collectGuitarBend(const Note* note,
457 : int channel,
458 : int onTime, int graceOffset, int previousChordTicks,
459 : PitchWheelRenderer& pitchWheelRenderer, MidiInstrumentEffect effect)
460 : {
461 50 : if (!shouldProceedBend(note)) {
462 17 : return;
463 : }
464 :
465 33 : const auto& graceNoteBendDurations = getGraceNoteBendDurations(note);
466 :
467 33 : int curPitchBendSegmentStart = onTime;
468 :
469 33 : int quarterOffsetFromStartNote = 0;
470 33 : int currentQuarterTones = 0;
471 :
472 33 : if (note->bendFor()->bendType() == GuitarBendType::GRACE_NOTE_BEND) {
473 3 : curPitchBendSegmentStart -= graceOffset;
474 : }
475 :
476 33 : const float scale = (float)g_wheelSpec.mLimit / g_wheelSpec.mAmplitude;
477 :
478 84 : while (note->bendFor() || note->tieFor()) {
479 54 : GuitarBend* bendFor = note->bendFor();
480 54 : int duration = note->chord()->actualTicks().ticks();
481 54 : if (bendFor) {
482 50 : const Note* endNote = bendFor->endNote();
483 50 : if (!endNote) {
484 0 : break;
485 : }
486 :
487 50 : bool graceBeforeBend = false;
488 50 : if (note->chord()->isGraceBefore() && bendFor) {
489 7 : if (endNote->noteType() == NoteType::NORMAL) {
490 7 : duration = (previousChordTicks == -1) ? GRACE_BEND_DURATION : std::min(previousChordTicks / 2, GRACE_BEND_DURATION);
491 7 : graceBeforeBend = true;
492 : }
493 43 : } else if (muse::contains(graceNoteBendDurations, note)) {
494 20 : duration = graceNoteBendDurations.at(note);
495 : }
496 :
497 50 : bendFor->computeBendAmount();
498 50 : currentQuarterTones = bendFor->bendAmountInQuarterTones();
499 :
500 50 : if (bendFor->bendType() == GuitarBendType::PRE_DIVE) {
501 4 : quarterOffsetFromStartNote += currentQuarterTones;
502 4 : duration = 0;
503 : } else {
504 46 : BendPlaybackInfo bendPlaybackInfo = getBendPlaybackInfo(bendFor, curPitchBendSegmentStart, duration, graceBeforeBend);
505 46 : double initialPitchBendValue = quarterOffsetFromStartNote / 2.0;
506 :
507 46 : if (bendPlaybackInfo.startTick > curPitchBendSegmentStart && initialPitchBendValue != 0) {
508 1 : addConstPitchWheel(curPitchBendSegmentStart, bendPlaybackInfo.startTick, initialPitchBendValue, pitchWheelRenderer,
509 : channel,
510 : note->staffIdx(), effect);
511 : }
512 :
513 46 : double tickDelta = duration * (bendPlaybackInfo.endTimeFactor - bendPlaybackInfo.startTimeFactor);
514 46 : if (muse::is_zero(tickDelta)) {
515 0 : quarterOffsetFromStartNote += currentQuarterTones;
516 0 : curPitchBendSegmentStart += duration;
517 0 : note = endNote;
518 0 : continue;
519 : }
520 46 : double a = currentQuarterTones / 2.0 / (tickDelta * tickDelta);
521 46 : double b = initialPitchBendValue;
522 697 : auto bendFunc = [startTick = bendPlaybackInfo.startTick, scale, a, b] (uint32_t tick) {
523 651 : float x = (float)(tick - startTick);
524 651 : float y = a * x * x + b;
525 651 : return y * scale;
526 46 : };
527 :
528 46 : PitchWheelRenderer::PitchWheelFunction pitchWheelSquareFunc;
529 :
530 46 : pitchWheelSquareFunc.func = bendFunc;
531 :
532 46 : pitchWheelSquareFunc.mStartTick = bendPlaybackInfo.startTick;
533 46 : pitchWheelSquareFunc.mEndTick = bendPlaybackInfo.endTick;
534 :
535 46 : pitchWheelRenderer.addPitchWheelFunction(pitchWheelSquareFunc, channel, note->staffIdx(), effect);
536 46 : quarterOffsetFromStartNote += currentQuarterTones;
537 :
538 46 : const int curPitchBendSegmentEnd = curPitchBendSegmentStart + duration;
539 46 : if (bendPlaybackInfo.endTick < curPitchBendSegmentEnd) {
540 20 : int constPitchWheelduration
541 20 : = (quarterOffsetFromStartNote == 0 ? g_wheelSpec.mStep : curPitchBendSegmentEnd - bendPlaybackInfo.endTick);
542 20 : addConstPitchWheel(bendPlaybackInfo.endTick, bendPlaybackInfo.endTick + constPitchWheelduration,
543 20 : quarterOffsetFromStartNote / 2.0, pitchWheelRenderer, channel,
544 : note->staffIdx(),
545 : effect);
546 : }
547 46 : }
548 :
549 50 : if (note == endNote) {
550 3 : break;
551 : }
552 :
553 47 : note = endNote;
554 : } else {
555 4 : if (!note->isGrace() && note->bendBack()) {
556 4 : int constPitchWheelduration = 0;
557 4 : int noteTick = note->tick().ticks();
558 4 : if (quarterOffsetFromStartNote == 0) {
559 : // reset pitchwheel once, no need to keep in for each tick
560 1 : constPitchWheelduration = g_wheelSpec.mStep;
561 : } else {
562 3 : Note* lastTied = note->lastTiedNote(false);
563 3 : IF_ASSERT_FAILED(lastTied) {
564 0 : LOGE() << "couldn't find tied note for note on track " << note->track() << ", tick " << note->tick().ticks() <<
565 0 : ", guitar bend midi may be incorrect";
566 0 : constPitchWheelduration = note->chord()->actualTicks().ticks();
567 : } else {
568 3 : Chord* lastChord = lastTied->chord();
569 : // keep the last pitchwheel value for the total duration of tied notes
570 3 : constPitchWheelduration = lastChord->tick().ticks() - noteTick
571 3 : + (lastTied->bendFor() ? 0 : lastChord->actualTicks().ticks());
572 : }
573 : }
574 :
575 4 : addConstPitchWheel(noteTick, noteTick + constPitchWheelduration, quarterOffsetFromStartNote / 2.0, pitchWheelRenderer,
576 : channel,
577 : note->staffIdx(), effect);
578 : }
579 :
580 4 : const Tie* tie = note->tieFor();
581 4 : note = tie->endNote();
582 4 : if (!note) {
583 0 : break;
584 : }
585 : }
586 :
587 51 : curPitchBendSegmentStart += duration;
588 : }
589 :
590 : // adding pitch wheel to last note of bend/tie chain, if it's end of bend
591 33 : if (!note->isGrace() && note->bendBack()) {
592 14 : int constPitchWheelduration = (quarterOffsetFromStartNote == 0) ? g_wheelSpec.mStep : note->chord()->actualTicks().ticks();
593 28 : addConstPitchWheel(note->tick().ticks(),
594 28 : note->tick().ticks() + constPitchWheelduration, quarterOffsetFromStartNote / 2.0, pitchWheelRenderer, channel,
595 : note->staffIdx(), effect);
596 : }
597 33 : }
598 :
599 0 : static void collectBend(const PitchValues& playData, staff_idx_t staffIdx,
600 : int channel,
601 : int onTime, int offTime,
602 : PitchWheelRenderer& pitchWheelRenderer, MidiInstrumentEffect effect)
603 : {
604 0 : size_t pitchSize = playData.size();
605 :
606 0 : const float scale = 2 * (float)g_wheelSpec.mLimit / g_wheelSpec.mAmplitude / PitchValue::PITCH_FOR_SEMITONE;
607 0 : uint32_t duration = offTime - onTime;
608 :
609 0 : for (size_t i = 0; i < pitchSize - 1; i++) {
610 0 : PitchValue curValue = playData.at(i);
611 0 : PitchValue nextValue = playData.at(i + 1);
612 :
613 : //! y = a x^2 + b - curve
614 0 : float curTick = (float)curValue.time * duration / PitchValue::MAX_TIME;
615 0 : float nextTick = (float)nextValue.time * duration / PitchValue::MAX_TIME;
616 :
617 0 : float a = (float)(nextValue.pitch - curValue.pitch) / ((curTick - nextTick) * (curTick - nextTick));
618 0 : float b = curValue.pitch;
619 :
620 0 : uint32_t x0 = curValue.time * duration / PitchValue::MAX_TIME;
621 :
622 0 : PitchWheelRenderer::PitchWheelFunction func;
623 0 : func.mStartTick = onTime + x0;
624 0 : uint32_t startTimeNextPoint = nextValue.time * duration / PitchValue::MAX_TIME;
625 0 : func.mEndTick = onTime + startTimeNextPoint;
626 :
627 0 : auto bendFunc = [ startTick = func.mStartTick, scale,
628 : a, b] (uint32_t tick) {
629 0 : float x = (float)(tick - startTick);
630 :
631 0 : float y = a * x * x + b;
632 :
633 0 : return y * scale;
634 0 : };
635 0 : func.func = bendFunc;
636 0 : pitchWheelRenderer.addPitchWheelFunction(func, channel, staffIdx, effect);
637 0 : }
638 0 : PitchWheelRenderer::PitchWheelFunction func;
639 0 : func.mStartTick = onTime + playData.at(pitchSize - 1).time * duration / PitchValue::MAX_TIME;
640 0 : func.mEndTick = offTime;
641 :
642 0 : if (func.mEndTick == func.mStartTick) {
643 0 : return;
644 : }
645 :
646 : //! y = releaseValue linear curve
647 0 : uint32_t releaseValue = playData.at(pitchSize - 1).pitch * scale;
648 0 : auto bendFunc = [releaseValue] (uint32_t tick) {
649 : UNUSED(tick)
650 0 : return releaseValue;
651 0 : };
652 0 : func.func = bendFunc;
653 0 : pitchWheelRenderer.addPitchWheelFunction(func, channel, staffIdx, effect);
654 0 : }
655 :
656 22 : static bool letRingShouldApply(const NoteEvent& event, const Note* note)
657 : {
658 22 : if (note->hasSlideFromNote()) {
659 4 : return false;
660 : }
661 :
662 18 : if (isGlissandoBack(note)) {
663 1 : return true;
664 : }
665 :
666 17 : if (event.slide() || isGlissandoFor(note)) {
667 6 : return false;
668 : }
669 :
670 11 : return true;
671 : }
672 :
673 84 : static void renderSnd(EventsHolder& events, const Chord* chord, int noteChannel, int tickOffset,
674 : const CompatMidiRendererInternal::Context& context)
675 : {
676 84 : Fraction stick = chord->tick();
677 84 : Fraction etick = stick + chord->ticks();
678 84 : const VelocityMap& veloEvents = context.velocitiesByTrack.at(chord->track());
679 84 : const VelocityMap& multEvents = context.velocityMultiplicationsByTrack.at(chord->track());
680 84 : auto changes = veloEvents.changesInRange(stick, etick);
681 84 : auto multChanges = multEvents.changesInRange(stick, etick);
682 :
683 84 : std::map<int, int> velocityMap;
684 185 : for (auto& change : changes) {
685 101 : int lastVal = -1;
686 101 : int endPoint = change.second.ticks();
687 2122 : for (int t = change.first.ticks(); t <= endPoint; t++) {
688 2021 : int velo = veloEvents.val(Fraction::fromTicks(t));
689 2021 : if (velo == lastVal) {
690 1810 : continue;
691 : }
692 211 : lastVal = velo;
693 :
694 211 : velocityMap[t] = velo;
695 : }
696 : }
697 :
698 84 : double CONVERSION_FACTOR = CompatMidiRendererInternal::ARTICULATION_CONV_FACTOR;
699 168 : for (auto& change : multChanges) {
700 : // Ignore fix events: they are available as cached ramp starts
701 : // and considering them ends up with multiplying twice effectively
702 84 : if (change.first == change.second) {
703 84 : continue;
704 : }
705 :
706 0 : int lastVal = CompatMidiRendererInternal::ARTICULATION_CONV_FACTOR;
707 0 : int endPoint = change.second.ticks();
708 0 : int lastVelocity = 0;
709 0 : auto lastValocityIt = velocityMap.upper_bound(change.first.ticks());
710 0 : if (lastValocityIt != velocityMap.end()) {
711 0 : lastVelocity = lastValocityIt->second;
712 0 : } else if (!velocityMap.empty()) {
713 0 : lastVelocity = velocityMap.cbegin()->second;
714 : }
715 :
716 0 : for (int t = change.first.ticks(); t <= endPoint; t++) {
717 0 : int mult = multEvents.val(Fraction::fromTicks(t));
718 0 : if (mult == lastVal || mult == CONVERSION_FACTOR) {
719 0 : continue;
720 : }
721 0 : lastVal = mult;
722 :
723 0 : double realMult = mult / CONVERSION_FACTOR;
724 0 : if (velocityMap.find(t) != velocityMap.end()) {
725 0 : lastVelocity = velocityMap[t];
726 0 : velocityMap[t] *= realMult;
727 : } else {
728 0 : velocityMap[t] = lastVelocity * realMult;
729 : }
730 : }
731 : }
732 :
733 287 : for (auto point = velocityMap.cbegin(); point != velocityMap.cend(); ++point) {
734 : // NOTE:JT if we ever want to use poly aftertouch instead of CC, this is where we want to
735 : // be using it. Instead of ME_CONTROLLER, use ME_POLYAFTER (but duplicate for each note in chord)
736 203 : NPlayEvent event = NPlayEvent(ME_CONTROLLER, noteChannel, context.sndController, std::clamp(point->second, 0, 127));
737 203 : event.setOriginatingStaff(chord->staffIdx());
738 203 : events[noteChannel].insert(std::make_pair(point->first + tickOffset, event));
739 : }
740 84 : }
741 :
742 3 : int graceBendDuration(const Chord* chord)
743 : {
744 3 : int graceDuration = GRACE_BEND_DURATION;
745 3 : if (chord) {
746 1 : graceDuration = std::min(chord->ticks().ticks() / 2, graceDuration);
747 : }
748 :
749 3 : return graceDuration;
750 : }
751 :
752 462 : static int calculateTieLength(const Note* note)
753 : {
754 462 : int tieLen = 0;
755 :
756 462 : const Note* n = note;
757 574 : while (n) {
758 : // Process ties or bends
759 574 : const Tie* tieFor = n->tieForNonPartial();
760 574 : const GuitarBend* bendFor = n->bendFor();
761 :
762 574 : if (tieFor && tieFor->endNote() != n) {
763 35 : n = tieFor->endNote();
764 539 : } else if (bendFor && bendFor->endNote() != n) {
765 77 : bool isPreDive = bendFor->bendType() == GuitarBendType::PRE_DIVE;
766 77 : n = bendFor->endNote();
767 77 : if (isPreDive) {
768 4 : continue; // PRE_DIVE ghost already has parent chord's ticks
769 : }
770 : } else {
771 462 : break;
772 : }
773 :
774 108 : IF_ASSERT_FAILED(n) {
775 0 : break;
776 : }
777 :
778 108 : const NoteEventList& nel = n->playEvents();
779 :
780 108 : if (!nel.empty() && (!n->chord()->isGrace())) {
781 69 : tieLen += nel[0].len() * n->chord()->actualTicks().ticks() / NoteEvent::NOTE_LENGTH;
782 : }
783 : }
784 :
785 462 : return tieLen;
786 : }
787 :
788 : //---------------------------------------------------------
789 : // collectNote
790 : //---------------------------------------------------------
791 :
792 462 : static void collectNote(EventsHolder& events, const Note* note, const CollectNoteParams& noteParams, Staff* staff,
793 : PitchWheelRenderer& pitchWheelRenderer, const CompatMidiRendererInternal::Context& context)
794 : {
795 462 : if (!note->play() || note->hidden()) { // do not play overlapping notes
796 0 : return;
797 : }
798 :
799 462 : Chord* chord = note->chord();
800 462 : const Instrument* instr = chord->part()->instrument(chord->tick());
801 462 : MidiInstrumentEffect noteEffect = noteParams.effect;
802 :
803 462 : int noteChannel = getChannel(instr, note, noteEffect, context);
804 510 : auto midiEffectFromEvent = [](const NoteEvent& event) {
805 510 : if (event.slide()) {
806 88 : return MidiInstrumentEffect::SLIDE;
807 : }
808 :
809 422 : return MidiInstrumentEffect::NONE;
810 : };
811 :
812 462 : int tieLen = calculateTieLength(note);
813 462 : if (chord->isGrace()) {
814 35 : assert(!CompatMidiRendererInternal::graceNotesMerged(chord)); // this function should not be called on a grace note if grace notes are merged
815 35 : chord = toChord(chord->ownershipParent());
816 : }
817 :
818 462 : int ticks = chord->actualTicks().ticks(); // ticks of the actual note
819 : // calculate additional length due to ties forward
820 : // taking NoteEvent length adjustments into account
821 :
822 462 : int tick1 = note->tick().ticks() + noteParams.tickOffset;
823 462 : const GuitarBend* bendFor = note->bendFor();
824 462 : const GuitarBend* bendBack = note->bendBack();
825 :
826 462 : NoteEventList nel = note->playEvents();
827 462 : size_t nels = nel.size();
828 1012 : for (size_t i = 0; i < nels; ++i) {
829 566 : const NoteEvent& e = nel[i]; // we make an explicit const ref, not a const copy. no need to copy as we won't change the original object.
830 :
831 : // skip if note has a tie into it and only one NoteEvent
832 : // its length was already added to previous note
833 : // if we wish to suppress first note of ornament
834 : // then change "nels == 1" to "i == 0", and change "break" to "continue"
835 566 : if (note->tieBack() && nels == 1 && !isGlissandoFor(note)) {
836 16 : Note* tiedBack = note->tieBack()->startNote();
837 16 : if (!tiedBack) {
838 16 : break;
839 : }
840 :
841 16 : const auto& eventsList = tiedBack->playEvents();
842 16 : IF_ASSERT_FAILED(!eventsList.empty()) {
843 0 : LOGE() << "play events are empty for note on track " << tiedBack->track() << ", tick " << tiedBack->tick().ticks();
844 0 : break;
845 : }
846 :
847 16 : if (noteEffect == MidiInstrumentEffect::NONE) {
848 15 : noteEffect = midiEffectFromEvent(eventsList.front());
849 15 : noteChannel = getChannel(instr, note, noteEffect, context);
850 : }
851 :
852 16 : break;
853 : }
854 :
855 : // skipping the notes which are connected by bends
856 550 : if (bendBack && bendBack->bendType() != GuitarBendType::PRE_BEND && i == 0) {
857 47 : continue;
858 : }
859 :
860 503 : int p = std::clamp(note->ppitch() + e.pitch() + note->harmonicPitchOffset(), 0, 127);
861 503 : int on = tick1 + (ticks * e.ontime()) / 1000;
862 503 : int off = on + (ticks * e.len()) / 1000 - 1;
863 :
864 503 : if (note->deadNote()) {
865 0 : const double ticksPerSecond = chord->score()->multipliedTempo(chord->tick()).val * Constants::DIVISION;
866 0 : constexpr double deadNoteDurationInSec = 0.05;
867 0 : const double deadNoteDurationInTicks = ticksPerSecond * deadNoteDurationInSec;
868 0 : if (off - on > deadNoteDurationInTicks) {
869 0 : off = on + deadNoteDurationInTicks;
870 : }
871 : } else {
872 503 : if ((note->tieFor() || bendFor) && i == nels - 1) {
873 37 : off += tieLen;
874 : }
875 :
876 503 : if (noteParams.letRingNote && letRingShouldApply(e, note)) {
877 12 : off = std::max(off, noteParams.endLetRingTick);
878 12 : if (off - on > LET_RING_MAX_TICKS) {
879 1 : off = on + LET_RING_MAX_TICKS;
880 : }
881 : }
882 : }
883 :
884 : // Get the velocity used for this note from the staff
885 : // This allows correct playback of tremolos even without SND enabled.
886 503 : Fraction nonUnwoundTick = Fraction::fromTicks(on - noteParams.tickOffset);
887 503 : int velo = context.velocitiesByTrack.at(note->track()).val(nonUnwoundTick) * noteParams.velocityMultiplier * e.velocityMultiplier();
888 503 : if (e.play()) {
889 503 : PlayNoteParams playParams;
890 503 : MidiInstrumentEffect eventEffect = noteEffect;
891 503 : int eventChannel = noteChannel;
892 503 : playParams.pitch = p;
893 503 : playParams.velo = std::clamp(velo, 1, 127);
894 503 : playParams.onTime = std::max(0, on - noteParams.graceOffsetOn);
895 503 : playParams.offTime = std::max(0, off - noteParams.graceOffsetOff);
896 :
897 503 : if (eventEffect == MidiInstrumentEffect::NONE) {
898 495 : eventEffect = midiEffectFromEvent(e);
899 495 : eventChannel = getChannel(instr, note, eventEffect, context);
900 : }
901 :
902 503 : playParams.effect = eventEffect;
903 503 : playParams.channel = eventChannel;
904 :
905 503 : if (noteParams.graceOffsetOn == 0) {
906 496 : playParams.offset = ticks * e.offset() / 1000;
907 : }
908 :
909 503 : playParams.staffIdx = static_cast<int>(staff->idx());
910 503 : playParams.callAllSoundOff = noteParams.callAllSoundOff;
911 503 : playNote(events, note, playParams, pitchWheelRenderer);
912 :
913 503 : if (instr->singleNoteDynamics()) {
914 84 : renderSnd(events, chord, noteChannel, noteParams.tickOffset, context);
915 : }
916 : }
917 : }
918 :
919 : // Bends
920 462 : if (bendFor) {
921 50 : collectGuitarBend(note, noteChannel, tick1, noteParams.graceOffsetOn, noteParams.previousChordTicks, pitchWheelRenderer,
922 : noteEffect);
923 : } else {
924 : // old bends implementation
925 412 : for (const EngravingItem* e : note->el()) {
926 0 : if (!e || (!e->isBend())) {
927 0 : continue;
928 : }
929 :
930 0 : const Bend* bend = toBend(e);
931 0 : if (!bend->playBend()) {
932 0 : break;
933 : }
934 :
935 0 : collectBend(bend->points(), bend->staffIdx(), noteChannel, tick1, tick1 + getPlayTicksForBend(
936 0 : note).ticks(), pitchWheelRenderer, noteEffect);
937 : }
938 : }
939 462 : }
940 :
941 : //---------------------------------------------------------
942 : // aeolusSetStop
943 : //---------------------------------------------------------
944 :
945 0 : static void aeolusSetStop(int tick, int channel, int i, int k, bool val, EventsHolder& events)
946 : {
947 0 : NPlayEvent event;
948 0 : event.setType(ME_CONTROLLER);
949 0 : event.setController(98);
950 0 : if (val) {
951 0 : event.setValue(0x40 + 0x20 + i);
952 : } else {
953 0 : event.setValue(0x40 + 0x10 + i);
954 : }
955 :
956 0 : event.setChannel(static_cast<uint8_t>(channel));
957 0 : events[channel].emplace(tick, event);
958 :
959 0 : event.setValue(k);
960 0 : events[channel].emplace(tick, event);
961 0 : }
962 :
963 : //---------------------------------------------------------
964 : // collectProgramChanges
965 : //---------------------------------------------------------
966 :
967 359 : static void collectProgramChanges(EventsHolder& events, Measure const* m, const Staff* staff, int tickOffset)
968 : {
969 359 : int firstStaffIdx = static_cast<int>(staff->idx());
970 359 : int nextStaffIdx = firstStaffIdx + 1;
971 :
972 : //
973 : // collect program changes and controller
974 : //
975 1188 : for (Segment* s = m->first(SegmentType::ChordRest); s; s = s->next(SegmentType::ChordRest)) {
976 1058 : for (EngravingItem* e : s->annotations()) {
977 233 : if (!e->isStaffTextBase() || static_cast<int>(e->staffIdx()) < firstStaffIdx
978 233 : || static_cast<int>(e->staffIdx()) >= nextStaffIdx) {
979 225 : continue;
980 : }
981 4 : const StaffTextBase* st1 = toStaffTextBase(e);
982 4 : Fraction tick = s->tick() + Fraction::fromTicks(tickOffset);
983 :
984 4 : Instrument* instr = e->part()->instrument(tick);
985 4 : for (const ChannelActions& ca : st1->channelActions()) {
986 0 : int channel = instr->channel().at(ca.channel)->channel();
987 0 : for (const String& ma : ca.midiActionNames) {
988 0 : NamedEventList* nel = instr->midiAction(ma, ca.channel);
989 0 : if (!nel) {
990 0 : continue;
991 : }
992 0 : for (MidiCoreEvent event : nel->events) {
993 0 : event.setChannel(channel);
994 0 : NPlayEvent e1(event);
995 0 : e1.setOriginatingStaff(firstStaffIdx);
996 0 : if (e1.dataA() == CTRL_PROGRAM) {
997 0 : events[channel].emplace(tick.ticks() - 1, e1);
998 : } else {
999 0 : events[channel].emplace(tick.ticks(), e1);
1000 : }
1001 : }
1002 : }
1003 : }
1004 4 : if (st1->setAeolusStops()) {
1005 0 : Staff* s1 = st1->staff();
1006 0 : int voice = 0;
1007 0 : int channel = s1->channel(tick, voice);
1008 :
1009 0 : for (int i = 0; i < 4; ++i) {
1010 : static int num[4] = { 12, 13, 16, 16 };
1011 0 : for (int k = 0; k < num[i]; ++k) {
1012 0 : aeolusSetStop(tick.ticks(), channel, i, k, st1->getAeolusStop(i, k), events);
1013 : }
1014 : }
1015 : }
1016 : }
1017 : }
1018 359 : }
1019 :
1020 : //---------------------------------------------------------
1021 : // renderHarmony
1022 : /// renders chord symbols
1023 : //---------------------------------------------------------
1024 0 : static void renderHarmony(EventsHolder& events, Measure const* m, Harmony* h, int tickOffset,
1025 : const CompatMidiRendererInternal::Context& context)
1026 : {
1027 0 : if (!h->isRealizable() || context.harmonyChannelSetting == CompatMidiRendererInternal::HarmonyChannelSetting::DISABLED) {
1028 0 : return;
1029 : }
1030 :
1031 0 : if (context.partsWithMutedHarmony.find(h->part()->id().toStdString()) != context.partsWithMutedHarmony.end()) {
1032 0 : return;
1033 : }
1034 :
1035 0 : Staff* staff = m->score()->staff(h->track() / VOICES);
1036 0 : const InstrChannel* instrChannel = staff->part()->harmonyChannel();
1037 0 : IF_ASSERT_FAILED(instrChannel) {
1038 0 : LOGE() << "channel for harmony isn't found for part " << staff->part()->partName();
1039 0 : return;
1040 : }
1041 :
1042 0 : if (!staff->isPrimaryStaff()) {
1043 0 : return;
1044 : }
1045 :
1046 0 : int channel = instrChannel->channel();
1047 0 : int staffIdx = static_cast<int>(staff->idx());
1048 :
1049 0 : if (context.harmonyChannelSetting == CompatMidiRendererInternal::HarmonyChannelSetting::LOOKUP) {
1050 0 : CompatMidiRendererInternal::ChannelLookup::LookupData lookupData;
1051 0 : lookupData.harmony = true;
1052 0 : channel = context.channels->getChannel(channel, lookupData);
1053 : }
1054 :
1055 0 : int velocity = context.velocitiesByTrack.at(h->track()).val(h->tick());
1056 :
1057 0 : const RealizedHarmony& r = h->getRealizedHarmony();
1058 :
1059 0 : NPlayEvent ev(ME_NOTEON, static_cast<uint8_t>(channel), 0, velocity);
1060 0 : ev.setHarmony(h);
1061 0 : Fraction duration = r.getActualDuration(h->tick().ticks() + tickOffset);
1062 :
1063 0 : int onTime = h->tick().ticks() + tickOffset;
1064 0 : int offTime = onTime + duration.ticks();
1065 :
1066 0 : ev.setOriginatingStaff(staffIdx);
1067 0 : ev.setTuning(0.0);
1068 :
1069 : //add play events
1070 0 : const RealizedHarmony::PitchMap& notes = r.notes();
1071 0 : for (const auto& [pitch, _] : notes) {
1072 0 : ev.setPitch(pitch);
1073 0 : ev.setVelo(velocity);
1074 0 : events[channel].emplace(onTime, ev);
1075 0 : ev.setVelo(0);
1076 0 : events[channel].emplace(offTime, ev);
1077 : }
1078 : }
1079 :
1080 399 : void CompatMidiRendererInternal::collectGraceBeforeChordEvents(Chord* chord, Chord* prevChord, EventsHolder& events, double veloMultiplier,
1081 : Staff* st,
1082 : int tickOffset,
1083 : PitchWheelRenderer& pitchWheelRenderer, MidiInstrumentEffect effect)
1084 : {
1085 : // calculate offset for grace notes here
1086 399 : const std::vector<Chord*> grChords = chord->graceNotesBefore(); // create copy
1087 399 : std::vector<Chord*> graceNotesBeforeBar;
1088 399 : std::copy_if(grChords.begin(), grChords.end(), std::back_inserter(graceNotesBeforeBar), [](Chord* ch) {
1089 20 : return ch->noteType() == NoteType::ACCIACCATURA;
1090 : });
1091 :
1092 399 : int graceTickSum = 0;
1093 399 : int graceTickOffset = 0;
1094 :
1095 399 : size_t acciacaturaGraceSize = graceNotesBeforeBar.size();
1096 399 : if (acciacaturaGraceSize > 0) {
1097 3 : graceTickSum = graceNotesBeforeBar[0]->ticks().ticks();
1098 3 : if (prevChord) {
1099 3 : graceTickSum = std::min(prevChord->ticks().ticks() / 2, graceTickSum);
1100 : }
1101 :
1102 3 : graceTickOffset = graceTickSum / static_cast<int>(acciacaturaGraceSize);
1103 : } else {
1104 396 : bool hasGraceBend = std::any_of(grChords.begin(), grChords.end(), [](Chord* ch) {
1105 16 : return std::any_of(ch->notes().begin(), ch->notes().end(), [](Note* n) {
1106 16 : return n->isGraceBendStart();
1107 16 : });
1108 : });
1109 :
1110 396 : if (hasGraceBend) {
1111 3 : graceTickSum = graceBendDuration(prevChord);
1112 : }
1113 : }
1114 :
1115 399 : if (!graceNotesMerged(chord)) {
1116 398 : int currentBeaforeBeatNote = 0;
1117 418 : for (Chord* c : grChords) {
1118 40 : for (const Note* note : c->notes()) {
1119 20 : GuitarBend* bendFor = note->bendFor();
1120 20 : if (bendFor && bendFor->bendType() == GuitarBendType::PRE_BEND) {
1121 5 : continue;
1122 : }
1123 :
1124 15 : CollectNoteParams params;
1125 15 : params.effect = effect;
1126 15 : params.velocityMultiplier = veloMultiplier;
1127 15 : params.tickOffset = tickOffset;
1128 :
1129 15 : bool isGraceBend = (note->bendFor() && note->bendFor()->bendType() == GuitarBendType::GRACE_NOTE_BEND);
1130 15 : if (prevChord) {
1131 8 : params.previousChordTicks = prevChord->actualTicks().ticks();
1132 : }
1133 :
1134 15 : if (note->noteType() == NoteType::ACCIACCATURA) {
1135 4 : params.graceOffsetOn = graceTickSum - graceTickOffset * currentBeaforeBeatNote;
1136 4 : params.graceOffsetOff = graceTickSum - graceTickOffset * (currentBeaforeBeatNote + 1);
1137 :
1138 4 : collectNote(events, note, params, st, pitchWheelRenderer, m_context);
1139 11 : } else if (isGraceBend) {
1140 3 : params.graceOffsetOn = graceTickSum;
1141 3 : params.graceOffsetOff = 0;
1142 :
1143 3 : collectNote(events, note, params, st, pitchWheelRenderer, m_context);
1144 : } else {
1145 8 : collectNote(events, note, params, st, pitchWheelRenderer, m_context);
1146 : }
1147 : }
1148 :
1149 20 : currentBeaforeBeatNote++;
1150 : }
1151 : }
1152 399 : }
1153 :
1154 399 : bool shouldPlayHammerOn(const Chord* chord)
1155 : {
1156 399 : int currentTick = chord->tick().ticks();
1157 399 : Chord* firstTiedChord = chord->nextTiedChord(true, false);
1158 399 : const Score* score = chord->score();
1159 427 : while (firstTiedChord) {
1160 28 : currentTick = firstTiedChord->tick().ticks();
1161 28 : firstTiedChord = firstTiedChord->nextTiedChord(true, false);
1162 : }
1163 :
1164 568 : for (auto it : score->spannerMap().findOverlapping(currentTick, currentTick + chord->ticks().ticks())) {
1165 176 : Spanner* spanner = it.value;
1166 176 : if (spanner->track() != chord->track()) {
1167 49 : continue;
1168 : }
1169 :
1170 127 : if (spanner->isHammerOnPullOff() && (spanner->startChord() != chord)) {
1171 7 : return true;
1172 : }
1173 : }
1174 :
1175 392 : return false;
1176 : }
1177 :
1178 399 : CompatMidiRendererInternal::ChordParams CompatMidiRendererInternal::collectChordParams(const Chord* chord, int tickOffset) const
1179 : {
1180 399 : ChordParams chordParams;
1181 :
1182 399 : int currentTick = chord->tick().ticks();
1183 524 : for (auto it : score->spannerMap().findOverlapping(currentTick + 1, currentTick + 2)) {
1184 125 : Spanner* spanner = it.value;
1185 125 : if (spanner->track() != chord->track()) {
1186 32 : continue;
1187 : }
1188 :
1189 93 : if (spanner->isLetRing()) {
1190 19 : LetRing* letRing = toLetRing(spanner);
1191 19 : chordParams.letRing = true;
1192 19 : ChordRest* endCR = letRing->endCR();
1193 19 : chordParams.endLetRingTick = (endCR ? endCR->endTick().ticks() : letRing->tick2().ticks()) + tickOffset;
1194 74 : } else if (spanner->isPalmMute()) {
1195 4 : chordParams.palmMute = true;
1196 : }
1197 : }
1198 :
1199 399 : chordParams.hammerOnPullOff = shouldPlayHammerOn(chord);
1200 399 : return chordParams;
1201 : }
1202 :
1203 : //---------------------------------------------------------
1204 : // doCollectMeasureEvents
1205 : //---------------------------------------------------------
1206 :
1207 359 : void CompatMidiRendererInternal::doCollectMeasureEvents(EventsHolder& events, Measure const* m, const Staff* staff, int tickOffset,
1208 : PitchWheelRenderer& pitchWheelRenderer, std::array<Chord*, VOICES>& prevChords)
1209 : {
1210 359 : staff_idx_t firstStaffIdx = staff->idx();
1211 22902 : for (Staff* st : staff->masterScore()->staves()) {
1212 22543 : if (staff->id() == st->id()) {
1213 359 : firstStaffIdx = st->idx();
1214 : }
1215 : }
1216 359 : staff_idx_t nextStaffIdx = firstStaffIdx + 1;
1217 :
1218 359 : SegmentType st = SegmentType::ChordRest;
1219 359 : track_idx_t strack = firstStaffIdx * VOICES;
1220 359 : track_idx_t etrack = nextStaffIdx * VOICES;
1221 1188 : for (Segment* seg = m->first(st); seg; seg = seg->next(st)) {
1222 829 : Fraction tick = seg->tick();
1223 :
1224 : //render harmony
1225 1058 : for (EngravingItem* e : seg->annotations()) {
1226 229 : if (!e || (e->track() < strack) || (e->track() >= etrack)) {
1227 75 : continue;
1228 : }
1229 154 : Harmony* h = nullptr;
1230 154 : if (e->isHarmony()) {
1231 0 : h = toHarmony(e);
1232 154 : } else if (e->isFretDiagram()) {
1233 0 : h = toFretDiagram(e)->harmony();
1234 : }
1235 154 : if (!h || !h->play()) {
1236 154 : continue;
1237 : }
1238 0 : renderHarmony(events, m, h, tickOffset, m_context);
1239 : }
1240 :
1241 3773 : for (track_idx_t track = strack; track < etrack; ++track) {
1242 : // Skip linked staves, except primary
1243 2944 : Staff* st1 = m->score()->staff(track / VOICES);
1244 2944 : if (!st1->isPrimaryStaff()) {
1245 124 : track += VOICES - 1;
1246 2545 : continue;
1247 : }
1248 :
1249 2820 : size_t voice = track % VOICES;
1250 2820 : EngravingItem* cr = seg->element(track);
1251 2820 : if (!cr || !cr->isChord()) {
1252 2421 : prevChords[voice] = nullptr;
1253 2421 : continue;
1254 : }
1255 :
1256 399 : Chord* chord = toChord(cr);
1257 399 : double veloMultiplier = NoteEvent::DEFAULT_VELOCITY_MULTIPLIER * chordVelocityMultiplier(chord, m_context);
1258 :
1259 : //
1260 : // Add normal note events
1261 : //
1262 399 : ChordParams chordParams = collectChordParams(chord, tickOffset);
1263 :
1264 399 : MidiInstrumentEffect effect = MidiInstrumentEffect::NONE;
1265 399 : if (chordParams.palmMute) {
1266 4 : effect = MidiInstrumentEffect::PALM_MUTE;
1267 395 : } else if (chordParams.hammerOnPullOff) {
1268 7 : effect = MidiInstrumentEffect::HAMMER_PULL;
1269 : }
1270 :
1271 399 : collectGraceBeforeChordEvents(chord, prevChords[voice], events, veloMultiplier, st1, tickOffset, pitchWheelRenderer, effect);
1272 :
1273 399 : Instrument* instr = st1->part()->instrument(tick);
1274 826 : for (const Note* note : chord->notes()) {
1275 427 : CollectNoteParams params;
1276 427 : params.velocityMultiplier = veloMultiplier;
1277 427 : params.tickOffset = tickOffset;
1278 427 : params.letRingNote = chordParams.letRing;
1279 427 : params.endLetRingTick = chordParams.endLetRingTick;
1280 427 : if (m_context.instrumentsHaveEffects) {
1281 82 : params.effect = effect;
1282 : }
1283 :
1284 427 : if (m_context.eachStringHasChannel && instr->hasStrings()) {
1285 83 : params.callAllSoundOff = true;
1286 : }
1287 :
1288 427 : collectNote(events, note, params, st1, pitchWheelRenderer, m_context);
1289 : }
1290 :
1291 399 : if (!graceNotesMerged(chord)) {
1292 398 : const std::vector<Chord*> graceAfter = chord->graceNotesAfter(); // create copy
1293 418 : for (Chord* c : graceAfter) {
1294 40 : for (const Note* note : c->notes()) {
1295 20 : CollectNoteParams params;
1296 20 : params.velocityMultiplier = veloMultiplier;
1297 20 : params.tickOffset = tickOffset;
1298 20 : params.effect = effect;
1299 20 : collectNote(events, note, params, st1, pitchWheelRenderer, m_context);
1300 : }
1301 : }
1302 398 : }
1303 :
1304 399 : prevChords[voice] = chord;
1305 : }
1306 : }
1307 359 : }
1308 :
1309 79 : CompatMidiRendererInternal::CompatMidiRendererInternal(Score* s)
1310 79 : : score(s)
1311 : {
1312 79 : }
1313 :
1314 : //---------------------------------------------------------
1315 : // collectMeasureEvents
1316 : // redirects to the correct function based on the passed method
1317 : //---------------------------------------------------------
1318 :
1319 359 : void CompatMidiRendererInternal::collectMeasureEvents(EventsHolder& events, Measure const* m, const Staff* staff, int tickOffset,
1320 : PitchWheelRenderer& pitchWheelRenderer, std::array<Chord*, VOICES>& prevChords)
1321 : {
1322 359 : doCollectMeasureEvents(events, m, staff, tickOffset, pitchWheelRenderer, prevChords);
1323 :
1324 359 : collectProgramChanges(events, m, staff, tickOffset);
1325 359 : }
1326 :
1327 : //---------------------------------------------------------
1328 : // renderStaff
1329 : //---------------------------------------------------------
1330 :
1331 260 : void CompatMidiRendererInternal::renderStaff(EventsHolder& events, const Staff* staff, PitchWheelRenderer& pitchWheelRenderer)
1332 : {
1333 260 : Measure const* lastMeasure = nullptr;
1334 :
1335 260 : const RepeatList& repeatList = score->repeatList();
1336 260 : std::array<Chord*, VOICES> prevChords = { nullptr };
1337 :
1338 533 : for (const RepeatSegment* rs : repeatList) {
1339 273 : const int tickOffset = rs->utick - rs->tick;
1340 :
1341 273 : Measure const* const start = rs->firstMeasure();
1342 :
1343 359 : for (Measure const* m = start; m; m = m->nextMeasure()) {
1344 359 : staff_idx_t staffIdx = staff->idx();
1345 359 : if (m->isMeasureRepeatGroup(staffIdx)) {
1346 12 : MeasureRepeat* mr = m->measureRepeatElement(staffIdx);
1347 12 : Measure const* playMeasure = lastMeasure;
1348 12 : if (!playMeasure || !mr) {
1349 0 : continue;
1350 : }
1351 :
1352 20 : for (int i = m->measureRepeatCount(staffIdx); i < mr->numMeasures() && playMeasure->prevMeasure(); ++i) {
1353 8 : playMeasure = playMeasure->prevMeasure();
1354 : }
1355 :
1356 12 : int offset = (m->tick() - playMeasure->tick()).ticks();
1357 12 : collectMeasureEvents(events, playMeasure, staff, tickOffset + offset, pitchWheelRenderer, prevChords);
1358 : } else {
1359 347 : lastMeasure = m;
1360 347 : collectMeasureEvents(events, lastMeasure, staff, tickOffset, pitchWheelRenderer, prevChords);
1361 : }
1362 :
1363 359 : if (m == rs->lastMeasure()) {
1364 273 : break;
1365 : }
1366 : }
1367 : }
1368 260 : }
1369 :
1370 : //---------------------------------------------------------
1371 : // renderSpanners
1372 : //---------------------------------------------------------
1373 :
1374 79 : void CompatMidiRendererInternal::renderSpanners(EventsHolder& events, PitchWheelRenderer& pitchWheelRenderer)
1375 : {
1376 127 : for (const auto& sp : score->spannerMap().map()) {
1377 48 : Spanner* s = sp.second;
1378 :
1379 48 : if (!s->staff()->isPrimaryStaff()) {
1380 14 : continue;
1381 : }
1382 :
1383 34 : int idx = s->staff()->channel(s->tick(), 0);
1384 34 : int channel = s->part()->instrument(s->tick())->channel(idx)->channel();
1385 34 : const auto& channels = m_context.channels->channelsMap[channel];
1386 34 : if (channels.empty()) {
1387 21 : doRenderSpanners(events, s, channel, pitchWheelRenderer, MidiInstrumentEffect::NONE);
1388 : } else {
1389 46 : for (const auto& channel2 : channels) {
1390 33 : doRenderSpanners(events, s, channel2.second, pitchWheelRenderer, channel2.first.effect);
1391 : }
1392 : }
1393 : }
1394 79 : }
1395 :
1396 4 : static std::vector<std::pair<int, int> > collectTicksForEffect(const Score* const score, track_idx_t track, int stick, int etick,
1397 : MidiInstrumentEffect effect)
1398 : {
1399 4 : std::vector<std::pair<int, int> > ticksForEffect;
1400 4 : int curTick = stick;
1401 :
1402 8 : for (auto it : score->spannerMap().findOverlapping(stick, etick)) {
1403 4 : Spanner* spanner = it.value;
1404 :
1405 4 : if (spanner->track() != track) {
1406 0 : continue;
1407 : }
1408 :
1409 4 : if (spanner->isPalmMute()) {
1410 0 : int palmMuteStartTick = spanner->tick().ticks();
1411 0 : int palmMuteEndTick = spanner->tick2().ticks();
1412 :
1413 0 : if (curTick < palmMuteStartTick) {
1414 0 : int nextTick = std::min(palmMuteStartTick, etick);
1415 0 : if (effect == MidiInstrumentEffect::NONE) {
1416 0 : ticksForEffect.push_back({ curTick, nextTick - 1 });
1417 : }
1418 :
1419 0 : curTick = nextTick;
1420 : }
1421 :
1422 0 : if (palmMuteStartTick <= curTick) {
1423 0 : int nextTick = std::min(palmMuteEndTick, etick);
1424 0 : if (effect == MidiInstrumentEffect::PALM_MUTE) {
1425 0 : ticksForEffect.push_back({ curTick, nextTick - 1 });
1426 : }
1427 :
1428 0 : curTick = nextTick;
1429 : }
1430 : }
1431 : }
1432 :
1433 4 : if (curTick < etick && effect == MidiInstrumentEffect::NONE) {
1434 4 : ticksForEffect.push_back({ curTick, etick - 1 });
1435 : }
1436 :
1437 4 : return ticksForEffect;
1438 0 : }
1439 :
1440 4 : static VibratoParams getVibratoParams(VibratoType type)
1441 : {
1442 4 : VibratoParams params;
1443 :
1444 4 : switch (type) {
1445 1 : case VibratoType::GUITAR_VIBRATO:
1446 : // guitar vibrato, up only
1447 1 : params.lowPitch = 0;
1448 1 : params.highPitch = 10;
1449 1 : params.period = Constants::DIVISION / 3;
1450 1 : break;
1451 :
1452 1 : case VibratoType::GUITAR_VIBRATO_WIDE:
1453 1 : params.lowPitch = 0; // 100 is a semitone
1454 1 : params.highPitch = 20;
1455 1 : params.period = Constants::DIVISION / 2.5;
1456 1 : break;
1457 :
1458 0 : case VibratoType::VIBRATO_SAWTOOTH_WIDE:
1459 : // vibrato with whammy bar up and down
1460 0 : params.lowPitch = -25; // 1/16
1461 0 : params.highPitch = 25;
1462 0 : params.period = Constants::DIVISION / 2;
1463 0 : break;
1464 :
1465 2 : case VibratoType::VIBRATO_SAWTOOTH:
1466 2 : params.lowPitch = -12;
1467 2 : params.highPitch = 12;
1468 2 : params.period = Constants::DIVISION / 2;
1469 2 : break;
1470 :
1471 0 : default:
1472 0 : LOGE() << "vibrato type is not handled in midi renderer";
1473 0 : break;
1474 : }
1475 :
1476 4 : return params;
1477 : }
1478 :
1479 54 : void CompatMidiRendererInternal::doRenderSpanners(EventsHolder& events, Spanner* s, uint32_t channel,
1480 : PitchWheelRenderer& pitchWheelRenderer,
1481 : MidiInstrumentEffect effect)
1482 : {
1483 : struct PedalEvent {
1484 : int tick = 0;
1485 : bool on = true;
1486 : int staffIdx = 0;
1487 :
1488 : PedalEvent() = default;
1489 0 : PedalEvent(int tick, bool on, int staffIdx)
1490 0 : : tick(tick), on(on), staffIdx(staffIdx)
1491 : {
1492 0 : }
1493 : };
1494 :
1495 54 : std::vector<PedalEvent> pedalEventList;
1496 :
1497 54 : int staffIdx = static_cast<int>(s->staffIdx());
1498 :
1499 54 : if (s->isPedal()) {
1500 0 : PedalEvent lastEvent;
1501 :
1502 0 : if (!pedalEventList.empty()) {
1503 0 : lastEvent = pedalEventList.back();
1504 : } else {
1505 0 : lastEvent = { 0, true, staffIdx };
1506 : }
1507 :
1508 0 : int st = s->tick().ticks();
1509 :
1510 0 : if (!lastEvent.on && lastEvent.tick >= (st + 2)) {
1511 0 : pedalEventList.emplace(pedalEventList.cend() - 1,
1512 0 : st + (2 - MScore::pedalEventsMinTicks), false, staffIdx);
1513 : }
1514 0 : int a = st + 2;
1515 0 : pedalEventList.emplace_back(a, true, staffIdx);
1516 :
1517 0 : int t = s->tick2().ticks() + (2 - MScore::pedalEventsMinTicks);
1518 0 : if (!score->repeatList().empty()) {
1519 0 : const RepeatSegment& lastRepeat = *score->repeatList().back();
1520 0 : if (t > lastRepeat.utick + lastRepeat.len()) {
1521 0 : t = lastRepeat.utick + lastRepeat.len();
1522 : }
1523 : }
1524 0 : pedalEventList.emplace_back(t, false, staffIdx);
1525 54 : } else if (s->isVibrato()) {
1526 4 : int stick = s->tick().ticks();
1527 4 : int etick = s->tick2().ticks();
1528 :
1529 : // from start to end of trill, send bend events at regular interval
1530 4 : Vibrato* t = toVibrato(s);
1531 4 : VibratoParams vibratoParams = getVibratoParams(t->vibratoType());
1532 :
1533 4 : std::vector<std::pair<int, int> > vibratoTicksForEffect = collectTicksForEffect(score, s->track(), stick, etick, effect);
1534 :
1535 8 : for (const auto& [tickStart, tickEnd] : vibratoTicksForEffect) {
1536 4 : collectVibrato(channel, tickStart, tickEnd, vibratoParams, pitchWheelRenderer, effect, s->staffIdx());
1537 : }
1538 4 : }
1539 :
1540 54 : for (const auto& pe : pedalEventList) {
1541 0 : NPlayEvent event;
1542 0 : if (pe.on) {
1543 0 : event = NPlayEvent(ME_CONTROLLER, static_cast<uint8_t>(channel), CTRL_SUSTAIN, 127);
1544 : } else {
1545 0 : event = NPlayEvent(ME_CONTROLLER, static_cast<uint8_t>(channel), CTRL_SUSTAIN, 0);
1546 : }
1547 0 : event.setOriginatingStaff(pe.staffIdx);
1548 0 : event.setEffect(effect);
1549 0 : events[channel].emplace(pe.tick, event);
1550 : }
1551 54 : }
1552 :
1553 : //---------------------------------------------------------
1554 : // findFirstTrill
1555 : // search the spanners in the score, finding the first one
1556 : // which overlaps this chord and is of type ElementType::TRILL
1557 : //---------------------------------------------------------
1558 :
1559 858 : static Trill* findFirstTrill(Chord* chord)
1560 : {
1561 858 : auto spanners = chord->score()->spannerMap().findOverlapping(1 + chord->tick().ticks(), chord->endTick().ticks() - 1);
1562 1122 : for (auto i : spanners) {
1563 266 : if (!i.value->isTrill()) {
1564 264 : continue;
1565 : }
1566 2 : if (i.value->track() != chord->track()) {
1567 0 : continue;
1568 : }
1569 2 : Trill* trill = toTrill(i.value);
1570 2 : if (!trill->playSpanner()) {
1571 0 : continue;
1572 : }
1573 2 : return trill;
1574 : }
1575 856 : return nullptr;
1576 858 : }
1577 :
1578 79 : void CompatMidiRendererInternal::renderScore(EventsHolder& events, const Context& context, bool expandRepeats)
1579 : {
1580 : UNUSED(expandRepeats);
1581 :
1582 79 : m_context = context;
1583 79 : PitchWheelRenderer pitchWheelRender(g_wheelSpec);
1584 :
1585 79 : score->updateSwing();
1586 79 : score->updateCapo();
1587 :
1588 79 : if (!m_context.useDefaultArticulations) {
1589 79 : fillArticulationsInfo();
1590 : }
1591 :
1592 79 : CompatMidiRender::createPlayEvents(score, score->firstMeasure(), nullptr, m_context);
1593 :
1594 79 : score->updateChannel();
1595 79 : fillScoreVelocities(score, m_context);
1596 :
1597 : // create note & other events
1598 339 : for (const Staff* st : score->staves()) {
1599 260 : renderStaff(events, st, pitchWheelRender);
1600 : }
1601 79 : events.fixupMIDI();
1602 :
1603 : // create sustain pedal events
1604 79 : renderSpanners(events, pitchWheelRender);
1605 :
1606 79 : EventsHolder pitchWheelEvents = pitchWheelRender.renderPitchWheel();
1607 79 : events.mergePitchWheelEvents(pitchWheelEvents);
1608 79 : if (m_context.applyCaesuras) {
1609 13 : m_context.pauseMap->calculate(score);
1610 : }
1611 79 : }
1612 :
1613 79 : void CompatMidiRendererInternal::fillArticulationsInfo()
1614 : {
1615 309 : for (const Part* part : score->parts()) {
1616 460 : for (const auto& [tick, instr] : part->instruments()) {
1617 230 : String instrId = instr->id();
1618 1840 : for (auto it = Context::s_builtInArticulationsValues.cbegin(); it != Context::s_builtInArticulationsValues.cend(); it++) {
1619 1610 : const String& articulationName = it->first;
1620 1610 : const std::vector<MidiArticulation>& instrArticulations = instr->articulation();
1621 : bool instrHasArticulation
1622 1610 : = std::any_of(instrArticulations.begin(),
1623 3220 : instrArticulations.end(), [articulationName](const MidiArticulation& instrArticulation) {
1624 6640 : return instrArticulation.name == articulationName;
1625 : });
1626 :
1627 1610 : if (!instrHasArticulation) {
1628 357 : m_context.articulationsWithoutValuesByInstrument[instrId].insert(articulationName);
1629 : }
1630 : }
1631 230 : }
1632 : }
1633 79 : }
1634 :
1635 728 : double chordVelocityMultiplier(const Chord* chord, const CompatMidiRendererInternal::Context& context)
1636 : {
1637 728 : double veloMultiplier = 1.0;
1638 728 : Instrument* instr = chord->part()->instrument();
1639 730 : for (Articulation* a : chord->articulations()) {
1640 2 : if (a->playArticulation()) {
1641 2 : veloMultiplier *= velocityMultiplierByInstrument(instr, a->articulationName(), context);
1642 : }
1643 : }
1644 :
1645 728 : return veloMultiplier;
1646 : }
1647 :
1648 2 : double velocityMultiplierByInstrument(const Instrument* instrument, const String& articulationName,
1649 : const CompatMidiRendererInternal::Context& context)
1650 : {
1651 : using Ctx = CompatMidiRendererInternal::Context;
1652 2 : if (context.useDefaultArticulations) {
1653 0 : auto it = Ctx::s_builtInArticulationsValues.find(articulationName);
1654 0 : if (it != Ctx::s_builtInArticulationsValues.end()) {
1655 0 : return it->second.velocityMultiplier;
1656 : }
1657 : } else {
1658 2 : auto articulationsForInstrumentIt = context.articulationsWithoutValuesByInstrument.find(instrument->id());
1659 2 : if (articulationsForInstrumentIt != context.articulationsWithoutValuesByInstrument.end()) {
1660 2 : const auto& articulationsForInstrument = articulationsForInstrumentIt->second;
1661 2 : if (articulationsForInstrument.find(articulationName) != articulationsForInstrument.end()) {
1662 2 : return Ctx::s_builtInArticulationsValues[articulationName].velocityMultiplier;
1663 : }
1664 : }
1665 : }
1666 :
1667 0 : return instrument->getVelocityMultiplier(articulationName);
1668 : }
1669 :
1670 : /* static */
1671 972 : uint32_t getChannel(const Instrument* instr, const Note* note, MidiInstrumentEffect effect,
1672 : const CompatMidiRendererInternal::Context& context)
1673 : {
1674 972 : int subchannel = note->subchannel();
1675 972 : int channel = instr->channel(subchannel)->channel();
1676 :
1677 972 : if (!context.instrumentsHaveEffects && !context.eachStringHasChannel) {
1678 782 : return channel;
1679 : }
1680 :
1681 190 : CompatMidiRendererInternal::ChannelLookup::LookupData lookupData;
1682 :
1683 190 : if (context.instrumentsHaveEffects) {
1684 170 : lookupData.effect = effect;
1685 : }
1686 :
1687 190 : if (context.eachStringHasChannel && instr->hasStrings()) {
1688 177 : if (note->string() >= 0) {
1689 127 : lookupData.string = note->string();
1690 : } else {
1691 50 : int string = 0;
1692 50 : int fret = 0;
1693 50 : const StringData* stringData = instr->stringData();
1694 50 : IF_ASSERT_FAILED(stringData && stringData->convertPitch(note->pitch(), note->staff(), &string, &fret)) {
1695 0 : LOGE() << "channel isn't calculated for instrument " << instr->nameAsPlainText();
1696 0 : return channel;
1697 : }
1698 :
1699 50 : lookupData.string = string;
1700 : }
1701 :
1702 177 : lookupData.staffIdx = note->staffIdx();
1703 : }
1704 :
1705 190 : return context.channels->getChannel(channel, lookupData);
1706 : }
1707 :
1708 190 : uint32_t CompatMidiRendererInternal::ChannelLookup::getChannel(uint32_t instrumentChannel, const LookupData& lookupData)
1709 : {
1710 190 : auto& channelsForInstrument = channelsMap[instrumentChannel];
1711 :
1712 190 : auto channelIt = channelsForInstrument.find(lookupData);
1713 190 : if (channelIt != channelsForInstrument.end()) {
1714 140 : return channelIt->second;
1715 : }
1716 :
1717 50 : channelsForInstrument.insert({ lookupData, maxChannel });
1718 50 : return maxChannel++;
1719 : }
1720 :
1721 439 : bool CompatMidiRendererInternal::ChannelLookup::LookupData::operator<(const CompatMidiRendererInternal::ChannelLookup::LookupData& other)
1722 : const
1723 : {
1724 439 : if (harmony && !other.harmony) {
1725 0 : return true;
1726 : }
1727 :
1728 439 : if (!harmony && other.harmony) {
1729 0 : return false;
1730 : }
1731 :
1732 439 : return std::tie(string, staffIdx, effect) < std::tie(other.string, other.staffIdx, other.effect);
1733 : }
1734 :
1735 : // In the case that graceNotesBefore or graceNotesAfter are attached to a note
1736 : // with an articulation such as a trill, then the grace notes are/will-be/have-been
1737 : // already merged into the articulation.
1738 : // So this predicate, graceNotesMerged, checks for this condition to avoid calling
1739 : // functions which would re-emit the grace notes by a different algorithm.
1740 :
1741 858 : bool CompatMidiRendererInternal::graceNotesMerged(Chord* chord)
1742 : {
1743 858 : if (findFirstTrill(chord)) {
1744 2 : return true;
1745 : }
1746 858 : for (Articulation* a : chord->articulations()) {
1747 42 : for (auto& oe : excursions) {
1748 40 : if (oe.atype == a->symId()) {
1749 0 : return true;
1750 : }
1751 : }
1752 : }
1753 856 : return false;
1754 : }
1755 :
1756 : /* static */
1757 8 : void fillHairpinVelocities(const Hairpin* h, std::unordered_map<track_idx_t, VelocityMap>& velocitiesByTrack)
1758 : {
1759 8 : Staff* st = h->staff();
1760 8 : Fraction tick = h->tick();
1761 8 : Fraction tick2 = h->tick2();
1762 8 : int veloChange = h->veloChange();
1763 8 : ChangeMethod method = h->veloChangeMethod();
1764 :
1765 : // Make the change negative when the hairpin is a diminuendo
1766 8 : HairpinType htype = h->hairpinType();
1767 8 : ChangeDirection direction = ChangeDirection::INCREASING;
1768 8 : if (htype == HairpinType::DIM_HAIRPIN || htype == HairpinType::DIM_LINE) {
1769 3 : veloChange *= -1;
1770 3 : direction = ChangeDirection::DECREASING;
1771 : }
1772 :
1773 8 : switch (h->voiceAssignment()) {
1774 0 : case VoiceAssignment::ALL_VOICE_IN_STAFF:
1775 0 : if (st->isPrimaryStaff()) {
1776 0 : for (track_idx_t track = st->idx() * VOICES; track < (st->idx() + 1) * VOICES; ++track) {
1777 0 : velocitiesByTrack[track].addHairpin(tick, tick2, veloChange, method, direction);
1778 : }
1779 : }
1780 0 : break;
1781 8 : case VoiceAssignment::ALL_VOICE_IN_INSTRUMENT:
1782 16 : for (Staff* s : st->part()->staves()) {
1783 8 : if (!s->isPrimaryStaff()) {
1784 0 : continue;
1785 : }
1786 40 : for (track_idx_t track = s->idx() * VOICES; track < (s->idx() + 1) * VOICES; ++track) {
1787 32 : velocitiesByTrack[track].addHairpin(tick, tick2, veloChange, method, direction);
1788 : }
1789 : }
1790 8 : break;
1791 0 : case VoiceAssignment::CURRENT_VOICE_ONLY:
1792 0 : if (st->isPrimaryStaff()) {
1793 0 : velocitiesByTrack[h->track()].addHairpin(tick, tick2, veloChange, method, direction);
1794 : }
1795 0 : break;
1796 : }
1797 8 : }
1798 :
1799 79 : void fillScoreVelocities(const Score* score, CompatMidiRendererInternal::Context& context)
1800 : {
1801 79 : Score* mainScore = score->masterScore();
1802 :
1803 79 : if (!mainScore->firstMeasure()) {
1804 0 : return;
1805 : }
1806 :
1807 970 : for (Segment* s = mainScore->firstMeasure()->first(); s; s = s->next1()) {
1808 891 : Fraction tick = s->tick();
1809 1039 : for (const EngravingItem* e : s->annotations()) {
1810 148 : if (!e->isDynamic() || !e->staff()->isPrimaryStaff()) {
1811 72 : continue;
1812 : }
1813 :
1814 76 : const Dynamic* d = toDynamic(e);
1815 76 : int v = d->velocity();
1816 :
1817 : // treat an invalid dynamic as no change, i.e. a dynamic set to 0
1818 76 : if (v < 1) {
1819 0 : continue;
1820 : }
1821 :
1822 : // make sure value is legal
1823 76 : v = std::clamp(v, 1, 127);
1824 :
1825 : // If a dynamic has 'velocity change' update its ending
1826 76 : int change = d->changeInVelocity();
1827 76 : ChangeDirection direction = ChangeDirection::INCREASING;
1828 76 : if (change < 0) {
1829 0 : direction = ChangeDirection::DECREASING;
1830 : }
1831 :
1832 76 : const Fraction etick = change != 0 ? tick + d->velocityChangeLength(mainScore->multipliedTempo(tick)) : tick;
1833 :
1834 76 : switch (d->voiceAssignment()) {
1835 0 : case VoiceAssignment::ALL_VOICE_IN_STAFF: {
1836 0 : for (track_idx_t track = d->staffIdx() * VOICES; track < (d->staffIdx() + 1) * VOICES; ++track) {
1837 0 : context.velocitiesByTrack[track].addDynamic(tick, v);
1838 : }
1839 0 : if (change != 0) {
1840 0 : ChangeMethod method = ChangeMethod::NORMAL;
1841 0 : for (track_idx_t track = d->staffIdx() * VOICES; track < (d->staffIdx() + 1) * VOICES; ++track) {
1842 0 : context.velocitiesByTrack[track].addHairpin(tick, etick, change, method, direction);
1843 : }
1844 : }
1845 : }
1846 0 : break;
1847 76 : case VoiceAssignment::ALL_VOICE_IN_INSTRUMENT: {
1848 76 : Part* part = d->staff()->part();
1849 76 : staff_idx_t pStartStaff = part->staves().front()->idx();
1850 76 : staff_idx_t pEndStaff = part->staves().back()->idx();
1851 76 : if (d->staffIdx() < pStartStaff || d->staffIdx() > pEndStaff) {
1852 0 : break;
1853 : }
1854 186 : for (staff_idx_t staffIdx = pStartStaff; staffIdx <= pEndStaff; ++staffIdx) {
1855 110 : Staff* stp = mainScore->staff(staffIdx);
1856 110 : if (!stp->isPrimaryStaff()) {
1857 31 : continue;
1858 : }
1859 395 : for (track_idx_t track = stp->idx() * VOICES; track < (stp->idx() + 1) * VOICES; ++track) {
1860 316 : context.velocitiesByTrack[track].addDynamic(tick, v);
1861 : }
1862 79 : if (change != 0) {
1863 0 : ChangeMethod method = ChangeMethod::NORMAL;
1864 0 : for (track_idx_t track = stp->idx() * VOICES; track < (stp->idx() + 1) * VOICES; ++track) {
1865 0 : context.velocitiesByTrack[track].addHairpin(tick, etick, change, method, direction);
1866 : }
1867 : }
1868 : }
1869 : }
1870 76 : break;
1871 0 : case VoiceAssignment::CURRENT_VOICE_ONLY: {
1872 0 : context.velocitiesByTrack[d->track()].addDynamic(tick, v);
1873 0 : if (change != 0) {
1874 0 : ChangeMethod method = ChangeMethod::NORMAL;
1875 0 : context.velocitiesByTrack[d->track()].addHairpin(tick, etick, change, method, direction);
1876 : }
1877 : }
1878 0 : break;
1879 : }
1880 : }
1881 :
1882 891 : if (s->isChordRestType()) {
1883 3572 : for (track_idx_t track = 0; track < mainScore->ntracks(); ++track) {
1884 3116 : EngravingItem* el = s->element(track);
1885 3116 : if (!el || !el->isChord() || !el->staff()->isPrimaryStaff()) {
1886 3116 : continue;
1887 : }
1888 :
1889 329 : Chord* chord = toChord(el);
1890 :
1891 329 : double veloMultiplier = chordVelocityMultiplier(chord, context);
1892 :
1893 329 : if (muse::RealIsEqual(veloMultiplier, 1.0)) {
1894 329 : continue;
1895 : }
1896 :
1897 0 : Fraction ARTICULATION_CHANGE_TIME = std::min(s->ticks(), ARTICULATION_CHANGE_TIME_MAX);
1898 0 : int start = veloMultiplier * CompatMidiRendererInternal::ARTICULATION_CONV_FACTOR;
1899 0 : int change = (veloMultiplier - 1) * CompatMidiRendererInternal::ARTICULATION_CONV_FACTOR;
1900 0 : context.velocityMultiplicationsByTrack[track].addDynamic(chord->tick(), start);
1901 0 : context.velocityMultiplicationsByTrack[track].addHairpin(chord->tick(),
1902 0 : chord->tick() + ARTICULATION_CHANGE_TIME, change, ChangeMethod::NORMAL,
1903 : ChangeDirection::DECREASING);
1904 : }
1905 : }
1906 : }
1907 :
1908 127 : for (const auto& sp : mainScore->spannerMap().map()) {
1909 48 : Spanner* s = sp.second;
1910 48 : if (!s->isHairpin() || !s->staff()->isPrimaryStaff()) {
1911 40 : continue;
1912 : }
1913 :
1914 8 : fillHairpinVelocities(toHairpin(s), context.velocitiesByTrack);
1915 : }
1916 :
1917 339 : for (Staff* st : mainScore->staves()) {
1918 260 : if (!st->isPrimaryStaff()) {
1919 27 : continue;
1920 : }
1921 1165 : for (track_idx_t track = st->idx() * VOICES; track < (st->idx() + 1) * VOICES; ++track) {
1922 932 : context.velocitiesByTrack[track].setup();
1923 932 : context.velocityMultiplicationsByTrack[track].setup();
1924 : }
1925 : }
1926 :
1927 127 : for (auto it = mainScore->spanner().cbegin(); it != mainScore->spanner().cend(); ++it) {
1928 48 : Spanner* spanner = (*it).second;
1929 48 : if (!spanner->isVolta()) {
1930 44 : continue;
1931 : }
1932 :
1933 4 : Volta* volta = toVolta(spanner);
1934 4 : Staff* st = volta->staff();
1935 4 : if (!st->isPrimaryStaff()) {
1936 0 : continue;
1937 : }
1938 20 : for (track_idx_t track = st->idx() * VOICES; track < (st->idx() + 1) * VOICES; ++track) {
1939 16 : fillVoltaVelocities(volta, context.velocitiesByTrack[track]);
1940 : }
1941 : }
1942 : }
1943 :
1944 : /* static */
1945 16 : void fillVoltaVelocities(const Volta* volta, VelocityMap& veloMap)
1946 : {
1947 16 : Measure* startMeasure = volta->startMeasure();
1948 16 : Measure* endMeasure = volta->endMeasure();
1949 :
1950 16 : if (startMeasure && endMeasure) {
1951 16 : if (!endMeasure->repeatEnd()) {
1952 0 : return;
1953 : }
1954 :
1955 16 : Fraction startTick = startMeasure->tick() - Fraction::eps();
1956 16 : Fraction endTick = endMeasure->endTick() - Fraction::eps();
1957 16 : int prevVelo = veloMap.val(startTick);
1958 16 : veloMap.addDynamic(endTick, prevVelo);
1959 : }
1960 : }
1961 : }
|