LCOV - code coverage report
Current view: top level - muse/framework/musesampler/internal - musesamplersequencer.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 0.0 % 339 0
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              : 
      23              : #include "musesamplersequencer.h"
      24              : 
      25              : #include <algorithm>
      26              : 
      27              : #include "apitypes.h"
      28              : 
      29              : using namespace muse;
      30              : using namespace muse::musesampler;
      31              : using namespace muse::mpe;
      32              : 
      33              : static const std::unordered_map<ArticulationType, ms_NoteArticulation> ARTICULATION_TYPES_PART1 {
      34              :     { ArticulationType::Standard, ms_NoteArticulation_None },
      35              :     { ArticulationType::Staccato, ms_NoteArticulation_Staccato },
      36              :     { ArticulationType::Staccatissimo, ms_NoteArticulation_Staccatissimo },
      37              :     { ArticulationType::Accent, ms_NoteArticulation_Accent },
      38              :     { ArticulationType::Tenuto, ms_NoteArticulation_Tenuto },
      39              :     { ArticulationType::Marcato, ms_NoteArticulation_Marcato },
      40              :     { ArticulationType::Harmonic, ms_NoteArticulation_Harmonics },
      41              :     { ArticulationType::PalmMute, ms_NoteArticulation_PalmMute },
      42              :     { ArticulationType::Mute, ms_NoteArticulation_Mute },
      43              :     { ArticulationType::Legato, ms_NoteArticulation_Slur },
      44              :     { ArticulationType::Trill, ms_NoteArticulation_Trill },
      45              :     { ArticulationType::TrillBaroque, ms_NoteArticulation_Trill },
      46              : 
      47              :     { ArticulationType::Arpeggio, ms_NoteArticulation_ArpeggioUp },
      48              :     { ArticulationType::ArpeggioUp, ms_NoteArticulation_ArpeggioUp },
      49              :     { ArticulationType::ArpeggioDown, ms_NoteArticulation_ArpeggioDown },
      50              : 
      51              :     { ArticulationType::Tremolo8th, ms_NoteArticulation_Tremolo1 },
      52              :     { ArticulationType::Tremolo16th, ms_NoteArticulation_Tremolo2 },
      53              :     { ArticulationType::Tremolo32nd, ms_NoteArticulation_Tremolo3 },
      54              :     { ArticulationType::Tremolo64th, ms_NoteArticulation_Tremolo3 },
      55              :     { ArticulationType::TremoloBuzz, ms_NoteArticulation_BuzzTremolo },
      56              : 
      57              :     { ArticulationType::DiscreteGlissando, ms_NoteArticulation_Glissando },
      58              :     { ArticulationType::ContinuousGlissando, ms_NoteArticulation_Portamento },
      59              :     { ArticulationType::Slide, ms_NoteArticulation_Portamento },
      60              : 
      61              :     { ArticulationType::Scoop, ms_NoteArticulation_Scoop },
      62              :     { ArticulationType::Plop, ms_NoteArticulation_Plop },
      63              :     { ArticulationType::Doit, ms_NoteArticulation_Doit },
      64              :     { ArticulationType::Fall, ms_NoteArticulation_Fall },
      65              :     { ArticulationType::PreAppoggiatura, ms_NoteArticulation_Appoggiatura },
      66              :     { ArticulationType::PostAppoggiatura, ms_NoteArticulation_Appoggiatura },
      67              :     { ArticulationType::Acciaccatura, ms_NoteArticulation_Acciaccatura },
      68              : 
      69              :     { ArticulationType::Open, ms_NoteArticulation_Open },
      70              : 
      71              :     { ArticulationType::Pizzicato, ms_NoteArticulation_Pizzicato },
      72              :     { ArticulationType::SnapPizzicato, ms_NoteArticulation_SnapPizzicato },
      73              : 
      74              :     { ArticulationType::UpperMordent, ms_NoteArticulation_MordentWhole },
      75              :     { ArticulationType::UpMordent, ms_NoteArticulation_MordentWhole },
      76              :     { ArticulationType::LowerMordent, ms_NoteArticulation_MordentInvertedWhole },
      77              :     { ArticulationType::DownMordent, ms_NoteArticulation_MordentInvertedWhole },
      78              :     { ArticulationType::Turn, ms_NoteArticulation_TurnWholeWhole },
      79              :     { ArticulationType::InvertedTurn, ms_NoteArticulation_TurnInvertedWholeWhole },
      80              : 
      81              :     { ArticulationType::ColLegno, ms_NoteArticulation_ColLegno },
      82              :     { ArticulationType::SulTasto, ms_NoteArticulation_SulTasto },
      83              :     { ArticulationType::SulPont, ms_NoteArticulation_SulPonticello },
      84              : 
      85              :     { ArticulationType::MalletBellOnTable, ms_NoteArticulation_MalletBellOnTable },
      86              :     { ArticulationType::MalletBellSuspended, ms_NoteArticulation_MalletBellSuspended },
      87              :     { ArticulationType::MalletLift, ms_NoteArticulation_MalletLift },
      88              :     { ArticulationType::PluckLift, ms_NoteArticulation_PluckLift },
      89              :     { ArticulationType::Gyro, ms_NoteArticulation_Gyro },
      90              :     { ArticulationType::Martellato, ms_NoteArticulation_Martellato },
      91              :     { ArticulationType::MartellatoLift, ms_NoteArticulation_MartellatoLift },
      92              :     { ArticulationType::HandMartellato, ms_NoteArticulation_HandMartellato },
      93              :     { ArticulationType::MutedMartellato, ms_NoteArticulation_MutedMartellato },
      94              : 
      95              :     { ArticulationType::LaissezVibrer, ms_NoteArticulation_LaissezVibrer },
      96              : 
      97              :     { ArticulationType::LeftHandTapping, ms_NoteArticulation_LeftHandTapping },
      98              :     { ArticulationType::RightHandTapping, ms_NoteArticulation_RightHandTapping },
      99              : };
     100              : 
     101              : static const std::unordered_map<ArticulationType, ms_NoteArticulation2> ARTICULATION_TYPES_PART2 {
     102              :     { ArticulationType::Standard, ms_NoteArticulation2_None },
     103              :     { ArticulationType::Ring, ms_NoteArticulation2_Ring },
     104              :     { ArticulationType::ThumbDamp, ms_NoteArticulation2_ThumbDamp },
     105              :     { ArticulationType::BrushDamp, ms_NoteArticulation2_BrushDamp },
     106              :     { ArticulationType::RingTouch, ms_NoteArticulation2_RingTouch },
     107              :     { ArticulationType::Pluck, ms_NoteArticulation2_Pluck },
     108              :     { ArticulationType::SingingBell, ms_NoteArticulation2_SingingBell },
     109              :     { ArticulationType::SingingVibrate, ms_NoteArticulation2_SingingVibrate },
     110              :     { ArticulationType::Swing, ms_NoteArticulation2_HandbellSwing },
     111              :     { ArticulationType::Echo, ms_NoteArticulation2_Echo },
     112              :     { ArticulationType::Breath, ms_NoteArticulation2_BreathMark },
     113              : };
     114              : 
     115              : static const std::unordered_map<ArticulationType, ms_NoteHead> NOTEHEAD_TYPES {
     116              :     { ArticulationType::CrossNote, ms_NoteHead_XNote },
     117              :     { ArticulationType::CrossLargeNote, ms_NoteHead_LargeX },
     118              :     { ArticulationType::CrossOrnateNote, ms_NoteHead_OrnateXNote },
     119              :     { ArticulationType::GhostNote, ms_NoteHead_Ghost },
     120              :     { ArticulationType::CircleNote, ms_NoteHead_Circle },
     121              :     { ArticulationType::CircleCrossNote, ms_NoteHead_CircleXNote },
     122              :     { ArticulationType::CircleDotNote, ms_NoteHead_CircleDot },
     123              :     { ArticulationType::TriangleLeftNote, ms_NoteHead_Triangle },
     124              :     { ArticulationType::TriangleRightNote, ms_NoteHead_TriangleRight },
     125              :     { ArticulationType::TriangleUpNote, ms_NoteHead_TriangleUp },
     126              :     { ArticulationType::TriangleDownNote, ms_NoteHead_TriangleDown },
     127              :     { ArticulationType::TriangleRoundDownNote, ms_NoteHead_TriangleRoundDown },
     128              :     { ArticulationType::DiamondNote, ms_NoteHead_Diamond },
     129              :     { ArticulationType::MoonNote, ms_NoteHead_FlatTop },
     130              :     { ArticulationType::PlusNote, ms_NoteHead_Plus },
     131              :     { ArticulationType::SlashNote, ms_NoteHead_Slash },
     132              :     { ArticulationType::SquareNote, ms_NoteHead_Square },
     133              :     { ArticulationType::SlashedBackwardsNote, ms_NoteHead_SlashRightFilled },
     134              :     { ArticulationType::SlashedForwardsNote, ms_NoteHead_SlashLeftFilled },
     135              : };
     136              : 
     137            0 : void MuseSamplerSequencer::init(MuseSamplerLibHandlerPtr samplerLib, ms_MuseSampler sampler, IMuseSamplerTracks* tracks,
     138              :                                 std::string&& defaultPresetCode)
     139              : {
     140            0 :     m_samplerLib = samplerLib;
     141            0 :     m_sampler = sampler;
     142            0 :     m_tracks = tracks;
     143            0 :     m_defaultPresetCode = std::move(defaultPresetCode);
     144            0 : }
     145              : 
     146            0 : void MuseSamplerSequencer::updateMainStreamEvents(const PlaybackEventsMap& events, const DynamicAutomationLayers& dynamics)
     147              : {
     148            0 :     IF_ASSERT_FAILED(m_samplerLib && m_sampler) {
     149            0 :         return;
     150              :     }
     151              : 
     152            0 :     clearAllTracks();
     153              : 
     154            0 :     loadEvents(events);
     155            0 :     loadDynamicEvents(dynamics);
     156              : 
     157            0 :     finalizeAllTracks();
     158              : }
     159              : 
     160            0 : void MuseSamplerSequencer::updateOffStreamEvents(const PlaybackEventsMap& events)
     161              : {
     162            0 :     m_auditionParamsCache.clear();
     163              : 
     164            0 :     for (const auto& pair : events) {
     165            0 :         for (const auto& event : pair.second) {
     166            0 :             if (std::holds_alternative<mpe::NoteEvent>(event)) {
     167            0 :                 addAuditionNoteEvent(std::get<mpe::NoteEvent>(event));
     168            0 :             } else if (std::holds_alternative<mpe::ControllerChangeEvent>(event)) {
     169            0 :                 addAuditionCCEvent(std::get<mpe::ControllerChangeEvent>(event), pair.first);
     170              :             } else {
     171            0 :                 parseAuditionParams(event, m_auditionParamsCache);
     172              :             }
     173              :         }
     174              :     }
     175              : 
     176            0 :     updateOffSequenceIterator();
     177            0 : }
     178              : 
     179            0 : void MuseSamplerSequencer::clearAllTracks()
     180              : {
     181            0 :     m_layerIdxToTrackIdx.clear();
     182            0 :     m_presetChangesByTrack.clear();
     183              : 
     184            0 :     for (ms_Track track : allTracks()) {
     185            0 :         m_samplerLib->clearTrack(m_sampler, track);
     186              :     }
     187            0 : }
     188              : 
     189            0 : void MuseSamplerSequencer::finalizeAllTracks()
     190              : {
     191            0 :     for (ms_Track track : allTracks()) {
     192            0 :         m_samplerLib->finalizeTrack(m_sampler, track);
     193              :     }
     194            0 : }
     195              : 
     196            0 : ms_Track MuseSamplerSequencer::findOrCreateTrack(layer_idx_t layerIdx)
     197              : {
     198            0 :     const TrackList& tracks = m_tracks->allTracks();
     199            0 :     auto it = m_layerIdxToTrackIdx.find(layerIdx);
     200              : 
     201              :     // A track has already been assigned to this layer
     202            0 :     if (it != m_layerIdxToTrackIdx.end()) {
     203            0 :         if (it->second < tracks.size()) {
     204            0 :             return tracks.at(it->second);
     205              :         }
     206              : 
     207            0 :         ASSERT_X("Invalid track index");
     208              :         m_layerIdxToTrackIdx.erase(it);
     209              :     }
     210              : 
     211              :     // Try to find a free track
     212            0 :     std::unordered_set<track_idx_t> assignedTracks(m_layerIdxToTrackIdx.size());
     213            0 :     for (const auto& pair: m_layerIdxToTrackIdx) {
     214            0 :         assignedTracks.insert(pair.second);
     215              :     }
     216              : 
     217            0 :     for (track_idx_t trackIdx = 0; trackIdx < tracks.size(); ++trackIdx) {
     218            0 :         if (!muse::contains(assignedTracks, trackIdx)) {
     219            0 :             m_layerIdxToTrackIdx.emplace(layerIdx, trackIdx);
     220            0 :             return tracks.at(trackIdx);
     221              :         }
     222              :     }
     223              : 
     224              :     // Add a new track
     225            0 :     ms_Track newTrack = m_tracks->addTrack();
     226            0 :     if (newTrack) {
     227            0 :         m_layerIdxToTrackIdx.emplace(layerIdx, tracks.size() - 1);
     228            0 :         return newTrack;
     229              :     }
     230              : 
     231              :     // Could not add the track. Use 1st track as a fallback
     232            0 :     if (!tracks.empty()) {
     233            0 :         m_layerIdxToTrackIdx.emplace(layerIdx, 0);
     234            0 :         return tracks.front();
     235              :     }
     236              : 
     237            0 :     UNREACHABLE;
     238              :     return nullptr;
     239            0 : }
     240              : 
     241            0 : ms_Track MuseSamplerSequencer::findTrack(layer_idx_t layerIdx) const
     242              : {
     243            0 :     auto it = m_layerIdxToTrackIdx.find(layerIdx);
     244            0 :     if (it == m_layerIdxToTrackIdx.end()) {
     245            0 :         return nullptr;
     246              :     }
     247              : 
     248            0 :     const TrackList& tracks = m_tracks->allTracks();
     249            0 :     IF_ASSERT_FAILED(it->second < tracks.size()) {
     250            0 :         return nullptr;
     251              :     }
     252              : 
     253            0 :     return tracks.at(it->second);
     254              : }
     255              : 
     256            0 : const TrackList& MuseSamplerSequencer::allTracks() const
     257              : {
     258            0 :     IF_ASSERT_FAILED(m_tracks) {
     259            0 :         static const TrackList dummy;
     260            0 :         return dummy;
     261              :     }
     262              : 
     263            0 :     return m_tracks->allTracks();
     264              : }
     265              : 
     266            0 : void MuseSamplerSequencer::loadEvents(const PlaybackEventsMap& changes)
     267              : {
     268            0 :     for (const auto& pair : changes) {
     269            0 :         for (const auto& event : pair.second) {
     270            0 :             if (std::holds_alternative<mpe::NoteEvent>(event)) {
     271            0 :                 addNoteEvent(std::get<mpe::NoteEvent>(event));
     272            0 :             } else if (std::holds_alternative<mpe::TextArticulationEvent>(event)) {
     273            0 :                 addTextArticulationEvent(std::get<mpe::TextArticulationEvent>(event), pair.first);
     274            0 :             } else if (std::holds_alternative<mpe::SoundPresetChangeEvent>(event)) {
     275            0 :                 addSoundPresetEvent(std::get<mpe::SoundPresetChangeEvent>(event), pair.first);
     276            0 :             } else if (std::holds_alternative<mpe::SyllableEvent>(event)) {
     277            0 :                 addSyllableEvent(std::get<mpe::SyllableEvent>(event), pair.first);
     278              :             }
     279              :         }
     280              :     }
     281            0 : }
     282              : 
     283            0 : void MuseSamplerSequencer::loadDynamicEvents(const DynamicAutomationLayers& changes)
     284              : {
     285            0 :     constexpr timestamp_t STEP_INTERVAL_US = 30000;
     286              : 
     287            0 :     for (const auto& layer : changes) {
     288            0 :         ms_Track track = findTrack(layer.first);
     289            0 :         if (!track) {
     290            0 :             continue;
     291              :         }
     292              : 
     293            0 :         std::optional<double> lastRatio;
     294              : 
     295            0 :         resampleCurve(layer.second, STEP_INTERVAL_US, [&](timestamp_t t, real_t normalized) {
     296            0 :             const double ratio = dynamicLevelRatio(dynamicLevelFromNormalized(normalized));
     297            0 :             if (lastRatio.has_value() && RealIsEqual(*lastRatio, ratio)) {
     298            0 :                 return;
     299              :             }
     300            0 :             lastRatio = ratio;
     301            0 :             m_samplerLib->addDynamicsEvent(m_sampler, track, DynamicEvent { t, ratio });
     302              :         });
     303              :     }
     304            0 : }
     305              : 
     306            0 : void MuseSamplerSequencer::addNoteEvent(const mpe::NoteEvent& noteEvent)
     307              : {
     308            0 :     IF_ASSERT_FAILED(m_samplerLib && m_sampler && m_tracks) {
     309            0 :         return;
     310              :     }
     311              : 
     312            0 :     const mpe::ArrangementContext& arrangementCtx = noteEvent.arrangementCtx();
     313            0 :     const mpe::ArticulationMap& articulations = noteEvent.expressionCtx().articulations;
     314            0 :     const voice_layer_idx_t voiceIdx = arrangementCtx.voiceLayerIndex;
     315            0 :     const layer_idx_t layerIdx = makeLayerIdx(arrangementCtx.staffLayerIndex, voiceIdx);
     316              : 
     317            0 :     ms_Track track = findOrCreateTrack(layerIdx);
     318            0 :     IF_ASSERT_FAILED(track) {
     319            0 :         return;
     320              :     }
     321              : 
     322            0 :     for (const auto& art : articulations) {
     323            0 :         if (art.first == ArticulationType::Pedal) {
     324            0 :             addPedalEvent(art.second.meta, track);
     325            0 :             continue;
     326              :         }
     327              : 
     328            0 :         const ms_NoteArticulation ms_art = muse::value(ARTICULATION_TYPES_PART1, art.first, ms_NoteArticulation_None);
     329            0 :         if (m_samplerLib->isRangedArticulation(ms_art)) {
     330              :             // If this starts an articulation range, indicate the start
     331            0 :             if (art.second.occupiedFrom == 0 && art.second.occupiedTo != HUNDRED_PERCENT) {
     332            0 :                 if (m_samplerLib->addTrackEventRangeStart(m_sampler, track, voiceIdx, ms_art) != ms_Result_OK) {
     333            0 :                     LOGE() << "Unable to add ranged articulation range start";
     334              :                 }
     335              :             }
     336              :         }
     337              :     }
     338              : 
     339              :     musesampler::NoteEvent event;
     340            0 :     event._voice = voiceIdx;
     341            0 :     event._location_us = arrangementCtx.nominalTimestamp;
     342            0 :     event._duration_us = arrangementCtx.nominalDuration;
     343            0 :     event._tempo = arrangementCtx.bps * 60.0; // API expects BPM
     344              : 
     345            0 :     pitchAndTuning(noteEvent.pitchCtx().nominalPitchLevel, event._pitch, event._offset_cents);
     346            0 :     parseArticulations(articulations, event._articulation, event._articulation_2, event._notehead);
     347              : 
     348            0 :     long long noteEventId = 0;
     349            0 :     if (!m_samplerLib->addNoteEvent(m_sampler, track, event, noteEventId)) {
     350            0 :         LOGE() << "Unable to add event for track, timestamp: " << event._location_us;
     351              :     }
     352              : 
     353            0 :     for (auto& art : articulations) {
     354            0 :         const ms_NoteArticulation ms_art = muse::value(ARTICULATION_TYPES_PART1, art.first, ms_NoteArticulation_None);
     355            0 :         if (m_samplerLib->isRangedArticulation(ms_art)) {
     356              :             // If this ends an articulation range, indicate the end
     357            0 :             if (art.second.occupiedFrom != 0 && art.second.occupiedTo == HUNDRED_PERCENT) {
     358            0 :                 if (m_samplerLib->addTrackEventRangeEnd(m_sampler, track, voiceIdx, ms_art) != ms_Result_OK) {
     359            0 :                     LOGE() << "Unable to add ranged articulation range end";
     360              :                 }
     361              :             }
     362              :         }
     363              :     }
     364              : 
     365            0 :     if (!noteEvent.pitchCtx().pitchCurve.empty() && articulations.contains(ArticulationType::Multibend)) {
     366            0 :         addPitchBends(noteEvent, noteEventId, track);
     367              :     }
     368              : 
     369            0 :     if (articulations.contains(ArticulationType::Vibrato)) {
     370            0 :         addVibrato(noteEvent, noteEventId, track);
     371              :     }
     372              : }
     373              : 
     374            0 : void MuseSamplerSequencer::addPedalEvent(const mpe::ArticulationMeta& meta, ms_Track track)
     375              : {
     376            0 :     if (meta.hasStart()) {
     377            0 :         m_samplerLib->addPedalEvent(m_sampler, track, PedalEvent { meta.timestamp, 1.0 });
     378              :     }
     379              : 
     380            0 :     if (meta.hasEnd()) {
     381            0 :         m_samplerLib->addPedalEvent(m_sampler, track, PedalEvent { meta.timestamp + meta.overallDuration, 0.0 });
     382              :     }
     383            0 : }
     384              : 
     385            0 : void MuseSamplerSequencer::addTextArticulationEvent(const mpe::TextArticulationEvent& event, long long startUs)
     386              : {
     387            0 :     if (event.text.empty()) {
     388            0 :         return;
     389              :     }
     390              : 
     391            0 :     ms_Track track = findOrCreateTrack(event.layerIdx);
     392            0 :     IF_ASSERT_FAILED(track) {
     393            0 :         return;
     394              :     }
     395              : 
     396              :     // Make sure that the string exists long enough
     397            0 :     const std::string str = event.text.toStdString();
     398              : 
     399              :     ms_TextArticulationEvent evt;
     400            0 :     evt._start_us = startUs;
     401              : 
     402            0 :     if (event.text != ORDINARY_PLAYING_TECHNIQUE_CODE) {
     403            0 :         evt._articulation = str.c_str();
     404              :     } else {
     405            0 :         evt._articulation = ""; // resets the active articulation
     406              :     }
     407              : 
     408            0 :     m_samplerLib->addTextArticulationEvent(m_sampler, track, evt);
     409            0 : }
     410              : 
     411            0 : void MuseSamplerSequencer::addSoundPresetEvent(const mpe::SoundPresetChangeEvent& event, long long startUs)
     412              : {
     413            0 :     if (event.code.empty()) {
     414            0 :         return;
     415              :     }
     416              : 
     417            0 :     ms_Track track = findOrCreateTrack(event.layerIdx);
     418            0 :     IF_ASSERT_FAILED(track) {
     419            0 :         return;
     420              :     }
     421              : 
     422            0 :     std::map<long long, ms_PresetChange>& presetChanges = m_presetChangesByTrack[track];
     423            0 :     ms_PresetChange presetChange = 0;
     424              : 
     425            0 :     auto it = presetChanges.find(startUs);
     426            0 :     if (it == presetChanges.end()) {
     427            0 :         presetChange = m_samplerLib->createPresetChange(m_sampler, track, startUs);
     428            0 :         presetChanges[startUs] = presetChange;
     429              :     } else {
     430            0 :         presetChange = it->second;
     431              :     }
     432              : 
     433              :     // Make sure that the string exists long enough
     434            0 :     const std::string code = event.code.toStdString();
     435            0 :     m_samplerLib->addPreset(m_sampler, track, presetChange, code.c_str());
     436            0 : }
     437              : 
     438            0 : void MuseSamplerSequencer::addSyllableEvent(const mpe::SyllableEvent& event, long long positionUs)
     439              : {
     440            0 :     if (event.text.empty()) {
     441            0 :         return;
     442              :     }
     443              : 
     444            0 :     ms_Track track = findOrCreateTrack(event.layerIdx);
     445            0 :     IF_ASSERT_FAILED(track) {
     446            0 :         return;
     447              :     }
     448              : 
     449              :     // Make sure that the string exists long enough
     450            0 :     const std::string str = event.text.toStdString();
     451              : 
     452              :     SyllableEvent evt;
     453            0 :     evt._position_us = positionUs;
     454            0 :     evt._text = str.c_str();
     455            0 :     evt._hyphened_to_next = event.flags.testFlag(mpe::SyllableEvent::HyphenedToNext);
     456              : 
     457            0 :     m_samplerLib->addSyllableEvent(m_sampler, track, evt);
     458            0 : }
     459              : 
     460            0 : void MuseSamplerSequencer::addPitchBends(const mpe::NoteEvent& noteEvent, long long noteEventId, ms_Track track)
     461              : {
     462            0 :     auto addPitchBendEvent = [this, noteEventId, track](long long startUs, long long durationUs, pitch_level_t pitchOffset) {
     463              :         ms_PitchBendInfo pitchBend;
     464            0 :         pitchBend.event_id = noteEventId;
     465            0 :         pitchBend._start_us = startUs;
     466            0 :         pitchBend._duration_us = durationUs;
     467            0 :         const int rawCents = pitchLevelToCents(pitchOffset);
     468            0 :         pitchBend._offset_cents = std::clamp(rawCents, -MAX_PITCH_BEND_RANGE_CENTS, MAX_PITCH_BEND_RANGE_CENTS);
     469            0 :         pitchBend._type = PitchBend_Bezier;
     470            0 :         m_samplerLib->addPitchBend(m_sampler, track, pitchBend);
     471            0 :     };
     472              : 
     473            0 :     const PitchCurve& pitchCurve = noteEvent.pitchCtx().pitchCurve;
     474            0 :     const duration_t noteDuration = noteEvent.arrangementCtx().actualDuration;
     475            0 :     const pitch_level_t startPitchOffset = muse::value(pitchCurve, 0, 0);
     476            0 :     long long startOffsetUs = 0;
     477              : 
     478            0 :     if (startPitchOffset != 0) {
     479            0 :         startOffsetUs = 1000; // 1ms
     480            0 :         addPitchBendEvent(0, startOffsetUs, startPitchOffset);
     481              :     }
     482              : 
     483            0 :     for (auto it = pitchCurve.begin(); it != pitchCurve.end(); ++it) {
     484            0 :         auto nextIt = std::next(it);
     485            0 :         if (nextIt == pitchCurve.end() || nextIt->second == it->second) {
     486            0 :             continue;
     487              :         }
     488              : 
     489            0 :         const long long currOffsetStartUs = noteDuration * percentageToFactor(it->first);
     490            0 :         const long long nextOffsetStartUs = noteDuration * percentageToFactor(nextIt->first);
     491            0 :         const long long startUs = currOffsetStartUs + startOffsetUs;
     492            0 :         const long long durationUs = nextOffsetStartUs - currOffsetStartUs - startOffsetUs;
     493              : 
     494            0 :         addPitchBendEvent(startUs, durationUs, nextIt->second);
     495              :     }
     496            0 : }
     497              : 
     498            0 : void MuseSamplerSequencer::addVibrato(const mpe::NoteEvent& noteEvent, long long noteEventId, ms_Track track)
     499              : {
     500            0 :     const duration_t duration = noteEvent.arrangementCtx().actualDuration;
     501              :     // stand-in data before actual mpe support
     502            0 :     constexpr auto MAX_VIBRATO_STARTOFFSET_US = (int64_t)0.1 * 1000000;
     503              :     // stand-in data before actual mpe support
     504            0 :     constexpr int VIBRATO_DEPTH_CENTS = 23;
     505              : 
     506              :     ms_VibratoInfo vibrato;
     507            0 :     vibrato.event_id = noteEventId;
     508            0 :     vibrato._start_us = std::min((int64_t)(duration * 0.1), MAX_VIBRATO_STARTOFFSET_US);
     509            0 :     vibrato._duration_us = duration;
     510            0 :     vibrato._depth_cents = VIBRATO_DEPTH_CENTS;
     511              : 
     512            0 :     m_samplerLib->addVibrato(m_sampler, track, vibrato);
     513            0 : }
     514              : 
     515            0 : void MuseSamplerSequencer::addAuditionNoteEvent(const mpe::NoteEvent& noteEvent)
     516              : {
     517            0 :     const mpe::ArrangementContext& arrangementCtx = noteEvent.arrangementCtx();
     518            0 :     const mpe::ArticulationMap& articulations = noteEvent.expressionCtx().articulations;
     519              : 
     520            0 :     layer_idx_t layerIdx = makeLayerIdx(arrangementCtx.staffLayerIndex, arrangementCtx.voiceLayerIndex);
     521            0 :     ms_Track track = findOrCreateTrack(layerIdx);
     522            0 :     IF_ASSERT_FAILED(track) {
     523            0 :         return;
     524              :     }
     525              : 
     526            0 :     int pitch = 0, offsetCents = 0;
     527            0 :     pitchAndTuning(noteEvent.pitchCtx().nominalPitchLevel, pitch, offsetCents);
     528              : 
     529            0 :     if (arrangementCtx.hasStart()) {
     530            0 :         AuditionStartNoteEvent noteOn;
     531            0 :         noteOn.msTrack = track;
     532              : 
     533            0 :         auto& msEvent = noteOn.msEvent;
     534            0 :         msEvent._pitch = pitch;
     535            0 :         msEvent._offset_cents = offsetCents;
     536              : 
     537            0 :         parseArticulations(articulations, msEvent._articulation, msEvent._articulation_2, msEvent._notehead);
     538              : 
     539            0 :         if (noteEvent.expressionCtx().velocityOverride.has_value()) {
     540            0 :             msEvent._dynamics = noteEvent.expressionCtx().velocityOverride.value();
     541              :         } else {
     542            0 :             msEvent._dynamics = dynamicLevelRatio(noteEvent.expressionCtx().nominalDynamicLevel);
     543              :         }
     544              : 
     545            0 :         msEvent._active_presets = m_auditionParamsCache.presets.empty() ? m_defaultPresetCode.c_str()
     546            0 :                                   : m_auditionParamsCache.presets.c_str();
     547            0 :         msEvent._active_text_articulation = m_auditionParamsCache.textArticulation.c_str();
     548            0 :         msEvent._active_syllable = m_auditionParamsCache.syllable.c_str();
     549            0 :         msEvent._articulation_text_starts_at_note = m_auditionParamsCache.textArticulationStartsAtNote;
     550            0 :         msEvent._syllable_starts_at_note = m_auditionParamsCache.syllableStartsAtNote;
     551              : 
     552            0 :         m_offStreamEvents[arrangementCtx.actualTimestamp].push_back(noteOn);
     553              :     }
     554              : 
     555            0 :     if (arrangementCtx.hasEnd()) {
     556            0 :         AuditionStopNoteEvent noteOff;
     557            0 :         noteOff.msEvent = { pitch };
     558            0 :         noteOff.msTrack = track;
     559              : 
     560            0 :         timestamp_t timestampTo = arrangementCtx.actualTimestamp + arrangementCtx.actualDuration;
     561            0 :         m_offStreamEvents[timestampTo].emplace_back(std::move(noteOff));
     562              :     }
     563              : 
     564            0 :     auto pedalIt = articulations.find(mpe::ArticulationType::Pedal);
     565            0 :     if (pedalIt != articulations.end()) {
     566            0 :         addAuditionPedalEvent(pedalIt->second.meta, track);
     567              :     }
     568              : }
     569              : 
     570            0 : void MuseSamplerSequencer::addAuditionPedalEvent(const mpe::ArticulationMeta& meta, ms_Track track)
     571              : {
     572            0 :     AuditionCCEvent event;
     573            0 :     event.cc = 64;
     574            0 :     event.msTrack = track;
     575              : 
     576            0 :     if (meta.hasStart()) {
     577            0 :         event.value = 1;
     578            0 :         m_offStreamEvents[meta.timestamp].push_back(event);
     579              :     }
     580              : 
     581            0 :     if (meta.hasEnd()) {
     582            0 :         event.value = 0;
     583            0 :         m_offStreamEvents[meta.timestamp + meta.overallDuration].push_back(event);
     584              :     }
     585            0 : }
     586              : 
     587            0 : void MuseSamplerSequencer::addAuditionCCEvent(const mpe::ControllerChangeEvent& event, long long positionUs)
     588              : {
     589              :     static const std::unordered_map<mpe::ControllerChangeEvent::Type, int> TYPE_TO_CC {
     590              :         { mpe::ControllerChangeEvent::Modulation, 1 },
     591              :         { mpe::ControllerChangeEvent::SustainPedalOnOff, 64 },
     592              :         { mpe::ControllerChangeEvent::PitchBend, 128 },
     593            0 :     };
     594              : 
     595            0 :     auto ccIt = TYPE_TO_CC.find(event.type);
     596            0 :     if (ccIt == TYPE_TO_CC.end()) {
     597            0 :         return;
     598              :     }
     599              : 
     600            0 :     AuditionCCEvent ccEvent;
     601            0 :     ccEvent.cc = ccIt->second;
     602            0 :     ccEvent.value = event.val;
     603            0 :     ccEvent.msTrack = findOrCreateTrack(event.layerIdx);
     604            0 :     IF_ASSERT_FAILED(ccEvent.msTrack) {
     605            0 :         return;
     606              :     }
     607              : 
     608            0 :     m_offStreamEvents[positionUs].push_back(ccEvent);
     609              : }
     610              : 
     611            0 : void MuseSamplerSequencer::pitchAndTuning(const pitch_level_t nominalPitch, int& pitch, int& centsOffset) const
     612              : {
     613              :     // Get midi pitch
     614            0 :     float stepCount = mpe::ZERO_PITCH_LEVEL_MIDI_EQUIVALENT + nominalPitch / static_cast<float>(mpe::PITCH_LEVEL_STEP);
     615            0 :     pitch = RealRound(stepCount, 0);
     616              : 
     617              :     // Get tuning offset
     618            0 :     int semitonesCount = pitch - mpe::ZERO_PITCH_LEVEL_MIDI_EQUIVALENT;
     619            0 :     pitch_level_t tuningPitchLevel = nominalPitch - semitonesCount * mpe::PITCH_LEVEL_STEP;
     620            0 :     centsOffset = pitchLevelToCents(tuningPitchLevel);
     621            0 : }
     622              : 
     623            0 : int MuseSamplerSequencer::pitchLevelToCents(const pitch_level_t pitchLevel) const
     624              : {
     625              :     static constexpr float CONVERT_TO_CENTS = (100.f / static_cast<float>(PITCH_LEVEL_STEP));
     626              : 
     627            0 :     return pitchLevel * CONVERT_TO_CENTS;
     628              : }
     629              : 
     630            0 : double MuseSamplerSequencer::dynamicLevelRatio(const dynamic_level_t level) const
     631              : {
     632              :     static constexpr dynamic_level_t MIN_SUPPORTED_DYNAMIC_LEVEL = dynamicLevelFromType(DynamicType::ppppppppp);
     633              :     static constexpr dynamic_level_t MAX_SUPPORTED_DYNAMIC_LEVEL = dynamicLevelFromType(DynamicType::fffffffff);
     634              : 
     635              :     // Piecewise linear simple scaling to expected MuseSampler values:
     636              :     static const std::list<std::pair<dynamic_level_t, double> > conversionMap = {
     637              :         { MIN_SUPPORTED_DYNAMIC_LEVEL, 0.0 },
     638              :         { dynamicLevelFromType(DynamicType::ppppp), 1.0 / 127.0 },
     639              :         { dynamicLevelFromType(DynamicType::pppp), 2.0 / 127.0 },
     640              :         { dynamicLevelFromType(DynamicType::ppp), 4.0 / 127.0 },
     641              :         { dynamicLevelFromType(DynamicType::pp), 8.0 / 127.0 },
     642              :         { dynamicLevelFromType(DynamicType::p), 16.0 / 127.0 },
     643              :         { dynamicLevelFromType(DynamicType::mp), 32.0 / 127.0 },
     644              :         { dynamicLevelFromType(DynamicType::mf), 64.0 / 127.0 },
     645              :         { dynamicLevelFromType(DynamicType::f), 96.0 / 127.0 },
     646              :         { dynamicLevelFromType(DynamicType::ff), 120.0 / 127.0 },
     647              :         { dynamicLevelFromType(DynamicType::fff), 127.0 / 127.0 },
     648              :         { MAX_SUPPORTED_DYNAMIC_LEVEL, 127.0 / 127.0 }
     649            0 :     };
     650              : 
     651            0 :     auto prev_level = conversionMap.begin();
     652            0 :     auto last_level = conversionMap.end();
     653            0 :     auto level_it = std::next(prev_level);
     654            0 :     while (level_it != last_level) {
     655            0 :         if (level >= prev_level->first && level <= level_it->first) {
     656            0 :             auto alpha = static_cast<double>(level - prev_level->first) / static_cast<double>(level_it->first - prev_level->first);
     657            0 :             return alpha * level_it->second + (1.0 - alpha) * prev_level->second;
     658              :         }
     659            0 :         prev_level = level_it;
     660            0 :         level_it = std::next(level_it);
     661              :     }
     662            0 :     LOGE() << "Out of range dynamic value found!";
     663            0 :     return 48.0 / 127.0;
     664              : }
     665              : 
     666            0 : void MuseSamplerSequencer::parseArticulations(const ArticulationMap& articulations,
     667              :                                               ms_NoteArticulation& articulations1,
     668              :                                               ms_NoteArticulation2& articulations2,
     669              :                                               ms_NoteHead& notehead) const
     670              : {
     671            0 :     notehead = ms_NoteHead_Normal;
     672            0 :     uint64_t arts1 = 0;
     673            0 :     uint64_t arts2 = 0;
     674              : 
     675            0 :     for (const auto& pair : articulations) {
     676            0 :         if (pair.first == mpe::ArticulationType::Standard) {
     677            0 :             continue;
     678              :         }
     679              : 
     680              :         //! NOTE: skip last note of slur / hopo, sampler requirement
     681            0 :         if (pair.first == mpe::ArticulationType::Legato) {
     682            0 :             if (pair.second.occupiedFrom != 0 && pair.second.occupiedTo == HUNDRED_PERCENT) {
     683            0 :                 continue;
     684              :             }
     685              :         }
     686              : 
     687            0 :         auto art1It = ARTICULATION_TYPES_PART1.find(pair.first);
     688            0 :         if (art1It != ARTICULATION_TYPES_PART1.cend()) {
     689            0 :             arts1 |= art1It->second;
     690            0 :             continue;
     691              :         }
     692              : 
     693            0 :         auto art2It = ARTICULATION_TYPES_PART2.find(pair.first);
     694            0 :         if (art2It != ARTICULATION_TYPES_PART2.cend()) {
     695            0 :             arts2 |= art2It->second;
     696            0 :             continue;
     697              :         }
     698              : 
     699            0 :         if (notehead == ms_NoteHead_Ghost) {
     700            0 :             continue;
     701              :         }
     702              : 
     703            0 :         auto headIt = NOTEHEAD_TYPES.find(pair.first);
     704            0 :         if (headIt != NOTEHEAD_TYPES.cend()) {
     705            0 :             notehead = headIt->second;
     706              :         }
     707              :     }
     708              : 
     709            0 :     articulations1 = static_cast<ms_NoteArticulation>(arts1);
     710            0 :     articulations2 = static_cast<ms_NoteArticulation2>(arts2);
     711            0 : }
     712              : 
     713            0 : void MuseSamplerSequencer::parseAuditionParams(const mpe::PlaybackEvent& event, AuditionParams& out) const
     714              : {
     715            0 :     if (std::holds_alternative<mpe::TextArticulationEvent>(event)) {
     716            0 :         const mpe::TextArticulationEvent& artEvent = std::get<mpe::TextArticulationEvent>(event);
     717            0 :         out.textArticulation = artEvent.text.toStdString();
     718            0 :         out.textArticulationStartsAtNote = artEvent.flags.testFlag(mpe::TextArticulationEvent::StartsAtPlaybackPosition);
     719            0 :     } else if (std::holds_alternative<mpe::SoundPresetChangeEvent>(event)) {
     720            0 :         const mpe::SoundPresetChangeEvent& soundPresetEvent = std::get<mpe::SoundPresetChangeEvent>(event);
     721              : 
     722            0 :         if (out.presets.empty()) {
     723            0 :             out.presets = soundPresetEvent.code.toStdString();
     724              :         } else {
     725            0 :             out.presets += "|" + soundPresetEvent.code.toStdString();
     726              :         }
     727            0 :     } else if (std::holds_alternative<mpe::SyllableEvent>(event)) {
     728            0 :         const mpe::SyllableEvent& syllableEvent = std::get<mpe::SyllableEvent>(event);
     729            0 :         out.syllable = syllableEvent.text.toStdString();
     730            0 :         out.syllableStartsAtNote = syllableEvent.flags.testFlag(mpe::SyllableEvent::StartsAtPlaybackPosition);
     731              :     }
     732            0 : }
        

Generated by: LCOV version 2.0-1