annotate layer/SpectrogramLayer.cpp @ 1601:4739a1b2266f

This addAction API from Qt 5.6+ is still, unfortunately, too recent for some of our builds
author Chris Cannam
date Wed, 29 Apr 2020 14:23:51 +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