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

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

Generated by: LCOV version 2.0-1