Line data Source code
1 : #include "compatmidirender.h"
2 :
3 : #include <cmath>
4 : #include <tuple>
5 :
6 : #include "global/realfn.h"
7 :
8 : #include "dom/arpeggio.h"
9 : #include "dom/articulation.h"
10 : #include "dom/chord.h"
11 : #include "dom/drumset.h"
12 : #include "dom/durationtype.h"
13 : #include "dom/dynamic.h"
14 : #include "dom/glissando.h"
15 : #include "dom/instrument.h"
16 : #include "dom/masterscore.h"
17 : #include "dom/measure.h"
18 : #include "dom/measurerepeat.h"
19 : #include "dom/note.h"
20 : #include "dom/noteevent.h"
21 : #include "dom/part.h"
22 : #include "dom/repeatlist.h"
23 : #include "dom/score.h"
24 : #include "dom/segment.h"
25 : #include "dom/slur.h"
26 : #include "dom/staff.h"
27 : #include "dom/swing.h"
28 : #include "dom/synthesizerstate.h"
29 : #include "dom/tie.h"
30 :
31 : #include "types/bps.h"
32 : #include "dom/tremolosinglechord.h"
33 : #include "dom/tremolotwochord.h"
34 : #include "dom/trill.h"
35 : #include "dom/utils.h"
36 : #include "dom/volta.h"
37 : #include "editing/navigation.h"
38 : #include "types/constants.h"
39 :
40 : #include "easeinout.h"
41 :
42 : namespace mu::engraving {
43 : static int slideTicks(const Chord* chord);
44 : static int graceBendTicks(const Chord* chord);
45 :
46 79 : void CompatMidiRender::renderScore(Score* score, EventsHolder& events, const CompatMidiRendererInternal::Context& ctx, bool expandRepeats)
47 : {
48 79 : score->masterScore()->setExpandRepeats(expandRepeats);
49 79 : CompatMidiRendererInternal internal{ score };
50 79 : internal.renderScore(events, ctx, expandRepeats);
51 79 : }
52 :
53 12 : int CompatMidiRender::getControllerForSnd(Score* score, int globalSndController)
54 : {
55 12 : const int DEFAULT_CC = CTRL_BREATH;
56 :
57 12 : SynthesizerState s = score->synthesizerState();
58 12 : int method = s.method();
59 12 : int controller = s.ccToUse();
60 :
61 12 : if (method == -1) {
62 12 : controller = (globalSndController == -1) ? DEFAULT_CC : globalSndController;
63 : }
64 :
65 12 : return controller;
66 12 : }
67 :
68 79 : void CompatMidiRender::createPlayEvents(const Score* score, Measure const* start, Measure const* const end,
69 : const CompatMidiRendererInternal::Context& context)
70 : {
71 79 : if (!start) {
72 0 : start = score->firstMeasure();
73 : }
74 :
75 79 : track_idx_t etrack = score->nstaves() * VOICES;
76 1119 : for (track_idx_t track = 0; track < etrack; ++track) {
77 1040 : bool rangeEnded = false;
78 1040 : Chord* prevChord = nullptr;
79 2424 : for (Measure const* m = start; m; m = m->nextMeasure()) {
80 1384 : constexpr SegmentType st = SegmentType::ChordRest;
81 :
82 1384 : if (m == end) {
83 0 : rangeEnded = true;
84 : }
85 1384 : if (rangeEnded) {
86 : // The range has ended, but we should collect events
87 : // for tied notes. So we'll check if this is the case.
88 0 : const Segment* seg = m->first(st);
89 0 : const EngravingItem* e = seg->element(track);
90 0 : bool tie = false;
91 0 : if (e && e->isChord()) {
92 0 : for (const mu::engraving::Note* n : toChord(e)->notes()) {
93 0 : if (n->tieBack()) {
94 0 : tie = true;
95 0 : break;
96 : }
97 : }
98 : }
99 0 : if (!tie) {
100 0 : break;
101 : }
102 : }
103 :
104 : // skip linked staves, except primary
105 1384 : if (!m->score()->staff(track / VOICES)->isPrimaryStaff()) {
106 164 : continue;
107 : }
108 3852 : for (Segment* seg = m->first(st); seg; seg = seg->next(st)) {
109 2632 : EngravingItem* item = seg->element(track);
110 2632 : if (!item) {
111 2303 : continue;
112 : }
113 :
114 636 : if (!item->isChord()) {
115 307 : prevChord = nullptr;
116 307 : continue;
117 : }
118 :
119 329 : Chord* chord = toChord(item);
120 329 : Chord* nextChord = nullptr;
121 329 : ChordRestNavigateOptions options;
122 329 : options.skipGrace = true;
123 329 : if (ChordRest* chr = Navigation::nextChordRest(chord, options); chr && chr->isChord()) {
124 219 : nextChord = toChord(chr);
125 : }
126 :
127 329 : if (!nextChord) {
128 : // check if next chord is in next measure
129 110 : Measure* nextMeasure = m->nextMeasure();
130 110 : if (nextMeasure) {
131 16 : nextChord = CompatMidiRender::getChordFromSegment(nextMeasure->first(st), track);
132 : }
133 : }
134 :
135 329 : CompatMidiRender::createPlayEvents(score, context, chord, prevChord, nextChord);
136 329 : prevChord = chord;
137 : }
138 : }
139 : }
140 79 : }
141 :
142 329 : void CompatMidiRender::createPlayEvents(const Score* score, const CompatMidiRendererInternal::Context& context, Chord* chord,
143 : Chord* prevChord, Chord* nextChord)
144 : {
145 329 : int gateTime = 100;
146 :
147 329 : Fraction tick = chord->tick();
148 329 : Slur* slur = 0;
149 571 : for (auto sp : score->spannerMap().map()) {
150 252 : if (!sp.second->isSlur() || sp.second->staffIdx() != chord->staffIdx()) {
151 227 : continue;
152 : }
153 25 : Slur* s = toSlur(sp.second);
154 25 : if (tick >= s->tick() && tick < s->tick2()) {
155 10 : slur = s;
156 10 : break;
157 : }
158 : }
159 : // gateTime is 100% for slurred notes
160 329 : if (!slur) {
161 319 : Instrument* instr = chord->part()->instrument(tick);
162 319 : instr->updateGateTime(&gateTime, String());
163 : }
164 :
165 329 : int ontime = 0;
166 329 : int trailtime = 0;
167 :
168 329 : CompatMidiRender::createGraceNotesPlayEvents(score, tick, chord, ontime, trailtime); // ontime and trailtime are modified by this call depending on grace notes before and after
169 329 : trailtime = CompatMidiRender::adjustTrailtime(trailtime, chord, nextChord);
170 :
171 329 : SwingParameters st = chord->staff()->swing(tick);
172 : // Check if swing needs to be applied
173 329 : if (st.swingUnit && !chord->tuplet()) {
174 0 : Swing::swingAdjustParams(chord, st, ontime, gateTime);
175 : }
176 : //
177 : // render normal (and articulated) chords
178 : //
179 329 : std::vector<NoteEventList> el = CompatMidiRender::renderChord(context, chord, prevChord, gateTime, ontime, trailtime);
180 329 : if (chord->playEventType() == PlayEventType::Auto) {
181 329 : chord->setNoteEventLists(el);
182 : }
183 : // don't change event list if type is PlayEventType::User
184 329 : }
185 :
186 : //---------------------------------------------------------
187 : // renderChord
188 : // ontime and trailtime in 1/1000 of duration
189 : // ontime signifies how much gap to leave, i.e., how late the note should start because of graceNotesBefore which have already been rendered
190 : // trailtime signifies how much gap to leave after the note to allow for graceNotesAfter to be rendered
191 : //---------------------------------------------------------
192 :
193 329 : std::vector<NoteEventList> CompatMidiRender::renderChord(const CompatMidiRendererInternal::Context& context, Chord* chord, Chord* prevChord,
194 : int gateTime, int ontime, int trailtime)
195 : {
196 329 : const std::vector<mu::engraving::Note*>& notes = chord->notes();
197 329 : if (notes.empty()) {
198 0 : return std::vector<NoteEventList>();
199 : }
200 :
201 329 : std::vector<NoteEventList> ell(notes.size(), NoteEventList());
202 :
203 329 : bool arpeggio = false;
204 :
205 : /// when note has glissando or slide from note, tremolo should be shortened
206 329 : int baseLength = NoteEvent::NOTE_LENGTH - ontime - trailtime;
207 329 : double tremoloLength = (double)baseLength / NoteEvent::NOTE_LENGTH;
208 :
209 329 : bool glissandoStart = std::any_of(notes.begin(), notes.end(), [] (mu::engraving::Note* note) {
210 351 : return CompatMidiRender::noteIsGlissandoStart(note);
211 : });
212 :
213 329 : if (glissandoStart) {
214 5 : tremoloLength = 0.5 * baseLength / NoteEvent::NOTE_LENGTH;
215 : } else {
216 324 : bool hasSlideOut = std::any_of(notes.begin(), notes.end(), [] (mu::engraving::Note* note) {
217 346 : return note->hasSlideFromNote();
218 : });
219 :
220 324 : if (hasSlideOut) {
221 9 : tremoloLength = (baseLength - CompatMidiRender::slideLengthChordDependent(chord)) / (double)NoteEvent::NOTE_LENGTH;
222 : }
223 : }
224 :
225 329 : bool tremolo = false;
226 :
227 329 : if (chord->arpeggio() && chord->arpeggio()->playArpeggio()) {
228 5 : CompatMidiRender::renderArpeggio(chord, ell, ontime);
229 5 : arpeggio = true;
230 : } else {
231 324 : if (chord->tremoloType() != TremoloType::INVALID_TREMOLO) {
232 4 : CompatMidiRender::renderTremolo(chord, ell, ontime, tremoloLength);
233 4 : tremolo = true;
234 : }
235 :
236 324 : CompatMidiRender::renderChordArticulation(context, chord, ell, gateTime, (double)ontime / NoteEvent::NOTE_LENGTH, tremolo);
237 : }
238 :
239 : // Check each note and apply gateTime
240 680 : for (size_t i : getNotesIndexesToRender(chord)) {
241 351 : mu::engraving::Note* note = chord->notes()[i];
242 351 : NoteEventList* el = &ell[i];
243 :
244 351 : CompatMidiRender::createSlideOutNotePlayEvents(note, el, ontime, tremolo);
245 351 : if (arpeggio) {
246 15 : continue; // don't add extra events and apply gateTime to arpeggio
247 : }
248 : // If we are here then we still need to render the note.
249 : // Render its body if necessary and apply gateTime.
250 336 : if (el->empty() && chord->tremoloChordType() != TremoloChordType::TremoloSecondChord) {
251 320 : el->push_back(NoteEvent(0, ontime, 1000 - trailtime,
252 320 : !note->ghost() ? NoteEvent::DEFAULT_VELOCITY_MULTIPLIER : NoteEvent::GHOST_VELOCITY_MULTIPLIER));
253 :
254 320 : Glissando* gl = CompatMidiRender::backGlissando(note);
255 320 : if (gl && CompatMidiRender::isGlissandoValid(gl) && !gl->glissandoShift()) {
256 3 : el->back().setSlide(true);
257 : }
258 : }
259 :
260 336 : CompatMidiRender::createSlideInNotePlayEvents(note, prevChord, el);
261 :
262 776 : for (NoteEvent& e : *el) {
263 440 : e.setLen(e.len() * gateTime / 100);
264 : }
265 :
266 336 : std::sort(el->begin(), el->end(), [](const NoteEvent& left, const NoteEvent& right) {
267 227 : int l1 = left.ontime();
268 227 : int l2 = -left.offset();
269 227 : int r1 = right.ontime();
270 227 : int r2 = -right.offset();
271 :
272 227 : return std::tie(l1, l2) < std::tie(r1, r2);
273 : });
274 329 : }
275 :
276 329 : return ell;
277 329 : }
278 :
279 : //---------------------------------------------------------
280 : // renderArpeggio
281 : //---------------------------------------------------------
282 :
283 5 : void CompatMidiRender::renderArpeggio(Chord* chord, std::vector<NoteEventList>& ell, int ontime)
284 : {
285 5 : int notes = int(chord->notes().size());
286 5 : int l = 64;
287 5 : while (l && (l * notes > chord->upNote()->playTicks())) {
288 0 : l = 2 * l / 3;
289 : }
290 : int start, end, step;
291 5 : bool up = chord->arpeggio()->arpeggioType() != ArpeggioType::DOWN && chord->arpeggio()->arpeggioType() != ArpeggioType::DOWN_STRAIGHT;
292 5 : if (up) {
293 3 : start = 0;
294 3 : end = notes;
295 3 : step = 1;
296 : } else {
297 2 : start = notes - 1;
298 2 : end = -1;
299 2 : step = -1;
300 : }
301 5 : int j = 0;
302 20 : for (int i = start; i != end; i += step) {
303 15 : NoteEventList* events = &(ell)[i];
304 15 : events->clear();
305 :
306 15 : auto tempoRatio = chord->score()->multipliedTempo(chord->tick()).val / Constants::DEFAULT_TEMPO.val;
307 15 : int ot = (l * j * 1000) / chord->upNote()->playTicks()
308 15 : * tempoRatio * chord->arpeggio()->Stretch();
309 15 : ot = std::clamp(ot + ontime, ot, 1000);
310 :
311 15 : events->push_back(NoteEvent(0, ot, 1000 - ot));
312 15 : j++;
313 : }
314 5 : }
315 :
316 : //---------------------------------------------------------
317 : // renderTremolo
318 : //---------------------------------------------------------
319 :
320 4 : void CompatMidiRender::renderTremolo(Chord* chord, std::vector<NoteEventList>& ell, int& ontime, double tremoloPartOfChord /* = 1.0 */)
321 : {
322 4 : if (muse::RealIsNull(tremoloPartOfChord)) {
323 0 : return;
324 : }
325 :
326 : struct TremoloAdapter {
327 : TremoloSingleChord* singleChord = nullptr;
328 : TremoloTwoChord* twoChord = nullptr;
329 4 : TremoloAdapter(TremoloSingleChord* s, TremoloTwoChord* t)
330 4 : : singleChord(s), twoChord(t) {}
331 :
332 4 : TremoloType tremoloType() const
333 : {
334 4 : return singleChord ? singleChord->tremoloType()
335 4 : : (twoChord ? twoChord->tremoloType() : TremoloType::INVALID_TREMOLO);
336 : }
337 :
338 4 : int lines() const { return singleChord ? singleChord->lines() : (twoChord ? twoChord->lines() : 0); }
339 : };
340 :
341 4 : TremoloAdapter tremolo = TremoloAdapter(chord->tremoloSingleChord(), chord->tremoloTwoChord());
342 :
343 4 : Segment* seg = chord->segment();
344 : //TremoloDispatcher* tremolo = chord->tremoloDispatcher();
345 4 : int notes = int(chord->notes().size());
346 :
347 : // check if tremolo was rendered before for drum staff
348 4 : const Drumset* ds = CompatMidiRender::getDrumset(chord);
349 4 : if (ds) {
350 0 : for (mu::engraving::Note* n : chord->notes()) {
351 0 : DrumInstrumentVariant div = ds->findVariant(n->pitch(), chord->articulations(), tremolo.tremoloType());
352 0 : if (div.pitch != INVALID_PITCH && div.tremolo == tremolo.tremoloType()) {
353 0 : return; // already rendered
354 : }
355 0 : }
356 : }
357 :
358 : // we cannot render buzz roll with MIDI events only
359 4 : if (tremolo.tremoloType() == TremoloType::BUZZ_ROLL) {
360 0 : return;
361 : }
362 :
363 : // render tremolo with multiple events
364 4 : if (chord->tremoloChordType() == TremoloChordType::TremoloFirstChord) {
365 0 : int t = Constants::DIVISION / (1 << (tremolo.lines() + chord->durationType().hooks()));
366 0 : if (t == 0) { // avoid crash on very short tremolo
367 0 : t = 1;
368 : }
369 0 : SegmentType st = SegmentType::ChordRest;
370 0 : Segment* seg2 = seg->next(st);
371 0 : track_idx_t track = chord->track();
372 0 : while (seg2 && !seg2->element(track)) {
373 0 : seg2 = seg2->next(st);
374 : }
375 :
376 0 : if (!seg2) {
377 0 : return;
378 : }
379 :
380 0 : EngravingItem* s2El = seg2->element(track);
381 0 : if (s2El) {
382 0 : if (!s2El->isChord()) {
383 0 : return;
384 : }
385 : } else {
386 0 : return;
387 : }
388 :
389 0 : Chord* c2 = toChord(s2El);
390 0 : if (c2->isChord()) {
391 0 : int notes2 = int(c2->notes().size());
392 0 : int tnotes = std::max(notes, notes2);
393 0 : int tticks = chord->ticks().ticks() * 2; // use twice the size
394 0 : int n = tticks / t;
395 0 : n /= 2;
396 0 : int l = 2000 * t / tticks;
397 0 : for (int k = 0; k < tnotes; ++k) {
398 : NoteEventList* events;
399 0 : if (k < notes) {
400 : // first chord has note
401 0 : events = &ell[k];
402 0 : events->clear();
403 : } else {
404 : // otherwise reuse note 0
405 0 : events = &ell[0];
406 : }
407 0 : if (k < notes && k < notes2) {
408 : // both chords have note
409 0 : int p1 = chord->notes()[k]->pitch();
410 0 : int p2 = c2->notes()[k]->pitch();
411 0 : int dpitch = p2 - p1;
412 0 : for (int i = 0; i < n; ++i) {
413 0 : events->push_back(NoteEvent(0, l * i * 2, l));
414 0 : events->push_back(NoteEvent(dpitch, l * i * 2 + l, l));
415 : }
416 0 : } else if (k < notes) {
417 : // only first chord has note
418 0 : for (int i = 0; i < n; ++i) {
419 0 : events->push_back(NoteEvent(0, l * i * 2, l));
420 : }
421 : } else {
422 : // only second chord has note
423 : // reuse note 0 of first chord
424 0 : int p1 = chord->notes()[0]->pitch();
425 0 : int p2 = c2->notes()[k]->pitch();
426 0 : int dpitch = p2 - p1;
427 0 : for (int i = 0; i < n; ++i) {
428 0 : events->push_back(NoteEvent(dpitch, l * i * 2 + l, l));
429 : }
430 : }
431 : }
432 : } else {
433 0 : LOGD("Chord::renderTremolo: cannot find 2. chord");
434 : }
435 4 : } else if (chord->tremoloChordType() == TremoloChordType::TremoloSecondChord) {
436 0 : for (int k = 0; k < notes; ++k) {
437 0 : NoteEventList* events = &(ell)[k];
438 0 : events->clear();
439 : }
440 4 : } else if (chord->tremoloChordType() == TremoloChordType::TremoloSingle) {
441 4 : int t = Constants::DIVISION / (1 << (tremolo.lines() + chord->durationType().hooks()));
442 4 : if (t == 0) { // avoid crash on very short tremolo
443 0 : t = 1;
444 : }
445 :
446 4 : int tremoloEventsSize = chord->ticks().ticks() / t * tremoloPartOfChord;
447 :
448 4 : constexpr int fullEventTime = 1000;
449 4 : int tremoloTime = fullEventTime * tremoloPartOfChord;
450 4 : int tremoloEventStep = tremoloTime / tremoloEventsSize;
451 4 : ontime += tremoloTime;
452 :
453 8 : for (int k = 0; k < notes; ++k) {
454 4 : NoteEventList* events = &(ell)[k];
455 4 : events->clear();
456 20 : for (int i = 0; i < tremoloEventsSize; i++) {
457 16 : events->push_back(NoteEvent(0, tremoloEventStep * i, tremoloEventStep));
458 : }
459 : }
460 : }
461 : }
462 :
463 : //---------------------------------------------------------
464 : // renderChordArticulation
465 : //---------------------------------------------------------
466 :
467 324 : void CompatMidiRender::renderChordArticulation(const CompatMidiRendererInternal::Context& context, Chord* chord,
468 : std::vector<NoteEventList>& ell, int& gateTime, double graceOnBeatProportion,
469 : bool tremoloBefore /* = false */)
470 : {
471 324 : Segment* seg = chord->segment();
472 324 : Instrument* instr = chord->part()->instrument(seg->tick());
473 :
474 660 : for (unsigned k = 0; k < chord->notes().size(); ++k) {
475 336 : NoteEventList* events = &ell[k];
476 336 : Note* note = chord->notes()[k];
477 : Trill* trill;
478 :
479 336 : if (noteIsGlissandoStart(note)) {
480 5 : CompatMidiRender::renderGlissando(events, note, graceOnBeatProportion, tremoloBefore);
481 331 : } else if (chord->staff()->isPitchedStaff(chord->tick()) && (trill = findFirstTrill(chord)) != nullptr) {
482 1 : CompatMidiRender::renderNoteArticulation(events, note, false, trill->trillType(), trill->ornamentStyle());
483 : } else {
484 331 : for (Articulation* a : chord->articulations()) {
485 1 : if (!a->playArticulation()) {
486 0 : continue;
487 : }
488 1 : if (!CompatMidiRender::renderNoteArticulation(events, note, false, a->symId(), a->ornamentStyle())) {
489 1 : CompatMidiRender::updateGateTime(instr, gateTime, a->articulationName(), context);
490 : }
491 : }
492 : }
493 : }
494 324 : }
495 :
496 1 : void CompatMidiRender::updateGateTime(const Instrument* instrument, int& gateTime, const String& articulationName,
497 : const CompatMidiRendererInternal::Context& context)
498 : {
499 : using Ctx = CompatMidiRendererInternal::Context;
500 1 : if (context.useDefaultArticulations) {
501 0 : auto it = Ctx::s_builtInArticulationsValues.find(articulationName);
502 0 : if (it != Ctx::s_builtInArticulationsValues.end()) {
503 0 : gateTime = std::min(gateTime, it->second.gateTime);
504 : } else {
505 0 : instrument->updateGateTime(&gateTime, articulationName);
506 : }
507 :
508 0 : return;
509 : }
510 :
511 1 : auto articulationsForInstrumentIt = context.articulationsWithoutValuesByInstrument.find(instrument->id());
512 1 : if (articulationsForInstrumentIt != context.articulationsWithoutValuesByInstrument.end()) {
513 1 : const auto& articulationsForInstrument = articulationsForInstrumentIt->second;
514 1 : if (articulationsForInstrument.find(articulationName) != articulationsForInstrument.end()) {
515 1 : gateTime = std::min(gateTime, Ctx::s_builtInArticulationsValues[articulationName].gateTime);
516 : } else {
517 0 : instrument->updateGateTime(&gateTime, articulationName);
518 : }
519 : }
520 : }
521 :
522 : //---------------------------------------------------------
523 : // createGraceNotesPlayEvent
524 : // as a side effect of createGraceNotesPlayEvents, ontime and trailtime (passed by ref)
525 : // are modified. ontime reflects the time needed to play the grace-notes-before, and
526 : // trailtime reflects the time for the grace-notes-after. These are used by the caller
527 : // to effect the on/off time of the main note
528 : //---------------------------------------------------------
529 :
530 329 : void CompatMidiRender::createGraceNotesPlayEvents(const Score* score, const Fraction& tick, Chord* chord, int& ontime, int& trailtime)
531 : {
532 329 : std::vector<Chord*> gnb = chord->graceNotesBefore(true);
533 329 : std::vector<Chord*> gna = chord->graceNotesAfter(true);
534 329 : int nb = int(gnb.size());
535 329 : int na = int(gna.size());
536 329 : if (0 == nb + na) {
537 304 : return; // return immediately if no grace notes to deal with
538 : }
539 : // return immediately if the chord has a trill or articulation which effectively plays the graces notes.
540 25 : if (CompatMidiRendererInternal::graceNotesMerged(chord)) {
541 0 : return;
542 : }
543 : // if there are graceNotesBefore and also graceNotesAfter, and the before grace notes are
544 : // not ACCIACCATURA, then the total time of all of them will be 50% of the time of the main note.
545 : // if the before grace notes are ACCIACCATURA then the grace notes after (if there are any).
546 : // get 50% of the time of the main note.
547 : // this is achieved by the two floating point weights: weighta and weightb whose total is 1.0
548 : // assuring that all the grace notes get the same duration, and their total is 50%.
549 : // exception is if the note is dotted or double-dotted; see below.
550 25 : float weighta = float(na) / (nb + na);
551 :
552 25 : int graceDuration = 0;
553 25 : bool drumset = (CompatMidiRender::getDrumset(chord) != nullptr);
554 25 : const double ticksPerSecond = score->multipliedTempo(tick).val * Constants::DIVISION;
555 25 : const double chordTimeMS = (chord->actualTicks().ticks() / ticksPerSecond) * 1000;
556 25 : if (drumset) {
557 0 : int flamDuration = 15; //ms
558 0 : graceDuration = flamDuration / chordTimeMS * 1000; //ratio 1/1000 from the main note length
559 0 : ontime = graceDuration * nb;
560 25 : } else if (nb) {
561 : //
562 : // render grace notes:
563 : // simplified implementation:
564 : // - grace notes start on the beat of the main note
565 : // - duration: appoggiatura: 0.5 * duration of main note (2/3 for dotted notes, 4/7 for double-dotted)
566 : // acciacatura: min of 0.5 * duration or 65ms fixed (independent of duration or tempo)
567 : // - for appoggiaturas, the duration is divided by the number of grace notes
568 : // - the grace note duration as notated does not matter
569 : //
570 9 : Chord* graceChord = gnb[0];
571 9 : const auto& graceNotes = graceChord->notes();
572 9 : bool graceBend = std::any_of(graceNotes.begin(), graceNotes.end(), [](Note* note) {
573 9 : return note->isGraceBendStart();
574 : });
575 :
576 9 : if (graceChord->noteType() == NoteType::ACCIACCATURA || graceBend) {
577 6 : ontime = 0;
578 6 : graceDuration = 0;
579 6 : weighta = 1.0;
580 : } else {
581 3 : const double graceTimeMS = (graceChord->actualTicks().ticks() / ticksPerSecond) * 1000;
582 :
583 : // 1000 occurs below as a unit for ontime
584 3 : ontime = std::min(500, static_cast<int>((graceTimeMS / chordTimeMS) * 1000));
585 3 : graceDuration = ontime / nb;
586 3 : weighta = 1.0;
587 3 : trailtime += ontime;
588 : }
589 : }
590 :
591 36 : for (int i = 0, on = 0; i < nb; ++i) {
592 11 : std::vector<NoteEventList> el;
593 11 : Chord* gc = gnb.at(i);
594 11 : size_t nn = gc->notes().size();
595 22 : for (unsigned ii = 0; ii < nn; ++ii) {
596 11 : NoteEventList nel;
597 11 : nel.push_back(NoteEvent(0, on, graceDuration));
598 11 : el.push_back(nel);
599 11 : }
600 :
601 11 : if (gc->playEventType() == PlayEventType::Auto) {
602 11 : gc->setNoteEventLists(el);
603 : }
604 :
605 11 : on += graceDuration;
606 11 : }
607 :
608 25 : bool graceBendAfter = std::any_of(gna.begin(), gna.end(), [](const Chord* graceChord) {
609 17 : for (Note* note : graceChord->notes()) {
610 16 : if (note->bendBack()) {
611 15 : return true;
612 : }
613 : }
614 :
615 1 : return false;
616 : });
617 :
618 25 : if (!graceBendAfter && na) {
619 1 : if (chord->dots() == 1) {
620 0 : trailtime = floor(667 * weighta);
621 1 : } else if (chord->dots() == 2) {
622 0 : trailtime = floor(571 * weighta);
623 : } else {
624 1 : trailtime = floor(500 * weighta);
625 : }
626 1 : int graceDuration1 = trailtime / na;
627 1 : int on = 1000 - trailtime;
628 2 : for (int i = 0; i < na; ++i) {
629 1 : std::vector<NoteEventList> el;
630 1 : Chord* gc = gna.at(i);
631 1 : size_t nn = gc->notes().size();
632 2 : for (size_t ii = 0; ii < nn; ++ii) {
633 1 : NoteEventList nel;
634 1 : nel.push_back(NoteEvent(0, on, graceDuration1)); // NoteEvent(pitch,ontime,len)
635 1 : el.push_back(nel);
636 1 : }
637 :
638 1 : if (gc->playEventType() == PlayEventType::Auto) {
639 1 : gc->setNoteEventLists(el);
640 : }
641 1 : on += graceDuration1;
642 1 : }
643 : }
644 633 : }
645 :
646 : //---------------------------------------------------------
647 : // renderGlissando
648 : //---------------------------------------------------------
649 :
650 5 : void CompatMidiRender::renderGlissando(NoteEventList* events, Note* notestart, double graceOnBeatProportion,
651 : bool tremoloBefore /* = false */)
652 : {
653 5 : std::vector<int> empty = {};
654 5 : std::vector<int> body;
655 10 : for (Spanner* s : notestart->spannerFor()) {
656 5 : if (s->isGlissando() && s->playSpanner() && Glissando::pitchSteps(s, body)) {
657 5 : CompatMidiRender::renderNoteArticulation(events, notestart, true, Constants::DIVISION, empty, body, false, true, empty, 16, 0,
658 : graceOnBeatProportion, tremoloBefore);
659 : }
660 : }
661 5 : }
662 :
663 : //---------------------------------------------------------
664 : // renderNoteArticulation
665 : // prefix, vector of int, normally something like {0,-1,0,1} modeling the prefix of tremblement relative to the base note
666 : // body, vector of int, normally something like {0,-1,0,1} modeling the possibly repeated tremblement relative to the base note
667 : // tickspernote, number of ticks, either _16h or _32nd, i.e., Constants::division/4 or Constants::division/8
668 : // repeatp, true means repeat the body as many times as possible to fill the time slice.
669 : // sustainp, true means the last note of the body is sustained to fill remaining time slice
670 : //---------------------------------------------------------
671 :
672 6 : bool CompatMidiRender::renderNoteArticulation(NoteEventList* events, Note* note, bool chromatic, int requestedTicksPerNote,
673 : const std::vector<int>& prefix, const std::vector<int>& body,
674 : bool repeatp, bool sustainp, const std::vector<int>& suffix,
675 : int fastestFreq /* = 64 */, int slowestFreq /* = 8 */, // 64 Hz and 8 Hz
676 : double graceOnBeatProportion /* = 0 */, bool tremoloBefore /* = false */)
677 : {
678 6 : if (!tremoloBefore) {
679 5 : events->clear();
680 : }
681 :
682 6 : Chord* chord = note->chord();
683 6 : int maxticks = CompatMidiRender::totalTiedNoteTicks(note);
684 6 : int space = 1000 * maxticks;
685 6 : int numrepeat = 1;
686 6 : int sustain = 0;
687 6 : int ontime = 0;
688 :
689 : ///@NOTE grace note + glissando combination is treated separetely, in this method grace notes for glissando are not added
690 : /// graceOnBeatProportion is used for calculating grace note "on beat" offset
691 6 : int gnb = noteIsGlissandoStart(note) ? 0 : int(note->chord()->graceNotesBefore().size());
692 6 : int p = int(prefix.size());
693 6 : int b = int(body.size());
694 6 : int s = int(suffix.size());
695 6 : int gna = int(note->chord()->graceNotesAfter().size());
696 :
697 6 : int ticksPerNote = 0;
698 :
699 6 : if (gnb + p + b + s + gna <= 0) {
700 0 : return false;
701 : }
702 :
703 6 : Fraction tick = chord->tick();
704 6 : BeatsPerSecond tempo = chord->score()->multipliedTempo(tick);
705 6 : int ticksPerSecond = tempo.val * Constants::DIVISION;
706 :
707 6 : int minTicksPerNote = int(ticksPerSecond / fastestFreq);
708 6 : int maxTicksPerNote = (0 == slowestFreq) ? 0 : int(ticksPerSecond / slowestFreq);
709 :
710 : // for fast tempos, we have to slow down the tremblement frequency, i.e., increase the ticks per note
711 6 : if (requestedTicksPerNote >= minTicksPerNote) {
712 : } else { // try to divide the requested frequency by a power of 2 if possible, if not, use the maximum frequency, ie., minTicksPerNote
713 0 : ticksPerNote = requestedTicksPerNote;
714 0 : while (ticksPerNote < minTicksPerNote) {
715 0 : ticksPerNote *= 2; // decrease the tremblement frequency
716 : }
717 0 : if (ticksPerNote > maxTicksPerNote) {
718 0 : ticksPerNote = minTicksPerNote;
719 : }
720 : }
721 :
722 6 : ticksPerNote = std::max(requestedTicksPerNote, minTicksPerNote);
723 :
724 6 : if (slowestFreq <= 0) { // no slowest freq given such as something silly like glissando with 4 notes over 8 counts.
725 1 : } else if (ticksPerNote <= maxTicksPerNote) { // in a good range, so we don't need to adjust ticksPerNote
726 : } else {
727 : // for slow tempos, such as adagio, we may need to speed up the tremblement frequency, i.e., decrease the ticks per note, to make it sound reasonable.
728 0 : ticksPerNote = requestedTicksPerNote;
729 0 : while (ticksPerNote > maxTicksPerNote) {
730 0 : ticksPerNote /= 2;
731 : }
732 0 : if (ticksPerNote < minTicksPerNote) {
733 0 : ticksPerNote = minTicksPerNote;
734 : }
735 : }
736 : // calculate whether to shorten the duration value.
737 6 : if (ticksPerNote * (gnb + p + b + s + gna) <= maxticks) {
738 : // plenty of space to play the notes without changing the requested trill note duration
739 5 : } else if (ticksPerNote == minTicksPerNote) {
740 0 : return false; // the ornament is impossible to implement respecting the minimum duration and all the notes it contains
741 : } else {
742 5 : ticksPerNote = maxticks / (gnb + p + b + s + gna); // integer division ignoring remainder
743 5 : if (slowestFreq <= 0) {
744 0 : } else if (ticksPerNote < minTicksPerNote) {
745 0 : return false;
746 : }
747 : }
748 :
749 6 : int millespernote = space * ticksPerNote / maxticks; // rescale duration into per mille
750 :
751 : // local function:
752 : // look ahead in the given vector to see if the current note is the same pitch as the next note or next several notes.
753 : // If so, increment the duration by the appropriate note duration, and increment the index, j, to the next note index
754 : // of a different pitch.
755 : // The total duration of the tied note is returned, and the index is modified.
756 2 : auto tieForward = [millespernote](int& j, const std::vector<int>& vec) {
757 2 : int size = int(vec.size());
758 2 : int duration = millespernote;
759 2 : while (j < size - 1 && vec[j] == vec[j + 1]) {
760 0 : duration += millespernote;
761 0 : j++;
762 : }
763 2 : return duration;
764 6 : };
765 :
766 : // local function:
767 : // append a NoteEvent either by calculating an articulationExcursion or by
768 : // the given chromatic relative pitch.
769 : // RETURNS the new ontime value. The caller is expected to assign this value.
770 : auto makeEvent
771 24 : = [note, chord, chromatic, events](int pitch, int ontime, int duration,
772 : double velocityMultiplier = NoteEvent::DEFAULT_VELOCITY_MULTIPLIER, bool play = true) {
773 24 : if (note->ghost()) {
774 0 : velocityMultiplier *= NoteEvent::GHOST_VELOCITY_MULTIPLIER;
775 : }
776 32 : events->push_back(NoteEvent(chromatic ? pitch : chromaticPitchSteps(note, note, pitch),
777 24 : ontime / chord->actualTicks().ticks(),
778 56 : duration / chord->actualTicks().ticks(), velocityMultiplier, play));
779 :
780 24 : return ontime + duration;
781 6 : };
782 :
783 : // calculate the number of times to repeat the body, and sustain the last note of the body
784 : // 1000 = P + numrepeat*B+sustain + S
785 6 : if (repeatp) {
786 1 : numrepeat = (space - millespernote * (gnb + p + s + gna)) / (millespernote * b);
787 : }
788 6 : if (sustainp) {
789 6 : sustain = space - millespernote * (gnb + p + numrepeat * b + s + gna);
790 : }
791 :
792 : // render the prefix
793 8 : for (int j = 0; j < p; j++) {
794 2 : ontime = makeEvent(prefix[j], ontime, tieForward(j, prefix));
795 : }
796 :
797 6 : if (b > 0) {
798 : // Check that we are doing a glissando
799 : enum GlissandoState {
800 : NOT_FOUND,
801 : VALID,
802 : INVALID
803 6 : } glissandoState = GlissandoState::NOT_FOUND;
804 :
805 6 : std::vector<int> onTimes;
806 6 : for (Spanner* spanner : note->spannerFor()) {
807 5 : if (spanner->isGlissando()) {
808 5 : Glissando* glissando = toGlissando(spanner);
809 5 : if (!isGlissandoValid(glissando)) {
810 1 : glissandoState = GlissandoState::INVALID;
811 1 : break;
812 : }
813 :
814 8 : EaseInOut easeInOut(static_cast<double>(glissando->easeIn()) / 100.0,
815 4 : static_cast<double>(glissando->easeOut()) / 100.0);
816 :
817 : // shifting glissando sounds to the second half of glissando duration
818 4 : int totalDuration = millespernote * b;
819 4 : constexpr double glissandoPart = 0.33;
820 :
821 4 : int glissandoDuration = tremoloBefore ? totalDuration * glissandoPart : totalDuration
822 3 : * (1 - graceOnBeatProportion) * glissandoPart;
823 4 : easeInOut.timeList(b - 1, glissandoDuration, &onTimes);
824 :
825 4 : if (!onTimes.empty()) {
826 4 : onTimes[0] += graceOnBeatProportion * totalDuration;
827 : }
828 :
829 4 : if (onTimes.size() > 1) {
830 4 : int offset = onTimes[1];
831 16 : for (size_t i = 1; i < onTimes.size(); i++) {
832 12 : onTimes[i] += totalDuration - glissandoDuration - offset;
833 : }
834 : }
835 :
836 4 : glissandoState = GlissandoState::VALID;
837 4 : break;
838 : }
839 : }
840 6 : if (glissandoState == GlissandoState::VALID) {
841 4 : const double defaultVelocityMultiplier = 1.0;
842 :
843 : // render the body, i.e. the glissando
844 4 : if (onTimes.size() > 1) {
845 4 : makeEvent(body[0], onTimes[0], onTimes[1] - onTimes[0],
846 4 : defaultVelocityMultiplier, !note->tieBack());
847 :
848 4 : Glissando* gl = backGlissando(note);
849 4 : if (gl && isGlissandoValid(gl) && !gl->glissandoShift()) {
850 0 : events->back().setSlide(true);
851 : }
852 : }
853 :
854 12 : for (int j = 1; j < b - 1; j++) {
855 8 : makeEvent(body[j], onTimes[j], onTimes[j + 1] - onTimes[j],
856 : defaultVelocityMultiplier);
857 8 : events->back().setSlide(true);
858 : }
859 :
860 4 : if (b > 1) {
861 4 : makeEvent(body[b - 1], onTimes[b - 1], (millespernote * b - onTimes[b - 1]) + sustain, defaultVelocityMultiplier);
862 4 : events->back().setSlide(true);
863 : }
864 2 : } else if (glissandoState != GlissandoState::INVALID) {
865 : // render the body, but not the final repetition
866 3 : for (int r = 0; r < numrepeat - 1; r++) {
867 6 : for (int j = 0; j < b; j++) {
868 4 : ontime = makeEvent(body[j], ontime, millespernote);
869 : }
870 : }
871 : // render the final repetition of body, but not the final note of the repetition
872 2 : for (int j = 0; j < b - 1; j++) {
873 1 : ontime = makeEvent(body[j], ontime, millespernote);
874 : }
875 : // render the final note of the final repeat of body
876 1 : ontime = makeEvent(body[b - 1], ontime, millespernote + sustain);
877 : }
878 6 : }
879 : // render the suffix
880 6 : for (int j = 0; j < s; j++) {
881 0 : ontime = makeEvent(suffix[j], ontime, tieForward(j, suffix));
882 : }
883 :
884 6 : return true;
885 : }
886 :
887 : //---------------------------------------------------------
888 : // renderNoteArticulation
889 : //---------------------------------------------------------
890 :
891 2 : bool CompatMidiRender::renderNoteArticulation(NoteEventList* events, Note* note, bool chromatic, SymId articulationType,
892 : OrnamentStyle ornamentStyle)
893 : {
894 2 : if (!note->staff()->isPitchedStaff(note->tick())) { // not enough info in tab staff
895 0 : return false;
896 : }
897 :
898 2 : std::vector<int> emptypattern = {};
899 26 : for (auto& oe : excursions) {
900 27 : if (oe.atype == articulationType && (0 == oe.ostyles.size()
901 27 : || oe.ostyles.end() != oe.ostyles.find(ornamentStyle))) {
902 2 : return renderNoteArticulation(events, note, chromatic, oe.duration,
903 1 : oe.prefix, oe.body, oe.repeatp, oe.sustainp, oe.suffix);
904 : }
905 : }
906 1 : return false;
907 2 : }
908 :
909 : //---------------------------------------------------------
910 : // renderNoteArticulation
911 : //---------------------------------------------------------
912 :
913 1 : bool CompatMidiRender::renderNoteArticulation(NoteEventList* events, Note* note, bool chromatic, TrillType trillType,
914 : OrnamentStyle ornamentStyle)
915 : {
916 : std::map<TrillType, SymId> articulationMap = {
917 : { TrillType::TRILL_LINE, SymId::ornamentTrill },
918 : { TrillType::UPPRALL_LINE, SymId::ornamentUpPrall },
919 : { TrillType::DOWNPRALL_LINE, SymId::ornamentPrecompMordentUpperPrefix },
920 : { TrillType::PRALLPRALL_LINE, SymId::ornamentTrill }
921 2 : };
922 1 : auto it = articulationMap.find(trillType);
923 1 : if (it == articulationMap.cend()) {
924 0 : return false;
925 : } else {
926 1 : return CompatMidiRender::renderNoteArticulation(events, note, chromatic, it->second, ornamentStyle);
927 : }
928 1 : }
929 :
930 336 : void CompatMidiRender::createSlideInNotePlayEvents(Note* note, Chord* prevChord, NoteEventList* el)
931 : {
932 336 : if (!note->hasSlideToNote()) {
933 318 : return;
934 : }
935 :
936 18 : int slideNotes = 0;
937 18 : bool upSlide = note->slideToType() == Note::SlideType::UpToNote;
938 18 : if (upSlide) {
939 12 : slideNotes = std::clamp(note->fret() - 1, 0, NoteEvent::SLIDE_AMOUNT);
940 : } else {
941 6 : slideNotes = NoteEvent::SLIDE_AMOUNT;
942 : }
943 :
944 18 : if (slideNotes == 0) {
945 2 : return;
946 : }
947 :
948 16 : int totalSlideDuration = 0;
949 16 : double currentTicks = note->chord()->ticks().ticks();
950 16 : if (prevChord) {
951 : /// event length is about current chord's events, but we adjust previous
952 10 : totalSlideDuration = (double)slideLengthChordDependent(prevChord) / currentTicks * prevChord->ticks().ticks();
953 : } else {
954 6 : totalSlideDuration = (double)NoteEvent::NOTE_LENGTH * SLIDE_DURATION / currentTicks;
955 : }
956 :
957 16 : const int slideDuration = totalSlideDuration / slideNotes;
958 :
959 16 : int slideOn = 0;
960 16 : int pitchOffset = upSlide ? -1 : 1;
961 16 : int pitch = pitchOffset * slideNotes;
962 61 : for (int i = 0; i < slideNotes; ++i) {
963 45 : NoteEvent event { pitch, 0, slideDuration, 1.0, true, totalSlideDuration - slideOn };
964 45 : event.setSlide(true);
965 45 : el->push_back(std::move(event));
966 45 : slideOn += slideDuration;
967 45 : pitch -= pitchOffset;
968 : }
969 : }
970 :
971 351 : void CompatMidiRender::createSlideOutNotePlayEvents(Note* note, NoteEventList* el, int onTime, bool hasTremolo)
972 : {
973 351 : if (!note->hasSlideFromNote()) {
974 342 : return;
975 : }
976 :
977 9 : const int slideNotes = NoteEvent::SLIDE_AMOUNT;
978 9 : const int totalSlideDuration = slideLengthChordDependent(note->chord());
979 9 : const int slideDuration = totalSlideDuration / slideNotes;
980 :
981 9 : int slideOn = NoteEvent::NOTE_LENGTH - totalSlideDuration;
982 9 : double velocity = !note->ghost() ? NoteEvent::DEFAULT_VELOCITY_MULTIPLIER : NoteEvent::GHOST_VELOCITY_MULTIPLIER;
983 9 : if (!hasTremolo) {
984 8 : el->push_back(NoteEvent(0, onTime, slideOn - onTime, velocity, !note->tieBack()));
985 : }
986 :
987 9 : int pitch = 0;
988 9 : int pitchOffset = note->slideFromType() == Note::SlideType::UpFromNote ? 1 : -1;
989 36 : for (int i = 0; i < slideNotes; ++i) {
990 27 : pitch += pitchOffset;
991 27 : NoteEvent event { pitch, slideOn, slideDuration, velocity };
992 27 : event.setSlide(true);
993 27 : el->push_back(std::move(event));
994 27 : slideOn += slideDuration;
995 : }
996 : }
997 :
998 16 : Chord* CompatMidiRender::getChordFromSegment(Segment* segment, track_idx_t track)
999 : {
1000 16 : if (segment) {
1001 16 : EngravingItem* item = segment->element(track);
1002 16 : if (item && item->isChord()) {
1003 6 : return toChord(item);
1004 : }
1005 : }
1006 :
1007 10 : return nullptr;
1008 : }
1009 :
1010 : //---------------------------------------------------------
1011 : // findFirstTrill
1012 : // search the spanners in the score, finding the first one
1013 : // which overlaps this chord and is of type ElementType::TRILL
1014 : //---------------------------------------------------------
1015 :
1016 356 : Trill* CompatMidiRender::findFirstTrill(Chord* chord)
1017 : {
1018 356 : auto spanners = chord->score()->spannerMap().findOverlapping(1 + chord->tick().ticks(), chord->endTick().ticks() - 1);
1019 507 : for (auto i : spanners) {
1020 152 : if (!i.value->isTrill()) {
1021 151 : continue;
1022 : }
1023 1 : if (i.value->track() != chord->track()) {
1024 0 : continue;
1025 : }
1026 1 : Trill* trill = toTrill(i.value);
1027 1 : if (!trill->playSpanner()) {
1028 0 : continue;
1029 : }
1030 1 : return trill;
1031 : }
1032 355 : return nullptr;
1033 356 : }
1034 :
1035 329 : int CompatMidiRender::adjustTrailtime(int trailtime, Chord* currentChord, Chord* nextChord)
1036 : {
1037 329 : if (!nextChord) {
1038 104 : return trailtime;
1039 : }
1040 :
1041 225 : const std::vector<Chord*>& graceBeforeChords = nextChord->graceNotesBefore();
1042 225 : std::vector<Chord*> graceNotesBeforeBar;
1043 :
1044 225 : bool hasGraceBend = std::any_of(graceBeforeChords.begin(), graceBeforeChords.end(), [](Chord* ch) {
1045 9 : return std::any_of(ch->notes().begin(), ch->notes().end(), [](Note* n) {
1046 9 : return n->isGraceBendStart();
1047 9 : });
1048 : });
1049 :
1050 225 : if (!hasGraceBend) {
1051 224 : std::copy_if(graceBeforeChords.begin(), graceBeforeChords.end(), std::back_inserter(graceNotesBeforeBar), [](Chord* ch) {
1052 8 : return ch->noteType() == NoteType::ACCIACCATURA;
1053 : });
1054 : }
1055 :
1056 225 : const int currentTicks = currentChord->ticks().ticks();
1057 225 : IF_ASSERT_FAILED(currentTicks > 0) {
1058 0 : return trailtime;
1059 : }
1060 :
1061 225 : int reducedTicks = 0;
1062 :
1063 225 : const auto& notes = nextChord->notes();
1064 225 : if (hasGraceBend) {
1065 1 : reducedTicks = graceBendTicks(currentChord);
1066 224 : } else if (!graceNotesBeforeBar.empty()) {
1067 3 : reducedTicks = std::min(graceNotesBeforeBar[0]->ticks().ticks(), currentTicks / 2);
1068 : } else {
1069 221 : bool anySlidesIn = std::any_of(notes.begin(), notes.end(), [](const Note* note) {
1070 239 : return note->slideToType() == Note::SlideType::DownToNote
1071 239 : || (note->slideToType() == Note::SlideType::UpToNote && note->fret() > 1);
1072 : });
1073 :
1074 221 : if (anySlidesIn) {
1075 10 : reducedTicks = slideTicks(currentChord);
1076 : }
1077 : }
1078 :
1079 225 : trailtime += reducedTicks / (double)currentTicks * NoteEvent::NOTE_LENGTH;
1080 225 : return trailtime;
1081 225 : }
1082 :
1083 : //---------------------------------------------------------
1084 : // noteIsGlissandoStart
1085 : //---------------------------------------------------------
1086 :
1087 693 : bool CompatMidiRender::noteIsGlissandoStart(mu::engraving::Note* note)
1088 : {
1089 769 : for (Spanner* spanner : note->spannerFor()) {
1090 91 : if (spanner->isGlissando() && spanner->endElement() && spanner->endElement()->isNote()) {
1091 15 : return true;
1092 : }
1093 : }
1094 678 : return false;
1095 : }
1096 :
1097 28 : int CompatMidiRender::slideLengthChordDependent(Chord* chord)
1098 : {
1099 28 : const int currentTicks = chord->ticks().ticks();
1100 28 : if (currentTicks <= SLIDE_DURATION) {
1101 6 : return NoteEvent::NOTE_LENGTH / 2;
1102 : }
1103 :
1104 22 : return (double)NoteEvent::NOTE_LENGTH * SLIDE_DURATION / currentTicks;
1105 : }
1106 :
1107 329 : std::set<size_t> CompatMidiRender::getNotesIndexesToRender(Chord* chord)
1108 : {
1109 329 : std::set<size_t> notesIndexesToRender;
1110 :
1111 329 : auto& notes = chord->notes();
1112 : /// not adding sounds for the same pitches in chord (for example, on different strings)
1113 329 : std::map<int, int> longestPlayTicksForPitch;
1114 :
1115 680 : for (Note* note : notes) {
1116 351 : int pitch = note->pitch();
1117 351 : auto& pitchLength = longestPlayTicksForPitch[pitch];
1118 351 : pitchLength = std::max(pitchLength, note->playTicks());
1119 : }
1120 :
1121 351 : auto noteShouldBeRendered = [](Note* n) {
1122 379 : while (n->tieBackNonPartial() && n != n->tieBack()->startNote()) {
1123 28 : n = n->tieBack()->startNote();
1124 28 : if (findFirstTrill(n->chord())) {
1125 : // The previous tied note probably has events for this note too.
1126 : // That is, we don't need to render this note separately.
1127 0 : return false;
1128 : }
1129 :
1130 28 : for (Articulation* a : n->chord()->articulations()) {
1131 0 : if (a->isOrnament()) {
1132 0 : return false;
1133 : }
1134 : }
1135 : }
1136 :
1137 351 : return true;
1138 : };
1139 :
1140 680 : for (size_t i = 0; i < notes.size(); i++) {
1141 351 : Note* n = notes[i];
1142 351 : if (noteShouldBeRendered(n) && longestPlayTicksForPitch[n->pitch()] == n->playTicks()) {
1143 351 : notesIndexesToRender.insert(i);
1144 : }
1145 : }
1146 :
1147 329 : return notesIndexesToRender;
1148 329 : }
1149 :
1150 : //---------------------------------------------------------
1151 : // backGlissando
1152 : //---------------------------------------------------------
1153 :
1154 324 : Glissando* CompatMidiRender::backGlissando(Note* note)
1155 : {
1156 358 : for (Spanner* spanner : note->spannerBack()) {
1157 39 : if (spanner->isGlissando() && spanner->startElement() && spanner->startElement()->isNote()) {
1158 5 : return toGlissando(spanner);
1159 : }
1160 : }
1161 319 : return nullptr;
1162 : }
1163 :
1164 : //---------------------------------------------------------
1165 : // isGlissandoValid
1166 : //
1167 : // checking if glissando can be played
1168 : //---------------------------------------------------------
1169 :
1170 10 : bool CompatMidiRender::isGlissandoValid(Glissando* glissando)
1171 : {
1172 10 : EngravingItem* startItem = glissando->startElement();
1173 10 : EngravingItem* endItem = glissando->endElement();
1174 10 : if (!startItem || !endItem || !startItem->isNote() || !endItem->isNote()) {
1175 0 : return false;
1176 : }
1177 :
1178 10 : return toNote(startItem)->string() == toNote(endItem)->string();
1179 : }
1180 :
1181 29 : const Drumset* CompatMidiRender::getDrumset(const Chord* chord)
1182 : {
1183 29 : if (chord->staff() && chord->staff()->isDrumStaff(chord->tick())) {
1184 0 : const Drumset* ds = chord->staff()->part()->instrument(chord->tick())->drumset();
1185 0 : return ds;
1186 : }
1187 29 : return nullptr;
1188 : }
1189 :
1190 : //---------------------------------------------------------
1191 : // totalTiedNoteTicks
1192 : // return the total of the actualTicks of the given note plus
1193 : // the chain of zero or more notes tied to it to the right.
1194 : //---------------------------------------------------------
1195 :
1196 6 : int CompatMidiRender::totalTiedNoteTicks(Note* note)
1197 : {
1198 6 : Fraction total = note->chord()->actualTicks();
1199 6 : while (note->tieFor() && note->tieFor()->endNote() && (note->chord()->tick() < note->tieFor()->endNote()->chord()->tick())) {
1200 0 : note = note->tieFor()->endNote();
1201 0 : total += note->chord()->actualTicks();
1202 : }
1203 12 : return total.ticks();
1204 : }
1205 :
1206 10 : static int slideTicks(const Chord* chord)
1207 : {
1208 10 : const int currentTicks = chord->ticks().ticks();
1209 10 : if (currentTicks <= SLIDE_DURATION) {
1210 2 : return currentTicks / 2;
1211 : }
1212 :
1213 8 : return SLIDE_DURATION;
1214 : }
1215 :
1216 1 : static int graceBendTicks(const Chord* chord)
1217 : {
1218 1 : const int currentTicks = chord->ticks().ticks();
1219 1 : if (currentTicks / 2 <= GRACE_BEND_DURATION) {
1220 0 : return currentTicks / 2;
1221 : }
1222 :
1223 1 : return GRACE_BEND_DURATION;
1224 : }
1225 :
1226 543 : int CompatMidiRender::tick(const CompatMidiRendererInternal::Context& ctx, int tick)
1227 : {
1228 543 : return ctx.applyCaesuras ? ctx.pauseMap->tickWithPauses(tick) : tick;
1229 : }
1230 : }
|