SpectrogramLayer.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-2009 Chris Cannam and 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 "SpectrogramLayer.h"
17 
18 #include "view/View.h"
19 #include "base/Profiler.h"
20 #include "base/AudioLevel.h"
21 #include "base/Window.h"
22 #include "base/Pitch.h"
23 #include "base/Preferences.h"
24 #include "base/RangeMapper.h"
25 #include "base/LogRange.h"
26 #include "base/ColumnOp.h"
27 #include "base/Strings.h"
28 #include "base/StorageAdviser.h"
29 #include "base/Exceptions.h"
30 #include "widgets/CommandHistory.h"
31 #include "data/model/Dense3DModelPeakCache.h"
32 
33 #include "ColourMapper.h"
34 #include "PianoScale.h"
35 #include "PaintAssistant.h"
36 #include "Colour3DPlotRenderer.h"
37 #include "Colour3DPlotExporter.h"
38 
39 #include <QPainter>
40 #include <QImage>
41 #include <QPixmap>
42 #include <QRect>
43 #include <QApplication>
44 #include <QMessageBox>
45 #include <QMouseEvent>
46 #include <QTextStream>
47 #include <QSettings>
48 
49 #include <iostream>
50 
51 #include <cassert>
52 #include <cmath>
53 
54 //#define DEBUG_SPECTROGRAM 1
55 //#define DEBUG_SPECTROGRAM_REPAINT 1
56 
57 using namespace std;
58 
60  m_channel(0),
61  m_windowSize(1024),
62  m_windowType(HanningWindow),
63  m_windowHopLevel(2),
64  m_oversampling(1),
65  m_gain(1.0),
66  m_initialGain(1.0),
67  m_threshold(1.0e-8f),
68  m_initialThreshold(1.0e-8f),
69  m_colourRotation(0),
70  m_initialRotation(0),
71  m_minFrequency(10),
72  m_maxFrequency(8000),
73  m_initialMaxFrequency(8000),
74  m_verticallyFixed(false),
75  m_colourScale(ColourScaleType::Log),
76  m_colourScaleMultiple(1.0),
77  m_colourMap(0),
78  m_colourInverted(false),
79  m_binScale(BinScale::Linear),
80  m_binDisplay(BinDisplay::AllBins),
81  m_normalization(ColumnNormalization::None),
82  m_normalizeVisibleArea(false),
83  m_lastEmittedZoomStep(-1),
84  m_synchronous(false),
85  m_haveDetailedScale(false),
86  m_exiting(false),
87  m_peakCacheDivisor(8)
88 {
89  QString colourConfigName = "spectrogram-colour";
90  int colourConfigDefault = int(ColourMapper::Green);
91 
92  if (config == FullRangeDb) {
94  setMaxFrequency(0);
95  } else if (config == MelodicRange) {
96  setWindowSize(8192);
98  m_initialMaxFrequency = 1500;
99  setMaxFrequency(1500);
100  setMinFrequency(40);
104  colourConfigName = "spectrogram-melodic-colour";
105  colourConfigDefault = int(ColourMapper::Sunset);
106 // setGain(20);
107  } else if (config == MelodicPeaks) {
108  setWindowSize(4096);
110  m_initialMaxFrequency = 2000;
111  setMaxFrequency(2000);
112  setMinFrequency(40);
116  setNormalization(ColumnNormalization::Max1);
117  colourConfigName = "spectrogram-melodic-colour";
118  colourConfigDefault = int(ColourMapper::Sunset);
119  }
120 
121  QSettings settings;
122  settings.beginGroup("Preferences");
123  setColourMap(settings.value(colourConfigName, colourConfigDefault).toInt());
124  settings.endGroup();
125 
126  Preferences *prefs = Preferences::getInstance();
127  connect(prefs, SIGNAL(propertyChanged(PropertyContainer::PropertyName)),
128  this, SLOT(preferenceChanged(PropertyContainer::PropertyName)));
129  setWindowType(prefs->getWindowType());
130 }
131 
133 {
136 }
137 
138 void
140 {
141  if (m_verticallyFixed) return;
142  m_verticallyFixed = true;
144 }
145 
146 ModelId
148 {
149  // Creating Colour3DPlotExporters is cheap, so we create one on
150  // every call - calls probably being infrequent - to avoid having
151  // to worry about view lifecycles. We can't delete them on the
152  // same call of course as we need to return a valid id, so we push
153  // them onto a list that then gets cleared (with calls to
154  // Colour3DPlotExporter::discardSources() and
155  // ModelById::release()) in deleteDerivedModels().
156 
158  sources.verticalBinLayer = this;
159  sources.fft = m_fftModel;
160  sources.source = sources.fft;
161  sources.provider = v;
162 
164  params.binDisplay = m_binDisplay;
165  params.scaleFactor = 1.0;
167  m_normalization != ColumnNormalization::Hybrid) {
168  params.scaleFactor *= 2.f / float(getWindowSize());
169  }
170  params.threshold = m_threshold; // matching ColourScale in getRenderer
171  params.gain = m_gain; // matching ColourScale in getRenderer
173 
174  ModelId exporter = ModelById::add
175  (std::make_shared<Colour3DPlotExporter>(sources, params));
176  m_exporters.push_back(exporter);
177  return exporter;
178 }
179 
180 void
182 {
183  ModelById::release(m_fftModel);
184  ModelById::release(m_peakCache);
185  ModelById::release(m_wholeCache);
186 
187  for (auto exporterId: m_exporters) {
188  if (auto exporter =
189  ModelById::getAs<Colour3DPlotExporter>(exporterId)) {
190  exporter->discardSources();
191  }
192  ModelById::release(exporterId);
193  }
194  m_exporters.clear();
195 
196  m_fftModel = {};
197  m_peakCache = {};
198  m_wholeCache = {};
199 }
200 
201 pair<ColourScaleType, double>
203 {
204  switch (value) {
205  case 0: return { ColourScaleType::Linear, 1.0 };
206  case 1: return { ColourScaleType::Meter, 1.0 };
207  case 2: return { ColourScaleType::Log, 2.0 }; // dB^2 (i.e. log of power)
208  case 3: return { ColourScaleType::Log, 1.0 }; // dB (of magnitude)
209  case 4: return { ColourScaleType::Phase, 1.0 };
210  default: return { ColourScaleType::Linear, 1.0 };
211  }
212 }
213 
214 int
216 {
217  switch (scale) {
218  case ColourScaleType::Linear: return 0;
219  case ColourScaleType::Meter: return 1;
220  case ColourScaleType::Log: return (multiple > 1.5 ? 2 : 3);
221  case ColourScaleType::Phase: return 4;
224  default: return 0;
225  }
226 }
227 
228 std::pair<ColumnNormalization, bool>
230 {
231  switch (value) {
232  default:
233  case 0: return { ColumnNormalization::None, false };
234  case 1: return { ColumnNormalization::Max1, false };
235  case 2: return { ColumnNormalization::None, true }; // visible area
236  case 3: return { ColumnNormalization::Hybrid, false };
237  }
238 }
239 
240 int
241 SpectrogramLayer::convertFromColumnNorm(ColumnNormalization norm, bool visible)
242 {
243  if (visible) return 2;
244  switch (norm) {
245  case ColumnNormalization::None: return 0;
246  case ColumnNormalization::Max1: return 1;
247  case ColumnNormalization::Hybrid: return 3;
248 
249  case ColumnNormalization::Sum1:
250  case ColumnNormalization::Range01:
251  default: return 0;
252  }
253 }
254 
255 void
257 {
258  auto newModel = ModelById::getAs<DenseTimeValueModel>(modelId);
259  if (!modelId.isNone() && !newModel) {
260  throw std::logic_error("Not a DenseTimeValueModel");
261  }
262 
263  if (modelId == m_model) return;
264  m_model = modelId;
265 
266  if (newModel) {
268 
270 
271  connect(newModel.get(),
272  SIGNAL(modelChanged(ModelId)),
273  this, SLOT(cacheInvalid(ModelId)));
274  connect(newModel.get(),
275  SIGNAL(modelChangedWithin(ModelId, sv_frame_t, sv_frame_t)),
276  this, SLOT(cacheInvalid(ModelId, sv_frame_t, sv_frame_t)));
277  }
278 
279  emit modelReplaced();
280 }
281 
282 Layer::PropertyList
284 {
285  PropertyList list;
286  list.push_back("Colour");
287  list.push_back("Colour Scale");
288  list.push_back("Window Size");
289  list.push_back("Window Increment");
290  list.push_back("Oversampling");
291  list.push_back("Normalization");
292  list.push_back("Bin Display");
293  list.push_back("Threshold");
294  list.push_back("Gain");
295  list.push_back("Colour Rotation");
296 // list.push_back("Min Frequency");
297 // list.push_back("Max Frequency");
298  list.push_back("Frequency Scale");
299  return list;
300 }
301 
302 QString
303 SpectrogramLayer::getPropertyLabel(const PropertyName &name) const
304 {
305  if (name == "Colour") return tr("Colour");
306  if (name == "Colour Scale") return tr("Colour Scale");
307  if (name == "Window Size") return tr("Window Size");
308  if (name == "Window Increment") return tr("Window Overlap");
309  if (name == "Oversampling") return tr("Oversampling");
310  if (name == "Normalization") return tr("Normalization");
311  if (name == "Bin Display") return tr("Bin Display");
312  if (name == "Threshold") return tr("Threshold");
313  if (name == "Gain") return tr("Gain");
314  if (name == "Colour Rotation") return tr("Colour Rotation");
315  if (name == "Min Frequency") return tr("Min Frequency");
316  if (name == "Max Frequency") return tr("Max Frequency");
317  if (name == "Frequency Scale") return tr("Frequency Scale");
318  return "";
319 }
320 
321 QString
322 SpectrogramLayer::getPropertyIconName(const PropertyName &) const
323 {
324  return "";
325 }
326 
327 Layer::PropertyType
328 SpectrogramLayer::getPropertyType(const PropertyName &name) const
329 {
330  if (name == "Gain") return RangeProperty;
331  if (name == "Colour Rotation") return RangeProperty;
332  if (name == "Threshold") return RangeProperty;
333  if (name == "Colour") return ColourMapProperty;
334  return ValueProperty;
335 }
336 
337 QString
338 SpectrogramLayer::getPropertyGroupName(const PropertyName &name) const
339 {
340  if (name == "Bin Display" ||
341  name == "Frequency Scale") return tr("Bins");
342  if (name == "Window Size" ||
343  name == "Window Increment" ||
344  name == "Oversampling") return tr("Window");
345  if (name == "Colour" ||
346  name == "Threshold" ||
347  name == "Colour Rotation") return tr("Colour");
348  if (name == "Normalization" ||
349  name == "Gain" ||
350  name == "Colour Scale") return tr("Scale");
351  return QString();
352 }
353 
354 int
356  int *min, int *max, int *deflt) const
357 {
358  int val = 0;
359 
360  int garbage0, garbage1, garbage2;
361  if (!min) min = &garbage0;
362  if (!max) max = &garbage1;
363  if (!deflt) deflt = &garbage2;
364 
365  if (name == "Gain") {
366 
367  *min = -50;
368  *max = 50;
369 
370  *deflt = int(lrint(log10(m_initialGain) * 20.0));
371  if (*deflt < *min) *deflt = *min;
372  if (*deflt > *max) *deflt = *max;
373 
374  val = int(lrint(log10(m_gain) * 20.0));
375  if (val < *min) val = *min;
376  if (val > *max) val = *max;
377 
378  } else if (name == "Threshold") {
379 
380  *min = -81;
381  *max = -1;
382 
383  *deflt = int(lrint(AudioLevel::multiplier_to_dB(m_initialThreshold)));
384  if (*deflt < *min) *deflt = *min;
385  if (*deflt > *max) *deflt = *max;
386 
387  val = int(lrint(AudioLevel::multiplier_to_dB(m_threshold)));
388  if (val < *min) val = *min;
389  if (val > *max) val = *max;
390 
391  } else if (name == "Colour Rotation") {
392 
393  *min = 0;
394  *max = 256;
395  *deflt = m_initialRotation;
396 
397  val = m_colourRotation;
398 
399  } else if (name == "Colour Scale") {
400 
401  // linear, meter, db^2, db, phase
402  *min = 0;
403  *max = 4;
404  *deflt = 2;
405 
407 
408  } else if (name == "Colour") {
409 
410  *min = 0;
411  *max = ColourMapper::getColourMapCount() - 1;
412  *deflt = 0;
413 
414  val = m_colourMap;
415 
416  } else if (name == "Window Size") {
417 
418  *min = 0;
419  *max = 10;
420  *deflt = 5;
421 
422  val = 0;
423  int ws = m_windowSize;
424  while (ws > 32) { ws >>= 1; val ++; }
425 
426  } else if (name == "Window Increment") {
427 
428  *min = 0;
429  *max = 5;
430  *deflt = 2;
431 
432  val = m_windowHopLevel;
433 
434  } else if (name == "Oversampling") {
435 
436  *min = 0;
437  *max = 3;
438  *deflt = 0;
439 
440  val = 0;
441  int ov = m_oversampling;
442  while (ov > 1) { ov >>= 1; val ++; }
443 
444  } else if (name == "Min Frequency") {
445 
446  *min = 0;
447  *max = 9;
448  *deflt = 1;
449 
450  switch (m_minFrequency) {
451  case 0: default: val = 0; break;
452  case 10: val = 1; break;
453  case 20: val = 2; break;
454  case 40: val = 3; break;
455  case 100: val = 4; break;
456  case 250: val = 5; break;
457  case 500: val = 6; break;
458  case 1000: val = 7; break;
459  case 4000: val = 8; break;
460  case 10000: val = 9; break;
461  }
462 
463  } else if (name == "Max Frequency") {
464 
465  *min = 0;
466  *max = 9;
467  *deflt = 6;
468 
469  switch (m_maxFrequency) {
470  case 500: val = 0; break;
471  case 1000: val = 1; break;
472  case 1500: val = 2; break;
473  case 2000: val = 3; break;
474  case 4000: val = 4; break;
475  case 6000: val = 5; break;
476  case 8000: val = 6; break;
477  case 12000: val = 7; break;
478  case 16000: val = 8; break;
479  default: val = 9; break;
480  }
481 
482  } else if (name == "Frequency Scale") {
483 
484  *min = 0;
485  *max = 1;
486  *deflt = int(BinScale::Linear);
487  val = (int)m_binScale;
488 
489  } else if (name == "Bin Display") {
490 
491  *min = 0;
492  *max = 2;
493  *deflt = int(BinDisplay::AllBins);
494  val = (int)m_binDisplay;
495 
496  } else if (name == "Normalization") {
497 
498  *min = 0;
499  *max = 3;
500  *deflt = 0;
501 
503 
504  } else {
505  val = Layer::getPropertyRangeAndValue(name, min, max, deflt);
506  }
507 
508  return val;
509 }
510 
511 QString
513  int value) const
514 {
515  if (name == "Colour") {
516  return ColourMapper::getColourMapLabel(value);
517  }
518  if (name == "Colour Scale") {
519  switch (value) {
520  default:
521  case 0: return tr("Linear");
522  case 1: return tr("Meter");
523  case 2: return tr("dBV^2");
524  case 3: return tr("dBV");
525  case 4: return tr("Phase");
526  }
527  }
528  if (name == "Normalization") {
529  switch(value) {
530  default:
531  case 0: return tr("None");
532  case 1: return tr("Col");
533  case 2: return tr("View");
534  case 3: return tr("Hybrid");
535  }
536 // return ""; // icon only
537  }
538  if (name == "Window Size") {
539  return QString("%1").arg(32 << value);
540  }
541  if (name == "Window Increment") {
542  switch (value) {
543  default:
544  case 0: return tr("None");
545  case 1: return tr("25 %");
546  case 2: return tr("50 %");
547  case 3: return tr("75 %");
548  case 4: return tr("87.5 %");
549  case 5: return tr("93.75 %");
550  }
551  }
552  if (name == "Oversampling") {
553  switch (value) {
554  default:
555  case 0: return tr("1x");
556  case 1: return tr("2x");
557  case 2: return tr("4x");
558  case 3: return tr("8x");
559  }
560  }
561  if (name == "Min Frequency") {
562  switch (value) {
563  default:
564  case 0: return tr("No min");
565  case 1: return tr("10 Hz");
566  case 2: return tr("20 Hz");
567  case 3: return tr("40 Hz");
568  case 4: return tr("100 Hz");
569  case 5: return tr("250 Hz");
570  case 6: return tr("500 Hz");
571  case 7: return tr("1 KHz");
572  case 8: return tr("4 KHz");
573  case 9: return tr("10 KHz");
574  }
575  }
576  if (name == "Max Frequency") {
577  switch (value) {
578  default:
579  case 0: return tr("500 Hz");
580  case 1: return tr("1 KHz");
581  case 2: return tr("1.5 KHz");
582  case 3: return tr("2 KHz");
583  case 4: return tr("4 KHz");
584  case 5: return tr("6 KHz");
585  case 6: return tr("8 KHz");
586  case 7: return tr("12 KHz");
587  case 8: return tr("16 KHz");
588  case 9: return tr("No max");
589  }
590  }
591  if (name == "Frequency Scale") {
592  switch (value) {
593  default:
594  case 0: return tr("Linear");
595  case 1: return tr("Log");
596  }
597  }
598  if (name == "Bin Display") {
599  switch (value) {
600  default:
601  case 0: return tr("All Bins");
602  case 1: return tr("Peak Bins");
603  case 2: return tr("Frequencies");
604  }
605  }
606  return tr("<unknown>");
607 }
608 
609 QString
611  int value) const
612 {
613  if (name == "Normalization") {
614  switch(value) {
615  default:
616  case 0: return "normalise-none";
617  case 1: return "normalise-columns";
618  case 2: return "normalise";
619  case 3: return "normalise-hybrid";
620  }
621  }
622  return "";
623 }
624 
625 RangeMapper *
626 SpectrogramLayer::getNewPropertyRangeMapper(const PropertyName &name) const
627 {
628  if (name == "Gain") {
629  return new LinearRangeMapper(-50, 50, -25, 25, tr("dB"));
630  }
631  if (name == "Threshold") {
632  return new LinearRangeMapper(-81, -1, -81, -1, tr("dB"), false,
633  { { -81, Strings::minus_infinity } });
634  }
635  return nullptr;
636 }
637 
638 void
639 SpectrogramLayer::setProperty(const PropertyName &name, int value)
640 {
641  if (name == "Gain") {
642  setGain(float(pow(10, float(value)/20.0)));
643  } else if (name == "Threshold") {
644  if (value == -81) setThreshold(0.0);
645  else setThreshold(float(AudioLevel::dB_to_multiplier(value)));
646  } else if (name == "Colour Rotation") {
647  setColourRotation(value);
648  } else if (name == "Colour") {
649  setColourMap(value);
650  } else if (name == "Window Size") {
651  setWindowSize(32 << value);
652  } else if (name == "Window Increment") {
653  setWindowHopLevel(value);
654  } else if (name == "Oversampling") {
655  setOversampling(1 << value);
656  } else if (name == "Min Frequency") {
657  switch (value) {
658  default:
659  case 0: setMinFrequency(0); break;
660  case 1: setMinFrequency(10); break;
661  case 2: setMinFrequency(20); break;
662  case 3: setMinFrequency(40); break;
663  case 4: setMinFrequency(100); break;
664  case 5: setMinFrequency(250); break;
665  case 6: setMinFrequency(500); break;
666  case 7: setMinFrequency(1000); break;
667  case 8: setMinFrequency(4000); break;
668  case 9: setMinFrequency(10000); break;
669  }
670  int vs = getCurrentVerticalZoomStep();
671  if (vs != m_lastEmittedZoomStep) {
672  emit verticalZoomChanged();
674  }
675  } else if (name == "Max Frequency") {
676  switch (value) {
677  case 0: setMaxFrequency(500); break;
678  case 1: setMaxFrequency(1000); break;
679  case 2: setMaxFrequency(1500); break;
680  case 3: setMaxFrequency(2000); break;
681  case 4: setMaxFrequency(4000); break;
682  case 5: setMaxFrequency(6000); break;
683  case 6: setMaxFrequency(8000); break;
684  case 7: setMaxFrequency(12000); break;
685  case 8: setMaxFrequency(16000); break;
686  default:
687  case 9: setMaxFrequency(0); break;
688  }
689  int vs = getCurrentVerticalZoomStep();
690  if (vs != m_lastEmittedZoomStep) {
691  emit verticalZoomChanged();
693  }
694  } else if (name == "Colour Scale") {
696  switch (value) {
697  default:
698  case 0: setColourScale(ColourScaleType::Linear); break;
699  case 1: setColourScale(ColourScaleType::Meter); break;
700  case 2:
703  break;
704  case 3: setColourScale(ColourScaleType::Log); break;
705  case 4: setColourScale(ColourScaleType::Phase); break;
706  }
707  } else if (name == "Frequency Scale") {
708  switch (value) {
709  default:
710  case 0: setBinScale(BinScale::Linear); break;
711  case 1: setBinScale(BinScale::Log); break;
712  }
713  } else if (name == "Bin Display") {
714  switch (value) {
715  default:
716  case 0: setBinDisplay(BinDisplay::AllBins); break;
717  case 1: setBinDisplay(BinDisplay::PeakBins); break;
719  }
720  } else if (name == "Normalization") {
721  auto n = convertToColumnNorm(value);
722  setNormalization(n.first);
723  setNormalizeVisibleArea(n.second);
724  }
725 }
726 
727 void
729 {
730 #ifdef DEBUG_SPECTROGRAM
731  cerr << "SpectrogramLayer::invalidateRenderers called" << endl;
732 #endif
733 
734  for (ViewRendererMap::iterator i = m_renderers.begin();
735  i != m_renderers.end(); ++i) {
736  delete i->second;
737  }
738  m_renderers.clear();
739 }
740 
741 void
742 SpectrogramLayer::preferenceChanged(PropertyContainer::PropertyName name)
743 {
744  SVDEBUG << "SpectrogramLayer::preferenceChanged(" << name << ")" << endl;
745 
746  if (name == "Window Type") {
747  setWindowType(Preferences::getInstance()->getWindowType());
748  return;
749  }
750  if (name == "Spectrogram Y Smoothing") {
753  emit layerParametersChanged();
754  }
755  if (name == "Spectrogram X Smoothing") {
758  emit layerParametersChanged();
759  }
760  if (name == "Tuning Frequency") {
761  emit layerParametersChanged();
762  }
763 }
764 
765 void
767 {
768  if (m_channel == ch) return;
769 
771  m_channel = ch;
773 
774  emit layerParametersChanged();
775 }
776 
777 int
779 {
780  return m_channel;
781 }
782 
783 int
785 {
786  return m_windowSize * m_oversampling;
787 }
788 
789 void
791 {
792  if (m_windowSize == ws) return;
794  m_windowSize = ws;
796  emit layerParametersChanged();
797 }
798 
799 int
801 {
802  return m_windowSize;
803 }
804 
805 void
807 {
808  if (m_windowHopLevel == v) return;
810  m_windowHopLevel = v;
812  emit layerParametersChanged();
813 }
814 
815 int
817 {
818  return m_windowHopLevel;
819 }
820 
821 void
823 {
824  if (m_oversampling == oversampling) return;
826  m_oversampling = oversampling;
828  emit layerParametersChanged();
829 }
830 
831 int
833 {
834  return m_oversampling;
835 }
836 
837 void
839 {
840  if (m_windowType == w) return;
841 
843 
844  m_windowType = w;
845 
847 
848  emit layerParametersChanged();
849 }
850 
851 WindowType
853 {
854  return m_windowType;
855 }
856 
857 void
859 {
860 // SVDEBUG << "SpectrogramLayer::setGain(" << gain << ") (my gain is now "
861 // << m_gain << ")" << endl;
862 
863  if (m_gain == gain) return;
864 
866 
867  m_gain = gain;
868 
869  emit layerParametersChanged();
870 }
871 
872 float
874 {
875  return m_gain;
876 }
877 
878 void
880 {
881  if (m_threshold == threshold) return;
882 
884 
885  m_threshold = threshold;
886 
887  emit layerParametersChanged();
888 }
889 
890 float
892 {
893  return m_threshold;
894 }
895 
896 void
898 {
899  if (m_minFrequency == mf) return;
900 
901  if (m_verticallyFixed) {
902  throw std::logic_error("setMinFrequency called with value differing from the default, on SpectrogramLayer with verticallyFixed true");
903  }
904 
905 // SVDEBUG << "SpectrogramLayer::setMinFrequency: " << mf << endl;
906 
909 
910  m_minFrequency = mf;
911 
912  emit layerParametersChanged();
913 }
914 
915 int
917 {
918  return m_minFrequency;
919 }
920 
921 void
923 {
924  if (m_maxFrequency == mf) return;
925 
926  if (m_verticallyFixed) {
927  throw std::logic_error("setMaxFrequency called with value differing from the default, on SpectrogramLayer with verticallyFixed true");
928  }
929 
930 // SVDEBUG << "SpectrogramLayer::setMaxFrequency: " << mf << endl;
931 
934 
935  m_maxFrequency = mf;
936 
937  emit layerParametersChanged();
938 }
939 
940 int
942 {
943  return m_maxFrequency;
944 }
945 
946 void
948 {
949  if (r < 0) r = 0;
950  if (r > 256) r = 256;
951  int distance = r - m_colourRotation;
952 
953  if (distance != 0) {
954  m_colourRotation = r;
955  }
956 
957  // Initially the idea with colour rotation was that we would just
958  // rotate the palette of an already-generated cache. That's not
959  // really practical now that cacheing is handled in a separate
960  // class in which the main cache no longer has a palette.
962 
963  emit layerParametersChanged();
964 }
965 
966 void
968 {
969  if (m_colourScale == colourScale) return;
970 
972 
973  m_colourScale = colourScale;
974 
975  emit layerParametersChanged();
976 }
977 
980 {
981  return m_colourScale;
982 }
983 
984 void
986 {
987  if (m_colourScaleMultiple == multiple) return;
988 
990 
991  m_colourScaleMultiple = multiple;
992 
993  emit layerParametersChanged();
994 }
995 
996 double
998 {
999  return m_colourScaleMultiple;
1000 }
1001 
1002 void
1004 {
1005  if (m_colourMap == map) return;
1006 
1008 
1009  m_colourMap = map;
1010 
1011  emit layerParametersChanged();
1012 }
1013 
1014 int
1016 {
1017  return m_colourMap;
1018 }
1019 
1020 void
1022 {
1023  if (m_binScale == binScale) return;
1024 
1026  m_binScale = binScale;
1027 
1028  emit layerParametersChanged();
1029 }
1030 
1031 BinScale
1033 {
1034  return m_binScale;
1035 }
1036 
1037 void
1039 {
1040  if (m_binDisplay == binDisplay) return;
1041 
1043  m_binDisplay = binDisplay;
1044 
1045  emit layerParametersChanged();
1046 }
1047 
1048 BinDisplay
1050 {
1051  return m_binDisplay;
1052 }
1053 
1054 void
1056 {
1057  if (m_normalization == n) return;
1058 
1061  m_normalization = n;
1062 
1063  emit layerParametersChanged();
1064 }
1065 
1066 ColumnNormalization
1068 {
1069  return m_normalization;
1070 }
1071 
1072 void
1074 {
1075  if (m_normalizeVisibleArea == n) return;
1076 
1080 
1081  emit layerParametersChanged();
1082 }
1083 
1084 bool
1086 {
1087  return m_normalizeVisibleArea;
1088 }
1089 
1090 void
1092 {
1093  if (dormant) {
1094 
1095 #ifdef DEBUG_SPECTROGRAM_REPAINT
1096  cerr << "SpectrogramLayer::setLayerDormant(" << dormant << ")"
1097  << endl;
1098 #endif
1099 
1100  if (isLayerDormant(v)) {
1101  return;
1102  }
1103 
1104  Layer::setLayerDormant(v, true);
1105 
1107 
1108  } else {
1109 
1110  Layer::setLayerDormant(v, false);
1111  }
1112 }
1113 
1114 bool
1116 {
1117  // we do our own cacheing, and don't want to be responsible for
1118  // guaranteeing to get an invisible seam if someone else scrolls
1119  // us and we just fill in
1120  return false;
1121 }
1122 
1123 void
1125 {
1126 #ifdef DEBUG_SPECTROGRAM_REPAINT
1127  cerr << "SpectrogramLayer::cacheInvalid()" << endl;
1128 #endif
1129 
1132 }
1133 
1134 void
1136  ModelId,
1137 #ifdef DEBUG_SPECTROGRAM_REPAINT
1138  sv_frame_t from, sv_frame_t to
1139 #else
1140  sv_frame_t , sv_frame_t
1141 #endif
1142  )
1143 {
1144 #ifdef DEBUG_SPECTROGRAM_REPAINT
1145  cerr << "SpectrogramLayer::cacheInvalid(" << from << ", " << to << ")" << endl;
1146 #endif
1147 
1148  // We used to call invalidateMagnitudes(from, to) to invalidate
1149  // only those caches whose views contained some of the (from, to)
1150  // range. That's the right thing to do; it has been lost in
1151  // pulling out the image cache code, but it might not matter very
1152  // much, since the underlying models for spectrogram layers don't
1153  // change very often. Let's see.
1156 }
1157 
1158 bool
1160 {
1161  return ColourMapper(m_colourMap, m_colourInverted, 1.f, 255.f)
1162  .hasLightBackground();
1163 }
1164 
1165 double
1167 {
1168  auto model = ModelById::getAs<DenseTimeValueModel>(m_model);
1169  if (!model) return 0.0;
1170 
1171  sv_samplerate_t sr = model->getSampleRate();
1172  double minf = double(sr) / getFFTSize();
1173 
1174  if (m_minFrequency > 0.0) {
1175  int minbin = int((double(m_minFrequency) * getFFTSize()) / sr + 0.01);
1176  if (minbin < 1) minbin = 1;
1177  minf = minbin * sr / getFFTSize();
1178  }
1179 
1180  return minf;
1181 }
1182 
1183 double
1185 {
1186  auto model = ModelById::getAs<DenseTimeValueModel>(m_model);
1187  if (!model) return 0.0;
1188 
1189  sv_samplerate_t sr = model->getSampleRate();
1190  double maxf = double(sr) / 2;
1191 
1192  if (m_maxFrequency > 0.0) {
1193  int maxbin = int((double(m_maxFrequency) * getFFTSize()) / sr + 0.1);
1194  if (maxbin > getFFTSize() / 2) maxbin = getFFTSize() / 2;
1195  maxf = maxbin * sr / getFFTSize();
1196  }
1197 
1198  return maxf;
1199 }
1200 
1201 bool
1202 SpectrogramLayer::getYBinRange(LayerGeometryProvider *v, int y, double &q0, double &q1) const
1203 {
1204  Profiler profiler("SpectrogramLayer::getYBinRange");
1205  int h = v->getPaintHeight();
1206  if (y < 0 || y >= h) return false;
1207  q0 = getBinForY(v, y);
1208  q1 = getBinForY(v, y-1);
1209  return true;
1210 }
1211 
1212 double
1214 {
1215  auto model = ModelById::getAs<DenseTimeValueModel>(m_model);
1216  if (!model) return 0.0;
1217 
1218  double minf = getEffectiveMinFrequency();
1219  double maxf = getEffectiveMaxFrequency();
1220  bool logarithmic = (m_binScale == BinScale::Log);
1221  sv_samplerate_t sr = model->getSampleRate();
1222 
1223  double freq = (bin * sr) / getFFTSize();
1224 
1225  double y = v->getYForFrequency(freq, minf, maxf, logarithmic);
1226 
1227  return y;
1228 }
1229 
1230 double
1232 {
1233  auto model = ModelById::getAs<DenseTimeValueModel>(m_model);
1234  if (!model) return 0.0;
1235 
1236  sv_samplerate_t sr = model->getSampleRate();
1237  double minf = getEffectiveMinFrequency();
1238  double maxf = getEffectiveMaxFrequency();
1239 
1240  bool logarithmic = (m_binScale == BinScale::Log);
1241 
1242  double freq = v->getFrequencyForY(y, minf, maxf, logarithmic);
1243 
1244  // Now map on to ("proportion of") actual bins
1245  double bin = (freq * getFFTSize()) / sr;
1246 
1247  return bin;
1248 }
1249 
1250 bool
1251 SpectrogramLayer::getXBinRange(LayerGeometryProvider *v, int x, double &s0, double &s1) const
1252 {
1253  auto model = ModelById::getAs<DenseTimeValueModel>(m_model);
1254  if (!model) return false;
1255 
1256  sv_frame_t modelStart = model->getStartFrame();
1257  sv_frame_t modelEnd = model->getEndFrame();
1258 
1259  // Each pixel column covers an exact range of sample frames:
1260  sv_frame_t f0 = v->getFrameForX(x) - modelStart;
1261  sv_frame_t f1 = v->getFrameForX(x + 1) - modelStart - 1;
1262 
1263  if (f1 < int(modelStart) || f0 > int(modelEnd)) {
1264  return false;
1265  }
1266 
1267  // And that range may be drawn from a possibly non-integral
1268  // range of spectrogram windows:
1269 
1270  int windowIncrement = getWindowIncrement();
1271  s0 = double(f0) / windowIncrement;
1272  s1 = double(f1) / windowIncrement;
1273 
1274  return true;
1275 }
1276 
1277 bool
1278 SpectrogramLayer::getXBinSourceRange(LayerGeometryProvider *v, int x, RealTime &min, RealTime &max) const
1279 {
1280  auto model = ModelById::getAs<DenseTimeValueModel>(m_model);
1281  if (!model) return false;
1282 
1283  double s0 = 0, s1 = 0;
1284  if (!getXBinRange(v, x, s0, s1)) return false;
1285 
1286  int s0i = int(s0 + 0.001);
1287  int s1i = int(s1);
1288 
1289  int windowIncrement = getWindowIncrement();
1290  int w0 = s0i * windowIncrement - (m_windowSize - windowIncrement)/2;
1291  int w1 = s1i * windowIncrement + windowIncrement +
1292  (m_windowSize - windowIncrement)/2 - 1;
1293 
1294  min = RealTime::frame2RealTime(w0, model->getSampleRate());
1295  max = RealTime::frame2RealTime(w1, model->getSampleRate());
1296  return true;
1297 }
1298 
1299 bool
1300 SpectrogramLayer::getYBinSourceRange(LayerGeometryProvider *v, int y, double &freqMin, double &freqMax)
1301 const
1302 {
1303  auto model = ModelById::getAs<DenseTimeValueModel>(m_model);
1304  if (!model) return false;
1305 
1306  double q0 = 0, q1 = 0;
1307  if (!getYBinRange(v, y, q0, q1)) return false;
1308 
1309  int q0i = int(q0 + 0.001);
1310  int q1i = int(q1);
1311 
1312  sv_samplerate_t sr = model->getSampleRate();
1313 
1314  for (int q = q0i; q <= q1i; ++q) {
1315  if (q == q0i) freqMin = (sr * q) / getFFTSize();
1316  if (q == q1i) freqMax = (sr * (q+1)) / getFFTSize();
1317  }
1318  return true;
1319 }
1320 
1321 bool
1323  double &freqMin, double &freqMax,
1324  double &adjFreqMin, double &adjFreqMax)
1325 const
1326 {
1327  auto model = ModelById::getAs<DenseTimeValueModel>(m_model);
1328  if (!model || !model->isOK() || !model->isReady()) {
1329  return false;
1330  }
1331 
1332  auto fft = ModelById::getAs<FFTModel>(m_fftModel);
1333  if (!fft) return false;
1334 
1335  double s0 = 0, s1 = 0;
1336  if (!getXBinRange(v, x, s0, s1)) return false;
1337 
1338  double q0 = 0, q1 = 0;
1339  if (!getYBinRange(v, y, q0, q1)) return false;
1340 
1341  int s0i = int(s0 + 0.001);
1342  int s1i = int(s1);
1343 
1344  int q0i = int(q0 + 0.001);
1345  int q1i = int(q1);
1346 
1347  sv_samplerate_t sr = model->getSampleRate();
1348 
1349  bool haveAdj = false;
1350 
1351  bool peaksOnly = (m_binDisplay == BinDisplay::PeakBins ||
1353 
1354  for (int q = q0i; q <= q1i; ++q) {
1355 
1356  for (int s = s0i; s <= s1i; ++s) {
1357 
1358  double binfreq = (double(sr) * q) / getFFTSize();
1359  if (q == q0i) freqMin = binfreq;
1360  if (q == q1i) freqMax = binfreq;
1361 
1362  if (peaksOnly && !fft->isLocalPeak(s, q)) continue;
1363 
1364  if (!fft->isOverThreshold
1365  (s, q, float(m_threshold * double(getFFTSize())/2.0))) {
1366  continue;
1367  }
1368 
1369  double freq = binfreq;
1370 
1371  if (s < int(fft->getWidth()) - 1) {
1372 
1373  fft->estimateStableFrequency(s, q, freq);
1374 
1375  if (!haveAdj || freq < adjFreqMin) adjFreqMin = freq;
1376  if (!haveAdj || freq > adjFreqMax) adjFreqMax = freq;
1377 
1378  haveAdj = true;
1379  }
1380  }
1381  }
1382 
1383  if (!haveAdj) {
1384  adjFreqMin = adjFreqMax = 0.0;
1385  }
1386 
1387  return haveAdj;
1388 }
1389 
1390 bool
1392  double &min, double &max,
1393  double &phaseMin, double &phaseMax) const
1394 {
1395  auto model = ModelById::getAs<DenseTimeValueModel>(m_model);
1396  if (!model || !model->isOK() || !model->isReady()) {
1397  return false;
1398  }
1399 
1400  double q0 = 0, q1 = 0;
1401  if (!getYBinRange(v, y, q0, q1)) return false;
1402 
1403  double s0 = 0, s1 = 0;
1404  if (!getXBinRange(v, x, s0, s1)) return false;
1405 
1406  int q0i = int(q0 + 0.001);
1407  int q1i = int(q1);
1408 
1409  int s0i = int(s0 + 0.001);
1410  int s1i = int(s1);
1411 
1412  bool rv = false;
1413 
1414  auto fft = ModelById::getAs<FFTModel>(m_fftModel);
1415 
1416  if (fft) {
1417 
1418  int cw = fft->getWidth();
1419  int ch = fft->getHeight();
1420 
1421  min = 0.0;
1422  max = 0.0;
1423  phaseMin = 0.0;
1424  phaseMax = 0.0;
1425  bool have = false;
1426 
1427  for (int q = q0i; q <= q1i; ++q) {
1428  for (int s = s0i; s <= s1i; ++s) {
1429  if (s >= 0 && q >= 0 && s < cw && q < ch) {
1430 
1431  double value;
1432 
1433  value = fft->getPhaseAt(s, q);
1434  if (!have || value < phaseMin) { phaseMin = value; }
1435  if (!have || value > phaseMax) { phaseMax = value; }
1436 
1437  value = fft->getMagnitudeAt(s, q) / (getFFTSize()/2.0);
1438  if (!have || value < min) { min = value; }
1439  if (!have || value > max) { max = value; }
1440 
1441  have = true;
1442  }
1443  }
1444  }
1445 
1446  if (have) {
1447  rv = true;
1448  }
1449  }
1450 
1451  return rv;
1452 }
1453 
1454 void
1456 {
1457  SVDEBUG << "SpectrogramLayer::recreateFFTModel called" << endl;
1458 
1459  { // scope, avoid hanging on to this pointer
1460  auto model = ModelById::getAs<DenseTimeValueModel>(m_model);
1461  if (!model || !model->isOK()) {
1463  return;
1464  }
1465  }
1466 
1468 
1469  auto newFFTModel = std::make_shared<FFTModel>(m_model,
1470  m_channel,
1471  m_windowType,
1472  m_windowSize,
1474  getFFTSize());
1475 
1476  if (!newFFTModel->isOK()) {
1477  QMessageBox::critical
1478  (nullptr, tr("FFT cache failed"),
1479  tr("Failed to create the FFT model for this spectrogram.\n"
1480  "There may be insufficient memory or disc space to continue."));
1481  return;
1482  }
1483 
1484  if (m_verticallyFixed) {
1485  newFFTModel->setMaximumFrequency(getMaxFrequency());
1486  }
1487 
1488  m_fftModel = ModelById::add(newFFTModel);
1489 
1490  bool createWholeCache = false;
1491  checkCacheSpace(&m_peakCacheDivisor, &createWholeCache);
1492 
1493  if (createWholeCache) {
1494 
1495  auto whole = std::make_shared<Dense3DModelPeakCache>(m_fftModel, 1);
1496  m_wholeCache = ModelById::add(whole);
1497 
1498  auto peaks = std::make_shared<Dense3DModelPeakCache>(m_fftModel,
1500  m_peakCache = ModelById::add(peaks);
1501 
1502  } else {
1503 
1504  auto peaks = std::make_shared<Dense3DModelPeakCache>(m_fftModel,
1506  m_peakCache = ModelById::add(peaks);
1507  }
1508 }
1509 
1510 void
1511 SpectrogramLayer::checkCacheSpace(int *suggestedPeakDivisor,
1512  bool *createWholeCache) const
1513 {
1514  *suggestedPeakDivisor = 8;
1515  *createWholeCache = false;
1516 
1517  auto fftModel = ModelById::getAs<FFTModel>(m_fftModel);
1518  if (!fftModel) return;
1519 
1520  size_t sz =
1521  size_t(fftModel->getWidth()) *
1522  size_t(fftModel->getHeight()) *
1523  sizeof(float);
1524 
1525  try {
1526  SVDEBUG << "Requesting advice from StorageAdviser on whether to create whole-model cache" << endl;
1527  // The lower amount here is the amount required for the
1528  // slightly higher-resolution version of the peak cache
1529  // without a whole-model cache; the higher amount is that for
1530  // the whole-model cache. The factors of 1024 are because
1531  // StorageAdviser rather stupidly works in kilobytes
1532  StorageAdviser::Recommendation recommendation =
1533  StorageAdviser::recommend
1534  (StorageAdviser::Criteria(StorageAdviser::SpeedCritical |
1535  StorageAdviser::PrecisionCritical |
1536  StorageAdviser::FrequentLookupLikely),
1537  (sz / 8) / 1024, sz / 1024);
1538  if (recommendation & StorageAdviser::UseDisc) {
1539  SVDEBUG << "Seems inadvisable to create whole-model cache" << endl;
1540  } else if (recommendation & StorageAdviser::ConserveSpace) {
1541  SVDEBUG << "Seems inadvisable to create whole-model cache but acceptable to use the slightly higher-resolution peak cache" << endl;
1542  *suggestedPeakDivisor = 4;
1543  } else {
1544  SVDEBUG << "Seems fine to create whole-model cache" << endl;
1545  *createWholeCache = true;
1546  }
1547  } catch (const InsufficientDiscSpace &) {
1548  SVDEBUG << "Seems like a terrible idea to create whole-model cache" << endl;
1549  }
1550 }
1551 
1552 ModelId
1554 {
1555  return m_fftModel;
1556 }
1557 
1558 void
1560 {
1561 #ifdef DEBUG_SPECTROGRAM
1562  cerr << "SpectrogramLayer::invalidateMagnitudes called" << endl;
1563 #endif
1564  m_viewMags.clear();
1565 }
1566 
1567 void
1569 {
1570  m_synchronous = synchronous;
1571 }
1572 
1575 {
1576  int viewId = v->getId();
1577 
1578  if (m_renderers.find(viewId) == m_renderers.end()) {
1579 
1581  sources.verticalBinLayer = this;
1582  sources.fft = m_fftModel;
1583  sources.source = sources.fft;
1584  if (!m_peakCache.isNone()) sources.peakCaches.push_back(m_peakCache);
1585  if (!m_wholeCache.isNone()) sources.peakCaches.push_back(m_wholeCache);
1586 
1587  ColourScale::Parameters cparams;
1588  cparams.colourMap = m_colourMap;
1589  cparams.scaleType = m_colourScale;
1590  cparams.multiple = m_colourScaleMultiple;
1591 
1593  cparams.gain = m_gain;
1594  cparams.threshold = m_threshold;
1595  }
1596 
1597  double minValue = 0.0f;
1598  double maxValue = 1.0f;
1599 
1600  if (m_normalizeVisibleArea && m_viewMags[viewId].isSet()) {
1601  minValue = m_viewMags[viewId].getMin();
1602  maxValue = m_viewMags[viewId].getMax();
1603  } else if (m_colourScale == ColourScaleType::Linear &&
1604  m_normalization == ColumnNormalization::None) {
1605  maxValue = 0.1f;
1606  }
1607 
1608  if (maxValue <= minValue) {
1609  maxValue = minValue + 0.1f;
1610  }
1611  if (maxValue <= m_threshold) {
1612  maxValue = m_threshold + 0.1f;
1613  }
1614 
1615  cparams.minValue = minValue;
1616  cparams.maxValue = maxValue;
1617 
1618  m_lastRenderedMags[viewId] = MagnitudeRange(float(minValue),
1619  float(maxValue));
1620 
1622  params.colourScale = ColourScale(cparams);
1623  params.normalization = m_normalization;
1624  params.binDisplay = m_binDisplay;
1625  params.binScale = m_binScale;
1626  params.alwaysOpaque = true;
1627  params.invertVertical = false;
1628  params.scaleFactor = 1.0;
1630 
1632  m_normalization != ColumnNormalization::Hybrid) {
1633  params.scaleFactor *= 2.f / float(getWindowSize());
1634  }
1635 
1636  Preferences::SpectrogramSmoothing smoothing =
1637  Preferences::getInstance()->getSpectrogramSmoothing();
1638  params.interpolate =
1639  (smoothing != Preferences::NoSpectrogramSmoothing);
1640 
1641  m_renderers[viewId] = new Colour3DPlotRenderer(sources, params);
1642 
1646  }
1647 
1648  return m_renderers[viewId];
1649 }
1650 
1651 void
1653 {
1654  Colour3DPlotRenderer *renderer = getRenderer(v);
1655 
1657  MagnitudeRange magRange;
1658  int viewId = v->getId();
1659 
1660  bool continuingPaint = !renderer->geometryChanged(v);
1661 
1662  if (continuingPaint) {
1663  magRange = m_viewMags[viewId];
1664  }
1665 
1666  if (m_synchronous) {
1667 
1668  result = renderer->render(v, paint, rect);
1669 
1670  } else {
1671 
1672  result = renderer->renderTimeConstrained(v, paint, rect);
1673 
1674 #ifdef DEBUG_SPECTROGRAM_REPAINT
1675  cerr << "rect width from this paint: " << result.rendered.width()
1676  << ", mag range in this paint: " << result.range.getMin() << " -> "
1677  << result.range.getMax() << endl;
1678 #endif
1679 
1680  QRect uncached = renderer->getLargestUncachedRect(v);
1681  if (uncached.width() > 0) {
1682  v->updatePaintRect(uncached);
1683  }
1684  }
1685 
1686  magRange.sample(result.range);
1687 
1688  if (magRange.isSet()) {
1689  if (m_viewMags[viewId] != magRange) {
1690  m_viewMags[viewId] = magRange;
1691 #ifdef DEBUG_SPECTROGRAM_REPAINT
1692  cerr << "mag range in this view has changed: "
1693  << magRange.getMin() << " -> " << magRange.getMax() << endl;
1694 #endif
1695  }
1696  }
1697 
1698  if (!continuingPaint && m_normalizeVisibleArea &&
1699  m_viewMags[viewId] != m_lastRenderedMags[viewId]) {
1700 #ifdef DEBUG_SPECTROGRAM_REPAINT
1701  cerr << "mag range has changed from last rendered range: re-rendering"
1702  << endl;
1703 #endif
1704  delete m_renderers[viewId];
1705  m_renderers.erase(viewId);
1706  v->updatePaintRect(v->getPaintRect());
1707  }
1708 }
1709 
1710 void
1711 SpectrogramLayer::paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const
1712 {
1713  Profiler profiler("SpectrogramLayer::paint", false);
1714 
1715 #ifdef DEBUG_SPECTROGRAM_REPAINT
1716  cerr << "SpectrogramLayer::paint() entering: m_model is " << m_model << ", zoom level is " << v->getZoomLevel() << endl;
1717 
1718  cerr << "SpectrogramLayer::paint(): rect is " << rect.x() << "," << rect.y() << " " << rect.width() << "x" << rect.height() << endl;
1719 #endif
1720 
1721  auto model = ModelById::getAs<DenseTimeValueModel>(m_model);
1722  if (!model || !model->isOK() || !model->isReady()) {
1723  return;
1724  }
1725 
1726  paintWithRenderer(v, paint, rect);
1727 
1728  illuminateLocalFeatures(v, paint);
1729 }
1730 
1731 void
1733 {
1734  Profiler profiler("SpectrogramLayer::illuminateLocalFeatures");
1735 
1736  auto model = ModelById::getAs<DenseTimeValueModel>(m_model);
1737 
1738  QPoint localPos;
1739  if (!v->shouldIlluminateLocalFeatures(this, localPos) || !model) {
1740  return;
1741  }
1742 
1743 #ifdef DEBUG_SPECTROGRAM_REPAINT
1744  cerr << "SpectrogramLayer: illuminateLocalFeatures("
1745  << localPos.x() << "," << localPos.y() << ")" << endl;
1746 #endif
1747 
1748  double s0, s1;
1749  double f0, f1;
1750 
1751  if (getXBinRange(v, localPos.x(), s0, s1) &&
1752  getYBinSourceRange(v, localPos.y(), f0, f1)) {
1753 
1754  int s0i = int(s0 + 0.001);
1755  int s1i = int(s1);
1756 
1757  int x0 = v->getXForFrame(s0i * getWindowIncrement());
1758  int x1 = v->getXForFrame((s1i + 1) * getWindowIncrement());
1759 
1760  int y1 = int(getYForFrequency(v, f1));
1761  int y0 = int(getYForFrequency(v, f0));
1762 
1763 #ifdef DEBUG_SPECTROGRAM_REPAINT
1764  cerr << "SpectrogramLayer: illuminate "
1765  << x0 << "," << y1 << " -> " << x1 << "," << y0 << endl;
1766 #endif
1767 
1768  paint.setPen(v->getForeground());
1769 
1771 
1772  paint.drawRect(x0, y1, x1 - x0 + 1, y0 - y1 + 1);
1773  }
1774 }
1775 
1776 double
1778 {
1779  return v->getYForFrequency(frequency,
1783 }
1784 
1785 double
1787 {
1788  return v->getFrequencyForY(y,
1792 }
1793 
1794 int
1796 {
1797  auto fftModel = ModelById::getAs<FFTModel>(m_fftModel);
1798  if (!fftModel) return 100;
1799  int completion = fftModel->getCompletion();
1800 #ifdef DEBUG_SPECTROGRAM_REPAINT
1801  cerr << "SpectrogramLayer::getCompletion: completion = " << completion << endl;
1802 #endif
1803  return completion;
1804 }
1805 
1806 QString
1808 {
1809  auto fftModel = ModelById::getAs<FFTModel>(m_fftModel);
1810  if (!fftModel) return "";
1811  return fftModel->getError();
1812 }
1813 
1814 bool
1815 SpectrogramLayer::getValueExtents(double &min, double &max,
1816  bool &logarithmic, QString &unit) const
1817 {
1818  auto model = ModelById::getAs<DenseTimeValueModel>(m_model);
1819  if (!model) return false;
1820 
1821  sv_samplerate_t sr = model->getSampleRate();
1822  min = double(sr) / getFFTSize();
1823  max = double(sr) / 2;
1824 
1825  logarithmic = (m_binScale == BinScale::Log);
1826  unit = "Hz";
1827  return true;
1828 }
1829 
1830 bool
1831 SpectrogramLayer::getDisplayExtents(double &min, double &max) const
1832 {
1833  min = getEffectiveMinFrequency();
1834  max = getEffectiveMaxFrequency();
1835 
1836 // SVDEBUG << "SpectrogramLayer::getDisplayExtents: " << min << "->" << max << endl;
1837  return true;
1838 }
1839 
1840 bool
1842 {
1843  auto model = ModelById::getAs<DenseTimeValueModel>(m_model);
1844  if (!model) return false;
1845 
1846 // SVDEBUG << "SpectrogramLayer::setDisplayExtents: " << min << "->" << max << endl;
1847 
1848  if (min < 0) min = 0;
1849  if (max > model->getSampleRate()/2.0) max = model->getSampleRate()/2.0;
1850 
1851  int minf = int(lrint(min));
1852  int maxf = int(lrint(max));
1853 
1854  if (m_minFrequency == minf && m_maxFrequency == maxf) return true;
1855 
1858 
1859  if (m_verticallyFixed &&
1860  (m_minFrequency != minf || m_maxFrequency != maxf)) {
1861  throw std::logic_error("setDisplayExtents called with values differing from the defaults, on SpectrogramLayer with verticallyFixed true");
1862  }
1863 
1864  m_minFrequency = minf;
1865  m_maxFrequency = maxf;
1866 
1867  emit layerParametersChanged();
1868 
1869  int vs = getCurrentVerticalZoomStep();
1870  if (vs != m_lastEmittedZoomStep) {
1871  emit verticalZoomChanged();
1872  m_lastEmittedZoomStep = vs;
1873  }
1874 
1875  return true;
1876 }
1877 
1878 bool
1880  double &value, QString &unit) const
1881 {
1882  value = getFrequencyForY(v, y);
1883  unit = "Hz";
1884  return true;
1885 }
1886 
1887 bool
1889  sv_frame_t &frame,
1890  int &resolution,
1891  SnapType snap, int) const
1892 {
1893  resolution = getWindowIncrement();
1894  sv_frame_t left = (frame / resolution) * resolution;
1895  sv_frame_t right = left + resolution;
1896 
1897  switch (snap) {
1898  case SnapLeft: frame = left; break;
1899  case SnapRight: frame = right; break;
1900  case SnapNeighbouring:
1901  if (frame - left > right - frame) frame = right;
1902  else frame = left;
1903  break;
1904  }
1905 
1906  return true;
1907 }
1908 
1909 void
1911 {
1912  const Colour3DPlotRenderer *renderer = getRenderer(v);
1913  if (!renderer) return;
1914 
1915  QRect rect = renderer->findSimilarRegionExtents(e->pos());
1916  if (rect.isValid()) {
1917  MeasureRect mr;
1918  setMeasureRectFromPixrect(v, mr, rect);
1920  (new AddMeasurementRectCommand(this, mr));
1921  }
1922 }
1923 
1924 bool
1926  QPoint cursorPos,
1927  vector<QRect> &extents) const
1928 {
1929  // Qt 5.13 deprecates QFontMetrics::width(), but its suggested
1930  // replacement (horizontalAdvance) was only added in Qt 5.11
1931  // which is too new for us
1932 #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
1933 
1934  QRect vertical(cursorPos.x() - 12, 0, 12, v->getPaintHeight());
1935  extents.push_back(vertical);
1936 
1937  QRect horizontal(0, cursorPos.y(), cursorPos.x(), 1);
1938  extents.push_back(horizontal);
1939 
1940  int sw = getVerticalScaleWidth(v, m_haveDetailedScale, paint);
1941 
1942  QRect freq(sw, cursorPos.y() - paint.fontMetrics().ascent() - 2,
1943  paint.fontMetrics().width("123456 Hz") + 2,
1944  paint.fontMetrics().height());
1945  extents.push_back(freq);
1946 
1947  QRect pitch(sw, cursorPos.y() + 2,
1948  paint.fontMetrics().width("C#10+50c") + 2,
1949  paint.fontMetrics().height());
1950  extents.push_back(pitch);
1951 
1952  QRect rt(cursorPos.x(),
1953  v->getPaintHeight() - paint.fontMetrics().height() - 2,
1954  paint.fontMetrics().width("1234.567 s"),
1955  paint.fontMetrics().height());
1956  extents.push_back(rt);
1957 
1958  int w(paint.fontMetrics().width("1234567890") + 2);
1959  QRect frame(cursorPos.x() - w - 2,
1960  v->getPaintHeight() - paint.fontMetrics().height() - 2,
1961  w,
1962  paint.fontMetrics().height());
1963  extents.push_back(frame);
1964 
1965  return true;
1966 }
1967 
1968 void
1970  QPoint cursorPos) const
1971 {
1972  auto model = ModelById::getAs<DenseTimeValueModel>(m_model);
1973  if (!model) return;
1974 
1975  paint.save();
1976 
1977  int sw = getVerticalScaleWidth(v, m_haveDetailedScale, paint);
1978 
1979  QFont fn = paint.font();
1980  if (fn.pointSize() > 8) {
1981  fn.setPointSize(fn.pointSize() - 1);
1982  paint.setFont(fn);
1983  }
1984  paint.setPen(m_crosshairColour);
1985 
1986  paint.drawLine(0, cursorPos.y(), cursorPos.x() - 1, cursorPos.y());
1987  paint.drawLine(cursorPos.x(), 0, cursorPos.x(), v->getPaintHeight());
1988 
1989  double fundamental = getFrequencyForY(v, cursorPos.y());
1990 
1992  (v, paint,
1993  sw + 2,
1994  cursorPos.y() - 2,
1995  QString("%1 Hz").arg(fundamental),
1997 
1998  if (Pitch::isFrequencyInMidiRange(fundamental)) {
1999  QString pitchLabel = Pitch::getPitchLabelForFrequency(fundamental);
2001  (v, paint,
2002  sw + 2,
2003  cursorPos.y() + paint.fontMetrics().ascent() + 2,
2004  pitchLabel,
2006  }
2007 
2008  sv_frame_t frame = v->getFrameForX(cursorPos.x());
2009  RealTime rt = RealTime::frame2RealTime(frame, model->getSampleRate());
2010  QString rtLabel = QString("%1 s").arg(rt.toText(true).c_str());
2011  QString frameLabel = QString("%1").arg(frame);
2013  (v, paint,
2014  cursorPos.x() - paint.fontMetrics().width(frameLabel) - 2,
2015  v->getPaintHeight() - 2,
2016  frameLabel,
2019  (v, paint,
2020  cursorPos.x() + 2,
2021  v->getPaintHeight() - 2,
2022  rtLabel,
2024 
2025  int harmonic = 2;
2026 
2027  while (harmonic < 100) {
2028 
2029  int hy = int(lrint(getYForFrequency(v, fundamental * harmonic)));
2030  if (hy < 0 || hy > v->getPaintHeight()) break;
2031 
2032  int len = 7;
2033 
2034  if (harmonic % 2 == 0) {
2035  if (harmonic % 4 == 0) {
2036  len = 12;
2037  } else {
2038  len = 10;
2039  }
2040  }
2041 
2042  paint.drawLine(cursorPos.x() - len,
2043  hy,
2044  cursorPos.x(),
2045  hy);
2046 
2047  ++harmonic;
2048  }
2049 
2050  paint.restore();
2051 }
2052 
2053 QString
2055 {
2056  int x = pos.x();
2057  int y = pos.y();
2058 
2059  auto model = ModelById::getAs<DenseTimeValueModel>(m_model);
2060  if (!model || !model->isOK()) return "";
2061 
2062  double magMin = 0, magMax = 0;
2063  double phaseMin = 0, phaseMax = 0;
2064  double freqMin = 0, freqMax = 0;
2065  double adjFreqMin = 0, adjFreqMax = 0;
2066  QString pitchMin, pitchMax;
2067  RealTime rtMin, rtMax;
2068 
2069  bool haveValues = false;
2070 
2071  if (!getXBinSourceRange(v, x, rtMin, rtMax)) {
2072  return "";
2073  }
2074  if (getXYBinSourceRange(v, x, y, magMin, magMax, phaseMin, phaseMax)) {
2075  haveValues = true;
2076  }
2077 
2078  QString adjFreqText = "", adjPitchText = "";
2079 
2081 
2082  if (!getAdjustedYBinSourceRange(v, x, y, freqMin, freqMax,
2083  adjFreqMin, adjFreqMax)) {
2084  return "";
2085  }
2086 
2087  if (adjFreqMin != adjFreqMax) {
2088  adjFreqText = tr("Peak Frequency:\t%1 - %2 Hz\n")
2089  .arg(adjFreqMin).arg(adjFreqMax);
2090  } else {
2091  adjFreqText = tr("Peak Frequency:\t%1 Hz\n")
2092  .arg(adjFreqMin);
2093  }
2094 
2095  QString pmin = Pitch::getPitchLabelForFrequency(adjFreqMin);
2096  QString pmax = Pitch::getPitchLabelForFrequency(adjFreqMax);
2097 
2098  if (pmin != pmax) {
2099  adjPitchText = tr("Peak Pitch:\t%3 - %4\n").arg(pmin).arg(pmax);
2100  } else {
2101  adjPitchText = tr("Peak Pitch:\t%2\n").arg(pmin);
2102  }
2103 
2104  } else {
2105 
2106  if (!getYBinSourceRange(v, y, freqMin, freqMax)) return "";
2107  }
2108 
2109  QString text;
2110 
2111  if (rtMin != rtMax) {
2112  text += tr("Time:\t%1 - %2\n")
2113  .arg(rtMin.toText(true).c_str())
2114  .arg(rtMax.toText(true).c_str());
2115  } else {
2116  text += tr("Time:\t%1\n")
2117  .arg(rtMin.toText(true).c_str());
2118  }
2119 
2120  if (freqMin != freqMax) {
2121  text += tr("%1Bin Frequency:\t%2 - %3 Hz\n%4Bin Pitch:\t%5 - %6\n")
2122  .arg(adjFreqText)
2123  .arg(freqMin)
2124  .arg(freqMax)
2125  .arg(adjPitchText)
2126  .arg(Pitch::getPitchLabelForFrequency(freqMin))
2127  .arg(Pitch::getPitchLabelForFrequency(freqMax));
2128  } else {
2129  text += tr("%1Bin Frequency:\t%2 Hz\n%3Bin Pitch:\t%4\n")
2130  .arg(adjFreqText)
2131  .arg(freqMin)
2132  .arg(adjPitchText)
2133  .arg(Pitch::getPitchLabelForFrequency(freqMin));
2134  }
2135 
2136  if (haveValues) {
2137  double dbMin = AudioLevel::multiplier_to_dB(magMin);
2138  double dbMax = AudioLevel::multiplier_to_dB(magMax);
2139  QString dbMinString;
2140  QString dbMaxString;
2141  if (dbMin == AudioLevel::DB_FLOOR) {
2142  dbMinString = Strings::minus_infinity;
2143  } else {
2144  dbMinString = QString("%1").arg(lrint(dbMin));
2145  }
2146  if (dbMax == AudioLevel::DB_FLOOR) {
2147  dbMaxString = Strings::minus_infinity;
2148  } else {
2149  dbMaxString = QString("%1").arg(lrint(dbMax));
2150  }
2151  if (lrint(dbMin) != lrint(dbMax)) {
2152  text += tr("dB:\t%1 - %2").arg(dbMinString).arg(dbMaxString);
2153  } else {
2154  text += tr("dB:\t%1").arg(dbMinString);
2155  }
2156  if (phaseMin != phaseMax) {
2157  text += tr("\nPhase:\t%1 - %2").arg(phaseMin).arg(phaseMax);
2158  } else {
2159  text += tr("\nPhase:\t%1").arg(phaseMin);
2160  }
2161  }
2162 
2163  return text;
2164 }
2165 
2166 int
2168 {
2169  int cw;
2170 
2171  cw = paint.fontMetrics().width("-80dB");
2172 
2173  return cw;
2174 }
2175 
2176 int
2178 {
2179  auto model = ModelById::getAs<DenseTimeValueModel>(m_model);
2180  if (!model || !model->isOK()) return 0;
2181 
2182  int cw = 0;
2183  if (detailed) cw = getColourScaleWidth(paint);
2184 
2185  int tw = paint.fontMetrics().width(QString("%1")
2186  .arg(m_maxFrequency > 0 ?
2187  m_maxFrequency - 1 :
2188  model->getSampleRate() / 2));
2189 
2190  int fw = paint.fontMetrics().width(tr("43Hz"));
2191  if (tw < fw) tw = fw;
2192 
2193  int tickw = (m_binScale == BinScale::Log ? 10 : 4);
2194 
2195  return cw + tickw + tw + 13;
2196 }
2197 
2198 void
2200  QPainter &paint, QRect rect) const
2201 {
2202  auto model = ModelById::getAs<DenseTimeValueModel>(m_model);
2203  if (!model || !model->isOK()) {
2204  return;
2205  }
2206 
2207  Profiler profiler("SpectrogramLayer::paintVerticalScale");
2208 
2210 
2211  int h = rect.height(), w = rect.width();
2212  int textHeight = paint.fontMetrics().height();
2213 
2214  if (detailed && (h > textHeight * 3 + 10)) {
2215  paintDetailedScale(v, paint, rect);
2216  }
2217  m_haveDetailedScale = detailed;
2218 
2219  int tickw = (m_binScale == BinScale::Log ? 10 : 4);
2220  int pkw = (m_binScale == BinScale::Log ? 10 : 0);
2221 
2222  int bins = getFFTSize() / 2;
2223  sv_samplerate_t sr = model->getSampleRate();
2224 
2225  if (m_maxFrequency > 0) {
2226  bins = int((double(m_maxFrequency) * getFFTSize()) / sr + 0.1);
2227  if (bins > getFFTSize() / 2) bins = getFFTSize() / 2;
2228  }
2229 
2230  int cw = 0;
2231  if (detailed) cw = getColourScaleWidth(paint);
2232 
2233  int py = -1;
2234  int toff = -textHeight + paint.fontMetrics().ascent() + 2;
2235 
2236  paint.drawLine(cw + 7, 0, cw + 7, h);
2237 
2238  int bin = -1;
2239 
2240  for (int y = 0; y < v->getPaintHeight(); ++y) {
2241 
2242  double q0, q1;
2243  if (!getYBinRange(v, v->getPaintHeight() - y, q0, q1)) continue;
2244 
2245  int vy;
2246 
2247  if (int(q0) > bin) {
2248  vy = y;
2249  bin = int(q0);
2250  } else {
2251  continue;
2252  }
2253 
2254  int freq = int((sr * bin) / getFFTSize());
2255 
2256  if (py >= 0 && (vy - py) < textHeight - 1) {
2257  if (m_binScale == BinScale::Linear) {
2258  paint.drawLine(w - tickw, h - vy, w, h - vy);
2259  }
2260  continue;
2261  }
2262 
2263  QString text = QString("%1").arg(freq);
2264  if (bin == 1) text = tr("%1Hz").arg(freq); // bin 0 is DC
2265  paint.drawLine(cw + 7, h - vy, w - pkw - 1, h - vy);
2266 
2267  if (h - vy - textHeight >= -2) {
2268  int tx = w - 3 - paint.fontMetrics().width(text) - max(tickw, pkw);
2269  paint.drawText(tx, h - vy + toff, text);
2270  }
2271 
2272  py = vy;
2273  }
2274 
2275  if (m_binScale == BinScale::Log) {
2276 
2277  // piano keyboard
2278 
2280  (v, paint, QRect(w - pkw - 1, 0, pkw, h),
2282  }
2283 
2284  m_haveDetailedScale = detailed;
2285 }
2286 
2287 void
2289  QPainter &paint, QRect rect) const
2290 {
2291  // The colour scale
2292 
2294  paintDetailedScalePhase(v, paint, rect);
2295  return;
2296  }
2297 
2298  int h = rect.height();
2299  int textHeight = paint.fontMetrics().height();
2300  int toff = -textHeight + paint.fontMetrics().ascent() + 2;
2301 
2302  int cw = getColourScaleWidth(paint);
2303  int cbw = paint.fontMetrics().width("dB");
2304 
2305  int topLines = 2;
2306 
2307  int ch = h - textHeight * (topLines + 1) - 8;
2308 // paint.drawRect(4, textHeight + 4, cw - 1, ch + 1);
2309  paint.drawRect(4 + cw - cbw, textHeight * topLines + 4, cbw - 1, ch + 1);
2310 
2311  QString top, bottom;
2312  double min = m_viewMags[v->getId()].getMin();
2313  double max = m_viewMags[v->getId()].getMax();
2314 
2315  if (min < m_threshold) min = m_threshold;
2316  if (max <= min) max = min + 0.1;
2317 
2318  double dBmin = AudioLevel::multiplier_to_dB(min);
2319  double dBmax = AudioLevel::multiplier_to_dB(max);
2320 
2321 #ifdef DEBUG_SPECTROGRAM_REPAINT
2322  cerr << "paintVerticalScale: for view id " << v->getId()
2323  << ": min = " << min << ", max = " << max
2324  << ", dBmin = " << dBmin << ", dBmax = " << dBmax << endl;
2325 #endif
2326 
2327  if (dBmax < -60.f) dBmax = -60.f;
2328  else top = QString("%1").arg(lrint(dBmax));
2329 
2330  if (dBmin < dBmax - 60.f) dBmin = dBmax - 60.f;
2331  bottom = QString("%1").arg(lrint(dBmin));
2332 
2333 #ifdef DEBUG_SPECTROGRAM_REPAINT
2334  cerr << "adjusted dB range to min = " << dBmin << ", max = " << dBmax
2335  << endl;
2336 #endif
2337 
2338  paint.drawText((cw + 6 - paint.fontMetrics().width("dBFS")) / 2,
2339  2 + textHeight + toff, "dBFS");
2340 
2341  paint.drawText(3 + cw - cbw - paint.fontMetrics().width(top),
2342  2 + textHeight * topLines + toff + textHeight/2, top);
2343 
2344  paint.drawText(3 + cw - cbw - paint.fontMetrics().width(bottom),
2345  h + toff - 3 - textHeight/2, bottom);
2346 
2347  paint.save();
2348  paint.setBrush(Qt::NoBrush);
2349 
2350  int lasty = 0;
2351  int lastdb = 0;
2352 
2353  for (int i = 0; i < ch; ++i) {
2354 
2355  double dBval = dBmin + (((dBmax - dBmin) * i) / (ch - 1));
2356  int idb = int(dBval);
2357 
2358  double value = AudioLevel::dB_to_multiplier(dBval);
2359  paint.setPen(getRenderer(v)->getColour(value));
2360 
2361  int y = textHeight * topLines + 4 + ch - i;
2362 
2363  paint.drawLine(5 + cw - cbw, y, cw + 2, y);
2364 
2365  if (i == 0) {
2366  lasty = y;
2367  lastdb = idb;
2368  } else if (i < ch - paint.fontMetrics().ascent() &&
2369  idb != lastdb &&
2370  ((abs(y - lasty) > textHeight &&
2371  idb % 10 == 0) ||
2372  (abs(y - lasty) > paint.fontMetrics().ascent() &&
2373  idb % 5 == 0))) {
2374  paint.setPen(v->getForeground());
2375  QString text = QString("%1").arg(idb);
2376  paint.drawText(3 + cw - cbw - paint.fontMetrics().width(text),
2377  y + toff + textHeight/2, text);
2378  paint.drawLine(5 + cw - cbw, y, 8 + cw - cbw, y);
2379  lasty = y;
2380  lastdb = idb;
2381  }
2382  }
2383  paint.restore();
2384 }
2385 
2386 void
2388  QPainter &paint, QRect rect) const
2389 {
2390  // The colour scale in phase mode
2391 
2392  int h = rect.height();
2393  int textHeight = paint.fontMetrics().height();
2394  int toff = -textHeight + paint.fontMetrics().ascent() + 2;
2395 
2396  int cw = getColourScaleWidth(paint);
2397 
2398  // Phase is not measured in dB of course, but this places the
2399  // scale at the same position as in the magnitude spectrogram
2400  int cbw = paint.fontMetrics().width("dB");
2401 
2402  int topLines = 1;
2403 
2404  int ch = h - textHeight * (topLines + 1) - 8;
2405  paint.drawRect(4 + cw - cbw, textHeight * topLines + 4, cbw - 1, ch + 1);
2406 
2407  QString top = Strings::pi, bottom = Strings::minus_pi, middle = "0";
2408 
2409  double min = -M_PI;
2410  double max = M_PI;
2411 
2412  paint.drawText(3 + cw - cbw - paint.fontMetrics().width(top),
2413  2 + textHeight * topLines + toff + textHeight/2, top);
2414 
2415  paint.drawText(3 + cw - cbw - paint.fontMetrics().width(middle),
2416  2 + textHeight * topLines + ch/2 + toff + textHeight/2, middle);
2417 
2418  paint.drawText(3 + cw - cbw - paint.fontMetrics().width(bottom),
2419  h + toff - 3 - textHeight/2, bottom);
2420 
2421  paint.save();
2422  paint.setBrush(Qt::NoBrush);
2423 
2424  for (int i = 0; i < ch; ++i) {
2425  double val = min + (((max - min) * i) / (ch - 1));
2426  paint.setPen(getRenderer(v)->getColour(val));
2427  int y = textHeight * topLines + 4 + ch - i;
2428  paint.drawLine(5 + cw - cbw, y, cw + 2, y);
2429  }
2430  paint.restore();
2431 }
2432 
2433 class SpectrogramRangeMapper : public RangeMapper
2434 {
2435 public:
2436  SpectrogramRangeMapper(sv_samplerate_t sr, int /* fftsize */) :
2437  m_dist(sr / 2),
2438  m_s2(sqrt(sqrt(2))) { }
2440 
2441  int getPositionForValue(double value) const override {
2442 
2443  double dist = m_dist;
2444 
2445  int n = 0;
2446 
2447  while (dist > (value + 0.00001) && dist > 0.1) {
2448  dist /= m_s2;
2449  ++n;
2450  }
2451 
2452  return n;
2453  }
2454 
2455  int getPositionForValueUnclamped(double value) const override {
2456  // We don't really support this
2457  return getPositionForValue(value);
2458  }
2459 
2460  double getValueForPosition(int position) const override {
2461 
2462  // Vertical zoom step 0 shows the entire range from DC ->
2463  // Nyquist frequency. Step 1 shows 2^(1/4) of the range of
2464  // step 0, and so on until the visible range is smaller than
2465  // the frequency step between bins at the current fft size.
2466 
2467  double dist = m_dist;
2468 
2469  int n = 0;
2470  while (n < position) {
2471  dist /= m_s2;
2472  ++n;
2473  }
2474 
2475  return dist;
2476  }
2477 
2478  double getValueForPositionUnclamped(int position) const override {
2479  // We don't really support this
2480  return getValueForPosition(position);
2481  }
2482 
2483  QString getUnit() const override { return "Hz"; }
2484 
2485 protected:
2486  double m_dist;
2487  double m_s2;
2488 };
2489 
2490 int
2492 {
2493  auto model = ModelById::getAs<DenseTimeValueModel>(m_model);
2494  if (!model) return 0;
2495 
2496  sv_samplerate_t sr = model->getSampleRate();
2497 
2498  SpectrogramRangeMapper mapper(sr, getFFTSize());
2499 
2500 // int maxStep = mapper.getPositionForValue((double(sr) / getFFTSize()) + 0.001);
2501  int maxStep = mapper.getPositionForValue(0);
2502  int minStep = mapper.getPositionForValue(double(sr) / 2);
2503 
2504  int initialMax = m_initialMaxFrequency;
2505  if (initialMax == 0) initialMax = int(sr / 2);
2506 
2507  defaultStep = mapper.getPositionForValue(initialMax) - minStep;
2508 
2509 // SVDEBUG << "SpectrogramLayer::getVerticalZoomSteps: " << maxStep - minStep << " (" << maxStep <<"-" << minStep << "), default is " << defaultStep << " (from initial max freq " << initialMax << ")" << endl;
2510 
2511  return maxStep - minStep;
2512 }
2513 
2514 int
2516 {
2517  auto model = ModelById::getAs<DenseTimeValueModel>(m_model);
2518  if (!model) return 0;
2519 
2520  double dmin, dmax;
2521  getDisplayExtents(dmin, dmax);
2522 
2523  SpectrogramRangeMapper mapper(model->getSampleRate(), getFFTSize());
2524  int n = mapper.getPositionForValue(dmax - dmin);
2525 // SVDEBUG << "SpectrogramLayer::getCurrentVerticalZoomStep: " << n << endl;
2526  return n;
2527 }
2528 
2529 void
2531 {
2532  auto model = ModelById::getAs<DenseTimeValueModel>(m_model);
2533  if (!model) return;
2534 
2535  double dmin = m_minFrequency, dmax = m_maxFrequency;
2536 // getDisplayExtents(dmin, dmax);
2537 
2538 // cerr << "current range " << dmin << " -> " << dmax << ", range " << dmax-dmin << ", mid " << (dmax + dmin)/2 << endl;
2539 
2540  sv_samplerate_t sr = model->getSampleRate();
2541  SpectrogramRangeMapper mapper(sr, getFFTSize());
2542  double newdist = mapper.getValueForPosition(step);
2543 
2544  double newmin, newmax;
2545 
2546  if (m_binScale == BinScale::Log) {
2547 
2548  // need to pick newmin and newmax such that
2549  //
2550  // (log(newmin) + log(newmax)) / 2 == logmid
2551  // and
2552  // newmax - newmin = newdist
2553  //
2554  // so log(newmax - newdist) + log(newmax) == 2logmid
2555  // log(newmax(newmax - newdist)) == 2logmid
2556  // newmax.newmax - newmax.newdist == exp(2logmid)
2557  // newmax^2 + (-newdist)newmax + -exp(2logmid) == 0
2558  // quadratic with a = 1, b = -newdist, c = -exp(2logmid), all known
2559  //
2560  // positive root
2561  // newmax = (newdist + sqrt(newdist^2 + 4exp(2logmid))) / 2
2562  //
2563  // but logmid = (log(dmin) + log(dmax)) / 2
2564  // so exp(2logmid) = exp(log(dmin) + log(dmax))
2565  // = exp(log(dmin.dmax))
2566  // = dmin.dmax
2567  // so newmax = (newdist + sqrtf(newdist^2 + 4dmin.dmax)) / 2
2568 
2569  newmax = (newdist + sqrt(newdist*newdist + 4*dmin*dmax)) / 2;
2570  newmin = newmax - newdist;
2571 
2572 // cerr << "newmin = " << newmin << ", newmax = " << newmax << endl;
2573 
2574  } else {
2575  double dmid = (dmax + dmin) / 2;
2576  newmin = dmid - newdist / 2;
2577  newmax = dmid + newdist / 2;
2578  }
2579 
2580  double mmin, mmax;
2581  mmin = 0;
2582  mmax = double(sr) / 2;
2583 
2584  if (newmin < mmin) {
2585  newmax += (mmin - newmin);
2586  newmin = mmin;
2587  }
2588  if (newmax > mmax) {
2589  newmax = mmax;
2590  }
2591 
2592 // SVDEBUG << "SpectrogramLayer::setVerticalZoomStep: " << step << ": " << newmin << " -> " << newmax << " (range " << newdist << ")" << endl;
2593 
2594  setMinFrequency(int(lrint(newmin)));
2595  setMaxFrequency(int(lrint(newmax)));
2596 }
2597 
2598 RangeMapper *
2600 {
2601  auto model = ModelById::getAs<DenseTimeValueModel>(m_model);
2602  if (!model) return nullptr;
2603  return new SpectrogramRangeMapper(model->getSampleRate(), getFFTSize());
2604 }
2605 
2606 void
2608 {
2609  int y0 = 0;
2610  if (r.startY > 0.0) y0 = int(getYForFrequency(v, r.startY));
2611 
2612  int y1 = y0;
2613  if (r.endY > 0.0) y1 = int(getYForFrequency(v, r.endY));
2614 
2615 // SVDEBUG << "SpectrogramLayer::updateMeasureRectYCoords: start " << r.startY << " -> " << y0 << ", end " << r.endY << " -> " << y1 << endl;
2616 
2617  r.pixrect = QRect(r.pixrect.x(), y0, r.pixrect.width(), y1 - y0);
2618 }
2619 
2620 void
2622 {
2623  if (start) {
2624  r.startY = getFrequencyForY(v, y);
2625  r.endY = r.startY;
2626  } else {
2627  r.endY = getFrequencyForY(v, y);
2628  }
2629 // SVDEBUG << "SpectrogramLayer::setMeasureRectYCoord: start " << r.startY << " <- " << y << ", end " << r.endY << " <- " << y << endl;
2630 
2631 }
2632 
2633 void
2634 SpectrogramLayer::toXml(QTextStream &stream,
2635  QString indent, QString extraAttributes) const
2636 {
2637  QString s;
2638 
2639  s += QString("channel=\"%1\" "
2640  "windowSize=\"%2\" "
2641  "windowHopLevel=\"%3\" "
2642  "oversampling=\"%4\" "
2643  "gain=\"%5\" "
2644  "threshold=\"%6\" ")
2645  .arg(m_channel)
2646  .arg(m_windowSize)
2647  .arg(m_windowHopLevel)
2648  .arg(m_oversampling)
2649  .arg(m_gain)
2650  .arg(m_threshold);
2651 
2652  s += QString("minFrequency=\"%1\" "
2653  "maxFrequency=\"%2\" "
2654  "colourScale=\"%3\" "
2655  "colourRotation=\"%4\" "
2656  "frequencyScale=\"%5\" "
2657  "binDisplay=\"%6\" ")
2658  .arg(m_minFrequency)
2659  .arg(m_maxFrequency)
2661  .arg(m_colourRotation)
2662  .arg(int(m_binScale))
2663  .arg(int(m_binDisplay));
2664 
2665  // New-style colour map attribute, by string id rather than by
2666  // number
2667 
2668  s += QString("colourMap=\"%1\" ")
2670 
2671  // Old-style colour map attribute
2672 
2673  s += QString("colourScheme=\"%1\" ")
2675 
2676  // New-style normalization attributes, allowing for more types of
2677  // normalization in future: write out the column normalization
2678  // type separately, and then whether we are normalizing visible
2679  // area as well afterwards
2680 
2681  s += QString("columnNormalization=\"%1\" ")
2682  .arg(m_normalization == ColumnNormalization::Max1 ? "peak" :
2683  m_normalization == ColumnNormalization::Hybrid ? "hybrid" : "none");
2684 
2685  // Old-style normalization attribute. We *don't* write out
2686  // normalizeHybrid here because the only release that would accept
2687  // it (Tony v1.0) has a totally different scale factor for
2688  // it. We'll just have to accept that session files from Tony
2689  // v2.0+ will look odd in Tony v1.0
2690 
2691  s += QString("normalizeColumns=\"%1\" ")
2692  .arg(m_normalization == ColumnNormalization::Max1 ? "true" : "false");
2693 
2694  // And this applies to both old- and new-style attributes
2695 
2696  s += QString("normalizeVisibleArea=\"%1\" ")
2697  .arg(m_normalizeVisibleArea ? "true" : "false");
2698 
2699  Layer::toXml(stream, indent, extraAttributes + " " + s);
2700 }
2701 
2702 void
2703 SpectrogramLayer::setProperties(const QXmlAttributes &attributes)
2704 {
2705  bool ok = false;
2706 
2707  int channel = attributes.value("channel").toInt(&ok);
2708  if (ok) setChannel(channel);
2709 
2710  int windowSize = attributes.value("windowSize").toUInt(&ok);
2711  if (ok) setWindowSize(windowSize);
2712 
2713  int windowHopLevel = attributes.value("windowHopLevel").toUInt(&ok);
2714  if (ok) setWindowHopLevel(windowHopLevel);
2715  else {
2716  int windowOverlap = attributes.value("windowOverlap").toUInt(&ok);
2717  // a percentage value
2718  if (ok) {
2719  if (windowOverlap == 0) setWindowHopLevel(0);
2720  else if (windowOverlap == 25) setWindowHopLevel(1);
2721  else if (windowOverlap == 50) setWindowHopLevel(2);
2722  else if (windowOverlap == 75) setWindowHopLevel(3);
2723  else if (windowOverlap == 90) setWindowHopLevel(4);
2724  }
2725  }
2726 
2727  int oversampling = attributes.value("oversampling").toUInt(&ok);
2728  if (ok) setOversampling(oversampling);
2729 
2730  float gain = attributes.value("gain").toFloat(&ok);
2731  if (ok) setGain(gain);
2732 
2733  float threshold = attributes.value("threshold").toFloat(&ok);
2734  if (ok) setThreshold(threshold);
2735 
2736  int minFrequency = attributes.value("minFrequency").toUInt(&ok);
2737  if (ok) {
2738  SVDEBUG << "SpectrogramLayer::setProperties: setting min freq to " << minFrequency << endl;
2739  setMinFrequency(minFrequency);
2740  }
2741 
2742  int maxFrequency = attributes.value("maxFrequency").toUInt(&ok);
2743  if (ok) {
2744  SVDEBUG << "SpectrogramLayer::setProperties: setting max freq to " << maxFrequency << endl;
2745  setMaxFrequency(maxFrequency);
2746  }
2747 
2748  auto colourScale = convertToColourScale
2749  (attributes.value("colourScale").toInt(&ok));
2750  if (ok) {
2751  setColourScale(colourScale.first);
2752  setColourScaleMultiple(colourScale.second);
2753  }
2754 
2755  QString colourMapId = attributes.value("colourMap");
2756  int colourMap = ColourMapper::getColourMapById(colourMapId);
2757  if (colourMap >= 0) {
2758  setColourMap(colourMap);
2759  } else {
2760  colourMap = attributes.value("colourScheme").toInt(&ok);
2761  if (ok && colourMap < ColourMapper::getColourMapCount()) {
2762  setColourMap(colourMap);
2763  }
2764  }
2765 
2766  int colourRotation = attributes.value("colourRotation").toInt(&ok);
2767  if (ok) setColourRotation(colourRotation);
2768 
2769  BinScale binScale = (BinScale)
2770  attributes.value("frequencyScale").toInt(&ok);
2771  if (ok) setBinScale(binScale);
2772 
2773  BinDisplay binDisplay = (BinDisplay)
2774  attributes.value("binDisplay").toInt(&ok);
2775  if (ok) setBinDisplay(binDisplay);
2776 
2777  bool haveNewStyleNormalization = false;
2778 
2779  QString columnNormalization = attributes.value("columnNormalization");
2780 
2781  if (columnNormalization != "") {
2782 
2783  haveNewStyleNormalization = true;
2784 
2785  if (columnNormalization == "peak") {
2786  setNormalization(ColumnNormalization::Max1);
2787  } else if (columnNormalization == "hybrid") {
2788  setNormalization(ColumnNormalization::Hybrid);
2789  } else if (columnNormalization == "none") {
2790  setNormalization(ColumnNormalization::None);
2791  } else {
2792  SVCERR << "NOTE: Unknown or unsupported columnNormalization attribute \""
2793  << columnNormalization << "\"" << endl;
2794  }
2795  }
2796 
2797  if (!haveNewStyleNormalization) {
2798 
2799  bool normalizeColumns =
2800  (attributes.value("normalizeColumns").trimmed() == "true");
2801  if (normalizeColumns) {
2802  setNormalization(ColumnNormalization::Max1);
2803  }
2804 
2805  bool normalizeHybrid =
2806  (attributes.value("normalizeHybrid").trimmed() == "true");
2807  if (normalizeHybrid) {
2808  setNormalization(ColumnNormalization::Hybrid);
2809  }
2810  }
2811 
2812  bool normalizeVisibleArea =
2813  (attributes.value("normalizeVisibleArea").trimmed() == "true");
2814  setNormalizeVisibleArea(normalizeVisibleArea);
2815 
2816  if (!haveNewStyleNormalization && m_normalization == ColumnNormalization::Hybrid) {
2817  // Tony v1.0 is (and hopefully will remain!) the only released
2818  // SV-a-like to use old-style attributes when saving sessions
2819  // that ask for hybrid normalization. It saves them with the
2820  // wrong gain factor, so hack in a fix for that here -- this
2821  // gives us backward but not forward compatibility.
2822  setGain(m_gain / float(getFFTSize() / 2));
2823  }
2824 }
2825 
QString getPropertyValueLabel(const PropertyName &, int value) const override
void setChannel(int)
Specify the channel to use from the source model.
void setColourRotation(int)
Specify the colourmap rotation for the colour scale.
double threshold
Threshold below which every value is mapped to background pixel 0.
Definition: ColourScale.h:59
virtual bool isLayerDormant(const LayerGeometryProvider *v) const
Return whether the layer is dormant (i.e.
Definition: Layer.cpp:144
RangeMapper * getNewVerticalZoomRangeMapper() const override
Create and return a range mapper for vertical zoom step values.
void measureDoubleClick(LayerGeometryProvider *, QMouseEvent *) override
int getMaxFrequency() const
bool getYScaleValue(const LayerGeometryProvider *, int, double &, QString &) const override
Return the value and unit at the given y coordinate in the given view.
void setNormalizeVisibleArea(bool)
Specify whether to normalize the visible area.
bool isLayerScrollable(const LayerGeometryProvider *) const override
This should return true if the layer can safely be scrolled automatically by a given view (simply cop...
void cacheInvalid(ModelId)
bool setDisplayExtents(double min, double max) override
Set the displayed minimum and maximum values for the y axis to the given range, if supported...
SpectrogramLayer(Configuration=FullRangeDb)
Construct a SpectrogramLayer with default parameters appropriate for the given configuration.
ColumnNormalization normalization
Type of column normalization.
virtual QColor getForeground() const =0
const LayerGeometryProvider * provider
ColourScale colourScale
A complete ColourScale object by value, used for colour map conversion.
PropertyList getProperties() const override
double getYForBin(const LayerGeometryProvider *, double bin) const override
!! VerticalBinLayer methods. Note overlap with get*BinRange()
double getFrequencyForY(const LayerGeometryProvider *v, int y) const
void checkCacheSpace(int *suggestedPeakDivisor, bool *createWholeCache) const
int getWindowSize() const
int getWindowIncrement() const
virtual bool shouldIlluminateLocalFeatures(const Layer *, QPoint &) const =0
void toXml(QTextStream &stream, QString indent="", QString extraAttributes="") const override
Convert the layer&#39;s data (though not those of the model it refers to) into XML for file output...
Definition: Layer.cpp:653
static std::pair< ColourScaleType, double > convertToColourScale(int value)
void illuminateLocalFeatures(LayerGeometryProvider *v, QPainter &painter) const
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...
double getValueForPositionUnclamped(int position) const override
virtual ZoomLevel getZoomLevel() const =0
Return the zoom level, i.e.
RenderResult renderTimeConstrained(const LayerGeometryProvider *v, QPainter &paint, QRect rect)
Render the requested area using the given painter, obtaining geometry (e.g.
void updateMeasureRectYCoords(LayerGeometryProvider *v, const MeasureRect &r) const override
ViewMagMap m_lastRenderedMags
int getPropertyRangeAndValue(const PropertyName &, int *min, int *max, int *deflt) const override
void setBinScale(BinScale)
Specify the scale for the y axis.
bool getXBinSourceRange(LayerGeometryProvider *v, int x, RealTime &timeMin, RealTime &timeMax) const
void modelReplaced()
void paintWithRenderer(LayerGeometryProvider *v, QPainter &paint, QRect rect) const
double getBinForY(const LayerGeometryProvider *, double y) const override
Return the bin number, possibly fractional, at the given y coordinate.
int getColourScaleWidth(QPainter &) const
int getOversampling() const
double scaleFactor
Initial scale factor (e.g.
virtual double getFrequencyForY(double y, double minFreq, double maxFreq, bool logarithmic) const =0
Return the closest frequency to the given (maybe fractional) pixel y-coordinate, if the frequency ran...
Map values within a range onto a set of colours, with a given distribution (linear, log etc) and optional colourmap rotation.
Definition: ColourScale.h:34
int getFFTSize() const
ViewRendererMap m_renderers
bool getYBinRange(LayerGeometryProvider *v, int y, double &freqBinMin, double &freqBinMax) const
void setColourScaleMultiple(double)
Specify multiple factor for colour scale.
void addCommand(Command *command)
Add a command to the command history.
bool getXYBinSourceRange(LayerGeometryProvider *v, int x, int y, double &min, double &max, double &phaseMin, double &phaseMax) const
void setThreshold(float threshold)
Set the threshold for sample values to qualify for being shown in the FFT, in voltage units...
void setNormalization(ColumnNormalization)
Specify the normalization mode for individual columns.
void setModel(ModelId model)
virtual void setLayerDormant(const LayerGeometryProvider *v, bool dormant)
Indicate that a layer is not currently visible in the given view and is not expected to become visibl...
Definition: Layer.cpp:136
BinDisplay binDisplay
Selection of bins to include in the export.
int getMinFrequency() const
double gain
Gain that is applied before thresholding, in the display, matching the ColourScale object parameters...
virtual sv_frame_t getFrameForX(int x) const =0
Return the closest frame to the given pixel x-coordinate.
bool geometryChanged(const LayerGeometryProvider *v)
Return true if the provider&#39;s geometry differs from the cache, or if we are not using a cache...
void setProperty(const PropertyName &, int value) override
double maxValue
Maximum value in source range.
Definition: ColourScale.h:52
int colourMap
A colour map index as used by ColourMapper.
Definition: ColourScale.h:43
static int getColourMapCount()
Return the number of known colour maps.
WindowType m_windowType
void setVerticallyFixed()
Mark the spectrogram layer as having a fixed range in the vertical axis.
void setWindowHopLevel(int level)
static std::pair< ColumnNormalization, bool > convertToColumnNorm(int value)
double getEffectiveMinFrequency() const
void setColourMap(int map)
Specify the colour map.
static int getBackwardCompatibilityColourMap(int n)
Older versions of colour-handling code save and reload colour maps by numerical index and can&#39;t prope...
bool snapToFeatureFrame(LayerGeometryProvider *v, sv_frame_t &frame, int &resolution, SnapType snap, int ycoord) const override
Adjust the given frame to snap to the nearest feature, if possible.
void modelChanged(ModelId)
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 getVerticalScaleWidth(LayerGeometryProvider *v, bool detailed, QPainter &) const override
double getValueForPosition(int position) const override
void setVerticalZoomStep(int) override
Set the vertical zoom step.
ColourScaleType getColourScale() const
int colourRotation
Colourmap rotation, in the range 0-255.
bool interpolate
Whether to apply smoothing when rendering cells at more than one pixel per cell.
double gain
Gain to apply before thresholding, mapping, and clamping.
Definition: ColourScale.h:62
QRect rendered
The rect that was actually rendered.
double minValue
Minimum value in source range.
Definition: ColourScale.h:49
void setMeasureRectYCoord(LayerGeometryProvider *v, MeasureRect &r, bool start, int y) const override
void setOversampling(int oversampling)
double threshold
Threshold below which every value is mapped to background pixel 0 in the display, matching the Colour...
ModelId getExportModel(LayerGeometryProvider *) const override
Return the ID of a model representing the contents of this layer in a form suitable for export to a t...
bool hasLightBackground() const
Return true if the colour map is intended to be placed over a light background, false otherwise...
virtual double getYForFrequency(double frequency, double minFreq, double maxFreq, bool logarithmic) const =0
Return the (maybe fractional) pixel y-coordinate corresponding to a given frequency, if the frequency range is as specified.
virtual QRect getPaintRect() const =0
To be called from a layer, to obtain the extent of the surface that the layer is currently painting t...
bool hasLightBackground() const override
float getGain() const
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 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.
double getYForFrequency(const LayerGeometryProvider *v, double frequency) const
void layerParametersChanged()
int getWindowHopLevel() const
void setSynchronousPainting(bool synchronous) override
Enable or disable synchronous painting.
void verticalZoomChanged()
ColumnNormalization m_normalization
BinDisplay m_binDisplay
RenderResult render(const LayerGeometryProvider *v, QPainter &paint, QRect rect)
Render the requested area using the given painter, obtaining geometry (e.g.
void paintDetailedScale(LayerGeometryProvider *v, QPainter &paint, QRect rect) const
QString getError(LayerGeometryProvider *v) const override
Return an error string if any errors have occurred while loading or processing data for the given vie...
void paintPianoVertical(LayerGeometryProvider *v, QPainter &paint, QRect rect, double minf, double maxf)
Definition: PianoScale.cpp:31
int getVerticalZoomSteps(int &defaultStep) const override
Get the number of vertical zoom steps available for this layer.
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...
SnapType
Definition: Layer.h:195
void preferenceChanged(PropertyContainer::PropertyName name)
QString getUnit() const override
std::vector< ModelId > m_exporters
A class for mapping intensity values onto various colour maps.
Definition: ColourMapper.h:27
bool alwaysOpaque
Whether cells should always be opaque.
static CommandHistory * getInstance()
void connectSignals(ModelId)
Definition: Layer.cpp:49
virtual int getPaintHeight() const
int getPositionForValue(double value) const override
bool getYBinSourceRange(LayerGeometryProvider *v, int y, double &freqMin, double &freqMax) const
void paintVerticalScale(LayerGeometryProvider *v, bool detailed, QPainter &paint, QRect rect) const override
ColourScaleType m_colourScale
static int convertFromColourScale(ColourScaleType type, double multiple)
ColourScaleType scaleType
Distribution for the scale.
Definition: ColourScale.h:46
RangeMapper * getNewPropertyRangeMapper(const PropertyName &) const override
SpectrogramRangeMapper(sv_samplerate_t sr, int)
double scaleFactor
Initial scale factor (e.g.
void paintDetailedScalePhase(LayerGeometryProvider *v, QPainter &paint, QRect rect) const
void toXml(QTextStream &stream, QString indent="", QString extraAttributes="") const override
QString getPropertyIconName(const PropertyName &) const override
ColumnNormalization normalization
Type of column normalization.
QString getPropertyGroupName(const PropertyName &) const override
bool invertVertical
Whether to render the whole caboodle upside-down.
void setProperties(const QXmlAttributes &attributes) override
Set the particular properties of a layer (those specific to the subclass) from a set of XML attribute...
ColourScaleType
Definition: ColourScale.h:21
BinDisplay getBinDisplay() const
void paintCrosshairs(LayerGeometryProvider *, QPainter &, QPoint) const override
double getEffectiveMaxFrequency() const
virtual void updatePaintRect(QRect r)=0
Colour3DPlotRenderer * getRenderer(LayerGeometryProvider *) const
BinScale getBinScale() const
QString getPropertyValueIconName(const PropertyName &, int value) const override
ColumnNormalization getNormalization() const
void setLayerDormant(const LayerGeometryProvider *v, bool dormant) override
Indicate that a layer is not currently visible in the given view and is not expected to become visibl...
QRect getLargestUncachedRect(const LayerGeometryProvider *v)
Return the area of the largest rectangle within the entire area of the cache that is unavailable in t...
double startY
Definition: Layer.h:645
void setGain(float gain)
Set the gain multiplier for sample values in this view.
virtual void setMeasureRectFromPixrect(LayerGeometryProvider *v, MeasureRect &r, QRect pixrect) const
Definition: Layer.cpp:565
virtual int getId() const =0
Retrieve the id of this object.
int getColourMap() const
const VerticalBinLayer * verticalBinLayer
QString getFeatureDescription(LayerGeometryProvider *v, QPoint &) const override
void setColourScale(ColourScaleType)
Specify the scale for sample levels.
static QString getColourMapId(int n)
Return a machine-readable id string for the colour map with the given index.
int getChannel() const
QString getPropertyLabel(const PropertyName &) const override
int getCompletion(LayerGeometryProvider *v) const override
Return the proportion of background work complete in drawing this view, as a percentage – in most ca...
static void drawVisibleText(const LayerGeometryProvider *, QPainter &p, int x, int y, QString text, TextStyle style)
MagnitudeRange range
The magnitude range of the data in the rendered area, after initial scaling (parameters.scaleFactor) and normalisation, for use in displaying colour scale etc.
int getPositionForValueUnclamped(double value) const override
void modelChangedWithin(ModelId, sv_frame_t startFrame, sv_frame_t endFrame)
ModelId getSliceableModel() const override
QColor getContrastingColour() const
Return a colour that contrasts somewhat with the colours in the map, so as to be used for cursors etc...
const VerticalBinLayer * verticalBinLayer
float getThreshold() const
double getColourScaleMultiple() const
bool getAdjustedYBinSourceRange(LayerGeometryProvider *v, int x, int y, double &freqMin, double &freqMax, double &adjFreqMin, double &adjFreqMax) const
void setWindowType(WindowType type)
virtual int getXForFrame(sv_frame_t frame) const =0
Return the pixel x-coordinate corresponding to a given sample frame (which may be negative)...
bool getXBinRange(LayerGeometryProvider *v, int x, double &windowMin, double &windowMax) const
std::vector< ModelId > peakCaches
int getCurrentVerticalZoomStep() const override
Get the current vertical zoom step.
double multiple
Multiple to apply after thresholding and mapping.
Definition: ColourScale.h:69
BinScale binScale
Scale for vertical bin spacing (linear or logarithmic).
bool getCrosshairExtents(LayerGeometryProvider *, QPainter &, QPoint cursorPos, std::vector< QRect > &extents) const override
BinDisplay binDisplay
Selection of bins to display.
bool getNormalizeVisibleArea() const
void setBinDisplay(BinDisplay)
Specify the processing of frequency bins for the y axis.
WindowType getWindowType() const
QRect findSimilarRegionExtents(QPoint point) const
Return the enclosing rectangle for the region of similar colour to the given point within the cache...
static int convertFromColumnNorm(ColumnNormalization norm, bool visible)
PropertyType getPropertyType(const PropertyName &) const override