| Chris@58 | 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */ | 
| Chris@0 | 2 | 
| Chris@0 | 3 /* | 
| Chris@59 | 4     Sonic Visualiser | 
| Chris@59 | 5     An audio file viewer and annotation editor. | 
| Chris@59 | 6     Centre for Digital Music, Queen Mary, University of London. | 
| Chris@484 | 7     This file copyright 2006-2009 Chris Cannam and QMUL. | 
| Chris@0 | 8 | 
| Chris@59 | 9     This program is free software; you can redistribute it and/or | 
| Chris@59 | 10     modify it under the terms of the GNU General Public License as | 
| Chris@59 | 11     published by the Free Software Foundation; either version 2 of the | 
| Chris@59 | 12     License, or (at your option) any later version.  See the file | 
| Chris@59 | 13     COPYING included with this distribution for more information. | 
| Chris@0 | 14 */ | 
| Chris@0 | 15 | 
| Chris@0 | 16 #include "SpectrogramLayer.h" | 
| Chris@0 | 17 | 
| Chris@128 | 18 #include "view/View.h" | 
| Chris@0 | 19 #include "base/Profiler.h" | 
| Chris@0 | 20 #include "base/AudioLevel.h" | 
| Chris@0 | 21 #include "base/Window.h" | 
| Chris@24 | 22 #include "base/Pitch.h" | 
| Chris@118 | 23 #include "base/Preferences.h" | 
| Chris@167 | 24 #include "base/RangeMapper.h" | 
| Chris@253 | 25 #include "base/LogRange.h" | 
| Chris@376 | 26 #include "widgets/CommandHistory.h" | 
| Chris@376 | 27 #include "ColourMapper.h" | 
| Chris@283 | 28 #include "ImageRegionFinder.h" | 
| Chris@484 | 29 #include "data/model/Dense3DModelPeakCache.h" | 
| Chris@690 | 30 #include "PianoScale.h" | 
| Chris@0 | 31 | 
| Chris@0 | 32 #include <QPainter> | 
| Chris@0 | 33 #include <QImage> | 
| Chris@0 | 34 #include <QPixmap> | 
| Chris@0 | 35 #include <QRect> | 
| Chris@92 | 36 #include <QApplication> | 
| Chris@178 | 37 #include <QMessageBox> | 
| Chris@283 | 38 #include <QMouseEvent> | 
| Chris@316 | 39 #include <QTextStream> | 
| Chris@1017 | 40 #include <QSettings> | 
| Chris@0 | 41 | 
| Chris@0 | 42 #include <iostream> | 
| Chris@0 | 43 | 
| Chris@0 | 44 #include <cassert> | 
| Chris@0 | 45 #include <cmath> | 
| Chris@0 | 46 | 
| Chris@545 | 47 #ifndef __GNUC__ | 
| Chris@545 | 48 #include <alloca.h> | 
| Chris@545 | 49 #endif | 
| Chris@545 | 50 | 
| Chris@1025 | 51 #define DEBUG_SPECTROGRAM_REPAINT 1 | 
| Chris@1025 | 52 | 
| Chris@1025 | 53 using namespace std; | 
| Chris@907 | 54 | 
| Chris@44 | 55 SpectrogramLayer::SpectrogramLayer(Configuration config) : | 
| Chris@0 | 56     m_model(0), | 
| Chris@0 | 57     m_channel(0), | 
| Chris@0 | 58     m_windowSize(1024), | 
| Chris@0 | 59     m_windowType(HanningWindow), | 
| Chris@97 | 60     m_windowHopLevel(2), | 
| Chris@109 | 61     m_zeroPadLevel(0), | 
| Chris@107 | 62     m_fftSize(1024), | 
| Chris@0 | 63     m_gain(1.0), | 
| Chris@215 | 64     m_initialGain(1.0), | 
| Chris@37 | 65     m_threshold(0.0), | 
| Chris@215 | 66     m_initialThreshold(0.0), | 
| Chris@9 | 67     m_colourRotation(0), | 
| Chris@215 | 68     m_initialRotation(0), | 
| Chris@119 | 69     m_minFrequency(10), | 
| Chris@0 | 70     m_maxFrequency(8000), | 
| Chris@135 | 71     m_initialMaxFrequency(8000), | 
| Chris@0 | 72     m_colourScale(dBColourScale), | 
| Chris@197 | 73     m_colourMap(0), | 
| Chris@0 | 74     m_frequencyScale(LinearFrequencyScale), | 
| Chris@37 | 75     m_binDisplay(AllBins), | 
| Chris@862 | 76     m_normalization(NoNormalization), | 
| Chris@133 | 77     m_lastEmittedZoomStep(-1), | 
| Chris@390 | 78     m_synchronous(false), | 
| Chris@608 | 79     m_haveDetailedScale(false), | 
| Chris@193 | 80     m_exiting(false), | 
| Chris@193 | 81     m_sliceableModel(0) | 
| Chris@0 | 82 { | 
| Chris@1017 | 83     QString colourConfigName = "spectrogram-colour"; | 
| Chris@1017 | 84     int colourConfigDefault = int(ColourMapper::Green); | 
| Chris@1017 | 85 | 
| Chris@215 | 86     if (config == FullRangeDb) { | 
| Chris@215 | 87         m_initialMaxFrequency = 0; | 
| Chris@215 | 88         setMaxFrequency(0); | 
| Chris@215 | 89     } else if (config == MelodicRange) { | 
| Chris@0 | 90 	setWindowSize(8192); | 
| Chris@97 | 91 	setWindowHopLevel(4); | 
| Chris@215 | 92         m_initialMaxFrequency = 1500; | 
| Chris@215 | 93 	setMaxFrequency(1500); | 
| Chris@215 | 94         setMinFrequency(40); | 
| Chris@0 | 95 	setColourScale(LinearColourScale); | 
| Chris@215 | 96         setColourMap(ColourMapper::Sunset); | 
| Chris@215 | 97         setFrequencyScale(LogFrequencyScale); | 
| Chris@1017 | 98         colourConfigName = "spectrogram-melodic-colour"; | 
| Chris@1017 | 99         colourConfigDefault = int(ColourMapper::Sunset); | 
| Chris@224 | 100 //        setGain(20); | 
| Chris@37 | 101     } else if (config == MelodicPeaks) { | 
| Chris@37 | 102 	setWindowSize(4096); | 
| Chris@97 | 103 	setWindowHopLevel(5); | 
| Chris@135 | 104         m_initialMaxFrequency = 2000; | 
| Chris@40 | 105 	setMaxFrequency(2000); | 
| Chris@37 | 106 	setMinFrequency(40); | 
| Chris@37 | 107 	setFrequencyScale(LogFrequencyScale); | 
| Chris@215 | 108 	setColourScale(LinearColourScale); | 
| Chris@37 | 109 	setBinDisplay(PeakFrequencies); | 
| Chris@862 | 110         setNormalization(NormalizeColumns); | 
| Chris@1017 | 111         colourConfigName = "spectrogram-melodic-colour"; | 
| Chris@1017 | 112         colourConfigDefault = int(ColourMapper::Sunset); | 
| Chris@0 | 113     } | 
| Chris@110 | 114 | 
| Chris@1017 | 115     QSettings settings; | 
| Chris@1017 | 116     settings.beginGroup("Preferences"); | 
| Chris@1017 | 117     setColourMap(settings.value(colourConfigName, colourConfigDefault).toInt()); | 
| Chris@1017 | 118     settings.endGroup(); | 
| Chris@1017 | 119 | 
| Chris@122 | 120     Preferences *prefs = Preferences::getInstance(); | 
| Chris@122 | 121     connect(prefs, SIGNAL(propertyChanged(PropertyContainer::PropertyName)), | 
| Chris@122 | 122             this, SLOT(preferenceChanged(PropertyContainer::PropertyName))); | 
| Chris@122 | 123     setWindowType(prefs->getWindowType()); | 
| Chris@122 | 124 | 
| Chris@197 | 125     initialisePalette(); | 
| Chris@0 | 126 } | 
| Chris@0 | 127 | 
| Chris@0 | 128 SpectrogramLayer::~SpectrogramLayer() | 
| Chris@0 | 129 { | 
| Chris@130 | 130     invalidateFFTModels(); | 
| Chris@0 | 131 } | 
| Chris@0 | 132 | 
| Chris@0 | 133 void | 
| Chris@0 | 134 SpectrogramLayer::setModel(const DenseTimeValueModel *model) | 
| Chris@0 | 135 { | 
| Chris@682 | 136 //    cerr << "SpectrogramLayer(" << this << "): setModel(" << model << ")" << endl; | 
| Chris@34 | 137 | 
| Chris@110 | 138     if (model == m_model) return; | 
| Chris@110 | 139 | 
| Chris@0 | 140     m_model = model; | 
| Chris@130 | 141     invalidateFFTModels(); | 
| Chris@0 | 142 | 
| Chris@0 | 143     if (!m_model || !m_model->isOK()) return; | 
| Chris@0 | 144 | 
| Chris@320 | 145     connectSignals(m_model); | 
| Chris@0 | 146 | 
| Chris@0 | 147     connect(m_model, SIGNAL(modelChanged()), this, SLOT(cacheInvalid())); | 
| Chris@906 | 148     connect(m_model, SIGNAL(modelChangedWithin(sv_frame_t, sv_frame_t)), | 
| Chris@906 | 149 	    this, SLOT(cacheInvalid(sv_frame_t, sv_frame_t))); | 
| Chris@0 | 150 | 
| Chris@0 | 151     emit modelReplaced(); | 
| Chris@110 | 152 } | 
| Chris@115 | 153 | 
| Chris@0 | 154 Layer::PropertyList | 
| Chris@0 | 155 SpectrogramLayer::getProperties() const | 
| Chris@0 | 156 { | 
| Chris@0 | 157     PropertyList list; | 
| Chris@87 | 158     list.push_back("Colour"); | 
| Chris@87 | 159     list.push_back("Colour Scale"); | 
| Chris@87 | 160     list.push_back("Window Size"); | 
| Chris@97 | 161     list.push_back("Window Increment"); | 
| Chris@862 | 162     list.push_back("Normalization"); | 
| Chris@87 | 163     list.push_back("Bin Display"); | 
| Chris@87 | 164     list.push_back("Threshold"); | 
| Chris@87 | 165     list.push_back("Gain"); | 
| Chris@87 | 166     list.push_back("Colour Rotation"); | 
| Chris@153 | 167 //    list.push_back("Min Frequency"); | 
| Chris@153 | 168 //    list.push_back("Max Frequency"); | 
| Chris@87 | 169     list.push_back("Frequency Scale"); | 
| Chris@153 | 170 ////    list.push_back("Zero Padding"); | 
| Chris@0 | 171     return list; | 
| Chris@0 | 172 } | 
| Chris@0 | 173 | 
| Chris@87 | 174 QString | 
| Chris@87 | 175 SpectrogramLayer::getPropertyLabel(const PropertyName &name) const | 
| Chris@87 | 176 { | 
| Chris@87 | 177     if (name == "Colour") return tr("Colour"); | 
| Chris@87 | 178     if (name == "Colour Scale") return tr("Colour Scale"); | 
| Chris@87 | 179     if (name == "Window Size") return tr("Window Size"); | 
| Chris@112 | 180     if (name == "Window Increment") return tr("Window Overlap"); | 
| Chris@862 | 181     if (name == "Normalization") return tr("Normalization"); | 
| Chris@87 | 182     if (name == "Bin Display") return tr("Bin Display"); | 
| Chris@87 | 183     if (name == "Threshold") return tr("Threshold"); | 
| Chris@87 | 184     if (name == "Gain") return tr("Gain"); | 
| Chris@87 | 185     if (name == "Colour Rotation") return tr("Colour Rotation"); | 
| Chris@87 | 186     if (name == "Min Frequency") return tr("Min Frequency"); | 
| Chris@87 | 187     if (name == "Max Frequency") return tr("Max Frequency"); | 
| Chris@87 | 188     if (name == "Frequency Scale") return tr("Frequency Scale"); | 
| Chris@109 | 189     if (name == "Zero Padding") return tr("Smoothing"); | 
| Chris@87 | 190     return ""; | 
| Chris@87 | 191 } | 
| Chris@87 | 192 | 
| Chris@335 | 193 QString | 
| Chris@862 | 194 SpectrogramLayer::getPropertyIconName(const PropertyName &) const | 
| Chris@335 | 195 { | 
| Chris@335 | 196     return ""; | 
| Chris@335 | 197 } | 
| Chris@335 | 198 | 
| Chris@0 | 199 Layer::PropertyType | 
| Chris@0 | 200 SpectrogramLayer::getPropertyType(const PropertyName &name) const | 
| Chris@0 | 201 { | 
| Chris@87 | 202     if (name == "Gain") return RangeProperty; | 
| Chris@87 | 203     if (name == "Colour Rotation") return RangeProperty; | 
| Chris@87 | 204     if (name == "Threshold") return RangeProperty; | 
| Chris@109 | 205     if (name == "Zero Padding") return ToggleProperty; | 
| Chris@0 | 206     return ValueProperty; | 
| Chris@0 | 207 } | 
| Chris@0 | 208 | 
| Chris@0 | 209 QString | 
| Chris@0 | 210 SpectrogramLayer::getPropertyGroupName(const PropertyName &name) const | 
| Chris@0 | 211 { | 
| Chris@153 | 212     if (name == "Bin Display" || | 
| Chris@153 | 213         name == "Frequency Scale") return tr("Bins"); | 
| Chris@87 | 214     if (name == "Window Size" || | 
| Chris@109 | 215 	name == "Window Increment" || | 
| Chris@109 | 216         name == "Zero Padding") return tr("Window"); | 
| Chris@87 | 217     if (name == "Colour" || | 
| Chris@87 | 218 	name == "Threshold" || | 
| Chris@87 | 219 	name == "Colour Rotation") return tr("Colour"); | 
| Chris@862 | 220     if (name == "Normalization" || | 
| Chris@153 | 221         name == "Gain" || | 
| Chris@87 | 222 	name == "Colour Scale") return tr("Scale"); | 
| Chris@0 | 223     return QString(); | 
| Chris@0 | 224 } | 
| Chris@0 | 225 | 
| Chris@0 | 226 int | 
| Chris@0 | 227 SpectrogramLayer::getPropertyRangeAndValue(const PropertyName &name, | 
| Chris@216 | 228 					   int *min, int *max, int *deflt) const | 
| Chris@0 | 229 { | 
| Chris@216 | 230     int val = 0; | 
| Chris@216 | 231 | 
| Chris@216 | 232     int garbage0, garbage1, garbage2; | 
| Chris@55 | 233     if (!min) min = &garbage0; | 
| Chris@55 | 234     if (!max) max = &garbage1; | 
| Chris@216 | 235     if (!deflt) deflt = &garbage2; | 
| Chris@10 | 236 | 
| Chris@87 | 237     if (name == "Gain") { | 
| Chris@0 | 238 | 
| Chris@0 | 239 	*min = -50; | 
| Chris@0 | 240 	*max = 50; | 
| Chris@0 | 241 | 
| Chris@906 | 242         *deflt = int(lrint(log10(m_initialGain) * 20.0)); | 
| Chris@216 | 243 	if (*deflt < *min) *deflt = *min; | 
| Chris@216 | 244 	if (*deflt > *max) *deflt = *max; | 
| Chris@216 | 245 | 
| Chris@906 | 246 	val = int(lrint(log10(m_gain) * 20.0)); | 
| Chris@216 | 247 	if (val < *min) val = *min; | 
| Chris@216 | 248 	if (val > *max) val = *max; | 
| Chris@0 | 249 | 
| Chris@87 | 250     } else if (name == "Threshold") { | 
| Chris@37 | 251 | 
| Chris@37 | 252 	*min = -50; | 
| Chris@37 | 253 	*max = 0; | 
| Chris@37 | 254 | 
| Chris@906 | 255         *deflt = int(lrint(AudioLevel::multiplier_to_dB(m_initialThreshold))); | 
| Chris@216 | 256 	if (*deflt < *min) *deflt = *min; | 
| Chris@216 | 257 	if (*deflt > *max) *deflt = *max; | 
| Chris@216 | 258 | 
| Chris@906 | 259 	val = int(lrint(AudioLevel::multiplier_to_dB(m_threshold))); | 
| Chris@216 | 260 	if (val < *min) val = *min; | 
| Chris@216 | 261 	if (val > *max) val = *max; | 
| Chris@37 | 262 | 
| Chris@87 | 263     } else if (name == "Colour Rotation") { | 
| Chris@9 | 264 | 
| Chris@9 | 265 	*min = 0; | 
| Chris@9 | 266 	*max = 256; | 
| Chris@216 | 267         *deflt = m_initialRotation; | 
| Chris@216 | 268 | 
| Chris@216 | 269 	val = m_colourRotation; | 
| Chris@9 | 270 | 
| Chris@87 | 271     } else if (name == "Colour Scale") { | 
| Chris@0 | 272 | 
| Chris@0 | 273 	*min = 0; | 
| Chris@176 | 274 	*max = 4; | 
| Chris@216 | 275         *deflt = int(dBColourScale); | 
| Chris@216 | 276 | 
| Chris@216 | 277 	val = (int)m_colourScale; | 
| Chris@0 | 278 | 
| Chris@87 | 279     } else if (name == "Colour") { | 
| Chris@0 | 280 | 
| Chris@0 | 281 	*min = 0; | 
| Chris@196 | 282 	*max = ColourMapper::getColourMapCount() - 1; | 
| Chris@216 | 283         *deflt = 0; | 
| Chris@216 | 284 | 
| Chris@216 | 285 	val = m_colourMap; | 
| Chris@0 | 286 | 
| Chris@87 | 287     } else if (name == "Window Size") { | 
| Chris@0 | 288 | 
| Chris@0 | 289 	*min = 0; | 
| Chris@0 | 290 	*max = 10; | 
| Chris@216 | 291         *deflt = 5; | 
| Chris@0 | 292 | 
| Chris@216 | 293 	val = 0; | 
| Chris@0 | 294 	int ws = m_windowSize; | 
| Chris@216 | 295 	while (ws > 32) { ws >>= 1; val ++; } | 
| Chris@0 | 296 | 
| Chris@97 | 297     } else if (name == "Window Increment") { | 
| Chris@0 | 298 | 
| Chris@0 | 299 	*min = 0; | 
| Chris@97 | 300 	*max = 5; | 
| Chris@216 | 301         *deflt = 2; | 
| Chris@216 | 302 | 
| Chris@216 | 303         val = m_windowHopLevel; | 
| Chris@0 | 304 | 
| Chris@109 | 305     } else if (name == "Zero Padding") { | 
| Chris@109 | 306 | 
| Chris@109 | 307 	*min = 0; | 
| Chris@109 | 308 	*max = 1; | 
| Chris@216 | 309         *deflt = 0; | 
| Chris@109 | 310 | 
| Chris@216 | 311         val = m_zeroPadLevel > 0 ? 1 : 0; | 
| Chris@109 | 312 | 
| Chris@87 | 313     } else if (name == "Min Frequency") { | 
| Chris@37 | 314 | 
| Chris@37 | 315 	*min = 0; | 
| Chris@37 | 316 	*max = 9; | 
| Chris@216 | 317         *deflt = 1; | 
| Chris@37 | 318 | 
| Chris@37 | 319 	switch (m_minFrequency) { | 
| Chris@216 | 320 	case 0: default: val = 0; break; | 
| Chris@216 | 321 	case 10: val = 1; break; | 
| Chris@216 | 322 	case 20: val = 2; break; | 
| Chris@216 | 323 	case 40: val = 3; break; | 
| Chris@216 | 324 	case 100: val = 4; break; | 
| Chris@216 | 325 	case 250: val = 5; break; | 
| Chris@216 | 326 	case 500: val = 6; break; | 
| Chris@216 | 327 	case 1000: val = 7; break; | 
| Chris@216 | 328 	case 4000: val = 8; break; | 
| Chris@216 | 329 	case 10000: val = 9; break; | 
| Chris@37 | 330 	} | 
| Chris@37 | 331 | 
| Chris@87 | 332     } else if (name == "Max Frequency") { | 
| Chris@0 | 333 | 
| Chris@0 | 334 	*min = 0; | 
| Chris@0 | 335 	*max = 9; | 
| Chris@216 | 336         *deflt = 6; | 
| Chris@0 | 337 | 
| Chris@0 | 338 	switch (m_maxFrequency) { | 
| Chris@216 | 339 	case 500: val = 0; break; | 
| Chris@216 | 340 	case 1000: val = 1; break; | 
| Chris@216 | 341 	case 1500: val = 2; break; | 
| Chris@216 | 342 	case 2000: val = 3; break; | 
| Chris@216 | 343 	case 4000: val = 4; break; | 
| Chris@216 | 344 	case 6000: val = 5; break; | 
| Chris@216 | 345 	case 8000: val = 6; break; | 
| Chris@216 | 346 	case 12000: val = 7; break; | 
| Chris@216 | 347 	case 16000: val = 8; break; | 
| Chris@216 | 348 	default: val = 9; break; | 
| Chris@0 | 349 	} | 
| Chris@0 | 350 | 
| Chris@87 | 351     } else if (name == "Frequency Scale") { | 
| Chris@0 | 352 | 
| Chris@0 | 353 	*min = 0; | 
| Chris@0 | 354 	*max = 1; | 
| Chris@216 | 355         *deflt = int(LinearFrequencyScale); | 
| Chris@216 | 356 	val = (int)m_frequencyScale; | 
| Chris@0 | 357 | 
| Chris@87 | 358     } else if (name == "Bin Display") { | 
| Chris@35 | 359 | 
| Chris@35 | 360 	*min = 0; | 
| Chris@35 | 361 	*max = 2; | 
| Chris@216 | 362         *deflt = int(AllBins); | 
| Chris@216 | 363 	val = (int)m_binDisplay; | 
| Chris@35 | 364 | 
| Chris@862 | 365     } else if (name == "Normalization") { | 
| Chris@36 | 366 | 
| Chris@862 | 367         *min = 0; | 
| Chris@862 | 368         *max = 3; | 
| Chris@862 | 369         *deflt = int(NoNormalization); | 
| Chris@862 | 370         val = (int)m_normalization; | 
| Chris@120 | 371 | 
| Chris@0 | 372     } else { | 
| Chris@216 | 373 	val = Layer::getPropertyRangeAndValue(name, min, max, deflt); | 
| Chris@0 | 374     } | 
| Chris@0 | 375 | 
| Chris@216 | 376     return val; | 
| Chris@0 | 377 } | 
| Chris@0 | 378 | 
| Chris@0 | 379 QString | 
| Chris@0 | 380 SpectrogramLayer::getPropertyValueLabel(const PropertyName &name, | 
| Chris@9 | 381 					int value) const | 
| Chris@0 | 382 { | 
| Chris@87 | 383     if (name == "Colour") { | 
| Chris@196 | 384         return ColourMapper::getColourMapName(value); | 
| Chris@0 | 385     } | 
| Chris@87 | 386     if (name == "Colour Scale") { | 
| Chris@0 | 387 	switch (value) { | 
| Chris@0 | 388 	default: | 
| Chris@37 | 389 	case 0: return tr("Linear"); | 
| Chris@37 | 390 	case 1: return tr("Meter"); | 
| Chris@215 | 391 	case 2: return tr("dBV^2"); | 
| Chris@215 | 392 	case 3: return tr("dBV"); | 
| Chris@119 | 393 	case 4: return tr("Phase"); | 
| Chris@0 | 394 	} | 
| Chris@0 | 395     } | 
| Chris@862 | 396     if (name == "Normalization") { | 
| Chris@862 | 397         return ""; // icon only | 
| Chris@862 | 398     } | 
| Chris@87 | 399     if (name == "Window Size") { | 
| Chris@0 | 400 	return QString("%1").arg(32 << value); | 
| Chris@0 | 401     } | 
| Chris@97 | 402     if (name == "Window Increment") { | 
| Chris@0 | 403 	switch (value) { | 
| Chris@0 | 404 	default: | 
| Chris@112 | 405 	case 0: return tr("None"); | 
| Chris@112 | 406 	case 1: return tr("25 %"); | 
| Chris@112 | 407 	case 2: return tr("50 %"); | 
| Chris@112 | 408 	case 3: return tr("75 %"); | 
| Chris@112 | 409 	case 4: return tr("87.5 %"); | 
| Chris@112 | 410 	case 5: return tr("93.75 %"); | 
| Chris@0 | 411 	} | 
| Chris@0 | 412     } | 
| Chris@109 | 413     if (name == "Zero Padding") { | 
| Chris@109 | 414         if (value == 0) return tr("None"); | 
| Chris@109 | 415         return QString("%1x").arg(value + 1); | 
| Chris@109 | 416     } | 
| Chris@87 | 417     if (name == "Min Frequency") { | 
| Chris@37 | 418 	switch (value) { | 
| Chris@37 | 419 	default: | 
| Chris@38 | 420 	case 0: return tr("No min"); | 
| Chris@37 | 421 	case 1: return tr("10 Hz"); | 
| Chris@37 | 422 	case 2: return tr("20 Hz"); | 
| Chris@37 | 423 	case 3: return tr("40 Hz"); | 
| Chris@37 | 424 	case 4: return tr("100 Hz"); | 
| Chris@37 | 425 	case 5: return tr("250 Hz"); | 
| Chris@37 | 426 	case 6: return tr("500 Hz"); | 
| Chris@37 | 427 	case 7: return tr("1 KHz"); | 
| Chris@37 | 428 	case 8: return tr("4 KHz"); | 
| Chris@37 | 429 	case 9: return tr("10 KHz"); | 
| Chris@37 | 430 	} | 
| Chris@37 | 431     } | 
| Chris@87 | 432     if (name == "Max Frequency") { | 
| Chris@0 | 433 	switch (value) { | 
| Chris@0 | 434 	default: | 
| Chris@0 | 435 	case 0: return tr("500 Hz"); | 
| Chris@0 | 436 	case 1: return tr("1 KHz"); | 
| Chris@0 | 437 	case 2: return tr("1.5 KHz"); | 
| Chris@0 | 438 	case 3: return tr("2 KHz"); | 
| Chris@0 | 439 	case 4: return tr("4 KHz"); | 
| Chris@0 | 440 	case 5: return tr("6 KHz"); | 
| Chris@0 | 441 	case 6: return tr("8 KHz"); | 
| Chris@0 | 442 	case 7: return tr("12 KHz"); | 
| Chris@0 | 443 	case 8: return tr("16 KHz"); | 
| Chris@38 | 444 	case 9: return tr("No max"); | 
| Chris@0 | 445 	} | 
| Chris@0 | 446     } | 
| Chris@87 | 447     if (name == "Frequency Scale") { | 
| Chris@0 | 448 	switch (value) { | 
| Chris@0 | 449 	default: | 
| Chris@0 | 450 	case 0: return tr("Linear"); | 
| Chris@0 | 451 	case 1: return tr("Log"); | 
| Chris@0 | 452 	} | 
| Chris@0 | 453     } | 
| Chris@87 | 454     if (name == "Bin Display") { | 
| Chris@35 | 455 	switch (value) { | 
| Chris@35 | 456 	default: | 
| Chris@37 | 457 	case 0: return tr("All Bins"); | 
| Chris@37 | 458 	case 1: return tr("Peak Bins"); | 
| Chris@37 | 459 	case 2: return tr("Frequencies"); | 
| Chris@35 | 460 	} | 
| Chris@35 | 461     } | 
| Chris@0 | 462     return tr("<unknown>"); | 
| Chris@0 | 463 } | 
| Chris@0 | 464 | 
| Chris@862 | 465 QString | 
| Chris@862 | 466 SpectrogramLayer::getPropertyValueIconName(const PropertyName &name, | 
| Chris@862 | 467                                            int value) const | 
| Chris@862 | 468 { | 
| Chris@862 | 469     if (name == "Normalization") { | 
| Chris@862 | 470         switch(value) { | 
| Chris@862 | 471         default: | 
| Chris@862 | 472         case 0: return "normalise-none"; | 
| Chris@862 | 473         case 1: return "normalise-columns"; | 
| Chris@862 | 474         case 2: return "normalise"; | 
| Chris@862 | 475         case 3: return "normalise-hybrid"; | 
| Chris@862 | 476         } | 
| Chris@862 | 477     } | 
| Chris@862 | 478     return ""; | 
| Chris@862 | 479 } | 
| Chris@862 | 480 | 
| Chris@167 | 481 RangeMapper * | 
| Chris@167 | 482 SpectrogramLayer::getNewPropertyRangeMapper(const PropertyName &name) const | 
| Chris@167 | 483 { | 
| Chris@167 | 484     if (name == "Gain") { | 
| Chris@167 | 485         return new LinearRangeMapper(-50, 50, -25, 25, tr("dB")); | 
| Chris@167 | 486     } | 
| Chris@167 | 487     if (name == "Threshold") { | 
| Chris@167 | 488         return new LinearRangeMapper(-50, 0, -50, 0, tr("dB")); | 
| Chris@167 | 489     } | 
| Chris@167 | 490     return 0; | 
| Chris@167 | 491 } | 
| Chris@167 | 492 | 
| Chris@0 | 493 void | 
| Chris@0 | 494 SpectrogramLayer::setProperty(const PropertyName &name, int value) | 
| Chris@0 | 495 { | 
| Chris@87 | 496     if (name == "Gain") { | 
| Chris@906 | 497 	setGain(float(pow(10, float(value)/20.0))); | 
| Chris@87 | 498     } else if (name == "Threshold") { | 
| Chris@37 | 499 	if (value == -50) setThreshold(0.0); | 
| Chris@906 | 500 	else setThreshold(float(AudioLevel::dB_to_multiplier(value))); | 
| Chris@87 | 501     } else if (name == "Colour Rotation") { | 
| Chris@9 | 502 	setColourRotation(value); | 
| Chris@87 | 503     } else if (name == "Colour") { | 
| Chris@197 | 504         setColourMap(value); | 
| Chris@87 | 505     } else if (name == "Window Size") { | 
| Chris@0 | 506 	setWindowSize(32 << value); | 
| Chris@97 | 507     } else if (name == "Window Increment") { | 
| Chris@97 | 508         setWindowHopLevel(value); | 
| Chris@109 | 509     } else if (name == "Zero Padding") { | 
| Chris@109 | 510         setZeroPadLevel(value > 0.1 ? 3 : 0); | 
| Chris@87 | 511     } else if (name == "Min Frequency") { | 
| Chris@37 | 512 	switch (value) { | 
| Chris@37 | 513 	default: | 
| Chris@37 | 514 	case 0: setMinFrequency(0); break; | 
| Chris@37 | 515 	case 1: setMinFrequency(10); break; | 
| Chris@37 | 516 	case 2: setMinFrequency(20); break; | 
| Chris@37 | 517 	case 3: setMinFrequency(40); break; | 
| Chris@37 | 518 	case 4: setMinFrequency(100); break; | 
| Chris@37 | 519 	case 5: setMinFrequency(250); break; | 
| Chris@37 | 520 	case 6: setMinFrequency(500); break; | 
| Chris@37 | 521 	case 7: setMinFrequency(1000); break; | 
| Chris@37 | 522 	case 8: setMinFrequency(4000); break; | 
| Chris@37 | 523 	case 9: setMinFrequency(10000); break; | 
| Chris@37 | 524 	} | 
| Chris@133 | 525         int vs = getCurrentVerticalZoomStep(); | 
| Chris@133 | 526         if (vs != m_lastEmittedZoomStep) { | 
| Chris@133 | 527             emit verticalZoomChanged(); | 
| Chris@133 | 528             m_lastEmittedZoomStep = vs; | 
| Chris@133 | 529         } | 
| Chris@87 | 530     } else if (name == "Max Frequency") { | 
| Chris@0 | 531 	switch (value) { | 
| Chris@0 | 532 	case 0: setMaxFrequency(500); break; | 
| Chris@0 | 533 	case 1: setMaxFrequency(1000); break; | 
| Chris@0 | 534 	case 2: setMaxFrequency(1500); break; | 
| Chris@0 | 535 	case 3: setMaxFrequency(2000); break; | 
| Chris@0 | 536 	case 4: setMaxFrequency(4000); break; | 
| Chris@0 | 537 	case 5: setMaxFrequency(6000); break; | 
| Chris@0 | 538 	case 6: setMaxFrequency(8000); break; | 
| Chris@0 | 539 	case 7: setMaxFrequency(12000); break; | 
| Chris@0 | 540 	case 8: setMaxFrequency(16000); break; | 
| Chris@0 | 541 	default: | 
| Chris@0 | 542 	case 9: setMaxFrequency(0); break; | 
| Chris@0 | 543 	} | 
| Chris@133 | 544         int vs = getCurrentVerticalZoomStep(); | 
| Chris@133 | 545         if (vs != m_lastEmittedZoomStep) { | 
| Chris@133 | 546             emit verticalZoomChanged(); | 
| Chris@133 | 547             m_lastEmittedZoomStep = vs; | 
| Chris@133 | 548         } | 
| Chris@87 | 549     } else if (name == "Colour Scale") { | 
| Chris@0 | 550 	switch (value) { | 
| Chris@0 | 551 	default: | 
| Chris@0 | 552 	case 0: setColourScale(LinearColourScale); break; | 
| Chris@0 | 553 	case 1: setColourScale(MeterColourScale); break; | 
| Chris@215 | 554 	case 2: setColourScale(dBSquaredColourScale); break; | 
| Chris@215 | 555 	case 3: setColourScale(dBColourScale); break; | 
| Chris@119 | 556 	case 4: setColourScale(PhaseColourScale); break; | 
| Chris@0 | 557 	} | 
| Chris@87 | 558     } else if (name == "Frequency Scale") { | 
| Chris@0 | 559 	switch (value) { | 
| Chris@0 | 560 	default: | 
| Chris@0 | 561 	case 0: setFrequencyScale(LinearFrequencyScale); break; | 
| Chris@0 | 562 	case 1: setFrequencyScale(LogFrequencyScale); break; | 
| Chris@0 | 563 	} | 
| Chris@87 | 564     } else if (name == "Bin Display") { | 
| Chris@35 | 565 	switch (value) { | 
| Chris@35 | 566 	default: | 
| Chris@37 | 567 	case 0: setBinDisplay(AllBins); break; | 
| Chris@37 | 568 	case 1: setBinDisplay(PeakBins); break; | 
| Chris@37 | 569 	case 2: setBinDisplay(PeakFrequencies); break; | 
| Chris@35 | 570 	} | 
| Chris@862 | 571     } else if (name == "Normalization") { | 
| Chris@862 | 572         switch (value) { | 
| Chris@862 | 573         default: | 
| Chris@862 | 574         case 0: setNormalization(NoNormalization); break; | 
| Chris@862 | 575         case 1: setNormalization(NormalizeColumns); break; | 
| Chris@862 | 576         case 2: setNormalization(NormalizeVisibleArea); break; | 
| Chris@862 | 577         case 3: setNormalization(NormalizeHybrid); break; | 
| Chris@862 | 578         } | 
| Chris@0 | 579     } | 
| Chris@0 | 580 } | 
| Chris@0 | 581 | 
| Chris@0 | 582 void | 
| Chris@478 | 583 SpectrogramLayer::invalidateImageCaches() | 
| Chris@95 | 584 { | 
| Chris@478 | 585     for (ViewImageCache::iterator i = m_imageCaches.begin(); | 
| Chris@478 | 586          i != m_imageCaches.end(); ++i) { | 
| Chris@95 | 587         i->second.validArea = QRect(); | 
| Chris@95 | 588     } | 
| Chris@95 | 589 } | 
| Chris@95 | 590 | 
| Chris@95 | 591 void | 
| Chris@906 | 592 SpectrogramLayer::invalidateImageCaches(sv_frame_t startFrame, sv_frame_t endFrame) | 
| Chris@95 | 593 { | 
| Chris@478 | 594     for (ViewImageCache::iterator i = m_imageCaches.begin(); | 
| Chris@478 | 595          i != m_imageCaches.end(); ++i) { | 
| Chris@131 | 596 | 
| Chris@95 | 597         //!!! when are views removed from the map? on setLayerDormant? | 
| Chris@918 | 598         const LayerGeometryProvider *v = i->first; | 
| Chris@95 | 599 | 
| Chris@391 | 600 #ifdef DEBUG_SPECTROGRAM_REPAINT | 
| Chris@985 | 601         cerr << "SpectrogramLayer::invalidateImageCaches(" | 
| Chris@391 | 602                   << startFrame << ", " << endFrame << "): view range is " | 
| Chris@391 | 603                   << v->getStartFrame() << ", " << v->getEndFrame() | 
| Chris@585 | 604                   << endl; | 
| Chris@391 | 605 | 
| Chris@682 | 606         cerr << "Valid area was: " << i->second.validArea.x() << ", " | 
| Chris@391 | 607                   << i->second.validArea.y() << " " | 
| Chris@391 | 608                   << i->second.validArea.width() << "x" | 
| Chris@682 | 609                   << i->second.validArea.height() << endl; | 
| Chris@391 | 610 #endif | 
| Chris@391 | 611 | 
| Chris@806 | 612         if (int(startFrame) > v->getStartFrame()) { | 
| Chris@391 | 613             if (startFrame >= v->getEndFrame()) { | 
| Chris@391 | 614 #ifdef DEBUG_SPECTROGRAM_REPAINT | 
| Chris@682 | 615                 cerr << "Modified start frame is off right of view" << endl; | 
| Chris@391 | 616 #endif | 
| Chris@391 | 617                 return; | 
| Chris@391 | 618             } | 
| Chris@391 | 619             int x = v->getXForFrame(startFrame); | 
| Chris@407 | 620 #ifdef DEBUG_SPECTROGRAM_REPAINT | 
| Chris@985 | 621             cerr << "clipping from 0 to " << x-1 << endl; | 
| Chris@407 | 622 #endif | 
| Chris@391 | 623             if (x > 1) { | 
| Chris@391 | 624                 i->second.validArea &= | 
| Chris@918 | 625                     QRect(0, 0, x-1, v->getPaintHeight()); | 
| Chris@391 | 626             } else { | 
| Chris@391 | 627                 i->second.validArea = QRect(); | 
| Chris@391 | 628             } | 
| Chris@391 | 629         } else { | 
| Chris@806 | 630             if (int(endFrame) < v->getStartFrame()) { | 
| Chris@391 | 631 #ifdef DEBUG_SPECTROGRAM_REPAINT | 
| Chris@682 | 632                 cerr << "Modified end frame is off left of view" << endl; | 
| Chris@391 | 633 #endif | 
| Chris@391 | 634                 return; | 
| Chris@391 | 635             } | 
| Chris@391 | 636             int x = v->getXForFrame(endFrame); | 
| Chris@391 | 637 #ifdef DEBUG_SPECTROGRAM_REPAINT | 
| Chris@988 | 638             cerr << "clipping from " << x+1 << " to " << v->getPaintWidth() | 
| Chris@585 | 639                       << endl; | 
| Chris@391 | 640 #endif | 
| Chris@918 | 641             if (x < v->getPaintWidth()) { | 
| Chris@391 | 642                 i->second.validArea &= | 
| Chris@918 | 643                     QRect(x+1, 0, v->getPaintWidth()-(x+1), v->getPaintHeight()); | 
| Chris@391 | 644             } else { | 
| Chris@391 | 645                 i->second.validArea = QRect(); | 
| Chris@391 | 646             } | 
| Chris@95 | 647         } | 
| Chris@391 | 648 | 
| Chris@391 | 649 #ifdef DEBUG_SPECTROGRAM_REPAINT | 
| Chris@682 | 650         cerr << "Valid area is now: " << i->second.validArea.x() << ", " | 
| Chris@391 | 651                   << i->second.validArea.y() << " " | 
| Chris@391 | 652                   << i->second.validArea.width() << "x" | 
| Chris@682 | 653                   << i->second.validArea.height() << endl; | 
| Chris@391 | 654 #endif | 
| Chris@95 | 655     } | 
| Chris@95 | 656 } | 
| Chris@95 | 657 | 
| Chris@95 | 658 void | 
| Chris@122 | 659 SpectrogramLayer::preferenceChanged(PropertyContainer::PropertyName name) | 
| Chris@122 | 660 { | 
| Chris@587 | 661     SVDEBUG << "SpectrogramLayer::preferenceChanged(" << name << ")" << endl; | 
| Chris@122 | 662 | 
| Chris@122 | 663     if (name == "Window Type") { | 
| Chris@122 | 664         setWindowType(Preferences::getInstance()->getWindowType()); | 
| Chris@122 | 665         return; | 
| Chris@122 | 666     } | 
| Chris@490 | 667     if (name == "Spectrogram Y Smoothing") { | 
| Chris@490 | 668         invalidateImageCaches(); | 
| Chris@490 | 669         invalidateMagnitudes(); | 
| Chris@490 | 670         emit layerParametersChanged(); | 
| Chris@490 | 671     } | 
| Chris@490 | 672     if (name == "Spectrogram X Smoothing") { | 
| Chris@478 | 673         invalidateImageCaches(); | 
| Chris@122 | 674         invalidateMagnitudes(); | 
| Chris@122 | 675         emit layerParametersChanged(); | 
| Chris@122 | 676     } | 
| Chris@122 | 677     if (name == "Tuning Frequency") { | 
| Chris@122 | 678         emit layerParametersChanged(); | 
| Chris@122 | 679     } | 
| Chris@122 | 680 } | 
| Chris@122 | 681 | 
| Chris@122 | 682 void | 
| Chris@0 | 683 SpectrogramLayer::setChannel(int ch) | 
| Chris@0 | 684 { | 
| Chris@0 | 685     if (m_channel == ch) return; | 
| Chris@0 | 686 | 
| Chris@478 | 687     invalidateImageCaches(); | 
| Chris@0 | 688     m_channel = ch; | 
| Chris@130 | 689     invalidateFFTModels(); | 
| Chris@9 | 690 | 
| Chris@0 | 691     emit layerParametersChanged(); | 
| Chris@0 | 692 } | 
| Chris@0 | 693 | 
| Chris@0 | 694 int | 
| Chris@0 | 695 SpectrogramLayer::getChannel() const | 
| Chris@0 | 696 { | 
| Chris@0 | 697     return m_channel; | 
| Chris@0 | 698 } | 
| Chris@0 | 699 | 
| Chris@0 | 700 void | 
| Chris@805 | 701 SpectrogramLayer::setWindowSize(int ws) | 
| Chris@0 | 702 { | 
| Chris@0 | 703     if (m_windowSize == ws) return; | 
| Chris@0 | 704 | 
| Chris@478 | 705     invalidateImageCaches(); | 
| Chris@0 | 706 | 
| Chris@0 | 707     m_windowSize = ws; | 
| Chris@109 | 708     m_fftSize = ws * (m_zeroPadLevel + 1); | 
| Chris@0 | 709 | 
| Chris@130 | 710     invalidateFFTModels(); | 
| Chris@9 | 711 | 
| Chris@9 | 712     emit layerParametersChanged(); | 
| Chris@0 | 713 } | 
| Chris@0 | 714 | 
| Chris@805 | 715 int | 
| Chris@0 | 716 SpectrogramLayer::getWindowSize() const | 
| Chris@0 | 717 { | 
| Chris@0 | 718     return m_windowSize; | 
| Chris@0 | 719 } | 
| Chris@0 | 720 | 
| Chris@0 | 721 void | 
| Chris@805 | 722 SpectrogramLayer::setWindowHopLevel(int v) | 
| Chris@0 | 723 { | 
| Chris@97 | 724     if (m_windowHopLevel == v) return; | 
| Chris@0 | 725 | 
| Chris@478 | 726     invalidateImageCaches(); | 
| Chris@0 | 727 | 
| Chris@97 | 728     m_windowHopLevel = v; | 
| Chris@0 | 729 | 
| Chris@130 | 730     invalidateFFTModels(); | 
| Chris@9 | 731 | 
| Chris@9 | 732     emit layerParametersChanged(); | 
| Chris@9 | 733 | 
| Chris@110 | 734 //    fillCache(); | 
| Chris@0 | 735 } | 
| Chris@0 | 736 | 
| Chris@805 | 737 int | 
| Chris@97 | 738 SpectrogramLayer::getWindowHopLevel() const | 
| Chris@0 | 739 { | 
| Chris@97 | 740     return m_windowHopLevel; | 
| Chris@0 | 741 } | 
| Chris@0 | 742 | 
| Chris@0 | 743 void | 
| Chris@805 | 744 SpectrogramLayer::setZeroPadLevel(int v) | 
| Chris@109 | 745 { | 
| Chris@109 | 746     if (m_zeroPadLevel == v) return; | 
| Chris@109 | 747 | 
| Chris@478 | 748     invalidateImageCaches(); | 
| Chris@109 | 749 | 
| Chris@109 | 750     m_zeroPadLevel = v; | 
| Chris@109 | 751     m_fftSize = m_windowSize * (v + 1); | 
| Chris@110 | 752 | 
| Chris@130 | 753     invalidateFFTModels(); | 
| Chris@109 | 754 | 
| Chris@109 | 755     emit layerParametersChanged(); | 
| Chris@109 | 756 } | 
| Chris@109 | 757 | 
| Chris@805 | 758 int | 
| Chris@109 | 759 SpectrogramLayer::getZeroPadLevel() const | 
| Chris@109 | 760 { | 
| Chris@109 | 761     return m_zeroPadLevel; | 
| Chris@109 | 762 } | 
| Chris@109 | 763 | 
| Chris@109 | 764 void | 
| Chris@0 | 765 SpectrogramLayer::setWindowType(WindowType w) | 
| Chris@0 | 766 { | 
| Chris@0 | 767     if (m_windowType == w) return; | 
| Chris@0 | 768 | 
| Chris@478 | 769     invalidateImageCaches(); | 
| Chris@0 | 770 | 
| Chris@0 | 771     m_windowType = w; | 
| Chris@110 | 772 | 
| Chris@130 | 773     invalidateFFTModels(); | 
| Chris@9 | 774 | 
| Chris@9 | 775     emit layerParametersChanged(); | 
| Chris@0 | 776 } | 
| Chris@0 | 777 | 
| Chris@0 | 778 WindowType | 
| Chris@0 | 779 SpectrogramLayer::getWindowType() const | 
| Chris@0 | 780 { | 
| Chris@0 | 781     return m_windowType; | 
| Chris@0 | 782 } | 
| Chris@0 | 783 | 
| Chris@0 | 784 void | 
| Chris@0 | 785 SpectrogramLayer::setGain(float gain) | 
| Chris@0 | 786 { | 
| Chris@587 | 787 //    SVDEBUG << "SpectrogramLayer::setGain(" << gain << ") (my gain is now " | 
| Chris@585 | 788 //	      << m_gain << ")" << endl; | 
| Chris@55 | 789 | 
| Chris@40 | 790     if (m_gain == gain) return; | 
| Chris@0 | 791 | 
| Chris@478 | 792     invalidateImageCaches(); | 
| Chris@0 | 793 | 
| Chris@0 | 794     m_gain = gain; | 
| Chris@0 | 795 | 
| Chris@9 | 796     emit layerParametersChanged(); | 
| Chris@0 | 797 } | 
| Chris@0 | 798 | 
| Chris@0 | 799 float | 
| Chris@0 | 800 SpectrogramLayer::getGain() const | 
| Chris@0 | 801 { | 
| Chris@0 | 802     return m_gain; | 
| Chris@0 | 803 } | 
| Chris@0 | 804 | 
| Chris@0 | 805 void | 
| Chris@37 | 806 SpectrogramLayer::setThreshold(float threshold) | 
| Chris@37 | 807 { | 
| Chris@40 | 808     if (m_threshold == threshold) return; | 
| Chris@37 | 809 | 
| Chris@478 | 810     invalidateImageCaches(); | 
| Chris@37 | 811 | 
| Chris@37 | 812     m_threshold = threshold; | 
| Chris@37 | 813 | 
| Chris@37 | 814     emit layerParametersChanged(); | 
| Chris@37 | 815 } | 
| Chris@37 | 816 | 
| Chris@37 | 817 float | 
| Chris@37 | 818 SpectrogramLayer::getThreshold() const | 
| Chris@37 | 819 { | 
| Chris@37 | 820     return m_threshold; | 
| Chris@37 | 821 } | 
| Chris@37 | 822 | 
| Chris@37 | 823 void | 
| Chris@805 | 824 SpectrogramLayer::setMinFrequency(int mf) | 
| Chris@37 | 825 { | 
| Chris@37 | 826     if (m_minFrequency == mf) return; | 
| Chris@37 | 827 | 
| Chris@587 | 828 //    SVDEBUG << "SpectrogramLayer::setMinFrequency: " << mf << endl; | 
| Chris@187 | 829 | 
| Chris@478 | 830     invalidateImageCaches(); | 
| Chris@119 | 831     invalidateMagnitudes(); | 
| Chris@37 | 832 | 
| Chris@37 | 833     m_minFrequency = mf; | 
| Chris@37 | 834 | 
| Chris@37 | 835     emit layerParametersChanged(); | 
| Chris@37 | 836 } | 
| Chris@37 | 837 | 
| Chris@805 | 838 int | 
| Chris@37 | 839 SpectrogramLayer::getMinFrequency() const | 
| Chris@37 | 840 { | 
| Chris@37 | 841     return m_minFrequency; | 
| Chris@37 | 842 } | 
| Chris@37 | 843 | 
| Chris@37 | 844 void | 
| Chris@805 | 845 SpectrogramLayer::setMaxFrequency(int mf) | 
| Chris@0 | 846 { | 
| Chris@0 | 847     if (m_maxFrequency == mf) return; | 
| Chris@0 | 848 | 
| Chris@587 | 849 //    SVDEBUG << "SpectrogramLayer::setMaxFrequency: " << mf << endl; | 
| Chris@187 | 850 | 
| Chris@478 | 851     invalidateImageCaches(); | 
| Chris@119 | 852     invalidateMagnitudes(); | 
| Chris@0 | 853 | 
| Chris@0 | 854     m_maxFrequency = mf; | 
| Chris@0 | 855 | 
| Chris@9 | 856     emit layerParametersChanged(); | 
| Chris@0 | 857 } | 
| Chris@0 | 858 | 
| Chris@805 | 859 int | 
| Chris@0 | 860 SpectrogramLayer::getMaxFrequency() const | 
| Chris@0 | 861 { | 
| Chris@0 | 862     return m_maxFrequency; | 
| Chris@0 | 863 } | 
| Chris@0 | 864 | 
| Chris@0 | 865 void | 
| Chris@9 | 866 SpectrogramLayer::setColourRotation(int r) | 
| Chris@9 | 867 { | 
| Chris@478 | 868     invalidateImageCaches(); | 
| Chris@9 | 869 | 
| Chris@9 | 870     if (r < 0) r = 0; | 
| Chris@9 | 871     if (r > 256) r = 256; | 
| Chris@9 | 872     int distance = r - m_colourRotation; | 
| Chris@9 | 873 | 
| Chris@9 | 874     if (distance != 0) { | 
| Chris@197 | 875 	rotatePalette(-distance); | 
| Chris@9 | 876 	m_colourRotation = r; | 
| Chris@9 | 877     } | 
| Chris@9 | 878 | 
| Chris@9 | 879     emit layerParametersChanged(); | 
| Chris@9 | 880 } | 
| Chris@9 | 881 | 
| Chris@9 | 882 void | 
| Chris@0 | 883 SpectrogramLayer::setColourScale(ColourScale colourScale) | 
| Chris@0 | 884 { | 
| Chris@0 | 885     if (m_colourScale == colourScale) return; | 
| Chris@0 | 886 | 
| Chris@478 | 887     invalidateImageCaches(); | 
| Chris@0 | 888 | 
| Chris@0 | 889     m_colourScale = colourScale; | 
| Chris@0 | 890 | 
| Chris@9 | 891     emit layerParametersChanged(); | 
| Chris@0 | 892 } | 
| Chris@0 | 893 | 
| Chris@0 | 894 SpectrogramLayer::ColourScale | 
| Chris@0 | 895 SpectrogramLayer::getColourScale() const | 
| Chris@0 | 896 { | 
| Chris@0 | 897     return m_colourScale; | 
| Chris@0 | 898 } | 
| Chris@0 | 899 | 
| Chris@0 | 900 void | 
| Chris@197 | 901 SpectrogramLayer::setColourMap(int map) | 
| Chris@0 | 902 { | 
| Chris@197 | 903     if (m_colourMap == map) return; | 
| Chris@0 | 904 | 
| Chris@478 | 905     invalidateImageCaches(); | 
| Chris@0 | 906 | 
| Chris@197 | 907     m_colourMap = map; | 
| Chris@197 | 908     initialisePalette(); | 
| Chris@9 | 909 | 
| Chris@0 | 910     emit layerParametersChanged(); | 
| Chris@0 | 911 } | 
| Chris@0 | 912 | 
| Chris@196 | 913 int | 
| Chris@197 | 914 SpectrogramLayer::getColourMap() const | 
| Chris@0 | 915 { | 
| Chris@197 | 916     return m_colourMap; | 
| Chris@0 | 917 } | 
| Chris@0 | 918 | 
| Chris@0 | 919 void | 
| Chris@0 | 920 SpectrogramLayer::setFrequencyScale(FrequencyScale frequencyScale) | 
| Chris@0 | 921 { | 
| Chris@0 | 922     if (m_frequencyScale == frequencyScale) return; | 
| Chris@0 | 923 | 
| Chris@478 | 924     invalidateImageCaches(); | 
| Chris@0 | 925     m_frequencyScale = frequencyScale; | 
| Chris@9 | 926 | 
| Chris@9 | 927     emit layerParametersChanged(); | 
| Chris@0 | 928 } | 
| Chris@0 | 929 | 
| Chris@0 | 930 SpectrogramLayer::FrequencyScale | 
| Chris@0 | 931 SpectrogramLayer::getFrequencyScale() const | 
| Chris@0 | 932 { | 
| Chris@0 | 933     return m_frequencyScale; | 
| Chris@0 | 934 } | 
| Chris@0 | 935 | 
| Chris@0 | 936 void | 
| Chris@37 | 937 SpectrogramLayer::setBinDisplay(BinDisplay binDisplay) | 
| Chris@35 | 938 { | 
| Chris@37 | 939     if (m_binDisplay == binDisplay) return; | 
| Chris@35 | 940 | 
| Chris@478 | 941     invalidateImageCaches(); | 
| Chris@37 | 942     m_binDisplay = binDisplay; | 
| Chris@35 | 943 | 
| Chris@35 | 944     emit layerParametersChanged(); | 
| Chris@35 | 945 } | 
| Chris@35 | 946 | 
| Chris@37 | 947 SpectrogramLayer::BinDisplay | 
| Chris@37 | 948 SpectrogramLayer::getBinDisplay() const | 
| Chris@35 | 949 { | 
| Chris@37 | 950     return m_binDisplay; | 
| Chris@35 | 951 } | 
| Chris@35 | 952 | 
| Chris@35 | 953 void | 
| Chris@862 | 954 SpectrogramLayer::setNormalization(Normalization n) | 
| Chris@36 | 955 { | 
| Chris@862 | 956     if (m_normalization == n) return; | 
| Chris@36 | 957 | 
| Chris@478 | 958     invalidateImageCaches(); | 
| Chris@119 | 959     invalidateMagnitudes(); | 
| Chris@862 | 960     m_normalization = n; | 
| Chris@36 | 961 | 
| Chris@36 | 962     emit layerParametersChanged(); | 
| Chris@36 | 963 } | 
| Chris@36 | 964 | 
| Chris@862 | 965 SpectrogramLayer::Normalization | 
| Chris@862 | 966 SpectrogramLayer::getNormalization() const | 
| Chris@36 | 967 { | 
| Chris@862 | 968     return m_normalization; | 
| Chris@36 | 969 } | 
| Chris@36 | 970 | 
| Chris@36 | 971 void | 
| Chris@918 | 972 SpectrogramLayer::setLayerDormant(const LayerGeometryProvider *v, bool dormant) | 
| Chris@29 | 973 { | 
| Chris@33 | 974     if (dormant) { | 
| Chris@33 | 975 | 
| Chris@331 | 976 #ifdef DEBUG_SPECTROGRAM_REPAINT | 
| Chris@985 | 977         cerr << "SpectrogramLayer::setLayerDormant(" << dormant << ")" | 
| Chris@585 | 978                   << endl; | 
| Chris@331 | 979 #endif | 
| Chris@331 | 980 | 
| Chris@131 | 981         if (isLayerDormant(v)) { | 
| Chris@131 | 982             return; | 
| Chris@131 | 983         } | 
| Chris@131 | 984 | 
| Chris@131 | 985         Layer::setLayerDormant(v, true); | 
| Chris@33 | 986 | 
| Chris@920 | 987         const View *view = v->getView(); | 
| Chris@920 | 988 | 
| Chris@478 | 989 	invalidateImageCaches(); | 
| Chris@988 | 990 | 
| Chris@920 | 991         m_imageCaches.erase(view); | 
| Chris@920 | 992 | 
| Chris@920 | 993         if (m_fftModels.find(view) != m_fftModels.end()) { | 
| Chris@920 | 994 | 
| Chris@988 | 995             if (m_sliceableModel == m_fftModels[view]) { | 
| Chris@193 | 996                 bool replaced = false; | 
| Chris@193 | 997                 for (ViewFFTMap::iterator i = m_fftModels.begin(); | 
| Chris@193 | 998                      i != m_fftModels.end(); ++i) { | 
| Chris@985 | 999                     if (i->second != m_sliceableModel) { | 
| Chris@985 | 1000                         emit sliceableModelReplaced(m_sliceableModel, i->second); | 
| Chris@193 | 1001                         replaced = true; | 
| Chris@193 | 1002                         break; | 
| Chris@193 | 1003                     } | 
| Chris@193 | 1004                 } | 
| Chris@193 | 1005                 if (!replaced) emit sliceableModelReplaced(m_sliceableModel, 0); | 
| Chris@193 | 1006             } | 
| Chris@193 | 1007 | 
| Chris@988 | 1008             delete m_fftModels[view]; | 
| Chris@920 | 1009             m_fftModels.erase(view); | 
| Chris@920 | 1010 | 
| Chris@920 | 1011             delete m_peakCaches[view]; | 
| Chris@920 | 1012             m_peakCaches.erase(view); | 
| Chris@114 | 1013         } | 
| Chris@33 | 1014 | 
| Chris@33 | 1015     } else { | 
| Chris@33 | 1016 | 
| Chris@131 | 1017         Layer::setLayerDormant(v, false); | 
| Chris@33 | 1018     } | 
| Chris@29 | 1019 } | 
| Chris@29 | 1020 | 
| Chris@29 | 1021 void | 
| Chris@0 | 1022 SpectrogramLayer::cacheInvalid() | 
| Chris@0 | 1023 { | 
| Chris@391 | 1024 #ifdef DEBUG_SPECTROGRAM_REPAINT | 
| Chris@985 | 1025     cerr << "SpectrogramLayer::cacheInvalid()" << endl; | 
| Chris@391 | 1026 #endif | 
| Chris@391 | 1027 | 
| Chris@478 | 1028     invalidateImageCaches(); | 
| Chris@119 | 1029     invalidateMagnitudes(); | 
| Chris@0 | 1030 } | 
| Chris@0 | 1031 | 
| Chris@0 | 1032 void | 
| Chris@906 | 1033 SpectrogramLayer::cacheInvalid(sv_frame_t from, sv_frame_t to) | 
| Chris@0 | 1034 { | 
| Chris@391 | 1035 #ifdef DEBUG_SPECTROGRAM_REPAINT | 
| Chris@985 | 1036     cerr << "SpectrogramLayer::cacheInvalid(" << from << ", " << to << ")" << endl; | 
| Chris@391 | 1037 #endif | 
| Chris@391 | 1038 | 
| Chris@478 | 1039     invalidateImageCaches(from, to); | 
| Chris@391 | 1040     invalidateMagnitudes(); | 
| Chris@0 | 1041 } | 
| Chris@0 | 1042 | 
| Chris@224 | 1043 bool | 
| Chris@224 | 1044 SpectrogramLayer::hasLightBackground() const | 
| Chris@224 | 1045 { | 
| Chris@287 | 1046     return ColourMapper(m_colourMap, 1.f, 255.f).hasLightBackground(); | 
| Chris@224 | 1047 } | 
| Chris@224 | 1048 | 
| Chris@0 | 1049 void | 
| Chris@197 | 1050 SpectrogramLayer::initialisePalette() | 
| Chris@0 | 1051 { | 
| Chris@10 | 1052     int formerRotation = m_colourRotation; | 
| Chris@10 | 1053 | 
| Chris@197 | 1054     if (m_colourMap == (int)ColourMapper::BlackOnWhite) { | 
| Chris@197 | 1055 	m_palette.setColour(NO_VALUE, Qt::white); | 
| Chris@38 | 1056     } else { | 
| Chris@197 | 1057 	m_palette.setColour(NO_VALUE, Qt::black); | 
| Chris@38 | 1058     } | 
| Chris@0 | 1059 | 
| Chris@197 | 1060     ColourMapper mapper(m_colourMap, 1.f, 255.f); | 
| Chris@196 | 1061 | 
| Chris@0 | 1062     for (int pixel = 1; pixel < 256; ++pixel) { | 
| Chris@907 | 1063         m_palette.setColour((unsigned char)pixel, mapper.map(pixel)); | 
| Chris@0 | 1064     } | 
| Chris@9 | 1065 | 
| Chris@196 | 1066     m_crosshairColour = mapper.getContrastingColour(); | 
| Chris@196 | 1067 | 
| Chris@9 | 1068     m_colourRotation = 0; | 
| Chris@197 | 1069     rotatePalette(m_colourRotation - formerRotation); | 
| Chris@10 | 1070     m_colourRotation = formerRotation; | 
| Chris@478 | 1071 | 
| Chris@478 | 1072     m_drawBuffer = QImage(); | 
| Chris@9 | 1073 } | 
| Chris@9 | 1074 | 
| Chris@9 | 1075 void | 
| Chris@197 | 1076 SpectrogramLayer::rotatePalette(int distance) | 
| Chris@9 | 1077 { | 
| Chris@31 | 1078     QColor newPixels[256]; | 
| Chris@9 | 1079 | 
| Chris@197 | 1080     newPixels[NO_VALUE] = m_palette.getColour(NO_VALUE); | 
| Chris@9 | 1081 | 
| Chris@9 | 1082     for (int pixel = 1; pixel < 256; ++pixel) { | 
| Chris@9 | 1083 	int target = pixel + distance; | 
| Chris@9 | 1084 	while (target < 1) target += 255; | 
| Chris@9 | 1085 	while (target > 255) target -= 255; | 
| Chris@907 | 1086 	newPixels[target] = m_palette.getColour((unsigned char)pixel); | 
| Chris@9 | 1087     } | 
| Chris@9 | 1088 | 
| Chris@9 | 1089     for (int pixel = 0; pixel < 256; ++pixel) { | 
| Chris@907 | 1090 	m_palette.setColour((unsigned char)pixel, newPixels[pixel]); | 
| Chris@9 | 1091     } | 
| Chris@478 | 1092 | 
| Chris@478 | 1093     m_drawBuffer = QImage(); | 
| Chris@0 | 1094 } | 
| Chris@0 | 1095 | 
| Chris@38 | 1096 unsigned char | 
| Chris@918 | 1097 SpectrogramLayer::getDisplayValue(LayerGeometryProvider *v, double input) const | 
| Chris@38 | 1098 { | 
| Chris@38 | 1099     int value; | 
| Chris@37 | 1100 | 
| Chris@907 | 1101     double min = 0.0; | 
| Chris@907 | 1102     double max = 1.0; | 
| Chris@120 | 1103 | 
| Chris@862 | 1104     if (m_normalization == NormalizeVisibleArea) { | 
| Chris@120 | 1105         min = m_viewMags[v].getMin(); | 
| Chris@120 | 1106         max = m_viewMags[v].getMax(); | 
| Chris@862 | 1107     } else if (m_normalization != NormalizeColumns) { | 
| Chris@224 | 1108         if (m_colourScale == LinearColourScale //|| | 
| Chris@224 | 1109 //            m_colourScale == MeterColourScale) { | 
| Chris@224 | 1110             ) { | 
| Chris@907 | 1111             max = 0.1; | 
| Chris@120 | 1112         } | 
| Chris@120 | 1113     } | 
| Chris@120 | 1114 | 
| Chris@907 | 1115     double thresh = -80.0; | 
| Chris@907 | 1116 | 
| Chris@907 | 1117     if (max == 0.0) max = 1.0; | 
| Chris@907 | 1118     if (max == min) min = max - 0.0001; | 
| Chris@119 | 1119 | 
| Chris@40 | 1120     switch (m_colourScale) { | 
| Chris@40 | 1121 | 
| Chris@40 | 1122     default: | 
| Chris@40 | 1123     case LinearColourScale: | 
| Chris@907 | 1124         value = int(((input - min) / (max - min)) * 255.0) + 1; | 
| Chris@40 | 1125 	break; | 
| Chris@40 | 1126 | 
| Chris@40 | 1127     case MeterColourScale: | 
| Chris@210 | 1128         value = AudioLevel::multiplier_to_preview | 
| Chris@210 | 1129             ((input - min) / (max - min), 254) + 1; | 
| Chris@40 | 1130 	break; | 
| Chris@119 | 1131 | 
| Chris@210 | 1132     case dBSquaredColourScale: | 
| Chris@215 | 1133         input = ((input - min) * (input - min)) / ((max - min) * (max - min)); | 
| Chris@907 | 1134         if (input > 0.0) { | 
| Chris@907 | 1135             input = 10.0 * log10(input); | 
| Chris@133 | 1136         } else { | 
| Chris@133 | 1137             input = thresh; | 
| Chris@133 | 1138         } | 
| Chris@907 | 1139         if (min > 0.0) { | 
| Chris@907 | 1140             thresh = 10.0 * log10(min * min); | 
| Chris@907 | 1141             if (thresh < -80.0) thresh = -80.0; | 
| Chris@119 | 1142         } | 
| Chris@119 | 1143 	input = (input - thresh) / (-thresh); | 
| Chris@907 | 1144 	if (input < 0.0) input = 0.0; | 
| Chris@907 | 1145 	if (input > 1.0) input = 1.0; | 
| Chris@907 | 1146 	value = int(input * 255.0) + 1; | 
| Chris@119 | 1147 	break; | 
| Chris@40 | 1148 | 
| Chris@215 | 1149     case dBColourScale: | 
| Chris@215 | 1150         //!!! experiment with normalizing the visible area this way. | 
| Chris@215 | 1151         //In any case, we need to have some indication of what the dB | 
| Chris@215 | 1152         //scale is relative to. | 
| Chris@215 | 1153         input = (input - min) / (max - min); | 
| Chris@907 | 1154         if (input > 0.0) { | 
| Chris@907 | 1155             input = 10.0 * log10(input); | 
| Chris@215 | 1156         } else { | 
| Chris@215 | 1157             input = thresh; | 
| Chris@215 | 1158         } | 
| Chris@907 | 1159         if (min > 0.0) { | 
| Chris@907 | 1160             thresh = 10.0 * log10(min); | 
| Chris@907 | 1161             if (thresh < -80.0) thresh = -80.0; | 
| Chris@215 | 1162         } | 
| Chris@215 | 1163 	input = (input - thresh) / (-thresh); | 
| Chris@907 | 1164 	if (input < 0.0) input = 0.0; | 
| Chris@907 | 1165 	if (input > 1.0) input = 1.0; | 
| Chris@907 | 1166 	value = int(input * 255.0) + 1; | 
| Chris@215 | 1167 	break; | 
| Chris@215 | 1168 | 
| Chris@40 | 1169     case PhaseColourScale: | 
| Chris@40 | 1170 	value = int((input * 127.0 / M_PI) + 128); | 
| Chris@40 | 1171 	break; | 
| Chris@0 | 1172     } | 
| Chris@210 | 1173 | 
| Chris@38 | 1174     if (value > UCHAR_MAX) value = UCHAR_MAX; | 
| Chris@38 | 1175     if (value < 0) value = 0; | 
| Chris@907 | 1176     return (unsigned char)value; | 
| Chris@0 | 1177 } | 
| Chris@0 | 1178 | 
| Chris@905 | 1179 double | 
| Chris@40 | 1180 SpectrogramLayer::getEffectiveMinFrequency() const | 
| Chris@40 | 1181 { | 
| Chris@907 | 1182     sv_samplerate_t sr = m_model->getSampleRate(); | 
| Chris@905 | 1183     double minf = double(sr) / m_fftSize; | 
| Chris@40 | 1184 | 
| Chris@40 | 1185     if (m_minFrequency > 0.0) { | 
| Chris@805 | 1186 	int minbin = int((double(m_minFrequency) * m_fftSize) / sr + 0.01); | 
| Chris@40 | 1187 	if (minbin < 1) minbin = 1; | 
| Chris@107 | 1188 	minf = minbin * sr / m_fftSize; | 
| Chris@40 | 1189     } | 
| Chris@40 | 1190 | 
| Chris@40 | 1191     return minf; | 
| Chris@40 | 1192 } | 
| Chris@40 | 1193 | 
| Chris@905 | 1194 double | 
| Chris@40 | 1195 SpectrogramLayer::getEffectiveMaxFrequency() const | 
| Chris@40 | 1196 { | 
| Chris@907 | 1197     sv_samplerate_t sr = m_model->getSampleRate(); | 
| Chris@905 | 1198     double maxf = double(sr) / 2; | 
| Chris@40 | 1199 | 
| Chris@40 | 1200     if (m_maxFrequency > 0.0) { | 
| Chris@805 | 1201 	int maxbin = int((double(m_maxFrequency) * m_fftSize) / sr + 0.1); | 
| Chris@107 | 1202 	if (maxbin > m_fftSize / 2) maxbin = m_fftSize / 2; | 
| Chris@107 | 1203 	maxf = maxbin * sr / m_fftSize; | 
| Chris@40 | 1204     } | 
| Chris@40 | 1205 | 
| Chris@40 | 1206     return maxf; | 
| Chris@40 | 1207 } | 
| Chris@40 | 1208 | 
| Chris@0 | 1209 bool | 
| Chris@918 | 1210 SpectrogramLayer::getYBinRange(LayerGeometryProvider *v, int y, double &q0, double &q1) const | 
| Chris@0 | 1211 { | 
| Chris@382 | 1212     Profiler profiler("SpectrogramLayer::getYBinRange"); | 
| Chris@382 | 1213 | 
| Chris@918 | 1214     int h = v->getPaintHeight(); | 
| Chris@0 | 1215     if (y < 0 || y >= h) return false; | 
| Chris@0 | 1216 | 
| Chris@907 | 1217     sv_samplerate_t sr = m_model->getSampleRate(); | 
| Chris@905 | 1218     double minf = getEffectiveMinFrequency(); | 
| Chris@905 | 1219     double maxf = getEffectiveMaxFrequency(); | 
| Chris@0 | 1220 | 
| Chris@38 | 1221     bool logarithmic = (m_frequencyScale == LogFrequencyScale); | 
| Chris@38 | 1222 | 
| Chris@44 | 1223     q0 = v->getFrequencyForY(y, minf, maxf, logarithmic); | 
| Chris@44 | 1224     q1 = v->getFrequencyForY(y - 1, minf, maxf, logarithmic); | 
| Chris@38 | 1225 | 
| Chris@490 | 1226     // Now map these on to ("proportions of") actual bins, using raw | 
| Chris@490 | 1227     // FFT size (unsmoothed) | 
| Chris@490 | 1228 | 
| Chris@490 | 1229     q0 = (q0 * m_fftSize) / sr; | 
| Chris@490 | 1230     q1 = (q1 * m_fftSize) / sr; | 
| Chris@0 | 1231 | 
| Chris@0 | 1232     return true; | 
| Chris@0 | 1233 } | 
| Chris@486 | 1234 | 
| Chris@486 | 1235 bool | 
| Chris@918 | 1236 SpectrogramLayer::getSmoothedYBinRange(LayerGeometryProvider *v, int y, double &q0, double &q1) const | 
| Chris@486 | 1237 { | 
| Chris@486 | 1238     Profiler profiler("SpectrogramLayer::getSmoothedYBinRange"); | 
| Chris@486 | 1239 | 
| Chris@918 | 1240     int h = v->getPaintHeight(); | 
| Chris@486 | 1241     if (y < 0 || y >= h) return false; | 
| Chris@486 | 1242 | 
| Chris@907 | 1243     sv_samplerate_t sr = m_model->getSampleRate(); | 
| Chris@905 | 1244     double minf = getEffectiveMinFrequency(); | 
| Chris@905 | 1245     double maxf = getEffectiveMaxFrequency(); | 
| Chris@486 | 1246 | 
| Chris@486 | 1247     bool logarithmic = (m_frequencyScale == LogFrequencyScale); | 
| Chris@486 | 1248 | 
| Chris@486 | 1249     q0 = v->getFrequencyForY(y, minf, maxf, logarithmic); | 
| Chris@486 | 1250     q1 = v->getFrequencyForY(y - 1, minf, maxf, logarithmic); | 
| Chris@486 | 1251 | 
| Chris@490 | 1252     // Now map these on to ("proportions of") actual bins, using raw | 
| Chris@490 | 1253     // FFT size (unsmoothed) | 
| Chris@490 | 1254 | 
| Chris@490 | 1255     q0 = (q0 * getFFTSize(v)) / sr; | 
| Chris@490 | 1256     q1 = (q1 * getFFTSize(v)) / sr; | 
| Chris@486 | 1257 | 
| Chris@486 | 1258     return true; | 
| Chris@486 | 1259 } | 
| Chris@38 | 1260 | 
| Chris@0 | 1261 bool | 
| Chris@918 | 1262 SpectrogramLayer::getXBinRange(LayerGeometryProvider *v, int x, double &s0, double &s1) const | 
| Chris@0 | 1263 { | 
| Chris@907 | 1264     sv_frame_t modelStart = m_model->getStartFrame(); | 
| Chris@907 | 1265     sv_frame_t modelEnd = m_model->getEndFrame(); | 
| Chris@0 | 1266 | 
| Chris@0 | 1267     // Each pixel column covers an exact range of sample frames: | 
| Chris@907 | 1268     sv_frame_t f0 = v->getFrameForX(x) - modelStart; | 
| Chris@907 | 1269     sv_frame_t f1 = v->getFrameForX(x + 1) - modelStart - 1; | 
| Chris@20 | 1270 | 
| Chris@41 | 1271     if (f1 < int(modelStart) || f0 > int(modelEnd)) { | 
| Chris@41 | 1272 	return false; | 
| Chris@41 | 1273     } | 
| Chris@20 | 1274 | 
| Chris@0 | 1275     // And that range may be drawn from a possibly non-integral | 
| Chris@0 | 1276     // range of spectrogram windows: | 
| Chris@0 | 1277 | 
| Chris@805 | 1278     int windowIncrement = getWindowIncrement(); | 
| Chris@905 | 1279     s0 = double(f0) / windowIncrement; | 
| Chris@905 | 1280     s1 = double(f1) / windowIncrement; | 
| Chris@0 | 1281 | 
| Chris@0 | 1282     return true; | 
| Chris@0 | 1283 } | 
| Chris@0 | 1284 | 
| Chris@0 | 1285 bool | 
| Chris@918 | 1286 SpectrogramLayer::getXBinSourceRange(LayerGeometryProvider *v, int x, RealTime &min, RealTime &max) const | 
| Chris@0 | 1287 { | 
| Chris@905 | 1288     double s0 = 0, s1 = 0; | 
| Chris@44 | 1289     if (!getXBinRange(v, x, s0, s1)) return false; | 
| Chris@0 | 1290 | 
| Chris@0 | 1291     int s0i = int(s0 + 0.001); | 
| Chris@0 | 1292     int s1i = int(s1); | 
| Chris@0 | 1293 | 
| Chris@0 | 1294     int windowIncrement = getWindowIncrement(); | 
| Chris@0 | 1295     int w0 = s0i * windowIncrement - (m_windowSize - windowIncrement)/2; | 
| Chris@0 | 1296     int w1 = s1i * windowIncrement + windowIncrement + | 
| Chris@0 | 1297 	(m_windowSize - windowIncrement)/2 - 1; | 
| Chris@0 | 1298 | 
| Chris@0 | 1299     min = RealTime::frame2RealTime(w0, m_model->getSampleRate()); | 
| Chris@0 | 1300     max = RealTime::frame2RealTime(w1, m_model->getSampleRate()); | 
| Chris@0 | 1301     return true; | 
| Chris@0 | 1302 } | 
| Chris@0 | 1303 | 
| Chris@0 | 1304 bool | 
| Chris@918 | 1305 SpectrogramLayer::getYBinSourceRange(LayerGeometryProvider *v, int y, double &freqMin, double &freqMax) | 
| Chris@0 | 1306 const | 
| Chris@0 | 1307 { | 
| Chris@905 | 1308     double q0 = 0, q1 = 0; | 
| Chris@44 | 1309     if (!getYBinRange(v, y, q0, q1)) return false; | 
| Chris@0 | 1310 | 
| Chris@0 | 1311     int q0i = int(q0 + 0.001); | 
| Chris@0 | 1312     int q1i = int(q1); | 
| Chris@0 | 1313 | 
| Chris@907 | 1314     sv_samplerate_t sr = m_model->getSampleRate(); | 
| Chris@0 | 1315 | 
| Chris@0 | 1316     for (int q = q0i; q <= q1i; ++q) { | 
| Chris@121 | 1317 	if (q == q0i) freqMin = (sr * q) / m_fftSize; | 
| Chris@121 | 1318 	if (q == q1i) freqMax = (sr * (q+1)) / m_fftSize; | 
| Chris@0 | 1319     } | 
| Chris@0 | 1320     return true; | 
| Chris@0 | 1321 } | 
| Chris@35 | 1322 | 
| Chris@35 | 1323 bool | 
| Chris@918 | 1324 SpectrogramLayer::getAdjustedYBinSourceRange(LayerGeometryProvider *v, int x, int y, | 
| Chris@905 | 1325 					     double &freqMin, double &freqMax, | 
| Chris@905 | 1326 					     double &adjFreqMin, double &adjFreqMax) | 
| Chris@35 | 1327 const | 
| Chris@35 | 1328 { | 
| Chris@277 | 1329     if (!m_model || !m_model->isOK() || !m_model->isReady()) { | 
| Chris@277 | 1330 	return false; | 
| Chris@277 | 1331     } | 
| Chris@277 | 1332 | 
| Chris@130 | 1333     FFTModel *fft = getFFTModel(v); | 
| Chris@114 | 1334     if (!fft) return false; | 
| Chris@110 | 1335 | 
| Chris@905 | 1336     double s0 = 0, s1 = 0; | 
| Chris@44 | 1337     if (!getXBinRange(v, x, s0, s1)) return false; | 
| Chris@35 | 1338 | 
| Chris@905 | 1339     double q0 = 0, q1 = 0; | 
| Chris@44 | 1340     if (!getYBinRange(v, y, q0, q1)) return false; | 
| Chris@35 | 1341 | 
| Chris@35 | 1342     int s0i = int(s0 + 0.001); | 
| Chris@35 | 1343     int s1i = int(s1); | 
| Chris@35 | 1344 | 
| Chris@35 | 1345     int q0i = int(q0 + 0.001); | 
| Chris@35 | 1346     int q1i = int(q1); | 
| Chris@35 | 1347 | 
| Chris@907 | 1348     sv_samplerate_t sr = m_model->getSampleRate(); | 
| Chris@35 | 1349 | 
| Chris@35 | 1350     bool haveAdj = false; | 
| Chris@35 | 1351 | 
| Chris@37 | 1352     bool peaksOnly = (m_binDisplay == PeakBins || | 
| Chris@37 | 1353 		      m_binDisplay == PeakFrequencies); | 
| Chris@37 | 1354 | 
| Chris@35 | 1355     for (int q = q0i; q <= q1i; ++q) { | 
| Chris@35 | 1356 | 
| Chris@35 | 1357 	for (int s = s0i; s <= s1i; ++s) { | 
| Chris@35 | 1358 | 
| Chris@905 | 1359 	    double binfreq = (double(sr) * q) / m_windowSize; | 
| Chris@35 | 1360 	    if (q == q0i) freqMin = binfreq; | 
| Chris@35 | 1361 	    if (q == q1i) freqMax = binfreq; | 
| Chris@37 | 1362 | 
| Chris@114 | 1363 	    if (peaksOnly && !fft->isLocalPeak(s, q)) continue; | 
| Chris@38 | 1364 | 
| Chris@907 | 1365 	    if (!fft->isOverThreshold(s, q, float(m_threshold * double(m_fftSize)/2.0))) continue; | 
| Chris@907 | 1366 | 
| Chris@907 | 1367             double freq = binfreq; | 
| Chris@40 | 1368 | 
| Chris@114 | 1369 	    if (s < int(fft->getWidth()) - 1) { | 
| Chris@38 | 1370 | 
| Chris@277 | 1371                 fft->estimateStableFrequency(s, q, freq); | 
| Chris@35 | 1372 | 
| Chris@38 | 1373 		if (!haveAdj || freq < adjFreqMin) adjFreqMin = freq; | 
| Chris@38 | 1374 		if (!haveAdj || freq > adjFreqMax) adjFreqMax = freq; | 
| Chris@35 | 1375 | 
| Chris@35 | 1376 		haveAdj = true; | 
| Chris@35 | 1377 	    } | 
| Chris@35 | 1378 	} | 
| Chris@35 | 1379     } | 
| Chris@35 | 1380 | 
| Chris@35 | 1381     if (!haveAdj) { | 
| Chris@40 | 1382 	adjFreqMin = adjFreqMax = 0.0; | 
| Chris@35 | 1383     } | 
| Chris@35 | 1384 | 
| Chris@35 | 1385     return haveAdj; | 
| Chris@35 | 1386 } | 
| Chris@0 | 1387 | 
| Chris@0 | 1388 bool | 
| Chris@918 | 1389 SpectrogramLayer::getXYBinSourceRange(LayerGeometryProvider *v, int x, int y, | 
| Chris@905 | 1390 				      double &min, double &max, | 
| Chris@905 | 1391 				      double &phaseMin, double &phaseMax) const | 
| Chris@0 | 1392 { | 
| Chris@277 | 1393     if (!m_model || !m_model->isOK() || !m_model->isReady()) { | 
| Chris@277 | 1394 	return false; | 
| Chris@277 | 1395     } | 
| Chris@277 | 1396 | 
| Chris@905 | 1397     double q0 = 0, q1 = 0; | 
| Chris@44 | 1398     if (!getYBinRange(v, y, q0, q1)) return false; | 
| Chris@0 | 1399 | 
| Chris@905 | 1400     double s0 = 0, s1 = 0; | 
| Chris@44 | 1401     if (!getXBinRange(v, x, s0, s1)) return false; | 
| Chris@0 | 1402 | 
| Chris@0 | 1403     int q0i = int(q0 + 0.001); | 
| Chris@0 | 1404     int q1i = int(q1); | 
| Chris@0 | 1405 | 
| Chris@0 | 1406     int s0i = int(s0 + 0.001); | 
| Chris@0 | 1407     int s1i = int(s1); | 
| Chris@0 | 1408 | 
| Chris@37 | 1409     bool rv = false; | 
| Chris@37 | 1410 | 
| Chris@805 | 1411     int zp = getZeroPadLevel(v); | 
| Chris@122 | 1412     q0i *= zp + 1; | 
| Chris@122 | 1413     q1i *= zp + 1; | 
| Chris@122 | 1414 | 
| Chris@130 | 1415     FFTModel *fft = getFFTModel(v); | 
| Chris@0 | 1416 | 
| Chris@114 | 1417     if (fft) { | 
| Chris@114 | 1418 | 
| Chris@114 | 1419         int cw = fft->getWidth(); | 
| Chris@114 | 1420         int ch = fft->getHeight(); | 
| Chris@0 | 1421 | 
| Chris@110 | 1422         min = 0.0; | 
| Chris@110 | 1423         max = 0.0; | 
| Chris@110 | 1424         phaseMin = 0.0; | 
| Chris@110 | 1425         phaseMax = 0.0; | 
| Chris@110 | 1426         bool have = false; | 
| Chris@0 | 1427 | 
| Chris@110 | 1428         for (int q = q0i; q <= q1i; ++q) { | 
| Chris@110 | 1429             for (int s = s0i; s <= s1i; ++s) { | 
| Chris@110 | 1430                 if (s >= 0 && q >= 0 && s < cw && q < ch) { | 
| Chris@117 | 1431 | 
| Chris@905 | 1432                     double value; | 
| Chris@38 | 1433 | 
| Chris@114 | 1434                     value = fft->getPhaseAt(s, q); | 
| Chris@110 | 1435                     if (!have || value < phaseMin) { phaseMin = value; } | 
| Chris@110 | 1436                     if (!have || value > phaseMax) { phaseMax = value; } | 
| Chris@91 | 1437 | 
| Chris@907 | 1438                     value = fft->getMagnitudeAt(s, q) / (m_fftSize/2.0); | 
| Chris@110 | 1439                     if (!have || value < min) { min = value; } | 
| Chris@110 | 1440                     if (!have || value > max) { max = value; } | 
| Chris@110 | 1441 | 
| Chris@110 | 1442                     have = true; | 
| Chris@110 | 1443                 } | 
| Chris@110 | 1444             } | 
| Chris@110 | 1445         } | 
| Chris@110 | 1446 | 
| Chris@110 | 1447         if (have) { | 
| Chris@110 | 1448             rv = true; | 
| Chris@110 | 1449         } | 
| Chris@0 | 1450     } | 
| Chris@0 | 1451 | 
| Chris@37 | 1452     return rv; | 
| Chris@0 | 1453 } | 
| Chris@0 | 1454 | 
| Chris@805 | 1455 int | 
| Chris@918 | 1456 SpectrogramLayer::getZeroPadLevel(const LayerGeometryProvider *v) const | 
| Chris@114 | 1457 { | 
| Chris@114 | 1458     //!!! tidy all this stuff | 
| Chris@114 | 1459 | 
| Chris@114 | 1460     if (m_binDisplay != AllBins) return 0; | 
| Chris@221 | 1461 | 
| Chris@221 | 1462     Preferences::SpectrogramSmoothing smoothing = | 
| Chris@221 | 1463         Preferences::getInstance()->getSpectrogramSmoothing(); | 
| Chris@221 | 1464 | 
| Chris@221 | 1465     if (smoothing == Preferences::NoSpectrogramSmoothing || | 
| Chris@221 | 1466         smoothing == Preferences::SpectrogramInterpolated) return 0; | 
| Chris@221 | 1467 | 
| Chris@114 | 1468     if (m_frequencyScale == LogFrequencyScale) return 3; | 
| Chris@114 | 1469 | 
| Chris@907 | 1470     sv_samplerate_t sr = m_model->getSampleRate(); | 
| Chris@114 | 1471 | 
| Chris@805 | 1472     int maxbin = m_fftSize / 2; | 
| Chris@114 | 1473     if (m_maxFrequency > 0) { | 
| Chris@184 | 1474 	maxbin = int((double(m_maxFrequency) * m_fftSize) / sr + 0.1); | 
| Chris@184 | 1475 	if (maxbin > m_fftSize / 2) maxbin = m_fftSize / 2; | 
| Chris@114 | 1476     } | 
| Chris@114 | 1477 | 
| Chris@805 | 1478     int minbin = 1; | 
| Chris@114 | 1479     if (m_minFrequency > 0) { | 
| Chris@114 | 1480 	minbin = int((double(m_minFrequency) * m_fftSize) / sr + 0.1); | 
| Chris@114 | 1481 	if (minbin < 1) minbin = 1; | 
| Chris@184 | 1482 	if (minbin >= maxbin) minbin = maxbin - 1; | 
| Chris@114 | 1483     } | 
| Chris@114 | 1484 | 
| Chris@905 | 1485     double perPixel = | 
| Chris@918 | 1486         double(v->getPaintHeight()) / | 
| Chris@905 | 1487         double((maxbin - minbin) / (m_zeroPadLevel + 1)); | 
| Chris@118 | 1488 | 
| Chris@118 | 1489     if (perPixel > 2.8) { | 
| Chris@118 | 1490         return 3; // 4x oversampling | 
| Chris@118 | 1491     } else if (perPixel > 1.5) { | 
| Chris@118 | 1492         return 1; // 2x | 
| Chris@114 | 1493     } else { | 
| Chris@118 | 1494         return 0; // 1x | 
| Chris@114 | 1495     } | 
| Chris@114 | 1496 } | 
| Chris@114 | 1497 | 
| Chris@805 | 1498 int | 
| Chris@918 | 1499 SpectrogramLayer::getFFTSize(const LayerGeometryProvider *v) const | 
| Chris@114 | 1500 { | 
| Chris@114 | 1501     return m_fftSize * (getZeroPadLevel(v) + 1); | 
| Chris@114 | 1502 } | 
| Chris@114 | 1503 | 
| Chris@130 | 1504 FFTModel * | 
| Chris@918 | 1505 SpectrogramLayer::getFFTModel(const LayerGeometryProvider *v) const | 
| Chris@114 | 1506 { | 
| Chris@114 | 1507     if (!m_model) return 0; | 
| Chris@114 | 1508 | 
| Chris@805 | 1509     int fftSize = getFFTSize(v); | 
| Chris@114 | 1510 | 
| Chris@920 | 1511     const View *view = v->getView(); | 
| Chris@920 | 1512 | 
| Chris@920 | 1513     if (m_fftModels.find(view) != m_fftModels.end()) { | 
| Chris@988 | 1514         if (m_fftModels[view] == 0) { | 
| Chris@184 | 1515 #ifdef DEBUG_SPECTROGRAM_REPAINT | 
| Chris@985 | 1516             cerr << "SpectrogramLayer::getFFTModel(" << v << "): Found null model" << endl; | 
| Chris@184 | 1517 #endif | 
| Chris@184 | 1518             return 0; | 
| Chris@184 | 1519         } | 
| Chris@988 | 1520         if (m_fftModels[view]->getHeight() != fftSize / 2 + 1) { | 
| Chris@184 | 1521 #ifdef DEBUG_SPECTROGRAM_REPAINT | 
| Chris@1001 | 1522             cerr << "SpectrogramLayer::getFFTModel(" << v << "): Found a model with the wrong height (" << m_fftModels[view]->getHeight() << ", wanted " << (fftSize / 2 + 1) << ")" << endl; | 
| Chris@184 | 1523 #endif | 
| Chris@988 | 1524             delete m_fftModels[view]; | 
| Chris@920 | 1525             m_fftModels.erase(view); | 
| Chris@920 | 1526             delete m_peakCaches[view]; | 
| Chris@920 | 1527             m_peakCaches.erase(view); | 
| Chris@184 | 1528         } else { | 
| Chris@184 | 1529 #ifdef DEBUG_SPECTROGRAM_REPAINT | 
| Chris@988 | 1530             cerr << "SpectrogramLayer::getFFTModel(" << v << "): Found a good model of height " << m_fftModels[view]->getHeight() << endl; | 
| Chris@184 | 1531 #endif | 
| Chris@988 | 1532             return m_fftModels[view]; | 
| Chris@114 | 1533         } | 
| Chris@114 | 1534     } | 
| Chris@114 | 1535 | 
| Chris@920 | 1536     if (m_fftModels.find(view) == m_fftModels.end()) { | 
| Chris@169 | 1537 | 
| Chris@169 | 1538         FFTModel *model = new FFTModel(m_model, | 
| Chris@169 | 1539                                        m_channel, | 
| Chris@169 | 1540                                        m_windowType, | 
| Chris@169 | 1541                                        m_windowSize, | 
| Chris@169 | 1542                                        getWindowIncrement(), | 
| Chris@975 | 1543                                        fftSize); | 
| Chris@169 | 1544 | 
| Chris@178 | 1545         if (!model->isOK()) { | 
| Chris@178 | 1546             QMessageBox::critical | 
| Chris@178 | 1547                 (0, tr("FFT cache failed"), | 
| Chris@178 | 1548                  tr("Failed to create the FFT model for this spectrogram.\n" | 
| Chris@178 | 1549                     "There may be insufficient memory or disc space to continue.")); | 
| Chris@178 | 1550             delete model; | 
| Chris@988 | 1551             m_fftModels[view] = 0; | 
| Chris@178 | 1552             return 0; | 
| Chris@178 | 1553         } | 
| Chris@178 | 1554 | 
| Chris@193 | 1555         if (!m_sliceableModel) { | 
| Chris@248 | 1556 #ifdef DEBUG_SPECTROGRAM | 
| Chris@682 | 1557             cerr << "SpectrogramLayer: emitting sliceableModelReplaced(0, " << model << ")" << endl; | 
| Chris@248 | 1558 #endif | 
| Chris@193 | 1559             ((SpectrogramLayer *)this)->sliceableModelReplaced(0, model); | 
| Chris@193 | 1560             m_sliceableModel = model; | 
| Chris@193 | 1561         } | 
| Chris@193 | 1562 | 
| Chris@988 | 1563         m_fftModels[view] = model; | 
| Chris@114 | 1564     } | 
| Chris@114 | 1565 | 
| Chris@988 | 1566     return m_fftModels[view]; | 
| Chris@114 | 1567 } | 
| Chris@114 | 1568 | 
| Chris@484 | 1569 Dense3DModelPeakCache * | 
| Chris@918 | 1570 SpectrogramLayer::getPeakCache(const LayerGeometryProvider *v) const | 
| Chris@484 | 1571 { | 
| Chris@920 | 1572     const View *view = v->getView(); | 
| Chris@920 | 1573     if (!m_peakCaches[view]) { | 
| Chris@484 | 1574         FFTModel *f = getFFTModel(v); | 
| Chris@484 | 1575         if (!f) return 0; | 
| Chris@920 | 1576         m_peakCaches[view] = new Dense3DModelPeakCache(f, 8); | 
| Chris@484 | 1577     } | 
| Chris@920 | 1578     return m_peakCaches[view]; | 
| Chris@484 | 1579 } | 
| Chris@484 | 1580 | 
| Chris@193 | 1581 const Model * | 
| Chris@193 | 1582 SpectrogramLayer::getSliceableModel() const | 
| Chris@193 | 1583 { | 
| Chris@193 | 1584     if (m_sliceableModel) return m_sliceableModel; | 
| Chris@193 | 1585     if (m_fftModels.empty()) return 0; | 
| Chris@985 | 1586     m_sliceableModel = m_fftModels.begin()->second; | 
| Chris@193 | 1587     return m_sliceableModel; | 
| Chris@193 | 1588 } | 
| Chris@193 | 1589 | 
| Chris@114 | 1590 void | 
| Chris@130 | 1591 SpectrogramLayer::invalidateFFTModels() | 
| Chris@114 | 1592 { | 
| Chris@130 | 1593     for (ViewFFTMap::iterator i = m_fftModels.begin(); | 
| Chris@130 | 1594          i != m_fftModels.end(); ++i) { | 
| Chris@985 | 1595         delete i->second; | 
| Chris@114 | 1596     } | 
| Chris@486 | 1597     for (PeakCacheMap::iterator i = m_peakCaches.begin(); | 
| Chris@486 | 1598          i != m_peakCaches.end(); ++i) { | 
| Chris@486 | 1599         delete i->second; | 
| Chris@486 | 1600     } | 
| Chris@114 | 1601 | 
| Chris@130 | 1602     m_fftModels.clear(); | 
| Chris@486 | 1603     m_peakCaches.clear(); | 
| Chris@193 | 1604 | 
| Chris@193 | 1605     if (m_sliceableModel) { | 
| Chris@682 | 1606         cerr << "SpectrogramLayer: emitting sliceableModelReplaced(" << m_sliceableModel << ", 0)" << endl; | 
| Chris@193 | 1607         emit sliceableModelReplaced(m_sliceableModel, 0); | 
| Chris@193 | 1608         m_sliceableModel = 0; | 
| Chris@193 | 1609     } | 
| Chris@114 | 1610 } | 
| Chris@114 | 1611 | 
| Chris@0 | 1612 void | 
| Chris@119 | 1613 SpectrogramLayer::invalidateMagnitudes() | 
| Chris@119 | 1614 { | 
| Chris@119 | 1615     m_viewMags.clear(); | 
| Chris@1025 | 1616     for (vector<MagnitudeRange>::iterator i = m_columnMags.begin(); | 
| Chris@119 | 1617          i != m_columnMags.end(); ++i) { | 
| Chris@119 | 1618         *i = MagnitudeRange(); | 
| Chris@119 | 1619     } | 
| Chris@119 | 1620 } | 
| Chris@119 | 1621 | 
| Chris@119 | 1622 bool | 
| Chris@918 | 1623 SpectrogramLayer::updateViewMagnitudes(LayerGeometryProvider *v) const | 
| Chris@119 | 1624 { | 
| Chris@119 | 1625     MagnitudeRange mag; | 
| Chris@119 | 1626 | 
| Chris@918 | 1627     int x0 = 0, x1 = v->getPaintWidth(); | 
| Chris@905 | 1628     double s00 = 0, s01 = 0, s10 = 0, s11 = 0; | 
| Chris@119 | 1629 | 
| Chris@203 | 1630     if (!getXBinRange(v, x0, s00, s01)) { | 
| Chris@907 | 1631         s00 = s01 = double(m_model->getStartFrame()) / getWindowIncrement(); | 
| Chris@203 | 1632     } | 
| Chris@203 | 1633 | 
| Chris@203 | 1634     if (!getXBinRange(v, x1, s10, s11)) { | 
| Chris@907 | 1635         s10 = s11 = double(m_model->getEndFrame()) / getWindowIncrement(); | 
| Chris@203 | 1636     } | 
| Chris@119 | 1637 | 
| Chris@1025 | 1638     int s0 = int(min(s00, s10) + 0.0001); | 
| Chris@1025 | 1639     int s1 = int(max(s01, s11) + 0.0001); | 
| Chris@203 | 1640 | 
| Chris@587 | 1641 //    SVDEBUG << "SpectrogramLayer::updateViewMagnitudes: x0 = " << x0 << ", x1 = " << x1 << ", s00 = " << s00 << ", s11 = " << s11 << " s0 = " << s0 << ", s1 = " << s1 << endl; | 
| Chris@119 | 1642 | 
| Chris@248 | 1643     if (int(m_columnMags.size()) <= s1) { | 
| Chris@119 | 1644         m_columnMags.resize(s1 + 1); | 
| Chris@119 | 1645     } | 
| Chris@119 | 1646 | 
| Chris@119 | 1647     for (int s = s0; s <= s1; ++s) { | 
| Chris@119 | 1648         if (m_columnMags[s].isSet()) { | 
| Chris@119 | 1649             mag.sample(m_columnMags[s]); | 
| Chris@119 | 1650         } | 
| Chris@119 | 1651     } | 
| Chris@119 | 1652 | 
| Chris@184 | 1653 #ifdef DEBUG_SPECTROGRAM_REPAINT | 
| Chris@985 | 1654     cerr << "SpectrogramLayer::updateViewMagnitudes returning from cols " | 
| Chris@585 | 1655               << s0 << " -> " << s1 << " inclusive" << endl; | 
| Chris@184 | 1656 #endif | 
| Chris@119 | 1657 | 
| Chris@119 | 1658     if (!mag.isSet()) return false; | 
| Chris@119 | 1659     if (mag == m_viewMags[v]) return false; | 
| Chris@119 | 1660     m_viewMags[v] = mag; | 
| Chris@119 | 1661     return true; | 
| Chris@119 | 1662 } | 
| Chris@119 | 1663 | 
| Chris@119 | 1664 void | 
| Chris@389 | 1665 SpectrogramLayer::setSynchronousPainting(bool synchronous) | 
| Chris@389 | 1666 { | 
| Chris@389 | 1667     m_synchronous = synchronous; | 
| Chris@389 | 1668 } | 
| Chris@389 | 1669 | 
| Chris@389 | 1670 void | 
| Chris@916 | 1671 SpectrogramLayer::paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const | 
| Chris@0 | 1672 { | 
| Chris@253 | 1673     // What a lovely, old-fashioned function this is. | 
| Chris@253 | 1674     // It's practically FORTRAN 77 in its clarity and linearity. | 
| Chris@253 | 1675 | 
| Chris@334 | 1676     Profiler profiler("SpectrogramLayer::paint", false); | 
| Chris@334 | 1677 | 
| Chris@0 | 1678 #ifdef DEBUG_SPECTROGRAM_REPAINT | 
| Chris@1026 | 1679     cerr << "SpectrogramLayer::paint() entering: m_model is " << m_model << ", zoom level is " << v->getZoomLevel() << endl; | 
| Chris@95 | 1680 | 
| Chris@1026 | 1681     cerr << "SpectrogramLayer::paint(): rect is " << rect.x() << "," << rect.y() << " " << rect.width() << "x" << rect.height() << endl; | 
| Chris@0 | 1682 #endif | 
| Chris@95 | 1683 | 
| Chris@907 | 1684     sv_frame_t startFrame = v->getStartFrame(); | 
| Chris@44 | 1685 | 
| Chris@0 | 1686     if (!m_model || !m_model->isOK() || !m_model->isReady()) { | 
| Chris@0 | 1687 	return; | 
| Chris@0 | 1688     } | 
| Chris@0 | 1689 | 
| Chris@47 | 1690     if (isLayerDormant(v)) { | 
| Chris@587 | 1691 	SVDEBUG << "SpectrogramLayer::paint(): Layer is dormant, making it undormant again" << endl; | 
| Chris@29 | 1692     } | 
| Chris@29 | 1693 | 
| Chris@48 | 1694     // Need to do this even if !isLayerDormant, as that could mean v | 
| Chris@48 | 1695     // is not in the dormancy map at all -- we need it to be present | 
| Chris@48 | 1696     // and accountable for when determining whether we need the cache | 
| Chris@48 | 1697     // in the cache-fill thread above. | 
| Chris@806 | 1698     //!!! no inter use cache-fill thread | 
| Chris@131 | 1699     const_cast<SpectrogramLayer *>(this)->Layer::setLayerDormant(v, false); | 
| Chris@48 | 1700 | 
| Chris@805 | 1701     int fftSize = getFFTSize(v); | 
| Chris@920 | 1702 | 
| Chris@920 | 1703     const View *view = v->getView(); | 
| Chris@920 | 1704 | 
| Chris@920 | 1705     ImageCache &cache = m_imageCaches[view]; | 
| Chris@95 | 1706 | 
| Chris@95 | 1707 #ifdef DEBUG_SPECTROGRAM_REPAINT | 
| Chris@1026 | 1708     cerr << "SpectrogramLayer::paint(): image cache valid area from " << cache.validArea.x() << "," << cache.validArea.y() << ", " << cache.validArea.width() << "x" << cache.validArea.height() << endl; | 
| Chris@95 | 1709 #endif | 
| Chris@95 | 1710 | 
| Chris@44 | 1711     int zoomLevel = v->getZoomLevel(); | 
| Chris@0 | 1712 | 
| Chris@0 | 1713     int x0 = 0; | 
| Chris@918 | 1714     int x1 = v->getPaintWidth(); | 
| Chris@0 | 1715 | 
| Chris@478 | 1716     bool recreateWholeImageCache = true; | 
| Chris@0 | 1717 | 
| Chris@95 | 1718     x0 = rect.left(); | 
| Chris@95 | 1719     x1 = rect.right() + 1; | 
| Chris@1022 | 1720 | 
| Chris@1022 | 1721     if (updateViewMagnitudes(v)) { | 
| Chris@1022 | 1722 #ifdef DEBUG_SPECTROGRAM_REPAINT | 
| Chris@1022 | 1723         cerr << "SpectrogramLayer: magnitude range changed to [" << m_viewMags[v].getMin() << "->" << m_viewMags[v].getMax() << "]" << endl; | 
| Chris@1022 | 1724 #endif | 
| Chris@1022 | 1725         if (m_normalization == NormalizeVisibleArea) { | 
| Chris@1022 | 1726             cache.validArea = QRect(); | 
| Chris@1022 | 1727         } | 
| Chris@1022 | 1728     } | 
| Chris@1023 | 1729 | 
| Chris@95 | 1730     if (cache.validArea.width() > 0) { | 
| Chris@95 | 1731 | 
| Chris@482 | 1732         int cw = cache.image.width(); | 
| Chris@482 | 1733         int ch = cache.image.height(); | 
| Chris@482 | 1734 | 
| Chris@95 | 1735 	if (int(cache.zoomLevel) == zoomLevel && | 
| Chris@918 | 1736 	    cw == v->getPaintWidth() && | 
| Chris@918 | 1737 	    ch == v->getPaintHeight()) { | 
| Chris@95 | 1738 | 
| Chris@1022 | 1739             // cache size and zoom level exactly match the view | 
| Chris@1022 | 1740 | 
| Chris@95 | 1741 	    if (v->getXForFrame(cache.startFrame) == | 
| Chris@95 | 1742 		v->getXForFrame(startFrame) && | 
| Chris@95 | 1743                 cache.validArea.x() <= x0 && | 
| Chris@95 | 1744                 cache.validArea.x() + cache.validArea.width() >= x1) { | 
| Chris@1022 | 1745 | 
| Chris@1022 | 1746                 // and cache begins at the right frame, so use it whole | 
| Chris@1022 | 1747 | 
| Chris@0 | 1748 #ifdef DEBUG_SPECTROGRAM_REPAINT | 
| Chris@682 | 1749 		cerr << "SpectrogramLayer: image cache good" << endl; | 
| Chris@0 | 1750 #endif | 
| Chris@0 | 1751 | 
| Chris@478 | 1752 		paint.drawImage(rect, cache.image, rect); | 
| Chris@479 | 1753 | 
| Chris@121 | 1754                 illuminateLocalFeatures(v, paint); | 
| Chris@0 | 1755 		return; | 
| Chris@0 | 1756 | 
| Chris@0 | 1757 	    } else { | 
| Chris@0 | 1758 | 
| Chris@1022 | 1759                 // cache doesn't begin at the right frame or doesn't | 
| Chris@1022 | 1760                 // contain the complete view, but might be scrollable | 
| Chris@1022 | 1761                 // or partially usable | 
| Chris@1022 | 1762 | 
| Chris@0 | 1763 #ifdef DEBUG_SPECTROGRAM_REPAINT | 
| Chris@682 | 1764 		cerr << "SpectrogramLayer: image cache partially OK" << endl; | 
| Chris@0 | 1765 #endif | 
| Chris@0 | 1766 | 
| Chris@478 | 1767 		recreateWholeImageCache = false; | 
| Chris@0 | 1768 | 
| Chris@95 | 1769 		int dx = v->getXForFrame(cache.startFrame) - | 
| Chris@44 | 1770 		         v->getXForFrame(startFrame); | 
| Chris@0 | 1771 | 
| Chris@0 | 1772 #ifdef DEBUG_SPECTROGRAM_REPAINT | 
| Chris@682 | 1773 		cerr << "SpectrogramLayer: dx = " << dx << " (image cache " << cw << "x" << ch << ")" << endl; | 
| Chris@0 | 1774 #endif | 
| Chris@0 | 1775 | 
| Chris@95 | 1776 		if (dx != 0 && | 
| Chris@482 | 1777                     dx > -cw && | 
| Chris@482 | 1778                     dx <  cw) { | 
| Chris@1022 | 1779 | 
| Chris@1022 | 1780                     // cache is scrollable, scroll it | 
| Chris@482 | 1781 | 
| Chris@482 | 1782                     int dxp = dx; | 
| Chris@482 | 1783                     if (dxp < 0) dxp = -dxp; | 
| Chris@907 | 1784                     size_t copy = (cw - dxp) * sizeof(QRgb); | 
| Chris@482 | 1785                     for (int y = 0; y < ch; ++y) { | 
| Chris@482 | 1786                         QRgb *line = (QRgb *)cache.image.scanLine(y); | 
| Chris@482 | 1787                         if (dx < 0) { | 
| Chris@482 | 1788                             memmove(line, line + dxp, copy); | 
| Chris@482 | 1789                         } else { | 
| Chris@482 | 1790                             memmove(line + dxp, line, copy); | 
| Chris@482 | 1791                         } | 
| Chris@331 | 1792                     } | 
| Chris@0 | 1793 | 
| Chris@1022 | 1794                     // and calculate its new valid area | 
| Chris@1022 | 1795 | 
| Chris@95 | 1796                     int px = cache.validArea.x(); | 
| Chris@95 | 1797                     int pw = cache.validArea.width(); | 
| Chris@0 | 1798 | 
| Chris@1026 | 1799                     // so px and pw will be updated to the new x and | 
| Chris@1026 | 1800                     // width of the valid area of cache; | 
| Chris@1026 | 1801 | 
| Chris@1026 | 1802                     // x0 and x1 will be the left and right extents of | 
| Chris@1026 | 1803                     // the area needing repainted | 
| Chris@1026 | 1804 | 
| Chris@1026 | 1805                     px += dx; | 
| Chris@1026 | 1806 | 
| Chris@0 | 1807 		    if (dx < 0) { | 
| Chris@1026 | 1808                         // we scrolled left | 
| Chris@95 | 1809                         if (px < 0) { | 
| Chris@95 | 1810                             pw += px; | 
| Chris@95 | 1811                             px = 0; | 
| Chris@1026 | 1812                             if (pw < 0) { | 
| Chris@1026 | 1813                                 pw = 0; | 
| Chris@1026 | 1814                             } | 
| Chris@95 | 1815                         } | 
| Chris@1026 | 1816                         x0 = px + pw; | 
| Chris@1026 | 1817                         x1 = cw; | 
| Chris@1026 | 1818                     } else { | 
| Chris@1026 | 1819                         // we scrolled right | 
| Chris@482 | 1820                         if (px + pw > cw) { | 
| Chris@1026 | 1821                             pw = cw - px; | 
| Chris@1026 | 1822                             if (pw < 0) { | 
| Chris@1026 | 1823                                 pw = 0; | 
| Chris@1026 | 1824                             } | 
| Chris@95 | 1825                         } | 
| Chris@1026 | 1826                         x0 = 0; | 
| Chris@1026 | 1827                         x1 = px; | 
| Chris@1026 | 1828                     } | 
| Chris@1026 | 1829 | 
| Chris@95 | 1830                     cache.validArea = | 
| Chris@95 | 1831                         QRect(px, cache.validArea.y(), | 
| Chris@95 | 1832                               pw, cache.validArea.height()); | 
| Chris@95 | 1833 | 
| Chris@1028 | 1834                     if (cache.validArea.width() == 0) { | 
| Chris@1028 | 1835                         recreateWholeImageCache = true; | 
| Chris@1028 | 1836                     } | 
| Chris@1028 | 1837 | 
| Chris@331 | 1838 #ifdef DEBUG_SPECTROGRAM_REPAINT | 
| Chris@1026 | 1839                     cerr << "SpectrogramLayer: valid area now " | 
| Chris@331 | 1840                               << px << "," << cache.validArea.y() | 
| Chris@331 | 1841                               << " " << pw << "x" << cache.validArea.height() | 
| Chris@682 | 1842                               << endl; | 
| Chris@331 | 1843 #endif | 
| Chris@1022 | 1844 | 
| Chris@331 | 1845                 } else if (dx != 0) { | 
| Chris@331 | 1846 | 
| Chris@1022 | 1847                     // we've moved too far from the cached area for it | 
| Chris@1022 | 1848                     // to be of use | 
| Chris@331 | 1849 | 
| Chris@391 | 1850 #ifdef DEBUG_SPECTROGRAM_REPAINT | 
| Chris@1026 | 1851                     cerr << "SpectrogramLayer: dx == " << dx << ": scrolled too far for cache to be useful" << endl; | 
| Chris@391 | 1852 #endif | 
| Chris@391 | 1853 | 
| Chris@331 | 1854                     cache.validArea = QRect(); | 
| Chris@478 | 1855                     recreateWholeImageCache = true; | 
| Chris@331 | 1856                 } | 
| Chris@0 | 1857 	    } | 
| Chris@0 | 1858 	} else { | 
| Chris@0 | 1859 #ifdef DEBUG_SPECTROGRAM_REPAINT | 
| Chris@682 | 1860 	    cerr << "SpectrogramLayer: image cache useless" << endl; | 
| Chris@224 | 1861             if (int(cache.zoomLevel) != zoomLevel) { | 
| Chris@682 | 1862                 cerr << "(cache zoomLevel " << cache.zoomLevel | 
| Chris@682 | 1863                           << " != " << zoomLevel << ")" << endl; | 
| Chris@224 | 1864             } | 
| Chris@918 | 1865             if (cw != v->getPaintWidth()) { | 
| Chris@682 | 1866                 cerr << "(cache width " << cw | 
| Chris@918 | 1867                           << " != " << v->getPaintWidth(); | 
| Chris@224 | 1868             } | 
| Chris@918 | 1869             if (ch != v->getPaintHeight()) { | 
| Chris@682 | 1870                 cerr << "(cache height " << ch | 
| Chris@918 | 1871                           << " != " << v->getPaintHeight(); | 
| Chris@224 | 1872             } | 
| Chris@0 | 1873 #endif | 
| Chris@95 | 1874             cache.validArea = QRect(); | 
| Chris@0 | 1875 	} | 
| Chris@0 | 1876     } | 
| Chris@95 | 1877 | 
| Chris@478 | 1878     if (recreateWholeImageCache) { | 
| Chris@1028 | 1879         if (!m_synchronous) { | 
| Chris@1028 | 1880             // When rendering the whole thing, start from somewhere near | 
| Chris@1028 | 1881             // the middle so that the region of interest appears first | 
| Chris@1028 | 1882             x0 = int(v->getPaintWidth() * 0.4); | 
| Chris@1028 | 1883         } else { | 
| Chris@1028 | 1884             x0 = 0; | 
| Chris@1028 | 1885         } | 
| Chris@918 | 1886         x1 = v->getPaintWidth(); | 
| Chris@95 | 1887     } | 
| Chris@95 | 1888 | 
| Chris@224 | 1889     // We always paint the full height when refreshing the cache. | 
| Chris@224 | 1890     // Smaller heights can be used when painting direct from cache | 
| Chris@224 | 1891     // (further up in this function), but we want to ensure the cache | 
| Chris@224 | 1892     // is coherent without having to worry about vertical matching of | 
| Chris@224 | 1893     // required and valid areas as well as horizontal. | 
| Chris@224 | 1894 | 
| Chris@918 | 1895     int h = v->getPaintHeight(); | 
| Chris@1025 | 1896 | 
| Chris@1024 | 1897     int repaintWidth = x1 - x0; | 
| Chris@0 | 1898 | 
| Chris@1026 | 1899     // If we are painting a section to the left of a valid area of | 
| Chris@1026 | 1900     // cache, then we must paint it "backwards", right-to-left. This | 
| Chris@1026 | 1901     // is because painting may be interrupted by a timeout, leaving us | 
| Chris@1026 | 1902     // with a partially painted area, and we have no way to record | 
| Chris@1026 | 1903     // that the union of the existing cached area and this new | 
| Chris@1026 | 1904     // partially painted bit is a valid cache area unless they are | 
| Chris@1026 | 1905     // adjacent (because our valid extent is a single x,width range). | 
| Chris@1026 | 1906 | 
| Chris@1026 | 1907     bool rightToLeft = (x0 == 0 && x1 < v->getPaintWidth()); | 
| Chris@1026 | 1908 | 
| Chris@95 | 1909 #ifdef DEBUG_SPECTROGRAM_REPAINT | 
| Chris@1026 | 1910     cerr << "SpectrogramLayer: x0 " << x0 << ", x1 " << x1 | 
| Chris@1026 | 1911          << ", repaintWidth " << repaintWidth << ", h " << h | 
| Chris@1026 | 1912          << ", rightToLeft " << rightToLeft << endl; | 
| Chris@95 | 1913 #endif | 
| Chris@95 | 1914 | 
| Chris@907 | 1915     sv_samplerate_t sr = m_model->getSampleRate(); | 
| Chris@122 | 1916 | 
| Chris@122 | 1917     // Set minFreq and maxFreq to the frequency extents of the possibly | 
| Chris@122 | 1918     // zero-padded visible bin range, and displayMinFreq and displayMaxFreq | 
| Chris@122 | 1919     // to the actual scale frequency extents (presumably not zero padded). | 
| Chris@253 | 1920 | 
| Chris@253 | 1921     // If we are zero padding, we want to use the zero-padded | 
| Chris@253 | 1922     // equivalents of the bins that we would be using if not zero | 
| Chris@253 | 1923     // padded, to avoid spaces at the top and bottom of the display. | 
| Chris@253 | 1924 | 
| Chris@253 | 1925     // Note fftSize is the actual zero-padded fft size, m_fftSize the | 
| Chris@253 | 1926     // nominal fft size. | 
| Chris@35 | 1927 | 
| Chris@805 | 1928     int maxbin = m_fftSize / 2; | 
| Chris@35 | 1929     if (m_maxFrequency > 0) { | 
| Chris@253 | 1930 	maxbin = int((double(m_maxFrequency) * m_fftSize) / sr + 0.001); | 
| Chris@253 | 1931 	if (maxbin > m_fftSize / 2) maxbin = m_fftSize / 2; | 
| Chris@35 | 1932     } | 
| Chris@111 | 1933 | 
| Chris@805 | 1934     int minbin = 1; | 
| Chris@37 | 1935     if (m_minFrequency > 0) { | 
| Chris@253 | 1936 	minbin = int((double(m_minFrequency) * m_fftSize) / sr + 0.001); | 
| Chris@682 | 1937 //        cerr << "m_minFrequency = " << m_minFrequency << " -> minbin = " << minbin << endl; | 
| Chris@40 | 1938 	if (minbin < 1) minbin = 1; | 
| Chris@184 | 1939 	if (minbin >= maxbin) minbin = maxbin - 1; | 
| Chris@37 | 1940     } | 
| Chris@37 | 1941 | 
| Chris@253 | 1942     int zpl = getZeroPadLevel(v) + 1; | 
| Chris@253 | 1943     minbin = minbin * zpl; | 
| Chris@253 | 1944     maxbin = (maxbin + 1) * zpl - 1; | 
| Chris@253 | 1945 | 
| Chris@905 | 1946     double minFreq = (double(minbin) * sr) / fftSize; | 
| Chris@905 | 1947     double maxFreq = (double(maxbin) * sr) / fftSize; | 
| Chris@905 | 1948 | 
| Chris@905 | 1949     double displayMinFreq = minFreq; | 
| Chris@905 | 1950     double displayMaxFreq = maxFreq; | 
| Chris@122 | 1951 | 
| Chris@122 | 1952     if (fftSize != m_fftSize) { | 
| Chris@122 | 1953         displayMinFreq = getEffectiveMinFrequency(); | 
| Chris@122 | 1954         displayMaxFreq = getEffectiveMaxFrequency(); | 
| Chris@122 | 1955     } | 
| Chris@122 | 1956 | 
| Chris@682 | 1957 //    cerr << "(giving actual minFreq " << minFreq << " and display minFreq " << displayMinFreq << ")" << endl; | 
| Chris@253 | 1958 | 
| Chris@518 | 1959     int increment = getWindowIncrement(); | 
| Chris@40 | 1960 | 
| Chris@40 | 1961     bool logarithmic = (m_frequencyScale == LogFrequencyScale); | 
| Chris@1026 | 1962 | 
| Chris@119 | 1963     MagnitudeRange overallMag = m_viewMags[v]; | 
| Chris@119 | 1964     bool overallMagChanged = false; | 
| Chris@119 | 1965 | 
| Chris@137 | 1966 #ifdef DEBUG_SPECTROGRAM_REPAINT | 
| Chris@1026 | 1967     cerr << "SpectrogramLayer: " << ((double(v->getFrameForX(1) - v->getFrameForX(0))) / increment) << " bin(s) per pixel" << endl; | 
| Chris@137 | 1968 #endif | 
| Chris@137 | 1969 | 
| Chris@1024 | 1970     if (repaintWidth == 0) { | 
| Chris@1024 | 1971         SVDEBUG << "*** NOTE: repaintWidth == 0" << endl; | 
| Chris@331 | 1972     } | 
| Chris@331 | 1973 | 
| Chris@382 | 1974     Profiler outerprof("SpectrogramLayer::paint: all cols"); | 
| Chris@382 | 1975 | 
| Chris@481 | 1976     // The draw buffer contains a fragment at either our pixel | 
| Chris@481 | 1977     // resolution (if there is more than one time-bin per pixel) or | 
| Chris@481 | 1978     // time-bin resolution (if a time-bin spans more than one pixel). | 
| Chris@481 | 1979     // We need to ensure that it starts and ends at points where a | 
| Chris@481 | 1980     // time-bin boundary occurs at an exact pixel boundary, and with a | 
| Chris@481 | 1981     // certain amount of overlap across existing pixels so that we can | 
| Chris@481 | 1982     // scale and draw from it without smoothing errors at the edges. | 
| Chris@481 | 1983 | 
| Chris@481 | 1984     // If (getFrameForX(x) / increment) * increment == | 
| Chris@481 | 1985     // getFrameForX(x), then x is a time-bin boundary.  We want two | 
| Chris@481 | 1986     // such boundaries at either side of the draw buffer -- one which | 
| Chris@481 | 1987     // we draw up to, and one which we subsequently crop at. | 
| Chris@481 | 1988 | 
| Chris@481 | 1989     bool bufferBinResolution = false; | 
| Chris@481 | 1990     if (increment > zoomLevel) bufferBinResolution = true; | 
| Chris@481 | 1991 | 
| Chris@907 | 1992     sv_frame_t leftBoundaryFrame = -1, leftCropFrame = -1; | 
| Chris@907 | 1993     sv_frame_t rightBoundaryFrame = -1, rightCropFrame = -1; | 
| Chris@481 | 1994 | 
| Chris@481 | 1995     int bufwid; | 
| Chris@481 | 1996 | 
| Chris@481 | 1997     if (bufferBinResolution) { | 
| Chris@481 | 1998 | 
| Chris@482 | 1999         for (int x = x0; ; --x) { | 
| Chris@907 | 2000             sv_frame_t f = v->getFrameForX(x); | 
| Chris@481 | 2001             if ((f / increment) * increment == f) { | 
| Chris@481 | 2002                 if (leftCropFrame == -1) leftCropFrame = f; | 
| Chris@1024 | 2003                 else if (x < x0 - 2) { | 
| Chris@1024 | 2004                     leftBoundaryFrame = f; | 
| Chris@1024 | 2005                     break; | 
| Chris@1024 | 2006                 } | 
| Chris@481 | 2007             } | 
| Chris@481 | 2008         } | 
| Chris@1024 | 2009         for (int x = x0 + repaintWidth; ; ++x) { | 
| Chris@907 | 2010             sv_frame_t f = v->getFrameForX(x); | 
| Chris@481 | 2011             if ((f / increment) * increment == f) { | 
| Chris@481 | 2012                 if (rightCropFrame == -1) rightCropFrame = f; | 
| Chris@1024 | 2013                 else if (x > x0 + repaintWidth + 2) { | 
| Chris@1024 | 2014                     rightBoundaryFrame = f; | 
| Chris@1024 | 2015                     break; | 
| Chris@1024 | 2016                 } | 
| Chris@481 | 2017             } | 
| Chris@481 | 2018         } | 
| Chris@485 | 2019 #ifdef DEBUG_SPECTROGRAM_REPAINT | 
| Chris@481 | 2020         cerr << "Left: crop: " << leftCropFrame << " (bin " << leftCropFrame/increment << "); boundary: " << leftBoundaryFrame << " (bin " << leftBoundaryFrame/increment << ")" << endl; | 
| Chris@481 | 2021         cerr << "Right: crop: " << rightCropFrame << " (bin " << rightCropFrame/increment << "); boundary: " << rightBoundaryFrame << " (bin " << rightBoundaryFrame/increment << ")" << endl; | 
| Chris@485 | 2022 #endif | 
| Chris@481 | 2023 | 
| Chris@907 | 2024         bufwid = int((rightBoundaryFrame - leftBoundaryFrame) / increment); | 
| Chris@481 | 2025 | 
| Chris@481 | 2026     } else { | 
| Chris@481 | 2027 | 
| Chris@1024 | 2028         bufwid = repaintWidth; | 
| Chris@481 | 2029     } | 
| Chris@481 | 2030 | 
| Chris@907 | 2031     vector<int> binforx(bufwid); | 
| Chris@907 | 2032     vector<double> binfory(h); | 
| Chris@907 | 2033 | 
| Chris@484 | 2034     bool usePeaksCache = false; | 
| Chris@484 | 2035 | 
| Chris@481 | 2036     if (bufferBinResolution) { | 
| Chris@481 | 2037         for (int x = 0; x < bufwid; ++x) { | 
| Chris@907 | 2038             binforx[x] = int(leftBoundaryFrame / increment) + x; | 
| Chris@481 | 2039         } | 
| Chris@481 | 2040         m_drawBuffer = QImage(bufwid, h, QImage::Format_Indexed8); | 
| Chris@481 | 2041     } else { | 
| Chris@481 | 2042         for (int x = 0; x < bufwid; ++x) { | 
| Chris@905 | 2043             double s0 = 0, s1 = 0; | 
| Chris@481 | 2044             if (getXBinRange(v, x + x0, s0, s1)) { | 
| Chris@481 | 2045                 binforx[x] = int(s0 + 0.0001); | 
| Chris@481 | 2046             } else { | 
| Chris@487 | 2047                 binforx[x] = -1; //??? | 
| Chris@481 | 2048             } | 
| Chris@481 | 2049         } | 
| Chris@481 | 2050         if (m_drawBuffer.width() < bufwid || m_drawBuffer.height() < h) { | 
| Chris@481 | 2051             m_drawBuffer = QImage(bufwid, h, QImage::Format_Indexed8); | 
| Chris@480 | 2052         } | 
| Chris@484 | 2053         usePeaksCache = (increment * 8) < zoomLevel; | 
| Chris@487 | 2054         if (m_colourScale == PhaseColourScale) usePeaksCache = false; | 
| Chris@480 | 2055     } | 
| Chris@481 | 2056 | 
| Chris@481 | 2057     for (int pixel = 0; pixel < 256; ++pixel) { | 
| Chris@907 | 2058         m_drawBuffer.setColor((unsigned char)pixel, | 
| Chris@907 | 2059                               m_palette.getColour((unsigned char)pixel).rgb()); | 
| Chris@481 | 2060     } | 
| Chris@481 | 2061 | 
| Chris@481 | 2062     m_drawBuffer.fill(0); | 
| Chris@1024 | 2063     int attainedBufwid = bufwid; | 
| Chris@480 | 2064 | 
| Chris@488 | 2065     if (m_binDisplay != PeakFrequencies) { | 
| Chris@488 | 2066 | 
| Chris@488 | 2067         for (int y = 0; y < h; ++y) { | 
| Chris@905 | 2068             double q0 = 0, q1 = 0; | 
| Chris@488 | 2069             if (!getSmoothedYBinRange(v, h-y-1, q0, q1)) { | 
| Chris@488 | 2070                 binfory[y] = -1; | 
| Chris@488 | 2071             } else { | 
| Chris@490 | 2072                 binfory[y] = q0; | 
| Chris@488 | 2073             } | 
| Chris@480 | 2074         } | 
| Chris@488 | 2075 | 
| Chris@1024 | 2076         attainedBufwid = | 
| Chris@1026 | 2077             paintDrawBuffer(v, bufwid, h, binforx, binfory, | 
| Chris@1026 | 2078                             usePeaksCache, | 
| Chris@1026 | 2079                             overallMag, overallMagChanged, | 
| Chris@1026 | 2080                             rightToLeft); | 
| Chris@488 | 2081 | 
| Chris@488 | 2082     } else { | 
| Chris@488 | 2083 | 
| Chris@1024 | 2084         attainedBufwid = | 
| Chris@1024 | 2085             paintDrawBufferPeakFrequencies(v, bufwid, h, binforx, | 
| Chris@1024 | 2086                                            minbin, maxbin, | 
| Chris@1024 | 2087                                            displayMinFreq, displayMaxFreq, | 
| Chris@1024 | 2088                                            logarithmic, | 
| Chris@1026 | 2089                                            overallMag, overallMagChanged, | 
| Chris@1026 | 2090                                            rightToLeft); | 
| Chris@480 | 2091     } | 
| Chris@481 | 2092 | 
| Chris@1024 | 2093     int failedToRepaint = bufwid - attainedBufwid; | 
| Chris@1025 | 2094     if (failedToRepaint > 0) { | 
| Chris@1025 | 2095 #ifdef DEBUG_SPECTROGRAM_REPAINT | 
| Chris@1026 | 2096         cerr << "SpectrogramLayer::paint(): Failed to repaint " << failedToRepaint << " of " << bufwid | 
| Chris@1025 | 2097              << " columns in time" << endl; | 
| Chris@1025 | 2098 #endif | 
| Chris@1025 | 2099     } else if (failedToRepaint < 0) { | 
| Chris@1024 | 2100         cerr << "WARNING: failedToRepaint < 0 (= " << failedToRepaint << ")" | 
| Chris@1024 | 2101              << endl; | 
| Chris@1024 | 2102         failedToRepaint = 0; | 
| Chris@1024 | 2103     } | 
| Chris@1024 | 2104 | 
| Chris@119 | 2105     if (overallMagChanged) { | 
| Chris@119 | 2106         m_viewMags[v] = overallMag; | 
| Chris@209 | 2107 #ifdef DEBUG_SPECTROGRAM_REPAINT | 
| Chris@1026 | 2108         cerr << "SpectrogramLayer: Overall mag is now [" << m_viewMags[v].getMin() << "->" << m_viewMags[v].getMax() << "] - will be updating" << endl; | 
| Chris@209 | 2109 #endif | 
| Chris@119 | 2110     } | 
| Chris@119 | 2111 | 
| Chris@382 | 2112     outerprof.end(); | 
| Chris@382 | 2113 | 
| Chris@382 | 2114     Profiler profiler2("SpectrogramLayer::paint: draw image"); | 
| Chris@137 | 2115 | 
| Chris@478 | 2116     if (recreateWholeImageCache) { | 
| Chris@407 | 2117 #ifdef DEBUG_SPECTROGRAM_REPAINT | 
| Chris@1026 | 2118         cerr << "SpectrogramLayer: Recreating image cache: width = " << v->getPaintWidth() | 
| Chris@585 | 2119                   << ", height = " << h << endl; | 
| Chris@407 | 2120 #endif | 
| Chris@918 | 2121 	cache.image = QImage(v->getPaintWidth(), h, QImage::Format_ARGB32_Premultiplied); | 
| Chris@0 | 2122     } | 
| Chris@0 | 2123 | 
| Chris@1024 | 2124     if (repaintWidth > 0) { | 
| Chris@1024 | 2125 | 
| Chris@224 | 2126 #ifdef DEBUG_SPECTROGRAM_REPAINT | 
| Chris@1026 | 2127         cerr << "SpectrogramLayer: Copying " << repaintWidth << "x" << h | 
| Chris@331 | 2128                   << " from draw buffer at " << 0 << "," << 0 | 
| Chris@1024 | 2129                   << " to " << repaintWidth << "x" << h << " on cache at " | 
| Chris@585 | 2130                   << x0 << "," << 0 << endl; | 
| Chris@224 | 2131 #endif | 
| Chris@224 | 2132 | 
| Chris@478 | 2133         QPainter cachePainter(&cache.image); | 
| Chris@481 | 2134 | 
| Chris@481 | 2135         if (bufferBinResolution) { | 
| Chris@481 | 2136             int scaledLeft = v->getXForFrame(leftBoundaryFrame); | 
| Chris@481 | 2137             int scaledRight = v->getXForFrame(rightBoundaryFrame); | 
| Chris@485 | 2138 #ifdef DEBUG_SPECTROGRAM_REPAINT | 
| Chris@1026 | 2139             cerr << "SpectrogramLayer: Rescaling image from " << bufwid | 
| Chris@481 | 2140                  << "x" << h << " to " | 
| Chris@481 | 2141                  << scaledRight-scaledLeft << "x" << h << endl; | 
| Chris@485 | 2142 #endif | 
| Chris@490 | 2143             Preferences::SpectrogramXSmoothing xsmoothing = | 
| Chris@490 | 2144                 Preferences::getInstance()->getSpectrogramXSmoothing(); | 
| Chris@1026 | 2145 | 
| Chris@481 | 2146             QImage scaled = m_drawBuffer.scaled | 
| Chris@481 | 2147                 (scaledRight - scaledLeft, h, | 
| Chris@490 | 2148                  Qt::IgnoreAspectRatio, | 
| Chris@490 | 2149                  ((xsmoothing == Preferences::SpectrogramXInterpolated) ? | 
| Chris@490 | 2150                   Qt::SmoothTransformation : Qt::FastTransformation)); | 
| Chris@1026 | 2151 | 
| Chris@481 | 2152             int scaledLeftCrop = v->getXForFrame(leftCropFrame); | 
| Chris@481 | 2153             int scaledRightCrop = v->getXForFrame(rightCropFrame); | 
| Chris@485 | 2154 #ifdef DEBUG_SPECTROGRAM_REPAINT | 
| Chris@1026 | 2155             cerr << "SpectrogramLayer: Drawing image region of width " << scaledRightCrop - scaledLeftCrop << " to " | 
| Chris@481 | 2156                  << scaledLeftCrop << " from " << scaledLeftCrop - scaledLeft << endl; | 
| Chris@485 | 2157 #endif | 
| Chris@481 | 2158             cachePainter.drawImage | 
| Chris@481 | 2159                 (QRect(scaledLeftCrop, 0, | 
| Chris@481 | 2160                        scaledRightCrop - scaledLeftCrop, h), | 
| Chris@481 | 2161                  scaled, | 
| Chris@481 | 2162                  QRect(scaledLeftCrop - scaledLeft, 0, | 
| Chris@481 | 2163                        scaledRightCrop - scaledLeftCrop, h)); | 
| Chris@1024 | 2164 | 
| Chris@481 | 2165         } else { | 
| Chris@1024 | 2166 | 
| Chris@1024 | 2167             cachePainter.drawImage(QRect(x0, 0, repaintWidth, h), | 
| Chris@481 | 2168                                    m_drawBuffer, | 
| Chris@1024 | 2169                                    QRect(0, 0, repaintWidth, h)); | 
| Chris@481 | 2170         } | 
| Chris@481 | 2171 | 
| Chris@331 | 2172         cachePainter.end(); | 
| Chris@331 | 2173     } | 
| Chris@331 | 2174 | 
| Chris@1024 | 2175     // update cache valid area based on painted area | 
| Chris@1024 | 2176 | 
| Chris@1028 | 2177     int left = x0; | 
| Chris@1028 | 2178     int wid = x1 - x0; | 
| Chris@1028 | 2179 | 
| Chris@1026 | 2180     if (failedToRepaint > 0) { | 
| Chris@1026 | 2181 #ifdef DEBUG_SPECTROGRAM_REPAINT | 
| Chris@1026 | 2182         cerr << "SpectrogramLayer: Reduced painted extent from " | 
| Chris@1026 | 2183              << left << "," << wid; | 
| Chris@1026 | 2184 #endif | 
| Chris@1026 | 2185         if (rightToLeft) { | 
| Chris@1026 | 2186             left += failedToRepaint; | 
| Chris@1026 | 2187         } | 
| Chris@1026 | 2188         wid -= failedToRepaint; | 
| Chris@1026 | 2189 | 
| Chris@1024 | 2190         if (wid < 0) wid = 0; | 
| Chris@1026 | 2191 | 
| Chris@1026 | 2192 #ifdef DEBUG_SPECTROGRAM_REPAINT | 
| Chris@1026 | 2193         cerr << " to " << left << "," << wid << endl; | 
| Chris@1026 | 2194 #endif | 
| Chris@1025 | 2195     } | 
| Chris@1026 | 2196 | 
| Chris@1028 | 2197     if (cache.validArea.width() > 0) { | 
| Chris@1028 | 2198         left = min(left, cache.validArea.x()); | 
| Chris@1028 | 2199         wid = cache.validArea.width() + wid; | 
| Chris@1028 | 2200     } | 
| Chris@1028 | 2201 | 
| Chris@1026 | 2202     cache.validArea = QRect(left, 0, wid, h); | 
| Chris@1025 | 2203 | 
| Chris@1024 | 2204 #ifdef DEBUG_SPECTROGRAM_REPAINT | 
| Chris@1026 | 2205     cerr << "SpectrogramLayer: Cache valid area becomes " << cache.validArea.x() | 
| Chris@1025 | 2206          << ", " << cache.validArea.y() << ", " | 
| Chris@1025 | 2207          << cache.validArea.width() << "x" | 
| Chris@1025 | 2208          << cache.validArea.height() << endl; | 
| Chris@1024 | 2209 #endif | 
| Chris@1024 | 2210 | 
| Chris@337 | 2211     QRect pr = rect & cache.validArea; | 
| Chris@337 | 2212 | 
| Chris@337 | 2213 #ifdef DEBUG_SPECTROGRAM_REPAINT | 
| Chris@1026 | 2214     cerr << "SpectrogramLayer: Copying " << pr.width() << "x" << pr.height() | 
| Chris@337 | 2215               << " from cache at " << pr.x() << "," << pr.y() | 
| Chris@585 | 2216               << " to window" << endl; | 
| Chris@337 | 2217 #endif | 
| Chris@337 | 2218 | 
| Chris@478 | 2219     paint.drawImage(pr.x(), pr.y(), cache.image, | 
| Chris@479 | 2220                     pr.x(), pr.y(), pr.width(), pr.height()); | 
| Chris@337 | 2221 | 
| Chris@331 | 2222     cache.startFrame = startFrame; | 
| Chris@331 | 2223     cache.zoomLevel = zoomLevel; | 
| Chris@119 | 2224 | 
| Chris@389 | 2225     if (!m_synchronous) { | 
| Chris@389 | 2226 | 
| Chris@862 | 2227         if ((m_normalization != NormalizeVisibleArea) || !overallMagChanged) { | 
| Chris@0 | 2228 | 
| Chris@389 | 2229             if (cache.validArea.x() > 0) { | 
| Chris@95 | 2230 #ifdef DEBUG_SPECTROGRAM_REPAINT | 
| Chris@985 | 2231                 cerr << "SpectrogramLayer::paint() updating left (0, " | 
| Chris@585 | 2232                           << cache.validArea.x() << ")" << endl; | 
| Chris@95 | 2233 #endif | 
| Chris@918 | 2234                 v->getView()->update(0, 0, cache.validArea.x(), h); | 
| Chris@389 | 2235             } | 
| Chris@389 | 2236 | 
| Chris@389 | 2237             if (cache.validArea.x() + cache.validArea.width() < | 
| Chris@478 | 2238                 cache.image.width()) { | 
| Chris@389 | 2239 #ifdef DEBUG_SPECTROGRAM_REPAINT | 
| Chris@985 | 2240                 cerr << "SpectrogramLayer::paint() updating right (" | 
| Chris@389 | 2241                           << cache.validArea.x() + cache.validArea.width() | 
| Chris@389 | 2242                           << ", " | 
| Chris@478 | 2243                           << cache.image.width() - (cache.validArea.x() + | 
| Chris@389 | 2244                                                      cache.validArea.width()) | 
| Chris@585 | 2245                           << ")" << endl; | 
| Chris@389 | 2246 #endif | 
| Chris@1024 | 2247                 v->getView()->update | 
| Chris@1024 | 2248                     (cache.validArea.x() + cache.validArea.width(), | 
| Chris@1024 | 2249                      0, | 
| Chris@1024 | 2250                      cache.image.width() - (cache.validArea.x() + | 
| Chris@1024 | 2251                                             cache.validArea.width()), | 
| Chris@1024 | 2252                      h); | 
| Chris@389 | 2253             } | 
| Chris@389 | 2254         } else { | 
| Chris@389 | 2255             // overallMagChanged | 
| Chris@682 | 2256             cerr << "\noverallMagChanged - updating all\n" << endl; | 
| Chris@389 | 2257             cache.validArea = QRect(); | 
| Chris@918 | 2258             v->getView()->update(); | 
| Chris@119 | 2259         } | 
| Chris@95 | 2260     } | 
| Chris@0 | 2261 | 
| Chris@121 | 2262     illuminateLocalFeatures(v, paint); | 
| Chris@120 | 2263 | 
| Chris@0 | 2264 #ifdef DEBUG_SPECTROGRAM_REPAINT | 
| Chris@985 | 2265     cerr << "SpectrogramLayer::paint() returning" << endl; | 
| Chris@0 | 2266 #endif | 
| Chris@0 | 2267 } | 
| Chris@0 | 2268 | 
| Chris@1024 | 2269 int | 
| Chris@918 | 2270 SpectrogramLayer::paintDrawBufferPeakFrequencies(LayerGeometryProvider *v, | 
| Chris@488 | 2271                                                  int w, | 
| Chris@488 | 2272                                                  int h, | 
| Chris@907 | 2273                                                  const vector<int> &binforx, | 
| Chris@488 | 2274                                                  int minbin, | 
| Chris@488 | 2275                                                  int maxbin, | 
| Chris@905 | 2276                                                  double displayMinFreq, | 
| Chris@905 | 2277                                                  double displayMaxFreq, | 
| Chris@491 | 2278                                                  bool logarithmic, | 
| Chris@491 | 2279                                                  MagnitudeRange &overallMag, | 
| Chris@1026 | 2280                                                  bool &overallMagChanged, | 
| Chris@1026 | 2281                                                  bool rightToLeft) const | 
| Chris@488 | 2282 { | 
| Chris@488 | 2283     Profiler profiler("SpectrogramLayer::paintDrawBufferPeakFrequencies"); | 
| Chris@488 | 2284 | 
| Chris@488 | 2285 #ifdef DEBUG_SPECTROGRAM_REPAINT | 
| Chris@1026 | 2286     cerr << "SpectrogramLayer::paintDrawBufferPeakFrequencies: minbin " << minbin << ", maxbin " << maxbin << "; w " << w << ", h " << h << endl; | 
| Chris@488 | 2287 #endif | 
| Chris@488 | 2288     if (minbin < 0) minbin = 0; | 
| Chris@488 | 2289     if (maxbin < 0) maxbin = minbin+1; | 
| Chris@488 | 2290 | 
| Chris@488 | 2291     FFTModel *fft = getFFTModel(v); | 
| Chris@1024 | 2292     if (!fft) return 0; | 
| Chris@488 | 2293 | 
| Chris@488 | 2294     FFTModel::PeakSet peakfreqs; | 
| Chris@488 | 2295 | 
| Chris@848 | 2296     int psx = -1; | 
| Chris@545 | 2297 | 
| Chris@545 | 2298 #ifdef __GNUC__ | 
| Chris@488 | 2299     float values[maxbin - minbin + 1]; | 
| Chris@545 | 2300 #else | 
| Chris@545 | 2301     float *values = (float *)alloca((maxbin - minbin + 1) * sizeof(float)); | 
| Chris@545 | 2302 #endif | 
| Chris@488 | 2303 | 
| Chris@1027 | 2304     int minColumns = 4; | 
| Chris@1027 | 2305     double maxTime = 0.15; // seconds; only for non-synchronous drawing | 
| Chris@1027 | 2306     auto startTime = chrono::steady_clock::now(); | 
| Chris@1027 | 2307 | 
| Chris@1026 | 2308     int start = 0; | 
| Chris@1026 | 2309     int finish = w; | 
| Chris@1026 | 2310     int step = 1; | 
| Chris@1026 | 2311 | 
| Chris@1026 | 2312     if (rightToLeft) { | 
| Chris@1026 | 2313         start = w-1; | 
| Chris@1026 | 2314         finish = -1; | 
| Chris@1026 | 2315         step = -1; | 
| Chris@1026 | 2316     } | 
| Chris@1026 | 2317 | 
| Chris@1027 | 2318     int columnCount = 0; | 
| Chris@1027 | 2319 | 
| Chris@1026 | 2320     for (int x = start; x != finish; x += step) { | 
| Chris@488 | 2321 | 
| Chris@1027 | 2322         ++columnCount; | 
| Chris@1027 | 2323 | 
| Chris@488 | 2324         if (binforx[x] < 0) continue; | 
| Chris@488 | 2325 | 
| Chris@488 | 2326         int sx0 = binforx[x]; | 
| Chris@488 | 2327         int sx1 = sx0; | 
| Chris@488 | 2328         if (x+1 < w) sx1 = binforx[x+1]; | 
| Chris@488 | 2329         if (sx0 < 0) sx0 = sx1 - 1; | 
| Chris@488 | 2330         if (sx0 < 0) continue; | 
| Chris@488 | 2331         if (sx1 <= sx0) sx1 = sx0 + 1; | 
| Chris@488 | 2332 | 
| Chris@488 | 2333         for (int sx = sx0; sx < sx1; ++sx) { | 
| Chris@488 | 2334 | 
| Chris@488 | 2335             if (sx < 0 || sx >= int(fft->getWidth())) continue; | 
| Chris@488 | 2336 | 
| Chris@488 | 2337             MagnitudeRange mag; | 
| Chris@488 | 2338 | 
| Chris@488 | 2339             if (sx != psx) { | 
| Chris@488 | 2340                 peakfreqs = fft->getPeakFrequencies(FFTModel::AllPeaks, sx, | 
| Chris@488 | 2341                                                     minbin, maxbin - 1); | 
| Chris@488 | 2342                 if (m_colourScale == PhaseColourScale) { | 
| Chris@488 | 2343                     fft->getPhasesAt(sx, values, minbin, maxbin - minbin + 1); | 
| Chris@862 | 2344                 } else if (m_normalization == NormalizeColumns) { | 
| Chris@488 | 2345                     fft->getNormalizedMagnitudesAt(sx, values, minbin, maxbin - minbin + 1); | 
| Chris@862 | 2346                 } else if (m_normalization == NormalizeHybrid) { | 
| Chris@719 | 2347                     fft->getNormalizedMagnitudesAt(sx, values, minbin, maxbin - minbin + 1); | 
| Chris@905 | 2348                     double max = fft->getMaximumMagnitudeAt(sx); | 
| Chris@719 | 2349                     if (max > 0.f) { | 
| Chris@719 | 2350                         for (int i = minbin; i <= maxbin; ++i) { | 
| Chris@907 | 2351                             values[i - minbin] = float(values[i - minbin] * log10(max)); | 
| Chris@719 | 2352                         } | 
| Chris@719 | 2353                     } | 
| Chris@488 | 2354                 } else { | 
| Chris@488 | 2355                     fft->getMagnitudesAt(sx, values, minbin, maxbin - minbin + 1); | 
| Chris@488 | 2356                 } | 
| Chris@488 | 2357                 psx = sx; | 
| Chris@488 | 2358             } | 
| Chris@488 | 2359 | 
| Chris@488 | 2360             for (FFTModel::PeakSet::const_iterator pi = peakfreqs.begin(); | 
| Chris@488 | 2361                  pi != peakfreqs.end(); ++pi) { | 
| Chris@488 | 2362 | 
| Chris@488 | 2363                 int bin = pi->first; | 
| Chris@907 | 2364                 double freq = pi->second; | 
| Chris@488 | 2365 | 
| Chris@488 | 2366                 if (bin < minbin) continue; | 
| Chris@488 | 2367                 if (bin > maxbin) break; | 
| Chris@488 | 2368 | 
| Chris@907 | 2369                 double value = values[bin - minbin]; | 
| Chris@488 | 2370 | 
| Chris@488 | 2371                 if (m_colourScale != PhaseColourScale) { | 
| Chris@862 | 2372                     if (m_normalization != NormalizeColumns) { | 
| Chris@907 | 2373                         value /= (m_fftSize/2.0); | 
| Chris@488 | 2374                     } | 
| Chris@907 | 2375                     mag.sample(float(value)); | 
| Chris@488 | 2376                     value *= m_gain; | 
| Chris@488 | 2377                 } | 
| Chris@488 | 2378 | 
| Chris@905 | 2379                 double y = v->getYForFrequency | 
| Chris@488 | 2380                     (freq, displayMinFreq, displayMaxFreq, logarithmic); | 
| Chris@488 | 2381 | 
| Chris@558 | 2382                 int iy = int(y + 0.5); | 
| Chris@558 | 2383                 if (iy < 0 || iy >= h) continue; | 
| Chris@558 | 2384 | 
| Chris@558 | 2385                 m_drawBuffer.setPixel(x, iy, getDisplayValue(v, value)); | 
| Chris@488 | 2386             } | 
| Chris@488 | 2387 | 
| Chris@488 | 2388             if (mag.isSet()) { | 
| Chris@488 | 2389                 if (sx >= int(m_columnMags.size())) { | 
| Chris@540 | 2390 #ifdef DEBUG_SPECTROGRAM | 
| Chris@682 | 2391                     cerr << "INTERNAL ERROR: " << sx << " >= " | 
| Chris@488 | 2392                               << m_columnMags.size() | 
| Chris@488 | 2393                               << " at SpectrogramLayer.cpp::paintDrawBuffer" | 
| Chris@682 | 2394                               << endl; | 
| Chris@540 | 2395 #endif | 
| Chris@490 | 2396                 } else { | 
| Chris@490 | 2397                     m_columnMags[sx].sample(mag); | 
| Chris@491 | 2398                     if (overallMag.sample(mag)) overallMagChanged = true; | 
| Chris@488 | 2399                 } | 
| Chris@488 | 2400             } | 
| Chris@488 | 2401         } | 
| Chris@1027 | 2402 | 
| Chris@1027 | 2403         if (!m_synchronous) { | 
| Chris@1027 | 2404             if (columnCount >= minColumns) { | 
| Chris@1027 | 2405                 auto t = chrono::steady_clock::now(); | 
| Chris@1027 | 2406                 double diff = chrono::duration<double>(t - startTime).count(); | 
| Chris@1027 | 2407                 if (diff > maxTime) { | 
| Chris@1027 | 2408 #ifdef DEBUG_SPECTROGRAM_REPAINT | 
| Chris@1027 | 2409                     cerr << "SpectrogramLayer::paintDrawBufferPeakFrequencies: Max time " << maxTime << " sec exceeded after " | 
| Chris@1027 | 2410                          << x << " columns with time " << diff << endl; | 
| Chris@1027 | 2411 #endif | 
| Chris@1027 | 2412                     return columnCount; | 
| Chris@1027 | 2413                 } | 
| Chris@1027 | 2414             } | 
| Chris@1027 | 2415         } | 
| Chris@488 | 2416     } | 
| Chris@1024 | 2417 | 
| Chris@1027 | 2418     return columnCount; | 
| Chris@488 | 2419 } | 
| Chris@488 | 2420 | 
| Chris@1024 | 2421 int | 
| Chris@918 | 2422 SpectrogramLayer::paintDrawBuffer(LayerGeometryProvider *v, | 
| Chris@481 | 2423                                   int w, | 
| Chris@481 | 2424                                   int h, | 
| Chris@907 | 2425                                   const vector<int> &binforx, | 
| Chris@907 | 2426                                   const vector<double> &binfory, | 
| Chris@491 | 2427                                   bool usePeaksCache, | 
| Chris@491 | 2428                                   MagnitudeRange &overallMag, | 
| Chris@1026 | 2429                                   bool &overallMagChanged, | 
| Chris@1026 | 2430                                   bool rightToLeft) const | 
| Chris@480 | 2431 { | 
| Chris@481 | 2432     Profiler profiler("SpectrogramLayer::paintDrawBuffer"); | 
| Chris@480 | 2433 | 
| Chris@490 | 2434     int minbin = int(binfory[0] + 0.0001); | 
| Chris@907 | 2435     int maxbin = int(binfory[h-1]); | 
| Chris@480 | 2436 | 
| Chris@485 | 2437 #ifdef DEBUG_SPECTROGRAM_REPAINT | 
| Chris@1026 | 2438     cerr << "SpectrogramLayer::paintDrawBuffer: minbin " << minbin << ", maxbin " << maxbin << "; w " << w << ", h " << h << endl; | 
| Chris@485 | 2439 #endif | 
| Chris@480 | 2440     if (minbin < 0) minbin = 0; | 
| Chris@480 | 2441     if (maxbin < 0) maxbin = minbin+1; | 
| Chris@480 | 2442 | 
| Chris@484 | 2443     DenseThreeDimensionalModel *sourceModel = 0; | 
| Chris@484 | 2444     FFTModel *fft = 0; | 
| Chris@484 | 2445     int divisor = 1; | 
| Chris@485 | 2446 #ifdef DEBUG_SPECTROGRAM_REPAINT | 
| Chris@1026 | 2447     cerr << "SpectrogramLayer::paintDrawBuffer: Note: bin display = " << m_binDisplay << ", w = " << w << ", binforx[" << w-1 << "] = " << binforx[w-1] << ", binforx[0] = " << binforx[0] << endl; | 
| Chris@485 | 2448 #endif | 
| Chris@484 | 2449     if (usePeaksCache) { //!!! | 
| Chris@484 | 2450         sourceModel = getPeakCache(v); | 
| Chris@484 | 2451         divisor = 8;//!!! | 
| Chris@484 | 2452         minbin = 0; | 
| Chris@484 | 2453         maxbin = sourceModel->getHeight(); | 
| Chris@484 | 2454     } else { | 
| Chris@484 | 2455         sourceModel = fft = getFFTModel(v); | 
| Chris@484 | 2456     } | 
| Chris@484 | 2457 | 
| Chris@1024 | 2458     if (!sourceModel) return 0; | 
| Chris@484 | 2459 | 
| Chris@490 | 2460     bool interpolate = false; | 
| Chris@490 | 2461     Preferences::SpectrogramSmoothing smoothing = | 
| Chris@490 | 2462         Preferences::getInstance()->getSpectrogramSmoothing(); | 
| Chris@490 | 2463     if (smoothing == Preferences::SpectrogramInterpolated || | 
| Chris@490 | 2464         smoothing == Preferences::SpectrogramZeroPaddedAndInterpolated) { | 
| Chris@490 | 2465         if (m_binDisplay != PeakBins && | 
| Chris@490 | 2466             m_binDisplay != PeakFrequencies) { | 
| Chris@490 | 2467             interpolate = true; | 
| Chris@490 | 2468         } | 
| Chris@490 | 2469     } | 
| Chris@490 | 2470 | 
| Chris@480 | 2471     int psx = -1; | 
| Chris@545 | 2472 | 
| Chris@545 | 2473 #ifdef __GNUC__ | 
| Chris@490 | 2474     float autoarray[maxbin - minbin + 1]; | 
| Chris@545 | 2475     float peaks[h]; | 
| Chris@545 | 2476 #else | 
| Chris@545 | 2477     float *autoarray = (float *)alloca((maxbin - minbin + 1) * sizeof(float)); | 
| Chris@545 | 2478     float *peaks = (float *)alloca(h * sizeof(float)); | 
| Chris@545 | 2479 #endif | 
| Chris@545 | 2480 | 
| Chris@490 | 2481     const float *values = autoarray; | 
| Chris@484 | 2482     DenseThreeDimensionalModel::Column c; | 
| Chris@480 | 2483 | 
| Chris@1027 | 2484     int minColumns = 4; | 
| Chris@1027 | 2485     double maxTime = 0.1; // seconds; only for non-synchronous drawing | 
| Chris@1027 | 2486     auto startTime = chrono::steady_clock::now(); | 
| Chris@1027 | 2487 | 
| Chris@1026 | 2488     int start = 0; | 
| Chris@1026 | 2489     int finish = w; | 
| Chris@1026 | 2490     int step = 1; | 
| Chris@1026 | 2491 | 
| Chris@1026 | 2492     if (rightToLeft) { | 
| Chris@1026 | 2493         start = w-1; | 
| Chris@1026 | 2494         finish = -1; | 
| Chris@1026 | 2495         step = -1; | 
| Chris@1026 | 2496     } | 
| Chris@1027 | 2497 | 
| Chris@1027 | 2498     int columnCount = 0; | 
| Chris@1026 | 2499 | 
| Chris@1026 | 2500     for (int x = start; x != finish; x += step) { | 
| Chris@1027 | 2501 | 
| Chris@1027 | 2502         ++columnCount; | 
| Chris@480 | 2503 | 
| Chris@482 | 2504         if (binforx[x] < 0) continue; | 
| Chris@482 | 2505 | 
| Chris@488 | 2506 //        float columnGain = m_gain; | 
| Chris@487 | 2507         float columnMax = 0.f; | 
| Chris@487 | 2508 | 
| Chris@484 | 2509         int sx0 = binforx[x] / divisor; | 
| Chris@483 | 2510         int sx1 = sx0; | 
| Chris@484 | 2511         if (x+1 < w) sx1 = binforx[x+1] / divisor; | 
| Chris@483 | 2512         if (sx0 < 0) sx0 = sx1 - 1; | 
| Chris@483 | 2513         if (sx0 < 0) continue; | 
| Chris@483 | 2514         if (sx1 <= sx0) sx1 = sx0 + 1; | 
| Chris@483 | 2515 | 
| Chris@483 | 2516         for (int y = 0; y < h; ++y) peaks[y] = 0.f; | 
| Chris@480 | 2517 | 
| Chris@483 | 2518         for (int sx = sx0; sx < sx1; ++sx) { | 
| Chris@483 | 2519 | 
| Chris@518 | 2520 #ifdef DEBUG_SPECTROGRAM_REPAINT | 
| Chris@682 | 2521 //            cerr << "sx = " << sx << endl; | 
| Chris@518 | 2522 #endif | 
| Chris@518 | 2523 | 
| Chris@484 | 2524             if (sx < 0 || sx >= int(sourceModel->getWidth())) continue; | 
| Chris@483 | 2525 | 
| Chris@488 | 2526             MagnitudeRange mag; | 
| Chris@488 | 2527 | 
| Chris@483 | 2528             if (sx != psx) { | 
| Chris@484 | 2529                 if (fft) { | 
| Chris@485 | 2530 #ifdef DEBUG_SPECTROGRAM_REPAINT | 
| Chris@1026 | 2531 //                    cerr << "Retrieving column " << sx << " from fft directly" << endl; | 
| Chris@485 | 2532 #endif | 
| Chris@487 | 2533                     if (m_colourScale == PhaseColourScale) { | 
| Chris@490 | 2534                         fft->getPhasesAt(sx, autoarray, minbin, maxbin - minbin + 1); | 
| Chris@862 | 2535                     } else if (m_normalization == NormalizeColumns) { | 
| Chris@490 | 2536                         fft->getNormalizedMagnitudesAt(sx, autoarray, minbin, maxbin - minbin + 1); | 
| Chris@862 | 2537                     } else if (m_normalization == NormalizeHybrid) { | 
| Chris@719 | 2538                         fft->getNormalizedMagnitudesAt(sx, autoarray, minbin, maxbin - minbin + 1); | 
| Chris@988 | 2539                         float max = fft->getMaximumMagnitudeAt(sx); | 
| Chris@988 | 2540                         float scale = log10f(max + 1.f); | 
| Chris@998 | 2541 //                        cout << "sx = " << sx << ", max = " << max << ", log10(max) = " << log10(max) << ", scale = " << scale << endl; | 
| Chris@719 | 2542                         for (int i = minbin; i <= maxbin; ++i) { | 
| Chris@862 | 2543                             autoarray[i - minbin] *= scale; | 
| Chris@719 | 2544                         } | 
| Chris@487 | 2545                     } else { | 
| Chris@490 | 2546                         fft->getMagnitudesAt(sx, autoarray, minbin, maxbin - minbin + 1); | 
| Chris@487 | 2547                     } | 
| Chris@484 | 2548                 } else { | 
| Chris@485 | 2549 #ifdef DEBUG_SPECTROGRAM_REPAINT | 
| Chris@1026 | 2550 //                    cerr << "Retrieving column " << sx << " from peaks cache" << endl; | 
| Chris@485 | 2551 #endif | 
| Chris@484 | 2552                     c = sourceModel->getColumn(sx); | 
| Chris@862 | 2553                     if (m_normalization == NormalizeColumns || | 
| Chris@862 | 2554                         m_normalization == NormalizeHybrid) { | 
| Chris@487 | 2555                         for (int y = 0; y < h; ++y) { | 
| Chris@487 | 2556                             if (c[y] > columnMax) columnMax = c[y]; | 
| Chris@487 | 2557                         } | 
| Chris@487 | 2558                     } | 
| Chris@1019 | 2559                     values = c.data() + minbin; | 
| Chris@484 | 2560                 } | 
| Chris@483 | 2561                 psx = sx; | 
| Chris@483 | 2562             } | 
| Chris@483 | 2563 | 
| Chris@483 | 2564             for (int y = 0; y < h; ++y) { | 
| Chris@480 | 2565 | 
| Chris@905 | 2566                 double sy0 = binfory[y]; | 
| Chris@905 | 2567                 double sy1 = sy0 + 1; | 
| Chris@481 | 2568                 if (y+1 < h) sy1 = binfory[y+1]; | 
| Chris@490 | 2569 | 
| Chris@907 | 2570                 double value = 0.0; | 
| Chris@907 | 2571 | 
| Chris@907 | 2572                 if (interpolate && fabs(sy1 - sy0) < 1.0) { | 
| Chris@490 | 2573 | 
| Chris@905 | 2574                     double centre = (sy0 + sy1) / 2; | 
| Chris@907 | 2575                     double dist = (centre - 0.5) - rint(centre - 0.5); | 
| Chris@490 | 2576                     int bin = int(centre); | 
| Chris@490 | 2577                     int other = (dist < 0 ? (bin-1) : (bin+1)); | 
| Chris@490 | 2578                     if (bin < minbin) bin = minbin; | 
| Chris@490 | 2579                     if (bin > maxbin) bin = maxbin; | 
| Chris@490 | 2580                     if (other < minbin || other > maxbin) other = bin; | 
| Chris@907 | 2581                     double prop = 1.0 - fabs(dist); | 
| Chris@905 | 2582 | 
| Chris@905 | 2583                     double v0 = values[bin - minbin]; | 
| Chris@905 | 2584                     double v1 = values[other - minbin]; | 
| Chris@490 | 2585                     if (m_binDisplay == PeakBins) { | 
| Chris@490 | 2586                         if (bin == minbin || bin == maxbin || | 
| Chris@490 | 2587                             v0 < values[bin-minbin-1] || | 
| Chris@907 | 2588                             v0 < values[bin-minbin+1]) v0 = 0.0; | 
| Chris@490 | 2589                         if (other == minbin || other == maxbin || | 
| Chris@490 | 2590                             v1 < values[other-minbin-1] || | 
| Chris@907 | 2591                             v1 < values[other-minbin+1]) v1 = 0.0; | 
| Chris@489 | 2592                     } | 
| Chris@907 | 2593                     if (v0 == 0.0 && v1 == 0.0) continue; | 
| Chris@907 | 2594                     value = prop * v0 + (1.0 - prop) * v1; | 
| Chris@484 | 2595 | 
| Chris@488 | 2596                     if (m_colourScale != PhaseColourScale) { | 
| Chris@862 | 2597                         if (m_normalization != NormalizeColumns && | 
| Chris@862 | 2598                             m_normalization != NormalizeHybrid) { | 
| Chris@907 | 2599                             value /= (m_fftSize/2.0); | 
| Chris@488 | 2600                         } | 
| Chris@907 | 2601                         mag.sample(float(value)); | 
| Chris@488 | 2602                         value *= m_gain; | 
| Chris@488 | 2603                     } | 
| Chris@488 | 2604 | 
| Chris@907 | 2605                     peaks[y] = float(value); | 
| Chris@490 | 2606 | 
| Chris@490 | 2607                 } else { | 
| Chris@490 | 2608 | 
| Chris@490 | 2609                     int by0 = int(sy0 + 0.0001); | 
| Chris@490 | 2610                     int by1 = int(sy1 + 0.0001); | 
| Chris@490 | 2611                     if (by1 < by0 + 1) by1 = by0 + 1; | 
| Chris@490 | 2612 | 
| Chris@490 | 2613                     for (int bin = by0; bin < by1; ++bin) { | 
| Chris@490 | 2614 | 
| Chris@490 | 2615                         value = values[bin - minbin]; | 
| Chris@490 | 2616                         if (m_binDisplay == PeakBins) { | 
| Chris@490 | 2617                             if (bin == minbin || bin == maxbin || | 
| Chris@490 | 2618                                 value < values[bin-minbin-1] || | 
| Chris@490 | 2619                                 value < values[bin-minbin+1]) continue; | 
| Chris@480 | 2620                         } | 
| Chris@490 | 2621 | 
| Chris@490 | 2622                         if (m_colourScale != PhaseColourScale) { | 
| Chris@862 | 2623                             if (m_normalization != NormalizeColumns && | 
| Chris@862 | 2624                                 m_normalization != NormalizeHybrid) { | 
| Chris@907 | 2625                                 value /= (m_fftSize/2.0); | 
| Chris@490 | 2626                             } | 
| Chris@907 | 2627                             mag.sample(float(value)); | 
| Chris@490 | 2628                             value *= m_gain; | 
| Chris@490 | 2629                         } | 
| Chris@490 | 2630 | 
| Chris@907 | 2631                         if (value > peaks[y]) { | 
| Chris@907 | 2632                             peaks[y] = float(value); //!!! not right for phase! | 
| Chris@907 | 2633                         } | 
| Chris@480 | 2634                     } | 
| Chris@480 | 2635                 } | 
| Chris@483 | 2636             } | 
| Chris@488 | 2637 | 
| Chris@488 | 2638             if (mag.isSet()) { | 
| Chris@488 | 2639                 if (sx >= int(m_columnMags.size())) { | 
| Chris@540 | 2640 #ifdef DEBUG_SPECTROGRAM | 
| Chris@682 | 2641                     cerr << "INTERNAL ERROR: " << sx << " >= " | 
| Chris@488 | 2642                               << m_columnMags.size() | 
| Chris@488 | 2643                               << " at SpectrogramLayer.cpp::paintDrawBuffer" | 
| Chris@682 | 2644                               << endl; | 
| Chris@540 | 2645 #endif | 
| Chris@490 | 2646                 } else { | 
| Chris@490 | 2647                     m_columnMags[sx].sample(mag); | 
| Chris@491 | 2648                     if (overallMag.sample(mag)) overallMagChanged = true; | 
| Chris@488 | 2649                 } | 
| Chris@488 | 2650             } | 
| Chris@483 | 2651         } | 
| Chris@483 | 2652 | 
| Chris@483 | 2653         for (int y = 0; y < h; ++y) { | 
| Chris@483 | 2654 | 
| Chris@905 | 2655             double peak = peaks[y]; | 
| Chris@483 | 2656 | 
| Chris@488 | 2657             if (m_colourScale != PhaseColourScale && | 
| Chris@862 | 2658                 (m_normalization == NormalizeColumns || | 
| Chris@862 | 2659                  m_normalization == NormalizeHybrid) && | 
| Chris@488 | 2660                 columnMax > 0.f) { | 
| Chris@488 | 2661                 peak /= columnMax; | 
| Chris@862 | 2662                 if (m_normalization == NormalizeHybrid) { | 
| Chris@862 | 2663                     peak *= log10(columnMax + 1.f); | 
| Chris@719 | 2664                 } | 
| Chris@480 | 2665             } | 
| Chris@483 | 2666 | 
| Chris@483 | 2667             unsigned char peakpix = getDisplayValue(v, peak); | 
| Chris@480 | 2668 | 
| Chris@480 | 2669             m_drawBuffer.setPixel(x, h-y-1, peakpix); | 
| Chris@480 | 2670         } | 
| Chris@1025 | 2671 | 
| Chris@1025 | 2672         if (!m_synchronous) { | 
| Chris@1027 | 2673             if (columnCount >= minColumns) { | 
| Chris@1025 | 2674                 auto t = chrono::steady_clock::now(); | 
| Chris@1025 | 2675                 double diff = chrono::duration<double>(t - startTime).count(); | 
| Chris@1025 | 2676                 if (diff > maxTime) { | 
| Chris@1025 | 2677 #ifdef DEBUG_SPECTROGRAM_REPAINT | 
| Chris@1026 | 2678                     cerr << "SpectrogramLayer::paintDrawBuffer: Max time " << maxTime << " sec exceeded after " | 
| Chris@1025 | 2679                          << x << " columns with time " << diff << endl; | 
| Chris@1025 | 2680 #endif | 
| Chris@1027 | 2681                     return columnCount; | 
| Chris@1025 | 2682                 } | 
| Chris@1025 | 2683             } | 
| Chris@1025 | 2684         } | 
| Chris@480 | 2685     } | 
| Chris@1024 | 2686 | 
| Chris@1027 | 2687     return columnCount; | 
| Chris@480 | 2688 } | 
| Chris@477 | 2689 | 
| Chris@121 | 2690 void | 
| Chris@918 | 2691 SpectrogramLayer::illuminateLocalFeatures(LayerGeometryProvider *v, QPainter &paint) const | 
| Chris@121 | 2692 { | 
| Chris@382 | 2693     Profiler profiler("SpectrogramLayer::illuminateLocalFeatures"); | 
| Chris@382 | 2694 | 
| Chris@121 | 2695     QPoint localPos; | 
| Chris@121 | 2696     if (!v->shouldIlluminateLocalFeatures(this, localPos) || !m_model) { | 
| Chris@121 | 2697         return; | 
| Chris@121 | 2698     } | 
| Chris@121 | 2699 | 
| Chris@682 | 2700 //    cerr << "SpectrogramLayer: illuminateLocalFeatures(" | 
| Chris@682 | 2701 //              << localPos.x() << "," << localPos.y() << ")" << endl; | 
| Chris@121 | 2702 | 
| Chris@905 | 2703     double s0, s1; | 
| Chris@905 | 2704     double f0, f1; | 
| Chris@121 | 2705 | 
| Chris@121 | 2706     if (getXBinRange(v, localPos.x(), s0, s1) && | 
| Chris@121 | 2707         getYBinSourceRange(v, localPos.y(), f0, f1)) { | 
| Chris@121 | 2708 | 
| Chris@121 | 2709         int s0i = int(s0 + 0.001); | 
| Chris@121 | 2710         int s1i = int(s1); | 
| Chris@121 | 2711 | 
| Chris@121 | 2712         int x0 = v->getXForFrame(s0i * getWindowIncrement()); | 
| Chris@121 | 2713         int x1 = v->getXForFrame((s1i + 1) * getWindowIncrement()); | 
| Chris@121 | 2714 | 
| Chris@248 | 2715         int y1 = int(getYForFrequency(v, f1)); | 
| Chris@248 | 2716         int y0 = int(getYForFrequency(v, f0)); | 
| Chris@121 | 2717 | 
| Chris@682 | 2718 //        cerr << "SpectrogramLayer: illuminate " | 
| Chris@682 | 2719 //                  << x0 << "," << y1 << " -> " << x1 << "," << y0 << endl; | 
| Chris@121 | 2720 | 
| Chris@287 | 2721         paint.setPen(v->getForeground()); | 
| Chris@133 | 2722 | 
| Chris@133 | 2723         //!!! should we be using paintCrosshairs for this? | 
| Chris@133 | 2724 | 
| Chris@121 | 2725         paint.drawRect(x0, y1, x1 - x0 + 1, y0 - y1 + 1); | 
| Chris@121 | 2726     } | 
| Chris@121 | 2727 } | 
| Chris@121 | 2728 | 
| Chris@905 | 2729 double | 
| Chris@918 | 2730 SpectrogramLayer::getYForFrequency(const LayerGeometryProvider *v, double frequency) const | 
| Chris@42 | 2731 { | 
| Chris@44 | 2732     return v->getYForFrequency(frequency, | 
| Chris@44 | 2733 			       getEffectiveMinFrequency(), | 
| Chris@44 | 2734 			       getEffectiveMaxFrequency(), | 
| Chris@44 | 2735 			       m_frequencyScale == LogFrequencyScale); | 
| Chris@42 | 2736 } | 
| Chris@42 | 2737 | 
| Chris@905 | 2738 double | 
| Chris@918 | 2739 SpectrogramLayer::getFrequencyForY(const LayerGeometryProvider *v, int y) const | 
| Chris@42 | 2740 { | 
| Chris@44 | 2741     return v->getFrequencyForY(y, | 
| Chris@44 | 2742 			       getEffectiveMinFrequency(), | 
| Chris@44 | 2743 			       getEffectiveMaxFrequency(), | 
| Chris@44 | 2744 			       m_frequencyScale == LogFrequencyScale); | 
| Chris@42 | 2745 } | 
| Chris@42 | 2746 | 
| Chris@0 | 2747 int | 
| Chris@918 | 2748 SpectrogramLayer::getCompletion(LayerGeometryProvider *v) const | 
| Chris@0 | 2749 { | 
| Chris@920 | 2750     const View *view = v->getView(); | 
| Chris@920 | 2751 | 
| Chris@920 | 2752     if (m_fftModels.find(view) == m_fftModels.end()) return 100; | 
| Chris@920 | 2753 | 
| Chris@988 | 2754     int completion = m_fftModels[view]->getCompletion(); | 
| Chris@224 | 2755 #ifdef DEBUG_SPECTROGRAM_REPAINT | 
| Chris@985 | 2756     cerr << "SpectrogramLayer::getCompletion: completion = " << completion << endl; | 
| Chris@224 | 2757 #endif | 
| Chris@0 | 2758     return completion; | 
| Chris@0 | 2759 } | 
| Chris@0 | 2760 | 
| Chris@583 | 2761 QString | 
| Chris@918 | 2762 SpectrogramLayer::getError(LayerGeometryProvider *v) const | 
| Chris@583 | 2763 { | 
| Chris@920 | 2764     const View *view = v->getView(); | 
| Chris@920 | 2765     if (m_fftModels.find(view) == m_fftModels.end()) return ""; | 
| Chris@988 | 2766     return m_fftModels[view]->getError(); | 
| Chris@583 | 2767 } | 
| Chris@583 | 2768 | 
| Chris@28 | 2769 bool | 
| Chris@905 | 2770 SpectrogramLayer::getValueExtents(double &min, double &max, | 
| Chris@101 | 2771                                   bool &logarithmic, QString &unit) const | 
| Chris@79 | 2772 { | 
| Chris@133 | 2773     if (!m_model) return false; | 
| Chris@133 | 2774 | 
| Chris@907 | 2775     sv_samplerate_t sr = m_model->getSampleRate(); | 
| Chris@905 | 2776     min = double(sr) / m_fftSize; | 
| Chris@905 | 2777     max = double(sr) / 2; | 
| Chris@133 | 2778 | 
| Chris@101 | 2779     logarithmic = (m_frequencyScale == LogFrequencyScale); | 
| Chris@79 | 2780     unit = "Hz"; | 
| Chris@79 | 2781     return true; | 
| Chris@79 | 2782 } | 
| Chris@79 | 2783 | 
| Chris@79 | 2784 bool | 
| Chris@905 | 2785 SpectrogramLayer::getDisplayExtents(double &min, double &max) const | 
| Chris@101 | 2786 { | 
| Chris@101 | 2787     min = getEffectiveMinFrequency(); | 
| Chris@101 | 2788     max = getEffectiveMaxFrequency(); | 
| Chris@253 | 2789 | 
| Chris@587 | 2790 //    SVDEBUG << "SpectrogramLayer::getDisplayExtents: " << min << "->" << max << endl; | 
| Chris@101 | 2791     return true; | 
| Chris@101 | 2792 } | 
| Chris@101 | 2793 | 
| Chris@101 | 2794 bool | 
| Chris@905 | 2795 SpectrogramLayer::setDisplayExtents(double min, double max) | 
| Chris@120 | 2796 { | 
| Chris@120 | 2797     if (!m_model) return false; | 
| Chris@187 | 2798 | 
| Chris@587 | 2799 //    SVDEBUG << "SpectrogramLayer::setDisplayExtents: " << min << "->" << max << endl; | 
| Chris@187 | 2800 | 
| Chris@120 | 2801     if (min < 0) min = 0; | 
| Chris@907 | 2802     if (max > m_model->getSampleRate()/2.0) max = m_model->getSampleRate()/2.0; | 
| Chris@120 | 2803 | 
| Chris@907 | 2804     int minf = int(lrint(min)); | 
| Chris@907 | 2805     int maxf = int(lrint(max)); | 
| Chris@120 | 2806 | 
| Chris@120 | 2807     if (m_minFrequency == minf && m_maxFrequency == maxf) return true; | 
| Chris@120 | 2808 | 
| Chris@478 | 2809     invalidateImageCaches(); | 
| Chris@120 | 2810     invalidateMagnitudes(); | 
| Chris@120 | 2811 | 
| Chris@120 | 2812     m_minFrequency = minf; | 
| Chris@120 | 2813     m_maxFrequency = maxf; | 
| Chris@120 | 2814 | 
| Chris@120 | 2815     emit layerParametersChanged(); | 
| Chris@120 | 2816 | 
| Chris@133 | 2817     int vs = getCurrentVerticalZoomStep(); | 
| Chris@133 | 2818     if (vs != m_lastEmittedZoomStep) { | 
| Chris@133 | 2819         emit verticalZoomChanged(); | 
| Chris@133 | 2820         m_lastEmittedZoomStep = vs; | 
| Chris@133 | 2821     } | 
| Chris@133 | 2822 | 
| Chris@120 | 2823     return true; | 
| Chris@120 | 2824 } | 
| Chris@120 | 2825 | 
| Chris@120 | 2826 bool | 
| Chris@918 | 2827 SpectrogramLayer::getYScaleValue(const LayerGeometryProvider *v, int y, | 
| Chris@905 | 2828                                  double &value, QString &unit) const | 
| Chris@261 | 2829 { | 
| Chris@261 | 2830     value = getFrequencyForY(v, y); | 
| Chris@261 | 2831     unit = "Hz"; | 
| Chris@261 | 2832     return true; | 
| Chris@261 | 2833 } | 
| Chris@261 | 2834 | 
| Chris@261 | 2835 bool | 
| Chris@918 | 2836 SpectrogramLayer::snapToFeatureFrame(LayerGeometryProvider *, | 
| Chris@907 | 2837                                      sv_frame_t &frame, | 
| Chris@805 | 2838 				     int &resolution, | 
| Chris@28 | 2839 				     SnapType snap) const | 
| Chris@13 | 2840 { | 
| Chris@13 | 2841     resolution = getWindowIncrement(); | 
| Chris@907 | 2842     sv_frame_t left = (frame / resolution) * resolution; | 
| Chris@907 | 2843     sv_frame_t right = left + resolution; | 
| Chris@28 | 2844 | 
| Chris@28 | 2845     switch (snap) { | 
| Chris@28 | 2846     case SnapLeft:  frame = left;  break; | 
| Chris@28 | 2847     case SnapRight: frame = right; break; | 
| Chris@28 | 2848     case SnapNearest: | 
| Chris@28 | 2849     case SnapNeighbouring: | 
| Chris@28 | 2850 	if (frame - left > right - frame) frame = right; | 
| Chris@28 | 2851 	else frame = left; | 
| Chris@28 | 2852 	break; | 
| Chris@28 | 2853     } | 
| Chris@28 | 2854 | 
| Chris@28 | 2855     return true; | 
| Chris@28 | 2856 } | 
| Chris@13 | 2857 | 
| Chris@283 | 2858 void | 
| Chris@918 | 2859 SpectrogramLayer::measureDoubleClick(LayerGeometryProvider *v, QMouseEvent *e) | 
| Chris@283 | 2860 { | 
| Chris@920 | 2861     const View *view = v->getView(); | 
| Chris@920 | 2862     ImageCache &cache = m_imageCaches[view]; | 
| Chris@478 | 2863 | 
| Chris@682 | 2864     cerr << "cache width: " << cache.image.width() << ", height: " | 
| Chris@920 | 2865          << cache.image.height() << endl; | 
| Chris@478 | 2866 | 
| Chris@478 | 2867     QImage image = cache.image; | 
| Chris@283 | 2868 | 
| Chris@283 | 2869     ImageRegionFinder finder; | 
| Chris@283 | 2870     QRect rect = finder.findRegionExtents(&image, e->pos()); | 
| Chris@283 | 2871     if (rect.isValid()) { | 
| Chris@283 | 2872         MeasureRect mr; | 
| Chris@283 | 2873         setMeasureRectFromPixrect(v, mr, rect); | 
| Chris@283 | 2874         CommandHistory::getInstance()->addCommand | 
| Chris@283 | 2875             (new AddMeasurementRectCommand(this, mr)); | 
| Chris@283 | 2876     } | 
| Chris@283 | 2877 } | 
| Chris@283 | 2878 | 
| Chris@77 | 2879 bool | 
| Chris@918 | 2880 SpectrogramLayer::getCrosshairExtents(LayerGeometryProvider *v, QPainter &paint, | 
| Chris@77 | 2881                                       QPoint cursorPos, | 
| Chris@1025 | 2882                                       vector<QRect> &extents) const | 
| Chris@77 | 2883 { | 
| Chris@918 | 2884     QRect vertical(cursorPos.x() - 12, 0, 12, v->getPaintHeight()); | 
| Chris@77 | 2885     extents.push_back(vertical); | 
| Chris@77 | 2886 | 
| Chris@77 | 2887     QRect horizontal(0, cursorPos.y(), cursorPos.x(), 1); | 
| Chris@77 | 2888     extents.push_back(horizontal); | 
| Chris@77 | 2889 | 
| Chris@608 | 2890     int sw = getVerticalScaleWidth(v, m_haveDetailedScale, paint); | 
| Chris@264 | 2891 | 
| Chris@280 | 2892     QRect freq(sw, cursorPos.y() - paint.fontMetrics().ascent() - 2, | 
| Chris@280 | 2893                paint.fontMetrics().width("123456 Hz") + 2, | 
| Chris@280 | 2894                paint.fontMetrics().height()); | 
| Chris@280 | 2895     extents.push_back(freq); | 
| Chris@264 | 2896 | 
| Chris@279 | 2897     QRect pitch(sw, cursorPos.y() + 2, | 
| Chris@279 | 2898                 paint.fontMetrics().width("C#10+50c") + 2, | 
| Chris@279 | 2899                 paint.fontMetrics().height()); | 
| Chris@279 | 2900     extents.push_back(pitch); | 
| Chris@279 | 2901 | 
| Chris@280 | 2902     QRect rt(cursorPos.x(), | 
| Chris@918 | 2903              v->getPaintHeight() - paint.fontMetrics().height() - 2, | 
| Chris@280 | 2904              paint.fontMetrics().width("1234.567 s"), | 
| Chris@280 | 2905              paint.fontMetrics().height()); | 
| Chris@280 | 2906     extents.push_back(rt); | 
| Chris@280 | 2907 | 
| Chris@280 | 2908     int w(paint.fontMetrics().width("1234567890") + 2); | 
| Chris@280 | 2909     QRect frame(cursorPos.x() - w - 2, | 
| Chris@918 | 2910                 v->getPaintHeight() - paint.fontMetrics().height() - 2, | 
| Chris@280 | 2911                 w, | 
| Chris@280 | 2912                 paint.fontMetrics().height()); | 
| Chris@280 | 2913     extents.push_back(frame); | 
| Chris@280 | 2914 | 
| Chris@77 | 2915     return true; | 
| Chris@77 | 2916 } | 
| Chris@77 | 2917 | 
| Chris@77 | 2918 void | 
| Chris@918 | 2919 SpectrogramLayer::paintCrosshairs(LayerGeometryProvider *v, QPainter &paint, | 
| Chris@77 | 2920                                   QPoint cursorPos) const | 
| Chris@77 | 2921 { | 
| Chris@77 | 2922     paint.save(); | 
| Chris@283 | 2923 | 
| Chris@608 | 2924     int sw = getVerticalScaleWidth(v, m_haveDetailedScale, paint); | 
| Chris@283 | 2925 | 
| Chris@282 | 2926     QFont fn = paint.font(); | 
| Chris@282 | 2927     if (fn.pointSize() > 8) { | 
| Chris@282 | 2928         fn.setPointSize(fn.pointSize() - 1); | 
| Chris@282 | 2929         paint.setFont(fn); | 
| Chris@282 | 2930     } | 
| Chris@77 | 2931     paint.setPen(m_crosshairColour); | 
| Chris@77 | 2932 | 
| Chris@77 | 2933     paint.drawLine(0, cursorPos.y(), cursorPos.x() - 1, cursorPos.y()); | 
| Chris@918 | 2934     paint.drawLine(cursorPos.x(), 0, cursorPos.x(), v->getPaintHeight()); | 
| Chris@77 | 2935 | 
| Chris@905 | 2936     double fundamental = getFrequencyForY(v, cursorPos.y()); | 
| Chris@77 | 2937 | 
| Chris@278 | 2938     v->drawVisibleText(paint, | 
| Chris@278 | 2939                        sw + 2, | 
| Chris@278 | 2940                        cursorPos.y() - 2, | 
| Chris@278 | 2941                        QString("%1 Hz").arg(fundamental), | 
| Chris@278 | 2942                        View::OutlinedText); | 
| Chris@278 | 2943 | 
| Chris@279 | 2944     if (Pitch::isFrequencyInMidiRange(fundamental)) { | 
| Chris@279 | 2945         QString pitchLabel = Pitch::getPitchLabelForFrequency(fundamental); | 
| Chris@279 | 2946         v->drawVisibleText(paint, | 
| Chris@279 | 2947                            sw + 2, | 
| Chris@279 | 2948                            cursorPos.y() + paint.fontMetrics().ascent() + 2, | 
| Chris@279 | 2949                            pitchLabel, | 
| Chris@279 | 2950                            View::OutlinedText); | 
| Chris@279 | 2951     } | 
| Chris@279 | 2952 | 
| Chris@907 | 2953     sv_frame_t frame = v->getFrameForX(cursorPos.x()); | 
| Chris@279 | 2954     RealTime rt = RealTime::frame2RealTime(frame, m_model->getSampleRate()); | 
| Chris@280 | 2955     QString rtLabel = QString("%1 s").arg(rt.toText(true).c_str()); | 
| Chris@280 | 2956     QString frameLabel = QString("%1").arg(frame); | 
| Chris@280 | 2957     v->drawVisibleText(paint, | 
| Chris@280 | 2958                        cursorPos.x() - paint.fontMetrics().width(frameLabel) - 2, | 
| Chris@918 | 2959                        v->getPaintHeight() - 2, | 
| Chris@280 | 2960                        frameLabel, | 
| Chris@280 | 2961                        View::OutlinedText); | 
| Chris@280 | 2962     v->drawVisibleText(paint, | 
| Chris@280 | 2963                        cursorPos.x() + 2, | 
| Chris@918 | 2964                        v->getPaintHeight() - 2, | 
| Chris@280 | 2965                        rtLabel, | 
| Chris@280 | 2966                        View::OutlinedText); | 
| Chris@264 | 2967 | 
| Chris@77 | 2968     int harmonic = 2; | 
| Chris@77 | 2969 | 
| Chris@77 | 2970     while (harmonic < 100) { | 
| Chris@77 | 2971 | 
| Chris@907 | 2972         int hy = int(lrint(getYForFrequency(v, fundamental * harmonic))); | 
| Chris@918 | 2973         if (hy < 0 || hy > v->getPaintHeight()) break; | 
| Chris@77 | 2974 | 
| Chris@77 | 2975         int len = 7; | 
| Chris@77 | 2976 | 
| Chris@77 | 2977         if (harmonic % 2 == 0) { | 
| Chris@77 | 2978             if (harmonic % 4 == 0) { | 
| Chris@77 | 2979                 len = 12; | 
| Chris@77 | 2980             } else { | 
| Chris@77 | 2981                 len = 10; | 
| Chris@77 | 2982             } | 
| Chris@77 | 2983         } | 
| Chris@77 | 2984 | 
| Chris@77 | 2985         paint.drawLine(cursorPos.x() - len, | 
| Chris@907 | 2986                        hy, | 
| Chris@77 | 2987                        cursorPos.x(), | 
| Chris@907 | 2988                        hy); | 
| Chris@77 | 2989 | 
| Chris@77 | 2990         ++harmonic; | 
| Chris@77 | 2991     } | 
| Chris@77 | 2992 | 
| Chris@77 | 2993     paint.restore(); | 
| Chris@77 | 2994 } | 
| Chris@77 | 2995 | 
| Chris@25 | 2996 QString | 
| Chris@918 | 2997 SpectrogramLayer::getFeatureDescription(LayerGeometryProvider *v, QPoint &pos) const | 
| Chris@25 | 2998 { | 
| Chris@25 | 2999     int x = pos.x(); | 
| Chris@25 | 3000     int y = pos.y(); | 
| Chris@0 | 3001 | 
| Chris@25 | 3002     if (!m_model || !m_model->isOK()) return ""; | 
| Chris@0 | 3003 | 
| Chris@905 | 3004     double magMin = 0, magMax = 0; | 
| Chris@905 | 3005     double phaseMin = 0, phaseMax = 0; | 
| Chris@905 | 3006     double freqMin = 0, freqMax = 0; | 
| Chris@905 | 3007     double adjFreqMin = 0, adjFreqMax = 0; | 
| Chris@25 | 3008     QString pitchMin, pitchMax; | 
| Chris@0 | 3009     RealTime rtMin, rtMax; | 
| Chris@0 | 3010 | 
| Chris@38 | 3011     bool haveValues = false; | 
| Chris@0 | 3012 | 
| Chris@44 | 3013     if (!getXBinSourceRange(v, x, rtMin, rtMax)) { | 
| Chris@38 | 3014 	return ""; | 
| Chris@38 | 3015     } | 
| Chris@44 | 3016     if (getXYBinSourceRange(v, x, y, magMin, magMax, phaseMin, phaseMax)) { | 
| Chris@38 | 3017 	haveValues = true; | 
| Chris@38 | 3018     } | 
| Chris@0 | 3019 | 
| Chris@35 | 3020     QString adjFreqText = "", adjPitchText = ""; | 
| Chris@35 | 3021 | 
| Chris@38 | 3022     if (m_binDisplay == PeakFrequencies) { | 
| Chris@35 | 3023 | 
| Chris@44 | 3024 	if (!getAdjustedYBinSourceRange(v, x, y, freqMin, freqMax, | 
| Chris@38 | 3025 					adjFreqMin, adjFreqMax)) { | 
| Chris@38 | 3026 	    return ""; | 
| Chris@38 | 3027 	} | 
| Chris@35 | 3028 | 
| Chris@35 | 3029 	if (adjFreqMin != adjFreqMax) { | 
| Chris@65 | 3030 	    adjFreqText = tr("Peak Frequency:\t%1 - %2 Hz\n") | 
| Chris@35 | 3031 		.arg(adjFreqMin).arg(adjFreqMax); | 
| Chris@35 | 3032 	} else { | 
| Chris@65 | 3033 	    adjFreqText = tr("Peak Frequency:\t%1 Hz\n") | 
| Chris@35 | 3034 		.arg(adjFreqMin); | 
| Chris@38 | 3035 	} | 
| Chris@38 | 3036 | 
| Chris@38 | 3037 	QString pmin = Pitch::getPitchLabelForFrequency(adjFreqMin); | 
| Chris@38 | 3038 	QString pmax = Pitch::getPitchLabelForFrequency(adjFreqMax); | 
| Chris@38 | 3039 | 
| Chris@38 | 3040 	if (pmin != pmax) { | 
| Chris@65 | 3041 	    adjPitchText = tr("Peak Pitch:\t%3 - %4\n").arg(pmin).arg(pmax); | 
| Chris@38 | 3042 	} else { | 
| Chris@65 | 3043 	    adjPitchText = tr("Peak Pitch:\t%2\n").arg(pmin); | 
| Chris@35 | 3044 	} | 
| Chris@35 | 3045 | 
| Chris@35 | 3046     } else { | 
| Chris@35 | 3047 | 
| Chris@44 | 3048 	if (!getYBinSourceRange(v, y, freqMin, freqMax)) return ""; | 
| Chris@35 | 3049     } | 
| Chris@35 | 3050 | 
| Chris@25 | 3051     QString text; | 
| Chris@25 | 3052 | 
| Chris@25 | 3053     if (rtMin != rtMax) { | 
| Chris@25 | 3054 	text += tr("Time:\t%1 - %2\n") | 
| Chris@25 | 3055 	    .arg(rtMin.toText(true).c_str()) | 
| Chris@25 | 3056 	    .arg(rtMax.toText(true).c_str()); | 
| Chris@25 | 3057     } else { | 
| Chris@25 | 3058 	text += tr("Time:\t%1\n") | 
| Chris@25 | 3059 	    .arg(rtMin.toText(true).c_str()); | 
| Chris@0 | 3060     } | 
| Chris@0 | 3061 | 
| Chris@25 | 3062     if (freqMin != freqMax) { | 
| Chris@65 | 3063 	text += tr("%1Bin Frequency:\t%2 - %3 Hz\n%4Bin Pitch:\t%5 - %6\n") | 
| Chris@65 | 3064 	    .arg(adjFreqText) | 
| Chris@25 | 3065 	    .arg(freqMin) | 
| Chris@25 | 3066 	    .arg(freqMax) | 
| Chris@65 | 3067 	    .arg(adjPitchText) | 
| Chris@65 | 3068 	    .arg(Pitch::getPitchLabelForFrequency(freqMin)) | 
| Chris@65 | 3069 	    .arg(Pitch::getPitchLabelForFrequency(freqMax)); | 
| Chris@65 | 3070     } else { | 
| Chris@65 | 3071 	text += tr("%1Bin Frequency:\t%2 Hz\n%3Bin Pitch:\t%4\n") | 
| Chris@35 | 3072 	    .arg(adjFreqText) | 
| Chris@25 | 3073 	    .arg(freqMin) | 
| Chris@65 | 3074 	    .arg(adjPitchText) | 
| Chris@65 | 3075 	    .arg(Pitch::getPitchLabelForFrequency(freqMin)); | 
| Chris@25 | 3076     } | 
| Chris@25 | 3077 | 
| Chris@38 | 3078     if (haveValues) { | 
| Chris@905 | 3079 	double dbMin = AudioLevel::multiplier_to_dB(magMin); | 
| Chris@905 | 3080 	double dbMax = AudioLevel::multiplier_to_dB(magMax); | 
| Chris@43 | 3081 	QString dbMinString; | 
| Chris@43 | 3082 	QString dbMaxString; | 
| Chris@43 | 3083 	if (dbMin == AudioLevel::DB_FLOOR) { | 
| Chris@43 | 3084 	    dbMinString = tr("-Inf"); | 
| Chris@43 | 3085 	} else { | 
| Chris@907 | 3086 	    dbMinString = QString("%1").arg(lrint(dbMin)); | 
| Chris@43 | 3087 	} | 
| Chris@43 | 3088 	if (dbMax == AudioLevel::DB_FLOOR) { | 
| Chris@43 | 3089 	    dbMaxString = tr("-Inf"); | 
| Chris@43 | 3090 	} else { | 
| Chris@907 | 3091 	    dbMaxString = QString("%1").arg(lrint(dbMax)); | 
| Chris@43 | 3092 	} | 
| Chris@907 | 3093 	if (lrint(dbMin) != lrint(dbMax)) { | 
| Chris@199 | 3094 	    text += tr("dB:\t%1 - %2").arg(dbMinString).arg(dbMaxString); | 
| Chris@25 | 3095 	} else { | 
| Chris@199 | 3096 	    text += tr("dB:\t%1").arg(dbMinString); | 
| Chris@25 | 3097 	} | 
| Chris@38 | 3098 	if (phaseMin != phaseMax) { | 
| Chris@38 | 3099 	    text += tr("\nPhase:\t%1 - %2").arg(phaseMin).arg(phaseMax); | 
| Chris@38 | 3100 	} else { | 
| Chris@38 | 3101 	    text += tr("\nPhase:\t%1").arg(phaseMin); | 
| Chris@38 | 3102 	} | 
| Chris@25 | 3103     } | 
| Chris@25 | 3104 | 
| Chris@25 | 3105     return text; | 
| Chris@0 | 3106 } | 
| Chris@25 | 3107 | 
| Chris@0 | 3108 int | 
| Chris@40 | 3109 SpectrogramLayer::getColourScaleWidth(QPainter &paint) const | 
| Chris@40 | 3110 { | 
| Chris@40 | 3111     int cw; | 
| Chris@40 | 3112 | 
| Chris@119 | 3113     cw = paint.fontMetrics().width("-80dB"); | 
| Chris@119 | 3114 | 
| Chris@40 | 3115     return cw; | 
| Chris@40 | 3116 } | 
| Chris@40 | 3117 | 
| Chris@40 | 3118 int | 
| Chris@918 | 3119 SpectrogramLayer::getVerticalScaleWidth(LayerGeometryProvider *, bool detailed, QPainter &paint) const | 
| Chris@0 | 3120 { | 
| Chris@0 | 3121     if (!m_model || !m_model->isOK()) return 0; | 
| Chris@0 | 3122 | 
| Chris@607 | 3123     int cw = 0; | 
| Chris@607 | 3124     if (detailed) cw = getColourScaleWidth(paint); | 
| Chris@40 | 3125 | 
| Chris@0 | 3126     int tw = paint.fontMetrics().width(QString("%1") | 
| Chris@0 | 3127 				     .arg(m_maxFrequency > 0 ? | 
| Chris@0 | 3128 					  m_maxFrequency - 1 : | 
| Chris@0 | 3129 					  m_model->getSampleRate() / 2)); | 
| Chris@0 | 3130 | 
| Chris@234 | 3131     int fw = paint.fontMetrics().width(tr("43Hz")); | 
| Chris@0 | 3132     if (tw < fw) tw = fw; | 
| Chris@40 | 3133 | 
| Chris@40 | 3134     int tickw = (m_frequencyScale == LogFrequencyScale ? 10 : 4); | 
| Chris@0 | 3135 | 
| Chris@40 | 3136     return cw + tickw + tw + 13; | 
| Chris@0 | 3137 } | 
| Chris@0 | 3138 | 
| Chris@0 | 3139 void | 
| Chris@918 | 3140 SpectrogramLayer::paintVerticalScale(LayerGeometryProvider *v, bool detailed, QPainter &paint, QRect rect) const | 
| Chris@0 | 3141 { | 
| Chris@0 | 3142     if (!m_model || !m_model->isOK()) { | 
| Chris@0 | 3143 	return; | 
| Chris@0 | 3144     } | 
| Chris@0 | 3145 | 
| Chris@382 | 3146     Profiler profiler("SpectrogramLayer::paintVerticalScale"); | 
| Chris@122 | 3147 | 
| Chris@120 | 3148     //!!! cache this? | 
| Chris@120 | 3149 | 
| Chris@0 | 3150     int h = rect.height(), w = rect.width(); | 
| Chris@0 | 3151 | 
| Chris@40 | 3152     int tickw = (m_frequencyScale == LogFrequencyScale ? 10 : 4); | 
| Chris@40 | 3153     int pkw = (m_frequencyScale == LogFrequencyScale ? 10 : 0); | 
| Chris@40 | 3154 | 
| Chris@805 | 3155     int bins = m_fftSize / 2; | 
| Chris@907 | 3156     sv_samplerate_t sr = m_model->getSampleRate(); | 
| Chris@0 | 3157 | 
| Chris@0 | 3158     if (m_maxFrequency > 0) { | 
| Chris@107 | 3159 	bins = int((double(m_maxFrequency) * m_fftSize) / sr + 0.1); | 
| Chris@107 | 3160 	if (bins > m_fftSize / 2) bins = m_fftSize / 2; | 
| Chris@0 | 3161     } | 
| Chris@0 | 3162 | 
| Chris@607 | 3163     int cw = 0; | 
| Chris@607 | 3164 | 
| Chris@607 | 3165     if (detailed) cw = getColourScaleWidth(paint); | 
| Chris@119 | 3166     int cbw = paint.fontMetrics().width("dB"); | 
| Chris@40 | 3167 | 
| Chris@0 | 3168     int py = -1; | 
| Chris@0 | 3169     int textHeight = paint.fontMetrics().height(); | 
| Chris@0 | 3170     int toff = -textHeight + paint.fontMetrics().ascent() + 2; | 
| Chris@0 | 3171 | 
| Chris@607 | 3172     if (detailed && (h > textHeight * 3 + 10)) { | 
| Chris@119 | 3173 | 
| Chris@119 | 3174         int topLines = 2; | 
| Chris@119 | 3175         if (m_colourScale == PhaseColourScale) topLines = 1; | 
| Chris@119 | 3176 | 
| Chris@119 | 3177 	int ch = h - textHeight * (topLines + 1) - 8; | 
| Chris@119 | 3178 //	paint.drawRect(4, textHeight + 4, cw - 1, ch + 1); | 
| Chris@119 | 3179 	paint.drawRect(4 + cw - cbw, textHeight * topLines + 4, cbw - 1, ch + 1); | 
| Chris@40 | 3180 | 
| Chris@40 | 3181 	QString top, bottom; | 
| Chris@905 | 3182         double min = m_viewMags[v].getMin(); | 
| Chris@905 | 3183         double max = m_viewMags[v].getMax(); | 
| Chris@905 | 3184 | 
| Chris@905 | 3185         double dBmin = AudioLevel::multiplier_to_dB(min); | 
| Chris@905 | 3186         double dBmax = AudioLevel::multiplier_to_dB(max); | 
| Chris@119 | 3187 | 
| Chris@120 | 3188         if (dBmax < -60.f) dBmax = -60.f; | 
| Chris@907 | 3189         else top = QString("%1").arg(lrint(dBmax)); | 
| Chris@120 | 3190 | 
| Chris@120 | 3191         if (dBmin < dBmax - 60.f) dBmin = dBmax - 60.f; | 
| Chris@907 | 3192         bottom = QString("%1").arg(lrint(dBmin)); | 
| Chris@119 | 3193 | 
| Chris@119 | 3194         //!!! & phase etc | 
| Chris@119 | 3195 | 
| Chris@119 | 3196         if (m_colourScale != PhaseColourScale) { | 
| Chris@119 | 3197             paint.drawText((cw + 6 - paint.fontMetrics().width("dBFS")) / 2, | 
| Chris@119 | 3198                            2 + textHeight + toff, "dBFS"); | 
| Chris@119 | 3199         } | 
| Chris@119 | 3200 | 
| Chris@119 | 3201 //	paint.drawText((cw + 6 - paint.fontMetrics().width(top)) / 2, | 
| Chris@119 | 3202 	paint.drawText(3 + cw - cbw - paint.fontMetrics().width(top), | 
| Chris@119 | 3203 		       2 + textHeight * topLines + toff + textHeight/2, top); | 
| Chris@119 | 3204 | 
| Chris@119 | 3205 	paint.drawText(3 + cw - cbw - paint.fontMetrics().width(bottom), | 
| Chris@119 | 3206 		       h + toff - 3 - textHeight/2, bottom); | 
| Chris@40 | 3207 | 
| Chris@40 | 3208 	paint.save(); | 
| Chris@40 | 3209 	paint.setBrush(Qt::NoBrush); | 
| Chris@119 | 3210 | 
| Chris@119 | 3211         int lasty = 0; | 
| Chris@119 | 3212         int lastdb = 0; | 
| Chris@119 | 3213 | 
| Chris@40 | 3214 	for (int i = 0; i < ch; ++i) { | 
| Chris@119 | 3215 | 
| Chris@905 | 3216             double dBval = dBmin + (((dBmax - dBmin) * i) / (ch - 1)); | 
| Chris@119 | 3217             int idb = int(dBval); | 
| Chris@119 | 3218 | 
| Chris@905 | 3219             double value = AudioLevel::dB_to_multiplier(dBval); | 
| Chris@119 | 3220             int colour = getDisplayValue(v, value * m_gain); | 
| Chris@210 | 3221 | 
| Chris@907 | 3222 	    paint.setPen(m_palette.getColour((unsigned char)colour)); | 
| Chris@119 | 3223 | 
| Chris@119 | 3224             int y = textHeight * topLines + 4 + ch - i; | 
| Chris@119 | 3225 | 
| Chris@119 | 3226             paint.drawLine(5 + cw - cbw, y, cw + 2, y); | 
| Chris@119 | 3227 | 
| Chris@119 | 3228             if (i == 0) { | 
| Chris@119 | 3229                 lasty = y; | 
| Chris@119 | 3230                 lastdb = idb; | 
| Chris@119 | 3231             } else if (i < ch - paint.fontMetrics().ascent() && | 
| Chris@120 | 3232                        idb != lastdb && | 
| Chris@119 | 3233                        ((abs(y - lasty) > textHeight && | 
| Chris@119 | 3234                          idb % 10 == 0) || | 
| Chris@119 | 3235                         (abs(y - lasty) > paint.fontMetrics().ascent() && | 
| Chris@119 | 3236                          idb % 5 == 0))) { | 
| Chris@287 | 3237                 paint.setPen(v->getBackground()); | 
| Chris@119 | 3238                 QString text = QString("%1").arg(idb); | 
| Chris@119 | 3239                 paint.drawText(3 + cw - cbw - paint.fontMetrics().width(text), | 
| Chris@119 | 3240                                y + toff + textHeight/2, text); | 
| Chris@287 | 3241                 paint.setPen(v->getForeground()); | 
| Chris@119 | 3242                 paint.drawLine(5 + cw - cbw, y, 8 + cw - cbw, y); | 
| Chris@119 | 3243                 lasty = y; | 
| Chris@119 | 3244                 lastdb = idb; | 
| Chris@119 | 3245             } | 
| Chris@40 | 3246 	} | 
| Chris@40 | 3247 	paint.restore(); | 
| Chris@40 | 3248     } | 
| Chris@40 | 3249 | 
| Chris@40 | 3250     paint.drawLine(cw + 7, 0, cw + 7, h); | 
| Chris@40 | 3251 | 
| Chris@0 | 3252     int bin = -1; | 
| Chris@0 | 3253 | 
| Chris@918 | 3254     for (int y = 0; y < v->getPaintHeight(); ++y) { | 
| Chris@0 | 3255 | 
| Chris@905 | 3256 	double q0, q1; | 
| Chris@918 | 3257 	if (!getYBinRange(v, v->getPaintHeight() - y, q0, q1)) continue; | 
| Chris@0 | 3258 | 
| Chris@0 | 3259 	int vy; | 
| Chris@0 | 3260 | 
| Chris@0 | 3261 	if (int(q0) > bin) { | 
| Chris@0 | 3262 	    vy = y; | 
| Chris@0 | 3263 	    bin = int(q0); | 
| Chris@0 | 3264 	} else { | 
| Chris@0 | 3265 	    continue; | 
| Chris@0 | 3266 	} | 
| Chris@0 | 3267 | 
| Chris@907 | 3268 	int freq = int((sr * bin) / m_fftSize); | 
| Chris@0 | 3269 | 
| Chris@0 | 3270 	if (py >= 0 && (vy - py) < textHeight - 1) { | 
| Chris@40 | 3271 	    if (m_frequencyScale == LinearFrequencyScale) { | 
| Chris@40 | 3272 		paint.drawLine(w - tickw, h - vy, w, h - vy); | 
| Chris@40 | 3273 	    } | 
| Chris@0 | 3274 	    continue; | 
| Chris@0 | 3275 	} | 
| Chris@0 | 3276 | 
| Chris@0 | 3277 	QString text = QString("%1").arg(freq); | 
| Chris@234 | 3278 	if (bin == 1) text = tr("%1Hz").arg(freq); // bin 0 is DC | 
| Chris@40 | 3279 	paint.drawLine(cw + 7, h - vy, w - pkw - 1, h - vy); | 
| Chris@0 | 3280 | 
| Chris@0 | 3281 	if (h - vy - textHeight >= -2) { | 
| Chris@1025 | 3282 	    int tx = w - 3 - paint.fontMetrics().width(text) - max(tickw, pkw); | 
| Chris@0 | 3283 	    paint.drawText(tx, h - vy + toff, text); | 
| Chris@0 | 3284 	} | 
| Chris@0 | 3285 | 
| Chris@0 | 3286 	py = vy; | 
| Chris@0 | 3287     } | 
| Chris@40 | 3288 | 
| Chris@40 | 3289     if (m_frequencyScale == LogFrequencyScale) { | 
| Chris@40 | 3290 | 
| Chris@277 | 3291         // piano keyboard | 
| Chris@277 | 3292 | 
| Chris@690 | 3293         PianoScale().paintPianoVertical | 
| Chris@690 | 3294             (v, paint, QRect(w - pkw - 1, 0, pkw, h), | 
| Chris@690 | 3295              getEffectiveMinFrequency(), getEffectiveMaxFrequency()); | 
| Chris@40 | 3296     } | 
| Chris@608 | 3297 | 
| Chris@608 | 3298     m_haveDetailedScale = detailed; | 
| Chris@0 | 3299 } | 
| Chris@0 | 3300 | 
| Chris@187 | 3301 class SpectrogramRangeMapper : public RangeMapper | 
| Chris@187 | 3302 { | 
| Chris@187 | 3303 public: | 
| Chris@901 | 3304     SpectrogramRangeMapper(sv_samplerate_t sr, int /* fftsize */) : | 
| Chris@901 | 3305         m_dist(sr / 2), | 
| Chris@901 | 3306         m_s2(sqrt(sqrt(2))) { } | 
| Chris@187 | 3307     ~SpectrogramRangeMapper() { } | 
| Chris@187 | 3308 | 
| Chris@901 | 3309     virtual int getPositionForValue(double value) const { | 
| Chris@901 | 3310 | 
| Chris@901 | 3311         double dist = m_dist; | 
| Chris@187 | 3312 | 
| Chris@187 | 3313         int n = 0; | 
| Chris@187 | 3314 | 
| Chris@901 | 3315         while (dist > (value + 0.00001) && dist > 0.1) { | 
| Chris@187 | 3316             dist /= m_s2; | 
| Chris@187 | 3317             ++n; | 
| Chris@187 | 3318         } | 
| Chris@187 | 3319 | 
| Chris@187 | 3320         return n; | 
| Chris@187 | 3321     } | 
| Chris@724 | 3322 | 
| Chris@901 | 3323     virtual int getPositionForValueUnclamped(double value) const { | 
| Chris@724 | 3324         // We don't really support this | 
| Chris@724 | 3325         return getPositionForValue(value); | 
| Chris@724 | 3326     } | 
| Chris@187 | 3327 | 
| Chris@901 | 3328     virtual double getValueForPosition(int position) const { | 
| Chris@187 | 3329 | 
| Chris@187 | 3330         // Vertical zoom step 0 shows the entire range from DC -> | 
| Chris@187 | 3331         // Nyquist frequency.  Step 1 shows 2^(1/4) of the range of | 
| Chris@187 | 3332         // step 0, and so on until the visible range is smaller than | 
| Chris@187 | 3333         // the frequency step between bins at the current fft size. | 
| Chris@187 | 3334 | 
| Chris@901 | 3335         double dist = m_dist; | 
| Chris@187 | 3336 | 
| Chris@187 | 3337         int n = 0; | 
| Chris@187 | 3338         while (n < position) { | 
| Chris@187 | 3339             dist /= m_s2; | 
| Chris@187 | 3340             ++n; | 
| Chris@187 | 3341         } | 
| Chris@187 | 3342 | 
| Chris@187 | 3343         return dist; | 
| Chris@187 | 3344     } | 
| Chris@187 | 3345 | 
| Chris@901 | 3346     virtual double getValueForPositionUnclamped(int position) const { | 
| Chris@724 | 3347         // We don't really support this | 
| Chris@724 | 3348         return getValueForPosition(position); | 
| Chris@724 | 3349     } | 
| Chris@724 | 3350 | 
| Chris@187 | 3351     virtual QString getUnit() const { return "Hz"; } | 
| Chris@187 | 3352 | 
| Chris@187 | 3353 protected: | 
| Chris@901 | 3354     double m_dist; | 
| Chris@901 | 3355     double m_s2; | 
| Chris@187 | 3356 }; | 
| Chris@187 | 3357 | 
| Chris@133 | 3358 int | 
| Chris@133 | 3359 SpectrogramLayer::getVerticalZoomSteps(int &defaultStep) const | 
| Chris@133 | 3360 { | 
| Chris@135 | 3361     if (!m_model) return 0; | 
| Chris@187 | 3362 | 
| Chris@907 | 3363     sv_samplerate_t sr = m_model->getSampleRate(); | 
| Chris@187 | 3364 | 
| Chris@187 | 3365     SpectrogramRangeMapper mapper(sr, m_fftSize); | 
| Chris@187 | 3366 | 
| Chris@905 | 3367 //    int maxStep = mapper.getPositionForValue((double(sr) / m_fftSize) + 0.001); | 
| Chris@187 | 3368     int maxStep = mapper.getPositionForValue(0); | 
| Chris@905 | 3369     int minStep = mapper.getPositionForValue(double(sr) / 2); | 
| Chris@250 | 3370 | 
| Chris@805 | 3371     int initialMax = m_initialMaxFrequency; | 
| Chris@907 | 3372     if (initialMax == 0) initialMax = int(sr / 2); | 
| Chris@250 | 3373 | 
| Chris@250 | 3374     defaultStep = mapper.getPositionForValue(initialMax) - minStep; | 
| Chris@250 | 3375 | 
| Chris@587 | 3376 //    SVDEBUG << "SpectrogramLayer::getVerticalZoomSteps: " << maxStep - minStep << " (" << maxStep <<"-" << minStep << "), default is " << defaultStep << " (from initial max freq " << initialMax << ")" << endl; | 
| Chris@187 | 3377 | 
| Chris@187 | 3378     return maxStep - minStep; | 
| Chris@133 | 3379 } | 
| Chris@133 | 3380 | 
| Chris@133 | 3381 int | 
| Chris@133 | 3382 SpectrogramLayer::getCurrentVerticalZoomStep() const | 
| Chris@133 | 3383 { | 
| Chris@133 | 3384     if (!m_model) return 0; | 
| Chris@133 | 3385 | 
| Chris@905 | 3386     double dmin, dmax; | 
| Chris@133 | 3387     getDisplayExtents(dmin, dmax); | 
| Chris@133 | 3388 | 
| Chris@187 | 3389     SpectrogramRangeMapper mapper(m_model->getSampleRate(), m_fftSize); | 
| Chris@187 | 3390     int n = mapper.getPositionForValue(dmax - dmin); | 
| Chris@587 | 3391 //    SVDEBUG << "SpectrogramLayer::getCurrentVerticalZoomStep: " << n << endl; | 
| Chris@133 | 3392     return n; | 
| Chris@133 | 3393 } | 
| Chris@133 | 3394 | 
| Chris@133 | 3395 void | 
| Chris@133 | 3396 SpectrogramLayer::setVerticalZoomStep(int step) | 
| Chris@133 | 3397 { | 
| Chris@187 | 3398     if (!m_model) return; | 
| Chris@187 | 3399 | 
| Chris@905 | 3400     double dmin = m_minFrequency, dmax = m_maxFrequency; | 
| Chris@253 | 3401 //    getDisplayExtents(dmin, dmax); | 
| Chris@253 | 3402 | 
| Chris@682 | 3403 //    cerr << "current range " << dmin << " -> " << dmax << ", range " << dmax-dmin << ", mid " << (dmax + dmin)/2 << endl; | 
| Chris@133 | 3404 | 
| Chris@907 | 3405     sv_samplerate_t sr = m_model->getSampleRate(); | 
| Chris@187 | 3406     SpectrogramRangeMapper mapper(sr, m_fftSize); | 
| Chris@905 | 3407     double newdist = mapper.getValueForPosition(step); | 
| Chris@905 | 3408 | 
| Chris@905 | 3409     double newmin, newmax; | 
| Chris@253 | 3410 | 
| Chris@253 | 3411     if (m_frequencyScale == LogFrequencyScale) { | 
| Chris@253 | 3412 | 
| Chris@253 | 3413         // need to pick newmin and newmax such that | 
| Chris@253 | 3414         // | 
| Chris@253 | 3415         // (log(newmin) + log(newmax)) / 2 == logmid | 
| Chris@253 | 3416         // and | 
| Chris@253 | 3417         // newmax - newmin = newdist | 
| Chris@253 | 3418         // | 
| Chris@253 | 3419         // so log(newmax - newdist) + log(newmax) == 2logmid | 
| Chris@253 | 3420         // log(newmax(newmax - newdist)) == 2logmid | 
| Chris@253 | 3421         // newmax.newmax - newmax.newdist == exp(2logmid) | 
| Chris@253 | 3422         // newmax^2 + (-newdist)newmax + -exp(2logmid) == 0 | 
| Chris@253 | 3423         // quadratic with a = 1, b = -newdist, c = -exp(2logmid), all known | 
| Chris@253 | 3424         // | 
| Chris@253 | 3425         // positive root | 
| Chris@253 | 3426         // newmax = (newdist + sqrt(newdist^2 + 4exp(2logmid))) / 2 | 
| Chris@253 | 3427         // | 
| Chris@253 | 3428         // but logmid = (log(dmin) + log(dmax)) / 2 | 
| Chris@253 | 3429         // so exp(2logmid) = exp(log(dmin) + log(dmax)) | 
| Chris@253 | 3430         // = exp(log(dmin.dmax)) | 
| Chris@253 | 3431         // = dmin.dmax | 
| Chris@253 | 3432         // so newmax = (newdist + sqrtf(newdist^2 + 4dmin.dmax)) / 2 | 
| Chris@253 | 3433 | 
| Chris@907 | 3434         newmax = (newdist + sqrt(newdist*newdist + 4*dmin*dmax)) / 2; | 
| Chris@253 | 3435         newmin = newmax - newdist; | 
| Chris@253 | 3436 | 
| Chris@682 | 3437 //        cerr << "newmin = " << newmin << ", newmax = " << newmax << endl; | 
| Chris@253 | 3438 | 
| Chris@253 | 3439     } else { | 
| Chris@905 | 3440         double dmid = (dmax + dmin) / 2; | 
| Chris@253 | 3441         newmin = dmid - newdist / 2; | 
| Chris@253 | 3442         newmax = dmid + newdist / 2; | 
| Chris@253 | 3443     } | 
| Chris@187 | 3444 | 
| Chris@905 | 3445     double mmin, mmax; | 
| Chris@187 | 3446     mmin = 0; | 
| Chris@905 | 3447     mmax = double(sr) / 2; | 
| Chris@133 | 3448 | 
| Chris@187 | 3449     if (newmin < mmin) { | 
| Chris@187 | 3450         newmax += (mmin - newmin); | 
| Chris@187 | 3451         newmin = mmin; | 
| Chris@187 | 3452     } | 
| Chris@187 | 3453     if (newmax > mmax) { | 
| Chris@187 | 3454         newmax = mmax; | 
| Chris@187 | 3455     } | 
| Chris@133 | 3456 | 
| Chris@587 | 3457 //    SVDEBUG << "SpectrogramLayer::setVerticalZoomStep: " << step << ": " << newmin << " -> " << newmax << " (range " << newdist << ")" << endl; | 
| Chris@253 | 3458 | 
| Chris@907 | 3459     setMinFrequency(int(lrint(newmin))); | 
| Chris@907 | 3460     setMaxFrequency(int(lrint(newmax))); | 
| Chris@187 | 3461 } | 
| Chris@187 | 3462 | 
| Chris@187 | 3463 RangeMapper * | 
| Chris@187 | 3464 SpectrogramLayer::getNewVerticalZoomRangeMapper() const | 
| Chris@187 | 3465 { | 
| Chris@187 | 3466     if (!m_model) return 0; | 
| Chris@187 | 3467     return new SpectrogramRangeMapper(m_model->getSampleRate(), m_fftSize); | 
| Chris@133 | 3468 } | 
| Chris@133 | 3469 | 
| Chris@273 | 3470 void | 
| Chris@918 | 3471 SpectrogramLayer::updateMeasureRectYCoords(LayerGeometryProvider *v, const MeasureRect &r) const | 
| Chris@273 | 3472 { | 
| Chris@273 | 3473     int y0 = 0; | 
| Chris@907 | 3474     if (r.startY > 0.0) y0 = int(getYForFrequency(v, r.startY)); | 
| Chris@273 | 3475 | 
| Chris@273 | 3476     int y1 = y0; | 
| Chris@907 | 3477     if (r.endY > 0.0) y1 = int(getYForFrequency(v, r.endY)); | 
| Chris@273 | 3478 | 
| Chris@587 | 3479 //    SVDEBUG << "SpectrogramLayer::updateMeasureRectYCoords: start " << r.startY << " -> " << y0 << ", end " << r.endY << " -> " << y1 << endl; | 
| Chris@273 | 3480 | 
| Chris@273 | 3481     r.pixrect = QRect(r.pixrect.x(), y0, r.pixrect.width(), y1 - y0); | 
| Chris@273 | 3482 } | 
| Chris@273 | 3483 | 
| Chris@273 | 3484 void | 
| Chris@918 | 3485 SpectrogramLayer::setMeasureRectYCoord(LayerGeometryProvider *v, MeasureRect &r, bool start, int y) const | 
| Chris@273 | 3486 { | 
| Chris@273 | 3487     if (start) { | 
| Chris@273 | 3488         r.startY = getFrequencyForY(v, y); | 
| Chris@273 | 3489         r.endY = r.startY; | 
| Chris@273 | 3490     } else { | 
| Chris@273 | 3491         r.endY = getFrequencyForY(v, y); | 
| Chris@273 | 3492     } | 
| Chris@587 | 3493 //    SVDEBUG << "SpectrogramLayer::setMeasureRectYCoord: start " << r.startY << " <- " << y << ", end " << r.endY << " <- " << y << endl; | 
| Chris@273 | 3494 | 
| Chris@273 | 3495 } | 
| Chris@273 | 3496 | 
| Chris@316 | 3497 void | 
| Chris@316 | 3498 SpectrogramLayer::toXml(QTextStream &stream, | 
| Chris@316 | 3499                         QString indent, QString extraAttributes) const | 
| Chris@6 | 3500 { | 
| Chris@6 | 3501     QString s; | 
| Chris@6 | 3502 | 
| Chris@6 | 3503     s += QString("channel=\"%1\" " | 
| Chris@6 | 3504 		 "windowSize=\"%2\" " | 
| Chris@153 | 3505 		 "windowHopLevel=\"%3\" " | 
| Chris@153 | 3506 		 "gain=\"%4\" " | 
| Chris@153 | 3507 		 "threshold=\"%5\" ") | 
| Chris@6 | 3508 	.arg(m_channel) | 
| Chris@6 | 3509 	.arg(m_windowSize) | 
| Chris@97 | 3510 	.arg(m_windowHopLevel) | 
| Chris@37 | 3511 	.arg(m_gain) | 
| Chris@37 | 3512 	.arg(m_threshold); | 
| Chris@37 | 3513 | 
| Chris@37 | 3514     s += QString("minFrequency=\"%1\" " | 
| Chris@37 | 3515 		 "maxFrequency=\"%2\" " | 
| Chris@37 | 3516 		 "colourScale=\"%3\" " | 
| Chris@37 | 3517 		 "colourScheme=\"%4\" " | 
| Chris@37 | 3518 		 "colourRotation=\"%5\" " | 
| Chris@37 | 3519 		 "frequencyScale=\"%6\" " | 
| Chris@761 | 3520 		 "binDisplay=\"%7\" ") | 
| Chris@37 | 3521 	.arg(m_minFrequency) | 
| Chris@6 | 3522 	.arg(m_maxFrequency) | 
| Chris@6 | 3523 	.arg(m_colourScale) | 
| Chris@197 | 3524 	.arg(m_colourMap) | 
| Chris@37 | 3525 	.arg(m_colourRotation) | 
| Chris@35 | 3526 	.arg(m_frequencyScale) | 
| Chris@761 | 3527 	.arg(m_binDisplay); | 
| Chris@761 | 3528 | 
| Chris@761 | 3529     s += QString("normalizeColumns=\"%1\" " | 
| Chris@761 | 3530                  "normalizeVisibleArea=\"%2\" " | 
| Chris@761 | 3531                  "normalizeHybrid=\"%3\" ") | 
| Chris@862 | 3532 	.arg(m_normalization == NormalizeColumns ? "true" : "false") | 
| Chris@862 | 3533         .arg(m_normalization == NormalizeVisibleArea ? "true" : "false") | 
| Chris@862 | 3534         .arg(m_normalization == NormalizeHybrid ? "true" : "false"); | 
| Chris@6 | 3535 | 
| Chris@316 | 3536     Layer::toXml(stream, indent, extraAttributes + " " + s); | 
| Chris@6 | 3537 } | 
| Chris@6 | 3538 | 
| Chris@11 | 3539 void | 
| Chris@11 | 3540 SpectrogramLayer::setProperties(const QXmlAttributes &attributes) | 
| Chris@11 | 3541 { | 
| Chris@11 | 3542     bool ok = false; | 
| Chris@11 | 3543 | 
| Chris@11 | 3544     int channel = attributes.value("channel").toInt(&ok); | 
| Chris@11 | 3545     if (ok) setChannel(channel); | 
| Chris@11 | 3546 | 
| Chris@805 | 3547     int windowSize = attributes.value("windowSize").toUInt(&ok); | 
| Chris@11 | 3548     if (ok) setWindowSize(windowSize); | 
| Chris@11 | 3549 | 
| Chris@805 | 3550     int windowHopLevel = attributes.value("windowHopLevel").toUInt(&ok); | 
| Chris@97 | 3551     if (ok) setWindowHopLevel(windowHopLevel); | 
| Chris@97 | 3552     else { | 
| Chris@805 | 3553         int windowOverlap = attributes.value("windowOverlap").toUInt(&ok); | 
| Chris@97 | 3554         // a percentage value | 
| Chris@97 | 3555         if (ok) { | 
| Chris@97 | 3556             if (windowOverlap == 0) setWindowHopLevel(0); | 
| Chris@97 | 3557             else if (windowOverlap == 25) setWindowHopLevel(1); | 
| Chris@97 | 3558             else if (windowOverlap == 50) setWindowHopLevel(2); | 
| Chris@97 | 3559             else if (windowOverlap == 75) setWindowHopLevel(3); | 
| Chris@97 | 3560             else if (windowOverlap == 90) setWindowHopLevel(4); | 
| Chris@97 | 3561         } | 
| Chris@97 | 3562     } | 
| Chris@11 | 3563 | 
| Chris@11 | 3564     float gain = attributes.value("gain").toFloat(&ok); | 
| Chris@11 | 3565     if (ok) setGain(gain); | 
| Chris@11 | 3566 | 
| Chris@37 | 3567     float threshold = attributes.value("threshold").toFloat(&ok); | 
| Chris@37 | 3568     if (ok) setThreshold(threshold); | 
| Chris@37 | 3569 | 
| Chris@805 | 3570     int minFrequency = attributes.value("minFrequency").toUInt(&ok); | 
| Chris@187 | 3571     if (ok) { | 
| Chris@587 | 3572         SVDEBUG << "SpectrogramLayer::setProperties: setting min freq to " << minFrequency << endl; | 
| Chris@187 | 3573         setMinFrequency(minFrequency); | 
| Chris@187 | 3574     } | 
| Chris@37 | 3575 | 
| Chris@805 | 3576     int maxFrequency = attributes.value("maxFrequency").toUInt(&ok); | 
| Chris@187 | 3577     if (ok) { | 
| Chris@587 | 3578         SVDEBUG << "SpectrogramLayer::setProperties: setting max freq to " << maxFrequency << endl; | 
| Chris@187 | 3579         setMaxFrequency(maxFrequency); | 
| Chris@187 | 3580     } | 
| Chris@11 | 3581 | 
| Chris@11 | 3582     ColourScale colourScale = (ColourScale) | 
| Chris@11 | 3583 	attributes.value("colourScale").toInt(&ok); | 
| Chris@11 | 3584     if (ok) setColourScale(colourScale); | 
| Chris@11 | 3585 | 
| Chris@197 | 3586     int colourMap = attributes.value("colourScheme").toInt(&ok); | 
| Chris@197 | 3587     if (ok) setColourMap(colourMap); | 
| Chris@11 | 3588 | 
| Chris@37 | 3589     int colourRotation = attributes.value("colourRotation").toInt(&ok); | 
| Chris@37 | 3590     if (ok) setColourRotation(colourRotation); | 
| Chris@37 | 3591 | 
| Chris@11 | 3592     FrequencyScale frequencyScale = (FrequencyScale) | 
| Chris@11 | 3593 	attributes.value("frequencyScale").toInt(&ok); | 
| Chris@11 | 3594     if (ok) setFrequencyScale(frequencyScale); | 
| Chris@35 | 3595 | 
| Chris@37 | 3596     BinDisplay binDisplay = (BinDisplay) | 
| Chris@37 | 3597 	attributes.value("binDisplay").toInt(&ok); | 
| Chris@37 | 3598     if (ok) setBinDisplay(binDisplay); | 
| Chris@36 | 3599 | 
| Chris@36 | 3600     bool normalizeColumns = | 
| Chris@36 | 3601 	(attributes.value("normalizeColumns").trimmed() == "true"); | 
| Chris@862 | 3602     if (normalizeColumns) { | 
| Chris@862 | 3603         setNormalization(NormalizeColumns); | 
| Chris@862 | 3604     } | 
| Chris@153 | 3605 | 
| Chris@153 | 3606     bool normalizeVisibleArea = | 
| Chris@153 | 3607 	(attributes.value("normalizeVisibleArea").trimmed() == "true"); | 
| Chris@862 | 3608     if (normalizeVisibleArea) { | 
| Chris@862 | 3609         setNormalization(NormalizeVisibleArea); | 
| Chris@862 | 3610     } | 
| Chris@761 | 3611 | 
| Chris@761 | 3612     bool normalizeHybrid = | 
| Chris@761 | 3613 	(attributes.value("normalizeHybrid").trimmed() == "true"); | 
| Chris@862 | 3614     if (normalizeHybrid) { | 
| Chris@862 | 3615         setNormalization(NormalizeHybrid); | 
| Chris@862 | 3616     } | 
| Chris@11 | 3617 } | 
| Chris@11 | 3618 |