annotate layer/SpectrogramLayer.cpp @ 1457:160e6d010141 single-point

Avoid showing progress bars for dormant layers. They'll typically be duplicates, e.g. in the vect app you can get many dormant layers all reporting the alignment completion at the same time. (Maybe it would be better just to reject progress bars for alignment in dormant layers? Hm)
author Chris Cannam
date Thu, 16 May 2019 15:50:02 +0100
parents 6cf3cb6641e1
children 66bf1abfefc1
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@1408 59 m_model(nullptr),
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@1408 86 m_fftModel(nullptr),
Chris@1408 87 m_wholeCache(nullptr),
Chris@1408 88 m_peakCache(nullptr),
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@1408 151 m_fftModel = nullptr;
Chris@1408 152 m_peakCache = nullptr;
Chris@1408 153 m_wholeCache = nullptr;
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@1408 585 return nullptr;
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@1419 1059 // we do our own cacheing, and don't want to be responsible for
Chris@1419 1060 // guaranteeing to get an invisible seam if someone else scrolls
Chris@1419 1061 // us and we just fill in
Chris@1232 1062 return false;
Chris@1232 1063 }
Chris@1232 1064
Chris@29 1065 void
Chris@0 1066 SpectrogramLayer::cacheInvalid()
Chris@0 1067 {
Chris@391 1068 #ifdef DEBUG_SPECTROGRAM_REPAINT
Chris@985 1069 cerr << "SpectrogramLayer::cacheInvalid()" << endl;
Chris@391 1070 #endif
Chris@391 1071
Chris@1106 1072 invalidateRenderers();
Chris@119 1073 invalidateMagnitudes();
Chris@0 1074 }
Chris@0 1075
Chris@0 1076 void
Chris@1037 1077 SpectrogramLayer::cacheInvalid(
Chris@1037 1078 #ifdef DEBUG_SPECTROGRAM_REPAINT
Chris@1037 1079 sv_frame_t from, sv_frame_t to
Chris@1037 1080 #else
Chris@1037 1081 sv_frame_t , sv_frame_t
Chris@1037 1082 #endif
Chris@1037 1083 )
Chris@0 1084 {
Chris@391 1085 #ifdef DEBUG_SPECTROGRAM_REPAINT
Chris@985 1086 cerr << "SpectrogramLayer::cacheInvalid(" << from << ", " << to << ")" << endl;
Chris@391 1087 #endif
Chris@391 1088
Chris@1030 1089 // We used to call invalidateMagnitudes(from, to) to invalidate
Chris@1030 1090 // only those caches whose views contained some of the (from, to)
Chris@1030 1091 // range. That's the right thing to do; it has been lost in
Chris@1030 1092 // pulling out the image cache code, but it might not matter very
Chris@1030 1093 // much, since the underlying models for spectrogram layers don't
Chris@1030 1094 // change very often. Let's see.
Chris@1106 1095 invalidateRenderers();
Chris@391 1096 invalidateMagnitudes();
Chris@0 1097 }
Chris@0 1098
Chris@224 1099 bool
Chris@224 1100 SpectrogramLayer::hasLightBackground() const
Chris@224 1101 {
Chris@1362 1102 return ColourMapper(m_colourMap, m_colourInverted, 1.f, 255.f)
Chris@1362 1103 .hasLightBackground();
Chris@224 1104 }
Chris@224 1105
Chris@905 1106 double
Chris@40 1107 SpectrogramLayer::getEffectiveMinFrequency() const
Chris@40 1108 {
Chris@907 1109 sv_samplerate_t sr = m_model->getSampleRate();
Chris@1087 1110 double minf = double(sr) / getFFTSize();
Chris@40 1111
Chris@40 1112 if (m_minFrequency > 0.0) {
Chris@1234 1113 int minbin = int((double(m_minFrequency) * getFFTSize()) / sr + 0.01);
Chris@1234 1114 if (minbin < 1) minbin = 1;
Chris@1234 1115 minf = minbin * sr / getFFTSize();
Chris@40 1116 }
Chris@40 1117
Chris@40 1118 return minf;
Chris@40 1119 }
Chris@40 1120
Chris@905 1121 double
Chris@40 1122 SpectrogramLayer::getEffectiveMaxFrequency() const
Chris@40 1123 {
Chris@907 1124 sv_samplerate_t sr = m_model->getSampleRate();
Chris@905 1125 double maxf = double(sr) / 2;
Chris@40 1126
Chris@40 1127 if (m_maxFrequency > 0.0) {
Chris@1234 1128 int maxbin = int((double(m_maxFrequency) * getFFTSize()) / sr + 0.1);
Chris@1234 1129 if (maxbin > getFFTSize() / 2) maxbin = getFFTSize() / 2;
Chris@1234 1130 maxf = maxbin * sr / getFFTSize();
Chris@40 1131 }
Chris@40 1132
Chris@40 1133 return maxf;
Chris@40 1134 }
Chris@40 1135
Chris@0 1136 bool
Chris@918 1137 SpectrogramLayer::getYBinRange(LayerGeometryProvider *v, int y, double &q0, double &q1) const
Chris@0 1138 {
Chris@382 1139 Profiler profiler("SpectrogramLayer::getYBinRange");
Chris@918 1140 int h = v->getPaintHeight();
Chris@0 1141 if (y < 0 || y >= h) return false;
Chris@1117 1142 q0 = getBinForY(v, y);
Chris@1117 1143 q1 = getBinForY(v, y-1);
Chris@1117 1144 return true;
Chris@1117 1145 }
Chris@1117 1146
Chris@1117 1147 double
Chris@1117 1148 SpectrogramLayer::getYForBin(const LayerGeometryProvider *v, double bin) const
Chris@1117 1149 {
Chris@1117 1150 double minf = getEffectiveMinFrequency();
Chris@1117 1151 double maxf = getEffectiveMaxFrequency();
Chris@1117 1152 bool logarithmic = (m_binScale == BinScale::Log);
Chris@1117 1153 sv_samplerate_t sr = m_model->getSampleRate();
Chris@1117 1154
Chris@1117 1155 double freq = (bin * sr) / getFFTSize();
Chris@1117 1156
Chris@1117 1157 double y = v->getYForFrequency(freq, minf, maxf, logarithmic);
Chris@1117 1158
Chris@1117 1159 return y;
Chris@1117 1160 }
Chris@1117 1161
Chris@1117 1162 double
Chris@1117 1163 SpectrogramLayer::getBinForY(const LayerGeometryProvider *v, double y) const
Chris@1117 1164 {
Chris@907 1165 sv_samplerate_t sr = m_model->getSampleRate();
Chris@905 1166 double minf = getEffectiveMinFrequency();
Chris@905 1167 double maxf = getEffectiveMaxFrequency();
Chris@0 1168
Chris@1103 1169 bool logarithmic = (m_binScale == BinScale::Log);
Chris@38 1170
Chris@1117 1171 double freq = v->getFrequencyForY(y, minf, maxf, logarithmic);
Chris@1117 1172
Chris@1117 1173 // Now map on to ("proportion of") actual bins
Chris@1117 1174 double bin = (freq * getFFTSize()) / sr;
Chris@1117 1175
Chris@1117 1176 return bin;
Chris@1085 1177 }
Chris@1085 1178
Chris@0 1179 bool
Chris@918 1180 SpectrogramLayer::getXBinRange(LayerGeometryProvider *v, int x, double &s0, double &s1) const
Chris@0 1181 {
Chris@907 1182 sv_frame_t modelStart = m_model->getStartFrame();
Chris@907 1183 sv_frame_t modelEnd = m_model->getEndFrame();
Chris@0 1184
Chris@0 1185 // Each pixel column covers an exact range of sample frames:
Chris@907 1186 sv_frame_t f0 = v->getFrameForX(x) - modelStart;
Chris@907 1187 sv_frame_t f1 = v->getFrameForX(x + 1) - modelStart - 1;
Chris@20 1188
Chris@41 1189 if (f1 < int(modelStart) || f0 > int(modelEnd)) {
Chris@1234 1190 return false;
Chris@41 1191 }
Chris@20 1192
Chris@0 1193 // And that range may be drawn from a possibly non-integral
Chris@0 1194 // range of spectrogram windows:
Chris@0 1195
Chris@805 1196 int windowIncrement = getWindowIncrement();
Chris@905 1197 s0 = double(f0) / windowIncrement;
Chris@905 1198 s1 = double(f1) / windowIncrement;
Chris@0 1199
Chris@0 1200 return true;
Chris@0 1201 }
Chris@0 1202
Chris@0 1203 bool
Chris@918 1204 SpectrogramLayer::getXBinSourceRange(LayerGeometryProvider *v, int x, RealTime &min, RealTime &max) const
Chris@0 1205 {
Chris@905 1206 double s0 = 0, s1 = 0;
Chris@44 1207 if (!getXBinRange(v, x, s0, s1)) return false;
Chris@0 1208
Chris@0 1209 int s0i = int(s0 + 0.001);
Chris@0 1210 int s1i = int(s1);
Chris@0 1211
Chris@0 1212 int windowIncrement = getWindowIncrement();
Chris@0 1213 int w0 = s0i * windowIncrement - (m_windowSize - windowIncrement)/2;
Chris@0 1214 int w1 = s1i * windowIncrement + windowIncrement +
Chris@1234 1215 (m_windowSize - windowIncrement)/2 - 1;
Chris@0 1216
Chris@0 1217 min = RealTime::frame2RealTime(w0, m_model->getSampleRate());
Chris@0 1218 max = RealTime::frame2RealTime(w1, m_model->getSampleRate());
Chris@0 1219 return true;
Chris@0 1220 }
Chris@0 1221
Chris@0 1222 bool
Chris@918 1223 SpectrogramLayer::getYBinSourceRange(LayerGeometryProvider *v, int y, double &freqMin, double &freqMax)
Chris@0 1224 const
Chris@0 1225 {
Chris@905 1226 double q0 = 0, q1 = 0;
Chris@44 1227 if (!getYBinRange(v, y, q0, q1)) return false;
Chris@0 1228
Chris@0 1229 int q0i = int(q0 + 0.001);
Chris@0 1230 int q1i = int(q1);
Chris@0 1231
Chris@907 1232 sv_samplerate_t sr = m_model->getSampleRate();
Chris@0 1233
Chris@0 1234 for (int q = q0i; q <= q1i; ++q) {
Chris@1234 1235 if (q == q0i) freqMin = (sr * q) / getFFTSize();
Chris@1234 1236 if (q == q1i) freqMax = (sr * (q+1)) / getFFTSize();
Chris@0 1237 }
Chris@0 1238 return true;
Chris@0 1239 }
Chris@35 1240
Chris@35 1241 bool
Chris@918 1242 SpectrogramLayer::getAdjustedYBinSourceRange(LayerGeometryProvider *v, int x, int y,
Chris@1234 1243 double &freqMin, double &freqMax,
Chris@1234 1244 double &adjFreqMin, double &adjFreqMax)
Chris@35 1245 const
Chris@35 1246 {
Chris@277 1247 if (!m_model || !m_model->isOK() || !m_model->isReady()) {
Chris@1234 1248 return false;
Chris@277 1249 }
Chris@277 1250
Chris@1088 1251 FFTModel *fft = getFFTModel();
Chris@114 1252 if (!fft) return false;
Chris@110 1253
Chris@905 1254 double s0 = 0, s1 = 0;
Chris@44 1255 if (!getXBinRange(v, x, s0, s1)) return false;
Chris@35 1256
Chris@905 1257 double q0 = 0, q1 = 0;
Chris@44 1258 if (!getYBinRange(v, y, q0, q1)) return false;
Chris@35 1259
Chris@35 1260 int s0i = int(s0 + 0.001);
Chris@35 1261 int s1i = int(s1);
Chris@35 1262
Chris@35 1263 int q0i = int(q0 + 0.001);
Chris@35 1264 int q1i = int(q1);
Chris@35 1265
Chris@907 1266 sv_samplerate_t sr = m_model->getSampleRate();
Chris@35 1267
Chris@35 1268 bool haveAdj = false;
Chris@35 1269
Chris@1103 1270 bool peaksOnly = (m_binDisplay == BinDisplay::PeakBins ||
Chris@1234 1271 m_binDisplay == BinDisplay::PeakFrequencies);
Chris@37 1272
Chris@35 1273 for (int q = q0i; q <= q1i; ++q) {
Chris@35 1274
Chris@1234 1275 for (int s = s0i; s <= s1i; ++s) {
Chris@1234 1276
Chris@1234 1277 double binfreq = (double(sr) * q) / m_windowSize;
Chris@1234 1278 if (q == q0i) freqMin = binfreq;
Chris@1234 1279 if (q == q1i) freqMax = binfreq;
Chris@1234 1280
Chris@1234 1281 if (peaksOnly && !fft->isLocalPeak(s, q)) continue;
Chris@1234 1282
Chris@1234 1283 if (!fft->isOverThreshold
Chris@1087 1284 (s, q, float(m_threshold * double(getFFTSize())/2.0))) {
Chris@1086 1285 continue;
Chris@1086 1286 }
Chris@907 1287
Chris@907 1288 double freq = binfreq;
Chris@1234 1289
Chris@1234 1290 if (s < int(fft->getWidth()) - 1) {
Chris@38 1291
Chris@277 1292 fft->estimateStableFrequency(s, q, freq);
Chris@1234 1293
Chris@1234 1294 if (!haveAdj || freq < adjFreqMin) adjFreqMin = freq;
Chris@1234 1295 if (!haveAdj || freq > adjFreqMax) adjFreqMax = freq;
Chris@1234 1296
Chris@1234 1297 haveAdj = true;
Chris@1234 1298 }
Chris@1234 1299 }
Chris@35 1300 }
Chris@35 1301
Chris@35 1302 if (!haveAdj) {
Chris@1234 1303 adjFreqMin = adjFreqMax = 0.0;
Chris@35 1304 }
Chris@35 1305
Chris@35 1306 return haveAdj;
Chris@35 1307 }
Chris@0 1308
Chris@0 1309 bool
Chris@918 1310 SpectrogramLayer::getXYBinSourceRange(LayerGeometryProvider *v, int x, int y,
Chris@1234 1311 double &min, double &max,
Chris@1234 1312 double &phaseMin, double &phaseMax) const
Chris@0 1313 {
Chris@277 1314 if (!m_model || !m_model->isOK() || !m_model->isReady()) {
Chris@1234 1315 return false;
Chris@277 1316 }
Chris@277 1317
Chris@905 1318 double q0 = 0, q1 = 0;
Chris@44 1319 if (!getYBinRange(v, y, q0, q1)) return false;
Chris@0 1320
Chris@905 1321 double s0 = 0, s1 = 0;
Chris@44 1322 if (!getXBinRange(v, x, s0, s1)) return false;
Chris@0 1323
Chris@0 1324 int q0i = int(q0 + 0.001);
Chris@0 1325 int q1i = int(q1);
Chris@0 1326
Chris@0 1327 int s0i = int(s0 + 0.001);
Chris@0 1328 int s1i = int(s1);
Chris@0 1329
Chris@37 1330 bool rv = false;
Chris@37 1331
Chris@1088 1332 FFTModel *fft = getFFTModel();
Chris@0 1333
Chris@114 1334 if (fft) {
Chris@114 1335
Chris@114 1336 int cw = fft->getWidth();
Chris@114 1337 int ch = fft->getHeight();
Chris@0 1338
Chris@110 1339 min = 0.0;
Chris@110 1340 max = 0.0;
Chris@110 1341 phaseMin = 0.0;
Chris@110 1342 phaseMax = 0.0;
Chris@110 1343 bool have = false;
Chris@0 1344
Chris@110 1345 for (int q = q0i; q <= q1i; ++q) {
Chris@110 1346 for (int s = s0i; s <= s1i; ++s) {
Chris@110 1347 if (s >= 0 && q >= 0 && s < cw && q < ch) {
Chris@117 1348
Chris@905 1349 double value;
Chris@38 1350
Chris@114 1351 value = fft->getPhaseAt(s, q);
Chris@110 1352 if (!have || value < phaseMin) { phaseMin = value; }
Chris@110 1353 if (!have || value > phaseMax) { phaseMax = value; }
Chris@91 1354
Chris@1087 1355 value = fft->getMagnitudeAt(s, q) / (getFFTSize()/2.0);
Chris@110 1356 if (!have || value < min) { min = value; }
Chris@110 1357 if (!have || value > max) { max = value; }
Chris@110 1358
Chris@110 1359 have = true;
Chris@1234 1360 }
Chris@110 1361 }
Chris@110 1362 }
Chris@110 1363
Chris@110 1364 if (have) {
Chris@110 1365 rv = true;
Chris@110 1366 }
Chris@0 1367 }
Chris@0 1368
Chris@37 1369 return rv;
Chris@0 1370 }
Chris@1234 1371
Chris@1211 1372 void
Chris@1211 1373 SpectrogramLayer::recreateFFTModel()
Chris@114 1374 {
Chris@1242 1375 SVDEBUG << "SpectrogramLayer::recreateFFTModel called" << endl;
Chris@1211 1376
Chris@1211 1377 if (!m_model || !m_model->isOK()) {
Chris@1408 1378 emit sliceableModelReplaced(m_fftModel, nullptr);
Chris@1242 1379 deleteDerivedModels();
Chris@1211 1380 return;
Chris@114 1381 }
Chris@1211 1382
Chris@1242 1383 if (m_fftModel) m_fftModel->aboutToDelete();
Chris@1242 1384
Chris@1242 1385 if (m_peakCache) m_peakCache->aboutToDelete();
Chris@1211 1386 delete m_peakCache;
Chris@1408 1387 m_peakCache = nullptr;
Chris@1212 1388
Chris@1242 1389 if (m_wholeCache) m_wholeCache->aboutToDelete();
Chris@1212 1390 delete m_wholeCache;
Chris@1408 1391 m_wholeCache = nullptr;
Chris@1211 1392
Chris@1242 1393 FFTModel *newModel = new FFTModel(m_model,
Chris@1242 1394 m_channel,
Chris@1242 1395 m_windowType,
Chris@1242 1396 m_windowSize,
Chris@1242 1397 getWindowIncrement(),
Chris@1242 1398 getFFTSize());
Chris@1242 1399
Chris@1242 1400 if (!newModel->isOK()) {
Chris@1088 1401 QMessageBox::critical
Chris@1408 1402 (nullptr, tr("FFT cache failed"),
Chris@1088 1403 tr("Failed to create the FFT model for this spectrogram.\n"
Chris@1088 1404 "There may be insufficient memory or disc space to continue."));
Chris@1242 1405 delete newModel;
Chris@1088 1406 delete m_fftModel;
Chris@1408 1407 m_fftModel = nullptr;
Chris@1211 1408 return;
Chris@114 1409 }
Chris@1212 1410
Chris@1242 1411 FFTModel *oldModel = m_fftModel;
Chris@1242 1412 m_fftModel = newModel;
Chris@1242 1413
Chris@1450 1414 bool createWholeCache = false;
Chris@1450 1415 checkCacheSpace(&m_peakCacheDivisor, &createWholeCache);
Chris@1450 1416
Chris@1450 1417 if (createWholeCache) {
Chris@1212 1418 m_wholeCache = new Dense3DModelPeakCache(m_fftModel, 1);
Chris@1212 1419 m_peakCache = new Dense3DModelPeakCache(m_wholeCache, m_peakCacheDivisor);
Chris@1212 1420 } else {
Chris@1212 1421 m_peakCache = new Dense3DModelPeakCache(m_fftModel, m_peakCacheDivisor);
Chris@1212 1422 }
Chris@1211 1423
Chris@1211 1424 emit sliceableModelReplaced(oldModel, m_fftModel);
Chris@1211 1425 delete oldModel;
Chris@484 1426 }
Chris@484 1427
Chris@1450 1428 void
Chris@1450 1429 SpectrogramLayer::checkCacheSpace(int *suggestedPeakDivisor,
Chris@1450 1430 bool *createWholeCache) const
Chris@1212 1431 {
Chris@1450 1432 *suggestedPeakDivisor = 8;
Chris@1450 1433 *createWholeCache = false;
Chris@1450 1434
Chris@1450 1435 if (!m_fftModel) return;
Chris@1212 1436
Chris@1212 1437 size_t sz =
Chris@1212 1438 size_t(m_fftModel->getWidth()) *
Chris@1212 1439 size_t(m_fftModel->getHeight()) *
Chris@1212 1440 sizeof(float);
Chris@1212 1441
Chris@1212 1442 try {
Chris@1212 1443 SVDEBUG << "Requesting advice from StorageAdviser on whether to create whole-model cache" << endl;
Chris@1450 1444 // The lower amount here is the amount required for the
Chris@1450 1445 // slightly higher-resolution version of the peak cache
Chris@1450 1446 // without a whole-model cache; the higher amount is that for
Chris@1450 1447 // the whole-model cache. The factors of 1024 are because
Chris@1450 1448 // StorageAdviser rather stupidly works in kilobytes
Chris@1212 1449 StorageAdviser::Recommendation recommendation =
Chris@1212 1450 StorageAdviser::recommend
Chris@1212 1451 (StorageAdviser::Criteria(StorageAdviser::SpeedCritical |
Chris@1212 1452 StorageAdviser::PrecisionCritical |
Chris@1212 1453 StorageAdviser::FrequentLookupLikely),
Chris@1450 1454 (sz / 8) / 1024, sz / 1024);
Chris@1450 1455 if (recommendation & StorageAdviser::UseDisc) {
Chris@1212 1456 SVDEBUG << "Seems inadvisable to create whole-model cache" << endl;
Chris@1450 1457 } else if (recommendation & StorageAdviser::ConserveSpace) {
Chris@1450 1458 SVDEBUG << "Seems inadvisable to create whole-model cache but acceptable to use the slightly higher-resolution peak cache" << endl;
Chris@1450 1459 *suggestedPeakDivisor = 4;
Chris@1450 1460 } else {
Chris@1212 1461 SVDEBUG << "Seems fine to create whole-model cache" << endl;
Chris@1450 1462 *createWholeCache = true;
Chris@1212 1463 }
Chris@1212 1464 } catch (const InsufficientDiscSpace &) {
Chris@1212 1465 SVDEBUG << "Seems like a terrible idea to create whole-model cache" << endl;
Chris@1212 1466 }
Chris@1212 1467 }
Chris@1212 1468
Chris@193 1469 const Model *
Chris@193 1470 SpectrogramLayer::getSliceableModel() const
Chris@193 1471 {
Chris@1088 1472 return m_fftModel;
Chris@193 1473 }
Chris@193 1474
Chris@114 1475 void
Chris@119 1476 SpectrogramLayer::invalidateMagnitudes()
Chris@119 1477 {
Chris@1044 1478 #ifdef DEBUG_SPECTROGRAM
Chris@1044 1479 cerr << "SpectrogramLayer::invalidateMagnitudes called" << endl;
Chris@1044 1480 #endif
Chris@119 1481 m_viewMags.clear();
Chris@119 1482 }
Chris@1134 1483
Chris@119 1484 void
Chris@389 1485 SpectrogramLayer::setSynchronousPainting(bool synchronous)
Chris@389 1486 {
Chris@389 1487 m_synchronous = synchronous;
Chris@389 1488 }
Chris@389 1489
Chris@1089 1490 Colour3DPlotRenderer *
Chris@1089 1491 SpectrogramLayer::getRenderer(LayerGeometryProvider *v) const
Chris@1089 1492 {
Chris@1136 1493 int viewId = v->getId();
Chris@1136 1494
Chris@1136 1495 if (m_renderers.find(viewId) == m_renderers.end()) {
Chris@1089 1496
Chris@1089 1497 Colour3DPlotRenderer::Sources sources;
Chris@1089 1498 sources.verticalBinLayer = this;
Chris@1090 1499 sources.fft = getFFTModel();
Chris@1090 1500 sources.source = sources.fft;
Chris@1212 1501 if (m_peakCache) sources.peakCaches.push_back(m_peakCache);
Chris@1212 1502 if (m_wholeCache) sources.peakCaches.push_back(m_wholeCache);
Chris@1089 1503
Chris@1092 1504 ColourScale::Parameters cparams;
Chris@1092 1505 cparams.colourMap = m_colourMap;
Chris@1137 1506 cparams.scaleType = m_colourScale;
Chris@1137 1507 cparams.multiple = m_colourScaleMultiple;
Chris@1129 1508
Chris@1129 1509 if (m_colourScale != ColourScaleType::Phase) {
Chris@1129 1510 cparams.gain = m_gain;
Chris@1129 1511 cparams.threshold = m_threshold;
Chris@1129 1512 }
Chris@1093 1513
Chris@1234 1514 double minValue = 0.0f;
Chris@1234 1515 double maxValue = 1.0f;
Chris@1136 1516
Chris@1136 1517 if (m_normalizeVisibleArea && m_viewMags[viewId].isSet()) {
Chris@1136 1518 minValue = m_viewMags[viewId].getMin();
Chris@1136 1519 maxValue = m_viewMags[viewId].getMax();
Chris@1136 1520 } else if (m_colourScale == ColourScaleType::Linear &&
Chris@1136 1521 m_normalization == ColumnNormalization::None) {
Chris@1136 1522 maxValue = 0.1f;
Chris@1093 1523 }
Chris@1129 1524
Chris@1136 1525 if (maxValue <= minValue) {
Chris@1136 1526 maxValue = minValue + 0.1f;
Chris@1136 1527 }
Chris@1136 1528 if (maxValue <= m_threshold) {
Chris@1136 1529 maxValue = m_threshold + 0.1f;
Chris@1136 1530 }
Chris@1136 1531
Chris@1136 1532 cparams.minValue = minValue;
Chris@1136 1533 cparams.maxValue = maxValue;
Chris@1136 1534
Chris@1234 1535 m_lastRenderedMags[viewId] = MagnitudeRange(float(minValue),
Chris@1234 1536 float(maxValue));
Chris@1136 1537
Chris@1089 1538 Colour3DPlotRenderer::Parameters params;
Chris@1092 1539 params.colourScale = ColourScale(cparams);
Chris@1089 1540 params.normalization = m_normalization;
Chris@1093 1541 params.binDisplay = m_binDisplay;
Chris@1093 1542 params.binScale = m_binScale;
Chris@1141 1543 params.alwaysOpaque = true;
Chris@1093 1544 params.invertVertical = false;
Chris@1125 1545 params.scaleFactor = 1.0;
Chris@1112 1546 params.colourRotation = m_colourRotation;
Chris@1093 1547
Chris@1145 1548 if (m_colourScale != ColourScaleType::Phase &&
Chris@1145 1549 m_normalization != ColumnNormalization::Hybrid) {
Chris@1403 1550 params.scaleFactor *= 2.f / float(getWindowSize());
Chris@1125 1551 }
Chris@1125 1552
Chris@1093 1553 Preferences::SpectrogramSmoothing smoothing =
Chris@1093 1554 Preferences::getInstance()->getSpectrogramSmoothing();
Chris@1093 1555 params.interpolate =
Chris@1399 1556 (smoothing != Preferences::NoSpectrogramSmoothing);
Chris@1089 1557
Chris@1234 1558 m_renderers[viewId] = new Colour3DPlotRenderer(sources, params);
Chris@1239 1559
Chris@1239 1560 m_crosshairColour =
Chris@1362 1561 ColourMapper(m_colourMap, m_colourInverted, 1.f, 255.f)
Chris@1362 1562 .getContrastingColour();
Chris@1089 1563 }
Chris@1089 1564
Chris@1234 1565 return m_renderers[viewId];
Chris@1089 1566 }
Chris@1089 1567
Chris@1089 1568 void
Chris@1106 1569 SpectrogramLayer::paintWithRenderer(LayerGeometryProvider *v, QPainter &paint, QRect rect) const
Chris@1106 1570 {
Chris@1121 1571 Colour3DPlotRenderer *renderer = getRenderer(v);
Chris@1121 1572
Chris@1121 1573 Colour3DPlotRenderer::RenderResult result;
Chris@1122 1574 MagnitudeRange magRange;
Chris@1122 1575 int viewId = v->getId();
Chris@1122 1576
Chris@1136 1577 bool continuingPaint = !renderer->geometryChanged(v);
Chris@1136 1578
Chris@1136 1579 if (continuingPaint) {
Chris@1122 1580 magRange = m_viewMags[viewId];
Chris@1122 1581 }
Chris@1106 1582
Chris@1106 1583 if (m_synchronous) {
Chris@1121 1584
Chris@1121 1585 result = renderer->render(v, paint, rect);
Chris@1121 1586
Chris@1121 1587 } else {
Chris@1121 1588
Chris@1121 1589 result = renderer->renderTimeConstrained(v, paint, rect);
Chris@1121 1590
Chris@1143 1591 #ifdef DEBUG_SPECTROGRAM_REPAINT
Chris@1122 1592 cerr << "rect width from this paint: " << result.rendered.width()
Chris@1122 1593 << ", mag range in this paint: " << result.range.getMin() << " -> "
Chris@1121 1594 << result.range.getMax() << endl;
Chris@1143 1595 #endif
Chris@1121 1596
Chris@1121 1597 QRect uncached = renderer->getLargestUncachedRect(v);
Chris@1121 1598 if (uncached.width() > 0) {
Chris@1121 1599 v->updatePaintRect(uncached);
Chris@1121 1600 }
Chris@1106 1601 }
Chris@1106 1602
Chris@1122 1603 magRange.sample(result.range);
Chris@1122 1604
Chris@1122 1605 if (magRange.isSet()) {
Chris@1136 1606 if (m_viewMags[viewId] != magRange) {
Chris@1122 1607 m_viewMags[viewId] = magRange;
Chris@1143 1608 #ifdef DEBUG_SPECTROGRAM_REPAINT
Chris@1136 1609 cerr << "mag range in this view has changed: "
Chris@1136 1610 << magRange.getMin() << " -> " << magRange.getMax() << endl;
Chris@1143 1611 #endif
Chris@1122 1612 }
Chris@1122 1613 }
Chris@1136 1614
Chris@1136 1615 if (!continuingPaint && m_normalizeVisibleArea &&
Chris@1136 1616 m_viewMags[viewId] != m_lastRenderedMags[viewId]) {
Chris@1143 1617 #ifdef DEBUG_SPECTROGRAM_REPAINT
Chris@1136 1618 cerr << "mag range has changed from last rendered range: re-rendering"
Chris@1136 1619 << endl;
Chris@1143 1620 #endif
Chris@1136 1621 delete m_renderers[viewId];
Chris@1136 1622 m_renderers.erase(viewId);
Chris@1136 1623 v->updatePaintRect(v->getPaintRect());
Chris@1136 1624 }
Chris@1106 1625 }
Chris@1106 1626
Chris@1106 1627 void
Chris@916 1628 SpectrogramLayer::paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const
Chris@0 1629 {
Chris@334 1630 Profiler profiler("SpectrogramLayer::paint", false);
Chris@334 1631
Chris@0 1632 #ifdef DEBUG_SPECTROGRAM_REPAINT
Chris@1026 1633 cerr << "SpectrogramLayer::paint() entering: m_model is " << m_model << ", zoom level is " << v->getZoomLevel() << endl;
Chris@95 1634
Chris@1026 1635 cerr << "SpectrogramLayer::paint(): rect is " << rect.x() << "," << rect.y() << " " << rect.width() << "x" << rect.height() << endl;
Chris@0 1636 #endif
Chris@95 1637
Chris@0 1638 if (!m_model || !m_model->isOK() || !m_model->isReady()) {
Chris@1234 1639 return;
Chris@29 1640 }
Chris@29 1641
Chris@1106 1642 paintWithRenderer(v, paint, rect);
Chris@1140 1643
Chris@1140 1644 illuminateLocalFeatures(v, paint);
Chris@480 1645 }
Chris@477 1646
Chris@121 1647 void
Chris@918 1648 SpectrogramLayer::illuminateLocalFeatures(LayerGeometryProvider *v, QPainter &paint) const
Chris@121 1649 {
Chris@382 1650 Profiler profiler("SpectrogramLayer::illuminateLocalFeatures");
Chris@382 1651
Chris@121 1652 QPoint localPos;
Chris@121 1653 if (!v->shouldIlluminateLocalFeatures(this, localPos) || !m_model) {
Chris@121 1654 return;
Chris@121 1655 }
Chris@121 1656
Chris@1143 1657 #ifdef DEBUG_SPECTROGRAM_REPAINT
Chris@1134 1658 cerr << "SpectrogramLayer: illuminateLocalFeatures("
Chris@1134 1659 << localPos.x() << "," << localPos.y() << ")" << endl;
Chris@1143 1660 #endif
Chris@121 1661
Chris@905 1662 double s0, s1;
Chris@905 1663 double f0, f1;
Chris@121 1664
Chris@121 1665 if (getXBinRange(v, localPos.x(), s0, s1) &&
Chris@121 1666 getYBinSourceRange(v, localPos.y(), f0, f1)) {
Chris@121 1667
Chris@121 1668 int s0i = int(s0 + 0.001);
Chris@121 1669 int s1i = int(s1);
Chris@121 1670
Chris@121 1671 int x0 = v->getXForFrame(s0i * getWindowIncrement());
Chris@121 1672 int x1 = v->getXForFrame((s1i + 1) * getWindowIncrement());
Chris@121 1673
Chris@248 1674 int y1 = int(getYForFrequency(v, f1));
Chris@248 1675 int y0 = int(getYForFrequency(v, f0));
Chris@121 1676
Chris@1143 1677 #ifdef DEBUG_SPECTROGRAM_REPAINT
Chris@1134 1678 cerr << "SpectrogramLayer: illuminate "
Chris@1134 1679 << x0 << "," << y1 << " -> " << x1 << "," << y0 << endl;
Chris@1143 1680 #endif
Chris@121 1681
Chris@287 1682 paint.setPen(v->getForeground());
Chris@133 1683
Chris@133 1684 //!!! should we be using paintCrosshairs for this?
Chris@133 1685
Chris@121 1686 paint.drawRect(x0, y1, x1 - x0 + 1, y0 - y1 + 1);
Chris@121 1687 }
Chris@121 1688 }
Chris@121 1689
Chris@905 1690 double
Chris@918 1691 SpectrogramLayer::getYForFrequency(const LayerGeometryProvider *v, double frequency) const
Chris@42 1692 {
Chris@44 1693 return v->getYForFrequency(frequency,
Chris@1234 1694 getEffectiveMinFrequency(),
Chris@1234 1695 getEffectiveMaxFrequency(),
Chris@1234 1696 m_binScale == BinScale::Log);
Chris@42 1697 }
Chris@42 1698
Chris@905 1699 double
Chris@918 1700 SpectrogramLayer::getFrequencyForY(const LayerGeometryProvider *v, int y) const
Chris@42 1701 {
Chris@44 1702 return v->getFrequencyForY(y,
Chris@1234 1703 getEffectiveMinFrequency(),
Chris@1234 1704 getEffectiveMaxFrequency(),
Chris@1234 1705 m_binScale == BinScale::Log);
Chris@42 1706 }
Chris@42 1707
Chris@0 1708 int
Chris@1090 1709 SpectrogramLayer::getCompletion(LayerGeometryProvider *) const
Chris@0 1710 {
Chris@1088 1711 if (!m_fftModel) return 100;
Chris@1088 1712 int completion = m_fftModel->getCompletion();
Chris@224 1713 #ifdef DEBUG_SPECTROGRAM_REPAINT
Chris@985 1714 cerr << "SpectrogramLayer::getCompletion: completion = " << completion << endl;
Chris@224 1715 #endif
Chris@0 1716 return completion;
Chris@0 1717 }
Chris@0 1718
Chris@583 1719 QString
Chris@1090 1720 SpectrogramLayer::getError(LayerGeometryProvider *) const
Chris@583 1721 {
Chris@1088 1722 if (!m_fftModel) return "";
Chris@1088 1723 return m_fftModel->getError();
Chris@583 1724 }
Chris@583 1725
Chris@28 1726 bool
Chris@905 1727 SpectrogramLayer::getValueExtents(double &min, double &max,
Chris@101 1728 bool &logarithmic, QString &unit) const
Chris@79 1729 {
Chris@133 1730 if (!m_model) return false;
Chris@133 1731
Chris@907 1732 sv_samplerate_t sr = m_model->getSampleRate();
Chris@1087 1733 min = double(sr) / getFFTSize();
Chris@905 1734 max = double(sr) / 2;
Chris@133 1735
Chris@1103 1736 logarithmic = (m_binScale == BinScale::Log);
Chris@79 1737 unit = "Hz";
Chris@79 1738 return true;
Chris@79 1739 }
Chris@79 1740
Chris@79 1741 bool
Chris@905 1742 SpectrogramLayer::getDisplayExtents(double &min, double &max) const
Chris@101 1743 {
Chris@101 1744 min = getEffectiveMinFrequency();
Chris@101 1745 max = getEffectiveMaxFrequency();
Chris@253 1746
Chris@587 1747 // SVDEBUG << "SpectrogramLayer::getDisplayExtents: " << min << "->" << max << endl;
Chris@101 1748 return true;
Chris@101 1749 }
Chris@101 1750
Chris@101 1751 bool
Chris@905 1752 SpectrogramLayer::setDisplayExtents(double min, double max)
Chris@120 1753 {
Chris@120 1754 if (!m_model) return false;
Chris@187 1755
Chris@587 1756 // SVDEBUG << "SpectrogramLayer::setDisplayExtents: " << min << "->" << max << endl;
Chris@187 1757
Chris@120 1758 if (min < 0) min = 0;
Chris@907 1759 if (max > m_model->getSampleRate()/2.0) max = m_model->getSampleRate()/2.0;
Chris@120 1760
Chris@907 1761 int minf = int(lrint(min));
Chris@907 1762 int maxf = int(lrint(max));
Chris@120 1763
Chris@120 1764 if (m_minFrequency == minf && m_maxFrequency == maxf) return true;
Chris@120 1765
Chris@1106 1766 invalidateRenderers();
Chris@120 1767 invalidateMagnitudes();
Chris@120 1768
Chris@120 1769 m_minFrequency = minf;
Chris@120 1770 m_maxFrequency = maxf;
Chris@120 1771
Chris@120 1772 emit layerParametersChanged();
Chris@120 1773
Chris@133 1774 int vs = getCurrentVerticalZoomStep();
Chris@133 1775 if (vs != m_lastEmittedZoomStep) {
Chris@133 1776 emit verticalZoomChanged();
Chris@133 1777 m_lastEmittedZoomStep = vs;
Chris@133 1778 }
Chris@133 1779
Chris@120 1780 return true;
Chris@120 1781 }
Chris@120 1782
Chris@120 1783 bool
Chris@918 1784 SpectrogramLayer::getYScaleValue(const LayerGeometryProvider *v, int y,
Chris@905 1785 double &value, QString &unit) const
Chris@261 1786 {
Chris@261 1787 value = getFrequencyForY(v, y);
Chris@261 1788 unit = "Hz";
Chris@261 1789 return true;
Chris@261 1790 }
Chris@261 1791
Chris@261 1792 bool
Chris@918 1793 SpectrogramLayer::snapToFeatureFrame(LayerGeometryProvider *,
Chris@907 1794 sv_frame_t &frame,
Chris@1234 1795 int &resolution,
Chris@1234 1796 SnapType snap) const
Chris@13 1797 {
Chris@13 1798 resolution = getWindowIncrement();
Chris@907 1799 sv_frame_t left = (frame / resolution) * resolution;
Chris@907 1800 sv_frame_t right = left + resolution;
Chris@28 1801
Chris@28 1802 switch (snap) {
Chris@28 1803 case SnapLeft: frame = left; break;
Chris@28 1804 case SnapRight: frame = right; break;
Chris@28 1805 case SnapNeighbouring:
Chris@1234 1806 if (frame - left > right - frame) frame = right;
Chris@1234 1807 else frame = left;
Chris@1234 1808 break;
Chris@28 1809 }
Chris@28 1810
Chris@28 1811 return true;
Chris@28 1812 }
Chris@13 1813
Chris@283 1814 void
Chris@1139 1815 SpectrogramLayer::measureDoubleClick(LayerGeometryProvider *v, QMouseEvent *e)
Chris@283 1816 {
Chris@1139 1817 const Colour3DPlotRenderer *renderer = getRenderer(v);
Chris@1139 1818 if (!renderer) return;
Chris@1139 1819
Chris@1139 1820 QRect rect = renderer->findSimilarRegionExtents(e->pos());
Chris@283 1821 if (rect.isValid()) {
Chris@283 1822 MeasureRect mr;
Chris@283 1823 setMeasureRectFromPixrect(v, mr, rect);
Chris@283 1824 CommandHistory::getInstance()->addCommand
Chris@283 1825 (new AddMeasurementRectCommand(this, mr));
Chris@283 1826 }
Chris@283 1827 }
Chris@283 1828
Chris@77 1829 bool
Chris@918 1830 SpectrogramLayer::getCrosshairExtents(LayerGeometryProvider *v, QPainter &paint,
Chris@77 1831 QPoint cursorPos,
Chris@1025 1832 vector<QRect> &extents) const
Chris@77 1833 {
Chris@918 1834 QRect vertical(cursorPos.x() - 12, 0, 12, v->getPaintHeight());
Chris@77 1835 extents.push_back(vertical);
Chris@77 1836
Chris@77 1837 QRect horizontal(0, cursorPos.y(), cursorPos.x(), 1);
Chris@77 1838 extents.push_back(horizontal);
Chris@77 1839
Chris@608 1840 int sw = getVerticalScaleWidth(v, m_haveDetailedScale, paint);
Chris@264 1841
Chris@280 1842 QRect freq(sw, cursorPos.y() - paint.fontMetrics().ascent() - 2,
Chris@280 1843 paint.fontMetrics().width("123456 Hz") + 2,
Chris@280 1844 paint.fontMetrics().height());
Chris@280 1845 extents.push_back(freq);
Chris@264 1846
Chris@279 1847 QRect pitch(sw, cursorPos.y() + 2,
Chris@279 1848 paint.fontMetrics().width("C#10+50c") + 2,
Chris@279 1849 paint.fontMetrics().height());
Chris@279 1850 extents.push_back(pitch);
Chris@279 1851
Chris@280 1852 QRect rt(cursorPos.x(),
Chris@918 1853 v->getPaintHeight() - paint.fontMetrics().height() - 2,
Chris@280 1854 paint.fontMetrics().width("1234.567 s"),
Chris@280 1855 paint.fontMetrics().height());
Chris@280 1856 extents.push_back(rt);
Chris@280 1857
Chris@280 1858 int w(paint.fontMetrics().width("1234567890") + 2);
Chris@280 1859 QRect frame(cursorPos.x() - w - 2,
Chris@918 1860 v->getPaintHeight() - paint.fontMetrics().height() - 2,
Chris@280 1861 w,
Chris@280 1862 paint.fontMetrics().height());
Chris@280 1863 extents.push_back(frame);
Chris@280 1864
Chris@77 1865 return true;
Chris@77 1866 }
Chris@77 1867
Chris@77 1868 void
Chris@918 1869 SpectrogramLayer::paintCrosshairs(LayerGeometryProvider *v, QPainter &paint,
Chris@77 1870 QPoint cursorPos) const
Chris@77 1871 {
Chris@77 1872 paint.save();
Chris@1437 1873
Chris@608 1874 int sw = getVerticalScaleWidth(v, m_haveDetailedScale, paint);
Chris@283 1875
Chris@282 1876 QFont fn = paint.font();
Chris@282 1877 if (fn.pointSize() > 8) {
Chris@282 1878 fn.setPointSize(fn.pointSize() - 1);
Chris@282 1879 paint.setFont(fn);
Chris@282 1880 }
Chris@77 1881 paint.setPen(m_crosshairColour);
Chris@77 1882
Chris@77 1883 paint.drawLine(0, cursorPos.y(), cursorPos.x() - 1, cursorPos.y());
Chris@918 1884 paint.drawLine(cursorPos.x(), 0, cursorPos.x(), v->getPaintHeight());
Chris@77 1885
Chris@905 1886 double fundamental = getFrequencyForY(v, cursorPos.y());
Chris@77 1887
Chris@1078 1888 PaintAssistant::drawVisibleText(v, paint,
Chris@278 1889 sw + 2,
Chris@278 1890 cursorPos.y() - 2,
Chris@278 1891 QString("%1 Hz").arg(fundamental),
Chris@1078 1892 PaintAssistant::OutlinedText);
Chris@278 1893
Chris@279 1894 if (Pitch::isFrequencyInMidiRange(fundamental)) {
Chris@279 1895 QString pitchLabel = Pitch::getPitchLabelForFrequency(fundamental);
Chris@1078 1896 PaintAssistant::drawVisibleText(v, paint,
Chris@279 1897 sw + 2,
Chris@279 1898 cursorPos.y() + paint.fontMetrics().ascent() + 2,
Chris@279 1899 pitchLabel,
Chris@1078 1900 PaintAssistant::OutlinedText);
Chris@279 1901 }
Chris@279 1902
Chris@907 1903 sv_frame_t frame = v->getFrameForX(cursorPos.x());
Chris@279 1904 RealTime rt = RealTime::frame2RealTime(frame, m_model->getSampleRate());
Chris@280 1905 QString rtLabel = QString("%1 s").arg(rt.toText(true).c_str());
Chris@280 1906 QString frameLabel = QString("%1").arg(frame);
Chris@1078 1907 PaintAssistant::drawVisibleText(v, paint,
Chris@280 1908 cursorPos.x() - paint.fontMetrics().width(frameLabel) - 2,
Chris@918 1909 v->getPaintHeight() - 2,
Chris@280 1910 frameLabel,
Chris@1078 1911 PaintAssistant::OutlinedText);
Chris@1078 1912 PaintAssistant::drawVisibleText(v, paint,
Chris@280 1913 cursorPos.x() + 2,
Chris@918 1914 v->getPaintHeight() - 2,
Chris@280 1915 rtLabel,
Chris@1078 1916 PaintAssistant::OutlinedText);
Chris@264 1917
Chris@77 1918 int harmonic = 2;
Chris@77 1919
Chris@77 1920 while (harmonic < 100) {
Chris@77 1921
Chris@907 1922 int hy = int(lrint(getYForFrequency(v, fundamental * harmonic)));
Chris@918 1923 if (hy < 0 || hy > v->getPaintHeight()) break;
Chris@77 1924
Chris@77 1925 int len = 7;
Chris@77 1926
Chris@77 1927 if (harmonic % 2 == 0) {
Chris@77 1928 if (harmonic % 4 == 0) {
Chris@77 1929 len = 12;
Chris@77 1930 } else {
Chris@77 1931 len = 10;
Chris@77 1932 }
Chris@77 1933 }
Chris@77 1934
Chris@77 1935 paint.drawLine(cursorPos.x() - len,
Chris@907 1936 hy,
Chris@77 1937 cursorPos.x(),
Chris@907 1938 hy);
Chris@77 1939
Chris@77 1940 ++harmonic;
Chris@77 1941 }
Chris@77 1942
Chris@77 1943 paint.restore();
Chris@77 1944 }
Chris@77 1945
Chris@25 1946 QString
Chris@918 1947 SpectrogramLayer::getFeatureDescription(LayerGeometryProvider *v, QPoint &pos) const
Chris@25 1948 {
Chris@25 1949 int x = pos.x();
Chris@25 1950 int y = pos.y();
Chris@0 1951
Chris@25 1952 if (!m_model || !m_model->isOK()) return "";
Chris@0 1953
Chris@905 1954 double magMin = 0, magMax = 0;
Chris@905 1955 double phaseMin = 0, phaseMax = 0;
Chris@905 1956 double freqMin = 0, freqMax = 0;
Chris@905 1957 double adjFreqMin = 0, adjFreqMax = 0;
Chris@25 1958 QString pitchMin, pitchMax;
Chris@0 1959 RealTime rtMin, rtMax;
Chris@0 1960
Chris@38 1961 bool haveValues = false;
Chris@0 1962
Chris@44 1963 if (!getXBinSourceRange(v, x, rtMin, rtMax)) {
Chris@1234 1964 return "";
Chris@38 1965 }
Chris@44 1966 if (getXYBinSourceRange(v, x, y, magMin, magMax, phaseMin, phaseMax)) {
Chris@1234 1967 haveValues = true;
Chris@38 1968 }
Chris@0 1969
Chris@35 1970 QString adjFreqText = "", adjPitchText = "";
Chris@35 1971
Chris@1103 1972 if (m_binDisplay == BinDisplay::PeakFrequencies) {
Chris@35 1973
Chris@1234 1974 if (!getAdjustedYBinSourceRange(v, x, y, freqMin, freqMax,
Chris@1234 1975 adjFreqMin, adjFreqMax)) {
Chris@1234 1976 return "";
Chris@1234 1977 }
Chris@1234 1978
Chris@1234 1979 if (adjFreqMin != adjFreqMax) {
Chris@1234 1980 adjFreqText = tr("Peak Frequency:\t%1 - %2 Hz\n")
Chris@1234 1981 .arg(adjFreqMin).arg(adjFreqMax);
Chris@1234 1982 } else {
Chris@1234 1983 adjFreqText = tr("Peak Frequency:\t%1 Hz\n")
Chris@1234 1984 .arg(adjFreqMin);
Chris@1234 1985 }
Chris@1234 1986
Chris@1234 1987 QString pmin = Pitch::getPitchLabelForFrequency(adjFreqMin);
Chris@1234 1988 QString pmax = Pitch::getPitchLabelForFrequency(adjFreqMax);
Chris@1234 1989
Chris@1234 1990 if (pmin != pmax) {
Chris@1234 1991 adjPitchText = tr("Peak Pitch:\t%3 - %4\n").arg(pmin).arg(pmax);
Chris@1234 1992 } else {
Chris@1234 1993 adjPitchText = tr("Peak Pitch:\t%2\n").arg(pmin);
Chris@1234 1994 }
Chris@35 1995
Chris@35 1996 } else {
Chris@1234 1997
Chris@1234 1998 if (!getYBinSourceRange(v, y, freqMin, freqMax)) return "";
Chris@35 1999 }
Chris@35 2000
Chris@25 2001 QString text;
Chris@25 2002
Chris@25 2003 if (rtMin != rtMax) {
Chris@1234 2004 text += tr("Time:\t%1 - %2\n")
Chris@1234 2005 .arg(rtMin.toText(true).c_str())
Chris@1234 2006 .arg(rtMax.toText(true).c_str());
Chris@25 2007 } else {
Chris@1234 2008 text += tr("Time:\t%1\n")
Chris@1234 2009 .arg(rtMin.toText(true).c_str());
Chris@0 2010 }
Chris@0 2011
Chris@25 2012 if (freqMin != freqMax) {
Chris@1234 2013 text += tr("%1Bin Frequency:\t%2 - %3 Hz\n%4Bin Pitch:\t%5 - %6\n")
Chris@1234 2014 .arg(adjFreqText)
Chris@1234 2015 .arg(freqMin)
Chris@1234 2016 .arg(freqMax)
Chris@1234 2017 .arg(adjPitchText)
Chris@1234 2018 .arg(Pitch::getPitchLabelForFrequency(freqMin))
Chris@1234 2019 .arg(Pitch::getPitchLabelForFrequency(freqMax));
Chris@65 2020 } else {
Chris@1234 2021 text += tr("%1Bin Frequency:\t%2 Hz\n%3Bin Pitch:\t%4\n")
Chris@1234 2022 .arg(adjFreqText)
Chris@1234 2023 .arg(freqMin)
Chris@1234 2024 .arg(adjPitchText)
Chris@1234 2025 .arg(Pitch::getPitchLabelForFrequency(freqMin));
Chris@1234 2026 }
Chris@25 2027
Chris@38 2028 if (haveValues) {
Chris@1234 2029 double dbMin = AudioLevel::multiplier_to_dB(magMin);
Chris@1234 2030 double dbMax = AudioLevel::multiplier_to_dB(magMax);
Chris@1234 2031 QString dbMinString;
Chris@1234 2032 QString dbMaxString;
Chris@1234 2033 if (dbMin == AudioLevel::DB_FLOOR) {
Chris@1234 2034 dbMinString = Strings::minus_infinity;
Chris@1234 2035 } else {
Chris@1234 2036 dbMinString = QString("%1").arg(lrint(dbMin));
Chris@1234 2037 }
Chris@1234 2038 if (dbMax == AudioLevel::DB_FLOOR) {
Chris@1234 2039 dbMaxString = Strings::minus_infinity;
Chris@1234 2040 } else {
Chris@1234 2041 dbMaxString = QString("%1").arg(lrint(dbMax));
Chris@1234 2042 }
Chris@1234 2043 if (lrint(dbMin) != lrint(dbMax)) {
Chris@1234 2044 text += tr("dB:\t%1 - %2").arg(dbMinString).arg(dbMaxString);
Chris@1234 2045 } else {
Chris@1234 2046 text += tr("dB:\t%1").arg(dbMinString);
Chris@1234 2047 }
Chris@1234 2048 if (phaseMin != phaseMax) {
Chris@1234 2049 text += tr("\nPhase:\t%1 - %2").arg(phaseMin).arg(phaseMax);
Chris@1234 2050 } else {
Chris@1234 2051 text += tr("\nPhase:\t%1").arg(phaseMin);
Chris@1234 2052 }
Chris@25 2053 }
Chris@25 2054
Chris@25 2055 return text;
Chris@0 2056 }
Chris@25 2057
Chris@0 2058 int
Chris@40 2059 SpectrogramLayer::getColourScaleWidth(QPainter &paint) const
Chris@40 2060 {
Chris@40 2061 int cw;
Chris@40 2062
Chris@119 2063 cw = paint.fontMetrics().width("-80dB");
Chris@119 2064
Chris@40 2065 return cw;
Chris@40 2066 }
Chris@40 2067
Chris@40 2068 int
Chris@918 2069 SpectrogramLayer::getVerticalScaleWidth(LayerGeometryProvider *, bool detailed, QPainter &paint) const
Chris@0 2070 {
Chris@0 2071 if (!m_model || !m_model->isOK()) return 0;
Chris@0 2072
Chris@607 2073 int cw = 0;
Chris@607 2074 if (detailed) cw = getColourScaleWidth(paint);
Chris@40 2075
Chris@0 2076 int tw = paint.fontMetrics().width(QString("%1")
Chris@1234 2077 .arg(m_maxFrequency > 0 ?
Chris@1234 2078 m_maxFrequency - 1 :
Chris@1234 2079 m_model->getSampleRate() / 2));
Chris@0 2080
Chris@234 2081 int fw = paint.fontMetrics().width(tr("43Hz"));
Chris@0 2082 if (tw < fw) tw = fw;
Chris@40 2083
Chris@1103 2084 int tickw = (m_binScale == BinScale::Log ? 10 : 4);
Chris@0 2085
Chris@40 2086 return cw + tickw + tw + 13;
Chris@0 2087 }
Chris@0 2088
Chris@0 2089 void
Chris@1142 2090 SpectrogramLayer::paintVerticalScale(LayerGeometryProvider *v, bool detailed,
Chris@1142 2091 QPainter &paint, QRect rect) const
Chris@0 2092 {
Chris@0 2093 if (!m_model || !m_model->isOK()) {
Chris@1234 2094 return;
Chris@0 2095 }
Chris@0 2096
Chris@382 2097 Profiler profiler("SpectrogramLayer::paintVerticalScale");
Chris@122 2098
Chris@120 2099 //!!! cache this?
Chris@1142 2100
Chris@0 2101 int h = rect.height(), w = rect.width();
Chris@1142 2102 int textHeight = paint.fontMetrics().height();
Chris@1142 2103
Chris@1142 2104 if (detailed && (h > textHeight * 3 + 10)) {
Chris@1142 2105 paintDetailedScale(v, paint, rect);
Chris@1142 2106 }
Chris@1142 2107 m_haveDetailedScale = detailed;
Chris@0 2108
Chris@1103 2109 int tickw = (m_binScale == BinScale::Log ? 10 : 4);
Chris@1103 2110 int pkw = (m_binScale == BinScale::Log ? 10 : 0);
Chris@40 2111
Chris@1087 2112 int bins = getFFTSize() / 2;
Chris@907 2113 sv_samplerate_t sr = m_model->getSampleRate();
Chris@0 2114
Chris@0 2115 if (m_maxFrequency > 0) {
Chris@1234 2116 bins = int((double(m_maxFrequency) * getFFTSize()) / sr + 0.1);
Chris@1234 2117 if (bins > getFFTSize() / 2) bins = getFFTSize() / 2;
Chris@0 2118 }
Chris@0 2119
Chris@607 2120 int cw = 0;
Chris@607 2121 if (detailed) cw = getColourScaleWidth(paint);
Chris@40 2122
Chris@0 2123 int py = -1;
Chris@0 2124 int toff = -textHeight + paint.fontMetrics().ascent() + 2;
Chris@0 2125
Chris@40 2126 paint.drawLine(cw + 7, 0, cw + 7, h);
Chris@40 2127
Chris@0 2128 int bin = -1;
Chris@0 2129
Chris@918 2130 for (int y = 0; y < v->getPaintHeight(); ++y) {
Chris@0 2131
Chris@1234 2132 double q0, q1;
Chris@1234 2133 if (!getYBinRange(v, v->getPaintHeight() - y, q0, q1)) continue;
Chris@1234 2134
Chris@1234 2135 int vy;
Chris@1234 2136
Chris@1234 2137 if (int(q0) > bin) {
Chris@1234 2138 vy = y;
Chris@1234 2139 bin = int(q0);
Chris@1234 2140 } else {
Chris@1234 2141 continue;
Chris@1234 2142 }
Chris@1234 2143
Chris@1234 2144 int freq = int((sr * bin) / getFFTSize());
Chris@1234 2145
Chris@1234 2146 if (py >= 0 && (vy - py) < textHeight - 1) {
Chris@1234 2147 if (m_binScale == BinScale::Linear) {
Chris@1234 2148 paint.drawLine(w - tickw, h - vy, w, h - vy);
Chris@1234 2149 }
Chris@1234 2150 continue;
Chris@1234 2151 }
Chris@1234 2152
Chris@1234 2153 QString text = QString("%1").arg(freq);
Chris@1234 2154 if (bin == 1) text = tr("%1Hz").arg(freq); // bin 0 is DC
Chris@1234 2155 paint.drawLine(cw + 7, h - vy, w - pkw - 1, h - vy);
Chris@1234 2156
Chris@1234 2157 if (h - vy - textHeight >= -2) {
Chris@1234 2158 int tx = w - 3 - paint.fontMetrics().width(text) - max(tickw, pkw);
Chris@1234 2159 paint.drawText(tx, h - vy + toff, text);
Chris@1234 2160 }
Chris@1234 2161
Chris@1234 2162 py = vy;
Chris@0 2163 }
Chris@40 2164
Chris@1103 2165 if (m_binScale == BinScale::Log) {
Chris@40 2166
Chris@277 2167 // piano keyboard
Chris@277 2168
Chris@690 2169 PianoScale().paintPianoVertical
Chris@690 2170 (v, paint, QRect(w - pkw - 1, 0, pkw, h),
Chris@690 2171 getEffectiveMinFrequency(), getEffectiveMaxFrequency());
Chris@40 2172 }
Chris@608 2173
Chris@608 2174 m_haveDetailedScale = detailed;
Chris@0 2175 }
Chris@0 2176
Chris@1142 2177 void
Chris@1142 2178 SpectrogramLayer::paintDetailedScale(LayerGeometryProvider *v,
Chris@1142 2179 QPainter &paint, QRect rect) const
Chris@1142 2180 {
Chris@1142 2181 // The colour scale
Chris@1143 2182
Chris@1143 2183 if (m_colourScale == ColourScaleType::Phase) {
Chris@1143 2184 paintDetailedScalePhase(v, paint, rect);
Chris@1143 2185 return;
Chris@1143 2186 }
Chris@1142 2187
Chris@1142 2188 int h = rect.height();
Chris@1142 2189 int textHeight = paint.fontMetrics().height();
Chris@1142 2190 int toff = -textHeight + paint.fontMetrics().ascent() + 2;
Chris@1142 2191
Chris@1142 2192 int cw = getColourScaleWidth(paint);
Chris@1142 2193 int cbw = paint.fontMetrics().width("dB");
Chris@1142 2194
Chris@1142 2195 int topLines = 2;
Chris@1142 2196
Chris@1142 2197 int ch = h - textHeight * (topLines + 1) - 8;
Chris@1234 2198 // paint.drawRect(4, textHeight + 4, cw - 1, ch + 1);
Chris@1142 2199 paint.drawRect(4 + cw - cbw, textHeight * topLines + 4, cbw - 1, ch + 1);
Chris@1142 2200
Chris@1142 2201 QString top, bottom;
Chris@1142 2202 double min = m_viewMags[v->getId()].getMin();
Chris@1142 2203 double max = m_viewMags[v->getId()].getMax();
Chris@1142 2204
Chris@1142 2205 if (min < m_threshold) min = m_threshold;
Chris@1142 2206 if (max <= min) max = min + 0.1;
Chris@1142 2207
Chris@1142 2208 double dBmin = AudioLevel::multiplier_to_dB(min);
Chris@1142 2209 double dBmax = AudioLevel::multiplier_to_dB(max);
Chris@1142 2210
Chris@1142 2211 #ifdef DEBUG_SPECTROGRAM_REPAINT
Chris@1142 2212 cerr << "paintVerticalScale: for view id " << v->getId()
Chris@1142 2213 << ": min = " << min << ", max = " << max
Chris@1142 2214 << ", dBmin = " << dBmin << ", dBmax = " << dBmax << endl;
Chris@1142 2215 #endif
Chris@1142 2216
Chris@1142 2217 if (dBmax < -60.f) dBmax = -60.f;
Chris@1142 2218 else top = QString("%1").arg(lrint(dBmax));
Chris@1142 2219
Chris@1142 2220 if (dBmin < dBmax - 60.f) dBmin = dBmax - 60.f;
Chris@1142 2221 bottom = QString("%1").arg(lrint(dBmin));
Chris@1142 2222
Chris@1142 2223 #ifdef DEBUG_SPECTROGRAM_REPAINT
Chris@1142 2224 cerr << "adjusted dB range to min = " << dBmin << ", max = " << dBmax
Chris@1142 2225 << endl;
Chris@1142 2226 #endif
Chris@1142 2227
Chris@1143 2228 paint.drawText((cw + 6 - paint.fontMetrics().width("dBFS")) / 2,
Chris@1143 2229 2 + textHeight + toff, "dBFS");
Chris@1142 2230
Chris@1142 2231 paint.drawText(3 + cw - cbw - paint.fontMetrics().width(top),
Chris@1142 2232 2 + textHeight * topLines + toff + textHeight/2, top);
Chris@1142 2233
Chris@1142 2234 paint.drawText(3 + cw - cbw - paint.fontMetrics().width(bottom),
Chris@1142 2235 h + toff - 3 - textHeight/2, bottom);
Chris@1142 2236
Chris@1142 2237 paint.save();
Chris@1142 2238 paint.setBrush(Qt::NoBrush);
Chris@1142 2239
Chris@1142 2240 int lasty = 0;
Chris@1142 2241 int lastdb = 0;
Chris@1142 2242
Chris@1142 2243 for (int i = 0; i < ch; ++i) {
Chris@1142 2244
Chris@1142 2245 double dBval = dBmin + (((dBmax - dBmin) * i) / (ch - 1));
Chris@1142 2246 int idb = int(dBval);
Chris@1142 2247
Chris@1142 2248 double value = AudioLevel::dB_to_multiplier(dBval);
Chris@1142 2249 paint.setPen(getRenderer(v)->getColour(value));
Chris@1142 2250
Chris@1142 2251 int y = textHeight * topLines + 4 + ch - i;
Chris@1142 2252
Chris@1142 2253 paint.drawLine(5 + cw - cbw, y, cw + 2, y);
Chris@1144 2254
Chris@1142 2255 if (i == 0) {
Chris@1142 2256 lasty = y;
Chris@1142 2257 lastdb = idb;
Chris@1142 2258 } else if (i < ch - paint.fontMetrics().ascent() &&
Chris@1142 2259 idb != lastdb &&
Chris@1142 2260 ((abs(y - lasty) > textHeight &&
Chris@1142 2261 idb % 10 == 0) ||
Chris@1142 2262 (abs(y - lasty) > paint.fontMetrics().ascent() &&
Chris@1142 2263 idb % 5 == 0))) {
Chris@1144 2264 paint.setPen(v->getForeground());
Chris@1142 2265 QString text = QString("%1").arg(idb);
Chris@1142 2266 paint.drawText(3 + cw - cbw - paint.fontMetrics().width(text),
Chris@1142 2267 y + toff + textHeight/2, text);
Chris@1142 2268 paint.drawLine(5 + cw - cbw, y, 8 + cw - cbw, y);
Chris@1142 2269 lasty = y;
Chris@1142 2270 lastdb = idb;
Chris@1142 2271 }
Chris@1142 2272 }
Chris@1142 2273 paint.restore();
Chris@1142 2274 }
Chris@1142 2275
Chris@1143 2276 void
Chris@1143 2277 SpectrogramLayer::paintDetailedScalePhase(LayerGeometryProvider *v,
Chris@1143 2278 QPainter &paint, QRect rect) const
Chris@1143 2279 {
Chris@1143 2280 // The colour scale in phase mode
Chris@1143 2281
Chris@1143 2282 int h = rect.height();
Chris@1143 2283 int textHeight = paint.fontMetrics().height();
Chris@1143 2284 int toff = -textHeight + paint.fontMetrics().ascent() + 2;
Chris@1143 2285
Chris@1143 2286 int cw = getColourScaleWidth(paint);
Chris@1143 2287
Chris@1143 2288 // Phase is not measured in dB of course, but this places the
Chris@1143 2289 // scale at the same position as in the magnitude spectrogram
Chris@1143 2290 int cbw = paint.fontMetrics().width("dB");
Chris@1143 2291
Chris@1143 2292 int topLines = 1;
Chris@1143 2293
Chris@1143 2294 int ch = h - textHeight * (topLines + 1) - 8;
Chris@1143 2295 paint.drawRect(4 + cw - cbw, textHeight * topLines + 4, cbw - 1, ch + 1);
Chris@1143 2296
Chris@1147 2297 QString top = Strings::pi, bottom = Strings::minus_pi, middle = "0";
Chris@1143 2298
Chris@1143 2299 double min = -M_PI;
Chris@1143 2300 double max = M_PI;
Chris@1143 2301
Chris@1143 2302 paint.drawText(3 + cw - cbw - paint.fontMetrics().width(top),
Chris@1143 2303 2 + textHeight * topLines + toff + textHeight/2, top);
Chris@1143 2304
Chris@1143 2305 paint.drawText(3 + cw - cbw - paint.fontMetrics().width(middle),
Chris@1143 2306 2 + textHeight * topLines + ch/2 + toff + textHeight/2, middle);
Chris@1143 2307
Chris@1143 2308 paint.drawText(3 + cw - cbw - paint.fontMetrics().width(bottom),
Chris@1143 2309 h + toff - 3 - textHeight/2, bottom);
Chris@1143 2310
Chris@1143 2311 paint.save();
Chris@1143 2312 paint.setBrush(Qt::NoBrush);
Chris@1143 2313
Chris@1143 2314 for (int i = 0; i < ch; ++i) {
Chris@1143 2315 double val = min + (((max - min) * i) / (ch - 1));
Chris@1143 2316 paint.setPen(getRenderer(v)->getColour(val));
Chris@1143 2317 int y = textHeight * topLines + 4 + ch - i;
Chris@1143 2318 paint.drawLine(5 + cw - cbw, y, cw + 2, y);
Chris@1143 2319 }
Chris@1143 2320 paint.restore();
Chris@1143 2321 }
Chris@1143 2322
Chris@187 2323 class SpectrogramRangeMapper : public RangeMapper
Chris@187 2324 {
Chris@187 2325 public:
Chris@901 2326 SpectrogramRangeMapper(sv_samplerate_t sr, int /* fftsize */) :
Chris@901 2327 m_dist(sr / 2),
Chris@901 2328 m_s2(sqrt(sqrt(2))) { }
Chris@1405 2329 ~SpectrogramRangeMapper() override { }
Chris@187 2330
Chris@1405 2331 int getPositionForValue(double value) const override {
Chris@901 2332
Chris@901 2333 double dist = m_dist;
Chris@187 2334
Chris@187 2335 int n = 0;
Chris@187 2336
Chris@901 2337 while (dist > (value + 0.00001) && dist > 0.1) {
Chris@187 2338 dist /= m_s2;
Chris@187 2339 ++n;
Chris@187 2340 }
Chris@187 2341
Chris@187 2342 return n;
Chris@187 2343 }
Chris@724 2344
Chris@1405 2345 int getPositionForValueUnclamped(double value) const override {
Chris@724 2346 // We don't really support this
Chris@724 2347 return getPositionForValue(value);
Chris@724 2348 }
Chris@187 2349
Chris@1405 2350 double getValueForPosition(int position) const override {
Chris@187 2351
Chris@187 2352 // Vertical zoom step 0 shows the entire range from DC ->
Chris@187 2353 // Nyquist frequency. Step 1 shows 2^(1/4) of the range of
Chris@187 2354 // step 0, and so on until the visible range is smaller than
Chris@187 2355 // the frequency step between bins at the current fft size.
Chris@187 2356
Chris@901 2357 double dist = m_dist;
Chris@187 2358
Chris@187 2359 int n = 0;
Chris@187 2360 while (n < position) {
Chris@187 2361 dist /= m_s2;
Chris@187 2362 ++n;
Chris@187 2363 }
Chris@187 2364
Chris@187 2365 return dist;
Chris@187 2366 }
Chris@187 2367
Chris@1405 2368 double getValueForPositionUnclamped(int position) const override {
Chris@724 2369 // We don't really support this
Chris@724 2370 return getValueForPosition(position);
Chris@724 2371 }
Chris@724 2372
Chris@1405 2373 QString getUnit() const override { return "Hz"; }
Chris@187 2374
Chris@187 2375 protected:
Chris@901 2376 double m_dist;
Chris@901 2377 double m_s2;
Chris@187 2378 };
Chris@187 2379
Chris@133 2380 int
Chris@133 2381 SpectrogramLayer::getVerticalZoomSteps(int &defaultStep) const
Chris@133 2382 {
Chris@135 2383 if (!m_model) return 0;
Chris@187 2384
Chris@907 2385 sv_samplerate_t sr = m_model->getSampleRate();
Chris@187 2386
Chris@1087 2387 SpectrogramRangeMapper mapper(sr, getFFTSize());
Chris@1087 2388
Chris@1087 2389 // int maxStep = mapper.getPositionForValue((double(sr) / getFFTSize()) + 0.001);
Chris@187 2390 int maxStep = mapper.getPositionForValue(0);
Chris@905 2391 int minStep = mapper.getPositionForValue(double(sr) / 2);
Chris@250 2392
Chris@805 2393 int initialMax = m_initialMaxFrequency;
Chris@907 2394 if (initialMax == 0) initialMax = int(sr / 2);
Chris@250 2395
Chris@250 2396 defaultStep = mapper.getPositionForValue(initialMax) - minStep;
Chris@250 2397
Chris@587 2398 // SVDEBUG << "SpectrogramLayer::getVerticalZoomSteps: " << maxStep - minStep << " (" << maxStep <<"-" << minStep << "), default is " << defaultStep << " (from initial max freq " << initialMax << ")" << endl;
Chris@187 2399
Chris@187 2400 return maxStep - minStep;
Chris@133 2401 }
Chris@133 2402
Chris@133 2403 int
Chris@133 2404 SpectrogramLayer::getCurrentVerticalZoomStep() const
Chris@133 2405 {
Chris@133 2406 if (!m_model) return 0;
Chris@133 2407
Chris@905 2408 double dmin, dmax;
Chris@133 2409 getDisplayExtents(dmin, dmax);
Chris@133 2410
Chris@1087 2411 SpectrogramRangeMapper mapper(m_model->getSampleRate(), getFFTSize());
Chris@187 2412 int n = mapper.getPositionForValue(dmax - dmin);
Chris@587 2413 // SVDEBUG << "SpectrogramLayer::getCurrentVerticalZoomStep: " << n << endl;
Chris@133 2414 return n;
Chris@133 2415 }
Chris@133 2416
Chris@133 2417 void
Chris@133 2418 SpectrogramLayer::setVerticalZoomStep(int step)
Chris@133 2419 {
Chris@187 2420 if (!m_model) return;
Chris@187 2421
Chris@905 2422 double dmin = m_minFrequency, dmax = m_maxFrequency;
Chris@253 2423 // getDisplayExtents(dmin, dmax);
Chris@253 2424
Chris@682 2425 // cerr << "current range " << dmin << " -> " << dmax << ", range " << dmax-dmin << ", mid " << (dmax + dmin)/2 << endl;
Chris@133 2426
Chris@907 2427 sv_samplerate_t sr = m_model->getSampleRate();
Chris@1087 2428 SpectrogramRangeMapper mapper(sr, getFFTSize());
Chris@905 2429 double newdist = mapper.getValueForPosition(step);
Chris@905 2430
Chris@905 2431 double newmin, newmax;
Chris@253 2432
Chris@1103 2433 if (m_binScale == BinScale::Log) {
Chris@253 2434
Chris@253 2435 // need to pick newmin and newmax such that
Chris@253 2436 //
Chris@253 2437 // (log(newmin) + log(newmax)) / 2 == logmid
Chris@253 2438 // and
Chris@253 2439 // newmax - newmin = newdist
Chris@253 2440 //
Chris@253 2441 // so log(newmax - newdist) + log(newmax) == 2logmid
Chris@253 2442 // log(newmax(newmax - newdist)) == 2logmid
Chris@253 2443 // newmax.newmax - newmax.newdist == exp(2logmid)
Chris@253 2444 // newmax^2 + (-newdist)newmax + -exp(2logmid) == 0
Chris@253 2445 // quadratic with a = 1, b = -newdist, c = -exp(2logmid), all known
Chris@253 2446 //
Chris@253 2447 // positive root
Chris@253 2448 // newmax = (newdist + sqrt(newdist^2 + 4exp(2logmid))) / 2
Chris@253 2449 //
Chris@253 2450 // but logmid = (log(dmin) + log(dmax)) / 2
Chris@253 2451 // so exp(2logmid) = exp(log(dmin) + log(dmax))
Chris@253 2452 // = exp(log(dmin.dmax))
Chris@253 2453 // = dmin.dmax
Chris@253 2454 // so newmax = (newdist + sqrtf(newdist^2 + 4dmin.dmax)) / 2
Chris@253 2455
Chris@907 2456 newmax = (newdist + sqrt(newdist*newdist + 4*dmin*dmax)) / 2;
Chris@253 2457 newmin = newmax - newdist;
Chris@253 2458
Chris@682 2459 // cerr << "newmin = " << newmin << ", newmax = " << newmax << endl;
Chris@253 2460
Chris@253 2461 } else {
Chris@905 2462 double dmid = (dmax + dmin) / 2;
Chris@253 2463 newmin = dmid - newdist / 2;
Chris@253 2464 newmax = dmid + newdist / 2;
Chris@253 2465 }
Chris@187 2466
Chris@905 2467 double mmin, mmax;
Chris@187 2468 mmin = 0;
Chris@905 2469 mmax = double(sr) / 2;
Chris@133 2470
Chris@187 2471 if (newmin < mmin) {
Chris@187 2472 newmax += (mmin - newmin);
Chris@187 2473 newmin = mmin;
Chris@187 2474 }
Chris@187 2475 if (newmax > mmax) {
Chris@187 2476 newmax = mmax;
Chris@187 2477 }
Chris@133 2478
Chris@587 2479 // SVDEBUG << "SpectrogramLayer::setVerticalZoomStep: " << step << ": " << newmin << " -> " << newmax << " (range " << newdist << ")" << endl;
Chris@253 2480
Chris@907 2481 setMinFrequency(int(lrint(newmin)));
Chris@907 2482 setMaxFrequency(int(lrint(newmax)));
Chris@187 2483 }
Chris@187 2484
Chris@187 2485 RangeMapper *
Chris@187 2486 SpectrogramLayer::getNewVerticalZoomRangeMapper() const
Chris@187 2487 {
Chris@1408 2488 if (!m_model) return nullptr;
Chris@1087 2489 return new SpectrogramRangeMapper(m_model->getSampleRate(), getFFTSize());
Chris@133 2490 }
Chris@133 2491
Chris@273 2492 void
Chris@918 2493 SpectrogramLayer::updateMeasureRectYCoords(LayerGeometryProvider *v, const MeasureRect &r) const
Chris@273 2494 {
Chris@273 2495 int y0 = 0;
Chris@907 2496 if (r.startY > 0.0) y0 = int(getYForFrequency(v, r.startY));
Chris@273 2497
Chris@273 2498 int y1 = y0;
Chris@907 2499 if (r.endY > 0.0) y1 = int(getYForFrequency(v, r.endY));
Chris@273 2500
Chris@587 2501 // SVDEBUG << "SpectrogramLayer::updateMeasureRectYCoords: start " << r.startY << " -> " << y0 << ", end " << r.endY << " -> " << y1 << endl;
Chris@273 2502
Chris@273 2503 r.pixrect = QRect(r.pixrect.x(), y0, r.pixrect.width(), y1 - y0);
Chris@273 2504 }
Chris@273 2505
Chris@273 2506 void
Chris@918 2507 SpectrogramLayer::setMeasureRectYCoord(LayerGeometryProvider *v, MeasureRect &r, bool start, int y) const
Chris@273 2508 {
Chris@273 2509 if (start) {
Chris@273 2510 r.startY = getFrequencyForY(v, y);
Chris@273 2511 r.endY = r.startY;
Chris@273 2512 } else {
Chris@273 2513 r.endY = getFrequencyForY(v, y);
Chris@273 2514 }
Chris@587 2515 // SVDEBUG << "SpectrogramLayer::setMeasureRectYCoord: start " << r.startY << " <- " << y << ", end " << r.endY << " <- " << y << endl;
Chris@273 2516
Chris@273 2517 }
Chris@273 2518
Chris@316 2519 void
Chris@316 2520 SpectrogramLayer::toXml(QTextStream &stream,
Chris@316 2521 QString indent, QString extraAttributes) const
Chris@6 2522 {
Chris@6 2523 QString s;
Chris@6 2524
Chris@6 2525 s += QString("channel=\"%1\" "
Chris@1234 2526 "windowSize=\"%2\" "
Chris@1234 2527 "windowHopLevel=\"%3\" "
Chris@1382 2528 "oversampling=\"%4\" "
Chris@1382 2529 "gain=\"%5\" "
Chris@1382 2530 "threshold=\"%6\" ")
Chris@1234 2531 .arg(m_channel)
Chris@1234 2532 .arg(m_windowSize)
Chris@1234 2533 .arg(m_windowHopLevel)
Chris@1382 2534 .arg(m_oversampling)
Chris@1234 2535 .arg(m_gain)
Chris@1234 2536 .arg(m_threshold);
Chris@37 2537
Chris@37 2538 s += QString("minFrequency=\"%1\" "
Chris@1234 2539 "maxFrequency=\"%2\" "
Chris@1234 2540 "colourScale=\"%3\" "
Chris@1362 2541 "colourRotation=\"%4\" "
Chris@1362 2542 "frequencyScale=\"%5\" "
Chris@1362 2543 "binDisplay=\"%6\" ")
Chris@1234 2544 .arg(m_minFrequency)
Chris@1234 2545 .arg(m_maxFrequency)
Chris@1234 2546 .arg(convertFromColourScale(m_colourScale, m_colourScaleMultiple))
Chris@1234 2547 .arg(m_colourRotation)
Chris@1234 2548 .arg(int(m_binScale))
Chris@1234 2549 .arg(int(m_binDisplay));
Chris@761 2550
Chris@1362 2551 // New-style colour map attribute, by string id rather than by
Chris@1362 2552 // number
Chris@1362 2553
Chris@1362 2554 s += QString("colourMap=\"%1\" ")
Chris@1362 2555 .arg(ColourMapper::getColourMapId(m_colourMap));
Chris@1362 2556
Chris@1362 2557 // Old-style colour map attribute
Chris@1362 2558
Chris@1362 2559 s += QString("colourScheme=\"%1\" ")
Chris@1362 2560 .arg(ColourMapper::getBackwardCompatibilityColourMap(m_colourMap));
Chris@1362 2561
Chris@1009 2562 // New-style normalization attributes, allowing for more types of
Chris@1009 2563 // normalization in future: write out the column normalization
Chris@1009 2564 // type separately, and then whether we are normalizing visible
Chris@1009 2565 // area as well afterwards
Chris@1009 2566
Chris@1009 2567 s += QString("columnNormalization=\"%1\" ")
Chris@1104 2568 .arg(m_normalization == ColumnNormalization::Max1 ? "peak" :
Chris@1104 2569 m_normalization == ColumnNormalization::Hybrid ? "hybrid" : "none");
Chris@1009 2570
Chris@1009 2571 // Old-style normalization attribute. We *don't* write out
Chris@1009 2572 // normalizeHybrid here because the only release that would accept
Chris@1009 2573 // it (Tony v1.0) has a totally different scale factor for
Chris@1009 2574 // it. We'll just have to accept that session files from Tony
Chris@1009 2575 // v2.0+ will look odd in Tony v1.0
Chris@1009 2576
Chris@1009 2577 s += QString("normalizeColumns=\"%1\" ")
Chris@1234 2578 .arg(m_normalization == ColumnNormalization::Max1 ? "true" : "false");
Chris@1009 2579
Chris@1009 2580 // And this applies to both old- and new-style attributes
Chris@1009 2581
Chris@1009 2582 s += QString("normalizeVisibleArea=\"%1\" ")
Chris@1104 2583 .arg(m_normalizeVisibleArea ? "true" : "false");
Chris@1009 2584
Chris@316 2585 Layer::toXml(stream, indent, extraAttributes + " " + s);
Chris@6 2586 }
Chris@6 2587
Chris@11 2588 void
Chris@11 2589 SpectrogramLayer::setProperties(const QXmlAttributes &attributes)
Chris@11 2590 {
Chris@11 2591 bool ok = false;
Chris@11 2592
Chris@11 2593 int channel = attributes.value("channel").toInt(&ok);
Chris@11 2594 if (ok) setChannel(channel);
Chris@11 2595
Chris@805 2596 int windowSize = attributes.value("windowSize").toUInt(&ok);
Chris@11 2597 if (ok) setWindowSize(windowSize);
Chris@11 2598
Chris@805 2599 int windowHopLevel = attributes.value("windowHopLevel").toUInt(&ok);
Chris@97 2600 if (ok) setWindowHopLevel(windowHopLevel);
Chris@97 2601 else {
Chris@805 2602 int windowOverlap = attributes.value("windowOverlap").toUInt(&ok);
Chris@97 2603 // a percentage value
Chris@97 2604 if (ok) {
Chris@97 2605 if (windowOverlap == 0) setWindowHopLevel(0);
Chris@97 2606 else if (windowOverlap == 25) setWindowHopLevel(1);
Chris@97 2607 else if (windowOverlap == 50) setWindowHopLevel(2);
Chris@97 2608 else if (windowOverlap == 75) setWindowHopLevel(3);
Chris@97 2609 else if (windowOverlap == 90) setWindowHopLevel(4);
Chris@97 2610 }
Chris@97 2611 }
Chris@11 2612
Chris@1382 2613 int oversampling = attributes.value("oversampling").toUInt(&ok);
Chris@1382 2614 if (ok) setOversampling(oversampling);
Chris@1382 2615
Chris@11 2616 float gain = attributes.value("gain").toFloat(&ok);
Chris@11 2617 if (ok) setGain(gain);
Chris@11 2618
Chris@37 2619 float threshold = attributes.value("threshold").toFloat(&ok);
Chris@37 2620 if (ok) setThreshold(threshold);
Chris@37 2621
Chris@805 2622 int minFrequency = attributes.value("minFrequency").toUInt(&ok);
Chris@187 2623 if (ok) {
Chris@587 2624 SVDEBUG << "SpectrogramLayer::setProperties: setting min freq to " << minFrequency << endl;
Chris@187 2625 setMinFrequency(minFrequency);
Chris@187 2626 }
Chris@37 2627
Chris@805 2628 int maxFrequency = attributes.value("maxFrequency").toUInt(&ok);
Chris@187 2629 if (ok) {
Chris@587 2630 SVDEBUG << "SpectrogramLayer::setProperties: setting max freq to " << maxFrequency << endl;
Chris@187 2631 setMaxFrequency(maxFrequency);
Chris@187 2632 }
Chris@11 2633
Chris@1137 2634 auto colourScale = convertToColourScale
Chris@1092 2635 (attributes.value("colourScale").toInt(&ok));
Chris@1137 2636 if (ok) {
Chris@1137 2637 setColourScale(colourScale.first);
Chris@1137 2638 setColourScaleMultiple(colourScale.second);
Chris@1137 2639 }
Chris@11 2640
Chris@1362 2641 QString colourMapId = attributes.value("colourMap");
Chris@1362 2642 int colourMap = ColourMapper::getColourMapById(colourMapId);
Chris@1362 2643 if (colourMap >= 0) {
Chris@1362 2644 setColourMap(colourMap);
Chris@1362 2645 } else {
Chris@1362 2646 colourMap = attributes.value("colourScheme").toInt(&ok);
Chris@1362 2647 if (ok && colourMap < ColourMapper::getColourMapCount()) {
Chris@1362 2648 setColourMap(colourMap);
Chris@1362 2649 }
Chris@1362 2650 }
Chris@11 2651
Chris@37 2652 int colourRotation = attributes.value("colourRotation").toInt(&ok);
Chris@37 2653 if (ok) setColourRotation(colourRotation);
Chris@37 2654
Chris@1103 2655 BinScale binScale = (BinScale)
Chris@1234 2656 attributes.value("frequencyScale").toInt(&ok);
Chris@1093 2657 if (ok) setBinScale(binScale);
Chris@1093 2658
Chris@1103 2659 BinDisplay binDisplay = (BinDisplay)
Chris@1234 2660 attributes.value("binDisplay").toInt(&ok);
Chris@37 2661 if (ok) setBinDisplay(binDisplay);
Chris@36 2662
Chris@1009 2663 bool haveNewStyleNormalization = false;
Chris@1009 2664
Chris@1009 2665 QString columnNormalization = attributes.value("columnNormalization");
Chris@1009 2666
Chris@1009 2667 if (columnNormalization != "") {
Chris@1009 2668
Chris@1009 2669 haveNewStyleNormalization = true;
Chris@1009 2670
Chris@1009 2671 if (columnNormalization == "peak") {
Chris@1104 2672 setNormalization(ColumnNormalization::Max1);
Chris@1009 2673 } else if (columnNormalization == "hybrid") {
Chris@1104 2674 setNormalization(ColumnNormalization::Hybrid);
Chris@1009 2675 } else if (columnNormalization == "none") {
Chris@1104 2676 setNormalization(ColumnNormalization::None);
Chris@1009 2677 } else {
Chris@1265 2678 SVCERR << "NOTE: Unknown or unsupported columnNormalization attribute \""
Chris@1009 2679 << columnNormalization << "\"" << endl;
Chris@1009 2680 }
Chris@1009 2681 }
Chris@1009 2682
Chris@1009 2683 if (!haveNewStyleNormalization) {
Chris@1009 2684
Chris@1009 2685 bool normalizeColumns =
Chris@1009 2686 (attributes.value("normalizeColumns").trimmed() == "true");
Chris@1009 2687 if (normalizeColumns) {
Chris@1104 2688 setNormalization(ColumnNormalization::Max1);
Chris@1009 2689 }
Chris@1009 2690
Chris@1009 2691 bool normalizeHybrid =
Chris@1009 2692 (attributes.value("normalizeHybrid").trimmed() == "true");
Chris@1009 2693 if (normalizeHybrid) {
Chris@1104 2694 setNormalization(ColumnNormalization::Hybrid);
Chris@1009 2695 }
Chris@862 2696 }
Chris@153 2697
Chris@153 2698 bool normalizeVisibleArea =
Chris@1099 2699 (attributes.value("normalizeVisibleArea").trimmed() == "true");
Chris@1104 2700 setNormalizeVisibleArea(normalizeVisibleArea);
Chris@1104 2701
Chris@1104 2702 if (!haveNewStyleNormalization && m_normalization == ColumnNormalization::Hybrid) {
Chris@1009 2703 // Tony v1.0 is (and hopefully will remain!) the only released
Chris@1009 2704 // SV-a-like to use old-style attributes when saving sessions
Chris@1009 2705 // that ask for hybrid normalization. It saves them with the
Chris@1009 2706 // wrong gain factor, so hack in a fix for that here -- this
Chris@1009 2707 // gives us backward but not forward compatibility.
Chris@1087 2708 setGain(m_gain / float(getFFTSize() / 2));
Chris@862 2709 }
Chris@11 2710 }
Chris@11 2711