SliceLayer.cpp
Go to the documentation of this file.
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
2 
3 /*
4  Sonic Visualiser
5  An audio file viewer and annotation editor.
6  Centre for Digital Music, Queen Mary, University of London.
7  This file copyright 2006-2007 QMUL.
8 
9  This program is free software; you can redistribute it and/or
10  modify it under the terms of the GNU General Public License as
11  published by the Free Software Foundation; either version 2 of the
12  License, or (at your option) any later version. See the file
13  COPYING included with this distribution for more information.
14 */
15 
16 #include "SliceLayer.h"
17 
18 #include "view/View.h"
19 #include "base/AudioLevel.h"
20 #include "base/RangeMapper.h"
21 #include "base/RealTime.h"
22 #include "ColourMapper.h"
23 #include "ColourDatabase.h"
24 
25 #include "PaintAssistant.h"
26 
27 #include "base/Profiler.h"
28 
29 #include <QPainter>
30 #include <QPainterPath>
31 #include <QTextStream>
32 
33 
35  m_binAlignment(BinsSpanScalePoints),
36  m_colourMap(int(ColourMapper::Ice)),
37  m_colourInverted(false),
38  m_energyScale(dBScale),
39  m_samplingMode(SampleMean),
40  m_plotStyle(PlotLines),
41  m_binScale(LinearBins),
42  m_normalize(false),
43  m_threshold(0.0),
44  m_initialThreshold(0.0),
45  m_gain(1.0),
46  m_minbin(0),
47  m_maxbin(0),
48  m_currentf0(0),
49  m_currentf1(0)
50 {
51 }
52 
54 {
55 
56 }
57 
58 void
60 {
61  auto newModel = ModelById::getAs<DenseThreeDimensionalModel>(modelId);
62 
63  if (!modelId.isNone() && !newModel) {
64  throw std::logic_error("Not a DenseThreeDimensionalModel");
65  }
66 
67  if (m_sliceableModel == modelId) return;
68  m_sliceableModel = modelId;
69 
70  if (newModel) {
72 
73  if (m_minbin == 0 && m_maxbin == 0) {
74  m_minbin = 0;
75  m_maxbin = newModel->getHeight();
76  }
77  }
78 
79  emit modelReplaced();
81 }
82 
83 void
84 SliceLayer::sliceableModelReplaced(ModelId orig, ModelId replacement)
85 {
86  SVDEBUG << "SliceLayer::sliceableModelReplaced(" << orig << ", " << replacement << ")" << endl;
87 
88  if (orig == m_sliceableModel) {
89  setSliceableModel(replacement);
90  }
91 }
92 
93 QString
95 {
96  int minbin, maxbin, range;
97  return getFeatureDescriptionAux(v, p, true, minbin, maxbin, range);
98 }
99 
100 QString
102  bool includeBinDescription,
103  int &minbin, int &maxbin, int &range) const
104 {
105  minbin = 0;
106  maxbin = 0;
107 
108  auto sliceableModel =
109  ModelById::getAs<DenseThreeDimensionalModel>(m_sliceableModel);
110  if (!sliceableModel) return "";
111 
113  minbin = int(getBinForX(v, p.x()));
114  maxbin = int(getBinForX(v, p.x() + 1));
115  } else {
116  minbin = int(getBinForX(v, p.x()) + 0.5);
117  maxbin = int(getBinForX(v, p.x() + 1) + 0.5);
118  }
119 
120  int mh = sliceableModel->getHeight();
121  if (minbin >= mh) minbin = mh - 1;
122  if (maxbin >= mh) maxbin = mh - 1;
123  if (minbin < 0) minbin = 0;
124  if (maxbin < 0) maxbin = 0;
125 
126  sv_samplerate_t sampleRate = sliceableModel->getSampleRate();
127 
128  sv_frame_t f0 = m_currentf0;
129  sv_frame_t f1 = m_currentf1;
130 
131  RealTime rt0 = RealTime::frame2RealTime(f0, sampleRate);
132  RealTime rt1 = RealTime::frame2RealTime(f1, sampleRate);
133 
134  range = int(f1 - f0 + 1);
135 
136  QString rtrangestr = QString("%1 s").arg((rt1 - rt0).toText().c_str());
137 
138  if (includeBinDescription) {
139 
140  int i0 = minbin - m_minbin;
141  int i1 = maxbin - m_minbin;
142 
143  float minvalue = 0.0;
144  if (in_range_for(m_values, i0)) minvalue = m_values[i0];
145 
146  float maxvalue = minvalue;
147  if (in_range_for(m_values, i1)) maxvalue = m_values[i1];
148 
149  if (minvalue > maxvalue) std::swap(minvalue, maxvalue);
150 
151  QString binstr;
152  if (maxbin != minbin) {
153  binstr = tr("%1 - %2").arg(minbin+1).arg(maxbin+1);
154  } else {
155  binstr = QString("%1").arg(minbin+1);
156  }
157 
158  QString valuestr;
159  if (maxvalue != minvalue) {
160  valuestr = tr("%1 - %2").arg(minvalue).arg(maxvalue);
161  } else {
162  valuestr = QString("%1").arg(minvalue);
163  }
164 
165  QString description = tr("Time:\t%1 - %2\nRange:\t%3 samples (%4)\nBin:\t%5\n%6 value:\t%7")
166  .arg(QString::fromStdString(rt0.toText(true)))
167  .arg(QString::fromStdString(rt1.toText(true)))
168  .arg(range)
169  .arg(rtrangestr)
170  .arg(binstr)
171  .arg(m_samplingMode == NearestSample ? tr("First") :
172  m_samplingMode == SampleMean ? tr("Mean") : tr("Peak"))
173  .arg(valuestr);
174 
175  return description;
176 
177  } else {
178 
179  QString description = tr("Time:\t%1 - %2\nRange:\t%3 samples (%4)")
180  .arg(QString::fromStdString(rt0.toText(true)))
181  .arg(QString::fromStdString(rt1.toText(true)))
182  .arg(range)
183  .arg(rtrangestr);
184 
185  return description;
186  }
187 }
188 
189 double
191 {
192  return getXForScalePoint(v, bin, m_minbin, m_maxbin);
193 }
194 
195 double
197  double p, double pmin, double pmax) const
198 {
199  double x = 0;
200 
201  int pw = v->getPaintWidth();
202  int origin = m_xorigins[v->getId()];
203  int w = pw - origin;
204  if (w < 1) w = 1;
205 
206  if (pmax <= pmin) {
207  pmax = pmin + 1.0;
208  }
209 
210  if (p < pmin) p = pmin;
211  if (p > pmax) p = pmax;
212 
213  if (m_binScale == LinearBins) {
214  x = (w * (p - pmin)) / (pmax - pmin);
215  } else {
216 
217  if (m_binScale == InvertedLogBins) {
218  // stoopid
219  p = pmax - p;
220  }
221 
222  // The 0.8 here is an awkward compromise. Our x-coord is
223  // proportional to log of bin number, with the x-coord "of a
224  // bin" being that of the left edge of the bin range. We can't
225  // start counting bins from 0, as that would give us x = -Inf
226  // and hide the first bin entirely. But if we start from 1, we
227  // are giving a lot of space to the first bin, which in most
228  // display modes won't be used because the "point" location
229  // for that bin is in the middle of it. Yet in some modes
230  // we'll still want it. A compromise is to count our first bin
231  // as "a bit less than 1", so that most of it is visible but a
232  // bit is tactfully cropped at the left edge so it doesn't
233  // take up so much space.
234  const double origin = 0.8;
235 
236  // sometimes we are called with a pmin/pmax range that begins
237  // before 0: in that situation, we shift everything along by
238  // the difference between 0 and pmin before doing any other
239  // calculations
240  double reqdshift = 0.0;
241  if (pmin < 0) reqdshift = -pmin;
242 
243  double pminlog = log10(pmin + reqdshift + origin);
244  double pmaxlog = log10(pmax + reqdshift + origin);
245  double plog = log10(p + reqdshift + origin);
246  x = (w * (plog - pminlog)) / (pmaxlog - pminlog);
247  /*
248  cerr << "getXForScalePoint(" << p << "): pmin = " << pmin
249  << ", pmax = " << pmax << ", w = " << w
250  << ", reqdshift = " << reqdshift
251  << ", pminlog = " << pminlog << ", pmaxlog = " << pmaxlog
252  << ", plog = " << plog
253  << " -> x = " << x << endl;
254  */
255  if (m_binScale == InvertedLogBins) {
256  // still stoopid
257  x = w - x;
258  }
259  }
260 
261  return x + origin;
262 }
263 
264 double
266 {
267  return getScalePointForX(v, x, m_minbin, m_maxbin);
268 }
269 
270 double
272  double x, double pmin, double pmax) const
273 {
274  double p = 0;
275 
276  int pw = v->getPaintWidth();
277  int origin = m_xorigins[v->getId()];
278 
279  int w = pw - origin;
280  if (w < 1) w = 1;
281 
282  x = x - origin;
283  if (x < 0) x = 0;
284 
285  double eps = 1e-10;
286 
287  if (pmax <= pmin) {
288  pmax = pmin + 1.0;
289  }
290 
291  if (m_binScale == LinearBins) {
292  p = pmin + eps + (x * (pmax - pmin)) / w;
293  } else {
294 
295  if (m_binScale == InvertedLogBins) {
296  x = w - x;
297  }
298 
299  // See comments in getXForScalePoint
300 
301  const double origin = 0.8;
302  double reqdshift = 0.0;
303  if (pmin < 0) reqdshift = -pmin;
304 
305  double pminlog = log10(pmin + reqdshift + origin);
306  double pmaxlog = log10(pmax + reqdshift + origin);
307 
308  double plog = pminlog + eps + (x * (pmaxlog - pminlog)) / w;
309  p = pow(10.0, plog) - reqdshift - origin;
310 
311  if (m_binScale == InvertedLogBins) {
312  p = pmax - p;
313  }
314  }
315 
316  return p;
317 }
318 
319 double
320 SliceLayer::getYForValue(const LayerGeometryProvider *v, double value, double &norm) const
321 {
322  norm = 0.0;
323 
324  if (m_yorigins.find(v->getId()) == m_yorigins.end()) return 0;
325 
326  value *= m_gain;
327 
328  int yorigin = m_yorigins[v->getId()];
329  int h = m_heights[v->getId()];
330  double thresh = getThresholdDb();
331 
332  double y = 0.0;
333 
334  if (h <= 0) return y;
335 
336  switch (m_energyScale) {
337 
338  case dBScale:
339  {
340  double db = thresh;
341  if (value > 0.0) db = 10.0 * log10(fabs(value));
342  if (db < thresh) db = thresh;
343  norm = (db - thresh) / -thresh;
344  y = yorigin - (double(h) * norm);
345  break;
346  }
347 
348  case MeterScale:
349  y = AudioLevel::multiplier_to_preview(value, h);
350  norm = double(y) / double(h);
351  y = yorigin - y;
352  break;
353 
354  case AbsoluteScale:
355  value = fabs(value);
356 #if (__GNUC__ >= 7)
357  __attribute__ ((fallthrough));
358 #endif
359 
360  case LinearScale:
361  default:
362  norm = (value - m_threshold);
363  if (norm < 0) norm = 0;
364  y = yorigin - (double(h) * norm);
365  break;
366  }
367 
368  return y;
369 }
370 
371 double
373 {
374  double value = 0.0;
375 
376  if (m_yorigins.find(v->getId()) == m_yorigins.end()) return value;
377 
378  int yorigin = m_yorigins[v->getId()];
379  int h = m_heights[v->getId()];
380  double thresh = getThresholdDb();
381 
382  if (h <= 0) return value;
383 
384  y = yorigin - y;
385 
386  switch (m_energyScale) {
387 
388  case dBScale:
389  {
390  double db = ((y / h) * -thresh) + thresh;
391  value = pow(10.0, db/10.0);
392  break;
393  }
394 
395  case MeterScale:
396  value = AudioLevel::preview_to_multiplier(int(lrint(y)), h);
397  break;
398 
399  case LinearScale:
400  case AbsoluteScale:
401  default:
402  value = y / h + m_threshold;
403  }
404 
405  return value / m_gain;
406 }
407 
408 void
409 SliceLayer::paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const
410 {
411  auto sliceableModel =
412  ModelById::getAs<DenseThreeDimensionalModel>(m_sliceableModel);
413  if (!sliceableModel ||
414  !sliceableModel->isOK() ||
415  !sliceableModel->isReady()) return;
416 
417  Profiler profiler("SliceLayer::paint()");
418 
419  paint.save();
420  paint.setRenderHint(QPainter::Antialiasing, true);
421  paint.setBrush(Qt::NoBrush);
422 
424  if (!m_scalePoints.empty()) {
425  paint.setPen(QColor(240, 240, 240));
426  int ratio = int(round(double(v->getPaintHeight()) /
428  for (int i = 0; i < (int)m_scalePoints.size(); ++i) {
429  paint.drawLine(0, m_scalePoints[i] * ratio,
430  rect.width(), m_scalePoints[i] * ratio);
431  }
432  }
433  }
434 
435  int mh = sliceableModel->getHeight();
436  int bin0 = 0;
437  if (m_maxbin > m_minbin) {
438  mh = m_maxbin - m_minbin;
439  bin0 = m_minbin;
440  }
441 
442  if (m_plotStyle == PlotBlocks) {
443  // Must use actual zero-width pen, too slow otherwise
444  paint.setPen(QPen(getBaseQColor(), 0));
445  } else {
446  // Similarly, if there are very many bins here, we use a
447  // thinner pen
448  QPen pen;
449  if (mh < 10000) {
450  pen = v->scalePen(QPen(getBaseQColor(), 0.8));
451  } else {
452  pen = QPen(getBaseQColor(), 1);
453  }
454  paint.setPen(pen);
455  }
456 
457  int xorigin = getVerticalScaleWidth(v, true, paint) + 1;
458  m_xorigins[v->getId()] = xorigin; // for use in getFeatureDescription
459 
460  int yorigin = v->getPaintHeight() - getHorizontalScaleHeight(v, paint) -
461  paint.fontMetrics().height();
462  int h = yorigin - paint.fontMetrics().height() - 8;
463 
464  m_yorigins[v->getId()] = yorigin; // for getYForValue etc
465  m_heights[v->getId()] = h;
466 
467  if (h <= 0) return;
468 
469  QPainterPath path;
470 
471  int divisor = 0;
472 
473  m_values.clear();
474  for (int bin = 0; bin < mh; ++bin) {
475  m_values.push_back(0.0);
476  }
477 
478  sv_frame_t f0 = v->getCentreFrame();
479  int f0x = v->getXForFrame(f0);
480  f0 = v->getFrameForX(f0x);
481  sv_frame_t f1 = v->getFrameForX(f0x + 1);
482  if (f1 > f0) --f1;
483 
484 // cerr << "centre frame " << v->getCentreFrame() << ", x " << f0x << ", f0 " << f0 << ", f1 " << f1 << endl;
485 
486  int res = sliceableModel->getResolution();
487  int col0 = int(f0 / res);
488  int col1 = col0;
489  if (m_samplingMode != NearestSample) col1 = int(f1 / res);
490  f0 = col0 * res;
491  f1 = (col1 + 1) * res - 1;
492 
493 // cerr << "resolution " << res << ", col0 " << col0 << ", col1 " << col1 << ", f0 " << f0 << ", f1 " << f1 << endl;
494 // cerr << "mh = " << mh << endl;
495 
496  m_currentf0 = f0;
497  m_currentf1 = f1;
498 
499  BiasCurve curve;
500  getBiasCurve(curve);
501  int cs = int(curve.size());
502 
503  for (int col = col0; col <= col1; ++col) {
504  DenseThreeDimensionalModel::Column column =
505  sliceableModel->getColumn(col);
506  for (int bin = 0; bin < mh; ++bin) {
507  float value = column[bin0 + bin];
508  if (bin < cs) value *= curve[bin];
509  if (m_samplingMode == SamplePeak) {
510  if (value > m_values[bin]) m_values[bin] = value;
511  } else {
512  m_values[bin] += value;
513  }
514  }
515  ++divisor;
516  }
517 
518  float max = 0.0;
519  for (int bin = 0; bin < mh; ++bin) {
520  if (m_samplingMode == SampleMean && divisor > 0) {
521  m_values[bin] /= float(divisor);
522  }
523  if (m_values[bin] > max) max = m_values[bin];
524  }
525  if (max != 0.0 && m_normalize) {
526  for (int bin = 0; bin < mh; ++bin) {
527  m_values[bin] /= max;
528  }
529  }
530 
532 
533  double ytop = 0, ybottom = 0;
534  bool firstBinOfPixel = true;
535 
536  QColor prevColour = v->getBackground();
537  double prevYtop = 0;
538 
539  double xleft = -1, xmiddle = -1, xright = -1;
540  double prevXmiddle = 0;
541 
542  for (int bin = 0; bin < mh; ++bin) {
543 
545  if (xright >= 0) xleft = xright; // previous value of
546  else xleft = getXForBin(v, bin0 + bin);
547  xmiddle = getXForBin(v, bin0 + bin + 0.5);
548  xright = getXForBin(v, bin0 + bin + 1);
549  } else {
550  if (xright >= 0) xleft = xright; // previous value of
551  else xleft = getXForBin(v, bin0 + bin - 0.5);
552  xmiddle = getXForBin(v, bin0 + bin);
553  xright = getXForBin(v, bin0 + bin + 0.5);
554  }
555 
556  double value = m_values[bin];
557  double norm = 0.0;
558  double y = getYForValue(v, value, norm);
559 
560  if (y < ytop || firstBinOfPixel) {
561  ytop = y;
562  }
563  if (y > ybottom || firstBinOfPixel) {
564  ybottom = y;
565  }
566 
567  if (int(xright) != int(xleft) || bin+1 == mh) {
568 
569  if (m_plotStyle == PlotLines) {
570 
571  if (bin == 0) {
572  path.moveTo(xmiddle, y);
573  } else {
574  if (ytop != ybottom) {
575  path.lineTo(xmiddle, ybottom);
576  path.lineTo(xmiddle, ytop);
577  path.moveTo(xmiddle, ybottom);
578  } else {
579  path.lineTo(xmiddle, ytop);
580  }
581  }
582 
583  } else if (m_plotStyle == PlotSteps) {
584 
585  if (bin == 0) {
586  path.moveTo(xleft, y);
587  } else {
588  path.lineTo(xleft, ytop);
589  }
590  path.lineTo(xright, ytop);
591 
592  } else if (m_plotStyle == PlotBlocks) {
593 
594  // work in pixel coords here, as we don't want the
595  // vertical edges to be antialiased
596 
597  path.moveTo(QPoint(int(xleft), int(yorigin)));
598  path.lineTo(QPoint(int(xleft), int(ytop)));
599  path.lineTo(QPoint(int(xright), int(ytop)));
600  path.lineTo(QPoint(int(xright), int(yorigin)));
601  path.lineTo(QPoint(int(xleft), int(yorigin)));
602 
603  } else if (m_plotStyle == PlotFilledBlocks) {
604 
605  QColor c = mapper.map(norm);
606  paint.setPen(Qt::NoPen);
607 
608  // work in pixel coords here, as we don't want the
609  // vertical edges to be antialiased
610 
611  if (xright > xleft + 1) {
612 
613  QVector<QPoint> pp;
614 
615  if (bin > 0) {
616  paint.setBrush(prevColour);
617  pp.clear();
618  pp << QPoint(int(prevXmiddle), int(yorigin));
619  pp << QPoint(int(prevXmiddle), int(prevYtop));
620  pp << QPoint(int((xmiddle + prevXmiddle) / 2),
621  int((ytop + prevYtop) / 2));
622  pp << QPoint(int((xmiddle + prevXmiddle) / 2),
623  int(yorigin));
624  paint.drawConvexPolygon(QPolygon(pp));
625 
626  paint.setBrush(c);
627  pp.clear();
628  pp << QPoint(int((xmiddle + prevXmiddle) / 2),
629  int(yorigin));
630  pp << QPoint(int((xmiddle + prevXmiddle) / 2),
631  int((ytop + prevYtop) / 2));
632  pp << QPoint(int(xmiddle), int(ytop));
633  pp << QPoint(int(xmiddle), int(yorigin));
634  paint.drawConvexPolygon(QPolygon(pp));
635  }
636 
637  prevColour = c;
638  prevYtop = ytop;
639 
640  } else {
641 
642  paint.fillRect(QRect(int(xleft), int(ytop),
643  int(xright) - int(xleft),
644  int(yorigin) - int(ytop)),
645  c);
646  }
647 
648  prevXmiddle = xmiddle;
649  }
650 
651  firstBinOfPixel = true;
652 
653  } else {
654  firstBinOfPixel = false;
655  }
656  }
657 
658  if (m_plotStyle != PlotFilledBlocks) {
659  paint.drawPath(path);
660  }
661  paint.restore();
662 }
663 
664 int
666 {
667  // Qt 5.13 deprecates QFontMetrics::width(), but its suggested
668  // replacement (horizontalAdvance) was only added in Qt 5.11
669  // which is too new for us
670 #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
671 
672  int width;
674  width = std::max(paint.fontMetrics().width("0.0") + 13,
675  paint.fontMetrics().width("x10-10"));
676  } else {
677  width = std::max(paint.fontMetrics().width(tr("0dB")),
678  paint.fontMetrics().width(tr("-Inf"))) + 13;
679  }
680  return width;
681 }
682 
683 void
684 SliceLayer::paintVerticalScale(LayerGeometryProvider *v, bool, QPainter &paint, QRect rect) const
685 {
686  double thresh = m_threshold;
688  thresh = AudioLevel::dB_to_multiplier(getThresholdDb());
689  }
690 
691 // int h = (rect.height() * 3) / 4;
692 // int y = (rect.height() / 2) - (h / 2);
693 
694  int yorigin = v->getPaintHeight() - getHorizontalScaleHeight(v, paint) -
695  paint.fontMetrics().height();
696  int h = yorigin - paint.fontMetrics().height() - 8;
697  if (h < 0) return;
698 
699  QRect actual(rect.x(), rect.y() + yorigin - h, rect.width(), h);
700 
701  int mult = 1;
702 
704  (paint, actual, thresh, 1.0 / m_gain,
706  mult,
707  const_cast<std::vector<int> *>(&m_scalePoints));
708 
709  // Ugly hack (but then everything about this scale drawing is a
710  // bit ugly). In pixel-doubling hi-dpi scenarios, the scale is
711  // painted at pixel-doubled resolution but we do explicit
712  // pixel-doubling ourselves when painting the layer content. We
713  // make a note of this here so that we can compare with the
714  // equivalent dimension in the paint method when deciding where to
715  // place scale continuation lines.
717 
718  if (mult != 1 && mult != 0) {
719  int log = int(lrint(log10(mult)));
720  QString a = tr("x10");
721  QString b = QString("%1").arg(-log);
722  paint.drawText(3, 8 + paint.fontMetrics().ascent(), a);
723  paint.drawText(3 + paint.fontMetrics().width(a),
724  3 + paint.fontMetrics().ascent(), b);
725  }
726 }
727 
728 bool
730 {
731  if (usesSolidColour()) {
733  return mapper.hasLightBackground();
734  } else {
736  }
737 }
738 
739 Layer::PropertyList
741 {
742  PropertyList list = SingleColourLayer::getProperties();
743  list.push_back("Bin Scale");
744  list.push_back("Plot Type");
745  list.push_back("Scale");
746  list.push_back("Normalize");
747  list.push_back("Threshold");
748  list.push_back("Gain");
749 
750  return list;
751 }
752 
753 QString
754 SliceLayer::getPropertyLabel(const PropertyName &name) const
755 {
756  if (name == "Plot Type") return tr("Plot Type");
757  if (name == "Scale") return tr("Scale");
758  if (name == "Normalize") return tr("Normalize");
759  if (name == "Threshold") return tr("Threshold");
760  if (name == "Gain") return tr("Gain");
761  if (name == "Sampling Mode") return tr("Sampling Mode");
762  if (name == "Bin Scale") return tr("Bin Scale");
764 }
765 
766 QString
767 SliceLayer::getPropertyIconName(const PropertyName &name) const
768 {
769  if (name == "Normalize") return "normalise";
770  return "";
771 }
772 
773 Layer::PropertyType
774 SliceLayer::getPropertyType(const PropertyName &name) const
775 {
776  if (name == "Gain") return RangeProperty;
777  if (name == "Normalize") return ToggleProperty;
778  if (name == "Threshold") return RangeProperty;
779  if (name == "Plot Type") return ValueProperty;
780  if (name == "Scale") return ValueProperty;
781  if (name == "Sampling Mode") return ValueProperty;
782  if (name == "Bin Scale") return ValueProperty;
783  if (name == "Colour" && usesSolidColour()) return ColourMapProperty;
785 }
786 
787 QString
788 SliceLayer::getPropertyGroupName(const PropertyName &name) const
789 {
790  if (name == "Scale" ||
791  name == "Normalize" ||
792  name == "Sampling Mode" ||
793  name == "Threshold" ||
794  name == "Gain") return tr("Scale");
795  if (name == "Plot Type" ||
796  name == "Bin Scale") return tr("Bins");
798 }
799 
800 int
801 SliceLayer::getPropertyRangeAndValue(const PropertyName &name,
802  int *min, int *max, int *deflt) const
803 {
804  int val = 0;
805 
806  int garbage0, garbage1, garbage2;
807  if (!min) min = &garbage0;
808  if (!max) max = &garbage1;
809  if (!deflt) deflt = &garbage2;
810 
811  if (name == "Gain") {
812 
813  *min = -50;
814  *max = 50;
815  *deflt = 0;
816 
817 // cerr << "gain is " << m_gain << ", mode is " << m_samplingMode << endl;
818 
819  val = int(lrint(log10(m_gain) * 20.0));
820  if (val < *min) val = *min;
821  if (val > *max) val = *max;
822 
823  } else if (name == "Threshold") {
824 
825  *min = -80;
826  *max = 0;
827 
828  *deflt = int(lrint(AudioLevel::multiplier_to_dB(m_initialThreshold)));
829  if (*deflt < *min) *deflt = *min;
830  if (*deflt > *max) *deflt = *max;
831 
832  val = int(lrint(AudioLevel::multiplier_to_dB(m_threshold)));
833  if (val < *min) val = *min;
834  if (val > *max) val = *max;
835 
836  } else if (name == "Normalize") {
837 
838  val = (m_normalize ? 1 : 0);
839  *deflt = 0;
840 
841  } else if (name == "Colour" && usesSolidColour()) {
842 
843  *min = 0;
844  *max = ColourMapper::getColourMapCount() - 1;
845  *deflt = int(ColourMapper::Ice);
846 
847  val = m_colourMap;
848 
849  } else if (name == "Scale") {
850 
851  *min = 0;
852  *max = 3;
853  *deflt = (int)dBScale;
854 
855  val = (int)m_energyScale;
856 
857  } else if (name == "Sampling Mode") {
858 
859  *min = 0;
860  *max = 2;
861  *deflt = (int)SampleMean;
862 
863  val = (int)m_samplingMode;
864 
865  } else if (name == "Plot Type") {
866 
867  *min = 0;
868  *max = 3;
869  *deflt = (int)PlotSteps;
870 
871  val = (int)m_plotStyle;
872 
873  } else if (name == "Bin Scale") {
874 
875  *min = 0;
876  *max = 2;
877  *deflt = (int)LinearBins;
878 // *max = 1; // I don't think we really do want to offer inverted log
879 
880  val = (int)m_binScale;
881 
882  } else {
883  val = SingleColourLayer::getPropertyRangeAndValue(name, min, max, deflt);
884  }
885 
886  return val;
887 }
888 
889 QString
890 SliceLayer::getPropertyValueLabel(const PropertyName &name,
891  int value) const
892 {
893  if (name == "Colour" && usesSolidColour()) {
894  return ColourMapper::getColourMapLabel(value);
895  }
896  if (name == "Scale") {
897  switch (value) {
898  default:
899  case 0: return tr("Linear");
900  case 1: return tr("Meter");
901  case 2: return tr("Log");
902  case 3: return tr("Absolute");
903  }
904  }
905  if (name == "Sampling Mode") {
906  switch (value) {
907  default:
908  case 0: return tr("Any");
909  case 1: return tr("Mean");
910  case 2: return tr("Peak");
911  }
912  }
913  if (name == "Plot Type") {
914  switch (value) {
915  default:
916  case 0: return tr("Lines");
917  case 1: return tr("Steps");
918  case 2: return tr("Blocks");
919  case 3: return tr("Colours");
920  }
921  }
922  if (name == "Bin Scale") {
923  switch (value) {
924  default:
925  case 0: return tr("Linear");
926  case 1: return tr("Log");
927  case 2: return tr("Rev Log");
928  }
929  }
930  return SingleColourLayer::getPropertyValueLabel(name, value);
931 }
932 
933 RangeMapper *
934 SliceLayer::getNewPropertyRangeMapper(const PropertyName &name) const
935 {
936  if (name == "Gain") {
937  return new LinearRangeMapper(-50, 50, -25, 25, tr("dB"));
938  }
939  if (name == "Threshold") {
940  return new LinearRangeMapper(-80, 0, -80, 0, tr("dB"));
941  }
943 }
944 
945 void
946 SliceLayer::setProperty(const PropertyName &name, int value)
947 {
948  if (name == "Gain") {
949  setGain(powf(10, float(value)/20.0f));
950  } else if (name == "Threshold") {
951  if (value == -80) setThreshold(0.0f);
952  else setThreshold(float(AudioLevel::dB_to_multiplier(value)));
953  } else if (name == "Colour" && usesSolidColour()) {
954  setFillColourMap(value);
955  } else if (name == "Scale") {
956  switch (value) {
957  default:
958  case 0: setEnergyScale(LinearScale); break;
959  case 1: setEnergyScale(MeterScale); break;
960  case 2: setEnergyScale(dBScale); break;
961  case 3: setEnergyScale(AbsoluteScale); break;
962  }
963  } else if (name == "Plot Type") {
964  setPlotStyle(PlotStyle(value));
965  } else if (name == "Sampling Mode") {
966  switch (value) {
967  default:
968  case 0: setSamplingMode(NearestSample); break;
969  case 1: setSamplingMode(SampleMean); break;
970  case 2: setSamplingMode(SamplePeak); break;
971  }
972  } else if (name == "Bin Scale") {
973  switch (value) {
974  default:
975  case 0: setBinScale(LinearBins); break;
976  case 1: setBinScale(LogBins); break;
977  case 2: setBinScale(InvertedLogBins); break;
978  }
979  } else if (name == "Normalize") {
980  setNormalize(value ? true : false);
981  } else {
982  SingleColourLayer::setProperty(name, value);
983  }
984 }
985 
986 void
988 {
989  if (m_colourMap == map) return;
990  m_colourMap = map;
991  emit layerParametersChanged();
992 }
993 
994 void
996 {
997  if (m_energyScale == scale) return;
998  m_energyScale = scale;
999  emit layerParametersChanged();
1000 }
1001 
1002 void
1004 {
1005  if (m_samplingMode == mode) return;
1006  m_samplingMode = mode;
1007  emit layerParametersChanged();
1008 }
1009 
1010 void
1012 {
1013  if (m_plotStyle == style) return;
1014  bool colourTypeChanged = (style == PlotFilledBlocks ||
1016  m_plotStyle = style;
1017  if (colourTypeChanged) {
1019  }
1020  emit layerParametersChanged();
1021 }
1022 
1023 void
1025 {
1026  if (m_binScale == scale) return;
1027  m_binScale = scale;
1028  emit layerParametersChanged();
1029 }
1030 
1031 void
1033 {
1034  if (m_normalize == n) return;
1035  m_normalize = n;
1036  emit layerParametersChanged();
1037 }
1038 
1039 void
1041 {
1042  if (m_threshold == thresh) return;
1043  m_threshold = thresh;
1044  emit layerParametersChanged();
1045 }
1046 
1047 void
1049 {
1050  if (m_gain == gain) return;
1051  m_gain = gain;
1052  emit layerParametersChanged();
1053 }
1054 
1055 float
1057 {
1058  if (m_threshold == 0.0) return -80.f;
1059  float db = float(AudioLevel::multiplier_to_dB(m_threshold));
1060  return db;
1061 }
1062 
1063 int
1064 SliceLayer::getDefaultColourHint(bool darkbg, bool &impose)
1065 {
1066  impose = false;
1068  (QString(darkbg ? "Bright Blue" : "Blue"));
1069 }
1070 
1071 void
1072 SliceLayer::toXml(QTextStream &stream,
1073  QString indent, QString extraAttributes) const
1074 {
1075  QString s;
1076 
1077  s += QString("energyScale=\"%1\" "
1078  "samplingMode=\"%2\" "
1079  "plotStyle=\"%3\" "
1080  "binScale=\"%4\" "
1081  "gain=\"%5\" "
1082  "threshold=\"%6\" "
1083  "normalize=\"%7\" %8 ")
1084  .arg(m_energyScale)
1085  .arg(m_samplingMode)
1086  .arg(m_plotStyle)
1087  .arg(m_binScale)
1088  .arg(m_gain)
1089  .arg(m_threshold)
1090  .arg(m_normalize ? "true" : "false")
1091  .arg(QString("minbin=\"%1\" "
1092  "maxbin=\"%2\"")
1093  .arg(m_minbin)
1094  .arg(m_maxbin));
1095 
1096  // New-style colour map attribute, by string id rather than by
1097  // number
1098 
1099  s += QString("fillColourMap=\"%1\" ")
1101 
1102  // Old-style colour map attribute
1103 
1104  s += QString("colourScheme=\"%1\" ")
1106 
1107  SingleColourLayer::toXml(stream, indent, extraAttributes + " " + s);
1108 }
1109 
1110 void
1111 SliceLayer::setProperties(const QXmlAttributes &attributes)
1112 {
1113  bool ok = false;
1114 
1116 
1117  EnergyScale scale = (EnergyScale)
1118  attributes.value("energyScale").toInt(&ok);
1119  if (ok) setEnergyScale(scale);
1120 
1121  SamplingMode mode = (SamplingMode)
1122  attributes.value("samplingMode").toInt(&ok);
1123  if (ok) setSamplingMode(mode);
1124 
1125  QString colourMapId = attributes.value("fillColourMap");
1126  int colourMap = ColourMapper::getColourMapById(colourMapId);
1127  if (colourMap >= 0) {
1128  setFillColourMap(colourMap);
1129  } else {
1130  colourMap = attributes.value("colourScheme").toInt(&ok);
1131  if (ok && colourMap < ColourMapper::getColourMapCount()) {
1132  setFillColourMap(colourMap);
1133  }
1134  }
1135 
1136  PlotStyle s = (PlotStyle)
1137  attributes.value("plotStyle").toInt(&ok);
1138  if (ok) setPlotStyle(s);
1139 
1140  BinScale b = (BinScale)
1141  attributes.value("binScale").toInt(&ok);
1142  if (ok) setBinScale(b);
1143 
1144  float gain = attributes.value("gain").toFloat(&ok);
1145  if (ok) setGain(gain);
1146 
1147  float threshold = attributes.value("threshold").toFloat(&ok);
1148  if (ok) setThreshold(threshold);
1149 
1150  bool normalize = (attributes.value("normalize").trimmed() == "true");
1151  setNormalize(normalize);
1152 
1153  bool alsoOk = false;
1154 
1155  float min = attributes.value("minbin").toFloat(&ok);
1156  float max = attributes.value("maxbin").toFloat(&alsoOk);
1157  if (ok && alsoOk) setDisplayExtents(min, max);
1158 }
1159 
1160 bool
1161 SliceLayer::getValueExtents(double &min, double &max, bool &logarithmic,
1162  QString &unit) const
1163 {
1164  auto sliceableModel =
1165  ModelById::getAs<DenseThreeDimensionalModel>(m_sliceableModel);
1166  if (!sliceableModel) return false;
1167 
1168  min = 0;
1169  max = double(sliceableModel->getHeight());
1170 
1171  logarithmic = (m_binScale == BinScale::LogBins);
1172  unit = "";
1173 
1174  return true;
1175 }
1176 
1177 bool
1178 SliceLayer::getDisplayExtents(double &min, double &max) const
1179 {
1180  auto sliceableModel =
1181  ModelById::getAs<DenseThreeDimensionalModel>(m_sliceableModel);
1182  if (!sliceableModel) return false;
1183 
1184  double hmax = double(sliceableModel->getHeight());
1185 
1186  min = m_minbin;
1187  max = m_maxbin;
1188  if (max <= min) {
1189  min = 0;
1190  max = hmax;
1191  }
1192  if (min < 0) min = 0;
1193  if (max > hmax) max = hmax;
1194 
1195  return true;
1196 }
1197 
1198 bool
1199 SliceLayer::setDisplayExtents(double min, double max)
1200 {
1201  auto sliceableModel =
1202  ModelById::getAs<DenseThreeDimensionalModel>(m_sliceableModel);
1203  if (!sliceableModel) return false;
1204 
1205  m_minbin = int(lrint(min));
1206  m_maxbin = int(lrint(max));
1207 
1208  if (m_minbin < 0) {
1209  m_minbin = 0;
1210  }
1211  if (m_maxbin < 0) {
1212  m_maxbin = 0;
1213  }
1214  if (m_minbin > sliceableModel->getHeight()) {
1215  m_minbin = sliceableModel->getHeight();
1216  }
1217  if (m_maxbin > sliceableModel->getHeight()) {
1218  m_maxbin = sliceableModel->getHeight();
1219  }
1220  if (m_maxbin < m_minbin) {
1221  m_maxbin = m_minbin;
1222  }
1223 
1224  emit layerParametersChanged();
1225  return true;
1226 }
1227 
1228 int
1229 SliceLayer::getVerticalZoomSteps(int &defaultStep) const
1230 {
1231  auto sliceableModel =
1232  ModelById::getAs<DenseThreeDimensionalModel>(m_sliceableModel);
1233  if (!sliceableModel) return 0;
1234 
1235  defaultStep = 0;
1236  int h = sliceableModel->getHeight();
1237  return h;
1238 }
1239 
1240 int
1242 {
1243  auto sliceableModel =
1244  ModelById::getAs<DenseThreeDimensionalModel>(m_sliceableModel);
1245  if (!sliceableModel) return 0;
1246 
1247  double min, max;
1248  getDisplayExtents(min, max);
1249  return sliceableModel->getHeight() - int(lrint(max - min));
1250 }
1251 
1252 void
1254 {
1255  auto sliceableModel =
1256  ModelById::getAs<DenseThreeDimensionalModel>(m_sliceableModel);
1257  if (!sliceableModel) return;
1258 
1259 // SVDEBUG << "SliceLayer::setVerticalZoomStep(" <<step <<"): before: minbin = " << m_minbin << ", maxbin = " << m_maxbin << endl;
1260 
1261  int dist = sliceableModel->getHeight() - step;
1262  if (dist < 1) dist = 1;
1263  double centre = m_minbin + (m_maxbin - m_minbin) / 2.0;
1264  int minbin = int(lrint(centre - dist/2.0));
1265  int maxbin = minbin + dist;
1266  setDisplayExtents(minbin, maxbin);
1267 }
1268 
1269 RangeMapper *
1271 {
1272  auto sliceableModel =
1273  ModelById::getAs<DenseThreeDimensionalModel>(m_sliceableModel);
1274  if (!sliceableModel) return nullptr;
1275 
1276  return new LinearRangeMapper(0, sliceableModel->getHeight(),
1277  0, sliceableModel->getHeight(), "");
1278 }
1279 
1280 void
1282 {
1283  double bin0 = getBinForX(v, rect.x());
1284  double bin1 = getBinForX(v, rect.x() + rect.width());
1285 
1286  // ignore y for now...
1287 
1288  SVDEBUG << "SliceLayer::zoomToRegion: zooming to bin range "
1289  << bin0 << " -> " << bin1 << endl;
1290 
1291  setDisplayExtents(floor(bin0), ceil(bin1));
1292 }
1293 
EnergyScale m_energyScale
Definition: SliceLayer.h:177
sv_frame_t m_currentf0
Definition: SliceLayer.h:192
bool getDisplayExtents(double &min, double &max) const override
Return the minimum and maximum values within the visible area for the y axis of this layer...
void paintVerticalScale(LayerGeometryProvider *v, bool, QPainter &paint, QRect rect) const override
Definition: SliceLayer.cpp:684
void setFillColourMap(int)
Definition: SliceLayer.cpp:987
virtual QColor getBackground() const =0
int getCurrentVerticalZoomStep() const override
Get the current vertical zoom step.
PropertyList getProperties() const override
bool getValueExtents(double &min, double &max, bool &logarithmic, QString &unit) const override
Return the minimum and maximum values for the y axis of the model in this layer, as well as whether t...
int getVerticalZoomSteps(int &defaultStep) const override
Get the number of vertical zoom steps available for this layer.
bool setDisplayExtents(double min, double max) override
Set the displayed minimum and maximum values for the y axis to the given range, if supported...
sv_frame_t m_currentf1
Definition: SliceLayer.h:193
virtual double getValueForY(const LayerGeometryProvider *v, double y) const
Definition: SliceLayer.cpp:372
void modelReplaced()
bool hasLightBackground() const override
Return true if the layer currently has a dark colour on a light background, false if it has a light c...
Definition: SliceLayer.cpp:729
void setSliceableModel(ModelId model)
Definition: SliceLayer.cpp:59
void setVerticalZoomStep(int) override
Set the vertical zoom step.
PropertyType getPropertyType(const PropertyName &) const override
Definition: SliceLayer.cpp:774
float m_initialThreshold
Definition: SliceLayer.h:183
bool m_colourInverted
Definition: SliceLayer.h:176
ModelId m_sliceableModel
Definition: SliceLayer.h:173
void setSamplingMode(SamplingMode)
PropertyList getProperties() const override
Definition: SliceLayer.cpp:740
std::map< int, int > m_heights
Definition: SliceLayer.h:191
QString getPropertyIconName(const PropertyName &) const override
Definition: SliceLayer.cpp:767
void setThreshold(float)
virtual sv_frame_t getFrameForX(int x) const =0
Return the closest frame to the given pixel x-coordinate.
static int getColourMapCount()
Return the number of known colour maps.
int m_scalePaintHeight
Definition: SliceLayer.h:188
QString getFeatureDescription(LayerGeometryProvider *v, QPoint &) const override
Definition: SliceLayer.cpp:94
QString getPropertyValueLabel(const PropertyName &, int value) const override
Definition: SliceLayer.cpp:890
virtual QColor getBaseQColor() const
std::vector< int > m_scalePoints
Definition: SliceLayer.h:187
void layerParameterRangesChanged()
void toXml(QTextStream &stream, QString indent="", QString extraAttributes="") const override
virtual float getThresholdDb() const
static int getBackwardCompatibilityColourMap(int n)
Older versions of colour-handling code save and reload colour maps by numerical index and can&#39;t prope...
PlotStyle m_plotStyle
Definition: SliceLayer.h:179
Interface for classes that provide geometry information (such as size, start frame, and a large number of other properties) about the disposition of a layer.
static QString getColourMapLabel(int n)
Return a human-readable label for the colour map with the given index.
int m_colourMap
Definition: SliceLayer.h:175
BinScale m_binScale
Definition: SliceLayer.h:180
RangeMapper * getNewPropertyRangeMapper(const PropertyName &) const override
Definition: SliceLayer.cpp:934
static void paintVerticalLevelScale(QPainter &p, QRect rect, double minVal, double maxVal, Scale scale, int &multRtn, std::vector< int > *markCoordRtns=0)
int m_minbin
Definition: SliceLayer.h:185
int getColourIndex(QString name) const
Return the index of the colour with the given name, if found in the database.
bool hasLightBackground() const
Return true if the colour map is intended to be placed over a light background, false otherwise...
virtual int getHorizontalScaleHeight(LayerGeometryProvider *, QPainter &) const
Definition: Layer.h:173
QString getPropertyGroupName(const PropertyName &) const override
void setNormalize(bool n)
int m_maxbin
Definition: SliceLayer.h:186
static int getColourMapById(QString id)
Return the index for the colour map with the given machine-readable id string, or -1 if the id is not...
void layerParametersChanged()
bool m_normalize
Definition: SliceLayer.h:181
std::vector< float > m_values
Definition: SliceLayer.h:194
double getXForScalePoint(const LayerGeometryProvider *, double p, double pmin, double pmax) const
Convert a point such as a bin number into x-coord, given max & min.
Definition: SliceLayer.cpp:196
virtual double getYForValue(const LayerGeometryProvider *v, double value, double &norm) const
Definition: SliceLayer.cpp:320
int getPropertyRangeAndValue(const PropertyName &, int *min, int *max, int *deflt) const override
QColor map(double value) const
Map the given value to a colour.
A class for mapping intensity values onto various colour maps.
Definition: ColourMapper.h:27
virtual double getBinForX(const LayerGeometryProvider *, double x) const
Convert an x-coord into (possibly non-integral) bin. May be overridden.
Definition: SliceLayer.cpp:265
void connectSignals(ModelId)
Definition: Layer.cpp:49
virtual int getPaintHeight() const
void setPlotStyle(PlotStyle style)
QString getPropertyLabel(const PropertyName &) const override
Definition: SliceLayer.cpp:754
PropertyType getPropertyType(const PropertyName &) const override
RangeMapper * getNewVerticalZoomRangeMapper() const override
Create and return a range mapper for vertical zoom step values.
void setBinScale(BinScale scale)
double getScalePointForX(const LayerGeometryProvider *, double x, double pmin, double pmax) const
Convert an x-coord into a point such as a bin number, given max & min.
Definition: SliceLayer.cpp:271
float m_threshold
Definition: SliceLayer.h:182
void setGain(float gain)
virtual QPen scalePen(QPen pen) const =0
void setProperties(const QXmlAttributes &attributes) override
Set the particular properties of a layer (those specific to the subclass) from a set of XML attribute...
virtual void zoomToRegion(const LayerGeometryProvider *, QRect) override
Update the X and Y axis scales, where appropriate, to focus on the given rectangular region...
std::vector< float > BiasCurve
Definition: SliceLayer.h:150
virtual ViewManager * getViewManager() const =0
void setEnergyScale(EnergyScale)
Definition: SliceLayer.cpp:995
BinAlignment m_binAlignment
Definition: SliceLayer.h:174
int getDefaultColourHint(bool dark, bool &impose) override
bool shouldShowScaleGuides() const
Definition: ViewManager.h:234
void setProperty(const PropertyName &, int value) override
Definition: SliceLayer.cpp:946
QString getPropertyValueLabel(const PropertyName &, int value) const override
SamplingMode m_samplingMode
Definition: SliceLayer.h:178
virtual int getId() const =0
Retrieve the id of this object.
void paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const override
Paint the given rectangle of this layer onto the given view using the given painter, superimposing it on top of any existing material in that view.
Definition: SliceLayer.cpp:409
bool hasLightBackground() const override
Return true if the layer currently has a dark colour on a light background, false if it has a light c...
static QString getColourMapId(int n)
Return a machine-readable id string for the colour map with the given index.
void setProperties(const QXmlAttributes &) override
Set the particular properties of a layer (those specific to the subclass) from a set of XML attribute...
virtual QString getFeatureDescriptionAux(LayerGeometryProvider *v, QPoint &, bool includeBinDescription, int &minbin, int &maxbin, int &range) const
Definition: SliceLayer.cpp:101
virtual double getXForBin(const LayerGeometryProvider *, double bin) const
Convert a (possibly non-integral) bin into x-coord. May be overridden.
Definition: SliceLayer.cpp:190
virtual sv_frame_t getCentreFrame() const =0
Return the centre frame of the visible widget.
float m_gain
Definition: SliceLayer.h:184
void toXml(QTextStream &stream, QString indent="", QString extraAttributes="") const override
RangeMapper * getNewPropertyRangeMapper(const PropertyName &) const override
virtual void getBiasCurve(BiasCurve &) const
Definition: SliceLayer.h:151
std::map< int, int > m_xorigins
Definition: SliceLayer.h:189
void setProperty(const PropertyName &, int value) override
QString getPropertyGroupName(const PropertyName &) const override
Definition: SliceLayer.cpp:788
virtual int getXForFrame(sv_frame_t frame) const =0
Return the pixel x-coordinate corresponding to a given sample frame (which may be negative)...
int getPropertyRangeAndValue(const PropertyName &, int *min, int *max, int *deflt) const override
Definition: SliceLayer.cpp:801
virtual int getPaintWidth() const
int getVerticalScaleWidth(LayerGeometryProvider *v, bool, QPainter &) const override
Definition: SliceLayer.cpp:665
bool usesSolidColour() const
Definition: SliceLayer.h:90
QString getPropertyLabel(const PropertyName &) const override
static ColourDatabase * getInstance()
void sliceableModelReplaced(ModelId, ModelId)
Definition: SliceLayer.cpp:84
std::map< int, int > m_yorigins
Definition: SliceLayer.h:190