annotate layer/SpectrogramLayer.cpp @ 1472:dbff4b290bf0 by-id

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