kwave  18.07.70
PlaybackController.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  PlaybackController.cpp - Interface for generic playback control
3  -------------------
4  begin : Nov 15 2000
5  copyright : (C) 2000 by Thomas Eschenbacher
6  email : Thomas Eschenbacher <Thomas.Eschenbacher@gmx.de>
7  ***************************************************************************/
8 
9 /***************************************************************************
10  * *
11  * This program is free software; you can redistribute it and/or modify *
12  * it under the terms of the GNU General Public License as published by *
13  * the Free Software Foundation; either version 2 of the License, or *
14  * (at your option) any later version. *
15  * *
16  ***************************************************************************/
17 
18 #include "config.h"
19 
20 #include <math.h>
21 
22 #include <QMutexLocker>
23 
24 #include "libkwave/MessageBox.h"
25 #include "libkwave/MixerMatrix.h"
31 #include "libkwave/Sample.h"
32 #include "libkwave/SampleArray.h"
33 #include "libkwave/SampleReader.h"
34 #include "libkwave/SignalManager.h"
35 #include "libkwave/String.h"
36 #include "libkwave/Utils.h"
37 
39 #define SCREEN_REFRESHES_PER_SECOND 10
40 
41 //***************************************************************************
43  Kwave::SignalManager &signal_manager
44 )
45  :m_signal_manager(signal_manager), m_thread(this, QVariant()),
46  m_device(Q_NULLPTR), m_lock_device(), m_playback_params(),
47  m_lock_playback(), m_should_seek(false), m_seek_pos(0),
48  m_track_selection_changed(false),
49  m_reload_mode(false), m_loop_mode(false), m_paused(false),
50  m_playing(false), m_playback_position(0), m_playback_start(0),
51  m_playback_end(0), m_old_first(0), m_old_last(0),
52  m_playback_factories()
53 {
54 
55  connect(this, SIGNAL(sigDevicePlaybackDone()),
56  this, SLOT(playbackDone()));
57  connect(this, SIGNAL(sigDevicePlaybackDone()),
58  this, SLOT(closeDevice()),
59  Qt::QueuedConnection);
60  connect(&m_signal_manager, SIGNAL(sigTrackSelectionChanged(bool)),
61  this, SLOT(trackSelectionChanged()));
62 
63 }
64 
65 //***************************************************************************
67 {
68  playbackStop();
69 }
70 
71 //***************************************************************************
73 {
74  // leave the reload mode in any case
75  m_reload_mode = false;
76 
77  if (m_playing) {
78  // first stop playback
80  emit sigPlaybackStopped();
81  }
82 
83  // (re)start from beginning without loop mode
86 
87  m_loop_mode = false;
88  m_paused = false;
89  m_playing = true;
90  emit sigPlaybackStarted();
91 
93 }
94 
95 //***************************************************************************
97 {
98  // leave the reload mode in any case
99  m_reload_mode = false;
100 
101  if (m_playing) {
102  // first stop playback
104  m_playing = false;
105  emit sigPlaybackStopped();
106  }
107 
108  // (re)start from beginning without loop mode
111 
112  m_loop_mode = true;
113  m_paused = false;
115 
116  m_playing = true;
117  emit sigPlaybackStarted();
118 }
119 
120 //***************************************************************************
122 {
123  // leave the reload mode in any case
124  m_reload_mode = false;
125 
126  if (!m_playing) return; // no effect if not playing
127 
128  m_paused = true;
129 
130  // stop playback for now and set the paused flag
132 }
133 
134 //***************************************************************************
136 {
137  // leave the reload mode in any case
138  m_reload_mode = false;
139 
140  // if not paused, do the same as start
141  if (!m_paused) {
142  playbackStart();
143  return;
144  }
145 
146  // else reset the paused flag and start from current position
148 
149  m_paused = false;
150  m_playing = true;
151 
152  emit sigPlaybackStarted();
153 }
154 
155 //***************************************************************************
157 {
158  // leave the reload mode in any case
159  m_reload_mode = false;
160 
161  // stopped in pause state
162  if (m_paused) {
163  m_playing = false;
164  m_paused = false;
165  emit sigPlaybackStopped();
166  }
167  if (!m_playing) return; // already stopped
169 }
170 
171 //***************************************************************************
173 {
174  if (pos < m_playback_start) pos = m_playback_start;
175  if (pos > m_playback_end) pos = m_playback_end;
176 
177  {
178  QMutexLocker lock(&m_lock_playback);
179  qDebug("seekTo(%llu)", pos);
180  m_seek_pos = pos;
181  m_should_seek = true;
182  }
183 
184  if (m_paused) {
185  // if playback is paused, we want an update of the playback
186  // position anyway. as this will not come from the device layer,
187  // fake an update right here
188  updatePlaybackPos(pos);
189  seekDone(pos);
190  }
191 }
192 
193 //***************************************************************************
195 {
196  emit sigSeekDone(pos);
197 }
198 
199 //***************************************************************************
201 {
202  m_playback_position = pos;
203  emit sigPlaybackPos(m_playback_position); // TODO => per TIMER !!!
204 }
205 
206 //***************************************************************************
208 {
209  if (m_reload_mode) {
210  // if we were in the reload mode, reset the
211  // paused flag and start again from current position
213  m_paused = false;
214  m_playing = true;
215 
216  // leave the "reload" mode
217  m_reload_mode = false;
218  return;
219  }
220 
221  m_playing = false;
222  if (m_paused)
223  emit sigPlaybackPaused();
224  else {
226  emit sigPlaybackStopped();
227  }
228 
229  m_old_first = 0;
230  m_old_last = 0;
231 }
232 
233 //***************************************************************************
235 {
236  if (!m_playing || m_paused) return; // no effect if not playing or paused
237 
238  // enter the "reload" mode
239  m_reload_mode = true;
240 
241  // stop playback for now and set the paused flag
242  m_paused = true;
244 }
245 
246 //***************************************************************************
248 {
249  m_playback_start = 0;
251  m_loop_mode = false;
252  m_playing = false;
253  m_paused = false;
254  m_reload_mode = false;
255 
256  emit sigPlaybackPos(0);
257  emit sigPlaybackStopped();
258 }
259 
260 //***************************************************************************
262 {
263  return m_loop_mode;
264 }
265 
266 //***************************************************************************
268 {
269  return m_playing;
270 }
271 
272 //***************************************************************************
274 {
275  return m_paused;
276 }
277 
278 //***************************************************************************
280 {
281  m_playback_start = pos;
282 }
283 
284 //***************************************************************************
286 {
287  m_playback_end = pos;
288 }
289 
290 //***************************************************************************
292 {
293  return m_playback_start;
294 }
295 
296 //***************************************************************************
298 {
299  return m_playback_end;
300 }
301 
302 //***************************************************************************
304 {
305  return m_playback_position;
306 }
307 
308 //***************************************************************************
310 {
311  // set the real sample rate for playback from the signal itself
313 
314  QMutexLocker lock_for_delete(&m_lock_device);
315 
316  // remove the old device if still one exists
317  if (m_device) {
318  qWarning("PlaybackController::startDevicePlayBack(): "
319  "removing stale instance");
320  delete m_device;
321  m_device = Q_NULLPTR;
322  }
323 
324  // open the device and abort if not possible
325 
326 // qDebug("PlaybackController::startDevicePlayBack(), device='%s'",
327 // DBG(m_playback_params.device));
329  if (!m_device) {
330  // simulate a "playback done" on errors
331  emit sigDevicePlaybackDone();
332  return;
333  }
334 
337 
338  if (m_paused) {
339  // continue after pause
340  if ((m_old_first != first) || (m_old_last != last)) {
341  // selection has changed
342  if (first != last) {
343  // something selected -> set new range
344  setStartPos(first);
345  setEndPos(last);
346 
347  sample_index_t pos = currentPos();
348  if ((pos < first) || (pos > last)) {
349  // completely new area selected, or the right margin
350  // has been moved before the current playback pointer
351  // -> play from start of new selection
352  updatePlaybackPos(first);
353  }
354  } else {
355  // nothing selected -> select all and move to position
356  setStartPos(first);
358  }
359  }
360  } else {
361  // determine first and last sample if not in paused mode"
362  if (first == last) {
363  // nothing selected -> play from cursor position
364  setStartPos(first);
366  } else {
367  // play only in selection
368  setStartPos(first);
369  setEndPos(last);
370  }
371  updatePlaybackPos(first);
372  }
373 
374  m_old_first = first;
375  m_old_last = last;
376 
377  m_thread.start();
378 }
379 
380 //***************************************************************************
382 {
383  m_thread.cancel();
384  if (!m_thread.isRunning()) {
385  qDebug("PlaybackController::stopDevicePlayBack() - not running");
386  emit sigDevicePlaybackDone();
387  }
388  closeDevice();
389 }
390 
391 //***************************************************************************
393 {
394  QMutexLocker lock(&m_lock_playback);
396 }
397 
398 //***************************************************************************
399 void Kwave::PlaybackController::run_wrapper(const QVariant &params)
400 {
401  Q_UNUSED(params);
402 
403  Kwave::MixerMatrix *mixer = Q_NULLPTR;
406  unsigned int out_channels = m_playback_params.channels;
407 
408  QList<unsigned int> all_tracks = m_signal_manager.allTracks();
409  unsigned int tracks = all_tracks.count();
410  QList<unsigned int> audible_tracks = m_signal_manager.selectedTracks();
411  unsigned int audible_count = audible_tracks.count();
412 
413  // get the list of selected channels
414  if (!tracks || !m_device) {
415  // not even one selected track or no (open) device
416  qDebug("PlaybackController::run(): no audible track(s) !");
417  emit sigDevicePlaybackDone();
418  return;
419  }
420 
421  // set up a set of sample reader (streams)
424  m_signal_manager, all_tracks, first, last);
425 
426  // create a new translation matrix for mixing up/down to the desired
427  // number of output channels
429 
430  // loop until process is stopped
431  // or run once if not in loop mode
432  Kwave::SampleArray in_samples(tracks);
433  Kwave::SampleArray out_samples(out_channels);
435  updatePlaybackPos(pos);
436 
437  // counter for refresh of the playback position
438  unsigned int pos_countdown = 0;
439 
440  do {
441 
442  // if current position is after start -> skip the passed
443  // samples (this happens when resuming after a pause)
444  if (pos > first) input.skip(pos - first);
445 
446  while ((pos++ <= last) && !m_thread.shouldStop()) {
447  unsigned int x;
448  unsigned int y;
449  bool seek_again = false;
450  bool seek_done = false;
451 
452  {
453  QMutexLocker _lock(&m_lock_playback);
454 
455  // check for track selection change (need for new mixer)
457  if (mixer) delete mixer;
458  mixer = Q_NULLPTR;
460  }
461 
462  if (!mixer) {
463  audible_tracks = m_signal_manager.selectedTracks();
464  audible_count = audible_tracks.count();
465  mixer = new Kwave::MixerMatrix(audible_count, out_channels);
466  Q_ASSERT(mixer);
467  if (!mixer) break;
468  seek_again = true; // re-synchronize all reader positions
469  }
470 
471  // check for seek requests
472  if (m_should_seek && (m_seek_pos != pos)) {
473  if (m_seek_pos < first) m_seek_pos = first;
474  if (m_seek_pos > last) { pos = last; break; }
475  pos = m_seek_pos;
476  m_should_seek = false;
477  seek_again = true;
478  seek_done = true;
479  }
480  }
481 
482  if (seek_again) input.seek(pos);
483  if (seek_done) seekDone(pos);;
484 
485  // fill input buffer with samples
486  for (x = 0; x < audible_count; ++x) {
487  in_samples[x] = 0;
488  Kwave::SampleReader *stream = input[audible_tracks[x]];
489  Q_ASSERT(stream);
490  if (!stream) continue;
491 
492  if (!stream->eof()) (*stream) >> in_samples[x];
493  }
494 
495  // multiply matrix with input to get output
496  const Kwave::SampleArray &in = in_samples;
497  for (y = 0; y < out_channels; ++y) {
498  double sum = 0;
499  for (x = 0; x < audible_count; ++x) {
500  sum += static_cast<double>(in[x]) * (*mixer)[x][y];
501  }
502  out_samples[y] = static_cast<sample_t>(sum);
503  }
504 
505  // write samples to the playback device
506  int result = -1;
507  {
508  QMutexLocker lock(&m_lock_device);
509  if (m_device)
510  result = m_device->write(out_samples);
511  }
512  if (result) {
513  m_thread.cancel();
514  pos = last;
515  }
516 
517  // update the playback position if timer elapsed
518  if (!pos_countdown) {
519  pos_countdown = Kwave::toUint(ceil(
521  updatePlaybackPos(pos);
522  } else {
523  --pos_countdown;
524  }
525  }
526 
527  // maybe we loop. in this case the playback starts
528  // again from the left marker
529  if (m_loop_mode && !m_thread.shouldStop()) {
530  input.reset();
531  pos = startPos();
532  }
533 
534  } while (m_loop_mode && !m_thread.shouldStop());
535 
536  // playback is done
537  emit sigDevicePlaybackDone();
538 // qDebug("PlaybackController::run() done.");
539 }
540 
541 //***************************************************************************
543 {
544  Kwave::PlayBackDevice *dev = Q_NULLPTR;
545  if (m_device) {
546  // NOTE: we could get a recursion here if we delete with the lock
547  // held, if the device calls processEvents during shutdown
548  QMutexLocker lock_for_delete(&m_lock_device);
549  dev = m_device;
550  m_device = Q_NULLPTR;
551  }
552  delete dev;
553 }
554 
555 //***************************************************************************
557 {
558  QList<Kwave::playback_method_t> all_methods;
559 
560  // create a list of all supported playback methods
562  QList<Kwave::playback_method_t> methods = f->supportedMethods();
563 
564  // return immediately on a direct match
565  if (methods.contains(method)) return;
566 
567  // otherwise accumulate all found methods
568  foreach (Kwave::playback_method_t m, methods)
569  if (!all_methods.contains(m))
570  all_methods.append(m);
571  }
572 
573  // no direct match found: take the best match (lowest number)
575  foreach (Kwave::playback_method_t m, all_methods) {
576  if (m == Kwave::PLAYBACK_NONE) continue; // not a valid selection
577  if (m < best) best = m;
578  }
579 
581  qDebug("playback method '%s' (%d) not supported "
582  "-> falling back to '%s' (%d)",
583  DBG(map.name(map.findFromData(method))), static_cast<int>(method),
584  DBG(map.name(map.findFromData(best))), static_cast<int>(best)
585  );
586 
587  method = best;
588 }
589 
590 //***************************************************************************
593 {
594  // locate the corresponding playback device factory (plugin)
595  Kwave::PlaybackDeviceFactory *factory = Q_NULLPTR;
597  Q_ASSERT(f);
598  if (f && f->supportedMethods().contains(method)) {
599  factory = f;
600  break;
601  }
602  }
603  if (!factory) return Q_NULLPTR;
604 
605  // create a new device instance, using the given method
606  return factory->createDevice(method);
607 }
608 
609 //***************************************************************************
611  int tracks,
612  const Kwave::PlayBackParam *playback_params)
613 {
614  // take playback parameters if given,
615  // otherwise fall back to current defaults
616  Kwave::PlayBackParam params = (playback_params) ?
617  *playback_params : m_playback_params;
618 
619  // if no playback parameters specified or no method selected:
620  // -> auto-detect best
621  if (!playback_params || (params.method == PLAYBACK_NONE))
622  checkMethod(params.method);
623 
624  if (!playback_params) {
626  params.rate = m_signal_manager.rate();
627  params.channels = m_signal_manager.selectedTracks().count();
628  }
629  }
630 
631  // try to create a new device, using the given playback method
632  Kwave::PlayBackDevice *device = createDevice(params.method);
633  if (!device) return Q_NULLPTR;
634 
635  // override the number of tracks if not negative
636  if (tracks > 0) params.channels = tracks;
637 
638  // open the playback device with it's default parameters
639  // open and initialize the device
640  QString result = device->open(
641  params.device,
642  params.rate,
643  params.channels,
644  params.bits_per_sample,
645  params.bufbase
646  );
647  if (result.length()) {
648  qWarning("PlayBackPlugin::openDevice(): opening the device failed.");
649 
650  // delete the device if it did not open
651  delete device;
652  device = Q_NULLPTR;
653 
654  // show an error message box
656  i18n("Unable to open '%1'",
657  params.device.section(QLatin1Char('|'), 0, 0)));
658  }
659 
660 
661  return device;
662 }
663 
664 //***************************************************************************
666  const Kwave::PlayBackParam &params)
667 {
668  m_playback_params = params;
669 }
670 
671 //***************************************************************************
674 {
675  m_playback_factories.append(factory);
676 }
677 
678 //***************************************************************************
681 {
682  m_playback_factories.removeAll(factory);
683 }
684 
685 //***************************************************************************
686 //***************************************************************************
QWidget * parentWidget() const
Kwave::SignalManager & m_signal_manager
virtual int write(const Kwave::SampleArray &samples)=0
sample_index_t first() const
Definition: Selection.h:71
void seekTo(sample_index_t pos)
void unregisterPlaybackDeviceFactory(Kwave::PlaybackDeviceFactory *factory)
void setDefaultParams(const Kwave::PlayBackParam &params)
void setEndPos(sample_index_t pos)
QList< Kwave::PlaybackDeviceFactory * > m_playback_factories
unsigned int channels
Definition: PlayBackParam.h:66
playback_method_t
Definition: PlayBackParam.h:31
sample_index_t last() const
Definition: Selection.h:76
unsigned int bits_per_sample
Definition: PlayBackParam.h:69
void updatePlaybackPos(sample_index_t pos)
Kwave::Selection & selection()
#define SCREEN_REFRESHES_PER_SECOND
quint64 sample_index_t
Definition: Sample.h:28
sample_index_t startPos() const
void checkMethod(Kwave::playback_method_t &method)
unsigned int bufbase
Definition: PlayBackParam.h:75
bool connect(Kwave::StreamObject &source, const char *output, Kwave::StreamObject &sink, const char *input)
Definition: Connect.cpp:48
bool eof() const
Definition: SampleReader.h:66
virtual void skip(sample_index_t count)
void seekDone(sample_index_t pos)
void sigPlaybackPos(sample_index_t pos)
virtual ~PlaybackController() Q_DECL_OVERRIDE
static int error(QWidget *widget, QString message, QString caption=QString())
Definition: MessageBox.cpp:126
virtual QList< Kwave::playback_method_t > supportedMethods()=0
void registerPlaybackDeviceFactory(Kwave::PlaybackDeviceFactory *factory)
sample_index_t length()
double rate() const
void sigSeekDone(sample_index_t pos)
virtual void cancel()
sample_index_t endPos() const
const QList< unsigned int > allTracks()
PlaybackController(Kwave::SignalManager &signal_manager)
virtual Kwave::PlayBackDevice * createDevice(Kwave::playback_method_t method)=0
Kwave::PlayBackDevice * m_device
QString name(IDX type) const
Definition: TypesMap.h:117
Kwave::playback_method_t method
Definition: PlayBackParam.h:78
#define DBG(qs)
Definition: String.h:55
const QList< unsigned int > selectedTracks()
virtual void start()
unsigned int toUint(T x)
Definition: Utils.h:109
Kwave::PlayBackDevice * openDevice(int tracks, const Kwave::PlayBackParam *playback_params)
virtual void seek(sample_index_t pos)
virtual Kwave::PlayBackDevice * createDevice(Kwave::playback_method_t method)
virtual QString open(const QString &device, double rate, unsigned int channels, unsigned int bits, unsigned int bufbase)=0
void setStartPos(sample_index_t pos)
IDX findFromData(const DATA &data) const
Definition: TypesMap.h:89
Kwave::PlayBackParam m_playback_params
virtual void run_wrapper(const QVariant &params) Q_DECL_OVERRIDE
Kwave::WorkerThread m_thread
sample_index_t currentPos() const
qint32 sample_t
Definition: Sample.h:37