kwave  18.07.70
SonagramPlugin.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  SonagramPlugin.cpp - plugin that shows a sonagram window
3  -------------------
4  begin : Fri Jul 28 2000
5  copyright : (C) 2000 by Thomas Eschenbacher
6  email : 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 <errno.h>
21 #include <limits.h>
22 #include <math.h>
23 #include <new>
24 #include <stdio.h>
25 #include <stdlib.h>
26 
27 #include <QApplication>
28 #include <QColor>
29 #include <QFutureSynchronizer>
30 #include <QImage>
31 #include <QMutexLocker>
32 #include <QString>
33 #include <QtConcurrentRun>
34 
35 #include "libkwave/GlobalLock.h"
36 #include "libkwave/MessageBox.h"
38 #include "libkwave/Plugin.h"
39 #include "libkwave/PluginManager.h"
40 #include "libkwave/Sample.h"
41 #include "libkwave/SampleReader.h"
42 #include "libkwave/SignalManager.h"
43 #include "libkwave/Track.h"
44 #include "libkwave/Utils.h"
46 
47 #include "libgui/OverViewCache.h"
49 
50 #include "SonagramDialog.h"
51 #include "SonagramPlugin.h"
52 #include "SonagramWindow.h"
53 
54 KWAVE_PLUGIN(sonagram, SonagramPlugin)
55 
56 
59 #define REPAINT_INTERVAL 500
60 
61 //***************************************************************************
63  const QVariantList &args)
64  :Kwave::Plugin(parent, args),
65  m_sonagram_window(Q_NULLPTR),
66  m_selection(Q_NULLPTR),
67  m_slices(0), m_fft_points(0),
68  m_window_type(Kwave::WINDOW_FUNC_NONE), m_color(true),
69  m_track_changes(true), m_follow_selection(false), m_image(),
70  m_overview_cache(Q_NULLPTR), m_slice_pool(), m_valid(MAX_SLICES, false),
71  m_pending_jobs(), m_lock_job_list(QMutex::Recursive), m_future(),
72  m_repaint_timer()
73 {
74  i18n("Sonagram");
75 
76  // connect the output ouf the sonagram worker thread
79  Qt::QueuedConnection);
80 
81  // connect repaint timer
82  connect(&m_repaint_timer, SIGNAL(timeout()),
83  this, SLOT(validate()));
84 }
85 
86 //***************************************************************************
88 {
89  m_repaint_timer.stop();
90 
92  m_sonagram_window = Q_NULLPTR;
93 
94  if (m_selection) delete m_selection;
95  m_selection = Q_NULLPTR;
96 }
97 
98 //***************************************************************************
99 QStringList *Kwave::SonagramPlugin::setup(QStringList &previous_params)
100 {
101  QStringList *result = Q_NULLPTR;
102 
103  // try to interprete the list of previous parameters, ignore errors
104  if (previous_params.count()) interpreteParameters(previous_params);
105 
106  Kwave::SonagramDialog *dlg = new(std::nothrow) Kwave::SonagramDialog(*this);
107  Q_ASSERT(dlg);
108  if (!dlg) return Q_NULLPTR;
109 
111  dlg->setColorMode(m_color ? 1 : 0);
114 
115  if (dlg->exec() == QDialog::Accepted) {
116  result = new QStringList();
117  Q_ASSERT(result);
118  if (result) dlg->parameters(*result);
119  };
120 
121  delete dlg;
122  return result;
123 }
124 
125 //***************************************************************************
127 {
128  bool ok;
129  QString param;
130 
131  // evaluate the parameter list
132  if (params.count() != 5) return -EINVAL;
133 
134  param = params[0];
135  m_fft_points = param.toUInt(&ok);
136  if (!ok) return -EINVAL;
138 
139  param = params[1];
141  if (!ok) return -EINVAL;
142 
143  param = params[2];
144  m_color = (param.toUInt(&ok) != 0);
145  if (!ok) return -EINVAL;
146 
147  param = params[3];
148  m_track_changes = (param.toUInt(&ok) != 0);
149  if (!ok) return -EINVAL;
150 
151  param = params[4];
152  m_follow_selection = (param.toUInt(&ok) != 0);
153  if (!ok) return -EINVAL;
154 
155  return 0;
156 }
157 
158 //***************************************************************************
159 int Kwave::SonagramPlugin::start(QStringList &params)
160 {
161  // clean up leftovers from last run
163  m_sonagram_window = Q_NULLPTR;
164  if (m_selection) delete m_selection;
165  m_selection = Q_NULLPTR;
167  m_overview_cache = Q_NULLPTR;
168 
169  Kwave::SignalManager &sig_mgr = signalManager();
170 
171  // interprete parameter list and abort if it contains invalid data
172  int result = interpreteParameters(params);
173  if (result) return result;
174 
175  // create an empty sonagram window
176  m_sonagram_window = new(std::nothrow)
178  Q_ASSERT(m_sonagram_window);
179  if (!m_sonagram_window) return -ENOMEM;
180 
181  // if the signal closes, close the sonagram window too
182  QObject::connect(&manager(), SIGNAL(sigClosed()),
183  m_sonagram_window, SLOT(close()));
184 
185  // get the current selection
186  QList<unsigned int> selected_channels;
187  sample_index_t offset = 0;
188  sample_index_t length = 0;
189  length = selection(&selected_channels, &offset, Q_NULLPTR, true);
190 
191  // abort if nothing is selected
192  if (!length || selected_channels.isEmpty())
193  return -EINVAL;
194 
195  // calculate the number of slices (width of image)
196  m_slices = Kwave::toUint(ceil(static_cast<double>(length) /
197  static_cast<double>(m_fft_points)));
199 
200  /* limit selection to INT_MAX samples (limitation of the cache index) */
201  if ((length / m_fft_points) >= INT_MAX) {
203  i18n("File or selection too large"));
204  return -EFBIG;
205  }
206 
207  // create a selection tracker
208  m_selection = new(std::nothrow) Kwave::SelectionTracker(
209  &sig_mgr, offset, length, &selected_channels);
210  Q_ASSERT(m_selection);
211  if (!m_selection) return -ENOMEM;
212 
213  connect(m_selection, SIGNAL(sigTrackInserted(QUuid)),
214  this, SLOT(slotTrackInserted(QUuid)));
215  connect(m_selection, SIGNAL(sigTrackDeleted(QUuid)),
216  this, SLOT(slotTrackDeleted(QUuid)));
217  connect(
218  m_selection,
219  SIGNAL(sigInvalidated(const QUuid*,sample_index_t,sample_index_t)),
220  this,
221  SLOT(slotInvalidated(const QUuid*,sample_index_t,sample_index_t))
222  );
223 
224  // create a new empty image
226 
227  // set the overview
228  m_overview_cache = new(std::nothrow)
229  Kwave::OverViewCache(sig_mgr, offset, length, &selected_channels);
230  Q_ASSERT(m_overview_cache);
231  if (!m_overview_cache) return -ENOMEM;
232 
233  refreshOverview(); // <- this needs the m_overview_cache
234 
235  if (m_track_changes) {
236  // stay informed about changes in the signal
237  connect(m_overview_cache, SIGNAL(changed()),
238  this, SLOT(refreshOverview()));
239  } else {
240  // overview cache is no longer needed
241  delete m_overview_cache;
242  m_overview_cache = Q_NULLPTR;
243  }
244 
245  // connect all needed signals
246  connect(m_sonagram_window, SIGNAL(destroyed()),
247  this, SLOT(windowDestroyed()));
248 
249  // activate the window with an initial image
250  // and all necessary information
255  m_sonagram_window->show();
256 
257  if (m_track_changes) {
258  QObject::connect(static_cast<QObject*>(&(manager())),
259  SIGNAL(sigSignalNameChanged(QString)),
260  m_sonagram_window, SLOT(setName(QString)));
261  }
262 
263  // increment the usage counter and release the plugin when the
264  // sonagram window closed
265  use();
266 
267  return 0;
268 }
269 
270 //***************************************************************************
272 {
273  unsigned int fft_points;
274  unsigned int slices;
275  Kwave::window_function_t window_type;
276  sample_index_t first_sample;
277  sample_index_t last_sample;
278  QBitArray valid;
279  QList<unsigned int> track_list;
280 
281  {
282  QMutexLocker _lock(&m_lock_job_list);
283 
284  if (!m_selection) return;
285  if (!m_selection->length() || (m_fft_points < 4)) return;
286 
287  fft_points = m_fft_points;
288  slices = m_slices;
289  window_type = m_window_type;
290  first_sample = m_selection->first();
291  last_sample = m_selection->last();
292  valid = m_valid;
293  m_valid.fill(true);
294 
295  const QList<QUuid> selected_tracks(m_selection->allTracks());
296  foreach (unsigned int track, signalManager().allTracks())
297  if (selected_tracks.contains(signalManager().uuidOfTrack(track)))
298  track_list.append(track);
299  }
300  const unsigned int tracks = track_list.count();
301 
302  Kwave::WindowFunction func(window_type);
303  const QVector<double> windowfunction = func.points(fft_points);
304  Q_ASSERT(windowfunction.count() == Kwave::toInt(fft_points));
305  if (windowfunction.count() != Kwave::toInt(fft_points)) return;
306 
308  signalManager(), track_list, first_sample, last_sample);
309 
310 // qDebug("SonagramPlugin[%p]::makeAllValid() [%llu .. %llu]",
311 // static_cast<void *>(this), first_sample, last_sample);
312 
313  QFutureSynchronizer<void> synchronizer;
314  for (unsigned int slice_nr = 0; slice_nr < slices; slice_nr++) {
315 // qDebug("SonagramPlugin::run(): calculating slice %d of %d",
316 // slice_nr, m_slices);
317 
318  if (valid[slice_nr]) continue;
319 
320  // determine start of the stripe
321  sample_index_t pos = first_sample + (slice_nr * fft_points);
322 
323  // get a new slice from the pool and initialize it
324  Kwave::SonagramPlugin::Slice *slice = m_slice_pool.allocate();
325  Q_ASSERT(slice);
326 
327  slice->m_index = slice_nr;
328  memset(slice->m_input, 0x00, sizeof(slice->m_input));
329  memset(slice->m_output, 0x00, sizeof(slice->m_output));
330 
331  if ((pos <= last_sample) && (tracks)) {
332  // initialize result with zeroes
333  memset(slice->m_result, 0x00, sizeof(slice->m_result));
334 
335  // seek to the start of the slice
336  source.seek(pos);
337 
338  // we have a new slice, now fill it's input buffer
339  double *in = slice->m_input;
340  for (unsigned int j = 0; j < fft_points; j++) {
341  double value = 0.0;
342  if (!(source.eof())) {
343  for (unsigned int t = 0; t < tracks; t++) {
344  sample_t s = 0;
345  Kwave::SampleReader *reader = source[t];
346  Q_ASSERT(reader);
347  if (reader) *reader >> s;
348  value += sample2double(s);
349  }
350  value /= tracks;
351  }
352  in[j] = value * windowfunction[j];
353  }
354 
355  // a background job is running soon
356  // (for counterpart, see insertSlice(...) below [main thread])
357  m_pending_jobs.lockForRead();
358 
359  // run the FFT in a background thread
360  synchronizer.addFuture(QtConcurrent::run(
362  );
363  } else {
364  // range has been deleted -> fill with "empty"
365  memset(slice->m_result, 0xFF, sizeof(slice->m_result));
366  m_pending_jobs.lockForRead();
367  emit sliceAvailable(slice);
368  }
369 
370  if (shouldStop()) break;
371  }
372 
373 // qDebug("SonagramPlugin::makeAllValid(): waiting for background jobs...");
374 
375  // wait for all worker threads
376  synchronizer.waitForFinished();
377 
378  // wait for queued signals
379  m_pending_jobs.lockForWrite();
380  m_pending_jobs.unlock();
381 
382 // qDebug("SonagramPlugin::makeAllValid(): done.");
383 }
384 
385 //***************************************************************************
386 void Kwave::SonagramPlugin::run(QStringList params)
387 {
388  qDebug("SonagramPlugin::run()");
389  Q_UNUSED(params);
390  {
391  // invalidate all slices
392  QMutexLocker _lock(&m_lock_job_list);
393  m_valid.fill(false);
394  }
395  makeAllValid();
396 }
397 
398 //***************************************************************************
400 {
401  fftw_plan p;
402 
403  // prepare for a 1-dimensional real-to-complex DFT
404  {
405  Kwave::GlobalLock _lock; // libfftw is not threadsafe!
406  p = fftw_plan_dft_r2c_1d(
407  m_fft_points,
408  &(slice->m_input[0]),
409  &(slice->m_output[0]),
410  FFTW_ESTIMATE
411  );
412  }
413  Q_ASSERT(p);
414  if (!p) return;
415 
416  // calculate the fft (according to the specs, this is the one and only
417  // libfft function that is threadsafe!)
418  fftw_execute(p);
419 
420  // norm all values to [0...254] and use them as pixel value
421  const double scale = static_cast<double>(m_fft_points) / 254.0;
422  for (unsigned int j = 0; j < m_fft_points / 2; j++) {
423  // get singal energy and scale to [0 .. 254]
424  double rea = slice->m_output[j][0];
425  double ima = slice->m_output[j][1];
426  double a = ((rea * rea) + (ima * ima)) / scale;
427 
428  slice->m_result[j] = static_cast<unsigned char>(qMin(a, double(254.0)));
429  }
430 
431  // free the allocated FFT resources
432  {
433  Kwave::GlobalLock _lock; // libfftw is not threadsafe!
434  fftw_destroy_plan(p);
435  }
436 
437  // emit the slice data to be synchronously inserted into
438  // the current image in the context of the main thread
439  // (Qt does the queuing for us)
440  emit sliceAvailable(slice);
441 }
442 
443 //***************************************************************************
445 {
446  // check: this must be called from the GUI thread only!
447  Q_ASSERT(this->thread() == QThread::currentThread());
448  Q_ASSERT(this->thread() == qApp->thread());
449 
450  Q_ASSERT(slice);
451  if (!slice) return;
452 
453  QByteArray result;
454  result.setRawData(reinterpret_cast<char *>(&(slice->m_result[0])),
455  m_fft_points / 2);
456  unsigned int nr = slice->m_index;
457 
458  // forward the slice to the window to display it
460 
461  // return the slice into the pool
462  m_slice_pool.release(slice);
463 
464  // job is done
465  m_pending_jobs.unlock();
466 }
467 
468 //***************************************************************************
469 void Kwave::SonagramPlugin::createNewImage(const unsigned int width,
470  const unsigned int height)
471 {
472  // delete the previous image
473  m_image = QImage();
475 
476  // do not create a new image if one dimension is zero!
477  Q_ASSERT(width);
478  Q_ASSERT(height);
479  if (!width || !height) return;
480 
481  // also do not create if the image size is out of range
482  Q_ASSERT(width <= 32767);
483  Q_ASSERT(height <= 32767);
484  if ((width >= 32767) || (height >= 32767)) return;
485 
486  // create the new image object
487  m_image = QImage(width, height, QImage::Format_Indexed8);
488  Q_ASSERT(!m_image.isNull());
489  if (m_image.isNull()) return;
490 
491  // initialize the image's palette with transparecy
492  m_image.setColorCount(256);
493  for (int i = 0; i < 256; i++) {
494  m_image.setColor(i, 0x00000000);
495  }
496 
497  // fill the image with "empty" (transparent)
498  m_image.fill(0xFF);
499 }
500 
501 //***************************************************************************
503 {
504  if (!m_overview_cache || !m_sonagram_window) return;
505 
506  QColor fg = m_sonagram_window->palette().light().color();
507  QColor bg = m_sonagram_window->palette().mid().color();
508  QImage overview = m_overview_cache->getOverView(
509  m_sonagram_window->width(), SONAGRAM_OVERVIEW_HEIGHT, fg, bg);
510 
511  m_sonagram_window->setOverView(overview);
512 }
513 
514 //***************************************************************************
516 {
517  // only re-start the repaint timer, this hides some GUI update artifacts
518  if (!m_repaint_timer.isActive()) {
519  m_repaint_timer.stop();
520  m_repaint_timer.setSingleShot(true);
522  }
523 }
524 
525 //***************************************************************************
527 {
528  // wait for previously running jobs to finish
529  if (m_future.isRunning()) {
531  return; // job is still running, come back later...
532  }
533 
534  // queue a background thread for updates
535  m_future = QtConcurrent::run(this, &Kwave::SonagramPlugin::makeAllValid);
536 }
537 
538 //***************************************************************************
539 void Kwave::SonagramPlugin::slotTrackInserted(const QUuid &track_id)
540 {
541  QMutexLocker _lock(&m_lock_job_list);
542 
543  Q_UNUSED(track_id);
544 
545  // check for "track changes" mode
546  if (!m_track_changes) return;
547 
548  // invalidate complete signal
549  m_valid.fill(false, m_slices);
551 }
552 
553 //***************************************************************************
554 void Kwave::SonagramPlugin::slotTrackDeleted(const QUuid &track_id)
555 {
556  QMutexLocker _lock(&m_lock_job_list);
557 
558  Q_UNUSED(track_id);
559 
560  // check for "track changes" mode
561  if (!m_track_changes) return;
562 
563  // invalidate complete signal
564  m_valid.fill(false, m_slices);
566 }
567 
568 //***************************************************************************
569 void Kwave::SonagramPlugin::slotInvalidated(const QUuid *track_id,
570  sample_index_t first,
571  sample_index_t last)
572 {
573  QMutexLocker lock(&m_lock_job_list);
574 
575  Q_UNUSED(track_id);
576 // qDebug("SonagramPlugin[%p]::slotInvalidated(%s, %llu, %llu)",
577 // static_cast<void *>(this),
578 // (track_id) ? DBG(track_id->toString()) : "*", first, last);
579 
580  // check for "track changes" mode
581  if (!m_track_changes) return;
582 
583  // adjust offsets, absolute -> relative
584  sample_index_t offset = (m_selection) ? m_selection->offset() : 0;
585  Q_ASSERT(first >= offset);
586  Q_ASSERT(last >= offset);
587  Q_ASSERT(last >= first);
588  first -= offset;
589  last -= offset;
590 
591  unsigned int first_idx = Kwave::toUint(first / m_fft_points);
592  unsigned int last_idx;
593  if (last >= (SAMPLE_INDEX_MAX - (m_fft_points - 1)))
594  last_idx = m_slices - 1;
595  else
596  last_idx = Kwave::toUint(qMin(Kwave::round_up(last,
597  static_cast<sample_index_t>(m_fft_points)) / m_fft_points,
598  static_cast<sample_index_t>(m_slices - 1))
599  );
600 
601  m_valid.fill(false, first_idx, last_idx + 1);
603 }
604 
605 //***************************************************************************
607 {
608  cancel();
609 
610  m_sonagram_window = Q_NULLPTR; // closes itself !
611 
612  if (m_selection) delete m_selection;
613  m_selection = Q_NULLPTR;
614 
616  m_overview_cache = Q_NULLPTR;
617 
618  release();
619 }
620 
621 //***************************************************************************
622 #include "SonagramPlugin.moc"
623 //***************************************************************************
624 //***************************************************************************
unsigned int m_fft_points
virtual QImage getOverView(int width, int height, const QColor &fg, const QColor &bg, double gain=1.0)
QFuture< void > m_future
sample_index_t offset() const
Definition: App.h:33
sample_index_t first() const
window_function_t
void parameters(QStringList &list)
void slotTrackDeleted(const QUuid &track_id)
int interpreteParameters(QStringList &params)
Kwave::FixedPool< MAX_FFT_JOBS, Slice > m_slice_pool
void sliceAvailable(Kwave::SonagramPlugin::Slice *slice)
QWidget * parentWidget() const
Definition: Plugin.cpp:450
#define SONAGRAM_OVERVIEW_HEIGHT
Kwave::SignalManager & signalManager()
Definition: Plugin.cpp:444
virtual void close()
Definition: Plugin.cpp:394
void release()
Definition: Plugin.cpp:420
void insertSlice(const unsigned int slice_nr, const QByteArray &slice)
QList< QUuid > allTracks()
quint64 sample_index_t
Definition: Sample.h:28
unsigned char m_result[MAX_FFT_POINTS]
Kwave::PluginManager & manager() const
Definition: Plugin.cpp:437
bool connect(Kwave::StreamObject &source, const char *output, Kwave::StreamObject &sink, const char *input)
Definition: Connect.cpp:48
void setColorMode(int color)
T round_up(T x, const T s)
Definition: Utils.h:96
void setTrackChanges(bool track_changes)
virtual bool eof() const
void setPoints(unsigned int points)
SonagramPlugin(QObject *parent, const QVariantList &args)
static int error(QWidget *widget, QString message, QString caption=QString())
Definition: MessageBox.cpp:126
QReadWriteLock m_pending_jobs
void setRate(double rate)
virtual ~SonagramPlugin() Q_DECL_OVERRIDE
Kwave::SonagramWindow * m_sonagram_window
int toInt(T x)
Definition: Utils.h:127
void setFollowSelection(bool follow_selection)
void sigClosed(Kwave::Plugin *p)
sample_index_t last() const
static window_function_t findFromName(const QString &name)
virtual void cancel()
Definition: Plugin.cpp:325
#define MAX_SLICES
virtual void run(QStringList params) Q_DECL_OVERRIDE
QString signalName()
Definition: Plugin.cpp:456
const QList< unsigned int > allTracks()
static double sample2double(const sample_t s)
Definition: Sample.h:73
Kwave::window_function_t m_window_type
void setOverView(const QImage &image)
virtual int start(QStringList &params) Q_DECL_OVERRIDE
virtual double signalRate()
Definition: Plugin.cpp:468
QVector< double > points(unsigned int len) const
#define KWAVE_PLUGIN(name, class)
Definition: Plugin.h:54
void calculateSlice(Kwave::SonagramPlugin::Slice *slice)
void slotTrackInserted(const QUuid &track_id)
fftw_complex m_output[MAX_FFT_POINTS]
#define REPAINT_INTERVAL
unsigned int toUint(T x)
Definition: Utils.h:109
double m_input[MAX_FFT_POINTS]
void use()
Definition: Plugin.cpp:412
virtual void seek(sample_index_t pos)
void setImage(QImage image)
void slotInvalidated(const QUuid *track_id, sample_index_t first, sample_index_t last)
#define SAMPLE_INDEX_MAX
Definition: Sample.h:31
Kwave::SelectionTracker * m_selection
Kwave::OverViewCache * m_overview_cache
void createNewImage(const unsigned int width, const unsigned int height)
bool shouldStop() const
Definition: Plugin.h:120
virtual QStringList * setup(QStringList &previous_params) Q_DECL_OVERRIDE
sample_index_t length() const
void insertSlice(Kwave::SonagramPlugin::Slice *slice)
#define MAX_FFT_POINTS
qint32 sample_t
Definition: Sample.h:37
virtual sample_index_t selection(QList< unsigned int > *tracks=Q_NULLPTR, sample_index_t *left=Q_NULLPTR, sample_index_t *right=Q_NULLPTR, bool expand_if_empty=false)
Definition: Plugin.cpp:480
void setColorMode(int mode)
void setWindowFunction(Kwave::window_function_t type)