annotate layer/SpectrogramLayer.cpp @ 1605:ae2d5f8ff005

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