annotate layer/SpectrogramLayer.cpp @ 1212:a1ee3108d1d3 3.0-integration

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