annotate layer/SpectrogramLayer.cpp @ 1330:c1f719094c25 zoom

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