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