annotate layer/SpectrogramLayer.cpp @ 1363:bbeffb29bf09

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