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