annotate layer/SpectrogramLayer.cpp @ 1496:d09345e578a7

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