LCOV - code coverage report
Current view: top level - muse/framework/audio/engine/internal - audiocontext.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 0.0 % 481 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) 2026 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              : #include "audiocontext.h"
      23              : 
      24              : #include "audio/common/audiosanitizer.h"
      25              : #include "audio/common/audioerrors.h"
      26              : #include "audio/common/audioutils.h"
      27              : 
      28              : #include "nodes/trackchain.h"
      29              : 
      30              : #include "contextplayer.h"
      31              : 
      32              : #include "muse_framework_config.h"
      33              : #ifdef MUSE_MODULE_AUDIO_EXPORT
      34              : #include "export/soundtrackwriter.h"
      35              : #endif
      36              : 
      37              : using namespace muse;
      38              : using namespace muse::audio;
      39              : using namespace muse::audio::engine;
      40              : 
      41            0 : AudioContext::AudioContext(const AudioCtxId& ctxId, IExecOperation* execOperation)
      42            0 :     : m_ctxId(ctxId)
      43            0 :     , m_execOperation(execOperation)
      44              : {
      45            0 :     m_player = std::make_shared<ContextPlayer>(this, execOperation);
      46              : 
      47            0 :     m_playheadNode = std::make_shared<PlayheadNode>(std::static_pointer_cast<IPlayhead>(m_player));
      48            0 :     m_mixer = std::make_shared<Mixer>();
      49              : 
      50            0 :     m_masterTrack.id = MASTER_TRACK_ID;
      51            0 :     m_masterTrack.type = TrackType::Master_track;
      52            0 :     m_masterTrack.name = "Master";
      53            0 :     m_masterTrack.chain = std::make_shared<TrackChain>(MASTER_TRACK_ID, "Master");
      54            0 : }
      55              : 
      56            0 : AudioCtxId AudioContext::id() const
      57              : {
      58            0 :     return m_ctxId;
      59              : }
      60              : 
      61            0 : Ret AudioContext::init()
      62              : {
      63            0 :     m_mixer->init();
      64              : 
      65              :     // Make the chain: audiocontext <- playheadnode
      66              :     // <- mastertrackchain <- mastersignalnode <- mastercontrolnode <- masterfxchain
      67              :     // <- mixer
      68            0 :     AutomationControlNodePtr masterControlNode = std::make_shared<AutomationControlNode>();
      69            0 :     masterControlNode->setPlayheadPosition(std::static_pointer_cast<IPlayheadPosition>(m_player));
      70              : 
      71            0 :     m_masterTrack.chain->setSource(m_mixer);
      72            0 :     m_masterTrack.chain->setFxChain(nullptr); //!< NOTE Master fx chain is not exists yet
      73            0 :     m_masterTrack.chain->setControl(masterControlNode);
      74            0 :     m_masterTrack.chain->setSignal(std::make_shared<SignalNode>());
      75            0 :     m_masterTrack.chain->rebuild();
      76              : 
      77            0 :     m_masterTrack.chain->connect(m_playheadNode);
      78              : 
      79            0 :     LOGD() << "Master track chain: " << m_playheadNode->dump();
      80              : 
      81            0 :     m_player->isActiveChanged().onReceive(this, [this](bool isActive) {
      82            0 :         setMode(isActive ? ProcessMode::Playing : ProcessMode::Idle);
      83            0 :     });
      84              : 
      85            0 :     return make_ret(Ret::Code::Ok);
      86            0 : }
      87              : 
      88            0 : void AudioContext::deinit()
      89              : {
      90              :     ONLY_AUDIO_ENGINE_THREAD;
      91              : 
      92            0 :     removeAllTracks();
      93              : 
      94            0 :     m_player.reset();
      95              : 
      96              :     // Explicitly disconnect and clear all channel members before
      97              :     // async_disconnectAll() and before the destructor runs. This ensures
      98              :     // subscribers are disconnected while they're still alive, not during IoC
      99              :     // teardown when some may already be destroyed.
     100            0 :     m_trackRemoved = async::Channel<TrackId>();
     101            0 :     m_trackAdded = async::Channel<TrackId>();
     102              : 
     103            0 :     m_saveSoundTracksProgress = SaveSoundTrackProgressData();
     104            0 :     m_sourceParamsChanged = async::Channel<TrackId, AudioSourceParams>();
     105            0 :     m_controlParamsChanged = async::Channel<TrackId, ControlParams>();
     106            0 :     m_fxChainParamsChanged = async::Channel<TrackId, AudioFxChain>();
     107            0 :     m_auxSendsParamsChanged = async::Channel<TrackId, AuxSendsParams>();
     108              : 
     109            0 :     async_disconnectAll();
     110            0 : }
     111              : 
     112              : // Config
     113            0 : void AudioContext::onModeChanged(const ProcessMode mode)
     114              : {
     115              :     ONLY_AUDIO_ENGINE_THREAD;
     116            0 :     if (mode == ProcessMode::Idle) {
     117            0 :         m_mixer->setTracksToProcessWhenIdle(m_tracksToProcessWhenIdle);
     118              :     }
     119              : 
     120              :     //! NOTE will be set on all nodes in the chain
     121            0 :     m_playheadNode->setMode(mode);
     122            0 : }
     123              : 
     124            0 : void AudioContext::onOutputSpecChanged(const OutputSpec& outputSpec)
     125              : {
     126              :     ONLY_AUDIO_ENGINE_THREAD;
     127              : 
     128              :     //! NOTE will be set on all nodes in the chain
     129            0 :     m_playheadNode->setOutputSpec(outputSpec);
     130              : 
     131            0 :     TimePosition currentPosition = m_player->currentPosition();
     132            0 :     if (currentPosition.isValid()) {
     133            0 :         m_player->seek(TimePosition::fromTime(currentPosition.time(), outputSpec.sampleRate));
     134              :     } else {
     135            0 :         m_player->seek(TimePosition::zero(outputSpec.sampleRate));
     136              :     }
     137            0 : }
     138              : 
     139              : // Setup tracks
     140            0 : TrackId AudioContext::newTrackId() const
     141              : {
     142              :     static TrackId lastId = MASTER_TRACK_ID;
     143            0 :     ++lastId;
     144            0 :     return lastId;
     145              : }
     146              : 
     147            0 : RetVal2<TrackId, TrackParams> AudioContext::addTrack(const std::string& trackName,
     148              :                                                      io::IODevice* playbackData,
     149              :                                                      const TrackParams& params)
     150              : {
     151              :     ONLY_AUDIO_ENGINE_THREAD;
     152              : 
     153              :     using RetType = RetVal2<TrackId, TrackParams>;
     154              : 
     155            0 :     if (!playbackData) {
     156            0 :         return RetType::make_ret(Err::InvalidAudioFilePath);
     157              :     }
     158              : 
     159            0 :     TrackId trackId = newTrackId();
     160            0 :     Track track;
     161            0 :     track.type = TrackType::Sound_track;
     162            0 :     track.id = trackId;
     163            0 :     track.name = trackName;
     164            0 :     track.params = params;
     165              :     //! NOT IMPLEMENTED YET
     166              :     // track.chain = ...
     167              : 
     168            0 :     doAddTrack(track);
     169              : 
     170            0 :     return RetType::make_ok(trackId, { });
     171            0 : }
     172              : 
     173            0 : RetVal2<TrackId, TrackParams> AudioContext::addTrack(const std::string& trackName,
     174              :                                                      const mpe::PlaybackData& playbackData,
     175              :                                                      const TrackParams& params)
     176              : {
     177              :     ONLY_AUDIO_ENGINE_THREAD;
     178              : 
     179              :     using RetType = RetVal2<TrackId, TrackParams>;
     180              : 
     181            0 :     if (!playbackData.setupData.isValid()) {
     182            0 :         return RetType::make_ret(Err::InvalidSetupData);
     183              :     }
     184              : 
     185            0 :     TrackId trackId = newTrackId();
     186              : 
     187            0 :     auto onOffStreamReceived = [this, trackId]() {
     188            0 :         std::unordered_set<TrackId> tracksToProcess = m_tracksToProcessWhenIdle;
     189              : 
     190            0 :         if (m_prevActiveTrackId == INVALID_TRACK_ID) {
     191            0 :             tracksToProcess.insert(trackId);
     192              :         } else {
     193            0 :             tracksToProcess.insert({ m_prevActiveTrackId, trackId });
     194              :         }
     195              : 
     196            0 :         m_mixer->setTracksToProcessWhenIdle(tracksToProcess);
     197            0 :         m_prevActiveTrackId = trackId;
     198            0 :     };
     199              : 
     200              :     // Make source
     201            0 :     RetVal<AudioSourceNodePtr> source = audioFactory()->makeEventSource(trackId, playbackData, params.source, onOffStreamReceived);
     202            0 :     if (!source.ret) {
     203            0 :         return RetType::make_ret(source.ret);
     204              :     }
     205              : 
     206            0 :     AutomationControlNodePtr controlNode = std::make_shared<AutomationControlNode>();
     207            0 :     controlNode->setPlayheadPosition(std::static_pointer_cast<IPlayheadPosition>(m_player));
     208              : 
     209            0 :     TrackChainPtr trackChain = std::make_shared<TrackChain>(trackId, trackName);
     210            0 :     trackChain->setOutputSpec(outputSpec());
     211            0 :     trackChain->setMode(mode());
     212            0 :     trackChain->setSource(source.val);
     213            0 :     trackChain->setFxChain(nullptr); // will be added later
     214            0 :     trackChain->setControl(controlNode);
     215            0 :     trackChain->setSignal(std::make_shared<SignalNode>());
     216            0 :     trackChain->rebuild();
     217              : 
     218            0 :     Ret ret = m_mixer->addTrack(trackChain, params.auxSends);
     219            0 :     if (!ret) {
     220            0 :         return RetType::make_ret(ret);
     221              :     }
     222              : 
     223              :     // Make track info
     224            0 :     Track track;
     225            0 :     track.type = TrackType::Event_track;
     226            0 :     track.id = trackId;
     227            0 :     track.name = trackName;
     228            0 :     track.params = params;
     229            0 :     track.chain = trackChain;
     230              : 
     231            0 :     track.params.source = source.val->inputParams();
     232              : 
     233            0 :     onFxChainParamsChanged(track, params.fxChain);
     234            0 :     onControlParamsChanged(track, params.control);
     235              : 
     236            0 :     doAddTrack(track);
     237              : 
     238            0 :     return RetType::make_ok(trackId, track.params);
     239            0 : }
     240              : 
     241            0 : RetVal2<TrackId, TrackParams> AudioContext::addAuxTrack(const std::string& trackName,
     242              :                                                         const TrackParams& params)
     243              : {
     244              :     ONLY_AUDIO_ENGINE_THREAD;
     245              : 
     246              :     using RetType = RetVal2<TrackId, TrackParams>;
     247              : 
     248            0 :     TrackId trackId = newTrackId();
     249              : 
     250            0 :     AutomationControlNodePtr controlNode = std::make_shared<AutomationControlNode>();
     251            0 :     controlNode->setPlayheadPosition(std::static_pointer_cast<IPlayheadPosition>(m_player));
     252              : 
     253            0 :     TrackChainPtr trackChain = std::make_shared<TrackChain>(trackId, trackName);
     254            0 :     trackChain->setOutputSpec(outputSpec());
     255            0 :     trackChain->setMode(mode());
     256            0 :     trackChain->setFxChain(nullptr); // will be added later
     257            0 :     trackChain->setControl(controlNode);
     258            0 :     trackChain->setSignal(std::make_shared<SignalNode>());
     259            0 :     trackChain->rebuild();
     260              : 
     261            0 :     Ret ret = m_mixer->addAuxTrack(trackChain);
     262            0 :     if (!ret) {
     263            0 :         return RetType::make_ret(ret);
     264              :     }
     265              : 
     266              :     // Make track info
     267            0 :     Track track;
     268            0 :     track.type = TrackType::Aux_track;
     269            0 :     track.id = trackId;
     270            0 :     track.name = trackName;
     271            0 :     track.params = params;
     272            0 :     track.chain = trackChain;
     273              : 
     274            0 :     onFxChainParamsChanged(track, params.fxChain);
     275            0 :     onControlParamsChanged(track, params.control);
     276              : 
     277            0 :     doAddTrack(track);
     278              : 
     279            0 :     return RetType::make_ok(trackId, track.params);
     280            0 : }
     281              : 
     282            0 : void AudioContext::doAddTrack(const Track& track)
     283              : {
     284              :     ONLY_AUDIO_ENGINE_THREAD;
     285            0 :     const TrackId trackId = track.id;
     286              : 
     287            0 :     if (auto source = std::dynamic_pointer_cast<AudioSourceNode>(track.chain->source())) {
     288            0 :         source->seek(m_player->currentPosition());
     289            0 :         source->inputParamsChanged().onReceive(this, [this, trackId](const AudioInputParams& params) {
     290            0 :             m_sourceParamsChanged.send(trackId, params);
     291            0 :         });
     292            0 :     }
     293              : 
     294            0 :     m_tracks.push_back(track);
     295            0 :     m_trackAdded.send(trackId);
     296              : 
     297            0 :     updateNonMutedTrackCount();
     298            0 : }
     299              : 
     300            0 : void AudioContext::onSourceParamsChanged(Track& track, const AudioSourceParams& params)
     301              : {
     302              :     ONLY_AUDIO_ENGINE_THREAD;
     303            0 :     if (auto source = std::dynamic_pointer_cast<AudioSourceNode>(track.chain->source())) {
     304            0 :         source->applyInputParams(params);
     305            0 :     }
     306              : 
     307            0 :     track.params.source = params;
     308            0 : }
     309              : 
     310            0 : void AudioContext::onControlParamsChanged(Track& track, const ControlParams& params)
     311              : {
     312              :     ONLY_AUDIO_ENGINE_THREAD;
     313            0 :     if (auto control = track.chain->control()) {
     314            0 :         const bool mutedChanged = control->muted() != params.muted;
     315              : 
     316            0 :         control->setVolume(params.volume);
     317            0 :         control->setPan(params.balance);
     318            0 :         control->setMuted(params.muted);
     319              : 
     320              :         //! NOTE For regular tracks
     321            0 :         if (mutedChanged && !params.muted && track.chain->source()) {
     322            0 :             if (auto source = std::dynamic_pointer_cast<AudioSourceNode>(track.chain->source())) {
     323            0 :                 source->seek(m_player->currentPosition());
     324            0 :             }
     325              :         }
     326            0 :     }
     327              : 
     328            0 :     track.params.control = params;
     329              : 
     330            0 :     updateNonMutedTrackCount();
     331            0 : }
     332              : 
     333            0 : void AudioContext::updateNonMutedTrackCount()
     334              : {
     335              :     ONLY_AUDIO_ENGINE_THREAD;
     336            0 :     size_t count = 0;
     337            0 :     for (const Track& t : m_tracks) {
     338            0 :         if (auto control = t.chain->control()) {
     339            0 :             if (!control->muted()) {
     340            0 :                 count++;
     341              :             }
     342            0 :         }
     343              :     }
     344            0 :     m_mixer->setNonMutedTrackCount(count);
     345            0 : }
     346              : 
     347            0 : void AudioContext::onFxChainParamsChanged(Track& track, const AudioFxChain& params)
     348              : {
     349              :     ONLY_AUDIO_ENGINE_THREAD;
     350              : 
     351            0 :     const TrackId trackId = track.id;
     352            0 :     std::shared_ptr<IPlayheadPosition> playheadPosition = std::static_pointer_cast<IPlayheadPosition>(m_player);
     353              : 
     354              :     // Make fx chain
     355            0 :     FxChainPtr fxChain = audioFactory()->makeTrackFxChain(trackId, params);
     356            0 :     fxChain->setPlayheadPosition(playheadPosition);
     357            0 :     fxChain->fxChainSpecChanged().onReceive(this, [this, trackId](const AudioFxChain& fxChainSpec) {
     358            0 :         if (Track* t = this->track(trackId)) {
     359            0 :             t->params.fxChain = fxChainSpec;
     360            0 :             m_fxChainParamsChanged.send(trackId, t->params.fxChain);
     361              :         }
     362            0 :     });
     363              : 
     364            0 :     fxChain->shouldProcessDuringSilenceChanged().onReceive(this, [this, trackId](bool shouldProcess) {
     365            0 :         onShouldProcessDuringSilenceChanged(trackId, shouldProcess);
     366            0 :     });
     367              : 
     368            0 :     track.chain->setFxChain(fxChain);
     369            0 :     track.chain->rebuild();
     370              : 
     371            0 :     track.params.fxChain = fxChain->fxChainSpec();
     372            0 : }
     373              : 
     374            0 : void AudioContext::onAuxSendsParamsChanged(Track& track, const AuxSendsParams& params)
     375              : {
     376              :     ONLY_AUDIO_ENGINE_THREAD;
     377              : 
     378            0 :     m_mixer->setAuxSends(track.id, params);
     379              : 
     380            0 :     track.params.auxSends = params;
     381            0 : }
     382              : 
     383            0 : void AudioContext::removeTrack(const TrackId trackId)
     384              : {
     385              :     ONLY_AUDIO_ENGINE_THREAD;
     386              : 
     387            0 :     auto it = std::find_if(m_tracks.begin(), m_tracks.end(), [trackId](const Track& t) {
     388            0 :         return t.id == trackId;
     389              :     });
     390              : 
     391            0 :     if (it == m_tracks.end()) {
     392            0 :         return;
     393              :     }
     394              : 
     395            0 :     Track track = *it;
     396            0 :     if (auto source = std::dynamic_pointer_cast<AudioSourceNode>(track.chain->source())) {
     397            0 :         source->inputParamsChanged().disconnect(this);
     398            0 :     }
     399              : 
     400            0 :     m_mixer->removeTrack(trackId);
     401            0 :     m_tracks.erase(it);
     402            0 :     muse::remove(m_tracksToProcessWhenIdle, trackId);
     403              : 
     404            0 :     if (m_prevActiveTrackId == trackId) {
     405            0 :         m_prevActiveTrackId = INVALID_TRACK_ID;
     406              :     }
     407              : 
     408            0 :     m_trackRemoved.send(trackId);
     409            0 : }
     410              : 
     411            0 : void AudioContext::removeAllTracks()
     412              : {
     413              :     ONLY_AUDIO_ENGINE_THREAD;
     414              : 
     415            0 :     for (const TrackId& id : trackIdList().val) {
     416            0 :         removeTrack(id);
     417            0 :     }
     418            0 : }
     419              : 
     420            0 : async::Channel<TrackId> AudioContext::trackAdded() const
     421              : {
     422              :     ONLY_AUDIO_ENGINE_THREAD;
     423            0 :     return m_trackAdded;
     424              : }
     425              : 
     426            0 : async::Channel<TrackId> AudioContext::trackRemoved() const
     427              : {
     428              :     ONLY_AUDIO_ENGINE_THREAD;
     429            0 :     return m_trackRemoved;
     430              : }
     431              : 
     432            0 : RetVal<TrackIdList> AudioContext::trackIdList() const
     433              : {
     434              :     ONLY_AUDIO_ENGINE_THREAD;
     435              : 
     436            0 :     TrackIdList result;
     437            0 :     result.reserve(m_tracks.size());
     438              : 
     439            0 :     for (const auto& t : m_tracks) {
     440            0 :         result.push_back(t.id);
     441              :     }
     442              : 
     443            0 :     return RetVal<TrackIdList>::make_ok(result);
     444            0 : }
     445              : 
     446            0 : const AudioContext::Track* AudioContext::track(const TrackId id) const
     447              : {
     448              :     ONLY_AUDIO_ENGINE_THREAD;
     449              : 
     450            0 :     if (id == MASTER_TRACK_ID) {
     451            0 :         return &m_masterTrack;
     452              :     }
     453              : 
     454            0 :     for (const Track& t : m_tracks) {
     455            0 :         if (t.id == id) {
     456            0 :             return &t;
     457              :         }
     458              :     }
     459            0 :     return nullptr;
     460              : }
     461              : 
     462            0 : AudioContext::Track* AudioContext::track(const TrackId id)
     463              : {
     464              :     ONLY_AUDIO_ENGINE_THREAD;
     465              : 
     466            0 :     if (id == MASTER_TRACK_ID) {
     467            0 :         return &m_masterTrack;
     468              :     }
     469              : 
     470            0 :     for (Track& t : m_tracks) {
     471            0 :         if (t.id == id) {
     472            0 :             return &t;
     473              :         }
     474              :     }
     475            0 :     return nullptr;
     476              : }
     477              : 
     478            0 : RetVal<TrackName> AudioContext::trackName(const TrackId trackId) const
     479              : {
     480              :     ONLY_AUDIO_ENGINE_THREAD;
     481              : 
     482            0 :     if (const Track* t = track(trackId)) {
     483            0 :         return RetVal<TrackName>::make_ok(t->name);
     484              :     }
     485              : 
     486            0 :     return RetVal<TrackName>::make_ret((int)Err::InvalidTrackId, "no track");
     487              : }
     488              : 
     489            0 : AudioSourceNodePtr AudioContext::trackSource(const TrackId trackId) const
     490              : {
     491              :     ONLY_AUDIO_ENGINE_THREAD;
     492            0 :     if (const Track* t = track(trackId)) {
     493            0 :         return std::dynamic_pointer_cast<AudioSourceNode>(t->chain->source());
     494              :     }
     495            0 :     return nullptr;
     496              : }
     497              : 
     498            0 : std::vector<AudioSourceNodePtr> AudioContext::allTracksSources() const
     499              : {
     500              :     ONLY_AUDIO_ENGINE_THREAD;
     501            0 :     std::vector<AudioSourceNodePtr> result;
     502            0 :     result.reserve(m_tracks.size());
     503            0 :     for (const Track& t : m_tracks) {
     504            0 :         if (auto source = std::dynamic_pointer_cast<AudioSourceNode>(t.chain->source())) {
     505            0 :             result.push_back(source);
     506            0 :         }
     507              :     }
     508            0 :     return result;
     509            0 : }
     510              : 
     511            0 : void AudioContext::onShouldProcessDuringSilenceChanged(const TrackId trackId, bool shouldProcess)
     512              : {
     513              :     ONLY_AUDIO_ENGINE_THREAD;
     514            0 :     if (shouldProcess) {
     515            0 :         m_tracksToProcessWhenIdle.insert(trackId);
     516              :     } else {
     517            0 :         muse::remove(m_tracksToProcessWhenIdle, trackId);
     518              :     }
     519              : 
     520            0 :     m_mixer->setTracksToProcessWhenIdle(m_tracksToProcessWhenIdle);
     521            0 : }
     522              : 
     523              : // Sources
     524            0 : AudioResourceMetaList AudioContext::availableInputResources() const
     525              : {
     526              :     ONLY_AUDIO_ENGINE_THREAD;
     527            0 :     return audioFactory()->availableInputResources();
     528              : }
     529              : 
     530            0 : SoundPresetList AudioContext::availableSoundPresets(const AudioResourceMeta& resourceMeta) const
     531              : {
     532              :     ONLY_AUDIO_ENGINE_THREAD;
     533            0 :     return audioFactory()->availableSoundPresets(resourceMeta);
     534              : }
     535              : 
     536            0 : RetVal<TrackParams> AudioContext::params(const TrackId trackId) const
     537              : {
     538              :     ONLY_AUDIO_ENGINE_THREAD;
     539            0 :     if (const Track* t = track(trackId)) {
     540            0 :         return RetVal<TrackParams>::make_ok(t->params);
     541              :     }
     542              : 
     543            0 :     return RetVal<TrackParams>::make_ret(Err::InvalidTrackId);
     544              : }
     545              : 
     546            0 : void AudioContext::setSourceParams(const TrackId trackId, const AudioSourceParams& params)
     547              : {
     548              :     ONLY_AUDIO_ENGINE_THREAD;
     549            0 :     if (Track* t = track(trackId)) {
     550            0 :         if (t->params.source != params) {
     551            0 :             onSourceParamsChanged(*t, params);
     552            0 :             m_sourceParamsChanged.send(trackId, t->params.source);
     553              :         }
     554              :     }
     555            0 : }
     556              : 
     557            0 : void AudioContext::setControlParams(const TrackId trackId, const ControlParams& params)
     558              : {
     559              :     ONLY_AUDIO_ENGINE_THREAD;
     560            0 :     if (Track* t = track(trackId)) {
     561            0 :         if (t->params.control != params) {
     562            0 :             onControlParamsChanged(*t, params);
     563            0 :             m_controlParamsChanged.send(trackId, t->params.control);
     564              :         }
     565              :     }
     566            0 : }
     567              : 
     568            0 : void AudioContext::setFxChainParams(const TrackId trackId, const AudioFxChain& params)
     569              : {
     570              :     ONLY_AUDIO_ENGINE_THREAD;
     571            0 :     if (Track* t = track(trackId)) {
     572            0 :         if (t->params.fxChain != params) {
     573            0 :             onFxChainParamsChanged(*t, params);
     574            0 :             m_fxChainParamsChanged.send(trackId, t->params.fxChain);
     575              :         }
     576              :     }
     577            0 : }
     578              : 
     579            0 : void AudioContext::setAuxSendsParams(const TrackId trackId, const AuxSendsParams& params)
     580              : {
     581              :     ONLY_AUDIO_ENGINE_THREAD;
     582            0 :     if (Track* t = track(trackId)) {
     583            0 :         if (t->params.auxSends != params) {
     584            0 :             onAuxSendsParamsChanged(*t, params);
     585            0 :             m_auxSendsParamsChanged.send(trackId, t->params.auxSends);
     586              :         }
     587              :     }
     588            0 : }
     589              : 
     590            0 : async::Channel<TrackId, AudioSourceParams> AudioContext::sourceParamsChanged() const
     591              : {
     592              :     ONLY_AUDIO_ENGINE_THREAD;
     593            0 :     return m_sourceParamsChanged;
     594              : }
     595              : 
     596            0 : async::Channel<TrackId, ControlParams> AudioContext::controlParamsChanged() const
     597              : {
     598              :     ONLY_AUDIO_ENGINE_THREAD;
     599            0 :     return m_controlParamsChanged;
     600              : }
     601              : 
     602            0 : async::Channel<TrackId, AudioFxChain> AudioContext::fxChainParamsChanged() const
     603              : {
     604              :     ONLY_AUDIO_ENGINE_THREAD;
     605            0 :     return m_fxChainParamsChanged;
     606              : }
     607              : 
     608            0 : async::Channel<TrackId, AuxSendsParams> AudioContext::auxSendsParamsChanged() const
     609              : {
     610              :     ONLY_AUDIO_ENGINE_THREAD;
     611            0 :     return m_auxSendsParamsChanged;
     612              : }
     613              : 
     614            0 : void AudioContext::processInput(const TrackId trackId) const
     615              : {
     616              :     ONLY_AUDIO_ENGINE_THREAD;
     617            0 :     if (const Track* t = track(trackId)) {
     618            0 :         if (auto source = std::dynamic_pointer_cast<AudioSourceNode>(t->chain->source())) {
     619            0 :             source->processInput();
     620            0 :         }
     621              :     }
     622            0 : }
     623              : 
     624            0 : RetVal<InputProcessingProgress> AudioContext::inputProcessingProgress(const TrackId trackId) const
     625              : {
     626              :     ONLY_AUDIO_ENGINE_THREAD;
     627            0 :     if (const Track* t = track(trackId)) {
     628            0 :         if (auto source = std::dynamic_pointer_cast<AudioSourceNode>(t->chain->source())) {
     629            0 :             return RetVal<InputProcessingProgress>::make_ok(source->inputProcessingProgress());
     630            0 :         }
     631              :     }
     632              : 
     633            0 :     return make_ret(Err::InvalidTrackId);
     634              : }
     635              : 
     636            0 : void AudioContext::clearCache(const TrackId trackId) const
     637              : {
     638              :     ONLY_AUDIO_ENGINE_THREAD;
     639            0 :     if (const Track* t = track(trackId)) {
     640            0 :         if (auto source = std::dynamic_pointer_cast<AudioSourceNode>(t->chain->source())) {
     641            0 :             source->clearCache();
     642            0 :         }
     643              :     }
     644            0 : }
     645              : 
     646            0 : void AudioContext::clearSources()
     647              : {
     648              :     ONLY_AUDIO_ENGINE_THREAD;
     649            0 :     audioFactory()->clearSynthSources();
     650            0 : }
     651              : 
     652            0 : AudioResourceMetaList AudioContext::availableOutputResources() const
     653              : {
     654              :     ONLY_AUDIO_ENGINE_THREAD;
     655            0 :     return audioFactory()->availableOutputResources();
     656              : }
     657              : 
     658            0 : RetVal<AudioSignalChanges> AudioContext::signalChanges(const TrackId trackId) const
     659              : {
     660              :     ONLY_AUDIO_ENGINE_THREAD;
     661            0 :     if (const Track* t = track(trackId)) {
     662            0 :         if (auto signal = t->chain->signal()) {
     663            0 :             return RetVal<AudioSignalChanges>::make_ok(signal->audioSignalChanges());
     664            0 :         }
     665              :     }
     666              : 
     667            0 :     return RetVal<AudioSignalChanges>::make_ret(Err::InvalidTrackId);
     668              : }
     669              : 
     670            0 : RetVal<AutomatedControlParamsChanges> AudioContext::automatedControlParamsChanges(const TrackId trackId) const
     671              : {
     672              :     ONLY_AUDIO_ENGINE_THREAD;
     673            0 :     if (const Track* t = track(trackId)) {
     674            0 :         if (auto control = t->chain->control()) {
     675            0 :             return RetVal<AutomatedControlParamsChanges>::make_ok(control->automatedControlParamsChanges());
     676            0 :         }
     677              :     }
     678              : 
     679            0 :     return RetVal<AutomatedControlParamsChanges>::make_ret(Err::InvalidTrackId);
     680              : }
     681              : 
     682            0 : void AudioContext::clearMasterOutputParams()
     683              : {
     684              :     ONLY_AUDIO_ENGINE_THREAD;
     685            0 :     setControlParams(MASTER_TRACK_ID, ControlParams());
     686            0 :     setFxChainParams(MASTER_TRACK_ID, AudioFxChain());
     687            0 : }
     688              : 
     689            0 : void AudioContext::clearAllFx()
     690              : {
     691              :     ONLY_AUDIO_ENGINE_THREAD;
     692            0 :     audioFactory()->clearAllFx();
     693            0 : }
     694              : 
     695              : // Play
     696            0 : async::Promise<Ret> AudioContext::prepareToPlay()
     697              : {
     698              :     ONLY_AUDIO_ENGINE_THREAD;
     699            0 :     return m_player->prepareToPlay();
     700              : }
     701              : 
     702            0 : void AudioContext::play(const secs_t delay)
     703              : {
     704            0 :     LOGD() << "\nAudioContext chain: " << m_playheadNode->dump();
     705              : 
     706              :     ONLY_AUDIO_ENGINE_THREAD;
     707            0 :     m_player->play(delay);
     708            0 : }
     709              : 
     710            0 : void AudioContext::seek(const secs_t newPosition, const bool flushSound)
     711              : {
     712              :     ONLY_AUDIO_ENGINE_THREAD;
     713            0 :     m_player->seek(TimePosition::fromTime(newPosition, m_outputSpec.sampleRate), flushSound);
     714            0 : }
     715              : 
     716            0 : void AudioContext::stop()
     717              : {
     718              :     ONLY_AUDIO_ENGINE_THREAD;
     719            0 :     m_player->stop();
     720            0 : }
     721              : 
     722            0 : void AudioContext::pause()
     723              : {
     724              :     ONLY_AUDIO_ENGINE_THREAD;
     725            0 :     m_player->pause();
     726            0 : }
     727              : 
     728            0 : void AudioContext::resume(const secs_t delay)
     729              : {
     730              :     ONLY_AUDIO_ENGINE_THREAD;
     731            0 :     m_player->resume(delay);
     732            0 : }
     733              : 
     734            0 : void AudioContext::setDuration(const secs_t duration)
     735              : {
     736              :     ONLY_AUDIO_ENGINE_THREAD;
     737            0 :     m_player->setDuration(duration);
     738            0 : }
     739              : 
     740            0 : Ret AudioContext::setLoop(const secs_t from, const secs_t to)
     741              : {
     742              :     ONLY_AUDIO_ENGINE_THREAD;
     743            0 :     return m_player->setLoop(from, to);
     744              : }
     745              : 
     746            0 : void AudioContext::resetLoop()
     747              : {
     748              :     ONLY_AUDIO_ENGINE_THREAD;
     749            0 :     m_player->resetLoop();
     750            0 : }
     751              : 
     752            0 : PlaybackStatus AudioContext::playbackStatus() const
     753              : {
     754              :     ONLY_AUDIO_ENGINE_THREAD;
     755            0 :     return m_player->playbackStatus();
     756              : }
     757              : 
     758            0 : async::Channel<PlaybackStatus> AudioContext::playbackStatusChanged() const
     759              : {
     760              :     ONLY_AUDIO_ENGINE_THREAD;
     761            0 :     return m_player->playbackStatusChanged();
     762              : }
     763              : 
     764            0 : secs_t AudioContext::playbackPosition() const
     765              : {
     766              :     ONLY_AUDIO_ENGINE_THREAD;
     767            0 :     return m_player->playbackPosition();
     768              : }
     769              : 
     770            0 : async::Channel<secs_t> AudioContext::playbackPositionChanged() const
     771              : {
     772              :     ONLY_AUDIO_ENGINE_THREAD;
     773            0 :     return m_player->playbackPositionChanged();
     774              : }
     775              : 
     776              : // Export
     777            0 : async::Promise<Ret> AudioContext::saveSoundTrack(io::IODevice& dstDevice, const SoundTrackFormat& format)
     778              : {
     779            0 :     return async::make_promise<Ret>([this, &dstDevice, format](auto resolve, auto) {
     780              :         ONLY_AUDIO_ENGINE_THREAD;
     781              : 
     782              : #ifdef MUSE_MODULE_AUDIO_EXPORT
     783              :         //! NOTE These engine state changes must run inside execOperation so they are
     784              :         // synchronized with the audio driver process (see doSaveSoundTrack).
     785            0 :         Operation prepare = [this]() {
     786            0 :             m_player->stop();
     787            0 :             m_player->seek(TimePosition::zero(m_outputSpec.sampleRate));
     788              :         };
     789            0 :         if (m_execOperation) {
     790            0 :             m_execOperation->execOperation(OperationType::LongOperation, prepare);
     791              :         } else {
     792            0 :             prepare();
     793              :         }
     794              : 
     795            0 :         const bool lazyProcessingWasEnabled = configuration()->isLazyProcessingOfOnlineSoundsEnabled();
     796            0 :         configuration()->setIsLazyProcessingOfOnlineSoundsEnabled(false);
     797              : 
     798            0 :         listenInputProcessing([this, &dstDevice, format, lazyProcessingWasEnabled, resolve](Ret ret) {
     799            0 :             if (ret) {
     800            0 :                 ret = doSaveSoundTrack(dstDevice, format);
     801              :             }
     802              : 
     803            0 :             configuration()->setIsLazyProcessingOfOnlineSoundsEnabled(lazyProcessingWasEnabled);
     804            0 :             (void)resolve(ret);
     805              :         });
     806              : 
     807            0 :         return async::Promise<Ret>::dummy_result();
     808              : #else
     809              :         return resolve(make_ret(Err::DisabledAudioExport, "audio export is disabled"));
     810              : #endif
     811            0 :     });
     812              : }
     813              : 
     814            0 : bool AudioContext::hasPendingChunks(const TrackId trackId) const
     815              : {
     816              :     ONLY_AUDIO_ENGINE_THREAD;
     817              : 
     818            0 :     if (const Track* t = track(trackId)) {
     819            0 :         if (auto source = std::dynamic_pointer_cast<AudioSourceNode>(t->chain->source())) {
     820            0 :             return source->hasPendingChunks();
     821            0 :         }
     822              :     }
     823              : 
     824            0 :     return false;
     825              : }
     826              : 
     827            0 : size_t AudioContext::tracksBeingProcessedCount() const
     828              : {
     829            0 :     size_t count = 0;
     830              : 
     831            0 :     for (TrackId trackId : trackIdList().val) {
     832            0 :         if (inputProcessingProgress(trackId).val.isStarted || hasPendingChunks(trackId)) {
     833            0 :             count++;
     834              :         }
     835            0 :     }
     836              : 
     837            0 :     return count;
     838              : }
     839              : 
     840            0 : void AudioContext::listenInputProcessing(std::function<void(const Ret&)> completed)
     841              : {
     842              : #ifdef MUSE_MODULE_AUDIO_EXPORT
     843              : 
     844            0 :     auto soundsInProgress = std::make_shared<size_t>(0);
     845              : 
     846            0 :     for (TrackId trackId : trackIdList().val) {
     847            0 :         if (!isOnlineAudioResource(params(trackId).val.source.resourceMeta)) {
     848            0 :             continue;
     849              :         }
     850              : 
     851            0 :         InputProcessingProgress inputProgress = inputProcessingProgress(trackId).val;
     852            0 :         if (!inputProgress.isStarted && !hasPendingChunks(trackId)) {
     853            0 :             continue;
     854              :         }
     855              : 
     856            0 :         (*soundsInProgress)++;
     857              : 
     858            0 :         if (inputProgress.isStarted) {
     859            0 :             m_saveSoundTracksProgress.progress.send(0, 100, SaveSoundTrackStage::ProcessingOnlineSounds);
     860              :         }
     861              : 
     862              :         using StatusInfo = InputProcessingProgress::StatusInfo;
     863              :         using ChunkInfoList = InputProcessingProgress::ChunkInfoList;
     864              :         using ProgressInfo = InputProcessingProgress::ProgressInfo;
     865              : 
     866            0 :         auto ch = inputProgress.processedChannel;
     867            0 :         ch.onReceive(this, [this, trackId, soundsInProgress, completed](const StatusInfo& status,
     868              :                                                                         const ChunkInfoList&,
     869              :                                                                         const ProgressInfo& info) {
     870            0 :             const size_t tracksBeingProcessedCount = this->tracksBeingProcessedCount();
     871              : 
     872            0 :             if (status.status == InputProcessingProgress::Status::Finished) {
     873            0 :                 inputProcessingProgress(trackId).val.processedChannel.disconnect(this);
     874              :             }
     875              : 
     876            0 :             if (tracksBeingProcessedCount == 0) {
     877            0 :                 m_saveSoundTracksProgress.aborted.disconnect(this);
     878            0 :                 completed(make_ok());
     879            0 :             } else if (tracksBeingProcessedCount == 1) {
     880            0 :                 m_saveSoundTracksProgress.progress.send(info.current, info.total, SaveSoundTrackStage::ProcessingOnlineSounds);
     881              :             } else {
     882            0 :                 const int64_t percentage = 100 - (100 / *soundsInProgress) * tracksBeingProcessedCount;
     883            0 :                 m_saveSoundTracksProgress.progress.send(percentage, 100, SaveSoundTrackStage::ProcessingOnlineSounds);
     884              :             }
     885            0 :         });
     886            0 :     }
     887              : 
     888            0 :     if ((*soundsInProgress) == 0) {
     889            0 :         completed(make_ok());
     890            0 :         return;
     891              :     }
     892              : 
     893            0 :     m_saveSoundTracksProgress.aborted.onNotify(this, [this, completed]() {
     894            0 :         m_saveSoundTracksProgress.aborted.disconnect(this);
     895              : 
     896            0 :         for (TrackId trackId : trackIdList().val) {
     897            0 :             if (isOnlineAudioResource(params(trackId).val.source.resourceMeta)) {
     898            0 :                 inputProcessingProgress(trackId).val.processedChannel.disconnect(this);
     899              :             }
     900            0 :         }
     901              : 
     902            0 :         completed(make_ret(Ret::Code::Cancel));
     903            0 :     });
     904              : #else
     905              :     UNUSED(completed);
     906              : #endif
     907            0 : }
     908              : 
     909            0 : Ret AudioContext::doSaveSoundTrack(io::IODevice& dstDevice, const SoundTrackFormat& format)
     910              : {
     911              : #ifdef MUSE_MODULE_AUDIO_EXPORT
     912              :     using namespace muse::audio::soundtrack;
     913              : 
     914            0 :     const secs_t totalDuration = m_player->duration();
     915            0 :     auto writer = std::make_shared<SoundTrackWriter>(dstDevice, format, totalDuration, m_mixer);
     916              : 
     917            0 :     writer->progress().progressChanged().onReceive(this, [this](int64_t current, int64_t total, std::string /*title*/) {
     918            0 :         m_saveSoundTracksProgress.progress.send(current, total, SaveSoundTrackStage::WritingSoundTrack);
     919            0 :     });
     920              : 
     921            0 :     std::weak_ptr<SoundTrackWriter> weakPtr = writer;
     922            0 :     m_saveSoundTracksProgress.aborted.onNotify(this, [weakPtr]() {
     923            0 :         if (auto writer = weakPtr.lock()) {
     924            0 :             writer->abort();
     925            0 :         }
     926            0 :     });
     927              : 
     928              :     //! NOTE The offline render and the source/engine state changes around it must run
     929              :     // inside execOperation so the audio driver process is muted for the whole export.
     930              :     // Otherwise the real time driver renders the shared graph (mixer, FX) concurrently
     931              :     // with the export, which is a data race on shared processors such as the reverb.
     932            0 :     Ret ret;
     933            0 :     Operation func = [this, writer, &ret]() {
     934            0 :         setMode(ProcessMode::PlayingOffline);
     935            0 :         ret = writer->write();
     936            0 :         m_mixer->setOutputSpec(outputSpec());
     937            0 :         setMode(ProcessMode::Idle);
     938            0 :         m_player->seek(TimePosition::zero(m_outputSpec.sampleRate));
     939            0 :     };
     940              : 
     941            0 :     if (m_execOperation) {
     942            0 :         m_execOperation->execOperation(OperationType::LongOperation, func);
     943              :     } else {
     944            0 :         func();
     945              :     }
     946              : 
     947            0 :     m_saveSoundTracksProgress.aborted.disconnect(this);
     948              : 
     949            0 :     return ret;
     950              : #else
     951              :     return make_ret(Err::DisabledAudioExport, "audio export is disabled");
     952              : #endif
     953            0 : }
     954              : 
     955            0 : void AudioContext::abortSavingAllSoundTracks()
     956              : {
     957              : #ifdef MUSE_MODULE_AUDIO_EXPORT
     958            0 :     m_saveSoundTracksProgress.aborted.notify();
     959              : #endif
     960            0 : }
     961              : 
     962            0 : SaveSoundTrackProgress AudioContext::saveSoundTrackProgressChanged() const
     963              : {
     964              :     ONLY_AUDIO_ENGINE_THREAD;
     965            0 :     return m_saveSoundTracksProgress.progress;
     966              : }
     967              : 
     968              : // Processing
     969            0 : void AudioContext::doSelfProcess(float* buffer, samples_t samplesPerChannel)
     970              : {
     971              :     ONLY_AUDIO_PROC_THREAD;
     972            0 :     m_playheadNode->process(buffer, samplesPerChannel);
     973            0 : }
        

Generated by: LCOV version 2.0-1