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

Generated by: LCOV version 2.0-1