annotate layer/SpectrogramLayer.cpp @ 1314:769d7890203b

Layers such as time-value layers with ColourDistinguishes property should use the dark background when a bright colour is selected. This is how it was always supposed to work, I think, but I broke it so long ago that this "fix" might itself come across as a surprising breaking change
author Chris Cannam
date Wed, 04 Jul 2018 15:33:47 +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