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