annotate layer/SpectrogramLayer.cpp @ 1523:1ccb64bfb22b

Make the waveform layer do the expected thing when MergeChannels mode (i.e. butterfly mode) is enabled even if there is only 1 channel. This combination isn't actually available in the UI for SV, but it's useful for Sonic Lineup.
author Chris Cannam
date Wed, 25 Sep 2019 13:42: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