SpectrumLayer.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 "SpectrumLayer.h"
17 
18 #include "data/model/FFTModel.h"
19 #include "view/View.h"
20 #include "base/AudioLevel.h"
21 #include "base/Preferences.h"
22 #include "base/RangeMapper.h"
23 #include "base/Pitch.h"
24 #include "base/Strings.h"
25 
26 #include "ColourMapper.h"
27 #include "PaintAssistant.h"
28 #include "PianoScale.h"
30 
31 #include <QPainter>
32 #include <QTextStream>
33 
34 
36  m_channel(-1),
37  m_channelSet(false),
38  m_windowSize(4096),
39  m_windowType(HanningWindow),
40  m_windowHopLevel(3),
41  m_oversampling(1),
42  m_showPeaks(false),
43  m_newFFTNeeded(true),
44  m_freqOfMinBin(0.0)
45 {
47 
48  Preferences *prefs = Preferences::getInstance();
49  connect(prefs, SIGNAL(propertyChanged(PropertyContainer::PropertyName)),
50  this, SLOT(preferenceChanged(PropertyContainer::PropertyName)));
51  setWindowType(prefs->getWindowType());
52 
54 }
55 
57 {
58  ModelById::release(m_sliceableModel);
59 }
60 
61 void
62 SpectrumLayer::setModel(ModelId modelId)
63 {
64  auto newModel = ModelById::getAs<DenseTimeValueModel>(modelId);
65  if (!modelId.isNone() && !newModel) {
66  throw std::logic_error("Not a DenseTimeValueModel");
67  }
68 
69  if (m_originModel == modelId) return;
70  m_originModel = modelId;
71 
72  m_newFFTNeeded = true;
73 
75 }
76 
77 void
79 {
80  SVDEBUG << "SpectrumLayer::setChannel(" << channel << ") from " << m_channel << endl;
81 
82  m_channelSet = true;
83 
84  if (m_channel == channel) return;
85 
86  m_channel = channel;
87 
88  m_newFFTNeeded = true;
89 
91 }
92 
93 void
95 {
96  ModelById::release(m_sliceableModel);
97  m_sliceableModel = {};
98 
99  if (m_originModel.isNone()) {
100  return;
101  }
102 
103  int fftSize = getFFTSize();
104 
105  auto newFFT = std::make_shared<FFTModel>(m_originModel,
106  m_channel,
107  m_windowType,
108  m_windowSize,
110  fftSize);
111 
112  if (m_minbin == 0 && m_maxbin == 0) {
113  m_minbin = 1;
114  m_freqOfMinBin = double(m_minbin * newFFT->getSampleRate())
115  / getFFTSize();
116  m_maxbin = newFFT->getHeight();
117  }
118 
119  setSliceableModel(ModelById::add(newFFT));
120 
121  m_biasCurve.clear();
122  for (int i = 0; i < fftSize; ++i) {
123  // Scale by the window size, not the FFT size, because we
124  // don't want to scale down by all the zero bins
125  m_biasCurve.push_back(1.f / (float(m_windowSize)/2.f));
126  }
127 
128  m_newFFTNeeded = false;
129 }
130 
131 Layer::PropertyList
133 {
134  PropertyList list = SliceLayer::getProperties();
135  list.push_back("Window Size");
136  list.push_back("Window Increment");
137  list.push_back("Oversampling");
138  list.push_back("Show Peak Frequencies");
139  return list;
140 }
141 
142 QString
143 SpectrumLayer::getPropertyLabel(const PropertyName &name) const
144 {
145  if (name == "Window Size") return tr("Window Size");
146  if (name == "Window Increment") return tr("Window Overlap");
147  if (name == "Oversampling") return tr("Oversampling");
148  if (name == "Show Peak Frequencies") return tr("Show Peak Frequencies");
149  return SliceLayer::getPropertyLabel(name);
150 }
151 
152 QString
153 SpectrumLayer::getPropertyIconName(const PropertyName &name) const
154 {
155  if (name == "Show Peak Frequencies") return "show-peaks";
156  return SliceLayer::getPropertyIconName(name);
157 }
158 
159 Layer::PropertyType
160 SpectrumLayer::getPropertyType(const PropertyName &name) const
161 {
162  if (name == "Window Size") return ValueProperty;
163  if (name == "Window Increment") return ValueProperty;
164  if (name == "Oversampling") return ValueProperty;
165  if (name == "Show Peak Frequencies") return ToggleProperty;
166  return SliceLayer::getPropertyType(name);
167 }
168 
169 QString
170 SpectrumLayer::getPropertyGroupName(const PropertyName &name) const
171 {
172  if (name == "Window Size" ||
173  name == "Window Increment" ||
174  name == "Oversampling") return tr("Window");
175  if (name == "Show Peak Frequencies") return tr("Bins");
177 }
178 
179 int
181  int *min, int *max, int *deflt) const
182 {
183  int val = 0;
184 
185  int garbage0, garbage1, garbage2;
186  if (!min) min = &garbage0;
187  if (!max) max = &garbage1;
188  if (!deflt) deflt = &garbage2;
189 
190  if (name == "Window Size") {
191 
192  *min = 0;
193  *max = 15;
194  *deflt = 5;
195 
196  val = 0;
197  int ws = m_windowSize;
198  while (ws > 32) { ws >>= 1; val ++; }
199 
200  } else if (name == "Window Increment") {
201 
202  *min = 0;
203  *max = 5;
204  *deflt = 2;
205 
206  val = m_windowHopLevel;
207 
208  } else if (name == "Oversampling") {
209 
210  *min = 0;
211  *max = 3;
212  *deflt = 0;
213 
214  val = 0;
215  int ov = m_oversampling;
216  while (ov > 1) { ov >>= 1; val ++; }
217 
218  } else if (name == "Show Peak Frequencies") {
219 
220  return m_showPeaks ? 1 : 0;
221 
222  } else {
223 
224  val = SliceLayer::getPropertyRangeAndValue(name, min, max, deflt);
225  }
226 
227  return val;
228 }
229 
230 QString
231 SpectrumLayer::getPropertyValueLabel(const PropertyName &name,
232  int value) const
233 {
234  if (name == "Window Size") {
235  return QString("%1").arg(32 << value);
236  }
237  if (name == "Window Increment") {
238  switch (value) {
239  default:
240  case 0: return tr("None");
241  case 1: return tr("25 %");
242  case 2: return tr("50 %");
243  case 3: return tr("75 %");
244  case 4: return tr("87.5 %");
245  case 5: return tr("93.75 %");
246  }
247  }
248  if (name == "Oversampling") {
249  switch (value) {
250  default:
251  case 0: return tr("1x");
252  case 1: return tr("2x");
253  case 2: return tr("4x");
254  case 3: return tr("8x");
255  }
256  }
257  return SliceLayer::getPropertyValueLabel(name, value);
258 }
259 
260 RangeMapper *
261 SpectrumLayer::getNewPropertyRangeMapper(const PropertyName &name) const
262 {
264 }
265 
266 void
267 SpectrumLayer::setProperty(const PropertyName &name, int value)
268 {
269  if (name == "Window Size") {
270  setWindowSize(32 << value);
271  } else if (name == "Window Increment") {
272  setWindowHopLevel(value);
273  } else if (name == "Oversampling") {
274  setOversampling(1 << value);
275  } else if (name == "Show Peak Frequencies") {
276  setShowPeaks(value ? true : false);
277  } else {
278  SliceLayer::setProperty(name, value);
279  }
280 }
281 
282 void
284 {
285  if (m_windowSize == ws) return;
286 
287  SVDEBUG << "setWindowSize: from " << m_windowSize
288  << " to " << ws << ": updating min and max bins from "
289  << m_minbin << " and " << m_maxbin << " to ";
290 
291  int previousWs = m_windowSize;
292  m_windowSize = ws;
293 
295  m_maxbin = int(round((double(m_maxbin) / previousWs) * m_windowSize));
296 
297  int h = getFFTSize() / 2 + 1;
298  if (m_minbin > h) m_minbin = h;
299  if (m_maxbin > h) m_maxbin = h;
300 
301  SVDEBUG << m_minbin << " and " << m_maxbin << endl;
302 
303  m_newFFTNeeded = true;
304  emit layerParametersChanged();
305 }
306 
307 void
309 {
310  if (m_windowHopLevel == v) return;
311  m_windowHopLevel = v;
312  m_newFFTNeeded = true;
313  emit layerParametersChanged();
314 }
315 
316 void
318 {
319  if (m_windowType == w) return;
320  m_windowType = w;
321  m_newFFTNeeded = true;
322  emit layerParametersChanged();
323 }
324 
325 void
327 {
328  if (m_oversampling == oversampling) return;
329 
330  SVDEBUG << "setOversampling: from " << m_oversampling
331  << " to " << oversampling << ": updating min and max bins from "
332  << m_minbin << " and " << m_maxbin << " to ";
333 
334  int previousOversampling = m_oversampling;
335  m_oversampling = oversampling;
336 
338  m_maxbin = int(round((double(m_maxbin) / previousOversampling) *
339  m_oversampling));
340 
341  int h = getFFTSize() / 2 + 1;
342  if (m_minbin > h) m_minbin = h;
343  if (m_maxbin > h) m_maxbin = h;
344 
345  SVDEBUG << m_minbin << " and " << m_maxbin << endl;
346 
347  m_newFFTNeeded = true;
348  emit layerParametersChanged();
349 }
350 
351 int
353 {
354  return m_oversampling;
355 }
356 
357 void
359 {
360  if (m_showPeaks == show) return;
361  m_showPeaks = show;
362  emit layerParametersChanged();
363 }
364 
365 void
366 SpectrumLayer::preferenceChanged(PropertyContainer::PropertyName name)
367 {
368  if (name == "Window Type") {
369  auto type = Preferences::getInstance()->getWindowType();
370  SVDEBUG << "SpectrumLayer::preferenceChanged: Window type changed to "
371  << type << endl;
372  setWindowType(type);
373  return;
374  }
375 }
376 
377 bool
378 SpectrumLayer::setDisplayExtents(double min, double max)
379 {
380  bool result = SliceLayer::setDisplayExtents(min, max);
381  if (result) {
383  }
384  return result;
385 }
386 
387 double
389 {
390  auto sliceableModel = ModelById::getAs<DenseThreeDimensionalModel>
392  if (!sliceableModel) return 0;
393  double bin = (freq * getFFTSize()) / sliceableModel->getSampleRate();
394  return bin;
395 }
396 
397 double
399 {
400  auto sliceableModel = ModelById::getAs<DenseThreeDimensionalModel>
402  if (!sliceableModel) return 0;
403  double bin = getBinForFrequency(getFrequencyForX(v, x));
404  return bin;
405 }
406 
407 double
409 {
410  auto sliceableModel = ModelById::getAs<DenseThreeDimensionalModel>
412  if (!sliceableModel) return 0;
413 
414  double fmin = getFrequencyForBin(m_minbin);
415  double fmax = getFrequencyForBin(m_maxbin);
416 
417  double freq = getScalePointForX(v, x, fmin, fmax);
418  return freq;
419 }
420 
421 double
423 {
424  auto sliceableModel = ModelById::getAs<DenseThreeDimensionalModel>
426  if (!sliceableModel) return 0;
427  double freq = (bin * sliceableModel->getSampleRate()) / getFFTSize();
428  return freq;
429 }
430 
431 double
433 {
434  auto sliceableModel = ModelById::getAs<DenseThreeDimensionalModel>
436  if (!sliceableModel) return 0;
437  double x = getXForFrequency(v, getFrequencyForBin(bin));
438  return x;
439 }
440 
441 double
443 {
444  auto sliceableModel = ModelById::getAs<DenseThreeDimensionalModel>
446  if (!sliceableModel) return 0;
447 
448  double fmin = getFrequencyForBin(m_minbin);
449  double fmax = getFrequencyForBin(m_maxbin);
450  double x = getXForScalePoint(v, freq, fmin, fmax);
451 
452  return x;
453 }
454 
455 bool
457  double &value, QString &unit) const
458 {
459  value = getFrequencyForX(v, x);
460  unit = "Hz";
461  return true;
462 }
463 
464 bool
466  double &value, QString &unit) const
467 {
468  value = getValueForY(v, y);
469 
471 
472  if (value > 0.0) {
473  value = 10.0 * log10(value);
474  if (value < m_threshold) {
475  value = m_threshold;
476  }
477  } else {
478  value = m_threshold;
479  }
480 
481  unit = "dBV";
482 
483  } else {
484  unit = "V";
485  }
486 
487  return true;
488 }
489 
490 bool
492  double &diff, QString &unit) const
493 {
494  bool rv = SliceLayer::getYScaleDifference(v, y0, y1, diff, unit);
495  if (rv && (unit == "dBV")) unit = "dB";
496  return rv;
497 }
498 
499 
500 bool
502  QPoint cursorPos,
503  std::vector<QRect> &extents) const
504 {
505  QRect vertical(cursorPos.x(), cursorPos.y(), 1, v->getPaintHeight() - cursorPos.y());
506  extents.push_back(vertical);
507 
508  QRect horizontal(0, cursorPos.y(), v->getPaintWidth(), 12);
509  extents.push_back(horizontal);
510 
511  int hoffset = 2;
512  if (m_binScale == LogBins) hoffset = 13;
513 
514  int sw = getVerticalScaleWidth(v, false, paint);
515 
516  // Qt 5.13 deprecates QFontMetrics::width(), but its suggested
517  // replacement (horizontalAdvance) was only added in Qt 5.11
518  // which is too new for us
519 #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
520 
521  QRect value(sw, cursorPos.y() - paint.fontMetrics().ascent() - 2,
522  paint.fontMetrics().width("0.0000001 V") + 2,
523  paint.fontMetrics().height());
524  extents.push_back(value);
525 
526  QRect log(sw, cursorPos.y() + 2,
527  paint.fontMetrics().width("-80.000 dBV") + 2,
528  paint.fontMetrics().height());
529  extents.push_back(log);
530 
531  QRect freq(cursorPos.x(),
532  v->getPaintHeight() - paint.fontMetrics().height() - hoffset,
533  paint.fontMetrics().width("123456 Hz") + 2,
534  paint.fontMetrics().height());
535  extents.push_back(freq);
536 
537  int w(paint.fontMetrics().width("C#10+50c") + 2);
538  QRect pitch(cursorPos.x() - w,
539  v->getPaintHeight() - paint.fontMetrics().height() - hoffset,
540  w,
541  paint.fontMetrics().height());
542  extents.push_back(pitch);
543 
544  return true;
545 }
546 
547 void
549  QPoint cursorPos) const
550 {
551  auto sliceableModel = ModelById::getAs<DenseThreeDimensionalModel>
553  if (!sliceableModel) return;
554 
555  paint.save();
556  QFont fn = paint.font();
557  if (fn.pointSize() > 8) {
558  fn.setPointSize(fn.pointSize() - 1);
559  paint.setFont(fn);
560  }
561 
563  paint.setPen(mapper.getContrastingColour());
564 
565  int xorigin = m_xorigins[v->getId()];
566  paint.drawLine(xorigin, cursorPos.y(), v->getPaintWidth(), cursorPos.y());
567  paint.drawLine(cursorPos.x(), cursorPos.y(), cursorPos.x(), v->getPaintHeight());
568 
569  double fundamental = getFrequencyForX(v, cursorPos.x());
570 
571  int hoffset = getHorizontalScaleHeight(v, paint) +
572  2 * paint.fontMetrics().height();
573 
575  cursorPos.x() + 2,
576  v->getPaintHeight() - 2 - hoffset,
577  tr("%1 Hz").arg(fundamental),
579 
580  if (Pitch::isFrequencyInMidiRange(fundamental)) {
581  QString pitchLabel = Pitch::getPitchLabelForFrequency(fundamental);
583  cursorPos.x() -
584  paint.fontMetrics().width(pitchLabel) - 2,
585  v->getPaintHeight() - 2 - hoffset,
586  pitchLabel,
588  }
589 
590  double value = getValueForY(v, cursorPos.y());
591 
593  xorigin + 2,
594  cursorPos.y() - 2,
595  QString("%1 V").arg(value),
597 
598  if (value > m_threshold) {
599  double db = 10.0 * log10(value);
601  xorigin + 2,
602  cursorPos.y() + 2 +
603  paint.fontMetrics().ascent(),
604  QString("%1 dBV").arg(db),
606  }
607 
608  int harmonic = 2;
609 
610  while (harmonic < 100) {
611 
612  int hx = int(lrint(getXForFrequency(v, fundamental * harmonic)));
613 
614  if (hx < xorigin || hx > v->getPaintWidth()) break;
615 
616  int len = 7;
617 
618  if (harmonic % 2 == 0) {
619  if (harmonic % 4 == 0) {
620  len = 12;
621  } else {
622  len = 10;
623  }
624  }
625 
626  paint.drawLine(hx,
627  cursorPos.y(),
628  hx,
629  cursorPos.y() + len);
630 
631  ++harmonic;
632  }
633 
634  paint.restore();
635 }
636 
637 QString
639 {
640  auto sliceableModel = ModelById::getAs<DenseThreeDimensionalModel>
642  if (!sliceableModel) return "";
643 
644  int minbin = 0, maxbin = 0, range = 0;
645  QString genericDesc = SliceLayer::getFeatureDescriptionAux
646  (v, p, false, minbin, maxbin, range);
647 
648  if (genericDesc == "") return "";
649 
650  int i0 = minbin - m_minbin;
651  int i1 = maxbin - m_minbin;
652 
653  float minvalue = 0.0;
654  if (in_range_for(m_values, i0)) minvalue = m_values[i0];
655 
656  float maxvalue = minvalue;
657  if (in_range_for(m_values, i1)) maxvalue = m_values[i1];
658 
659  if (minvalue > maxvalue) std::swap(minvalue, maxvalue);
660 
661  QString binstr;
662  QString hzstr;
663  int minfreq = int(lrint((minbin * sliceableModel->getSampleRate()) /
664  getFFTSize()));
665  int maxfreq = int(lrint((std::max(maxbin, minbin)
666  * sliceableModel->getSampleRate()) /
667  getFFTSize()));
668 
669  if (maxbin != minbin) {
670  binstr = tr("%1 - %2").arg(minbin+1).arg(maxbin+1);
671  } else {
672  binstr = QString("%1").arg(minbin+1);
673  }
674  if (minfreq != maxfreq) {
675  hzstr = tr("%1 - %2 Hz").arg(minfreq).arg(maxfreq);
676  } else {
677  hzstr = tr("%1 Hz").arg(minfreq);
678  }
679 
680  QString valuestr;
681  if (maxvalue != minvalue) {
682  valuestr = tr("%1 - %2").arg(minvalue).arg(maxvalue);
683  } else {
684  valuestr = QString("%1").arg(minvalue);
685  }
686 
687  QString dbstr;
688  double mindb = AudioLevel::multiplier_to_dB(minvalue);
689  double maxdb = AudioLevel::multiplier_to_dB(maxvalue);
690  QString mindbstr;
691  QString maxdbstr;
692  if (mindb == AudioLevel::DB_FLOOR) {
693  mindbstr = Strings::minus_infinity;
694  } else {
695  mindbstr = QString("%1").arg(lrint(mindb));
696  }
697  if (maxdb == AudioLevel::DB_FLOOR) {
698  maxdbstr = Strings::minus_infinity;
699  } else {
700  maxdbstr = QString("%1").arg(lrint(maxdb));
701  }
702  if (lrint(mindb) != lrint(maxdb)) {
703  dbstr = tr("%1 - %2").arg(mindbstr).arg(maxdbstr);
704  } else {
705  dbstr = tr("%1").arg(mindbstr);
706  }
707 
708  QString description;
709 
710  if (range > int(sliceableModel->getResolution())) {
711  description = tr("%1\nBin:\t%2 (%3)\n%4 value:\t%5\ndB:\t%6")
712  .arg(genericDesc)
713  .arg(binstr)
714  .arg(hzstr)
715  .arg(m_samplingMode == NearestSample ? tr("First") :
716  m_samplingMode == SampleMean ? tr("Mean") : tr("Peak"))
717  .arg(valuestr)
718  .arg(dbstr);
719  } else {
720  description = tr("%1\nBin:\t%2 (%3)\nValue:\t%4\ndB:\t%5")
721  .arg(genericDesc)
722  .arg(binstr)
723  .arg(hzstr)
724  .arg(valuestr)
725  .arg(dbstr);
726  }
727 
728  return description;
729 }
730 
731 void
732 SpectrumLayer::paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const
733 {
734  auto originModel = ModelById::get(m_originModel);
735  if (!originModel || !originModel->isOK() || !originModel->isReady()) {
736  SVDEBUG << "SpectrumLayer::paint: no origin model, or origin model not OK or not ready" << endl;
737  return;
738  }
739 
740  if (m_newFFTNeeded) {
741  SVDEBUG << "SpectrumLayer::paint: new FFT needed, calling setupFFT" << endl;
742  const_cast<SpectrumLayer *>(this)->setupFFT(); //ugh
743  }
744 
745  auto fft = ModelById::getAs<FFTModel>(m_sliceableModel);
746  if (!fft) return;
747 
748  double thresh = (pow(10, -6) / m_gain) * (getFFTSize() / 2.0); // -60dB adj
749 
750  int xorigin = getVerticalScaleWidth(v, false, paint) + 1;
751  int scaleHeight = getHorizontalScaleHeight(v, paint);
752 
753  QPoint localPos;
754  bool shouldIlluminate = v->shouldIlluminateLocalFeatures(this, localPos);
755 
756  int illuminateX = 0;
757  double illuminateFreq = 0.0;
758  double illuminateLevel = 0.0;
759 
760  ColourMapper mapper =
764 
765 // cerr << "shouldIlluminate = " << shouldIlluminate << ", localPos = " << localPos.x() << "," << localPos.y() << endl;
766 
767  if (fft && m_showPeaks) {
768 
769  // draw peak lines
770 
771  int col = int(v->getCentreFrame() / fft->getResolution());
772 
773  paint.save();
774  paint.setRenderHint(QPainter::Antialiasing, false);
775 
776  int peakminbin = 0;
777  int peakmaxbin = fft->getHeight() - 1;
778  double peakmaxfreq = Pitch::getFrequencyForPitch(128);
779  peakmaxbin = int(((peakmaxfreq * fft->getHeight() * 2) /
780  fft->getSampleRate()));
781 
782  FFTModel::PeakSet peaks = fft->getPeakFrequencies
783  (FFTModel::MajorPitchAdaptivePeaks, col, peakminbin, peakmaxbin);
784 
785  BiasCurve curve;
786  getBiasCurve(curve);
787  int cs = int(curve.size());
788 
789  int px = -1;
790 
791  int fuzz = ViewManager::scalePixelSize(3);
792 
793  for (FFTModel::PeakSet::iterator i = peaks.begin();
794  i != peaks.end(); ++i) {
795 
796  double freq = i->second;
797  int x = int(lrint(getXForFrequency(v, freq)));
798  if (x == px) {
799  continue;
800  }
801 
802  int bin = i->first;
803 
804 // cerr << "bin = " << bin << ", thresh = " << thresh << ", value = " << fft->getMagnitudeAt(col, bin) << endl;
805 
806  double value = fft->getValueAt(col, bin);
807  if (value < thresh) continue;
808  if (bin < cs) value *= curve[bin];
809 
810  double norm = 0.f;
811  // we only need the norm here, for the colour map
812  (void)getYForValue(v, value, norm);
813 
814  QColor colour = mapper.map(norm);
815 
816  paint.setPen(QPen(colour, 1));
817  paint.drawLine(x, 0, x, v->getPaintHeight() - scaleHeight - 1);
818 
819  if (shouldIlluminate && std::abs(localPos.x() - x) <= fuzz) {
820  illuminateX = x;
821  illuminateFreq = freq;
822  illuminateLevel = norm;
823  }
824 
825  px = x;
826  }
827 
828  paint.restore();
829  }
830 
831  paint.save();
832 
833  SliceLayer::paint(v, paint, rect);
834 
835  paintHorizontalScale(v, paint, xorigin);
836 
837  paint.restore();
838 
839  if (illuminateFreq > 0.0) {
840 
841  QColor colour = mapper.map(illuminateLevel);
842  paint.setPen(QPen(colour, 1));
843 
844  int labelY = v->getPaintHeight() -
845  getHorizontalScaleHeight(v, paint) -
846  paint.fontMetrics().height() * 4;
847 
848  QString text = tr("%1 Hz").arg(illuminateFreq);
849  int lw = paint.fontMetrics().width(text);
850 
851  int gap = ViewManager::scalePixelSize(v->getXForViewX(3));
852  double half = double(gap)/2.0;
853 
854  int labelX = illuminateX - lw - gap;
855  if (labelX < getVerticalScaleWidth(v, false, paint)) {
856  labelX = illuminateX + gap;
857  }
858 
860  (v, paint, labelX, labelY,
862 
863  if (Pitch::isFrequencyInMidiRange(illuminateFreq)) {
864  QString pitchLabel = Pitch::getPitchLabelForFrequency
865  (illuminateFreq);
867  (v, paint,
868  labelX, labelY + paint.fontMetrics().ascent() + gap,
869  pitchLabel, PaintAssistant::OutlinedText);
870  }
871  paint.fillRect(QRectF(illuminateX - half, labelY + gap, gap, gap),
872  colour);
873  }
874 }
875 
876 int
878  QPainter &paint) const
879 {
880  int pkh = int(paint.fontMetrics().height() * 0.7 + 0.5);
881  if (pkh < 10) pkh = 10;
882 
883  int scaleh = HorizontalFrequencyScale().getHeight(v, paint);
884 
885  return pkh + scaleh;
886 }
887 
888 void
890  QPainter &paint,
891  int xorigin) const
892 {
894  // (keyboard, crosshairs etc) should be applicable to any slice
895  // layer whose model has a vertical scale unit of Hz. However,
896  // the dense 3d model at the moment doesn't record its vertical
897  // scale unit -- we need to fix that and hoist this code as
898  // appropriate. Same really goes for any code in SpectrogramLayer
899  // that could be relevant to Colour3DPlotLayer with unit Hz, but
900  // that's a bigger proposition.
901 
903  return;
904  }
905 
906  int totalScaleHeight = getHorizontalScaleHeight(v, paint); // inc piano
907  int freqScaleHeight = HorizontalFrequencyScale().getHeight(v, paint);
908  int paintHeight = v->getPaintHeight();
909  int paintWidth = v->getPaintWidth();
910 
912  (v, this, paint,
913  QRect(xorigin, paintHeight - totalScaleHeight - 1,
914  paintWidth - 1, totalScaleHeight - freqScaleHeight));
915 
916  int scaleLeft = int(getXForBin(v, 1));
917 
918  paint.drawLine(int(getXForBin(v, 0)), paintHeight - freqScaleHeight,
919  scaleLeft, paintHeight - freqScaleHeight);
920 
921  QString hz = tr("Hz");
922  int hzw = paint.fontMetrics().width(hz);
923  if (scaleLeft > hzw + 5) {
924  paint.drawText
925  (scaleLeft - hzw - 5,
926  paintHeight - freqScaleHeight + paint.fontMetrics().ascent() + 5,
927  hz);
928  }
929 
931  (v, this, paint,
932  QRect(scaleLeft, paintHeight - freqScaleHeight,
933  paintWidth, totalScaleHeight),
934  m_binScale == LogBins);
935 }
936 
937 void
939 {
940  curve = m_biasCurve;
941 }
942 
943 void
944 SpectrumLayer::toXml(QTextStream &stream,
945  QString indent, QString extraAttributes) const
946 {
947  QString s = QString("windowSize=\"%1\" "
948  "windowHopLevel=\"%2\" "
949  "oversampling=\"%3\" "
950  "showPeaks=\"%4\" ")
951  .arg(m_windowSize)
952  .arg(m_windowHopLevel)
953  .arg(m_oversampling)
954  .arg(m_showPeaks ? "true" : "false");
955 
956  SliceLayer::toXml(stream, indent, extraAttributes + " " + s);
957 }
958 
959 void
960 SpectrumLayer::setProperties(const QXmlAttributes &attributes)
961 {
962  SliceLayer::setProperties(attributes);
963 
964  bool ok = false;
965 
966  int windowSize = attributes.value("windowSize").toUInt(&ok);
967  if (ok) setWindowSize(windowSize);
968 
969  int windowHopLevel = attributes.value("windowHopLevel").toUInt(&ok);
970  if (ok) setWindowHopLevel(windowHopLevel);
971 
972  int oversampling = attributes.value("oversampling").toUInt(&ok);
973  if (ok) setOversampling(oversampling);
974 
975  bool showPeaks = (attributes.value("showPeaks").trimmed() == "true");
976  setShowPeaks(showPeaks);
977 }
978 
979 
virtual void setProperty(const PropertyName &, int value) override
virtual int getXForViewX(int viewx) const =0
Return the closest pixel x-coordinate corresponding to a given view x-coordinate. ...
EnergyScale m_energyScale
Definition: SliceLayer.h:177
void paintScale(LayerGeometryProvider *v, const HorizontalScaleProvider *provider, QPainter &paint, QRect r, bool logarithmic)
void setChannel(int)
void setWindowSize(int)
bool setDisplayExtents(double min, double max) override
Set the displayed minimum and maximum values for the y axis to the given range, if supported...
virtual bool shouldIlluminateLocalFeatures(const Layer *, QPoint &) const =0
int getOversampling() const
static int scalePixelSize(int pixels)
Take a "design pixel" size and scale it for the actual display.
virtual void getBiasCurve(BiasCurve &) const override
virtual double getValueForY(const LayerGeometryProvider *v, double y) const
Definition: SliceLayer.cpp:372
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
PropertyType getPropertyType(const PropertyName &) const override
Definition: SliceLayer.cpp:774
bool m_colourInverted
Definition: SliceLayer.h:176
ModelId m_sliceableModel
Definition: SliceLayer.h:173
PropertyList getProperties() const override
Definition: SliceLayer.cpp:740
virtual RangeMapper * getNewPropertyRangeMapper(const PropertyName &) const override
virtual QString getFeatureDescription(LayerGeometryProvider *v, QPoint &) const override
QString getPropertyIconName(const PropertyName &) const override
Definition: SliceLayer.cpp:767
virtual double getXForBin(const LayerGeometryProvider *, double bin) const override
Convert a (possibly non-integral) bin into x-coord. May be overridden.
QString getPropertyValueLabel(const PropertyName &, int value) const override
Definition: SliceLayer.cpp:890
virtual double getXForFrequency(const LayerGeometryProvider *, double freq) const override
virtual 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.
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.
virtual int getHorizontalScaleHeight(LayerGeometryProvider *, QPainter &) const override
int m_colourMap
Definition: SliceLayer.h:175
BinScale m_binScale
Definition: SliceLayer.h:180
RangeMapper * getNewPropertyRangeMapper(const PropertyName &) const override
Definition: SliceLayer.cpp:934
int getWindowIncrement() const
int m_minbin
Definition: SliceLayer.h:185
void preferenceChanged(PropertyContainer::PropertyName name)
virtual QString getPropertyValueLabel(const PropertyName &, int value) const override
virtual double getFrequencyForX(const LayerGeometryProvider *, double x) const override
int getHeight(LayerGeometryProvider *v, QPainter &paint)
int m_maxbin
Definition: SliceLayer.h:186
virtual bool getYScaleValue(const LayerGeometryProvider *, int y, double &value, QString &unit) const override
Return the value and unit at the given y coordinate in the given view.
virtual int getPropertyRangeAndValue(const PropertyName &, int *min, int *max, int *deflt) const override
void layerParametersChanged()
BiasCurve m_biasCurve
bool shouldShowHorizontalValueScale() const
Definition: ViewManager.h:225
std::vector< float > m_values
Definition: SliceLayer.h:194
double m_freqOfMinBin
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
QColor map(double value) const
Map the given value to a colour.
virtual bool getYScaleDifference(const LayerGeometryProvider *, int y0, int y1, double &diff, QString &unit) const override
Return the difference between the values at the given y coordinates in the given view, and the unit of the difference.
A class for mapping intensity values onto various colour maps.
Definition: ColourMapper.h:27
virtual PropertyType getPropertyType(const PropertyName &) const override
virtual double getFrequencyForBin(double bin) const
virtual int getPaintHeight() const
virtual double getBinForX(const LayerGeometryProvider *, double x) const override
Convert an x-coord into (possibly non-integral) bin. May be overridden.
virtual void toXml(QTextStream &stream, QString indent="", QString extraAttributes="") const override
QString getPropertyLabel(const PropertyName &) const override
Definition: SliceLayer.cpp:754
void paintPianoHorizontal(LayerGeometryProvider *v, const HorizontalScaleProvider *p, QPainter &paint, QRect rect)
Definition: PianoScale.cpp:91
int getFFTSize() const
Definition: SpectrumLayer.h:96
virtual bool setDisplayExtents(double min, double max) override
Set the displayed minimum and maximum values for the y axis to the given range, if supported...
void setModel(ModelId model)
ModelId m_originModel
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
WindowType m_windowType
float m_threshold
Definition: SliceLayer.h:182
virtual void paintCrosshairs(LayerGeometryProvider *, QPainter &, QPoint) const override
std::vector< float > BiasCurve
Definition: SliceLayer.h:150
virtual void paintHorizontalScale(LayerGeometryProvider *, QPainter &, int xorigin) const
virtual ViewManager * getViewManager() const =0
BinAlignment m_binAlignment
Definition: SliceLayer.h:174
virtual PropertyList getProperties() const override
void setProperty(const PropertyName &, int value) override
Definition: SliceLayer.cpp:946
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
static void drawVisibleText(const LayerGeometryProvider *, QPainter &p, int x, int y, QString text, TextStyle style)
void setProperties(const QXmlAttributes &) override
Set the particular properties of a layer (those specific to the subclass) from a set of XML attribute...
void setOversampling(int oversampling)
virtual QString getFeatureDescriptionAux(LayerGeometryProvider *v, QPoint &, bool includeBinDescription, int &minbin, int &maxbin, int &range) const
Definition: SliceLayer.cpp:101
virtual bool getXScaleValue(const LayerGeometryProvider *v, int x, double &value, QString &unit) const override
Return the value and unit at the given x coordinate in the given view.
virtual sv_frame_t getCentreFrame() const =0
Return the centre frame of the visible widget.
QColor getContrastingColour() const
Return a colour that contrasts somewhat with the colours in the map, so as to be used for cursors etc...
float m_gain
Definition: SliceLayer.h:184
void setWindowType(WindowType type)
void setWindowHopLevel(int level)
void toXml(QTextStream &stream, QString indent="", QString extraAttributes="") const override
std::map< int, int > m_xorigins
Definition: SliceLayer.h:189
QString getPropertyGroupName(const PropertyName &) const override
Definition: SliceLayer.cpp:788
int getPropertyRangeAndValue(const PropertyName &, int *min, int *max, int *deflt) const override
Definition: SliceLayer.cpp:801
void setShowPeaks(bool)
virtual double getBinForFrequency(double freq) const
virtual void setProperties(const QXmlAttributes &) override
Set the particular properties of a layer (those specific to the subclass) from a set of XML attribute...
virtual int getPaintWidth() const
int getVerticalScaleWidth(LayerGeometryProvider *v, bool, QPainter &) const override
Definition: SliceLayer.cpp:665
virtual QString getPropertyGroupName(const PropertyName &) const override
virtual bool getYScaleDifference(const LayerGeometryProvider *v, int y0, int y1, double &diff, QString &unit) const
Return the difference between the values at the given y coordinates in the given view, and the unit of the difference.
Definition: Layer.cpp:173
virtual QString getPropertyLabel(const PropertyName &) const override
virtual bool getCrosshairExtents(LayerGeometryProvider *, QPainter &, QPoint cursorPos, std::vector< QRect > &extents) const override
virtual QString getPropertyIconName(const PropertyName &) const override