LCOV - code coverage report
Current view: top level - muse/framework/mpe - events.h Coverage Total Hit
Test: filtered_coverage.info Lines: 93.6 % 109 102
Test Date: 2026-11-05 04:07:02

            Line data    Source code
       1              : /*
       2              :  * SPDX-License-Identifier: GPL-3.0-only
       3              :  * MuseScore-CLA-applies
       4              :  *
       5              :  * MuseScore Studio
       6              :  * Music Composition & Notation
       7              :  *
       8              :  * Copyright (C) 2025 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              : #pragma once
      23              : 
      24              : #include <variant>
      25              : #include <vector>
      26              : 
      27              : #include "async/channel.h"
      28              : #include "realfn.h"
      29              : #include "global/types/number.h"
      30              : #include "types/flags.h"
      31              : #include "types/number.h"
      32              : #include "types/sharedmap.h"
      33              : 
      34              : #include "automationpoint.h"
      35              : #include "mpetypes.h"
      36              : #include "playbacksetupdata.h"
      37              : 
      38              : namespace muse::mpe {
      39              : struct ArrangementContext
      40              : {
      41              :     timestamp_t nominalTimestamp = 0;
      42              :     timestamp_t actualTimestamp = 0;
      43              :     duration_t nominalDuration = 0;
      44              :     duration_t actualDuration = 0;
      45              :     voice_layer_idx_t voiceLayerIndex = 0;
      46              :     staff_layer_idx_t staffLayerIndex = 0;
      47              :     double bps = 0.0;
      48              : 
      49            0 :     bool hasStart() const
      50              :     {
      51            0 :         return actualDuration > 0;
      52              :     }
      53              : 
      54            0 :     bool hasEnd() const
      55              :     {
      56            0 :         return actualDuration != mpe::INFINITE_DURATION;
      57              :     }
      58              : 
      59            2 :     bool operator==(const ArrangementContext& other) const
      60              :     {
      61            2 :         return nominalTimestamp == other.nominalTimestamp
      62            2 :                && actualTimestamp == other.actualTimestamp
      63            2 :                && nominalDuration == other.nominalDuration
      64            2 :                && actualDuration == other.actualDuration
      65            2 :                && voiceLayerIndex == other.voiceLayerIndex
      66            2 :                && staffLayerIndex == other.staffLayerIndex
      67            4 :                && muse::is_equal(bps, other.bps);
      68              :     }
      69              : };
      70              : 
      71              : struct PitchContext
      72              : {
      73              :     pitch_level_t nominalPitchLevel = 0;
      74              :     PitchCurve pitchCurve;
      75              : 
      76            6 :     bool operator==(const PitchContext& other) const
      77              :     {
      78            6 :         return nominalPitchLevel == other.nominalPitchLevel
      79            6 :                && pitchCurve == other.pitchCurve;
      80              :     }
      81              : };
      82              : 
      83              : struct ExpressionContext
      84              : {
      85              :     ArticulationMap articulations;
      86              :     dynamic_level_t nominalDynamicLevel = MIN_DYNAMIC_LEVEL;
      87              :     ExpressionCurve expressionCurve;
      88              :     std::optional<float> velocityOverride;
      89              : 
      90            6 :     bool operator==(const ExpressionContext& other) const
      91              :     {
      92            6 :         return articulations == other.articulations
      93            3 :                && nominalDynamicLevel == other.nominalDynamicLevel
      94            3 :                && expressionCurve == other.expressionCurve
      95            9 :                && velocityOverride == velocityOverride;
      96              :     }
      97              : };
      98              : 
      99              : struct NoteEvent
     100              : {
     101            1 :     NoteEvent() = default;
     102              : 
     103            2 :     explicit NoteEvent(ArrangementContext&& arrangementCtx,
     104              :                        PitchContext&& pitchCtx,
     105              :                        ExpressionContext&& expressionCtx)
     106            2 :         : m_arrangementCtx(arrangementCtx),
     107            2 :         m_pitchCtx(pitchCtx),
     108            2 :         m_expressionCtx(expressionCtx)
     109              :     {
     110            2 :     }
     111              : 
     112         2393 :     explicit NoteEvent(const timestamp_t nominalTimestamp,
     113              :                        const duration_t nominalDuration,
     114              :                        const voice_layer_idx_t voiceIdx,
     115              :                        const staff_layer_idx_t staffIdx,
     116              :                        const pitch_level_t nominalPitchLevel,
     117              :                        const dynamic_level_t nominalDynamicLevel,
     118              :                        const ArticulationMap& articulationsApplied,
     119              :                        const double bps,
     120              :                        const float requiredVelocityFraction = 0.f,
     121              :                        const PitchCurve& requiredPitchCurve = {})
     122         2393 :     {
     123         2393 :         m_arrangementCtx.nominalDuration = nominalDuration;
     124         2393 :         m_arrangementCtx.nominalTimestamp = nominalTimestamp;
     125         2393 :         m_arrangementCtx.voiceLayerIndex = voiceIdx;
     126         2393 :         m_arrangementCtx.staffLayerIndex = staffIdx;
     127         2393 :         m_arrangementCtx.bps = bps;
     128              : 
     129         2393 :         m_pitchCtx.nominalPitchLevel = nominalPitchLevel;
     130              : 
     131         2393 :         m_expressionCtx.articulations = articulationsApplied;
     132         2393 :         m_expressionCtx.nominalDynamicLevel = nominalDynamicLevel;
     133              : 
     134         2393 :         setUp(requiredVelocityFraction, requiredPitchCurve);
     135         2393 :     }
     136              : 
     137         3002 :     const ArrangementContext& arrangementCtx() const
     138              :     {
     139         3002 :         return m_arrangementCtx;
     140              :     }
     141              : 
     142          423 :     const PitchContext& pitchCtx() const
     143              :     {
     144          423 :         return m_pitchCtx;
     145              :     }
     146              : 
     147         1202 :     const ExpressionContext& expressionCtx() const
     148              :     {
     149         1202 :         return m_expressionCtx;
     150              :     }
     151              : 
     152            1 :     bool operator==(const NoteEvent& other) const
     153              :     {
     154            1 :         return m_arrangementCtx == other.m_arrangementCtx
     155            1 :                && m_pitchCtx == other.m_pitchCtx
     156            2 :                && m_expressionCtx == other.m_expressionCtx;
     157              :     }
     158              : 
     159              : private:
     160              : 
     161              :     template<typename T>
     162         5834 :     inline T mult(T v, float f)
     163              :     {
     164         5834 :         return static_cast<T>(static_cast<float>(v) * f);
     165              :     }
     166              : 
     167         2393 :     void setUp(const float requiredVelocityFraction, const PitchCurve& requiredPitchCurve)
     168              :     {
     169         2393 :         calculateActualDuration(m_expressionCtx.articulations);
     170         2393 :         calculateActualTimestamp(m_expressionCtx.articulations);
     171              : 
     172         2393 :         if (requiredPitchCurve.empty()) {
     173         2383 :             calculatePitchCurve(m_expressionCtx.articulations);
     174              :         } else {
     175           10 :             m_pitchCtx.pitchCurve = requiredPitchCurve;
     176              :         }
     177              : 
     178         2393 :         calculateExpressionCurve(m_expressionCtx.articulations, requiredVelocityFraction);
     179         2393 :     }
     180              : 
     181         2393 :     void calculateActualTimestamp(const ArticulationMap& articulationsApplied)
     182              :     {
     183         2393 :         m_arrangementCtx.actualTimestamp = m_arrangementCtx.nominalTimestamp;
     184              : 
     185         2393 :         if (articulationsApplied.empty()) {
     186            9 :             return;
     187              :         }
     188              : 
     189         2384 :         timestamp_t timestampOffsetValue = mult(m_arrangementCtx.nominalDuration,
     190              :                                                 percentageToFactor(articulationsApplied.averageTimestampOffset()));
     191              : 
     192         2384 :         m_arrangementCtx.actualTimestamp += timestampOffsetValue;
     193              :     }
     194              : 
     195         2393 :     void calculateActualDuration(const ArticulationMap& articulationsApplied)
     196              :     {
     197         2393 :         m_arrangementCtx.actualDuration = m_arrangementCtx.nominalDuration;
     198              : 
     199         2393 :         if (articulationsApplied.empty() || m_arrangementCtx.nominalDuration == INFINITE_DURATION) {
     200            9 :             return;
     201              :         }
     202              : 
     203         2384 :         m_arrangementCtx.actualDuration = mult(m_arrangementCtx.actualDuration,
     204              :                                                percentageToFactor(articulationsApplied.averageDurationFactor()));
     205              :     }
     206              : 
     207         2383 :     void calculatePitchCurve(const ArticulationMap& articulationsApplied)
     208              :     {
     209         2383 :         m_pitchCtx.pitchCurve = articulationsApplied.averagePitchOffsetMap();
     210              : 
     211         2383 :         pitch_level_t averagePitchRange = articulationsApplied.averagePitchRange();
     212         2383 :         if (averagePitchRange == 0 || averagePitchRange == PITCH_LEVEL_STEP) {
     213         2359 :             return;
     214              :         }
     215              : 
     216           24 :         float ratio = static_cast<float>(averagePitchRange) / static_cast<float>(PITCH_LEVEL_STEP);
     217           24 :         float patternUnitRatio = PITCH_LEVEL_STEP / static_cast<float>(ONE_PERCENT);
     218              : 
     219          288 :         for (auto& pair : m_pitchCtx.pitchCurve) {
     220          264 :             pair.second = static_cast<pitch_level_t>(RealRound(static_cast<float>(pair.second) * ratio * patternUnitRatio, 0));
     221              :         }
     222              :     }
     223              : 
     224         2393 :     void calculateExpressionCurve(const ArticulationMap& articulationsApplied, const float requiredVelocityFraction)
     225              :     {
     226         2393 :         m_expressionCtx.expressionCurve = articulationsApplied.averageDynamicOffsetMap();
     227              : 
     228         2393 :         if (!RealIsNull(requiredVelocityFraction)) {
     229            0 :             m_expressionCtx.velocityOverride = requiredVelocityFraction;
     230              :         }
     231              : 
     232         2393 :         if (m_expressionCtx.expressionCurve.empty()) {
     233         1327 :             return;
     234              :         }
     235              : 
     236         1066 :         dynamic_level_t articulationDynamicLevel = articulationsApplied.averageMaxAmplitudeLevel();
     237         1066 :         dynamic_level_t nominalDynamicLevel = m_expressionCtx.nominalDynamicLevel;
     238              : 
     239         1066 :         constexpr dynamic_level_t naturalDynamicLevel = dynamicLevelFromType(DynamicType::Natural);
     240              : 
     241         1066 :         float dynamicAmplifyFactor = static_cast<float>(articulationDynamicLevel - naturalDynamicLevel) / DYNAMIC_LEVEL_STEP;
     242              : 
     243         1066 :         dynamic_level_t amplificationDiff = mult(std::max(articulationsApplied.averageDynamicRange(), DYNAMIC_LEVEL_STEP),
     244              :                                                  dynamicAmplifyFactor);
     245              : 
     246         1066 :         dynamic_level_t actualDynamicLevel = nominalDynamicLevel + amplificationDiff;
     247              : 
     248         1066 :         if (actualDynamicLevel == articulationDynamicLevel) {
     249          833 :             return;
     250              :         }
     251              : 
     252          233 :         float ratio = static_cast<float>(actualDynamicLevel) / static_cast<float>(articulationDynamicLevel);
     253              : 
     254         2796 :         for (auto& pair : m_expressionCtx.expressionCurve) {
     255         2563 :             pair.second = static_cast<dynamic_level_t>(RealRound(pair.second * ratio, 0));
     256              :         }
     257              :     }
     258              : 
     259              :     ArrangementContext m_arrangementCtx;
     260              :     PitchContext m_pitchCtx;
     261              :     ExpressionContext m_expressionCtx;
     262              : };
     263              : 
     264              : struct ControllerChangeEvent {
     265              :     enum Type : signed char {
     266              :         Undefined = -1,
     267              :         Modulation,
     268              :         SustainPedalOnOff,
     269              :         PitchBend,
     270              :     };
     271              : 
     272              :     using Value = muse::number_t<float>;
     273              : 
     274              :     Type type = Undefined;
     275              :     Value val; // [0;1]
     276              :     layer_idx_t layerIdx = 0;
     277              : 
     278            1 :     bool operator==(const ControllerChangeEvent& e) const
     279              :     {
     280            1 :         return type == e.type && val == e.val && layerIdx == e.layerIdx;
     281              :     }
     282              : };
     283              : 
     284              : using ControllerChangeEventList = std::vector<ControllerChangeEvent>;
     285              : 
     286              : struct TextArticulationEvent {
     287              :     enum FlagType : unsigned char {
     288              :         NoFlags = 0,
     289              :         StartsAtPlaybackPosition,
     290              :     };
     291              : 
     292              :     String text;
     293              :     layer_idx_t layerIdx = 0;
     294              :     Flags<FlagType> flags;
     295              : 
     296           39 :     bool operator==(const TextArticulationEvent& e) const
     297              :     {
     298           39 :         return text == e.text && layerIdx == e.layerIdx && flags == e.flags;
     299              :     }
     300              : };
     301              : 
     302              : using TextArticulationEventList = std::vector<TextArticulationEvent>;
     303              : 
     304              : struct SoundPresetChangeEvent {
     305              :     String code;
     306              :     layer_idx_t layerIdx = 0;
     307              : 
     308            1 :     bool operator==(const SoundPresetChangeEvent& e) const
     309              :     {
     310            1 :         return code == e.code && layerIdx == e.layerIdx;
     311              :     }
     312              : };
     313              : 
     314              : using SoundPresetChangeEventList = std::vector<SoundPresetChangeEvent>;
     315              : 
     316              : struct SyllableEvent {
     317              :     enum FlagType : unsigned char {
     318              :         NoFlags = 0,
     319              :         StartsAtPlaybackPosition,
     320              :         HyphenedToNext,
     321              :     };
     322              : 
     323              :     String text;
     324              :     layer_idx_t layerIdx = 0;
     325              :     Flags<FlagType> flags;
     326              : 
     327            1 :     bool operator==(const SyllableEvent& e) const
     328              :     {
     329            1 :         return text == e.text && layerIdx == e.layerIdx && flags == e.flags;
     330              :     }
     331              : };
     332              : 
     333              : using SyllableEventList = std::vector<SyllableEvent>;
     334              : 
     335              : using PlaybackEvent = std::variant<std::monostate,
     336              :                                    NoteEvent,
     337              :                                    TextArticulationEvent,
     338              :                                    SoundPresetChangeEvent,
     339              :                                    SyllableEvent,
     340              :                                    ControllerChangeEvent>;
     341              : 
     342              : using PlaybackEventList = std::vector<PlaybackEvent>;
     343              : using PlaybackEventsMap = SharedMap<timestamp_t, PlaybackEventList>;
     344              : 
     345              : using DynamicAutomationMap = AutomationCurve<timestamp_t>;
     346              : using DynamicAutomationLayers = SharedMap<layer_idx_t, DynamicAutomationMap>;
     347              : 
     348              : using MainStreamChanges = async::Channel<PlaybackEventsMap, DynamicAutomationLayers>;
     349              : using OffStreamChanges = async::Channel<PlaybackEventsMap, bool /*flushOffstream*/>;
     350              : 
     351              : struct PlaybackData {
     352              :     PlaybackEventsMap originEvents;
     353              :     PlaybackSetupData setupData;
     354              :     DynamicAutomationLayers dynamics;
     355              : 
     356              :     MainStreamChanges mainStream;
     357              :     OffStreamChanges offStream;
     358              : 
     359            1 :     bool operator==(const PlaybackData& other) const
     360              :     {
     361            1 :         return originEvents == other.originEvents
     362            1 :                && setupData == other.setupData
     363            2 :                && dynamics == other.dynamics;
     364              :     }
     365              : 
     366            0 :     bool isValid() const
     367              :     {
     368            0 :         return setupData.isValid();
     369              :     }
     370              : };
     371              : }
        

Generated by: LCOV version 2.0-1