LCOV - code coverage report
Current view: top level - src/engraving/compat/midi - compatmidirender.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 81.8 % 638 522
Test Date: 2026-11-05 04:07:02

            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              : }
        

Generated by: LCOV version 2.0-1