annotate layer/SpectrogramLayer.cpp @ 1204:d421df27e184 3.0-integration

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