annotate layer/SpectrogramLayer.cpp @ 1245:f0e291fa7b9c

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