annotate layer/SpectrogramLayer.cpp @ 1403:10e768adaee5

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