Pencil2D Animation
Download Community News Docs Contribute
  • Overview
  • Articles
  • Code
  •  
  • Class List
  • Class Index
  • Class Hierarchy
  • Class Members
  • File List
Loading...
Searching...
No Matches
  • core_lib
  • src
  • managers
playbackmanager.cpp
1/*
2
3Pencil2D - Traditional Animation Software
4Copyright (C) 2005-2007 Patrick Corrieri & Pascal Naidon
5Copyright (C) 2012-2020 Matthew Chiawen Chang
6
7This program is free software; you can redistribute it and/or
8modify it under the terms of the GNU General Public License
9as published by the Free Software Foundation; version 2 of the License.
10
11This program is distributed in the hope that it will be useful,
12but WITHOUT ANY WARRANTY; without even the implied warranty of
13MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14GNU General Public License for more details.
15
16*/
17
18#include "playbackmanager.h"
19
20#include <QTimer>
21#include <QElapsedTimer>
22#include <QDebug>
23#include <QSettings>
24#include "object.h"
25#include "editor.h"
26#include "layersound.h"
27#include "layermanager.h"
28#include "soundclip.h"
29#include "toolmanager.h"
30
31
32PlaybackManager::PlaybackManager(Editor* editor) : BaseManager(editor, __FUNCTION__)
33{
34}
35
36PlaybackManager::~PlaybackManager()
37{
38 delete mElapsedTimer;
39}
40
41bool PlaybackManager::init()
42{
43 mTimer = new QTimer(this);
44 mTimer->setTimerType(Qt::PreciseTimer);
45
46 mFlipTimer = new QTimer(this);
47 mFlipTimer->setTimerType(Qt::PreciseTimer);
48
49 mScrubTimer = new QTimer(this);
50 mScrubTimer->setTimerType(Qt::PreciseTimer);
51 mSoundclipsToPLay.clear();
52
53 QSettings settings (PENCIL2D, PENCIL2D);
54 mFps = settings.value(SETTING_FPS).toInt();
55 mMsecSoundScrub = settings.value(SETTING_SOUND_SCRUB_MSEC).toInt();
56 if (mMsecSoundScrub == 0) { mMsecSoundScrub = 100; }
57 mSoundScrub = settings.value(SETTING_SOUND_SCRUB_ACTIVE).toBool();
58
59 mElapsedTimer = new QElapsedTimer;
60 connect(mTimer, &QTimer::timeout, this, &PlaybackManager::timerTick);
61 connect(mFlipTimer, &QTimer::timeout, this, &PlaybackManager::flipTimerTick);
62 return true;
63}
64
65Status PlaybackManager::load(Object* o)
66{
67 const ObjectData* data = o->data();
68
69 mIsLooping = data->isLooping();
70 mIsRangedPlayback = data->isRangedPlayback();
71 mMarkInFrame = data->getMarkInFrameNumber();
72 mMarkOutFrame = data->getMarkOutFrameNumber();
73 mFps = data->getFrameRate();
74
75 updateStartFrame();
76 updateEndFrame();
77
78 return Status::OK;
79}
80
81Status PlaybackManager::save(Object* o)
82{
83 ObjectData* data = o->data();
84 data->setLooping(mIsLooping);
85 data->setRangedPlayback(mIsRangedPlayback);
86 data->setMarkInFrameNumber(mMarkInFrame);
87 data->setMarkOutFrameNumber(mMarkOutFrame);
88 data->setFrameRate(mFps);
89 data->setCurrentFrame(editor()->currentFrame());
90 return Status::OK;
91}
92
93bool PlaybackManager::isPlaying()
94{
95 return (mTimer->isActive() || mFlipTimer->isActive());
96}
97
98void PlaybackManager::play()
99{
100 updateStartFrame();
101 updateEndFrame();
102
103 int frame = editor()->currentFrame();
104 if (frame >= mEndFrame || frame < mStartFrame)
105 {
106 editor()->scrubTo(mStartFrame);
107 frame = editor()->currentFrame();
108 }
109
110 mListOfActiveSoundFrames.clear();
111 // Check for any sounds we should start playing part-way through.
112 mCheckForSoundsHalfway = true;
113 playSounds(frame);
114
115 mTimer->setInterval(static_cast<int>(1000.f / mFps));
116 mTimer->start();
117
118 // for error correction, please ref skipFrame()
119 mPlayingFrameCounter = 1;
120 mElapsedTimer->start();
121
122 emit playStateChanged(true);
123}
124
125void PlaybackManager::stop()
126{
127 mTimer->stop();
128 stopSounds();
129 emit playStateChanged(false);
130}
131
132void PlaybackManager::playFlipRoll()
133{
134 if (isPlaying()) { return; }
135
136 int start = editor()->currentFrame();
137 int tmp = start;
138 mFlipList.clear();
139 QSettings settings(PENCIL2D, PENCIL2D);
140 mFlipRollMax = settings.value(SETTING_FLIP_ROLL_DRAWINGS).toInt();
141 for (int i = 0; i < mFlipRollMax; i++)
142 {
143 int prev = editor()->layers()->currentLayer()->getPreviousKeyFramePosition(tmp);
144 if (prev < tmp)
145 {
146 mFlipList.prepend(prev);
147 tmp = prev;
148 }
149 }
150 if (mFlipList.isEmpty()) { return; }
151
152 // run the roll...
153 mFlipRollInterval = settings.value(SETTING_FLIP_ROLL_MSEC).toInt();
154 mFlipList.append(start);
155 mFlipTimer->setInterval(mFlipRollInterval);
156
157 editor()->scrubTo(mFlipList[0]);
158 mFlipTimer->start();
159 emit playStateChanged(true);
160}
161
162void PlaybackManager::playFlipInBetween()
163{
164 if (isPlaying()) { return; }
165
166 LayerManager* layerMgr = editor()->layers();
167 int start = editor()->currentFrame();
168
169 int prev = layerMgr->currentLayer()->getPreviousKeyFramePosition(start);
170 int next = layerMgr->currentLayer()->getNextKeyFramePosition(start);
171
172 if (prev < start && next > start &&
173 layerMgr->currentLayer()->keyExists(prev) &&
174 layerMgr->currentLayer()->keyExists(next))
175 {
176 mFlipList.clear();
177 mFlipList.append(prev);
178 mFlipList.append(prev);
179 mFlipList.append(start);
180 mFlipList.append(next);
181 mFlipList.append(next);
182 mFlipList.append(start);
183 }
184 else
185 {
186 return;
187 }
188 // run the flip in-between...
189 QSettings settings(PENCIL2D, PENCIL2D);
190 mFlipInbetweenInterval = settings.value(SETTING_FLIP_INBETWEEN_MSEC).toInt();
191
192 mFlipTimer->setInterval(mFlipInbetweenInterval);
193 editor()->scrubTo(mFlipList[0]);
194 mFlipTimer->start();
195 emit playStateChanged(true);
196}
197
198void PlaybackManager::playScrub(int frame)
199{
200 if (!mSoundScrub || !mSoundclipsToPLay.isEmpty()) {return; }
201
202 auto layerMan = editor()->layers();
203 for (int i = 0; i < layerMan->count(); i++)
204 {
205 Layer* layer = layerMan->getLayer(i);
206 if (layer->type() == Layer::SOUND && layer->visible())
207 {
208 KeyFrame* key = layer->getKeyFrameWhichCovers(frame);
209 if (key != nullptr)
210 {
211 SoundClip* clip = static_cast<SoundClip*>(key);
212 mSoundclipsToPLay.append(clip);
213 }
214 }
215 }
216
217 if (mSoundclipsToPLay.isEmpty()) { return; }
218
219 mScrubTimer->singleShot(mMsecSoundScrub, this, &PlaybackManager::stopScrubPlayback);
220 for (int i = 0; i < mSoundclipsToPLay.count(); i++)
221 {
222 mSoundclipsToPLay.at(i)->playFromPosition(frame, mFps);
223 }
224}
225
226void PlaybackManager::setFps(int fps)
227{
228 if (mFps != fps)
229 {
230 mFps = fps;
231 QSettings settings (PENCIL2D, PENCIL2D);
232 settings.setValue(SETTING_FPS, fps);
233 emit fpsChanged(mFps);
234
235 // Update key-frame lengths of sound layers,
236 // since the length depends on fps.
237 for (int i = 0; i < object()->getLayerCount(); ++i)
238 {
239 Layer* layer = object()->getLayer(i);
240 if (layer->type() == Layer::SOUND)
241 {
242 auto soundLayer = dynamic_cast<LayerSound *>(layer);
243 soundLayer->updateFrameLengths(mFps);
244 }
245 }
246 }
247}
248
249void PlaybackManager::playSounds(int frame)
250{
251 // If sound is turned off, don't play anything.
252 if (!mIsPlaySound)
253 {
254 return;
255 }
256
257 std::vector< LayerSound* > kSoundLayers;
258 for (int i = 0; i < object()->getLayerCount(); ++i)
259 {
260 Layer* layer = object()->getLayer(i);
261 if (layer->type() == Layer::SOUND)
262 {
263 kSoundLayers.push_back(static_cast<LayerSound*>(layer));
264 }
265 }
266
267 for (LayerSound* layer : kSoundLayers)
268 {
269 KeyFrame* key = layer->getLastKeyFrameAtPosition(frame);
270
271 if (!layer->visible())
272 {
273 continue;
274 }
275
276 if (key != nullptr)
277 {
278 // add keyframe position to list
279 if (key->pos() + key->length() >= frame)
280 {
281 if (!mListOfActiveSoundFrames.contains(key->pos()))
282 {
283 mListOfActiveSoundFrames.append(key->pos());
284 }
285 }
286 }
287
288 if (mCheckForSoundsHalfway)
289 {
290 // Check for sounds which we should start playing from part-way through.
291 for (int i = 0; i < mListOfActiveSoundFrames.count(); i++)
292 {
293 int listPosition = mListOfActiveSoundFrames.at(i);
294 if (layer->keyExistsWhichCovers(listPosition))
295 {
296 key = layer->getKeyFrameWhichCovers(listPosition);
297 SoundClip* clip = static_cast<SoundClip*>(key);
298 clip->playFromPosition(frame, mFps);
299 }
300 }
301 }
302 else if (layer->keyExists(frame))
303 {
304 key = layer->getKeyFrameAt(frame);
305 SoundClip* clip = static_cast<SoundClip*>(key);
306
307 clip->play();
308
309 // save the position of our active sound frame
310 mActiveSoundFrame = frame;
311 }
312
313 if (frame >= mEndFrame)
314 {
315 if (layer->keyExists(mActiveSoundFrame))
316 {
317 key = layer->getKeyFrameWhichCovers(mActiveSoundFrame);
318 SoundClip* clip = static_cast<SoundClip*>(key);
319 clip->stop();
320
321 // make sure list is cleared on end
322 if (!mListOfActiveSoundFrames.isEmpty())
323 mListOfActiveSoundFrames.clear();
324 }
325 }
326 }
327
328 // Set flag to false, since this check should only be done when
329 // starting play-back, or when looping.
330 mCheckForSoundsHalfway = false;
331}
332
338bool PlaybackManager::skipFrame()
339{
340 // uncomment these debug outputs to see what happens
341 //float expectedTime = (mPlayingFrameCounter) * (1000.f / mFps);
342 //qDebug("Expected: %.2f ms", expectedTime);
343 //qDebug("Actual: %d ms", mElapsedTimer->elapsed());
344
345 int t = qRound((mPlayingFrameCounter - 1) * (1000.f / mFps));
346 if (mElapsedTimer->elapsed() < t)
347 {
348 qDebug() << "skip";
349 return true;
350 }
351
352 ++mPlayingFrameCounter;
353 return false;
354}
355
356void PlaybackManager::stopSounds()
357{
358 std::vector<LayerSound*> kSoundLayers;
359
360 for (int i = 0; i < object()->getLayerCount(); ++i)
361 {
362 Layer* layer = object()->getLayer(i);
363 if (layer->type() == Layer::SOUND)
364 {
365 kSoundLayers.push_back(static_cast<LayerSound*>(layer));
366 }
367 }
368
369 for (LayerSound* layer : kSoundLayers)
370 {
371 layer->foreachKeyFrame([](KeyFrame* key)
372 {
373 SoundClip* clip = static_cast<SoundClip*>(key);
374 clip->stop();
375 });
376 }
377}
378
379void PlaybackManager::stopScrubPlayback()
380{
381 for (int i = 0; i < mSoundclipsToPLay.count(); i++)
382 {
383 mSoundclipsToPLay.at(i)->pause();
384 }
385 mSoundclipsToPLay.clear();
386}
387
388void PlaybackManager::timerTick()
389{
390 int currentFrame = editor()->currentFrame();
391
392 // reach the end
393 if (currentFrame >= mEndFrame)
394 {
395 if (mIsLooping)
396 {
397 editor()->scrubTo(mStartFrame);
398 mCheckForSoundsHalfway = true;
399 }
400 else
401 {
402 stop();
403 }
404 return;
405 }
406
407 if (skipFrame())
408 return;
409
410 // keep going
411 editor()->scrubForward();
412
413 int newFrame = editor()->currentFrame();
414 playSounds(newFrame);
415}
416
417void PlaybackManager::flipTimerTick()
418{
419 if (mFlipList.count() < 2 || editor()->currentFrame() != mFlipList[0])
420 {
421 mFlipTimer->stop();
422 editor()->scrubTo(mFlipList.last());
423 emit playStateChanged(false);
424 }
425 else
426 {
427 editor()->scrubTo(mFlipList[1]);
428 mFlipList.removeFirst();
429 }
430}
431
432void PlaybackManager::setLooping(bool isLoop)
433{
434 if (mIsLooping != isLoop)
435 {
436 mIsLooping = isLoop;
437 emit loopStateChanged(mIsLooping);
438 }
439}
440
441void PlaybackManager::enableRangedPlayback(bool b)
442{
443 if (mIsRangedPlayback != b)
444 {
445 mIsRangedPlayback = b;
446
447 updateStartFrame();
448 updateEndFrame();
449
450 emit rangedPlaybackStateChanged(mIsRangedPlayback);
451 }
452}
453
454void PlaybackManager::setRangedStartFrame(int frame)
455{
456 mMarkInFrame = frame;
457 updateStartFrame();
458}
459
460void PlaybackManager::setRangedEndFrame(int frame)
461{
462 mMarkOutFrame = frame;
463 updateEndFrame();
464}
465
466void PlaybackManager::updateStartFrame()
467{
468 mStartFrame = (mIsRangedPlayback) ? mMarkInFrame : 1;
469}
470
471void PlaybackManager::updateEndFrame()
472{
473 int projectLength = editor()->layers()->animationLength();
474 mEndFrame = (mIsRangedPlayback) ? mMarkOutFrame : projectLength;
475}
476
477void PlaybackManager::enableSound(bool b)
478{
479 mIsPlaySound = b;
480
481 if (!mIsPlaySound)
482 {
483 stopSounds();
484
485 // If, during playback, the sound is turned on again,
486 // check for sounds partway through.
487 mCheckForSoundsHalfway = true;
488 }
489}
BaseManager
Definition: basemanager.h:29
Editor
Definition: editor.h:71
KeyFrame
Definition: keyframe.h:30
Layer
Definition: layer.h:33
LayerManager
Definition: layermanager.h:31
LayerManager::animationLength
int animationLength(bool includeSounds=true)
Get the length of current project.
Definition: layermanager.cpp:369
LayerSound
Definition: layersound.h:26
ObjectData
Definition: objectdata.h:27
Object
Definition: object.h:42
PlaybackManager::skipFrame
bool skipFrame()
PlaybackManager::skipFrame() Small errors accumulate while playing animation If the error is greater ...
Definition: playbackmanager.cpp:338
SoundClip
Definition: soundclip.h:27
Status
Definition: pencilerror.h:40
QElapsedTimer
QElapsedTimer::elapsed
qint64 elapsed() const const
QElapsedTimer::start
void start()
QObject::connect
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
QSettings
Qt::PreciseTimer
PreciseTimer
QTimer
QTimer::setInterval
void setInterval(int msec)
QTimer::isActive
bool isActive() const const
QTimer::singleShot
singleShot
QTimer::start
void start(int msec)
QTimer::stop
void stop()
QTimer::timeout
void timeout()
QTimer::setTimerType
void setTimerType(Qt::TimerType atype)
QVector::append
void append(const T &value)
QVector::at
const T & at(int i) const const
QVector::clear
void clear()
QVector::contains
bool contains(const T &value) const const
QVector::count
int count(const T &value) const const
QVector::isEmpty
bool isEmpty() const const
QVector::last
T & last()
QVector::prepend
void prepend(T &&value)
QVector::removeFirst
void removeFirst()
Generated on Thu May 8 2025 04:47:53 for Pencil2D by doxygen 1.9.6 based on revision 4513250b1d5b1a3676ec0e67b06b7a885ceaae39