annotate layer/SpectrogramLayer.cpp @ 1386:fc3d89f88690 spectrogramparam

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