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 : }
|