kwave  18.07.70
SonagramWindow.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  SonagramWindow.cpp - window for showing a sonagram
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 <limits.h>
21 #include <math.h>
22 #include <new>
23 
24 #include <QBitmap>
25 #include <QImage>
26 #include <QLabel>
27 #include <QMenuBar>
28 #include <QPointer>
29 #include <QLayout>
30 #include <QStatusBar>
31 #include <QTimer>
32 
33 #include "libkwave/String.h"
34 #include "libkwave/Utils.h"
36 
37 #include "libgui/FileDialog.h"
38 #include "libgui/ImageView.h"
39 #include "libgui/ScaleWidget.h"
40 
41 #include "SonagramWindow.h"
42 
46 #define REFRESH_DELAY 100
47 
53 #define COLOR_CUTOFF_RATIO (0.1/100.0)
54 
55 static const char *background[] = {
56 /* width height num_colors chars_per_pixel */
57 " 20 20 2 1",
58 /* colors */
59 "# c #808080",
60 ". c None",
61 /* pixels */
62 "##########..........",
63 "##########..........",
64 "##########..........",
65 "##########..........",
66 "##########..........",
67 "##########..........",
68 "##########..........",
69 "##########..........",
70 "##########..........",
71 "##########..........",
72 "..........##########",
73 "..........##########",
74 "..........##########",
75 "..........##########",
76 "..........##########",
77 "..........##########",
78 "..........##########",
79 "..........##########",
80 "..........##########",
81 "..........##########"
82 };
83 
84 //****************************************************************************
85 Kwave::SonagramWindow::SonagramWindow(QWidget *parent, const QString &name)
86  :KMainWindow(parent),
87  m_status_time(Q_NULLPTR),
88  m_status_freq(Q_NULLPTR),
89  m_status_ampl(Q_NULLPTR),
90  m_image(),
91  m_color_mode(0),
92  m_view(Q_NULLPTR),
93  m_overview(Q_NULLPTR),
94  m_points(0),
95  m_rate(0),
96  m_xscale(Q_NULLPTR),
97  m_yscale(Q_NULLPTR),
98  m_refresh_timer()
99 {
100 
101  for (unsigned int i = 0; i < 256; ++i) { m_histogram[i] = 0; }
102 
103  QWidget *mainwidget = new(std::nothrow) QWidget(this);
104  Q_ASSERT(mainwidget);
105  if (!mainwidget) return;
106  setCentralWidget(mainwidget);
107 
108  QGridLayout *top_layout = new(std::nothrow) QGridLayout(mainwidget/*, 3, 2*/);
109  Q_ASSERT(top_layout);
110  if (!top_layout) return;
111 
112  QMenuBar *bar = menuBar();
113  Q_ASSERT(bar);
114  if (!bar) return ;
115 
116 // QMenu *spectral = new QMenu();
117 // Q_ASSERT(spectral);
118 // if (!spectral) return ;
119 
120  QMenu *file = bar->addMenu(i18n("&Sonagram"));
121  Q_ASSERT(file);
122  if (!file) return ;
123 
124 // bar->addAction(i18n("&Spectral Data"), spectral);
125 // file->addAction(i18n("&Import from Bitmap..."), this, SLOT(load()));
126 
127  file->addAction(
128  QIcon::fromTheme(_("document-export")),
129  i18n("&Export to Bitmap..."),
130  this, SLOT(save())
131  );
132  file->addAction(
133  QIcon::fromTheme(_("dialog-close")),
134  i18n("&Close"),
135  this, SLOT(close()),
136  QKeySequence::Close
137  );
138 
139 // spectral->addAction (i18n("&Retransform to Signal"), this, SLOT(toSignal()));
140 
141  QStatusBar *status = statusBar();
142  Q_ASSERT(status);
143  if (!status) return ;
144 
145  m_status_time = new(std::nothrow)
146  QLabel(i18n("Time: ------ ms"), status);
147  m_status_freq = new(std::nothrow)
148  QLabel(i18n("Frequency: ------ Hz"), status);
149  m_status_ampl = new(std::nothrow)
150  QLabel(i18n("Amplitude: --- %"), status);
151  status->addPermanentWidget(m_status_time);
152  status->addPermanentWidget(m_status_freq);
153  status->addPermanentWidget(m_status_ampl);
154 
155  m_view = new(std::nothrow) Kwave::ImageView(mainwidget);
156  Q_ASSERT(m_view);
157  if (!m_view) return;
158  top_layout->addWidget(m_view, 0, 1);
159  QPalette palette;
160  palette.setBrush(m_view->backgroundRole(), QBrush(QImage(background)));
161  m_view->setAutoFillBackground(true);
162  m_view->setPalette(palette);
163 
164  m_xscale = new(std::nothrow)
165  Kwave::ScaleWidget(mainwidget, 0, 100, i18n("ms"));
166  Q_ASSERT(m_xscale);
167  if (!m_xscale) return;
168  m_xscale->setFixedHeight(m_xscale->sizeHint().height());
169  top_layout->addWidget(m_xscale, 1, 1);
170 
171  m_yscale = new(std::nothrow)
172  Kwave::ScaleWidget(mainwidget, 0, 100, i18n("Hz"));
173  Q_ASSERT(m_yscale);
174  if (!m_yscale) return ;
175  m_yscale->setFixedWidth(m_yscale->sizeHint().width());
176  m_yscale->setMinimumHeight(9*6*5);
177  top_layout->addWidget(m_yscale, 0, 0);
178 
179  m_overview = new(std::nothrow) Kwave::ImageView(mainwidget);
180  Q_ASSERT(m_overview);
181  if (!m_overview) return;
182  m_overview->setFixedHeight(SONAGRAM_OVERVIEW_HEIGHT);
183  top_layout->addWidget(m_overview, 2, 1);
184 
185  connect(m_view, SIGNAL(sigCursorPos(QPoint)),
186  this, SLOT(cursorPosChanged(QPoint)));
187  connect(&m_refresh_timer, SIGNAL(timeout()),
188  this, SLOT(refresh_view()));
189 
190  setName(name);
191 
192  top_layout->setRowStretch(0, 100);
193  top_layout->setRowStretch(1, 0);
194  top_layout->setRowStretch(2, 0);
195  top_layout->setColumnStretch(0, 0);
196  top_layout->setColumnStretch(1, 100);
197  top_layout->activate();
198 
199  if (m_status_time) m_status_time->setText(i18n("Time: 0 ms"));
200  if (m_status_freq) m_status_freq->setText(i18n("Frequency: 0 Hz"));
201  if (m_status_ampl) m_status_ampl->setText(i18n("Amplitude: 0 %"));
202 
203  // try to make 5:3 format (looks best)
204  int w = sizeHint().width();
205  int h = sizeHint().height();
206  if ((w * 3 / 5) < h) w = (h * 5) / 3;
207  if ((h * 5 / 3) < w) h = (w * 3) / 5;
208  resize(w, h);
209 
210  show();
211 }
212 
213 //****************************************************************************
215 {
216  QWidget::close();
217 }
218 
219 //****************************************************************************
221 {
222  if (m_image.isNull()) return;
223 
224  QPointer<Kwave::FileDialog> dlg = new (std::nothrow) Kwave::FileDialog(
225  _("kfiledialog:///kwave_sonagram"),
226  Kwave::FileDialog::SaveFile, QString(),
227  this, QUrl(), _("*.bmp")
228  );
229  if (!dlg) return;
230  dlg->setWindowTitle(i18n("Save Sonagram"));
231  if (dlg->exec() == QDialog::Accepted) {
232  QString filename = dlg->selectedUrl().toLocalFile();
233  if (!filename.isEmpty()) m_image.save(filename, "BMP");
234  }
235  delete dlg;
236 }
237 
238 //****************************************************************************
240 {
241 // if (image) {
242 // QString filename = QFileDialog::getOpenFileName(this, QString(), "", "*.bmp");
243 // printf ("loading %s\n", filename.local8Bit().data());
244 // if (!filename.isNull()) {
245 // printf ("loading %s\n", filename.local8Bit().data());
246 // QImage *newimage = new QImage (filename);
247 // Q_ASSERT(newimage);
248 // if (newimage) {
249 // if ((image->height() == newimage->height())
250 // && (image->width() == newimage->width())) {
251 //
252 // for (int i = 0; i < x; i++) {
253 // for (int j = 0; j < points / 2; j++) {
254 // if (data[i]) {
255 // // data[i][j].real;
256 // }
257 //
258 // }
259 // }
260 //
261 // delete image;
262 // image = newimage;
263 // view->setImage (image);
264 // } else {
265 // char buf[128];
266 // delete newimage;
267 // snprintf(buf, sizeof(buf), i18n("Bitmap must be %dx%d"),
268 // image->width(), image->height());
269 // KMsgBox::message (this, "Info", buf, 2);
270 // }
271 // } else
272 // KMsgBox::message (this, i18n("Error"),
273 // i18n("Could not open Bitmap"), 2);
274 // }
275 // }
276 }
277 
278 //****************************************************************************
280 {
281  Q_ASSERT(m_view);
282  if (!m_view) return;
283 
284  m_image = image;
285 
286  // re-initialize histogram over all pixels
287  for (unsigned int i = 0; i < 256; i++)
288  m_histogram[i] = 0;
289  if (!m_image.isNull()) {
290  for (int x = 0; x < m_image.width(); x++) {
291  for (int y = 0; y < m_image.height(); y++) {
292  quint8 p = static_cast<quint8>(m_image.pixelIndex(x, y));
293  m_histogram[p]++;
294  }
295  }
296  }
297 
298  refresh_view();
299 }
300 
301 //****************************************************************************
302 void Kwave::SonagramWindow::setOverView(const QImage &overview)
303 {
304  if (m_overview) m_overview->setImage(overview);
305 }
306 
307 //****************************************************************************
308 void Kwave::SonagramWindow::insertSlice(const unsigned int slice_nr,
309  const QByteArray &slice)
310 {
311  Q_ASSERT(m_view);
312  if (!m_view) return;
313  if (m_image.isNull()) return;
314 
315  unsigned int image_width = m_image.width();
316  unsigned int image_height = m_image.height();
317 
318  // slice is out of range ?
319  if (slice_nr >= image_width) return;
320 
321  unsigned int y;
322  unsigned int size = slice.size();
323  for (y = 0; y < size; y++) {
324  quint8 p;
325 
326  // remove the current pixel from the histogram
327  p = static_cast<quint8>(m_image.pixelIndex(slice_nr, y));
328  m_histogram[p]--;
329 
330  // set the new pixel value
331  p = slice[(size - 1) - y];
332  m_image.setPixel(slice_nr, y, p);
333 
334  // insert the new pixel into the histogram
335  m_histogram[p]++;
336  }
337  while (y < image_height) { // fill the rest with blank
338  m_image.setPixel(slice_nr, y++, 0xFE);
339  m_histogram[0xFE]++;
340  }
341 
342  if (!m_refresh_timer.isActive()) {
343  m_refresh_timer.setSingleShot(true);
345  }
346 }
347 
348 //****************************************************************************
350 {
351  if (m_image.isNull()) return;
352 
353  // get the sum of pixels != 0
354  unsigned long int sum = 0;
355  for (unsigned int i = 1; i <= 254; i++)
356  sum += m_histogram[i];
357 
358  // cut off all parts below the cutoff ratio (e.g. 0.1%)
359  unsigned int cutoff = Kwave::toUint(sum * COLOR_CUTOFF_RATIO);
360 
361  // get the last used color from the histogram
362  int last = 254;
363  while ((last >= 0) && (m_histogram[last] <= cutoff))
364  last--;
365 
366  QColor c;
367  for (int i = 0; i < 255; i++) {
368  int v;
369 
370  if (i >= last) {
371  v = 254;
372  } else {
373  // map [0...last] to [254...0]
374  v = ((last - i) * 254) / last;
375  }
376 
377  if (m_color_mode == 1) {
378  // rainbow effect
379  c.setHsv( (v * 255) / 255, 255, 255, 255);
380  } else {
381  // greyscale palette
382  c.setRgb(v, v, v, 255);
383  }
384 
385  m_image.setColor(i, c.rgba());
386 // qDebug("color[%3d] = 0x%08X",i, c.rgba());
387  }
388 
389  // use color 0xFF for transparency !
390  m_image.setColor(0xFF, QColor(0, 0, 0, 0).rgba());
391 }
392 
393 //****************************************************************************
395 {
396  Q_ASSERT(m_view);
397  if (!m_view) return;
400 }
401 
402 //****************************************************************************
404 {
406 // gsl_fft_complex_wavetable table;
407 //
408 // gsl_fft_complex_wavetable_alloc (points, &table);
409 // gsl_fft_complex_init (points, &table);
410 //
411 // Kwave::TopWidget *win = new Kwave::TopWidget(...);
412 //
413 // Q_ASSERT(win);
414 // if (win) {
415 //
416 // Kwave::Signal *newsig = new Kwave::Signal(length, rate);
417 // Q_ASSERT(newsig);
418 //
419 // //assure 10 Hz for correction signal, this should not be audible
420 // int slopesize = rate / 10;
421 //
422 // double *slope = new double [slopesize];
423 //
424 // if (slope && newsig) {
425 // for (int i = 0; i < slopesize; i++)
426 // slope[i] = 0.5 + 0.5 * cos( ((double) i) * M_PI / slopesize);
427 //
428 // win->show();
429 //
430 // int *output = newsig->getSample(); //sample data
431 // complex *tmp = new complex [points]; //this window holds the data for ifft and after that part of the signal
432 //
433 // if (output && tmp && data) {
434 // for (int i = 0; i < x; i++) {
435 // if (data[i]) memcpy (tmp, data[i], sizeof(complex)*points);
436 // gsl_fft_complex_inverse (tmp, points, &table);
437 //
438 // for (int j = 0; j < points; j++)
439 // output[i*points + j] = (int)(tmp[j].real * ((1 << 23)-1));
440 // }
441 // int dif ;
442 // int max;
443 // for (int i = 1; i < x; i++) //remove gaps between windows
444 // {
445 // max = slopesize;
446 // if (max > length - i*points) max = length - i * points;
447 // dif = output[i * points] - output[i * points - 1];
448 // if (dif < 2)
449 // for (int j = 0; j < max; j++) output[i*points + j] += (int) (slope[j] * dif );
450 // }
451 //
452 // win->setSignal (new SignalManager (newsig));
453 //
454 // if (tmp) delete[] tmp;
455 // } else {
456 // if (newsig) delete newsig;
457 // if (win) delete win;
458 // KMsgBox::message (this, i18n("Error"), i18n("Out of memory !"), 2);
459 // }
460 // }
461 // if (slope) delete[] slope;
462 // }
463 }
464 
465 //***************************************************************************
467  double *ms, double *f)
468 {
469  if (ms) {
470  // get the time coordinate [0...(N_samples-1)* (1/f_sample) ]
471  if (!qFuzzyIsNull(m_rate)) {
472  *ms = static_cast<double>(p.x()) *
473  static_cast<double>(m_points) * 1000.0 / m_rate;
474  } else {
475  *ms = 0;
476  }
477  }
478 
479  if (f) {
480  // get the frequency coordinate
481  double py = (m_points >= 2) ? (m_points / 2) - 1 : 0;
482  double y = py - p.y();
483  if (y < 0) y = 0;
484  *f = y / py * (m_rate / 2.0);
485  }
486 }
487 
488 //***************************************************************************
490 {
491  double ms;
492  double f;
493 
494  translatePixels2TF(QPoint(m_image.width() - 1, 0), &ms, &f);
495 
496  m_xscale->setMinMax(0, Kwave::toInt(rint(ms)));
497  m_yscale->setMinMax(0, Kwave::toInt(rint(f)));
498 }
499 
500 //***************************************************************************
502 {
503 }
504 
505 //***************************************************************************
507 {
508  Q_ASSERT(mode >= 0);
509  Q_ASSERT(mode <= 1);
510 
511  if (mode != m_color_mode) {
512  m_color_mode = mode;
513  setImage(m_image);
514  }
515 }
516 
517 //***************************************************************************
519 {
520  setWindowTitle((name.length()) ?
521  i18n("Sonagram of %1", name) :
522  i18n("Sonagram")
523  );
524 }
525 
526 //****************************************************************************
528 {
529  QStatusBar *status = statusBar();
530  Q_ASSERT(status);
531  Q_ASSERT(m_points);
532  Q_ASSERT(!qFuzzyIsNull(m_rate));
533  if (!status) return;
534  if (m_image.isNull()) return;
535  if (!m_points) return;
536  if (qFuzzyIsNull(m_rate)) return;
537 
538  double ms;
539  double f;
540  double a;
541  translatePixels2TF(pos, &ms, &f);
542 
543  // item 1: time in milliseconds
544  if (m_status_time)
545  m_status_time->setText(i18n("Time: %1", Kwave::ms2string(ms)));
546 
547  // item 2: frequency in Hz
548  if (m_status_freq)
549  m_status_freq->setText(i18n("Frequency: %1 Hz", Kwave::toInt(f)));
550 
551  // item 3: amplitude in %
552  if (m_image.valid(pos.x(), pos.y())) {
553  a = m_image.pixelIndex(pos.x(), pos.y()) * (100.0 / 254.0);
554  } else {
555  a = 0.0;
556  }
557  if (m_status_ampl)
558  m_status_ampl->setText(i18n("Amplitude: %1%", Kwave::toInt(a)));
559 }
560 
561 //****************************************************************************
562 void Kwave::SonagramWindow::setPoints(unsigned int points)
563 {
564  m_points = points;
566 }
567 
568 //****************************************************************************
570 {
571  m_rate = rate;
573 }
574 
575 //***************************************************************************
576 //***************************************************************************
Kwave::ScaleWidget * m_yscale
QUrl selectedUrl() const
Definition: FileDialog.cpp:253
#define COLOR_CUTOFF_RATIO
Kwave::ImageView * m_view
QString Q_DECL_EXPORT ms2string(double ms, int precision=6)
Definition: Utils.cpp:66
#define SONAGRAM_OVERVIEW_HEIGHT
void insertSlice(const unsigned int slice_nr, const QByteArray &slice)
bool connect(Kwave::StreamObject &source, const char *output, Kwave::StreamObject &sink, const char *input)
Definition: Connect.cpp:48
void setName(const QString &name)
void setMinMax(int min, int max)
Definition: ScaleWidget.cpp:72
const char name[16]
Definition: memcpy.c:510
void setPoints(unsigned int points)
void setRate(double rate)
Kwave::ImageView * m_overview
unsigned int m_histogram[256]
void cursorPosChanged(const QPoint pos)
static const char * background[]
int toInt(T x)
Definition: Utils.h:127
#define REFRESH_DELAY
virtual QSize sizeHint() const Q_DECL_OVERRIDE
void setOverView(const QImage &image)
Kwave::ScaleWidget * m_xscale
SonagramWindow(QWidget *parent, const QString &name)
#define _(m)
Definition: memcpy.c:66
unsigned int toUint(T x)
Definition: Utils.h:109
void setImage(QImage image)
Definition: ImageView.cpp:75
void setImage(QImage image)
void translatePixels2TF(const QPoint p, double *ms, double *f)
void setColorMode(int mode)