annotate layer/SpectrogramLayer.cpp @ 1534:bfd8b22fd67c

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