LCOV - code coverage report
Current view: top level - muse/framework/audio/main/internal - startaudiocontroller.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 0.7 % 137 1
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              : #include "startaudiocontroller.h"
      23              : 
      24              : #include "global/realfn.h"
      25              : #include "global/runtime.h"
      26              : #include "global/async/processevents.h"
      27              : 
      28              : #include "audio/common/audiosanitizer.h"
      29              : #include "audio/common/rpc/rpcpacker.h"
      30              : #include "audio/common/workmode.h"
      31              : #include "audio/common/alignmentbuffer.h"
      32              : 
      33              : #include "audio/engine/platform/general/generalaudioworker.h"
      34              : 
      35              : #ifndef Q_OS_WASM
      36              : #include "audio/engine/internal/enginecontroller.h"
      37              : #endif
      38              : 
      39              : #include "audio/devtools/inputlag.h"
      40              : 
      41              : #include "log.h"
      42              : 
      43              : using namespace muse;
      44              : using namespace muse::audio;
      45              : using namespace muse::audio::rpc;
      46              : 
      47            0 : static void measureInputLag(const float* buf, const size_t size)
      48              : {
      49            0 :     if (INPUT_LAG_TIMER_STARTED) {
      50            0 :         for (size_t i = 0; i < size; ++i) {
      51            0 :             if (!muse::RealIsNull(buf[i])) {
      52            0 :                 STOP_INPUT_LAG_TIMER;
      53            0 :                 return;
      54              :             }
      55              :         }
      56              :     }
      57              : }
      58              : 
      59            0 : StartAudioController::StartAudioController(std::shared_ptr<rpc::IRpcChannel> rpcChannel)
      60            0 :     : m_rpcChannel(rpcChannel)
      61              : {
      62            0 : }
      63              : 
      64              : #ifndef Q_OS_WASM
      65            0 : void StartAudioController::th_setupEngine()
      66              : {
      67            0 :     LOGI() << "begin engine run";
      68            0 :     runtime::setThreadName("audio_engine");
      69            0 :     AudioSanitizer::setupEngineThread();
      70              :     ONLY_AUDIO_ENGINE_THREAD;
      71              : 
      72            0 :     m_rpcChannel->setupOnEngine();
      73              : 
      74            0 :     m_engineController = std::make_shared<engine::EngineController>(m_rpcChannel);
      75            0 :     m_engineController->onStartRunning();
      76              : 
      77            0 :     LOGI() << "audio engine running";
      78            0 : }
      79              : 
      80              : #endif
      81              : 
      82            0 : void StartAudioController::init()
      83              : {
      84            0 :     m_rpcChannel->onNotification(rpc::GLOBAL_CTX_ID, rpc::MsgCode::EngineRunning, [this](const rpc::Msg&) {
      85            0 :         soundFontController()->loadSoundFonts();
      86              : 
      87            0 :         m_isEngineRunning.set(true);
      88            0 :     });
      89              : 
      90            0 :     workmode::load();
      91              : 
      92              : #ifndef Q_OS_WASM
      93            0 :     if (workmode::mode() == workmode::HybridMode) {
      94            0 :         m_worker = std::make_shared<engine::GeneralAudioWorker>();
      95            0 :         m_worker->run([this]() {
      96              :             static bool once = false;
      97            0 :             if (!once) {
      98            0 :                 th_setupEngine();
      99            0 :                 m_worker->setInterval(16 /*msec*/);
     100            0 :                 once = true;
     101              :             }
     102              : 
     103            0 :             static const std::thread::id thisThId = std::this_thread::get_id();
     104              : 
     105            0 :             async::processMessages(thisThId);
     106            0 :             m_rpcChannel->process();
     107            0 :         });
     108              :     }
     109              : #endif // not Q_OS_WASM
     110            0 : }
     111              : 
     112            0 : bool StartAudioController::isAudioStarted() const
     113              : {
     114            0 :     return m_isAudioStarted.val;
     115              : }
     116              : 
     117            0 : async::Channel<bool> StartAudioController::isAudioStartedChanged() const
     118              : {
     119            0 :     return m_isAudioStarted.ch;
     120              : }
     121              : 
     122            0 : void StartAudioController::startAudioProcessing(const IApplication::RunMode& mode)
     123              : {
     124            0 :     IAudioDriver::Spec requiredSpec;
     125            0 :     requiredSpec.deviceId = configuration()->audioOutputDeviceId();
     126            0 :     requiredSpec.output = configuration()->desiredOutputSpec();
     127              : 
     128              : #ifndef Q_OS_WASM
     129              : 
     130            0 :     m_requiredSamplesTotal = requiredSpec.output.samplesPerChannel * requiredSpec.output.audioChannelCount;
     131              : 
     132            0 :     audioDriverController()->activeSpecChanged().onReceive(this, [this](const IAudioDriver::Spec& spec) {
     133            0 :         m_requiredSamplesTotal = spec.output.samplesPerChannel * spec.output.audioChannelCount;
     134            0 :     });
     135              : 
     136            0 :     bool shouldMeasureInputLag = configuration()->shouldMeasureInputLag();
     137            0 :     requiredSpec.callback = [this, shouldMeasureInputLag]
     138              :                             (uint8_t* stream, int byteCount) {
     139            0 :         std::memset(stream, 0, byteCount);
     140              :         // driver metrics
     141            0 :         const size_t driverSamplesTotal = byteCount / sizeof(float);
     142            0 :         float* driverDest = reinterpret_cast<float*>(stream);
     143              : 
     144            0 :         const size_t requiredSamplesTotal = m_requiredSamplesTotal;
     145            0 :         const bool useAlignBuffer = driverSamplesTotal < requiredSamplesTotal;
     146              : 
     147              :         // process metrics
     148            0 :         size_t procSamplesTotal = 0;
     149            0 :         size_t procSamplesPerChannel = 0;
     150            0 :         float* procDest = 0;
     151              : 
     152            0 :         AlignmentBuffer* alignbuf = nullptr;
     153            0 :         if (useAlignBuffer) {
     154              :             // setup align buffer if need
     155            0 :             const size_t blocks = 2;
     156            0 :             const size_t capacity = requiredSamplesTotal * blocks;
     157            0 :             if (!m_alignmentBuffer || m_alignmentBuffer->capacity() != capacity) {
     158            0 :                 m_alignmentBuffer = std::make_shared<AlignmentBuffer>(capacity);
     159              :             }
     160            0 :             alignbuf = m_alignmentBuffer.get();     // minor optimization and easier debugging
     161            1 :             static thread_local std::vector<float> proc_buf;     // temp buffer
     162            0 :             if (proc_buf.size() != requiredSamplesTotal) {
     163            0 :                 proc_buf.resize(requiredSamplesTotal);
     164              :             }
     165              : 
     166              :             // set proc metrics
     167            0 :             procSamplesTotal = requiredSamplesTotal;
     168            0 :             procSamplesPerChannel = procSamplesTotal / 2;
     169            0 :             procDest = &proc_buf[0];
     170              :         } else {
     171            0 :             procSamplesTotal = driverSamplesTotal;
     172            0 :             procSamplesPerChannel = procSamplesTotal / 2;
     173            0 :             procDest = driverDest;
     174              :         }
     175              : 
     176              :         // try fill data
     177            0 :         if (useAlignBuffer) {
     178            0 :             assert(alignbuf);
     179            0 :             if (alignbuf->availableRead() >= driverSamplesTotal) {
     180            0 :                 alignbuf->read(driverDest, driverSamplesTotal);
     181            0 :                 return;
     182              :             }
     183              :         }
     184              : 
     185              :         // process
     186            0 :         if (workmode::mode() == workmode::HybridMode) {
     187            0 :             m_engineController->process(procDest, (unsigned)procSamplesPerChannel);
     188            0 :         } else if (workmode::mode() == workmode::DriverMode) {
     189              :             static bool once = false;
     190            0 :             if (!once) {
     191            0 :                 th_setupEngine();
     192            0 :                 once = true;
     193              :             }
     194              : 
     195            0 :             m_rpcChannel->process();
     196            0 :             m_engineController->process(procDest, (unsigned)procSamplesPerChannel);
     197              :         } else {
     198            0 :             UNREACHABLE;
     199              :         }
     200              : 
     201              :         // write temp and fill driver dest
     202            0 :         if (useAlignBuffer) {
     203            0 :             assert(alignbuf);
     204            0 :             alignbuf->write(procDest, procSamplesTotal);
     205            0 :             alignbuf->read(driverDest, driverSamplesTotal);
     206              :         }
     207              : 
     208            0 :         if (shouldMeasureInputLag) {
     209            0 :             measureInputLag(procDest, procSamplesTotal);
     210              :         }
     211            0 :     };
     212              : 
     213              : #endif // Q_OS_WASM
     214              : 
     215            0 :     IAudioDriver::Spec activeSpec;
     216            0 :     if (mode == IApplication::RunMode::GuiApp) {
     217            0 :         if (!audioDriverController()->open(requiredSpec, &activeSpec)) {
     218            0 :             return;
     219              :         }
     220              :     } else {
     221            0 :         activeSpec = requiredSpec;
     222              :     }
     223              : 
     224            0 :     AudioEngineConfig conf = configuration()->engineConfig();
     225            0 :     auto sendEngineInit = [this, activeSpec, conf]() {
     226            0 :         m_rpcChannel->send(rpc::make_request(rpc::GLOBAL_CTX_ID, MsgCode::EngineInit, RpcPacker::pack(activeSpec.output, conf)),
     227            0 :                            [this](const Msg&) {
     228            0 :             m_isAudioStarted.set(true);
     229            0 :         });
     230            0 :     };
     231              : 
     232            0 :     if (m_isEngineRunning.val) {
     233            0 :         sendEngineInit();
     234              :     } else {
     235            0 :         m_isEngineRunning.ch.onReceive(this, [sendEngineInit](bool arg) {
     236            0 :             if (arg) {
     237            0 :                 sendEngineInit();
     238              :             }
     239            0 :         });
     240              :     }
     241            0 : }
     242              : 
     243            0 : void StartAudioController::stopAudioProcessing()
     244              : {
     245              : #ifndef Q_OS_WASM
     246            0 :     bool isEngineDeinited = false;
     247              : 
     248              :     //! NOTE Stop waiting for the engine to start: below we process the RPC channel,
     249              :     //! and a still pending EngineRunning would send EngineInit in the middle of the shutdown
     250            0 :     m_isEngineRunning.ch.disconnect(this);
     251              : 
     252            0 :     m_rpcChannel->send(rpc::make_request(rpc::GLOBAL_CTX_ID, MsgCode::EngineDeinit), [this, &isEngineDeinited](const Msg&) {
     253            0 :         isEngineDeinited = true;
     254              : 
     255            0 :         if (m_isAudioStarted.val) {
     256            0 :             m_isAudioStarted.set(false);
     257              :         }
     258            0 :     });
     259              : 
     260            0 :     int attemptsLimit = 100;
     261            0 :     int attempt = 0;
     262              : 
     263              :     do {
     264            0 :         ++attempt;
     265              : 
     266              :         // Ensure that RPC process() is called at least once
     267            0 :         m_rpcChannel->process();
     268              : 
     269            0 :         if (isEngineDeinited) {
     270            0 :             break;
     271              :         }
     272              : 
     273            0 :         std::this_thread::yield();
     274              :         using namespace std::chrono_literals;
     275            0 :         std::this_thread::sleep_for(10ms);
     276            0 :     } while (!isEngineDeinited && attempt <= attemptsLimit);
     277              : 
     278            0 :     if (attempt >= attemptsLimit) {
     279            0 :         LOGE() << "Engine deinit failed because of number of attempts exceeded";
     280              :     }
     281              : 
     282            0 :     audioDriverController()->close();
     283              : 
     284            0 :     if (m_worker && m_worker->isRunning()) {
     285            0 :         m_worker->stop();
     286              :     }
     287              : 
     288              : #endif
     289            0 : }
        

Generated by: LCOV version 2.0-1