annotate layer/SpectrogramLayer.cpp @ 1558:760ada8e3972 spectrogram-export

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