LCOV - code coverage report
Current view: top level - muse/framework/audio/engine/internal - contextplayer.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 0.0 % 185 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) 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 "contextplayer.h"
      24              : 
      25              : #include "audio/common/audiosanitizer.h"
      26              : #include "audio/common/audioerrors.h"
      27              : 
      28              : #include "log.h"
      29              : 
      30              : using namespace muse;
      31              : using namespace muse::audio;
      32              : using namespace muse::audio::engine;
      33              : using namespace muse::async;
      34              : 
      35            0 : ContextPlayer::ContextPlayer(IGetTrackSource* getTracks, IExecOperation* execOperation)
      36            0 :     : m_trackSource(getTracks)
      37            0 :     , m_execOperation(execOperation)
      38              : {
      39            0 :     m_status.set(PlaybackStatus::Stopped);
      40            0 :     m_isActive.set(false);
      41              : 
      42            0 :     m_status.ch.onReceive(this, [this](const PlaybackStatus status) {
      43            0 :         onStatusChanged(status);
      44            0 :     });
      45              : 
      46              :     // Forwarding events from the processing thread to the engine thread
      47            0 :     m_timeEvent.onReceive(this, [this](const TimeEvent event) {
      48            0 :         onTimeEvent(event);
      49            0 :     });
      50            0 : }
      51              : 
      52            0 : void ContextPlayer::exec(OperationType type, const Operation& func)
      53              : {
      54              :     ONLY_AUDIO_ENGINE_THREAD;
      55            0 :     if (m_execOperation) {
      56            0 :         m_execOperation->execOperation(type, func);
      57              :     } else {
      58            0 :         func();
      59              :     }
      60            0 : }
      61              : 
      62            0 : void ContextPlayer::onStatusChanged(const PlaybackStatus status)
      63              : {
      64            0 :     const bool active = status == PlaybackStatus::Running;
      65            0 :     if (active) {
      66              :         //! NOTE If there is no countdown, activate the mixer.
      67              :         //! Otherwise, it will become active when the countdown ends.
      68            0 :         if (m_countDown.is_zero()) {
      69            0 :             m_isActive.set(true);
      70              :         }
      71              :     } else {
      72            0 :         m_isActive.set(false);
      73            0 :         flushAllTracks();
      74              :     }
      75            0 : }
      76              : 
      77            0 : void ContextPlayer::forward(const TimePosition& delta)
      78              : {
      79              :     ONLY_AUDIO_PROC_THREAD;
      80              : 
      81              :     // Check: Active
      82            0 :     if (m_status.val != PlaybackStatus::Running) {
      83            0 :         return;
      84              :     }
      85              : 
      86            0 :     const TimePosition newTime = proc_onTimeChanged(delta);
      87              : 
      88            0 :     if (m_currentPosition == newTime) {
      89            0 :         return;
      90              :     }
      91              : 
      92            0 :     m_currentPosition = newTime;
      93            0 :     m_timeChanged.send(m_currentPosition.time());
      94              : }
      95              : 
      96            0 : TimePosition ContextPlayer::proc_onTimeChanged(const TimePosition& delta)
      97              : {
      98              :     ONLY_AUDIO_PROC_THREAD;
      99              : 
     100              :     // Check: Count down
     101            0 :     if (!m_countDown.is_zero()) {
     102            0 :         m_countDown -= delta.time();
     103              : 
     104            0 :         if (m_countDown > 0.) {
     105            0 :             return m_currentPosition; // no change
     106              :         }
     107              : 
     108            0 :         m_countDown = 0.;
     109            0 :         m_timeEvent.send(TimeEvent { TimeEventType::CountDownEnded, m_currentPosition }); // forwarding an event to the engine thread
     110              :     }
     111              : 
     112              :     // Check: Loop
     113            0 :     const TimePosition newTime = m_currentPosition.forwarded(delta);
     114            0 :     if (m_timeLoopStart < m_timeLoopEnd && newTime.time() >= m_timeLoopEnd) {
     115              :         //! TODO Seek may be necessary to call this directly within the PROC thread.
     116            0 :         m_timeEvent.send(TimeEvent { TimeEventType::LoopEnded, newTime }); // forwarding an event to the engine thread
     117            0 :         const secs_t overshoot = newTime.time() - m_timeLoopEnd;
     118            0 :         return TimePosition::fromTime(m_timeLoopStart + overshoot, delta.sampleRate());
     119              :     }
     120              : 
     121              :     // Check: Duration
     122            0 :     if (newTime.time() >= m_timeDuration) {
     123            0 :         m_timeEvent.send(TimeEvent { TimeEventType::PlaybackEnded, newTime }); // forwarding an event to the engine thread
     124            0 :         return TimePosition::fromTime(m_timeDuration, delta.sampleRate());
     125              :     }
     126              : 
     127            0 :     return newTime;
     128              : }
     129              : 
     130            0 : const TimePosition& ContextPlayer::currentPosition() const
     131              : {
     132            0 :     return m_currentPosition;
     133              : }
     134              : 
     135            0 : void ContextPlayer::onTimeEvent(const TimeEvent event)
     136              : {
     137              :     ONLY_AUDIO_ENGINE_THREAD;
     138              : 
     139            0 :     switch (event.type) {
     140            0 :     case TimeEventType::CountDownEnded:
     141            0 :         exec(OperationType::QuickOperation, [this]() {
     142            0 :             m_isActive.set(m_status.val == PlaybackStatus::Running);
     143            0 :         });
     144            0 :         break;
     145            0 :     case TimeEventType::LoopEnded:
     146            0 :         exec(OperationType::QuickOperation, [this, event]() {
     147            0 :             seekAllTracks(event.position);
     148            0 :         });
     149            0 :         break;
     150            0 :     case TimeEventType::PlaybackEnded:
     151            0 :         exec(OperationType::QuickOperation, [this]() {
     152            0 :             pause();
     153            0 :         });
     154            0 :         break;
     155            0 :     default:
     156            0 :         break;
     157              :     }
     158            0 : }
     159              : 
     160            0 : async::Promise<Ret> ContextPlayer::prepareToPlay()
     161              : {
     162              :     ONLY_AUDIO_ENGINE_THREAD;
     163              : 
     164            0 :     return async::make_promise<Ret>([this](auto resolve, auto) {
     165            0 :         prepareAllTracksToPlay([resolve]() {
     166            0 :             (void)resolve(make_ok());
     167              :         });
     168              : 
     169            0 :         return Promise<Ret>::dummy_result();
     170            0 :     });
     171              : }
     172              : 
     173            0 : void ContextPlayer::play(const secs_t delay)
     174              : {
     175              :     ONLY_AUDIO_ENGINE_THREAD;
     176            0 :     ONLY_ON_OPERATION_EXEC;
     177              : 
     178            0 :     if (playbackStatus() == PlaybackStatus::Running) {
     179            0 :         return;
     180              :     }
     181              : 
     182            0 :     m_countDown = delay;
     183            0 :     m_status.set(PlaybackStatus::Running);
     184              : }
     185              : 
     186            0 : void ContextPlayer::seek(const TimePosition& position, const bool flushSound)
     187              : {
     188              :     ONLY_AUDIO_ENGINE_THREAD;
     189            0 :     ONLY_ON_OPERATION_EXEC;
     190              : 
     191              :     //! NOTE During export, the current time does not change, it remains at 0
     192              :     // but a seek operation is still required to reset the internal state of the sources (synthesizers).
     193              :     // if (newPosition == m_currentPosition.time()) {
     194              :     //     return;
     195              :     // }
     196              : 
     197            0 :     IF_ASSERT_FAILED(position.isValid()) {
     198            0 :         return;
     199              :     }
     200              : 
     201            0 :     IF_ASSERT_FAILED(m_trackSource) {
     202            0 :         return;
     203              :     }
     204              : 
     205            0 :     m_flushSoundOnSeek = flushSound;
     206            0 :     m_currentPosition = position;
     207            0 :     m_timeChanged.send(m_currentPosition.time());
     208            0 :     seekAllTracks(position);
     209            0 :     m_flushSoundOnSeek = true;
     210              : }
     211              : 
     212            0 : void ContextPlayer::stop()
     213              : {
     214              :     ONLY_AUDIO_ENGINE_THREAD;
     215            0 :     ONLY_ON_OPERATION_EXEC;
     216              : 
     217            0 :     if (playbackStatus() == PlaybackStatus::Stopped) {
     218            0 :         return;
     219              :     }
     220              : 
     221            0 :     m_status.set(PlaybackStatus::Stopped);
     222            0 :     m_countDown = 0.;
     223            0 :     seek(TimePosition::zero(m_currentPosition.sampleRate()));
     224            0 :     m_notYetReadyToPlayTracks.clear();
     225              : }
     226              : 
     227            0 : void ContextPlayer::pause()
     228              : {
     229              :     ONLY_AUDIO_ENGINE_THREAD;
     230            0 :     ONLY_ON_OPERATION_EXEC;
     231              : 
     232            0 :     if (playbackStatus() == PlaybackStatus::Paused) {
     233            0 :         return;
     234              :     }
     235              : 
     236            0 :     m_status.set(PlaybackStatus::Paused);
     237            0 :     m_notYetReadyToPlayTracks.clear();
     238              : }
     239              : 
     240            0 : void ContextPlayer::resume(const secs_t delay)
     241              : {
     242              :     ONLY_AUDIO_ENGINE_THREAD;
     243            0 :     ONLY_ON_OPERATION_EXEC;
     244              : 
     245            0 :     if (playbackStatus() == PlaybackStatus::Running) {
     246            0 :         return;
     247              :     }
     248              : 
     249            0 :     m_countDown = delay;
     250            0 :     seek(m_currentPosition);
     251            0 :     m_status.set(PlaybackStatus::Running);
     252              : }
     253              : 
     254            0 : secs_t ContextPlayer::duration() const
     255              : {
     256              :     ONLY_AUDIO_ENGINE_THREAD;
     257            0 :     return m_timeDuration;
     258              : }
     259              : 
     260            0 : void ContextPlayer::setDuration(const secs_t duration)
     261              : {
     262              :     ONLY_AUDIO_ENGINE_THREAD;
     263            0 :     m_timeDuration = duration;
     264            0 : }
     265              : 
     266            0 : Ret ContextPlayer::setLoop(const secs_t from, const secs_t to)
     267              : {
     268              :     ONLY_AUDIO_ENGINE_THREAD;
     269            0 :     ONLY_ON_OPERATION_EXEC;
     270              : 
     271            0 :     if (from >= to) {
     272            0 :         return make_ret(Err::InvalidTimeLoop);
     273              :     }
     274              : 
     275            0 :     m_timeLoopStart = from;
     276            0 :     m_timeLoopEnd = to;
     277              : 
     278            0 :     return Ret(Ret::Code::Ok);
     279              : }
     280              : 
     281            0 : void ContextPlayer::resetLoop()
     282              : {
     283              :     ONLY_AUDIO_ENGINE_THREAD;
     284            0 :     ONLY_ON_OPERATION_EXEC;
     285            0 :     m_timeLoopStart = 0;
     286            0 :     m_timeLoopEnd = 0;
     287            0 : }
     288              : 
     289            0 : secs_t ContextPlayer::playbackPosition() const
     290              : {
     291              :     ONLY_AUDIO_ENGINE_THREAD;
     292            0 :     return m_currentPosition.time();
     293              : }
     294              : 
     295            0 : Channel<secs_t> ContextPlayer::playbackPositionChanged() const
     296              : {
     297              :     ONLY_AUDIO_ENGINE_THREAD;
     298            0 :     return m_timeChanged;
     299              : }
     300              : 
     301            0 : PlaybackStatus ContextPlayer::playbackStatus() const
     302              : {
     303              :     ONLY_AUDIO_ENGINE_THREAD;
     304            0 :     return m_status.val;
     305              : }
     306              : 
     307            0 : Channel<PlaybackStatus> ContextPlayer::playbackStatusChanged() const
     308              : {
     309              :     ONLY_AUDIO_ENGINE_THREAD;
     310            0 :     return m_status.ch;
     311              : }
     312              : 
     313            0 : bool ContextPlayer::isActive() const
     314              : {
     315              :     ONLY_AUDIO_ENGINE_THREAD;
     316            0 :     return m_isActive.val;
     317              : }
     318              : 
     319            0 : Channel<bool> ContextPlayer::isActiveChanged() const
     320              : {
     321              :     ONLY_AUDIO_ENGINE_THREAD;
     322            0 :     return m_isActive.ch;
     323              : }
     324              : 
     325            0 : void ContextPlayer::seekAllTracks(const TimePosition& position)
     326              : {
     327              :     ONLY_AUDIO_ENGINE_THREAD;
     328            0 :     ONLY_ON_OPERATION_EXEC;
     329              : 
     330            0 :     IF_ASSERT_FAILED(m_trackSource) {
     331            0 :         return;
     332              :     }
     333              : 
     334            0 :     for (const auto& source : m_trackSource->allTracksSources()) {
     335            0 :         source->seek(position, m_flushSoundOnSeek);
     336            0 :     }
     337              : }
     338              : 
     339            0 : void ContextPlayer::flushAllTracks()
     340              : {
     341              :     ONLY_AUDIO_ENGINE_THREAD;
     342            0 :     ONLY_ON_OPERATION_EXEC;
     343              : 
     344            0 :     IF_ASSERT_FAILED(m_trackSource) {
     345            0 :         return;
     346              :     }
     347              : 
     348            0 :     for (const auto& source : m_trackSource->allTracksSources()) {
     349            0 :         source->flush();
     350            0 :     }
     351              : }
     352              : 
     353            0 : void ContextPlayer::prepareAllTracksToPlay(AllTracksReadyCallback allTracksReadyCallback)
     354              : {
     355              :     ONLY_AUDIO_ENGINE_THREAD;
     356              : 
     357            0 :     IF_ASSERT_FAILED(m_trackSource) {
     358            0 :         return;
     359              :     }
     360              : 
     361            0 :     m_notYetReadyToPlayTracks.clear();
     362              : 
     363            0 :     for (const auto& source : m_trackSource->allTracksSources()) {
     364            0 :         IF_ASSERT_FAILED(source) {
     365            0 :             continue;
     366              :         }
     367              : 
     368            0 :         IF_ASSERT_FAILED(source->mode() == ProcessMode::Idle) {
     369            0 :             continue;
     370              :         }
     371              : 
     372            0 :         source->prepareToPlay();
     373              : 
     374            0 :         if (!source->readyToPlay()) {
     375            0 :             m_notYetReadyToPlayTracks.insert(source);
     376              :         }
     377            0 :     }
     378              : 
     379            0 :     if (m_notYetReadyToPlayTracks.empty()) {
     380            0 :         allTracksReadyCallback();
     381            0 :         return;
     382              :     }
     383              : 
     384            0 :     for (const auto& source : m_notYetReadyToPlayTracks) {
     385            0 :         std::weak_ptr<AudioSourceNode> weakPtr = source;
     386            0 :         source->readyToPlayChanged().onNotify(this, [this, weakPtr, allTracksReadyCallback]() {
     387            0 :             if (auto source = weakPtr.lock()) {
     388            0 :                 muse::remove(m_notYetReadyToPlayTracks, source);
     389              : 
     390            0 :                 if (m_notYetReadyToPlayTracks.empty()) {
     391            0 :                     allTracksReadyCallback();
     392              :                 }
     393              : 
     394            0 :                 source->readyToPlayChanged().disconnect(this);
     395            0 :             }
     396            0 :         }, Asyncable::Mode::SetReplace);
     397            0 :     }
     398              : }
        

Generated by: LCOV version 2.0-1