LCOV - code coverage report
Current view: top level - muse/framework/audio/engine/internal - mixer.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 0.0 % 165 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              : #include "mixer.h"
      23              : 
      24              : #include <sstream>
      25              : 
      26              : #include "audio/common/audiosanitizer.h"
      27              : #include "audio/common/audioerrors.h"
      28              : 
      29              : #include "nodes/signalnode.h"
      30              : 
      31              : #include "log.h"
      32              : 
      33              : using namespace muse;
      34              : using namespace muse::async;
      35              : using namespace muse::audio;
      36              : using namespace muse::audio::engine;
      37              : 
      38              : constexpr size_t MIN_TRACK_COUNT_FOR_MULTITHREADING = 2;
      39              : 
      40            0 : static bool isChainSilent(const TrackChainPtr& chain)
      41              : {
      42            0 :     if (auto signal = chain->signal()) {
      43            0 :         return signal->isSilent();
      44            0 :     }
      45            0 :     return false;
      46              : }
      47              : 
      48            0 : Mixer::~Mixer()
      49              : {
      50              :     ONLY_AUDIO_MAIN_OR_ENGINE_THREAD;
      51            0 : }
      52              : 
      53            0 : void Mixer::init()
      54              : {
      55              :     ONLY_AUDIO_ENGINE_THREAD;
      56            0 : }
      57              : 
      58            0 : Ret Mixer::addTrack(TrackChainPtr trackChain, const AuxSendsParams& auxSends)
      59              : {
      60            0 :     const size_t outBufferSize = m_outputSpec.samplesPerChannel * m_outputSpec.audioChannelCount;
      61              : 
      62            0 :     TrackData trackData;
      63            0 :     trackData.trackId = trackChain->trackId();
      64            0 :     trackData.chain = trackChain;
      65            0 :     trackData.buffer.resize(outBufferSize);
      66              : 
      67            0 :     m_tracks.emplace_back(std::move(trackData));
      68            0 :     m_trackTasks.reserve(m_tracks.size());
      69              : 
      70            0 :     setAuxSends(trackData.trackId, auxSends);
      71              : 
      72            0 :     return make_ok();
      73            0 : }
      74              : 
      75            0 : Ret Mixer::addAuxTrack(TrackChainPtr trackChain)
      76              : {
      77              :     ONLY_AUDIO_ENGINE_THREAD;
      78            0 :     const size_t outBufferSize = m_outputSpec.samplesPerChannel * m_outputSpec.audioChannelCount;
      79              : 
      80            0 :     TrackData trackData;
      81            0 :     trackData.trackId = trackChain->trackId();
      82            0 :     trackData.chain = trackChain;
      83            0 :     trackData.buffer.resize(outBufferSize);
      84              : 
      85            0 :     m_auxTracks.emplace_back(std::move(trackData));
      86              : 
      87            0 :     return make_ok();
      88            0 : }
      89              : 
      90            0 : Ret Mixer::removeTrack(const TrackId trackId)
      91              : {
      92              :     ONLY_AUDIO_ENGINE_THREAD;
      93              : 
      94            0 :     bool removed = muse::remove_if(m_tracks, [trackId](const TrackData& track) {
      95            0 :         return track.trackId == trackId;
      96              :     });
      97              : 
      98            0 :     if (removed) {
      99            0 :         m_auxSends.erase(trackId);
     100              :     }
     101              : 
     102            0 :     if (!removed) {
     103            0 :         removed = muse::remove_if(m_auxTracks, [trackId](const TrackData& track) {
     104            0 :             return track.trackId == trackId;
     105              :         });
     106              :     }
     107              : 
     108            0 :     return removed ? make_ret(Ret::Code::Ok) : make_ret(Err::InvalidTrackId);
     109              : }
     110              : 
     111            0 : void Mixer::setAuxSends(const TrackId trackId, const AuxSendsParams& auxSends)
     112              : {
     113              :     ONLY_AUDIO_ENGINE_THREAD;
     114            0 :     m_auxSends[trackId] = auxSends;
     115            0 : }
     116              : 
     117            0 : void Mixer::onOutputSpecChanged(const OutputSpec& spec)
     118              : {
     119              :     ONLY_AUDIO_ENGINE_THREAD;
     120            0 :     for (auto& t : m_tracks) {
     121            0 :         t.chain->setOutputSpec(spec);
     122              :     }
     123              : 
     124            0 :     for (auto& t : m_auxTracks) {
     125            0 :         t.chain->setOutputSpec(spec);
     126              :     }
     127            0 : }
     128              : 
     129            0 : void Mixer::onModeChanged(const ProcessMode mode)
     130              : {
     131              :     ONLY_AUDIO_ENGINE_THREAD;
     132              : 
     133            0 :     for (auto& t : m_tracks) {
     134            0 :         t.chain->setMode(mode);
     135              :     }
     136              : 
     137            0 :     for (auto& t : m_auxTracks) {
     138            0 :         t.chain->setMode(mode);
     139              :     }
     140            0 : }
     141              : 
     142            0 : void Mixer::process(float* outBuffer, samples_t samplesPerChannel)
     143              : {
     144              :     ONLY_AUDIO_PROC_THREAD;
     145              : 
     146            0 :     if (!m_enabled) {
     147            0 :         return;
     148              :     }
     149              : 
     150            0 :     const size_t outBufferSize = samplesPerChannel * m_outputSpec.audioChannelCount;
     151              : 
     152            0 :     processTrackChannels(outBufferSize, samplesPerChannel);
     153              : 
     154            0 :     prepareAuxBuffers(outBufferSize);
     155              : 
     156            0 :     for (auto& t : m_tracks) {
     157            0 :         if (!t.processed) {
     158            0 :             continue;
     159              :         }
     160              : 
     161              :         //! NOTE If the signal is silent, do not write to the output buffer
     162              :         //! or the aux buffers
     163            0 :         if (isChainSilent(t.chain)) {
     164            0 :             continue;
     165              :         }
     166              : 
     167            0 :         mixOutputFromChannel(outBuffer, t.buffer.data(), outBufferSize);
     168            0 :         writeTrackToAuxBuffers(t.buffer.data(), outBufferSize, m_auxSends[t.trackId]);
     169              :     }
     170              : 
     171            0 :     processAuxChannels(outBuffer, samplesPerChannel);
     172              : }
     173              : 
     174            0 : void Mixer::processTrackChannels(size_t outBufferSize,
     175              :                                  size_t samplesPerChannel)
     176              : {
     177            0 :     auto processChannel = [outBufferSize, samplesPerChannel](TrackData& trackData) {
     178            0 :         IF_ASSERT_FAILED(trackData.chain) {
     179            0 :             return;
     180              :         }
     181              : 
     182            0 :         if (trackData.buffer.size() < outBufferSize) {
     183            0 :             trackData.buffer.resize(outBufferSize);
     184              :         }
     185              : 
     186            0 :         std::fill(trackData.buffer.begin(), trackData.buffer.begin() + outBufferSize, 0.f);
     187            0 :         trackData.chain->process(trackData.buffer.data(), samplesPerChannel);
     188            0 :         trackData.processed = true;
     189            0 :     };
     190              : 
     191            0 :     bool filterTracks = (m_mode == ProcessMode::Idle) && !m_tracksToProcessWhenIdle.empty();
     192              : 
     193              : #ifdef MUSE_THREADS_SUPPORT
     194            0 :     if (useMultithreading()) {
     195            0 :         m_trackTasks.clear();
     196            0 :         for (auto& t : m_tracks) {
     197            0 :             t.processed = false;
     198              : 
     199            0 :             if (filterTracks && !muse::contains(m_tracksToProcessWhenIdle, t.trackId)) {
     200            0 :                 continue;
     201              :             }
     202              : 
     203            0 :             m_trackTasks.emplace_back([tPtr = &t, processChannel] {
     204            0 :                 processChannel(*tPtr);
     205            0 :             });
     206              :         }
     207            0 :         audioTaskScheduler()->submitRealtimeTasksAndWait(m_trackTasks);
     208              :     } else
     209              : #endif
     210              :     {
     211            0 :         for (auto& t : m_tracks) {
     212            0 :             t.processed = false;
     213              : 
     214            0 :             if (filterTracks && !muse::contains(m_tracksToProcessWhenIdle, t.trackId)) {
     215            0 :                 continue;
     216              :             }
     217              : 
     218            0 :             processChannel(t);
     219              :         }
     220              :     }
     221            0 : }
     222              : 
     223            0 : void Mixer::setNonMutedTrackCount(size_t count)
     224              : {
     225            0 :     m_nonMutedTrackCount = count;
     226            0 : }
     227              : 
     228            0 : bool Mixer::useMultithreading() const
     229              : {
     230              : #ifdef MUSE_THREADS_SUPPORT
     231              :     //! NOTE Offline the render runs on the engine thread while it holds its async queue lock,
     232              :     //! and a pool worker needs that same lock to register a channel port, which deadlocks.
     233            0 :     if (m_mode == ProcessMode::PlayingOffline) {
     234            0 :         return false;
     235              :     }
     236              : 
     237            0 :     if (m_nonMutedTrackCount < MIN_TRACK_COUNT_FOR_MULTITHREADING) {
     238            0 :         return false;
     239              :     }
     240              : 
     241            0 :     if (m_mode == ProcessMode::Idle) {
     242            0 :         if (m_tracksToProcessWhenIdle.size() < MIN_TRACK_COUNT_FOR_MULTITHREADING) {
     243            0 :             return false;
     244              :         }
     245              :     }
     246              : 
     247            0 :     return true;
     248              : #else
     249              :     return false;
     250              : #endif
     251              : }
     252              : 
     253            0 : void Mixer::setTracksToProcessWhenIdle(const std::unordered_set<TrackId>& trackIds)
     254              : {
     255              :     ONLY_AUDIO_ENGINE_THREAD;
     256              : 
     257            0 :     m_tracksToProcessWhenIdle = trackIds;
     258            0 : }
     259              : 
     260            0 : void Mixer::mixOutputFromChannel(float* outBuffer, const float* inBuffer, size_t bufferSize) const
     261              : {
     262            0 :     IF_ASSERT_FAILED(outBuffer && inBuffer) {
     263            0 :         return;
     264              :     }
     265              : 
     266            0 :     for (size_t i = 0; i < bufferSize; ++i) {
     267            0 :         outBuffer[i] += inBuffer[i];
     268              :     }
     269              : }
     270              : 
     271            0 : void Mixer::prepareAuxBuffers(size_t outBufferSize)
     272              : {
     273            0 :     for (auto& aux : m_auxTracks) {
     274            0 :         aux.processed = false;
     275            0 :         if (!aux.chain->fxChain()) {
     276            0 :             continue;
     277              :         }
     278            0 :         aux.buffer.resize(outBufferSize);
     279            0 :         std::fill(aux.buffer.begin(), aux.buffer.begin() + outBufferSize, 0.f);
     280              :     }
     281            0 : }
     282              : 
     283            0 : void Mixer::writeTrackToAuxBuffers(const float* trackBuffer, size_t outBufferSize, const AuxSendsParams& auxSends)
     284              : {
     285            0 :     for (aux_channel_idx_t auxIdx = 0; auxIdx < auxSends.size(); ++auxIdx) {
     286            0 :         if (auxIdx >= m_auxTracks.size()) {
     287            0 :             break;
     288              :         }
     289              : 
     290            0 :         TrackData& aux = m_auxTracks.at(auxIdx);
     291            0 :         if (!aux.chain->fxChain()) {
     292            0 :             continue;
     293              :         }
     294              : 
     295            0 :         const AuxSendParams& auxSend = auxSends.at(auxIdx);
     296            0 :         if (!auxSend.active || muse::is_zero(auxSend.signalAmount)) {
     297            0 :             continue;
     298              :         }
     299              : 
     300            0 :         float* auxBuffer = aux.buffer.data();
     301            0 :         float signalAmount = auxSend.signalAmount;
     302              : 
     303            0 :         for (size_t i = 0; i < outBufferSize; ++i) {
     304            0 :             auxBuffer[i] += trackBuffer[i] * signalAmount;
     305              :         }
     306              : 
     307            0 :         aux.processed = true;
     308              :     }
     309            0 : }
     310              : 
     311            0 : void Mixer::processAuxChannels(float* buffer, samples_t samplesPerChannel)
     312              : {
     313            0 :     const size_t outBufferSize = samplesPerChannel * m_outputSpec.audioChannelCount;
     314              : 
     315            0 :     for (TrackData& aux : m_auxTracks) {
     316              :         //! NOTE Process when the aux received a signal this block (aux.processed) and/or if
     317              :         //! it's not yet silent (e.g. reverb is still ringing out)
     318            0 :         if (!aux.processed && isChainSilent(aux.chain)) {
     319            0 :             continue;
     320              :         }
     321              : 
     322            0 :         float* auxBuffer = aux.buffer.data();
     323            0 :         aux.chain->process(auxBuffer, samplesPerChannel);
     324              : 
     325              :         //! NOTE If the signal is silent, do not write to the output buffer
     326            0 :         if (!isChainSilent(aux.chain)) {
     327            0 :             mixOutputFromChannel(buffer, auxBuffer, outBufferSize);
     328              :         }
     329              :     }
     330            0 : }
     331              : 
     332            0 : std::string Mixer::dump() const
     333              : {
     334            0 :     std::stringstream ss;
     335            0 :     ss << "\n";
     336            0 :     ss << name() << ":";
     337              : 
     338            0 :     int indent = 2;
     339              : 
     340            0 :     ss << "\n";
     341            0 :     ss << std::string(indent, ' ') << "tracks: " << m_tracks.size();
     342            0 :     for (const auto& track : m_tracks) {
     343            0 :         ss << "\n";
     344            0 :         ss << std::string(indent, ' ') << "<--[" << track.trackId << "] " << track.chain->dump();
     345              :     }
     346              : 
     347            0 :     ss << "\n";
     348            0 :     ss << std::string(indent, ' ') << "auxs: " << m_auxTracks.size();
     349            0 :     for (const auto& aux : m_auxTracks) {
     350            0 :         ss << "\n";
     351            0 :         ss << std::string(indent, ' ') << "<--[" << aux.trackId << "] " << aux.chain->dump();
     352              :     }
     353              : 
     354            0 :     return ss.str();
     355            0 : }
        

Generated by: LCOV version 2.0-1