annotate layer/SpectrogramLayer.cpp @ 1105:ea5ae9dd10ba spectrogram-minor-refactor

Convert ColourScaleType into an enum class
author Chris Cannam
date Thu, 14 Jul 2016 16:52:16 +0100
parents 46cc4644206d
children 8abdefce36a6
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@376 27 #include "widgets/CommandHistory.h"
Chris@1078 28 #include "data/model/Dense3DModelPeakCache.h"
Chris@1078 29
Chris@376 30 #include "ColourMapper.h"
Chris@283 31 #include "ImageRegionFinder.h"
Chris@690 32 #include "PianoScale.h"
Chris@1078 33 #include "PaintAssistant.h"
Chris@1089 34 #include "Colour3DPlotRenderer.h"
Chris@0 35
Chris@0 36 #include <QPainter>
Chris@0 37 #include <QImage>
Chris@0 38 #include <QPixmap>
Chris@0 39 #include <QRect>
Chris@92 40 #include <QApplication>
Chris@178 41 #include <QMessageBox>
Chris@283 42 #include <QMouseEvent>
Chris@316 43 #include <QTextStream>
Chris@1017 44 #include <QSettings>
Chris@0 45
Chris@0 46 #include <iostream>
Chris@0 47
Chris@0 48 #include <cassert>
Chris@0 49 #include <cmath>
Chris@0 50
Chris@545 51 #ifndef __GNUC__
Chris@545 52 #include <alloca.h>
Chris@545 53 #endif
Chris@545 54
Chris@1090 55 #define DEBUG_SPECTROGRAM 1
Chris@1044 56 #define DEBUG_SPECTROGRAM_REPAINT 1
Chris@1025 57
Chris@1025 58 using namespace std;
Chris@907 59
Chris@44 60 SpectrogramLayer::SpectrogramLayer(Configuration config) :
Chris@0 61 m_model(0),
Chris@0 62 m_channel(0),
Chris@0 63 m_windowSize(1024),
Chris@0 64 m_windowType(HanningWindow),
Chris@97 65 m_windowHopLevel(2),
Chris@0 66 m_gain(1.0),
Chris@215 67 m_initialGain(1.0),
Chris@37 68 m_threshold(0.0),
Chris@215 69 m_initialThreshold(0.0),
Chris@9 70 m_colourRotation(0),
Chris@215 71 m_initialRotation(0),
Chris@119 72 m_minFrequency(10),
Chris@0 73 m_maxFrequency(8000),
Chris@135 74 m_initialMaxFrequency(8000),
Chris@1105 75 m_colourScale(ColourScaleType::Log),
Chris@197 76 m_colourMap(0),
Chris@1103 77 m_binScale(BinScale::Linear),
Chris@1103 78 m_binDisplay(BinDisplay::AllBins),
Chris@1104 79 m_normalization(ColumnNormalization::None),
Chris@1104 80 m_normalizeVisibleArea(false),
Chris@133 81 m_lastEmittedZoomStep(-1),
Chris@390 82 m_synchronous(false),
Chris@608 83 m_haveDetailedScale(false),
Chris@193 84 m_exiting(false),
Chris@1088 85 m_fftModel(0),
Chris@1088 86 m_peakCache(0),
Chris@1088 87 m_peakCacheDivisor(8)
Chris@0 88 {
Chris@1017 89 QString colourConfigName = "spectrogram-colour";
Chris@1017 90 int colourConfigDefault = int(ColourMapper::Green);
Chris@1017 91
Chris@215 92 if (config == FullRangeDb) {
Chris@215 93 m_initialMaxFrequency = 0;
Chris@215 94 setMaxFrequency(0);
Chris@215 95 } else if (config == MelodicRange) {
Chris@0 96 setWindowSize(8192);
Chris@97 97 setWindowHopLevel(4);
Chris@215 98 m_initialMaxFrequency = 1500;
Chris@215 99 setMaxFrequency(1500);
Chris@215 100 setMinFrequency(40);
Chris@1105 101 setColourScale(ColourScaleType::Linear);
Chris@215 102 setColourMap(ColourMapper::Sunset);
Chris@1103 103 setBinScale(BinScale::Log);
Chris@1017 104 colourConfigName = "spectrogram-melodic-colour";
Chris@1017 105 colourConfigDefault = int(ColourMapper::Sunset);
Chris@224 106 // setGain(20);
Chris@37 107 } else if (config == MelodicPeaks) {
Chris@37 108 setWindowSize(4096);
Chris@97 109 setWindowHopLevel(5);
Chris@135 110 m_initialMaxFrequency = 2000;
Chris@40 111 setMaxFrequency(2000);
Chris@37 112 setMinFrequency(40);
Chris@1103 113 setBinScale(BinScale::Log);
Chris@1105 114 setColourScale(ColourScaleType::Linear);
Chris@1103 115 setBinDisplay(BinDisplay::PeakFrequencies);
Chris@1104 116 setNormalization(ColumnNormalization::Max1);
Chris@1017 117 colourConfigName = "spectrogram-melodic-colour";
Chris@1017 118 colourConfigDefault = int(ColourMapper::Sunset);
Chris@0 119 }
Chris@110 120
Chris@1017 121 QSettings settings;
Chris@1017 122 settings.beginGroup("Preferences");
Chris@1017 123 setColourMap(settings.value(colourConfigName, colourConfigDefault).toInt());
Chris@1017 124 settings.endGroup();
Chris@1017 125
Chris@122 126 Preferences *prefs = Preferences::getInstance();
Chris@122 127 connect(prefs, SIGNAL(propertyChanged(PropertyContainer::PropertyName)),
Chris@122 128 this, SLOT(preferenceChanged(PropertyContainer::PropertyName)));
Chris@122 129 setWindowType(prefs->getWindowType());
Chris@122 130
Chris@197 131 initialisePalette();
Chris@0 132 }
Chris@0 133
Chris@0 134 SpectrogramLayer::~SpectrogramLayer()
Chris@0 135 {
Chris@1102 136 invalidateImageCaches();
Chris@1088 137 invalidateFFTModel();
Chris@0 138 }
Chris@0 139
Chris@1105 140 ColourScaleType
Chris@1104 141 SpectrogramLayer::convertToColourScale(int value)
Chris@1104 142 {
Chris@1104 143 switch (value) {
Chris@1105 144 case 0: return ColourScaleType::Linear;
Chris@1105 145 case 1: return ColourScaleType::Meter;
Chris@1105 146 case 2: return ColourScaleType::Log; //!!! db^2
Chris@1105 147 case 3: return ColourScaleType::Log;
Chris@1105 148 case 4: return ColourScaleType::Phase;
Chris@1105 149 default: return ColourScaleType::Linear;
Chris@1104 150 }
Chris@1104 151 }
Chris@1104 152
Chris@1104 153 int
Chris@1105 154 SpectrogramLayer::convertFromColourScale(ColourScaleType scale)
Chris@1104 155 {
Chris@1104 156 switch (scale) {
Chris@1105 157 case ColourScaleType::Linear: return 0;
Chris@1105 158 case ColourScaleType::Meter: return 1;
Chris@1105 159 case ColourScaleType::Log: return 3; //!!! + db^2
Chris@1105 160 case ColourScaleType::Phase: return 4;
Chris@1105 161
Chris@1105 162 case ColourScaleType::PlusMinusOne:
Chris@1105 163 case ColourScaleType::Absolute:
Chris@1104 164 default: return 0;
Chris@1104 165 }
Chris@1104 166 }
Chris@1104 167
Chris@1104 168 std::pair<ColumnNormalization, bool>
Chris@1104 169 SpectrogramLayer::convertToColumnNorm(int value)
Chris@1104 170 {
Chris@1104 171 switch (value) {
Chris@1104 172 default:
Chris@1104 173 case 0: return { ColumnNormalization::None, false };
Chris@1104 174 case 1: return { ColumnNormalization::Max1, false };
Chris@1104 175 case 2: return { ColumnNormalization::None, true }; // visible area
Chris@1104 176 case 3: return { ColumnNormalization::Hybrid, false };
Chris@1104 177 }
Chris@1104 178 }
Chris@1104 179
Chris@1104 180 int
Chris@1104 181 SpectrogramLayer::convertFromColumnNorm(ColumnNormalization norm, bool visible)
Chris@1104 182 {
Chris@1104 183 if (visible) return 2;
Chris@1104 184 switch (norm) {
Chris@1104 185 case ColumnNormalization::None: return 0;
Chris@1104 186 case ColumnNormalization::Max1: return 1;
Chris@1104 187 case ColumnNormalization::Hybrid: return 3;
Chris@1104 188
Chris@1104 189 case ColumnNormalization::Sum1:
Chris@1104 190 default: return 0;
Chris@1104 191 }
Chris@1104 192 }
Chris@1104 193
Chris@0 194 void
Chris@0 195 SpectrogramLayer::setModel(const DenseTimeValueModel *model)
Chris@0 196 {
Chris@682 197 // cerr << "SpectrogramLayer(" << this << "): setModel(" << model << ")" << endl;
Chris@34 198
Chris@110 199 if (model == m_model) return;
Chris@110 200
Chris@0 201 m_model = model;
Chris@1088 202 invalidateFFTModel();
Chris@0 203
Chris@0 204 if (!m_model || !m_model->isOK()) return;
Chris@0 205
Chris@320 206 connectSignals(m_model);
Chris@0 207
Chris@0 208 connect(m_model, SIGNAL(modelChanged()), this, SLOT(cacheInvalid()));
Chris@906 209 connect(m_model, SIGNAL(modelChangedWithin(sv_frame_t, sv_frame_t)),
Chris@906 210 this, SLOT(cacheInvalid(sv_frame_t, sv_frame_t)));
Chris@0 211
Chris@0 212 emit modelReplaced();
Chris@110 213 }
Chris@115 214
Chris@0 215 Layer::PropertyList
Chris@0 216 SpectrogramLayer::getProperties() const
Chris@0 217 {
Chris@0 218 PropertyList list;
Chris@87 219 list.push_back("Colour");
Chris@87 220 list.push_back("Colour Scale");
Chris@87 221 list.push_back("Window Size");
Chris@97 222 list.push_back("Window Increment");
Chris@862 223 list.push_back("Normalization");
Chris@87 224 list.push_back("Bin Display");
Chris@87 225 list.push_back("Threshold");
Chris@87 226 list.push_back("Gain");
Chris@87 227 list.push_back("Colour Rotation");
Chris@153 228 // list.push_back("Min Frequency");
Chris@153 229 // list.push_back("Max Frequency");
Chris@87 230 list.push_back("Frequency Scale");
Chris@0 231 return list;
Chris@0 232 }
Chris@0 233
Chris@87 234 QString
Chris@87 235 SpectrogramLayer::getPropertyLabel(const PropertyName &name) const
Chris@87 236 {
Chris@87 237 if (name == "Colour") return tr("Colour");
Chris@87 238 if (name == "Colour Scale") return tr("Colour Scale");
Chris@87 239 if (name == "Window Size") return tr("Window Size");
Chris@112 240 if (name == "Window Increment") return tr("Window Overlap");
Chris@862 241 if (name == "Normalization") return tr("Normalization");
Chris@87 242 if (name == "Bin Display") return tr("Bin Display");
Chris@87 243 if (name == "Threshold") return tr("Threshold");
Chris@87 244 if (name == "Gain") return tr("Gain");
Chris@87 245 if (name == "Colour Rotation") return tr("Colour Rotation");
Chris@87 246 if (name == "Min Frequency") return tr("Min Frequency");
Chris@87 247 if (name == "Max Frequency") return tr("Max Frequency");
Chris@87 248 if (name == "Frequency Scale") return tr("Frequency Scale");
Chris@87 249 return "";
Chris@87 250 }
Chris@87 251
Chris@335 252 QString
Chris@862 253 SpectrogramLayer::getPropertyIconName(const PropertyName &) const
Chris@335 254 {
Chris@335 255 return "";
Chris@335 256 }
Chris@335 257
Chris@0 258 Layer::PropertyType
Chris@0 259 SpectrogramLayer::getPropertyType(const PropertyName &name) const
Chris@0 260 {
Chris@87 261 if (name == "Gain") return RangeProperty;
Chris@87 262 if (name == "Colour Rotation") return RangeProperty;
Chris@87 263 if (name == "Threshold") return RangeProperty;
Chris@0 264 return ValueProperty;
Chris@0 265 }
Chris@0 266
Chris@0 267 QString
Chris@0 268 SpectrogramLayer::getPropertyGroupName(const PropertyName &name) const
Chris@0 269 {
Chris@153 270 if (name == "Bin Display" ||
Chris@153 271 name == "Frequency Scale") return tr("Bins");
Chris@87 272 if (name == "Window Size" ||
Chris@1086 273 name == "Window Increment") return tr("Window");
Chris@87 274 if (name == "Colour" ||
Chris@87 275 name == "Threshold" ||
Chris@87 276 name == "Colour Rotation") return tr("Colour");
Chris@862 277 if (name == "Normalization" ||
Chris@153 278 name == "Gain" ||
Chris@87 279 name == "Colour Scale") return tr("Scale");
Chris@0 280 return QString();
Chris@0 281 }
Chris@0 282
Chris@0 283 int
Chris@0 284 SpectrogramLayer::getPropertyRangeAndValue(const PropertyName &name,
Chris@216 285 int *min, int *max, int *deflt) const
Chris@0 286 {
Chris@216 287 int val = 0;
Chris@216 288
Chris@216 289 int garbage0, garbage1, garbage2;
Chris@55 290 if (!min) min = &garbage0;
Chris@55 291 if (!max) max = &garbage1;
Chris@216 292 if (!deflt) deflt = &garbage2;
Chris@10 293
Chris@87 294 if (name == "Gain") {
Chris@0 295
Chris@0 296 *min = -50;
Chris@0 297 *max = 50;
Chris@0 298
Chris@906 299 *deflt = int(lrint(log10(m_initialGain) * 20.0));
Chris@216 300 if (*deflt < *min) *deflt = *min;
Chris@216 301 if (*deflt > *max) *deflt = *max;
Chris@216 302
Chris@906 303 val = int(lrint(log10(m_gain) * 20.0));
Chris@216 304 if (val < *min) val = *min;
Chris@216 305 if (val > *max) val = *max;
Chris@0 306
Chris@87 307 } else if (name == "Threshold") {
Chris@37 308
Chris@37 309 *min = -50;
Chris@37 310 *max = 0;
Chris@37 311
Chris@906 312 *deflt = int(lrint(AudioLevel::multiplier_to_dB(m_initialThreshold)));
Chris@216 313 if (*deflt < *min) *deflt = *min;
Chris@216 314 if (*deflt > *max) *deflt = *max;
Chris@216 315
Chris@906 316 val = int(lrint(AudioLevel::multiplier_to_dB(m_threshold)));
Chris@216 317 if (val < *min) val = *min;
Chris@216 318 if (val > *max) val = *max;
Chris@37 319
Chris@87 320 } else if (name == "Colour Rotation") {
Chris@9 321
Chris@9 322 *min = 0;
Chris@9 323 *max = 256;
Chris@216 324 *deflt = m_initialRotation;
Chris@216 325
Chris@216 326 val = m_colourRotation;
Chris@9 327
Chris@87 328 } else if (name == "Colour Scale") {
Chris@0 329
Chris@1099 330 // linear, meter, db^2, db, phase
Chris@0 331 *min = 0;
Chris@176 332 *max = 4;
Chris@1092 333 *deflt = 2;
Chris@216 334
Chris@1104 335 val = convertFromColourScale(m_colourScale);
Chris@0 336
Chris@87 337 } else if (name == "Colour") {
Chris@0 338
Chris@0 339 *min = 0;
Chris@196 340 *max = ColourMapper::getColourMapCount() - 1;
Chris@216 341 *deflt = 0;
Chris@216 342
Chris@216 343 val = m_colourMap;
Chris@0 344
Chris@87 345 } else if (name == "Window Size") {
Chris@0 346
Chris@0 347 *min = 0;
Chris@0 348 *max = 10;
Chris@216 349 *deflt = 5;
Chris@0 350
Chris@216 351 val = 0;
Chris@0 352 int ws = m_windowSize;
Chris@216 353 while (ws > 32) { ws >>= 1; val ++; }
Chris@0 354
Chris@97 355 } else if (name == "Window Increment") {
Chris@0 356
Chris@0 357 *min = 0;
Chris@97 358 *max = 5;
Chris@216 359 *deflt = 2;
Chris@216 360
Chris@216 361 val = m_windowHopLevel;
Chris@0 362
Chris@87 363 } else if (name == "Min Frequency") {
Chris@37 364
Chris@37 365 *min = 0;
Chris@37 366 *max = 9;
Chris@216 367 *deflt = 1;
Chris@37 368
Chris@37 369 switch (m_minFrequency) {
Chris@216 370 case 0: default: val = 0; break;
Chris@216 371 case 10: val = 1; break;
Chris@216 372 case 20: val = 2; break;
Chris@216 373 case 40: val = 3; break;
Chris@216 374 case 100: val = 4; break;
Chris@216 375 case 250: val = 5; break;
Chris@216 376 case 500: val = 6; break;
Chris@216 377 case 1000: val = 7; break;
Chris@216 378 case 4000: val = 8; break;
Chris@216 379 case 10000: val = 9; break;
Chris@37 380 }
Chris@37 381
Chris@87 382 } else if (name == "Max Frequency") {
Chris@0 383
Chris@0 384 *min = 0;
Chris@0 385 *max = 9;
Chris@216 386 *deflt = 6;
Chris@0 387
Chris@0 388 switch (m_maxFrequency) {
Chris@216 389 case 500: val = 0; break;
Chris@216 390 case 1000: val = 1; break;
Chris@216 391 case 1500: val = 2; break;
Chris@216 392 case 2000: val = 3; break;
Chris@216 393 case 4000: val = 4; break;
Chris@216 394 case 6000: val = 5; break;
Chris@216 395 case 8000: val = 6; break;
Chris@216 396 case 12000: val = 7; break;
Chris@216 397 case 16000: val = 8; break;
Chris@216 398 default: val = 9; break;
Chris@0 399 }
Chris@0 400
Chris@87 401 } else if (name == "Frequency Scale") {
Chris@0 402
Chris@0 403 *min = 0;
Chris@0 404 *max = 1;
Chris@1103 405 *deflt = int(BinScale::Linear);
Chris@1093 406 val = (int)m_binScale;
Chris@0 407
Chris@87 408 } else if (name == "Bin Display") {
Chris@35 409
Chris@35 410 *min = 0;
Chris@35 411 *max = 2;
Chris@1103 412 *deflt = int(BinDisplay::AllBins);
Chris@216 413 val = (int)m_binDisplay;
Chris@35 414
Chris@862 415 } else if (name == "Normalization") {
Chris@36 416
Chris@862 417 *min = 0;
Chris@862 418 *max = 3;
Chris@1104 419 *deflt = 0;
Chris@1104 420
Chris@1104 421 val = convertFromColumnNorm(m_normalization, m_normalizeVisibleArea);
Chris@120 422
Chris@0 423 } else {
Chris@216 424 val = Layer::getPropertyRangeAndValue(name, min, max, deflt);
Chris@0 425 }
Chris@0 426
Chris@216 427 return val;
Chris@0 428 }
Chris@0 429
Chris@0 430 QString
Chris@0 431 SpectrogramLayer::getPropertyValueLabel(const PropertyName &name,
Chris@9 432 int value) const
Chris@0 433 {
Chris@87 434 if (name == "Colour") {
Chris@196 435 return ColourMapper::getColourMapName(value);
Chris@0 436 }
Chris@87 437 if (name == "Colour Scale") {
Chris@0 438 switch (value) {
Chris@0 439 default:
Chris@37 440 case 0: return tr("Linear");
Chris@37 441 case 1: return tr("Meter");
Chris@215 442 case 2: return tr("dBV^2");
Chris@215 443 case 3: return tr("dBV");
Chris@119 444 case 4: return tr("Phase");
Chris@0 445 }
Chris@0 446 }
Chris@862 447 if (name == "Normalization") {
Chris@862 448 return ""; // icon only
Chris@862 449 }
Chris@87 450 if (name == "Window Size") {
Chris@0 451 return QString("%1").arg(32 << value);
Chris@0 452 }
Chris@97 453 if (name == "Window Increment") {
Chris@0 454 switch (value) {
Chris@0 455 default:
Chris@112 456 case 0: return tr("None");
Chris@112 457 case 1: return tr("25 %");
Chris@112 458 case 2: return tr("50 %");
Chris@112 459 case 3: return tr("75 %");
Chris@112 460 case 4: return tr("87.5 %");
Chris@112 461 case 5: return tr("93.75 %");
Chris@0 462 }
Chris@0 463 }
Chris@87 464 if (name == "Min Frequency") {
Chris@37 465 switch (value) {
Chris@37 466 default:
Chris@38 467 case 0: return tr("No min");
Chris@37 468 case 1: return tr("10 Hz");
Chris@37 469 case 2: return tr("20 Hz");
Chris@37 470 case 3: return tr("40 Hz");
Chris@37 471 case 4: return tr("100 Hz");
Chris@37 472 case 5: return tr("250 Hz");
Chris@37 473 case 6: return tr("500 Hz");
Chris@37 474 case 7: return tr("1 KHz");
Chris@37 475 case 8: return tr("4 KHz");
Chris@37 476 case 9: return tr("10 KHz");
Chris@37 477 }
Chris@37 478 }
Chris@87 479 if (name == "Max Frequency") {
Chris@0 480 switch (value) {
Chris@0 481 default:
Chris@0 482 case 0: return tr("500 Hz");
Chris@0 483 case 1: return tr("1 KHz");
Chris@0 484 case 2: return tr("1.5 KHz");
Chris@0 485 case 3: return tr("2 KHz");
Chris@0 486 case 4: return tr("4 KHz");
Chris@0 487 case 5: return tr("6 KHz");
Chris@0 488 case 6: return tr("8 KHz");
Chris@0 489 case 7: return tr("12 KHz");
Chris@0 490 case 8: return tr("16 KHz");
Chris@38 491 case 9: return tr("No max");
Chris@0 492 }
Chris@0 493 }
Chris@87 494 if (name == "Frequency Scale") {
Chris@0 495 switch (value) {
Chris@0 496 default:
Chris@0 497 case 0: return tr("Linear");
Chris@0 498 case 1: return tr("Log");
Chris@0 499 }
Chris@0 500 }
Chris@87 501 if (name == "Bin Display") {
Chris@35 502 switch (value) {
Chris@35 503 default:
Chris@37 504 case 0: return tr("All Bins");
Chris@37 505 case 1: return tr("Peak Bins");
Chris@37 506 case 2: return tr("Frequencies");
Chris@35 507 }
Chris@35 508 }
Chris@0 509 return tr("<unknown>");
Chris@0 510 }
Chris@0 511
Chris@862 512 QString
Chris@862 513 SpectrogramLayer::getPropertyValueIconName(const PropertyName &name,
Chris@862 514 int value) const
Chris@862 515 {
Chris@862 516 if (name == "Normalization") {
Chris@862 517 switch(value) {
Chris@862 518 default:
Chris@862 519 case 0: return "normalise-none";
Chris@862 520 case 1: return "normalise-columns";
Chris@862 521 case 2: return "normalise";
Chris@862 522 case 3: return "normalise-hybrid";
Chris@862 523 }
Chris@862 524 }
Chris@862 525 return "";
Chris@862 526 }
Chris@862 527
Chris@167 528 RangeMapper *
Chris@167 529 SpectrogramLayer::getNewPropertyRangeMapper(const PropertyName &name) const
Chris@167 530 {
Chris@167 531 if (name == "Gain") {
Chris@167 532 return new LinearRangeMapper(-50, 50, -25, 25, tr("dB"));
Chris@167 533 }
Chris@167 534 if (name == "Threshold") {
Chris@167 535 return new LinearRangeMapper(-50, 0, -50, 0, tr("dB"));
Chris@167 536 }
Chris@167 537 return 0;
Chris@167 538 }
Chris@167 539
Chris@0 540 void
Chris@0 541 SpectrogramLayer::setProperty(const PropertyName &name, int value)
Chris@0 542 {
Chris@87 543 if (name == "Gain") {
Chris@906 544 setGain(float(pow(10, float(value)/20.0)));
Chris@87 545 } else if (name == "Threshold") {
Chris@37 546 if (value == -50) setThreshold(0.0);
Chris@906 547 else setThreshold(float(AudioLevel::dB_to_multiplier(value)));
Chris@87 548 } else if (name == "Colour Rotation") {
Chris@9 549 setColourRotation(value);
Chris@87 550 } else if (name == "Colour") {
Chris@197 551 setColourMap(value);
Chris@87 552 } else if (name == "Window Size") {
Chris@0 553 setWindowSize(32 << value);
Chris@97 554 } else if (name == "Window Increment") {
Chris@97 555 setWindowHopLevel(value);
Chris@87 556 } else if (name == "Min Frequency") {
Chris@37 557 switch (value) {
Chris@37 558 default:
Chris@37 559 case 0: setMinFrequency(0); break;
Chris@37 560 case 1: setMinFrequency(10); break;
Chris@37 561 case 2: setMinFrequency(20); break;
Chris@37 562 case 3: setMinFrequency(40); break;
Chris@37 563 case 4: setMinFrequency(100); break;
Chris@37 564 case 5: setMinFrequency(250); break;
Chris@37 565 case 6: setMinFrequency(500); break;
Chris@37 566 case 7: setMinFrequency(1000); break;
Chris@37 567 case 8: setMinFrequency(4000); break;
Chris@37 568 case 9: setMinFrequency(10000); break;
Chris@37 569 }
Chris@133 570 int vs = getCurrentVerticalZoomStep();
Chris@133 571 if (vs != m_lastEmittedZoomStep) {
Chris@133 572 emit verticalZoomChanged();
Chris@133 573 m_lastEmittedZoomStep = vs;
Chris@133 574 }
Chris@87 575 } else if (name == "Max Frequency") {
Chris@0 576 switch (value) {
Chris@0 577 case 0: setMaxFrequency(500); break;
Chris@0 578 case 1: setMaxFrequency(1000); break;
Chris@0 579 case 2: setMaxFrequency(1500); break;
Chris@0 580 case 3: setMaxFrequency(2000); break;
Chris@0 581 case 4: setMaxFrequency(4000); break;
Chris@0 582 case 5: setMaxFrequency(6000); break;
Chris@0 583 case 6: setMaxFrequency(8000); break;
Chris@0 584 case 7: setMaxFrequency(12000); break;
Chris@0 585 case 8: setMaxFrequency(16000); break;
Chris@0 586 default:
Chris@0 587 case 9: setMaxFrequency(0); break;
Chris@0 588 }
Chris@133 589 int vs = getCurrentVerticalZoomStep();
Chris@133 590 if (vs != m_lastEmittedZoomStep) {
Chris@133 591 emit verticalZoomChanged();
Chris@133 592 m_lastEmittedZoomStep = vs;
Chris@133 593 }
Chris@87 594 } else if (name == "Colour Scale") {
Chris@0 595 switch (value) {
Chris@0 596 default:
Chris@1105 597 case 0: setColourScale(ColourScaleType::Linear); break;
Chris@1105 598 case 1: setColourScale(ColourScaleType::Meter); break;
Chris@1105 599 case 2: setColourScale(ColourScaleType::Log); break; //!!! dB^2
Chris@1105 600 case 3: setColourScale(ColourScaleType::Log); break;
Chris@1105 601 case 4: setColourScale(ColourScaleType::Phase); break;
Chris@0 602 }
Chris@87 603 } else if (name == "Frequency Scale") {
Chris@0 604 switch (value) {
Chris@0 605 default:
Chris@1103 606 case 0: setBinScale(BinScale::Linear); break;
Chris@1103 607 case 1: setBinScale(BinScale::Log); break;
Chris@0 608 }
Chris@87 609 } else if (name == "Bin Display") {
Chris@35 610 switch (value) {
Chris@35 611 default:
Chris@1103 612 case 0: setBinDisplay(BinDisplay::AllBins); break;
Chris@1103 613 case 1: setBinDisplay(BinDisplay::PeakBins); break;
Chris@1103 614 case 2: setBinDisplay(BinDisplay::PeakFrequencies); break;
Chris@35 615 }
Chris@862 616 } else if (name == "Normalization") {
Chris@1104 617 auto n = convertToColumnNorm(value);
Chris@1104 618 setNormalization(n.first);
Chris@1104 619 setNormalizeVisibleArea(n.second);
Chris@0 620 }
Chris@0 621 }
Chris@0 622
Chris@0 623 void
Chris@478 624 SpectrogramLayer::invalidateImageCaches()
Chris@95 625 {
Chris@1044 626 #ifdef DEBUG_SPECTROGRAM
Chris@1044 627 cerr << "SpectrogramLayer::invalidateImageCaches called" << endl;
Chris@1044 628 #endif
Chris@478 629 for (ViewImageCache::iterator i = m_imageCaches.begin();
Chris@478 630 i != m_imageCaches.end(); ++i) {
Chris@1030 631 i->second.invalidate();
Chris@95 632 }
Chris@1089 633
Chris@1089 634 //!!!
Chris@1089 635 for (ViewRendererMap::iterator i = m_renderers.begin();
Chris@1089 636 i != m_renderers.end(); ++i) {
Chris@1089 637 delete i->second;
Chris@1089 638 }
Chris@1089 639 m_renderers.clear();
Chris@95 640 }
Chris@95 641
Chris@95 642 void
Chris@122 643 SpectrogramLayer::preferenceChanged(PropertyContainer::PropertyName name)
Chris@122 644 {
Chris@587 645 SVDEBUG << "SpectrogramLayer::preferenceChanged(" << name << ")" << endl;
Chris@122 646
Chris@122 647 if (name == "Window Type") {
Chris@122 648 setWindowType(Preferences::getInstance()->getWindowType());
Chris@122 649 return;
Chris@122 650 }
Chris@490 651 if (name == "Spectrogram Y Smoothing") {
Chris@1086 652 setWindowSize(m_windowSize);
Chris@490 653 invalidateImageCaches();
Chris@490 654 invalidateMagnitudes();
Chris@490 655 emit layerParametersChanged();
Chris@490 656 }
Chris@490 657 if (name == "Spectrogram X Smoothing") {
Chris@478 658 invalidateImageCaches();
Chris@122 659 invalidateMagnitudes();
Chris@122 660 emit layerParametersChanged();
Chris@122 661 }
Chris@122 662 if (name == "Tuning Frequency") {
Chris@122 663 emit layerParametersChanged();
Chris@122 664 }
Chris@122 665 }
Chris@122 666
Chris@122 667 void
Chris@0 668 SpectrogramLayer::setChannel(int ch)
Chris@0 669 {
Chris@0 670 if (m_channel == ch) return;
Chris@0 671
Chris@478 672 invalidateImageCaches();
Chris@0 673 m_channel = ch;
Chris@1088 674 invalidateFFTModel();
Chris@9 675
Chris@0 676 emit layerParametersChanged();
Chris@0 677 }
Chris@0 678
Chris@0 679 int
Chris@0 680 SpectrogramLayer::getChannel() const
Chris@0 681 {
Chris@0 682 return m_channel;
Chris@0 683 }
Chris@0 684
Chris@1086 685 int
Chris@1086 686 SpectrogramLayer::getFFTOversampling() const
Chris@1086 687 {
Chris@1103 688 if (m_binDisplay != BinDisplay::AllBins) {
Chris@1086 689 return 1;
Chris@1086 690 }
Chris@1086 691
Chris@1086 692 Preferences::SpectrogramSmoothing smoothing =
Chris@1086 693 Preferences::getInstance()->getSpectrogramSmoothing();
Chris@1086 694
Chris@1086 695 if (smoothing == Preferences::NoSpectrogramSmoothing ||
Chris@1086 696 smoothing == Preferences::SpectrogramInterpolated) {
Chris@1086 697 return 1;
Chris@1086 698 }
Chris@1086 699
Chris@1086 700 return 4;
Chris@1086 701 }
Chris@1086 702
Chris@1087 703 int
Chris@1087 704 SpectrogramLayer::getFFTSize() const
Chris@1087 705 {
Chris@1087 706 return m_windowSize * getFFTOversampling();
Chris@1087 707 }
Chris@1087 708
Chris@0 709 void
Chris@805 710 SpectrogramLayer::setWindowSize(int ws)
Chris@0 711 {
Chris@1087 712 if (m_windowSize == ws) return;
Chris@0 713
Chris@478 714 invalidateImageCaches();
Chris@0 715
Chris@0 716 m_windowSize = ws;
Chris@0 717
Chris@1088 718 invalidateFFTModel();
Chris@9 719
Chris@9 720 emit layerParametersChanged();
Chris@0 721 }
Chris@0 722
Chris@805 723 int
Chris@0 724 SpectrogramLayer::getWindowSize() const
Chris@0 725 {
Chris@0 726 return m_windowSize;
Chris@0 727 }
Chris@0 728
Chris@0 729 void
Chris@805 730 SpectrogramLayer::setWindowHopLevel(int v)
Chris@0 731 {
Chris@97 732 if (m_windowHopLevel == v) return;
Chris@0 733
Chris@478 734 invalidateImageCaches();
Chris@0 735
Chris@97 736 m_windowHopLevel = v;
Chris@0 737
Chris@1088 738 invalidateFFTModel();
Chris@9 739
Chris@9 740 emit layerParametersChanged();
Chris@9 741
Chris@110 742 // fillCache();
Chris@0 743 }
Chris@0 744
Chris@805 745 int
Chris@97 746 SpectrogramLayer::getWindowHopLevel() const
Chris@0 747 {
Chris@97 748 return m_windowHopLevel;
Chris@0 749 }
Chris@0 750
Chris@0 751 void
Chris@0 752 SpectrogramLayer::setWindowType(WindowType w)
Chris@0 753 {
Chris@0 754 if (m_windowType == w) return;
Chris@0 755
Chris@478 756 invalidateImageCaches();
Chris@0 757
Chris@0 758 m_windowType = w;
Chris@110 759
Chris@1088 760 invalidateFFTModel();
Chris@9 761
Chris@9 762 emit layerParametersChanged();
Chris@0 763 }
Chris@0 764
Chris@0 765 WindowType
Chris@0 766 SpectrogramLayer::getWindowType() const
Chris@0 767 {
Chris@0 768 return m_windowType;
Chris@0 769 }
Chris@0 770
Chris@0 771 void
Chris@0 772 SpectrogramLayer::setGain(float gain)
Chris@0 773 {
Chris@587 774 // SVDEBUG << "SpectrogramLayer::setGain(" << gain << ") (my gain is now "
Chris@585 775 // << m_gain << ")" << endl;
Chris@55 776
Chris@40 777 if (m_gain == gain) return;
Chris@0 778
Chris@478 779 invalidateImageCaches();
Chris@0 780
Chris@0 781 m_gain = gain;
Chris@0 782
Chris@9 783 emit layerParametersChanged();
Chris@0 784 }
Chris@0 785
Chris@0 786 float
Chris@0 787 SpectrogramLayer::getGain() const
Chris@0 788 {
Chris@0 789 return m_gain;
Chris@0 790 }
Chris@0 791
Chris@0 792 void
Chris@37 793 SpectrogramLayer::setThreshold(float threshold)
Chris@37 794 {
Chris@40 795 if (m_threshold == threshold) return;
Chris@37 796
Chris@478 797 invalidateImageCaches();
Chris@37 798
Chris@37 799 m_threshold = threshold;
Chris@37 800
Chris@37 801 emit layerParametersChanged();
Chris@37 802 }
Chris@37 803
Chris@37 804 float
Chris@37 805 SpectrogramLayer::getThreshold() const
Chris@37 806 {
Chris@37 807 return m_threshold;
Chris@37 808 }
Chris@37 809
Chris@37 810 void
Chris@805 811 SpectrogramLayer::setMinFrequency(int mf)
Chris@37 812 {
Chris@37 813 if (m_minFrequency == mf) return;
Chris@37 814
Chris@587 815 // SVDEBUG << "SpectrogramLayer::setMinFrequency: " << mf << endl;
Chris@187 816
Chris@478 817 invalidateImageCaches();
Chris@119 818 invalidateMagnitudes();
Chris@37 819
Chris@37 820 m_minFrequency = mf;
Chris@37 821
Chris@37 822 emit layerParametersChanged();
Chris@37 823 }
Chris@37 824
Chris@805 825 int
Chris@37 826 SpectrogramLayer::getMinFrequency() const
Chris@37 827 {
Chris@37 828 return m_minFrequency;
Chris@37 829 }
Chris@37 830
Chris@37 831 void
Chris@805 832 SpectrogramLayer::setMaxFrequency(int mf)
Chris@0 833 {
Chris@0 834 if (m_maxFrequency == mf) return;
Chris@0 835
Chris@587 836 // SVDEBUG << "SpectrogramLayer::setMaxFrequency: " << mf << endl;
Chris@187 837
Chris@478 838 invalidateImageCaches();
Chris@119 839 invalidateMagnitudes();
Chris@0 840
Chris@0 841 m_maxFrequency = mf;
Chris@0 842
Chris@9 843 emit layerParametersChanged();
Chris@0 844 }
Chris@0 845
Chris@805 846 int
Chris@0 847 SpectrogramLayer::getMaxFrequency() const
Chris@0 848 {
Chris@0 849 return m_maxFrequency;
Chris@0 850 }
Chris@0 851
Chris@0 852 void
Chris@9 853 SpectrogramLayer::setColourRotation(int r)
Chris@9 854 {
Chris@478 855 invalidateImageCaches();
Chris@9 856
Chris@9 857 if (r < 0) r = 0;
Chris@9 858 if (r > 256) r = 256;
Chris@9 859 int distance = r - m_colourRotation;
Chris@9 860
Chris@9 861 if (distance != 0) {
Chris@197 862 rotatePalette(-distance);
Chris@9 863 m_colourRotation = r;
Chris@9 864 }
Chris@9 865
Chris@9 866 emit layerParametersChanged();
Chris@9 867 }
Chris@9 868
Chris@9 869 void
Chris@1105 870 SpectrogramLayer::setColourScale(ColourScaleType colourScale)
Chris@0 871 {
Chris@0 872 if (m_colourScale == colourScale) return;
Chris@0 873
Chris@478 874 invalidateImageCaches();
Chris@0 875
Chris@0 876 m_colourScale = colourScale;
Chris@0 877
Chris@9 878 emit layerParametersChanged();
Chris@0 879 }
Chris@0 880
Chris@1105 881 ColourScaleType
Chris@0 882 SpectrogramLayer::getColourScale() const
Chris@0 883 {
Chris@0 884 return m_colourScale;
Chris@0 885 }
Chris@0 886
Chris@0 887 void
Chris@197 888 SpectrogramLayer::setColourMap(int map)
Chris@0 889 {
Chris@197 890 if (m_colourMap == map) return;
Chris@0 891
Chris@478 892 invalidateImageCaches();
Chris@0 893
Chris@197 894 m_colourMap = map;
Chris@197 895 initialisePalette();
Chris@9 896
Chris@0 897 emit layerParametersChanged();
Chris@0 898 }
Chris@0 899
Chris@196 900 int
Chris@197 901 SpectrogramLayer::getColourMap() const
Chris@0 902 {
Chris@197 903 return m_colourMap;
Chris@0 904 }
Chris@0 905
Chris@0 906 void
Chris@1103 907 SpectrogramLayer::setBinScale(BinScale binScale)
Chris@0 908 {
Chris@1093 909 if (m_binScale == binScale) return;
Chris@0 910
Chris@478 911 invalidateImageCaches();
Chris@1093 912 m_binScale = binScale;
Chris@9 913
Chris@9 914 emit layerParametersChanged();
Chris@0 915 }
Chris@0 916
Chris@1103 917 BinScale
Chris@1093 918 SpectrogramLayer::getBinScale() const
Chris@0 919 {
Chris@1093 920 return m_binScale;
Chris@0 921 }
Chris@0 922
Chris@0 923 void
Chris@1103 924 SpectrogramLayer::setBinDisplay(BinDisplay binDisplay)
Chris@35 925 {
Chris@37 926 if (m_binDisplay == binDisplay) return;
Chris@35 927
Chris@478 928 invalidateImageCaches();
Chris@37 929 m_binDisplay = binDisplay;
Chris@35 930
Chris@35 931 emit layerParametersChanged();
Chris@35 932 }
Chris@35 933
Chris@1103 934 BinDisplay
Chris@37 935 SpectrogramLayer::getBinDisplay() const
Chris@35 936 {
Chris@37 937 return m_binDisplay;
Chris@35 938 }
Chris@35 939
Chris@35 940 void
Chris@1104 941 SpectrogramLayer::setNormalization(ColumnNormalization n)
Chris@36 942 {
Chris@862 943 if (m_normalization == n) return;
Chris@36 944
Chris@478 945 invalidateImageCaches();
Chris@119 946 invalidateMagnitudes();
Chris@862 947 m_normalization = n;
Chris@36 948
Chris@36 949 emit layerParametersChanged();
Chris@36 950 }
Chris@36 951
Chris@1104 952 ColumnNormalization
Chris@862 953 SpectrogramLayer::getNormalization() const
Chris@36 954 {
Chris@862 955 return m_normalization;
Chris@36 956 }
Chris@36 957
Chris@36 958 void
Chris@1104 959 SpectrogramLayer::setNormalizeVisibleArea(bool n)
Chris@1104 960 {
Chris@1104 961 if (m_normalizeVisibleArea == n) return;
Chris@1104 962
Chris@1104 963 invalidateImageCaches();
Chris@1104 964 invalidateMagnitudes();
Chris@1104 965 m_normalizeVisibleArea = n;
Chris@1104 966
Chris@1104 967 emit layerParametersChanged();
Chris@1104 968 }
Chris@1104 969
Chris@1104 970 bool
Chris@1104 971 SpectrogramLayer::getNormalizeVisibleArea() const
Chris@1104 972 {
Chris@1104 973 return m_normalizeVisibleArea;
Chris@1104 974 }
Chris@1104 975
Chris@1104 976 void
Chris@918 977 SpectrogramLayer::setLayerDormant(const LayerGeometryProvider *v, bool dormant)
Chris@29 978 {
Chris@33 979 if (dormant) {
Chris@33 980
Chris@331 981 #ifdef DEBUG_SPECTROGRAM_REPAINT
Chris@985 982 cerr << "SpectrogramLayer::setLayerDormant(" << dormant << ")"
Chris@585 983 << endl;
Chris@331 984 #endif
Chris@331 985
Chris@131 986 if (isLayerDormant(v)) {
Chris@131 987 return;
Chris@131 988 }
Chris@131 989
Chris@131 990 Layer::setLayerDormant(v, true);
Chris@33 991
Chris@920 992 const View *view = v->getView();
Chris@920 993
Chris@478 994 invalidateImageCaches();
Chris@988 995
Chris@1030 996 m_imageCaches.erase(view->getId());
Chris@1030 997
Chris@1088 998 //!!! in theory we should call invalidateFFTModel() if and
Chris@1088 999 //!!! only if there are no remaining views in which we are not
Chris@1088 1000 //!!! dormant
Chris@33 1001
Chris@33 1002 } else {
Chris@33 1003
Chris@131 1004 Layer::setLayerDormant(v, false);
Chris@33 1005 }
Chris@29 1006 }
Chris@29 1007
Chris@29 1008 void
Chris@0 1009 SpectrogramLayer::cacheInvalid()
Chris@0 1010 {
Chris@391 1011 #ifdef DEBUG_SPECTROGRAM_REPAINT
Chris@985 1012 cerr << "SpectrogramLayer::cacheInvalid()" << endl;
Chris@391 1013 #endif
Chris@391 1014
Chris@478 1015 invalidateImageCaches();
Chris@119 1016 invalidateMagnitudes();
Chris@0 1017 }
Chris@0 1018
Chris@0 1019 void
Chris@1037 1020 SpectrogramLayer::cacheInvalid(
Chris@1037 1021 #ifdef DEBUG_SPECTROGRAM_REPAINT
Chris@1037 1022 sv_frame_t from, sv_frame_t to
Chris@1037 1023 #else
Chris@1037 1024 sv_frame_t , sv_frame_t
Chris@1037 1025 #endif
Chris@1037 1026 )
Chris@0 1027 {
Chris@391 1028 #ifdef DEBUG_SPECTROGRAM_REPAINT
Chris@985 1029 cerr << "SpectrogramLayer::cacheInvalid(" << from << ", " << to << ")" << endl;
Chris@391 1030 #endif
Chris@391 1031
Chris@1030 1032 // We used to call invalidateMagnitudes(from, to) to invalidate
Chris@1030 1033 // only those caches whose views contained some of the (from, to)
Chris@1030 1034 // range. That's the right thing to do; it has been lost in
Chris@1030 1035 // pulling out the image cache code, but it might not matter very
Chris@1030 1036 // much, since the underlying models for spectrogram layers don't
Chris@1030 1037 // change very often. Let's see.
Chris@1030 1038 invalidateImageCaches();
Chris@391 1039 invalidateMagnitudes();
Chris@0 1040 }
Chris@0 1041
Chris@224 1042 bool
Chris@224 1043 SpectrogramLayer::hasLightBackground() const
Chris@224 1044 {
Chris@287 1045 return ColourMapper(m_colourMap, 1.f, 255.f).hasLightBackground();
Chris@224 1046 }
Chris@224 1047
Chris@0 1048 void
Chris@197 1049 SpectrogramLayer::initialisePalette()
Chris@0 1050 {
Chris@10 1051 int formerRotation = m_colourRotation;
Chris@10 1052
Chris@197 1053 if (m_colourMap == (int)ColourMapper::BlackOnWhite) {
Chris@197 1054 m_palette.setColour(NO_VALUE, Qt::white);
Chris@38 1055 } else {
Chris@197 1056 m_palette.setColour(NO_VALUE, Qt::black);
Chris@38 1057 }
Chris@0 1058
Chris@197 1059 ColourMapper mapper(m_colourMap, 1.f, 255.f);
Chris@196 1060
Chris@0 1061 for (int pixel = 1; pixel < 256; ++pixel) {
Chris@907 1062 m_palette.setColour((unsigned char)pixel, mapper.map(pixel));
Chris@0 1063 }
Chris@9 1064
Chris@196 1065 m_crosshairColour = mapper.getContrastingColour();
Chris@196 1066
Chris@9 1067 m_colourRotation = 0;
Chris@197 1068 rotatePalette(m_colourRotation - formerRotation);
Chris@10 1069 m_colourRotation = formerRotation;
Chris@478 1070
Chris@478 1071 m_drawBuffer = QImage();
Chris@9 1072 }
Chris@9 1073
Chris@9 1074 void
Chris@197 1075 SpectrogramLayer::rotatePalette(int distance)
Chris@9 1076 {
Chris@31 1077 QColor newPixels[256];
Chris@9 1078
Chris@197 1079 newPixels[NO_VALUE] = m_palette.getColour(NO_VALUE);
Chris@9 1080
Chris@9 1081 for (int pixel = 1; pixel < 256; ++pixel) {
Chris@9 1082 int target = pixel + distance;
Chris@9 1083 while (target < 1) target += 255;
Chris@9 1084 while (target > 255) target -= 255;
Chris@907 1085 newPixels[target] = m_palette.getColour((unsigned char)pixel);
Chris@9 1086 }
Chris@9 1087
Chris@9 1088 for (int pixel = 0; pixel < 256; ++pixel) {
Chris@907 1089 m_palette.setColour((unsigned char)pixel, newPixels[pixel]);
Chris@9 1090 }
Chris@478 1091
Chris@478 1092 m_drawBuffer = QImage();
Chris@0 1093 }
Chris@0 1094
Chris@38 1095 unsigned char
Chris@918 1096 SpectrogramLayer::getDisplayValue(LayerGeometryProvider *v, double input) const
Chris@38 1097 {
Chris@1092 1098 int value = 0;
Chris@37 1099
Chris@907 1100 double min = 0.0;
Chris@907 1101 double max = 1.0;
Chris@120 1102
Chris@1104 1103 if (m_normalizeVisibleArea) {
Chris@1030 1104 min = m_viewMags[v->getId()].getMin();
Chris@1030 1105 max = m_viewMags[v->getId()].getMax();
Chris@1104 1106 } else if (m_normalization != ColumnNormalization::Max1) {
Chris@1105 1107 if (m_colourScale == ColourScaleType::Linear //||
Chris@1105 1108 // m_colourScale == ColourScaleType::Meter) {
Chris@224 1109 ) {
Chris@907 1110 max = 0.1;
Chris@120 1111 }
Chris@120 1112 }
Chris@120 1113
Chris@907 1114 double thresh = -80.0;
Chris@907 1115
Chris@907 1116 if (max == 0.0) max = 1.0;
Chris@907 1117 if (max == min) min = max - 0.0001;
Chris@119 1118
Chris@40 1119 switch (m_colourScale) {
Chris@40 1120
Chris@1105 1121 case ColourScaleType::Linear:
Chris@907 1122 value = int(((input - min) / (max - min)) * 255.0) + 1;
Chris@40 1123 break;
Chris@40 1124
Chris@1105 1125 case ColourScaleType::Meter:
Chris@210 1126 value = AudioLevel::multiplier_to_preview
Chris@210 1127 ((input - min) / (max - min), 254) + 1;
Chris@40 1128 break;
Chris@119 1129
Chris@1092 1130 //!!! check this
Chris@1092 1131 /* case dBSquaredColourScale:
Chris@215 1132 input = ((input - min) * (input - min)) / ((max - min) * (max - min));
Chris@907 1133 if (input > 0.0) {
Chris@907 1134 input = 10.0 * log10(input);
Chris@133 1135 } else {
Chris@133 1136 input = thresh;
Chris@133 1137 }
Chris@907 1138 if (min > 0.0) {
Chris@907 1139 thresh = 10.0 * log10(min * min);
Chris@907 1140 if (thresh < -80.0) thresh = -80.0;
Chris@119 1141 }
Chris@119 1142 input = (input - thresh) / (-thresh);
Chris@907 1143 if (input < 0.0) input = 0.0;
Chris@907 1144 if (input > 1.0) input = 1.0;
Chris@907 1145 value = int(input * 255.0) + 1;
Chris@119 1146 break;
Chris@1092 1147 */
Chris@1105 1148 case ColourScaleType::Log:
Chris@215 1149 //!!! experiment with normalizing the visible area this way.
Chris@215 1150 //In any case, we need to have some indication of what the dB
Chris@215 1151 //scale is relative to.
Chris@215 1152 input = (input - min) / (max - min);
Chris@907 1153 if (input > 0.0) {
Chris@907 1154 input = 10.0 * log10(input);
Chris@215 1155 } else {
Chris@215 1156 input = thresh;
Chris@215 1157 }
Chris@907 1158 if (min > 0.0) {
Chris@907 1159 thresh = 10.0 * log10(min);
Chris@907 1160 if (thresh < -80.0) thresh = -80.0;
Chris@215 1161 }
Chris@215 1162 input = (input - thresh) / (-thresh);
Chris@907 1163 if (input < 0.0) input = 0.0;
Chris@907 1164 if (input > 1.0) input = 1.0;
Chris@907 1165 value = int(input * 255.0) + 1;
Chris@215 1166 break;
Chris@215 1167
Chris@1105 1168 case ColourScaleType::Phase:
Chris@40 1169 value = int((input * 127.0 / M_PI) + 128);
Chris@40 1170 break;
Chris@1092 1171
Chris@1105 1172 case ColourScaleType::PlusMinusOne:
Chris@1105 1173 case ColourScaleType::Absolute:
Chris@1092 1174 default:
Chris@1092 1175 ;
Chris@0 1176 }
Chris@210 1177
Chris@38 1178 if (value > UCHAR_MAX) value = UCHAR_MAX;
Chris@38 1179 if (value < 0) value = 0;
Chris@907 1180 return (unsigned char)value;
Chris@0 1181 }
Chris@0 1182
Chris@905 1183 double
Chris@40 1184 SpectrogramLayer::getEffectiveMinFrequency() const
Chris@40 1185 {
Chris@907 1186 sv_samplerate_t sr = m_model->getSampleRate();
Chris@1087 1187 double minf = double(sr) / getFFTSize();
Chris@40 1188
Chris@40 1189 if (m_minFrequency > 0.0) {
Chris@1087 1190 int minbin = int((double(m_minFrequency) * getFFTSize()) / sr + 0.01);
Chris@40 1191 if (minbin < 1) minbin = 1;
Chris@1087 1192 minf = minbin * sr / getFFTSize();
Chris@40 1193 }
Chris@40 1194
Chris@40 1195 return minf;
Chris@40 1196 }
Chris@40 1197
Chris@905 1198 double
Chris@40 1199 SpectrogramLayer::getEffectiveMaxFrequency() const
Chris@40 1200 {
Chris@907 1201 sv_samplerate_t sr = m_model->getSampleRate();
Chris@905 1202 double maxf = double(sr) / 2;
Chris@40 1203
Chris@40 1204 if (m_maxFrequency > 0.0) {
Chris@1087 1205 int maxbin = int((double(m_maxFrequency) * getFFTSize()) / sr + 0.1);
Chris@1087 1206 if (maxbin > getFFTSize() / 2) maxbin = getFFTSize() / 2;
Chris@1087 1207 maxf = maxbin * sr / getFFTSize();
Chris@40 1208 }
Chris@40 1209
Chris@40 1210 return maxf;
Chris@40 1211 }
Chris@40 1212
Chris@0 1213 bool
Chris@918 1214 SpectrogramLayer::getYBinRange(LayerGeometryProvider *v, int y, double &q0, double &q1) const
Chris@0 1215 {
Chris@382 1216 Profiler profiler("SpectrogramLayer::getYBinRange");
Chris@382 1217
Chris@918 1218 int h = v->getPaintHeight();
Chris@0 1219 if (y < 0 || y >= h) return false;
Chris@0 1220
Chris@907 1221 sv_samplerate_t sr = m_model->getSampleRate();
Chris@905 1222 double minf = getEffectiveMinFrequency();
Chris@905 1223 double maxf = getEffectiveMaxFrequency();
Chris@0 1224
Chris@1103 1225 bool logarithmic = (m_binScale == BinScale::Log);
Chris@38 1226
Chris@44 1227 q0 = v->getFrequencyForY(y, minf, maxf, logarithmic);
Chris@44 1228 q1 = v->getFrequencyForY(y - 1, minf, maxf, logarithmic);
Chris@38 1229
Chris@1086 1230 // Now map these on to ("proportions of") actual bins
Chris@490 1231
Chris@1087 1232 q0 = (q0 * getFFTSize()) / sr;
Chris@1087 1233 q1 = (q1 * getFFTSize()) / sr;
Chris@0 1234
Chris@0 1235 return true;
Chris@0 1236 }
Chris@486 1237
Chris@1085 1238 double
Chris@1090 1239 SpectrogramLayer::getYForBin(LayerGeometryProvider *, double) const {
Chris@1085 1240 //!!! not implemented
Chris@1085 1241 throw std::logic_error("not implemented");
Chris@1085 1242 }
Chris@1085 1243
Chris@1085 1244 double
Chris@1085 1245 SpectrogramLayer::getBinForY(LayerGeometryProvider *v, double y) const
Chris@1085 1246 {
Chris@1085 1247 //!!! overlap with range methods above (but using double arg)
Chris@1085 1248 //!!! tidy this
Chris@38 1249
Chris@1085 1250 int h = v->getPaintHeight();
Chris@1085 1251 if (y < 0 || y >= h) return false;
Chris@1085 1252
Chris@1085 1253 sv_samplerate_t sr = m_model->getSampleRate();
Chris@1085 1254 double minf = getEffectiveMinFrequency();
Chris@1085 1255 double maxf = getEffectiveMaxFrequency();
Chris@1085 1256
Chris@1103 1257 bool logarithmic = (m_binScale == BinScale::Log);
Chris@1085 1258
Chris@1085 1259 double q = v->getFrequencyForY(y, minf, maxf, logarithmic);
Chris@1085 1260
Chris@1086 1261 // Now map on to ("proportions of") actual bins
Chris@1085 1262
Chris@1087 1263 q = (q * getFFTSize()) / sr;
Chris@1085 1264
Chris@1085 1265 return q;
Chris@1085 1266 }
Chris@1085 1267
Chris@0 1268 bool
Chris@918 1269 SpectrogramLayer::getXBinRange(LayerGeometryProvider *v, int x, double &s0, double &s1) const
Chris@0 1270 {
Chris@907 1271 sv_frame_t modelStart = m_model->getStartFrame();
Chris@907 1272 sv_frame_t modelEnd = m_model->getEndFrame();
Chris@0 1273
Chris@0 1274 // Each pixel column covers an exact range of sample frames:
Chris@907 1275 sv_frame_t f0 = v->getFrameForX(x) - modelStart;
Chris@907 1276 sv_frame_t f1 = v->getFrameForX(x + 1) - modelStart - 1;
Chris@20 1277
Chris@41 1278 if (f1 < int(modelStart) || f0 > int(modelEnd)) {
Chris@41 1279 return false;
Chris@41 1280 }
Chris@20 1281
Chris@0 1282 // And that range may be drawn from a possibly non-integral
Chris@0 1283 // range of spectrogram windows:
Chris@0 1284
Chris@805 1285 int windowIncrement = getWindowIncrement();
Chris@905 1286 s0 = double(f0) / windowIncrement;
Chris@905 1287 s1 = double(f1) / windowIncrement;
Chris@0 1288
Chris@0 1289 return true;
Chris@0 1290 }
Chris@0 1291
Chris@0 1292 bool
Chris@918 1293 SpectrogramLayer::getXBinSourceRange(LayerGeometryProvider *v, int x, RealTime &min, RealTime &max) const
Chris@0 1294 {
Chris@905 1295 double s0 = 0, s1 = 0;
Chris@44 1296 if (!getXBinRange(v, x, s0, s1)) return false;
Chris@0 1297
Chris@0 1298 int s0i = int(s0 + 0.001);
Chris@0 1299 int s1i = int(s1);
Chris@0 1300
Chris@0 1301 int windowIncrement = getWindowIncrement();
Chris@0 1302 int w0 = s0i * windowIncrement - (m_windowSize - windowIncrement)/2;
Chris@0 1303 int w1 = s1i * windowIncrement + windowIncrement +
Chris@0 1304 (m_windowSize - windowIncrement)/2 - 1;
Chris@0 1305
Chris@0 1306 min = RealTime::frame2RealTime(w0, m_model->getSampleRate());
Chris@0 1307 max = RealTime::frame2RealTime(w1, m_model->getSampleRate());
Chris@0 1308 return true;
Chris@0 1309 }
Chris@0 1310
Chris@0 1311 bool
Chris@918 1312 SpectrogramLayer::getYBinSourceRange(LayerGeometryProvider *v, int y, double &freqMin, double &freqMax)
Chris@0 1313 const
Chris@0 1314 {
Chris@905 1315 double q0 = 0, q1 = 0;
Chris@44 1316 if (!getYBinRange(v, y, q0, q1)) return false;
Chris@0 1317
Chris@0 1318 int q0i = int(q0 + 0.001);
Chris@0 1319 int q1i = int(q1);
Chris@0 1320
Chris@907 1321 sv_samplerate_t sr = m_model->getSampleRate();
Chris@0 1322
Chris@0 1323 for (int q = q0i; q <= q1i; ++q) {
Chris@1087 1324 if (q == q0i) freqMin = (sr * q) / getFFTSize();
Chris@1087 1325 if (q == q1i) freqMax = (sr * (q+1)) / getFFTSize();
Chris@0 1326 }
Chris@0 1327 return true;
Chris@0 1328 }
Chris@35 1329
Chris@35 1330 bool
Chris@918 1331 SpectrogramLayer::getAdjustedYBinSourceRange(LayerGeometryProvider *v, int x, int y,
Chris@905 1332 double &freqMin, double &freqMax,
Chris@905 1333 double &adjFreqMin, double &adjFreqMax)
Chris@35 1334 const
Chris@35 1335 {
Chris@277 1336 if (!m_model || !m_model->isOK() || !m_model->isReady()) {
Chris@277 1337 return false;
Chris@277 1338 }
Chris@277 1339
Chris@1088 1340 FFTModel *fft = getFFTModel();
Chris@114 1341 if (!fft) return false;
Chris@110 1342
Chris@905 1343 double s0 = 0, s1 = 0;
Chris@44 1344 if (!getXBinRange(v, x, s0, s1)) return false;
Chris@35 1345
Chris@905 1346 double q0 = 0, q1 = 0;
Chris@44 1347 if (!getYBinRange(v, y, q0, q1)) return false;
Chris@35 1348
Chris@35 1349 int s0i = int(s0 + 0.001);
Chris@35 1350 int s1i = int(s1);
Chris@35 1351
Chris@35 1352 int q0i = int(q0 + 0.001);
Chris@35 1353 int q1i = int(q1);
Chris@35 1354
Chris@907 1355 sv_samplerate_t sr = m_model->getSampleRate();
Chris@35 1356
Chris@35 1357 bool haveAdj = false;
Chris@35 1358
Chris@1103 1359 bool peaksOnly = (m_binDisplay == BinDisplay::PeakBins ||
Chris@1103 1360 m_binDisplay == BinDisplay::PeakFrequencies);
Chris@37 1361
Chris@35 1362 for (int q = q0i; q <= q1i; ++q) {
Chris@35 1363
Chris@35 1364 for (int s = s0i; s <= s1i; ++s) {
Chris@35 1365
Chris@905 1366 double binfreq = (double(sr) * q) / m_windowSize;
Chris@35 1367 if (q == q0i) freqMin = binfreq;
Chris@35 1368 if (q == q1i) freqMax = binfreq;
Chris@37 1369
Chris@114 1370 if (peaksOnly && !fft->isLocalPeak(s, q)) continue;
Chris@38 1371
Chris@1086 1372 if (!fft->isOverThreshold
Chris@1087 1373 (s, q, float(m_threshold * double(getFFTSize())/2.0))) {
Chris@1086 1374 continue;
Chris@1086 1375 }
Chris@907 1376
Chris@907 1377 double freq = binfreq;
Chris@40 1378
Chris@114 1379 if (s < int(fft->getWidth()) - 1) {
Chris@38 1380
Chris@277 1381 fft->estimateStableFrequency(s, q, freq);
Chris@35 1382
Chris@38 1383 if (!haveAdj || freq < adjFreqMin) adjFreqMin = freq;
Chris@38 1384 if (!haveAdj || freq > adjFreqMax) adjFreqMax = freq;
Chris@35 1385
Chris@35 1386 haveAdj = true;
Chris@35 1387 }
Chris@35 1388 }
Chris@35 1389 }
Chris@35 1390
Chris@35 1391 if (!haveAdj) {
Chris@40 1392 adjFreqMin = adjFreqMax = 0.0;
Chris@35 1393 }
Chris@35 1394
Chris@35 1395 return haveAdj;
Chris@35 1396 }
Chris@0 1397
Chris@0 1398 bool
Chris@918 1399 SpectrogramLayer::getXYBinSourceRange(LayerGeometryProvider *v, int x, int y,
Chris@905 1400 double &min, double &max,
Chris@905 1401 double &phaseMin, double &phaseMax) const
Chris@0 1402 {
Chris@277 1403 if (!m_model || !m_model->isOK() || !m_model->isReady()) {
Chris@277 1404 return false;
Chris@277 1405 }
Chris@277 1406
Chris@905 1407 double q0 = 0, q1 = 0;
Chris@44 1408 if (!getYBinRange(v, y, q0, q1)) return false;
Chris@0 1409
Chris@905 1410 double s0 = 0, s1 = 0;
Chris@44 1411 if (!getXBinRange(v, x, s0, s1)) return false;
Chris@0 1412
Chris@0 1413 int q0i = int(q0 + 0.001);
Chris@0 1414 int q1i = int(q1);
Chris@0 1415
Chris@0 1416 int s0i = int(s0 + 0.001);
Chris@0 1417 int s1i = int(s1);
Chris@0 1418
Chris@37 1419 bool rv = false;
Chris@37 1420
Chris@1088 1421 FFTModel *fft = getFFTModel();
Chris@0 1422
Chris@114 1423 if (fft) {
Chris@114 1424
Chris@114 1425 int cw = fft->getWidth();
Chris@114 1426 int ch = fft->getHeight();
Chris@0 1427
Chris@110 1428 min = 0.0;
Chris@110 1429 max = 0.0;
Chris@110 1430 phaseMin = 0.0;
Chris@110 1431 phaseMax = 0.0;
Chris@110 1432 bool have = false;
Chris@0 1433
Chris@110 1434 for (int q = q0i; q <= q1i; ++q) {
Chris@110 1435 for (int s = s0i; s <= s1i; ++s) {
Chris@110 1436 if (s >= 0 && q >= 0 && s < cw && q < ch) {
Chris@117 1437
Chris@905 1438 double value;
Chris@38 1439
Chris@114 1440 value = fft->getPhaseAt(s, q);
Chris@110 1441 if (!have || value < phaseMin) { phaseMin = value; }
Chris@110 1442 if (!have || value > phaseMax) { phaseMax = value; }
Chris@91 1443
Chris@1087 1444 value = fft->getMagnitudeAt(s, q) / (getFFTSize()/2.0);
Chris@110 1445 if (!have || value < min) { min = value; }
Chris@110 1446 if (!have || value > max) { max = value; }
Chris@110 1447
Chris@110 1448 have = true;
Chris@110 1449 }
Chris@110 1450 }
Chris@110 1451 }
Chris@110 1452
Chris@110 1453 if (have) {
Chris@110 1454 rv = true;
Chris@110 1455 }
Chris@0 1456 }
Chris@0 1457
Chris@37 1458 return rv;
Chris@0 1459 }
Chris@114 1460
Chris@130 1461 FFTModel *
Chris@1088 1462 SpectrogramLayer::getFFTModel() const
Chris@114 1463 {
Chris@114 1464 if (!m_model) return 0;
Chris@114 1465
Chris@1087 1466 int fftSize = getFFTSize();
Chris@114 1467
Chris@1088 1468 //!!! it is now surely slower to do this on every getFFTModel()
Chris@1088 1469 //!!! request than it would be to recreate the model immediately
Chris@1088 1470 //!!! when something changes instead of just invalidating it
Chris@920 1471
Chris@1088 1472 if (m_fftModel &&
Chris@1088 1473 m_fftModel->getHeight() == fftSize / 2 + 1 &&
Chris@1088 1474 m_fftModel->getWindowIncrement() == getWindowIncrement()) {
Chris@1088 1475 return m_fftModel;
Chris@114 1476 }
Chris@1088 1477
Chris@1088 1478 delete m_peakCache;
Chris@1088 1479 m_peakCache = 0;
Chris@1088 1480
Chris@1088 1481 delete m_fftModel;
Chris@1088 1482 m_fftModel = new FFTModel(m_model,
Chris@1088 1483 m_channel,
Chris@1088 1484 m_windowType,
Chris@1088 1485 m_windowSize,
Chris@1088 1486 getWindowIncrement(),
Chris@1088 1487 fftSize);
Chris@1088 1488
Chris@1088 1489 if (!m_fftModel->isOK()) {
Chris@1088 1490 QMessageBox::critical
Chris@1088 1491 (0, tr("FFT cache failed"),
Chris@1088 1492 tr("Failed to create the FFT model for this spectrogram.\n"
Chris@1088 1493 "There may be insufficient memory or disc space to continue."));
Chris@1088 1494 delete m_fftModel;
Chris@1088 1495 m_fftModel = 0;
Chris@1088 1496 return 0;
Chris@114 1497 }
Chris@114 1498
Chris@1088 1499 ((SpectrogramLayer *)this)->sliceableModelReplaced(0, m_fftModel);
Chris@1088 1500
Chris@1088 1501 return m_fftModel;
Chris@114 1502 }
Chris@114 1503
Chris@484 1504 Dense3DModelPeakCache *
Chris@1088 1505 SpectrogramLayer::getPeakCache() const
Chris@484 1506 {
Chris@1088 1507 //!!! see comment in getFFTModel
Chris@1088 1508
Chris@1088 1509 if (!m_peakCache) {
Chris@1088 1510 FFTModel *f = getFFTModel();
Chris@484 1511 if (!f) return 0;
Chris@1088 1512 m_peakCache = new Dense3DModelPeakCache(f, m_peakCacheDivisor);
Chris@484 1513 }
Chris@1088 1514 return m_peakCache;
Chris@484 1515 }
Chris@484 1516
Chris@193 1517 const Model *
Chris@193 1518 SpectrogramLayer::getSliceableModel() const
Chris@193 1519 {
Chris@1088 1520 return m_fftModel;
Chris@193 1521 }
Chris@193 1522
Chris@114 1523 void
Chris@1088 1524 SpectrogramLayer::invalidateFFTModel()
Chris@114 1525 {
Chris@1044 1526 #ifdef DEBUG_SPECTROGRAM
Chris@1088 1527 cerr << "SpectrogramLayer::invalidateFFTModel called" << endl;
Chris@1044 1528 #endif
Chris@1088 1529
Chris@1088 1530 emit sliceableModelReplaced(m_fftModel, 0);
Chris@1088 1531
Chris@1088 1532 delete m_fftModel;
Chris@1088 1533 delete m_peakCache;
Chris@1088 1534
Chris@1088 1535 m_fftModel = 0;
Chris@1088 1536 m_peakCache = 0;
Chris@114 1537 }
Chris@114 1538
Chris@0 1539 void
Chris@119 1540 SpectrogramLayer::invalidateMagnitudes()
Chris@119 1541 {
Chris@1044 1542 #ifdef DEBUG_SPECTROGRAM
Chris@1044 1543 cerr << "SpectrogramLayer::invalidateMagnitudes called" << endl;
Chris@1044 1544 #endif
Chris@119 1545 m_viewMags.clear();
Chris@1025 1546 for (vector<MagnitudeRange>::iterator i = m_columnMags.begin();
Chris@119 1547 i != m_columnMags.end(); ++i) {
Chris@119 1548 *i = MagnitudeRange();
Chris@119 1549 }
Chris@119 1550 }
Chris@119 1551
Chris@119 1552 bool
Chris@918 1553 SpectrogramLayer::updateViewMagnitudes(LayerGeometryProvider *v) const
Chris@119 1554 {
Chris@119 1555 MagnitudeRange mag;
Chris@119 1556
Chris@918 1557 int x0 = 0, x1 = v->getPaintWidth();
Chris@905 1558 double s00 = 0, s01 = 0, s10 = 0, s11 = 0;
Chris@119 1559
Chris@203 1560 if (!getXBinRange(v, x0, s00, s01)) {
Chris@907 1561 s00 = s01 = double(m_model->getStartFrame()) / getWindowIncrement();
Chris@203 1562 }
Chris@203 1563
Chris@203 1564 if (!getXBinRange(v, x1, s10, s11)) {
Chris@907 1565 s10 = s11 = double(m_model->getEndFrame()) / getWindowIncrement();
Chris@203 1566 }
Chris@119 1567
Chris@1025 1568 int s0 = int(min(s00, s10) + 0.0001);
Chris@1025 1569 int s1 = int(max(s01, s11) + 0.0001);
Chris@203 1570
Chris@587 1571 // SVDEBUG << "SpectrogramLayer::updateViewMagnitudes: x0 = " << x0 << ", x1 = " << x1 << ", s00 = " << s00 << ", s11 = " << s11 << " s0 = " << s0 << ", s1 = " << s1 << endl;
Chris@119 1572
Chris@248 1573 if (int(m_columnMags.size()) <= s1) {
Chris@119 1574 m_columnMags.resize(s1 + 1);
Chris@119 1575 }
Chris@119 1576
Chris@119 1577 for (int s = s0; s <= s1; ++s) {
Chris@119 1578 if (m_columnMags[s].isSet()) {
Chris@119 1579 mag.sample(m_columnMags[s]);
Chris@119 1580 }
Chris@119 1581 }
Chris@119 1582
Chris@184 1583 #ifdef DEBUG_SPECTROGRAM_REPAINT
Chris@985 1584 cerr << "SpectrogramLayer::updateViewMagnitudes returning from cols "
Chris@1044 1585 << s0 << " -> " << s1 << " inclusive" << endl;
Chris@1044 1586 cerr << "SpectrogramLayer::updateViewMagnitudes: for view id " << v->getId()
Chris@1044 1587 << ": min is " << mag.getMin() << ", max is " << mag.getMax() << endl;
Chris@184 1588 #endif
Chris@119 1589
Chris@119 1590 if (!mag.isSet()) return false;
Chris@1030 1591 if (mag == m_viewMags[v->getId()]) return false;
Chris@1030 1592 m_viewMags[v->getId()] = mag;
Chris@119 1593 return true;
Chris@119 1594 }
Chris@119 1595
Chris@119 1596 void
Chris@389 1597 SpectrogramLayer::setSynchronousPainting(bool synchronous)
Chris@389 1598 {
Chris@389 1599 m_synchronous = synchronous;
Chris@389 1600 }
Chris@389 1601
Chris@1030 1602 ScrollableImageCache &
Chris@1030 1603 SpectrogramLayer::getImageCacheReference(const LayerGeometryProvider *view) const
Chris@1030 1604 {
Chris@1101 1605 //!!! to go?
Chris@1030 1606 if (m_imageCaches.find(view->getId()) == m_imageCaches.end()) {
Chris@1090 1607 m_imageCaches[view->getId()] = ScrollableImageCache();
Chris@1030 1608 }
Chris@1030 1609 return m_imageCaches.at(view->getId());
Chris@1030 1610 }
Chris@1030 1611
Chris@389 1612 void
Chris@1089 1613 SpectrogramLayer::paintAlternative(LayerGeometryProvider *v, QPainter &paint, QRect rect) const
Chris@1089 1614 {
Chris@1093 1615 static int depth = 0;
Chris@1093 1616
Chris@1089 1617 Colour3DPlotRenderer *renderer = getRenderer(v);
Chris@1089 1618
Chris@1091 1619 if (m_synchronous) {
Chris@1091 1620 (void)renderer->render(v, paint, rect);
Chris@1091 1621 return;
Chris@1091 1622 }
Chris@1093 1623
Chris@1093 1624 ++depth;
Chris@1093 1625 cerr << "paint depth " << depth << endl;
Chris@1091 1626
Chris@1098 1627 (void)renderer->renderTimeConstrained(v, paint, rect);
Chris@1091 1628
Chris@1091 1629 //!!! + mag range
Chris@1091 1630
Chris@1096 1631 QRect uncached = renderer->getLargestUncachedRect();
Chris@1096 1632 if (uncached.width() > 0) {
Chris@1096 1633 cerr << "updating rect at " << uncached.x() << " width "
Chris@1096 1634 << uncached.width() << endl;
Chris@1096 1635 v->updatePaintRect(uncached);
Chris@1093 1636 }
Chris@1093 1637
Chris@1093 1638 cerr << "exiting paint depth " << depth << endl;
Chris@1093 1639 --depth;
Chris@1089 1640 }
Chris@1089 1641
Chris@1089 1642 Colour3DPlotRenderer *
Chris@1089 1643 SpectrogramLayer::getRenderer(LayerGeometryProvider *v) const
Chris@1089 1644 {
Chris@1089 1645 if (m_renderers.find(v->getId()) == m_renderers.end()) {
Chris@1089 1646
Chris@1089 1647 Colour3DPlotRenderer::Sources sources;
Chris@1089 1648 sources.verticalBinLayer = this;
Chris@1090 1649 sources.fft = getFFTModel();
Chris@1090 1650 sources.source = sources.fft;
Chris@1090 1651 sources.peaks = getPeakCache();
Chris@1089 1652
Chris@1092 1653 ColourScale::Parameters cparams;
Chris@1092 1654 cparams.colourMap = m_colourMap;
Chris@1092 1655 cparams.scale = m_colourScale;
Chris@1093 1656 cparams.threshold = m_threshold;
Chris@1093 1657 cparams.gain = m_gain;
Chris@1093 1658
Chris@1105 1659 if (m_colourScale != ColourScaleType::Phase &&
Chris@1104 1660 m_normalization == ColumnNormalization::None) {
Chris@1093 1661 cparams.gain *= 2.f / float(getFFTSize());
Chris@1093 1662 }
Chris@1089 1663
Chris@1089 1664 Colour3DPlotRenderer::Parameters params;
Chris@1092 1665 params.colourScale = ColourScale(cparams);
Chris@1089 1666 params.normalization = m_normalization;
Chris@1093 1667 params.binDisplay = m_binDisplay;
Chris@1093 1668 params.binScale = m_binScale;
Chris@1093 1669 params.alwaysOpaque = true;
Chris@1093 1670 params.invertVertical = false;
Chris@1093 1671
Chris@1093 1672 Preferences::SpectrogramSmoothing smoothing =
Chris@1093 1673 Preferences::getInstance()->getSpectrogramSmoothing();
Chris@1093 1674 params.interpolate =
Chris@1093 1675 (smoothing == Preferences::SpectrogramInterpolated ||
Chris@1093 1676 smoothing == Preferences::SpectrogramZeroPaddedAndInterpolated);
Chris@1089 1677
Chris@1089 1678 m_renderers[v->getId()] = new Colour3DPlotRenderer(sources, params);
Chris@1089 1679 }
Chris@1089 1680
Chris@1089 1681 return m_renderers[v->getId()];
Chris@1089 1682 }
Chris@1089 1683
Chris@1089 1684 void
Chris@916 1685 SpectrogramLayer::paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const
Chris@0 1686 {
Chris@334 1687 Profiler profiler("SpectrogramLayer::paint", false);
Chris@334 1688
Chris@0 1689 #ifdef DEBUG_SPECTROGRAM_REPAINT
Chris@1026 1690 cerr << "SpectrogramLayer::paint() entering: m_model is " << m_model << ", zoom level is " << v->getZoomLevel() << endl;
Chris@95 1691
Chris@1026 1692 cerr << "SpectrogramLayer::paint(): rect is " << rect.x() << "," << rect.y() << " " << rect.width() << "x" << rect.height() << endl;
Chris@0 1693 #endif
Chris@95 1694
Chris@907 1695 sv_frame_t startFrame = v->getStartFrame();
Chris@44 1696
Chris@0 1697 if (!m_model || !m_model->isOK() || !m_model->isReady()) {
Chris@0 1698 return;
Chris@0 1699 }
Chris@0 1700
Chris@47 1701 if (isLayerDormant(v)) {
Chris@587 1702 SVDEBUG << "SpectrogramLayer::paint(): Layer is dormant, making it undormant again" << endl;
Chris@29 1703 }
Chris@29 1704
Chris@1090 1705 paintAlternative(v, paint, rect);
Chris@1090 1706 return;
Chris@1089 1707
Chris@1089 1708 //!!!
Chris@1089 1709
Chris@48 1710 // Need to do this even if !isLayerDormant, as that could mean v
Chris@48 1711 // is not in the dormancy map at all -- we need it to be present
Chris@48 1712 // and accountable for when determining whether we need the cache
Chris@48 1713 // in the cache-fill thread above.
Chris@806 1714 //!!! no inter use cache-fill thread
Chris@131 1715 const_cast<SpectrogramLayer *>(this)->Layer::setLayerDormant(v, false);
Chris@48 1716
Chris@1087 1717 int fftSize = getFFTSize();
Chris@920 1718
Chris@920 1719 const View *view = v->getView();
Chris@1030 1720 ScrollableImageCache &cache = getImageCacheReference(view);
Chris@95 1721
Chris@95 1722 #ifdef DEBUG_SPECTROGRAM_REPAINT
Chris@1030 1723 cerr << "SpectrogramLayer::paint(): image cache valid area from " << cache.getValidLeft() << " width " << cache.getValidWidth() << ", height " << cache.getSize().height() << endl;
Chris@1030 1724 if (rect.x() + rect.width() + 1 < cache.getValidLeft() ||
Chris@1030 1725 rect.x() > cache.getValidRight()) {
Chris@1030 1726 cerr << "SpectrogramLayer: NOTE: requested rect is not contiguous with cache valid area" << endl;
Chris@1030 1727 }
Chris@95 1728 #endif
Chris@95 1729
Chris@44 1730 int zoomLevel = v->getZoomLevel();
Chris@0 1731
Chris@1030 1732 int x0 = v->getXForViewX(rect.x());
Chris@1030 1733 int x1 = v->getXForViewX(rect.x() + rect.width());
Chris@1030 1734 if (x0 < 0) x0 = 0;
Chris@1030 1735 if (x1 > v->getPaintWidth()) x1 = v->getPaintWidth();
Chris@1022 1736
Chris@1022 1737 if (updateViewMagnitudes(v)) {
Chris@1022 1738 #ifdef DEBUG_SPECTROGRAM_REPAINT
Chris@1030 1739 cerr << "SpectrogramLayer: magnitude range changed to [" << m_viewMags[v->getId()].getMin() << "->" << m_viewMags[v->getId()].getMax() << "]" << endl;
Chris@1022 1740 #endif
Chris@1104 1741 if (m_normalizeVisibleArea) {
Chris@1030 1742 cache.invalidate();
Chris@1022 1743 }
Chris@1022 1744 }
Chris@1030 1745
Chris@1030 1746 if (cache.getZoomLevel() != zoomLevel ||
Chris@1030 1747 cache.getSize() != v->getPaintSize()) {
Chris@1031 1748 #ifdef DEBUG_SPECTROGRAM_REPAINT
Chris@1031 1749 cerr << "SpectrogramLayer: resizing image cache from "
Chris@1031 1750 << cache.getSize().width() << "x" << cache.getSize().height()
Chris@1031 1751 << " to "
Chris@1031 1752 << v->getPaintSize().width() << "x" << v->getPaintSize().height()
Chris@1031 1753 << " and updating zoom level from " << cache.getZoomLevel()
Chris@1031 1754 << " to " << zoomLevel
Chris@1031 1755 << endl;
Chris@1031 1756 #endif
Chris@1030 1757 cache.resize(v->getPaintSize());
Chris@1031 1758 cache.setZoomLevel(zoomLevel);
Chris@1031 1759 cache.setStartFrame(startFrame);
Chris@1030 1760 }
Chris@1023 1761
Chris@1030 1762 if (cache.isValid()) {
Chris@482 1763
Chris@1030 1764 if (v->getXForFrame(cache.getStartFrame()) ==
Chris@1030 1765 v->getXForFrame(startFrame) &&
Chris@1030 1766 cache.getValidLeft() <= x0 &&
Chris@1030 1767 cache.getValidRight() >= x1) {
Chris@1022 1768
Chris@0 1769 #ifdef DEBUG_SPECTROGRAM_REPAINT
Chris@1030 1770 cerr << "SpectrogramLayer: image cache hit!" << endl;
Chris@0 1771 #endif
Chris@0 1772
Chris@1030 1773 paint.drawImage(rect, cache.getImage(), rect);
Chris@1030 1774
Chris@1030 1775 illuminateLocalFeatures(v, paint);
Chris@1030 1776 return;
Chris@1030 1777
Chris@1030 1778 } else {
Chris@1030 1779
Chris@1030 1780 // cache doesn't begin at the right frame or doesn't
Chris@1030 1781 // contain the complete view, but might be scrollable or
Chris@1030 1782 // partially usable
Chris@1022 1783
Chris@0 1784 #ifdef DEBUG_SPECTROGRAM_REPAINT
Chris@1030 1785 cerr << "SpectrogramLayer: scrolling the image cache if applicable" << endl;
Chris@0 1786 #endif
Chris@0 1787
Chris@1090 1788 cache.scrollTo(v, startFrame);
Chris@1030 1789
Chris@0 1790 #ifdef DEBUG_SPECTROGRAM_REPAINT
Chris@1031 1791 cerr << "SpectrogramLayer: after scrolling, cache valid from "
Chris@1030 1792 << cache.getValidLeft() << " width " << cache.getValidWidth()
Chris@1030 1793 << endl;
Chris@0 1794 #endif
Chris@1030 1795 }
Chris@0 1796 }
Chris@95 1797
Chris@1030 1798 bool rightToLeft = false;
Chris@1030 1799
Chris@1030 1800 if (!cache.isValid()) {
Chris@1028 1801 if (!m_synchronous) {
Chris@1028 1802 // When rendering the whole thing, start from somewhere near
Chris@1028 1803 // the middle so that the region of interest appears first
Chris@1038 1804
Chris@1038 1805 //!!! (perhaps we should have some cunning test to avoid
Chris@1038 1806 //!!! doing this if past repaints have appeared fast
Chris@1038 1807 //!!! enough to do the whole width in one shot)
Chris@1030 1808 if (x0 == 0 && x1 == v->getPaintWidth()) {
Chris@1037 1809 x0 = int(x1 * 0.3);
Chris@1030 1810 }
Chris@1028 1811 }
Chris@1030 1812 } else {
Chris@1030 1813 // When rendering only a part of the cache, we need to make
Chris@1030 1814 // sure that the part we're rendering is adjacent to (or
Chris@1030 1815 // overlapping) a valid area of cache, if we have one. The
Chris@1030 1816 // alternative is to ditch the valid area of cache and render
Chris@1030 1817 // only the requested area, but that's risky because this can
Chris@1030 1818 // happen when just waving the pointer over a small part of
Chris@1030 1819 // the view -- if we lose the partly-built cache every time
Chris@1030 1820 // the user does that, we'll never finish building it.
Chris@1030 1821 int left = x0;
Chris@1030 1822 int width = x1 - x0;
Chris@1030 1823 bool isLeftOfValidArea = false;
Chris@1031 1824 cache.adjustToTouchValidArea(left, width, isLeftOfValidArea);
Chris@1030 1825 x0 = left;
Chris@1030 1826 x1 = x0 + width;
Chris@1030 1827
Chris@1030 1828 // That call also told us whether we should be painting
Chris@1030 1829 // sub-regions of our target region in right-to-left order in
Chris@1030 1830 // order to ensure contiguity
Chris@1030 1831 rightToLeft = isLeftOfValidArea;
Chris@95 1832 }
Chris@1030 1833
Chris@224 1834 // We always paint the full height when refreshing the cache.
Chris@224 1835 // Smaller heights can be used when painting direct from cache
Chris@224 1836 // (further up in this function), but we want to ensure the cache
Chris@224 1837 // is coherent without having to worry about vertical matching of
Chris@224 1838 // required and valid areas as well as horizontal.
Chris@918 1839 int h = v->getPaintHeight();
Chris@1025 1840
Chris@1024 1841 int repaintWidth = x1 - x0;
Chris@0 1842
Chris@95 1843 #ifdef DEBUG_SPECTROGRAM_REPAINT
Chris@1026 1844 cerr << "SpectrogramLayer: x0 " << x0 << ", x1 " << x1
Chris@1026 1845 << ", repaintWidth " << repaintWidth << ", h " << h
Chris@1030 1846 << ", rightToLeft " << rightToLeft << endl;
Chris@95 1847 #endif
Chris@95 1848
Chris@907 1849 sv_samplerate_t sr = m_model->getSampleRate();
Chris@122 1850
Chris@122 1851 // Set minFreq and maxFreq to the frequency extents of the possibly
Chris@122 1852 // zero-padded visible bin range, and displayMinFreq and displayMaxFreq
Chris@122 1853 // to the actual scale frequency extents (presumably not zero padded).
Chris@253 1854
Chris@1086 1855 // If we are zero padding (i.e. oversampling) we want to use the
Chris@1086 1856 // zero-padded equivalents of the bins that we would be using if
Chris@1086 1857 // not zero padded, to avoid spaces at the top and bottom of the
Chris@1086 1858 // display.
Chris@35 1859
Chris@1087 1860 int maxbin = fftSize / 2;
Chris@35 1861 if (m_maxFrequency > 0) {
Chris@1087 1862 maxbin = int((double(m_maxFrequency) * fftSize) / sr + 0.001);
Chris@1087 1863 if (maxbin > fftSize / 2) maxbin = fftSize / 2;
Chris@35 1864 }
Chris@111 1865
Chris@805 1866 int minbin = 1;
Chris@37 1867 if (m_minFrequency > 0) {
Chris@1087 1868 minbin = int((double(m_minFrequency) * fftSize) / sr + 0.001);
Chris@682 1869 // cerr << "m_minFrequency = " << m_minFrequency << " -> minbin = " << minbin << endl;
Chris@40 1870 if (minbin < 1) minbin = 1;
Chris@184 1871 if (minbin >= maxbin) minbin = maxbin - 1;
Chris@37 1872 }
Chris@37 1873
Chris@1086 1874 int over = getFFTOversampling();
Chris@1086 1875 minbin = minbin * over;
Chris@1086 1876 maxbin = (maxbin + 1) * over - 1;
Chris@253 1877
Chris@905 1878 double minFreq = (double(minbin) * sr) / fftSize;
Chris@905 1879 double maxFreq = (double(maxbin) * sr) / fftSize;
Chris@905 1880
Chris@905 1881 double displayMinFreq = minFreq;
Chris@905 1882 double displayMaxFreq = maxFreq;
Chris@122 1883
Chris@1087 1884 //!!! if (fftSize != getFFTSize()) {
Chris@1087 1885 // displayMinFreq = getEffectiveMinFrequency();
Chris@1087 1886 // displayMaxFreq = getEffectiveMaxFrequency();
Chris@1087 1887 // }
Chris@122 1888
Chris@682 1889 // cerr << "(giving actual minFreq " << minFreq << " and display minFreq " << displayMinFreq << ")" << endl;
Chris@253 1890
Chris@518 1891 int increment = getWindowIncrement();
Chris@40 1892
Chris@1103 1893 bool logarithmic = (m_binScale == BinScale::Log);
Chris@1026 1894
Chris@1030 1895 MagnitudeRange overallMag = m_viewMags[v->getId()];
Chris@119 1896 bool overallMagChanged = false;
Chris@119 1897
Chris@137 1898 #ifdef DEBUG_SPECTROGRAM_REPAINT
Chris@1026 1899 cerr << "SpectrogramLayer: " << ((double(v->getFrameForX(1) - v->getFrameForX(0))) / increment) << " bin(s) per pixel" << endl;
Chris@137 1900 #endif
Chris@137 1901
Chris@1024 1902 if (repaintWidth == 0) {
Chris@1024 1903 SVDEBUG << "*** NOTE: repaintWidth == 0" << endl;
Chris@331 1904 }
Chris@331 1905
Chris@382 1906 Profiler outerprof("SpectrogramLayer::paint: all cols");
Chris@382 1907
Chris@481 1908 // The draw buffer contains a fragment at either our pixel
Chris@481 1909 // resolution (if there is more than one time-bin per pixel) or
Chris@481 1910 // time-bin resolution (if a time-bin spans more than one pixel).
Chris@481 1911 // We need to ensure that it starts and ends at points where a
Chris@481 1912 // time-bin boundary occurs at an exact pixel boundary, and with a
Chris@481 1913 // certain amount of overlap across existing pixels so that we can
Chris@481 1914 // scale and draw from it without smoothing errors at the edges.
Chris@481 1915
Chris@481 1916 // If (getFrameForX(x) / increment) * increment ==
Chris@481 1917 // getFrameForX(x), then x is a time-bin boundary. We want two
Chris@481 1918 // such boundaries at either side of the draw buffer -- one which
Chris@481 1919 // we draw up to, and one which we subsequently crop at.
Chris@481 1920
Chris@1039 1921 bool bufferIsBinResolution = false;
Chris@1039 1922 if (increment > zoomLevel) bufferIsBinResolution = true;
Chris@481 1923
Chris@907 1924 sv_frame_t leftBoundaryFrame = -1, leftCropFrame = -1;
Chris@907 1925 sv_frame_t rightBoundaryFrame = -1, rightCropFrame = -1;
Chris@481 1926
Chris@481 1927 int bufwid;
Chris@481 1928
Chris@1039 1929 if (bufferIsBinResolution) {
Chris@481 1930
Chris@482 1931 for (int x = x0; ; --x) {
Chris@907 1932 sv_frame_t f = v->getFrameForX(x);
Chris@481 1933 if ((f / increment) * increment == f) {
Chris@481 1934 if (leftCropFrame == -1) leftCropFrame = f;
Chris@1024 1935 else if (x < x0 - 2) {
Chris@1024 1936 leftBoundaryFrame = f;
Chris@1024 1937 break;
Chris@1024 1938 }
Chris@481 1939 }
Chris@481 1940 }
Chris@1024 1941 for (int x = x0 + repaintWidth; ; ++x) {
Chris@907 1942 sv_frame_t f = v->getFrameForX(x);
Chris@481 1943 if ((f / increment) * increment == f) {
Chris@481 1944 if (rightCropFrame == -1) rightCropFrame = f;
Chris@1024 1945 else if (x > x0 + repaintWidth + 2) {
Chris@1024 1946 rightBoundaryFrame = f;
Chris@1024 1947 break;
Chris@1024 1948 }
Chris@481 1949 }
Chris@481 1950 }
Chris@485 1951 #ifdef DEBUG_SPECTROGRAM_REPAINT
Chris@481 1952 cerr << "Left: crop: " << leftCropFrame << " (bin " << leftCropFrame/increment << "); boundary: " << leftBoundaryFrame << " (bin " << leftBoundaryFrame/increment << ")" << endl;
Chris@481 1953 cerr << "Right: crop: " << rightCropFrame << " (bin " << rightCropFrame/increment << "); boundary: " << rightBoundaryFrame << " (bin " << rightBoundaryFrame/increment << ")" << endl;
Chris@485 1954 #endif
Chris@481 1955
Chris@907 1956 bufwid = int((rightBoundaryFrame - leftBoundaryFrame) / increment);
Chris@481 1957
Chris@481 1958 } else {
Chris@481 1959
Chris@1024 1960 bufwid = repaintWidth;
Chris@481 1961 }
Chris@481 1962
Chris@907 1963 vector<int> binforx(bufwid);
Chris@907 1964 vector<double> binfory(h);
Chris@907 1965
Chris@484 1966 bool usePeaksCache = false;
Chris@484 1967
Chris@1039 1968 if (bufferIsBinResolution) {
Chris@481 1969 for (int x = 0; x < bufwid; ++x) {
Chris@907 1970 binforx[x] = int(leftBoundaryFrame / increment) + x;
Chris@481 1971 }
Chris@481 1972 m_drawBuffer = QImage(bufwid, h, QImage::Format_Indexed8);
Chris@481 1973 } else {
Chris@481 1974 for (int x = 0; x < bufwid; ++x) {
Chris@905 1975 double s0 = 0, s1 = 0;
Chris@481 1976 if (getXBinRange(v, x + x0, s0, s1)) {
Chris@481 1977 binforx[x] = int(s0 + 0.0001);
Chris@481 1978 } else {
Chris@487 1979 binforx[x] = -1; //???
Chris@481 1980 }
Chris@481 1981 }
Chris@1031 1982 if (m_drawBuffer.width() < bufwid || m_drawBuffer.height() != h) {
Chris@481 1983 m_drawBuffer = QImage(bufwid, h, QImage::Format_Indexed8);
Chris@480 1984 }
Chris@1054 1985 usePeaksCache = (increment * m_peakCacheDivisor) < zoomLevel;
Chris@1105 1986 if (m_colourScale == ColourScaleType::Phase) usePeaksCache = false;
Chris@480 1987 }
Chris@481 1988
Chris@481 1989 for (int pixel = 0; pixel < 256; ++pixel) {
Chris@907 1990 m_drawBuffer.setColor((unsigned char)pixel,
Chris@907 1991 m_palette.getColour((unsigned char)pixel).rgb());
Chris@481 1992 }
Chris@481 1993
Chris@481 1994 m_drawBuffer.fill(0);
Chris@1024 1995 int attainedBufwid = bufwid;
Chris@1039 1996
Chris@1039 1997 double softTimeLimit;
Chris@1039 1998
Chris@1039 1999 if (m_synchronous) {
Chris@1039 2000
Chris@1039 2001 // must paint the whole thing for synchronous mode, so give
Chris@1039 2002 // "no timeout"
Chris@1039 2003 softTimeLimit = 0.0;
Chris@1039 2004
Chris@1039 2005 } else if (bufferIsBinResolution) {
Chris@1039 2006
Chris@1039 2007 // calculating boundaries later will be too fiddly for partial
Chris@1039 2008 // paints, and painting should be fast anyway when this is the
Chris@1039 2009 // case because it means we're well zoomed in
Chris@1039 2010 softTimeLimit = 0.0;
Chris@1039 2011
Chris@1039 2012 } else {
Chris@1039 2013
Chris@1039 2014 // neither limitation applies, so use a short soft limit
Chris@1039 2015
Chris@1103 2016 if (m_binDisplay == BinDisplay::PeakFrequencies) {
Chris@1039 2017 softTimeLimit = 0.15;
Chris@1039 2018 } else {
Chris@1039 2019 softTimeLimit = 0.1;
Chris@1039 2020 }
Chris@1039 2021 }
Chris@1039 2022
Chris@1103 2023 if (m_binDisplay != BinDisplay::PeakFrequencies) {
Chris@488 2024
Chris@488 2025 for (int y = 0; y < h; ++y) {
Chris@905 2026 double q0 = 0, q1 = 0;
Chris@1086 2027 if (!getYBinRange(v, h-y-1, q0, q1)) {
Chris@488 2028 binfory[y] = -1;
Chris@488 2029 } else {
Chris@490 2030 binfory[y] = q0;
Chris@488 2031 }
Chris@480 2032 }
Chris@488 2033
Chris@1024 2034 attainedBufwid =
Chris@1026 2035 paintDrawBuffer(v, bufwid, h, binforx, binfory,
Chris@1026 2036 usePeaksCache,
Chris@1026 2037 overallMag, overallMagChanged,
Chris@1039 2038 rightToLeft,
Chris@1039 2039 softTimeLimit);
Chris@488 2040
Chris@488 2041 } else {
Chris@488 2042
Chris@1024 2043 attainedBufwid =
Chris@1024 2044 paintDrawBufferPeakFrequencies(v, bufwid, h, binforx,
Chris@1024 2045 minbin, maxbin,
Chris@1024 2046 displayMinFreq, displayMaxFreq,
Chris@1024 2047 logarithmic,
Chris@1026 2048 overallMag, overallMagChanged,
Chris@1039 2049 rightToLeft,
Chris@1039 2050 softTimeLimit);
Chris@480 2051 }
Chris@481 2052
Chris@1024 2053 int failedToRepaint = bufwid - attainedBufwid;
Chris@1031 2054
Chris@1031 2055 int paintedLeft = x0;
Chris@1031 2056 int paintedWidth = x1 - x0;
Chris@1031 2057
Chris@1025 2058 if (failedToRepaint > 0) {
Chris@1031 2059
Chris@1025 2060 #ifdef DEBUG_SPECTROGRAM_REPAINT
Chris@1026 2061 cerr << "SpectrogramLayer::paint(): Failed to repaint " << failedToRepaint << " of " << bufwid
Chris@1029 2062 << " columns in time (so managed to repaint " << bufwid - failedToRepaint << ")" << endl;
Chris@1025 2063 #endif
Chris@1031 2064
Chris@1031 2065 if (rightToLeft) {
Chris@1031 2066 paintedLeft += failedToRepaint;
Chris@1031 2067 }
Chris@1031 2068
Chris@1031 2069 paintedWidth -= failedToRepaint;
Chris@1031 2070
Chris@1031 2071 if (paintedWidth < 0) {
Chris@1031 2072 paintedWidth = 0;
Chris@1031 2073 }
Chris@1031 2074
Chris@1025 2075 } else if (failedToRepaint < 0) {
Chris@1024 2076 cerr << "WARNING: failedToRepaint < 0 (= " << failedToRepaint << ")"
Chris@1024 2077 << endl;
Chris@1024 2078 failedToRepaint = 0;
Chris@1024 2079 }
Chris@1031 2080
Chris@119 2081 if (overallMagChanged) {
Chris@1030 2082 m_viewMags[v->getId()] = overallMag;
Chris@209 2083 #ifdef DEBUG_SPECTROGRAM_REPAINT
Chris@1030 2084 cerr << "SpectrogramLayer: Overall mag is now [" << m_viewMags[v->getId()].getMin() << "->" << m_viewMags[v->getId()].getMax() << "] - will be updating" << endl;
Chris@209 2085 #endif
Chris@119 2086 }
Chris@119 2087
Chris@382 2088 outerprof.end();
Chris@382 2089
Chris@382 2090 Profiler profiler2("SpectrogramLayer::paint: draw image");
Chris@137 2091
Chris@1031 2092 if (paintedWidth > 0) {
Chris@1024 2093
Chris@224 2094 #ifdef DEBUG_SPECTROGRAM_REPAINT
Chris@1031 2095 cerr << "SpectrogramLayer: Copying " << paintedWidth << "x" << h
Chris@1031 2096 << " from draw buffer at " << paintedLeft - x0 << "," << 0
Chris@1031 2097 << " to " << paintedWidth << "x" << h << " on cache at "
Chris@585 2098 << x0 << "," << 0 << endl;
Chris@224 2099 #endif
Chris@224 2100
Chris@1039 2101 if (bufferIsBinResolution) {
Chris@1031 2102
Chris@481 2103 int scaledLeft = v->getXForFrame(leftBoundaryFrame);
Chris@481 2104 int scaledRight = v->getXForFrame(rightBoundaryFrame);
Chris@1031 2105
Chris@485 2106 #ifdef DEBUG_SPECTROGRAM_REPAINT
Chris@1026 2107 cerr << "SpectrogramLayer: Rescaling image from " << bufwid
Chris@481 2108 << "x" << h << " to "
Chris@481 2109 << scaledRight-scaledLeft << "x" << h << endl;
Chris@485 2110 #endif
Chris@1031 2111
Chris@490 2112 Preferences::SpectrogramXSmoothing xsmoothing =
Chris@490 2113 Preferences::getInstance()->getSpectrogramXSmoothing();
Chris@1026 2114
Chris@481 2115 QImage scaled = m_drawBuffer.scaled
Chris@481 2116 (scaledRight - scaledLeft, h,
Chris@490 2117 Qt::IgnoreAspectRatio,
Chris@490 2118 ((xsmoothing == Preferences::SpectrogramXInterpolated) ?
Chris@490 2119 Qt::SmoothTransformation : Qt::FastTransformation));
Chris@1026 2120
Chris@481 2121 int scaledLeftCrop = v->getXForFrame(leftCropFrame);
Chris@481 2122 int scaledRightCrop = v->getXForFrame(rightCropFrame);
Chris@1031 2123
Chris@485 2124 #ifdef DEBUG_SPECTROGRAM_REPAINT
Chris@1026 2125 cerr << "SpectrogramLayer: Drawing image region of width " << scaledRightCrop - scaledLeftCrop << " to "
Chris@481 2126 << scaledLeftCrop << " from " << scaledLeftCrop - scaledLeft << endl;
Chris@485 2127 #endif
Chris@1030 2128
Chris@1040 2129 int targetLeft = scaledLeftCrop;
Chris@1040 2130 if (targetLeft < 0) {
Chris@1040 2131 targetLeft = 0;
Chris@1040 2132 }
Chris@1040 2133
Chris@1040 2134 int targetWidth = scaledRightCrop - targetLeft;
Chris@1040 2135 if (targetLeft + targetWidth > cache.getSize().width()) {
Chris@1040 2136 targetWidth = cache.getSize().width() - targetLeft;
Chris@1040 2137 }
Chris@1031 2138
Chris@1040 2139 int sourceLeft = targetLeft - scaledLeft;
Chris@1040 2140 if (sourceLeft < 0) {
Chris@1040 2141 sourceLeft = 0;
Chris@1040 2142 }
Chris@1040 2143
Chris@1040 2144 int sourceWidth = targetWidth;
Chris@1040 2145
Chris@1040 2146 if (targetWidth > 0) {
Chris@1040 2147 cache.drawImage
Chris@1040 2148 (targetLeft,
Chris@1040 2149 targetWidth,
Chris@1040 2150 scaled,
Chris@1040 2151 sourceLeft,
Chris@1040 2152 sourceWidth);
Chris@1040 2153 }
Chris@1024 2154
Chris@481 2155 } else {
Chris@1024 2156
Chris@1031 2157 cache.drawImage(paintedLeft, paintedWidth,
Chris@1030 2158 m_drawBuffer,
Chris@1031 2159 paintedLeft - x0, paintedWidth);
Chris@481 2160 }
Chris@331 2161 }
Chris@331 2162
Chris@1026 2163 #ifdef DEBUG_SPECTROGRAM_REPAINT
Chris@1030 2164 cerr << "SpectrogramLayer: Cache valid area now from " << cache.getValidLeft()
Chris@1030 2165 << " width " << cache.getValidWidth() << ", height "
Chris@1030 2166 << cache.getSize().height() << endl;
Chris@1026 2167 #endif
Chris@1030 2168
Chris@1030 2169 QRect pr = rect & cache.getValidArea();
Chris@337 2170
Chris@337 2171 #ifdef DEBUG_SPECTROGRAM_REPAINT
Chris@1026 2172 cerr << "SpectrogramLayer: Copying " << pr.width() << "x" << pr.height()
Chris@337 2173 << " from cache at " << pr.x() << "," << pr.y()
Chris@585 2174 << " to window" << endl;
Chris@337 2175 #endif
Chris@337 2176
Chris@1030 2177 paint.drawImage(pr.x(), pr.y(), cache.getImage(),
Chris@479 2178 pr.x(), pr.y(), pr.width(), pr.height());
Chris@337 2179
Chris@389 2180 if (!m_synchronous) {
Chris@389 2181
Chris@1104 2182 if (!m_normalizeVisibleArea || !overallMagChanged) {
Chris@1031 2183
Chris@1031 2184 QRect areaLeft(0, 0, cache.getValidLeft(), h);
Chris@1031 2185 QRect areaRight(cache.getValidRight(), 0,
Chris@1031 2186 cache.getSize().width() - cache.getValidRight(), h);
Chris@1031 2187
Chris@1031 2188 bool haveSpaceLeft = (areaLeft.width() > 0);
Chris@1031 2189 bool haveSpaceRight = (areaRight.width() > 0);
Chris@1031 2190
Chris@1031 2191 bool updateLeft = haveSpaceLeft;
Chris@1031 2192 bool updateRight = haveSpaceRight;
Chris@1031 2193
Chris@1031 2194 if (updateLeft && updateRight) {
Chris@1031 2195 if (rightToLeft) {
Chris@1031 2196 // we just did something adjoining the cache on
Chris@1031 2197 // its left side, so now do something on its right
Chris@1031 2198 updateLeft = false;
Chris@1031 2199 } else {
Chris@1031 2200 updateRight = false;
Chris@1031 2201 }
Chris@389 2202 }
Chris@389 2203
Chris@1031 2204 if (updateLeft) {
Chris@1031 2205 #ifdef DEBUG_SPECTROGRAM_REPAINT
Chris@1031 2206 cerr << "SpectrogramLayer::paint() updating left ("
Chris@1031 2207 << areaLeft.x() << ", "
Chris@1031 2208 << areaLeft.width() << ")" << endl;
Chris@1031 2209 #endif
Chris@1031 2210 v->updatePaintRect(areaLeft);
Chris@1031 2211 }
Chris@1031 2212
Chris@1031 2213 if (updateRight) {
Chris@389 2214 #ifdef DEBUG_SPECTROGRAM_REPAINT
Chris@985 2215 cerr << "SpectrogramLayer::paint() updating right ("
Chris@1031 2216 << areaRight.x() << ", "
Chris@1031 2217 << areaRight.width() << ")" << endl;
Chris@389 2218 #endif
Chris@1031 2219 v->updatePaintRect(areaRight);
Chris@389 2220 }
Chris@1031 2221
Chris@389 2222 } else {
Chris@389 2223 // overallMagChanged
Chris@682 2224 cerr << "\noverallMagChanged - updating all\n" << endl;
Chris@1030 2225 cache.invalidate();
Chris@1030 2226 v->updatePaintRect(v->getPaintRect());
Chris@119 2227 }
Chris@95 2228 }
Chris@0 2229
Chris@121 2230 illuminateLocalFeatures(v, paint);
Chris@120 2231
Chris@0 2232 #ifdef DEBUG_SPECTROGRAM_REPAINT
Chris@985 2233 cerr << "SpectrogramLayer::paint() returning" << endl;
Chris@0 2234 #endif
Chris@0 2235 }
Chris@0 2236
Chris@1024 2237 int
Chris@918 2238 SpectrogramLayer::paintDrawBufferPeakFrequencies(LayerGeometryProvider *v,
Chris@488 2239 int w,
Chris@488 2240 int h,
Chris@907 2241 const vector<int> &binforx,
Chris@488 2242 int minbin,
Chris@488 2243 int maxbin,
Chris@905 2244 double displayMinFreq,
Chris@905 2245 double displayMaxFreq,
Chris@491 2246 bool logarithmic,
Chris@491 2247 MagnitudeRange &overallMag,
Chris@1026 2248 bool &overallMagChanged,
Chris@1039 2249 bool rightToLeft,
Chris@1039 2250 double softTimeLimit) const
Chris@488 2251 {
Chris@488 2252 Profiler profiler("SpectrogramLayer::paintDrawBufferPeakFrequencies");
Chris@488 2253
Chris@488 2254 #ifdef DEBUG_SPECTROGRAM_REPAINT
Chris@1026 2255 cerr << "SpectrogramLayer::paintDrawBufferPeakFrequencies: minbin " << minbin << ", maxbin " << maxbin << "; w " << w << ", h " << h << endl;
Chris@488 2256 #endif
Chris@488 2257 if (minbin < 0) minbin = 0;
Chris@488 2258 if (maxbin < 0) maxbin = minbin+1;
Chris@488 2259
Chris@1088 2260 FFTModel *fft = getFFTModel();
Chris@1024 2261 if (!fft) return 0;
Chris@488 2262
Chris@488 2263 FFTModel::PeakSet peakfreqs;
Chris@1064 2264 vector<float> preparedColumn;
Chris@1064 2265
Chris@848 2266 int psx = -1;
Chris@545 2267
Chris@1027 2268 int minColumns = 4;
Chris@1039 2269 bool haveTimeLimits = (softTimeLimit > 0.0);
Chris@1037 2270 double hardTimeLimit = softTimeLimit * 2.0;
Chris@1037 2271 bool overridingSoftLimit = false;
Chris@1027 2272 auto startTime = chrono::steady_clock::now();
Chris@1027 2273
Chris@1026 2274 int start = 0;
Chris@1026 2275 int finish = w;
Chris@1026 2276 int step = 1;
Chris@1026 2277
Chris@1026 2278 if (rightToLeft) {
Chris@1026 2279 start = w-1;
Chris@1026 2280 finish = -1;
Chris@1026 2281 step = -1;
Chris@1026 2282 }
Chris@1026 2283
Chris@1027 2284 int columnCount = 0;
Chris@1027 2285
Chris@1026 2286 for (int x = start; x != finish; x += step) {
Chris@488 2287
Chris@1027 2288 ++columnCount;
Chris@1027 2289
Chris@488 2290 if (binforx[x] < 0) continue;
Chris@488 2291
Chris@488 2292 int sx0 = binforx[x];
Chris@488 2293 int sx1 = sx0;
Chris@488 2294 if (x+1 < w) sx1 = binforx[x+1];
Chris@488 2295 if (sx0 < 0) sx0 = sx1 - 1;
Chris@488 2296 if (sx0 < 0) continue;
Chris@488 2297 if (sx1 <= sx0) sx1 = sx0 + 1;
Chris@488 2298
Chris@1064 2299 vector<float> pixelPeakColumn;
Chris@1064 2300
Chris@488 2301 for (int sx = sx0; sx < sx1; ++sx) {
Chris@488 2302
Chris@1064 2303 if (sx < 0 || sx >= int(fft->getWidth())) {
Chris@1064 2304 continue;
Chris@1064 2305 }
Chris@488 2306
Chris@488 2307 if (sx != psx) {
Chris@1064 2308
Chris@1064 2309 ColumnOp::Column column;
Chris@1064 2310
Chris@1064 2311 column = getColumnFromFFTModel(fft,
Chris@1064 2312 sx,
Chris@1064 2313 minbin,
Chris@1064 2314 maxbin - minbin + 1);
Chris@1064 2315
Chris@1105 2316 if (m_colourScale != ColourScaleType::Phase) {
Chris@1087 2317 column = ColumnOp::fftScale(column, getFFTSize());
Chris@1064 2318 }
Chris@1064 2319
Chris@1064 2320 recordColumnExtents(column,
Chris@1064 2321 sx,
Chris@1064 2322 overallMag,
Chris@1064 2323 overallMagChanged);
Chris@1064 2324
Chris@1105 2325 if (m_colourScale != ColourScaleType::Phase) {
Chris@1064 2326 column = ColumnOp::normalize(column, m_normalization);
Chris@1064 2327 }
Chris@1064 2328
Chris@1064 2329 preparedColumn = ColumnOp::applyGain(column, m_gain);
Chris@1064 2330
Chris@1064 2331 psx = sx;
Chris@1064 2332 }
Chris@1064 2333
Chris@1064 2334 if (sx == sx0) {
Chris@1064 2335 pixelPeakColumn = preparedColumn;
Chris@488 2336 peakfreqs = fft->getPeakFrequencies(FFTModel::AllPeaks, sx,
Chris@488 2337 minbin, maxbin - 1);
Chris@1064 2338 } else {
Chris@1064 2339 for (int i = 0; in_range_for(pixelPeakColumn, i); ++i) {
Chris@1064 2340 pixelPeakColumn[i] = std::max(pixelPeakColumn[i],
Chris@1064 2341 preparedColumn[i]);
Chris@488 2342 }
Chris@488 2343 }
Chris@488 2344 }
Chris@1065 2345
Chris@1065 2346 if (!pixelPeakColumn.empty()) {
Chris@1065 2347 for (FFTModel::PeakSet::const_iterator pi = peakfreqs.begin();
Chris@1065 2348 pi != peakfreqs.end(); ++pi) {
Chris@1065 2349
Chris@1065 2350 int bin = pi->first;
Chris@1065 2351 double freq = pi->second;
Chris@1065 2352
Chris@1065 2353 if (bin < minbin) continue;
Chris@1065 2354 if (bin > maxbin) break;
Chris@1065 2355
Chris@1065 2356 double value = pixelPeakColumn[bin - minbin];
Chris@1065 2357
Chris@1065 2358 double y = v->getYForFrequency
Chris@1065 2359 (freq, displayMinFreq, displayMaxFreq, logarithmic);
Chris@1065 2360
Chris@1065 2361 int iy = int(y + 0.5);
Chris@1065 2362 if (iy < 0 || iy >= h) continue;
Chris@1065 2363
Chris@1065 2364 m_drawBuffer.setPixel(x, iy, getDisplayValue(v, value));
Chris@1065 2365 }
Chris@1065 2366 }
Chris@1064 2367
Chris@1039 2368 if (haveTimeLimits) {
Chris@1027 2369 if (columnCount >= minColumns) {
Chris@1027 2370 auto t = chrono::steady_clock::now();
Chris@1027 2371 double diff = chrono::duration<double>(t - startTime).count();
Chris@1037 2372 if (diff > hardTimeLimit) {
Chris@1027 2373 #ifdef DEBUG_SPECTROGRAM_REPAINT
Chris@1037 2374 cerr << "SpectrogramLayer::paintDrawBufferPeakFrequencies: hard limit " << hardTimeLimit << " sec exceeded after "
Chris@1031 2375 << columnCount << " columns with time " << diff << endl;
Chris@1027 2376 #endif
Chris@1027 2377 return columnCount;
Chris@1037 2378 } else if (diff > softTimeLimit && !overridingSoftLimit) {
Chris@1037 2379 // If we're more than half way through by the time
Chris@1037 2380 // we reach the soft limit, ignore it (though
Chris@1037 2381 // still respect the hard limit, above). Otherwise
Chris@1037 2382 // respect the soft limit and return now.
Chris@1037 2383 if (columnCount > w/2) {
Chris@1037 2384 overridingSoftLimit = true;
Chris@1037 2385 } else {
Chris@1037 2386 #ifdef DEBUG_SPECTROGRAM_REPAINT
Chris@1037 2387 cerr << "SpectrogramLayer::paintDrawBufferPeakFrequencies: soft limit " << softTimeLimit << " sec exceeded after "
Chris@1037 2388 << columnCount << " columns with time " << diff << endl;
Chris@1037 2389 #endif
Chris@1037 2390 return columnCount;
Chris@1037 2391 }
Chris@1037 2392 }
Chris@1027 2393 }
Chris@1027 2394 }
Chris@488 2395 }
Chris@1024 2396
Chris@1027 2397 return columnCount;
Chris@488 2398 }
Chris@488 2399
Chris@1058 2400 vector<float>
Chris@1058 2401 SpectrogramLayer::getColumnFromFFTModel(FFTModel *fft,
Chris@1058 2402 int sx, // column number in model
Chris@1058 2403 int minbin,
Chris@1058 2404 int bincount) const
Chris@1058 2405 {
Chris@1058 2406 vector<float> values(bincount, 0.f);
Chris@1058 2407
Chris@1105 2408 if (m_colourScale == ColourScaleType::Phase) {
Chris@1058 2409 fft->getPhasesAt(sx, values.data(), minbin, bincount);
Chris@1058 2410 } else {
Chris@1058 2411 fft->getMagnitudesAt(sx, values.data(), minbin, bincount);
Chris@1058 2412 }
Chris@1058 2413
Chris@1061 2414 return values;
Chris@1058 2415 }
Chris@1058 2416
Chris@1058 2417 vector<float>
Chris@1058 2418 SpectrogramLayer::getColumnFromGenericModel(DenseThreeDimensionalModel *model,
Chris@1058 2419 int sx, // column number in model
Chris@1058 2420 int minbin,
Chris@1058 2421 int bincount) const
Chris@1058 2422 {
Chris@1105 2423 if (m_colourScale == ColourScaleType::Phase) {
Chris@1058 2424 throw std::logic_error("can't use phase scale with generic 3d model");
Chris@1058 2425 }
Chris@1058 2426
Chris@1058 2427 auto col = model->getColumn(sx);
Chris@1058 2428
Chris@1061 2429 return vector<float>(col.data() + minbin,
Chris@1061 2430 col.data() + minbin + bincount);
Chris@1058 2431 }
Chris@1058 2432
Chris@1058 2433 void
Chris@1058 2434 SpectrogramLayer::recordColumnExtents(const vector<float> &col,
Chris@1058 2435 int sx, // column index, for m_columnMags
Chris@1058 2436 MagnitudeRange &overallMag,
Chris@1059 2437 bool &overallMagChanged) const
Chris@1058 2438 {
Chris@1060 2439 if (!in_range_for(m_columnMags, sx)) {
Chris@1063 2440 m_columnMags.resize(sx + 1);
Chris@1058 2441 }
Chris@1058 2442 MagnitudeRange mr;
Chris@1058 2443 for (auto v: col) {
Chris@1058 2444 mr.sample(v);
Chris@1058 2445 }
Chris@1058 2446 m_columnMags[sx] = mr;
Chris@1058 2447 if (overallMag.sample(mr)) {
Chris@1058 2448 overallMagChanged = true;
Chris@1058 2449 }
Chris@1058 2450 }
Chris@1058 2451
Chris@1024 2452 int
Chris@918 2453 SpectrogramLayer::paintDrawBuffer(LayerGeometryProvider *v,
Chris@481 2454 int w,
Chris@481 2455 int h,
Chris@907 2456 const vector<int> &binforx,
Chris@907 2457 const vector<double> &binfory,
Chris@491 2458 bool usePeaksCache,
Chris@491 2459 MagnitudeRange &overallMag,
Chris@1026 2460 bool &overallMagChanged,
Chris@1039 2461 bool rightToLeft,
Chris@1039 2462 double softTimeLimit) const
Chris@480 2463 {
Chris@481 2464 Profiler profiler("SpectrogramLayer::paintDrawBuffer");
Chris@480 2465
Chris@490 2466 int minbin = int(binfory[0] + 0.0001);
Chris@907 2467 int maxbin = int(binfory[h-1]);
Chris@480 2468
Chris@485 2469 #ifdef DEBUG_SPECTROGRAM_REPAINT
Chris@1026 2470 cerr << "SpectrogramLayer::paintDrawBuffer: minbin " << minbin << ", maxbin " << maxbin << "; w " << w << ", h " << h << endl;
Chris@485 2471 #endif
Chris@480 2472 if (minbin < 0) minbin = 0;
Chris@480 2473 if (maxbin < 0) maxbin = minbin+1;
Chris@480 2474
Chris@1060 2475 DenseThreeDimensionalModel *peakCacheModel = 0;
Chris@1060 2476 FFTModel *fftModel = 0;
Chris@484 2477 DenseThreeDimensionalModel *sourceModel = 0;
Chris@1060 2478
Chris@485 2479 #ifdef DEBUG_SPECTROGRAM_REPAINT
Chris@1103 2480 cerr << "SpectrogramLayer::paintDrawBuffer: Note: bin display = " << int(m_binDisplay) << ", w = " << w << ", binforx[" << w-1 << "] = " << binforx[w-1] << ", binforx[0] = " << binforx[0] << endl;
Chris@485 2481 #endif
Chris@1060 2482
Chris@1060 2483 int divisor = 1;
Chris@1054 2484 if (usePeaksCache) {
Chris@1088 2485 peakCacheModel = getPeakCache();
Chris@1054 2486 divisor = m_peakCacheDivisor;
Chris@1060 2487 sourceModel = peakCacheModel;
Chris@484 2488 } else {
Chris@1088 2489 fftModel = getFFTModel();
Chris@1060 2490 sourceModel = fftModel;
Chris@484 2491 }
Chris@484 2492
Chris@1024 2493 if (!sourceModel) return 0;
Chris@484 2494
Chris@490 2495 bool interpolate = false;
Chris@490 2496 Preferences::SpectrogramSmoothing smoothing =
Chris@490 2497 Preferences::getInstance()->getSpectrogramSmoothing();
Chris@490 2498 if (smoothing == Preferences::SpectrogramInterpolated ||
Chris@490 2499 smoothing == Preferences::SpectrogramZeroPaddedAndInterpolated) {
Chris@1103 2500 if (m_binDisplay != BinDisplay::PeakBins &&
Chris@1103 2501 m_binDisplay != BinDisplay::PeakFrequencies) {
Chris@490 2502 interpolate = true;
Chris@490 2503 }
Chris@490 2504 }
Chris@490 2505
Chris@480 2506 int psx = -1;
Chris@545 2507
Chris@1027 2508 int minColumns = 4;
Chris@1039 2509 bool haveTimeLimits = (softTimeLimit > 0.0);
Chris@1037 2510 double hardTimeLimit = softTimeLimit * 2.0;
Chris@1037 2511 bool overridingSoftLimit = false;
Chris@1027 2512 auto startTime = chrono::steady_clock::now();
Chris@1027 2513
Chris@1026 2514 int start = 0;
Chris@1026 2515 int finish = w;
Chris@1026 2516 int step = 1;
Chris@1026 2517
Chris@1026 2518 if (rightToLeft) {
Chris@1026 2519 start = w-1;
Chris@1026 2520 finish = -1;
Chris@1026 2521 step = -1;
Chris@1026 2522 }
Chris@1027 2523
Chris@1027 2524 int columnCount = 0;
Chris@1026 2525
Chris@1060 2526 vector<float> preparedColumn;
Chris@1060 2527
Chris@1026 2528 for (int x = start; x != finish; x += step) {
Chris@1027 2529
Chris@1056 2530 // x is the on-canvas pixel coord; sx (later) will be the
Chris@1056 2531 // source column index
Chris@1056 2532
Chris@1027 2533 ++columnCount;
Chris@480 2534
Chris@482 2535 if (binforx[x] < 0) continue;
Chris@482 2536
Chris@484 2537 int sx0 = binforx[x] / divisor;
Chris@483 2538 int sx1 = sx0;
Chris@484 2539 if (x+1 < w) sx1 = binforx[x+1] / divisor;
Chris@483 2540 if (sx0 < 0) sx0 = sx1 - 1;
Chris@483 2541 if (sx0 < 0) continue;
Chris@483 2542 if (sx1 <= sx0) sx1 = sx0 + 1;
Chris@483 2543
Chris@1064 2544 vector<float> pixelPeakColumn;
Chris@1064 2545
Chris@483 2546 for (int sx = sx0; sx < sx1; ++sx) {
Chris@483 2547
Chris@518 2548 #ifdef DEBUG_SPECTROGRAM_REPAINT
Chris@682 2549 // cerr << "sx = " << sx << endl;
Chris@518 2550 #endif
Chris@518 2551
Chris@1064 2552 if (sx < 0 || sx >= sourceModel->getWidth()) {
Chris@1064 2553 continue;
Chris@1064 2554 }
Chris@488 2555
Chris@483 2556 if (sx != psx) {
Chris@1060 2557
Chris@1064 2558 // order:
Chris@1064 2559 // get column -> scale -> record extents ->
Chris@1063 2560 // normalise -> peak pick -> apply display gain ->
Chris@1063 2561 // distribute/interpolate
Chris@1063 2562
Chris@1063 2563 ColumnOp::Column column;
Chris@1063 2564
Chris@1060 2565 if (peakCacheModel) {
Chris@1060 2566 column = getColumnFromGenericModel(peakCacheModel,
Chris@1060 2567 sx,
Chris@1060 2568 minbin,
Chris@1060 2569 maxbin - minbin + 1);
Chris@484 2570 } else {
Chris@1060 2571 column = getColumnFromFFTModel(fftModel,
Chris@1060 2572 sx,
Chris@1060 2573 minbin,
Chris@1060 2574 maxbin - minbin + 1);
Chris@484 2575 }
Chris@1060 2576
Chris@1105 2577 if (m_colourScale != ColourScaleType::Phase) {
Chris@1087 2578 column = ColumnOp::fftScale(column, getFFTSize());
Chris@1064 2579 }
Chris@1063 2580
Chris@1062 2581 recordColumnExtents(column,
Chris@1062 2582 sx,
Chris@1062 2583 overallMag,
Chris@1062 2584 overallMagChanged);
Chris@1062 2585
Chris@1105 2586 if (m_colourScale != ColourScaleType::Phase) {
Chris@1064 2587 column = ColumnOp::normalize(column, m_normalization);
Chris@1064 2588 }
Chris@1063 2589
Chris@1103 2590 if (m_binDisplay == BinDisplay::PeakBins) {
Chris@1063 2591 column = ColumnOp::peakPick(column);
Chris@1063 2592 }
Chris@1063 2593
Chris@1062 2594 preparedColumn =
Chris@1064 2595 ColumnOp::distribute(ColumnOp::applyGain(column, m_gain),
Chris@1064 2596 h,
Chris@1064 2597 binfory,
Chris@1064 2598 minbin,
Chris@1064 2599 interpolate);
Chris@1060 2600
Chris@483 2601 psx = sx;
Chris@483 2602 }
Chris@483 2603
Chris@1064 2604 if (sx == sx0) {
Chris@1064 2605 pixelPeakColumn = preparedColumn;
Chris@1064 2606 } else {
Chris@1064 2607 for (int i = 0; in_range_for(pixelPeakColumn, i); ++i) {
Chris@1064 2608 pixelPeakColumn[i] = std::max(pixelPeakColumn[i],
Chris@1064 2609 preparedColumn[i]);
Chris@1064 2610 }
Chris@1064 2611 }
Chris@483 2612 }
Chris@483 2613
Chris@1065 2614 if (!pixelPeakColumn.empty()) {
Chris@1065 2615 for (int y = 0; y < h; ++y) {
Chris@1065 2616 m_drawBuffer.setPixel(x,
Chris@1065 2617 h-y-1,
Chris@1065 2618 getDisplayValue(v, pixelPeakColumn[y]));
Chris@1065 2619 }
Chris@480 2620 }
Chris@1025 2621
Chris@1039 2622 if (haveTimeLimits) {
Chris@1027 2623 if (columnCount >= minColumns) {
Chris@1025 2624 auto t = chrono::steady_clock::now();
Chris@1025 2625 double diff = chrono::duration<double>(t - startTime).count();
Chris@1037 2626 if (diff > hardTimeLimit) {
Chris@1025 2627 #ifdef DEBUG_SPECTROGRAM_REPAINT
Chris@1037 2628 cerr << "SpectrogramLayer::paintDrawBuffer: hard limit " << hardTimeLimit << " sec exceeded after "
Chris@1031 2629 << columnCount << " columns with time " << diff << endl;
Chris@1025 2630 #endif
Chris@1027 2631 return columnCount;
Chris@1037 2632 } else if (diff > softTimeLimit && !overridingSoftLimit) {
Chris@1037 2633 // If we're more than half way through by the time
Chris@1037 2634 // we reach the soft limit, ignore it (though
Chris@1037 2635 // still respect the hard limit, above). Otherwise
Chris@1037 2636 // respect the soft limit and return now.
Chris@1037 2637 if (columnCount > w/2) {
Chris@1037 2638 overridingSoftLimit = true;
Chris@1037 2639 } else {
Chris@1037 2640 #ifdef DEBUG_SPECTROGRAM_REPAINT
Chris@1037 2641 cerr << "SpectrogramLayer::paintDrawBuffer: soft limit " << softTimeLimit << " sec exceeded after "
Chris@1037 2642 << columnCount << " columns with time " << diff << endl;
Chris@1037 2643 #endif
Chris@1037 2644 return columnCount;
Chris@1037 2645 }
Chris@1037 2646 }
Chris@1025 2647 }
Chris@1025 2648 }
Chris@480 2649 }
Chris@1024 2650
Chris@1027 2651 return columnCount;
Chris@480 2652 }
Chris@477 2653
Chris@121 2654 void
Chris@918 2655 SpectrogramLayer::illuminateLocalFeatures(LayerGeometryProvider *v, QPainter &paint) const
Chris@121 2656 {
Chris@382 2657 Profiler profiler("SpectrogramLayer::illuminateLocalFeatures");
Chris@382 2658
Chris@121 2659 QPoint localPos;
Chris@121 2660 if (!v->shouldIlluminateLocalFeatures(this, localPos) || !m_model) {
Chris@121 2661 return;
Chris@121 2662 }
Chris@121 2663
Chris@682 2664 // cerr << "SpectrogramLayer: illuminateLocalFeatures("
Chris@682 2665 // << localPos.x() << "," << localPos.y() << ")" << endl;
Chris@121 2666
Chris@905 2667 double s0, s1;
Chris@905 2668 double f0, f1;
Chris@121 2669
Chris@121 2670 if (getXBinRange(v, localPos.x(), s0, s1) &&
Chris@121 2671 getYBinSourceRange(v, localPos.y(), f0, f1)) {
Chris@121 2672
Chris@121 2673 int s0i = int(s0 + 0.001);
Chris@121 2674 int s1i = int(s1);
Chris@121 2675
Chris@121 2676 int x0 = v->getXForFrame(s0i * getWindowIncrement());
Chris@121 2677 int x1 = v->getXForFrame((s1i + 1) * getWindowIncrement());
Chris@121 2678
Chris@248 2679 int y1 = int(getYForFrequency(v, f1));
Chris@248 2680 int y0 = int(getYForFrequency(v, f0));
Chris@121 2681
Chris@682 2682 // cerr << "SpectrogramLayer: illuminate "
Chris@682 2683 // << x0 << "," << y1 << " -> " << x1 << "," << y0 << endl;
Chris@121 2684
Chris@287 2685 paint.setPen(v->getForeground());
Chris@133 2686
Chris@133 2687 //!!! should we be using paintCrosshairs for this?
Chris@133 2688
Chris@121 2689 paint.drawRect(x0, y1, x1 - x0 + 1, y0 - y1 + 1);
Chris@121 2690 }
Chris@121 2691 }
Chris@121 2692
Chris@905 2693 double
Chris@918 2694 SpectrogramLayer::getYForFrequency(const LayerGeometryProvider *v, double frequency) const
Chris@42 2695 {
Chris@44 2696 return v->getYForFrequency(frequency,
Chris@44 2697 getEffectiveMinFrequency(),
Chris@44 2698 getEffectiveMaxFrequency(),
Chris@1103 2699 m_binScale == BinScale::Log);
Chris@42 2700 }
Chris@42 2701
Chris@905 2702 double
Chris@918 2703 SpectrogramLayer::getFrequencyForY(const LayerGeometryProvider *v, int y) const
Chris@42 2704 {
Chris@44 2705 return v->getFrequencyForY(y,
Chris@44 2706 getEffectiveMinFrequency(),
Chris@44 2707 getEffectiveMaxFrequency(),
Chris@1103 2708 m_binScale == BinScale::Log);
Chris@42 2709 }
Chris@42 2710
Chris@0 2711 int
Chris@1090 2712 SpectrogramLayer::getCompletion(LayerGeometryProvider *) const
Chris@0 2713 {
Chris@1088 2714 if (!m_fftModel) return 100;
Chris@1088 2715 int completion = m_fftModel->getCompletion();
Chris@224 2716 #ifdef DEBUG_SPECTROGRAM_REPAINT
Chris@985 2717 cerr << "SpectrogramLayer::getCompletion: completion = " << completion << endl;
Chris@224 2718 #endif
Chris@0 2719 return completion;
Chris@0 2720 }
Chris@0 2721
Chris@583 2722 QString
Chris@1090 2723 SpectrogramLayer::getError(LayerGeometryProvider *) const
Chris@583 2724 {
Chris@1088 2725 if (!m_fftModel) return "";
Chris@1088 2726 return m_fftModel->getError();
Chris@583 2727 }
Chris@583 2728
Chris@28 2729 bool
Chris@905 2730 SpectrogramLayer::getValueExtents(double &min, double &max,
Chris@101 2731 bool &logarithmic, QString &unit) const
Chris@79 2732 {
Chris@133 2733 if (!m_model) return false;
Chris@133 2734
Chris@907 2735 sv_samplerate_t sr = m_model->getSampleRate();
Chris@1087 2736 min = double(sr) / getFFTSize();
Chris@905 2737 max = double(sr) / 2;
Chris@133 2738
Chris@1103 2739 logarithmic = (m_binScale == BinScale::Log);
Chris@79 2740 unit = "Hz";
Chris@79 2741 return true;
Chris@79 2742 }
Chris@79 2743
Chris@79 2744 bool
Chris@905 2745 SpectrogramLayer::getDisplayExtents(double &min, double &max) const
Chris@101 2746 {
Chris@101 2747 min = getEffectiveMinFrequency();
Chris@101 2748 max = getEffectiveMaxFrequency();
Chris@253 2749
Chris@587 2750 // SVDEBUG << "SpectrogramLayer::getDisplayExtents: " << min << "->" << max << endl;
Chris@101 2751 return true;
Chris@101 2752 }
Chris@101 2753
Chris@101 2754 bool
Chris@905 2755 SpectrogramLayer::setDisplayExtents(double min, double max)
Chris@120 2756 {
Chris@120 2757 if (!m_model) return false;
Chris@187 2758
Chris@587 2759 // SVDEBUG << "SpectrogramLayer::setDisplayExtents: " << min << "->" << max << endl;
Chris@187 2760
Chris@120 2761 if (min < 0) min = 0;
Chris@907 2762 if (max > m_model->getSampleRate()/2.0) max = m_model->getSampleRate()/2.0;
Chris@120 2763
Chris@907 2764 int minf = int(lrint(min));
Chris@907 2765 int maxf = int(lrint(max));
Chris@120 2766
Chris@120 2767 if (m_minFrequency == minf && m_maxFrequency == maxf) return true;
Chris@120 2768
Chris@478 2769 invalidateImageCaches();
Chris@120 2770 invalidateMagnitudes();
Chris@120 2771
Chris@120 2772 m_minFrequency = minf;
Chris@120 2773 m_maxFrequency = maxf;
Chris@120 2774
Chris@120 2775 emit layerParametersChanged();
Chris@120 2776
Chris@133 2777 int vs = getCurrentVerticalZoomStep();
Chris@133 2778 if (vs != m_lastEmittedZoomStep) {
Chris@133 2779 emit verticalZoomChanged();
Chris@133 2780 m_lastEmittedZoomStep = vs;
Chris@133 2781 }
Chris@133 2782
Chris@120 2783 return true;
Chris@120 2784 }
Chris@120 2785
Chris@120 2786 bool
Chris@918 2787 SpectrogramLayer::getYScaleValue(const LayerGeometryProvider *v, int y,
Chris@905 2788 double &value, QString &unit) const
Chris@261 2789 {
Chris@261 2790 value = getFrequencyForY(v, y);
Chris@261 2791 unit = "Hz";
Chris@261 2792 return true;
Chris@261 2793 }
Chris@261 2794
Chris@261 2795 bool
Chris@918 2796 SpectrogramLayer::snapToFeatureFrame(LayerGeometryProvider *,
Chris@907 2797 sv_frame_t &frame,
Chris@805 2798 int &resolution,
Chris@28 2799 SnapType snap) const
Chris@13 2800 {
Chris@13 2801 resolution = getWindowIncrement();
Chris@907 2802 sv_frame_t left = (frame / resolution) * resolution;
Chris@907 2803 sv_frame_t right = left + resolution;
Chris@28 2804
Chris@28 2805 switch (snap) {
Chris@28 2806 case SnapLeft: frame = left; break;
Chris@28 2807 case SnapRight: frame = right; break;
Chris@28 2808 case SnapNearest:
Chris@28 2809 case SnapNeighbouring:
Chris@28 2810 if (frame - left > right - frame) frame = right;
Chris@28 2811 else frame = left;
Chris@28 2812 break;
Chris@28 2813 }
Chris@28 2814
Chris@28 2815 return true;
Chris@28 2816 }
Chris@13 2817
Chris@283 2818 void
Chris@918 2819 SpectrogramLayer::measureDoubleClick(LayerGeometryProvider *v, QMouseEvent *e)
Chris@283 2820 {
Chris@920 2821 const View *view = v->getView();
Chris@1030 2822 ScrollableImageCache &cache = getImageCacheReference(view);
Chris@1030 2823
Chris@1030 2824 cerr << "cache width: " << cache.getSize().width() << ", height: "
Chris@1030 2825 << cache.getSize().height() << endl;
Chris@1030 2826
Chris@1030 2827 QImage image = cache.getImage();
Chris@283 2828
Chris@283 2829 ImageRegionFinder finder;
Chris@283 2830 QRect rect = finder.findRegionExtents(&image, e->pos());
Chris@283 2831 if (rect.isValid()) {
Chris@283 2832 MeasureRect mr;
Chris@283 2833 setMeasureRectFromPixrect(v, mr, rect);
Chris@283 2834 CommandHistory::getInstance()->addCommand
Chris@283 2835 (new AddMeasurementRectCommand(this, mr));
Chris@283 2836 }
Chris@283 2837 }
Chris@283 2838
Chris@77 2839 bool
Chris@918 2840 SpectrogramLayer::getCrosshairExtents(LayerGeometryProvider *v, QPainter &paint,
Chris@77 2841 QPoint cursorPos,
Chris@1025 2842 vector<QRect> &extents) const
Chris@77 2843 {
Chris@918 2844 QRect vertical(cursorPos.x() - 12, 0, 12, v->getPaintHeight());
Chris@77 2845 extents.push_back(vertical);
Chris@77 2846
Chris@77 2847 QRect horizontal(0, cursorPos.y(), cursorPos.x(), 1);
Chris@77 2848 extents.push_back(horizontal);
Chris@77 2849
Chris@608 2850 int sw = getVerticalScaleWidth(v, m_haveDetailedScale, paint);
Chris@264 2851
Chris@280 2852 QRect freq(sw, cursorPos.y() - paint.fontMetrics().ascent() - 2,
Chris@280 2853 paint.fontMetrics().width("123456 Hz") + 2,
Chris@280 2854 paint.fontMetrics().height());
Chris@280 2855 extents.push_back(freq);
Chris@264 2856
Chris@279 2857 QRect pitch(sw, cursorPos.y() + 2,
Chris@279 2858 paint.fontMetrics().width("C#10+50c") + 2,
Chris@279 2859 paint.fontMetrics().height());
Chris@279 2860 extents.push_back(pitch);
Chris@279 2861
Chris@280 2862 QRect rt(cursorPos.x(),
Chris@918 2863 v->getPaintHeight() - paint.fontMetrics().height() - 2,
Chris@280 2864 paint.fontMetrics().width("1234.567 s"),
Chris@280 2865 paint.fontMetrics().height());
Chris@280 2866 extents.push_back(rt);
Chris@280 2867
Chris@280 2868 int w(paint.fontMetrics().width("1234567890") + 2);
Chris@280 2869 QRect frame(cursorPos.x() - w - 2,
Chris@918 2870 v->getPaintHeight() - paint.fontMetrics().height() - 2,
Chris@280 2871 w,
Chris@280 2872 paint.fontMetrics().height());
Chris@280 2873 extents.push_back(frame);
Chris@280 2874
Chris@77 2875 return true;
Chris@77 2876 }
Chris@77 2877
Chris@77 2878 void
Chris@918 2879 SpectrogramLayer::paintCrosshairs(LayerGeometryProvider *v, QPainter &paint,
Chris@77 2880 QPoint cursorPos) const
Chris@77 2881 {
Chris@77 2882 paint.save();
Chris@283 2883
Chris@608 2884 int sw = getVerticalScaleWidth(v, m_haveDetailedScale, paint);
Chris@283 2885
Chris@282 2886 QFont fn = paint.font();
Chris@282 2887 if (fn.pointSize() > 8) {
Chris@282 2888 fn.setPointSize(fn.pointSize() - 1);
Chris@282 2889 paint.setFont(fn);
Chris@282 2890 }
Chris@77 2891 paint.setPen(m_crosshairColour);
Chris@77 2892
Chris@77 2893 paint.drawLine(0, cursorPos.y(), cursorPos.x() - 1, cursorPos.y());
Chris@918 2894 paint.drawLine(cursorPos.x(), 0, cursorPos.x(), v->getPaintHeight());
Chris@77 2895
Chris@905 2896 double fundamental = getFrequencyForY(v, cursorPos.y());
Chris@77 2897
Chris@1078 2898 PaintAssistant::drawVisibleText(v, paint,
Chris@278 2899 sw + 2,
Chris@278 2900 cursorPos.y() - 2,
Chris@278 2901 QString("%1 Hz").arg(fundamental),
Chris@1078 2902 PaintAssistant::OutlinedText);
Chris@278 2903
Chris@279 2904 if (Pitch::isFrequencyInMidiRange(fundamental)) {
Chris@279 2905 QString pitchLabel = Pitch::getPitchLabelForFrequency(fundamental);
Chris@1078 2906 PaintAssistant::drawVisibleText(v, paint,
Chris@279 2907 sw + 2,
Chris@279 2908 cursorPos.y() + paint.fontMetrics().ascent() + 2,
Chris@279 2909 pitchLabel,
Chris@1078 2910 PaintAssistant::OutlinedText);
Chris@279 2911 }
Chris@279 2912
Chris@907 2913 sv_frame_t frame = v->getFrameForX(cursorPos.x());
Chris@279 2914 RealTime rt = RealTime::frame2RealTime(frame, m_model->getSampleRate());
Chris@280 2915 QString rtLabel = QString("%1 s").arg(rt.toText(true).c_str());
Chris@280 2916 QString frameLabel = QString("%1").arg(frame);
Chris@1078 2917 PaintAssistant::drawVisibleText(v, paint,
Chris@280 2918 cursorPos.x() - paint.fontMetrics().width(frameLabel) - 2,
Chris@918 2919 v->getPaintHeight() - 2,
Chris@280 2920 frameLabel,
Chris@1078 2921 PaintAssistant::OutlinedText);
Chris@1078 2922 PaintAssistant::drawVisibleText(v, paint,
Chris@280 2923 cursorPos.x() + 2,
Chris@918 2924 v->getPaintHeight() - 2,
Chris@280 2925 rtLabel,
Chris@1078 2926 PaintAssistant::OutlinedText);
Chris@264 2927
Chris@77 2928 int harmonic = 2;
Chris@77 2929
Chris@77 2930 while (harmonic < 100) {
Chris@77 2931
Chris@907 2932 int hy = int(lrint(getYForFrequency(v, fundamental * harmonic)));
Chris@918 2933 if (hy < 0 || hy > v->getPaintHeight()) break;
Chris@77 2934
Chris@77 2935 int len = 7;
Chris@77 2936
Chris@77 2937 if (harmonic % 2 == 0) {
Chris@77 2938 if (harmonic % 4 == 0) {
Chris@77 2939 len = 12;
Chris@77 2940 } else {
Chris@77 2941 len = 10;
Chris@77 2942 }
Chris@77 2943 }
Chris@77 2944
Chris@77 2945 paint.drawLine(cursorPos.x() - len,
Chris@907 2946 hy,
Chris@77 2947 cursorPos.x(),
Chris@907 2948 hy);
Chris@77 2949
Chris@77 2950 ++harmonic;
Chris@77 2951 }
Chris@77 2952
Chris@77 2953 paint.restore();
Chris@77 2954 }
Chris@77 2955
Chris@25 2956 QString
Chris@918 2957 SpectrogramLayer::getFeatureDescription(LayerGeometryProvider *v, QPoint &pos) const
Chris@25 2958 {
Chris@25 2959 int x = pos.x();
Chris@25 2960 int y = pos.y();
Chris@0 2961
Chris@25 2962 if (!m_model || !m_model->isOK()) return "";
Chris@0 2963
Chris@905 2964 double magMin = 0, magMax = 0;
Chris@905 2965 double phaseMin = 0, phaseMax = 0;
Chris@905 2966 double freqMin = 0, freqMax = 0;
Chris@905 2967 double adjFreqMin = 0, adjFreqMax = 0;
Chris@25 2968 QString pitchMin, pitchMax;
Chris@0 2969 RealTime rtMin, rtMax;
Chris@0 2970
Chris@38 2971 bool haveValues = false;
Chris@0 2972
Chris@44 2973 if (!getXBinSourceRange(v, x, rtMin, rtMax)) {
Chris@38 2974 return "";
Chris@38 2975 }
Chris@44 2976 if (getXYBinSourceRange(v, x, y, magMin, magMax, phaseMin, phaseMax)) {
Chris@38 2977 haveValues = true;
Chris@38 2978 }
Chris@0 2979
Chris@35 2980 QString adjFreqText = "", adjPitchText = "";
Chris@35 2981
Chris@1103 2982 if (m_binDisplay == BinDisplay::PeakFrequencies) {
Chris@35 2983
Chris@44 2984 if (!getAdjustedYBinSourceRange(v, x, y, freqMin, freqMax,
Chris@38 2985 adjFreqMin, adjFreqMax)) {
Chris@38 2986 return "";
Chris@38 2987 }
Chris@35 2988
Chris@35 2989 if (adjFreqMin != adjFreqMax) {
Chris@65 2990 adjFreqText = tr("Peak Frequency:\t%1 - %2 Hz\n")
Chris@35 2991 .arg(adjFreqMin).arg(adjFreqMax);
Chris@35 2992 } else {
Chris@65 2993 adjFreqText = tr("Peak Frequency:\t%1 Hz\n")
Chris@35 2994 .arg(adjFreqMin);
Chris@38 2995 }
Chris@38 2996
Chris@38 2997 QString pmin = Pitch::getPitchLabelForFrequency(adjFreqMin);
Chris@38 2998 QString pmax = Pitch::getPitchLabelForFrequency(adjFreqMax);
Chris@38 2999
Chris@38 3000 if (pmin != pmax) {
Chris@65 3001 adjPitchText = tr("Peak Pitch:\t%3 - %4\n").arg(pmin).arg(pmax);
Chris@38 3002 } else {
Chris@65 3003 adjPitchText = tr("Peak Pitch:\t%2\n").arg(pmin);
Chris@35 3004 }
Chris@35 3005
Chris@35 3006 } else {
Chris@35 3007
Chris@44 3008 if (!getYBinSourceRange(v, y, freqMin, freqMax)) return "";
Chris@35 3009 }
Chris@35 3010
Chris@25 3011 QString text;
Chris@25 3012
Chris@25 3013 if (rtMin != rtMax) {
Chris@25 3014 text += tr("Time:\t%1 - %2\n")
Chris@25 3015 .arg(rtMin.toText(true).c_str())
Chris@25 3016 .arg(rtMax.toText(true).c_str());
Chris@25 3017 } else {
Chris@25 3018 text += tr("Time:\t%1\n")
Chris@25 3019 .arg(rtMin.toText(true).c_str());
Chris@0 3020 }
Chris@0 3021
Chris@25 3022 if (freqMin != freqMax) {
Chris@65 3023 text += tr("%1Bin Frequency:\t%2 - %3 Hz\n%4Bin Pitch:\t%5 - %6\n")
Chris@65 3024 .arg(adjFreqText)
Chris@25 3025 .arg(freqMin)
Chris@25 3026 .arg(freqMax)
Chris@65 3027 .arg(adjPitchText)
Chris@65 3028 .arg(Pitch::getPitchLabelForFrequency(freqMin))
Chris@65 3029 .arg(Pitch::getPitchLabelForFrequency(freqMax));
Chris@65 3030 } else {
Chris@65 3031 text += tr("%1Bin Frequency:\t%2 Hz\n%3Bin Pitch:\t%4\n")
Chris@35 3032 .arg(adjFreqText)
Chris@25 3033 .arg(freqMin)
Chris@65 3034 .arg(adjPitchText)
Chris@65 3035 .arg(Pitch::getPitchLabelForFrequency(freqMin));
Chris@25 3036 }
Chris@25 3037
Chris@38 3038 if (haveValues) {
Chris@905 3039 double dbMin = AudioLevel::multiplier_to_dB(magMin);
Chris@905 3040 double dbMax = AudioLevel::multiplier_to_dB(magMax);
Chris@43 3041 QString dbMinString;
Chris@43 3042 QString dbMaxString;
Chris@43 3043 if (dbMin == AudioLevel::DB_FLOOR) {
Chris@43 3044 dbMinString = tr("-Inf");
Chris@43 3045 } else {
Chris@907 3046 dbMinString = QString("%1").arg(lrint(dbMin));
Chris@43 3047 }
Chris@43 3048 if (dbMax == AudioLevel::DB_FLOOR) {
Chris@43 3049 dbMaxString = tr("-Inf");
Chris@43 3050 } else {
Chris@907 3051 dbMaxString = QString("%1").arg(lrint(dbMax));
Chris@43 3052 }
Chris@907 3053 if (lrint(dbMin) != lrint(dbMax)) {
Chris@199 3054 text += tr("dB:\t%1 - %2").arg(dbMinString).arg(dbMaxString);
Chris@25 3055 } else {
Chris@199 3056 text += tr("dB:\t%1").arg(dbMinString);
Chris@25 3057 }
Chris@38 3058 if (phaseMin != phaseMax) {
Chris@38 3059 text += tr("\nPhase:\t%1 - %2").arg(phaseMin).arg(phaseMax);
Chris@38 3060 } else {
Chris@38 3061 text += tr("\nPhase:\t%1").arg(phaseMin);
Chris@38 3062 }
Chris@25 3063 }
Chris@25 3064
Chris@25 3065 return text;
Chris@0 3066 }
Chris@25 3067
Chris@0 3068 int
Chris@40 3069 SpectrogramLayer::getColourScaleWidth(QPainter &paint) const
Chris@40 3070 {
Chris@40 3071 int cw;
Chris@40 3072
Chris@119 3073 cw = paint.fontMetrics().width("-80dB");
Chris@119 3074
Chris@40 3075 return cw;
Chris@40 3076 }
Chris@40 3077
Chris@40 3078 int
Chris@918 3079 SpectrogramLayer::getVerticalScaleWidth(LayerGeometryProvider *, bool detailed, QPainter &paint) const
Chris@0 3080 {
Chris@0 3081 if (!m_model || !m_model->isOK()) return 0;
Chris@0 3082
Chris@607 3083 int cw = 0;
Chris@607 3084 if (detailed) cw = getColourScaleWidth(paint);
Chris@40 3085
Chris@0 3086 int tw = paint.fontMetrics().width(QString("%1")
Chris@0 3087 .arg(m_maxFrequency > 0 ?
Chris@0 3088 m_maxFrequency - 1 :
Chris@0 3089 m_model->getSampleRate() / 2));
Chris@0 3090
Chris@234 3091 int fw = paint.fontMetrics().width(tr("43Hz"));
Chris@0 3092 if (tw < fw) tw = fw;
Chris@40 3093
Chris@1103 3094 int tickw = (m_binScale == BinScale::Log ? 10 : 4);
Chris@0 3095
Chris@40 3096 return cw + tickw + tw + 13;
Chris@0 3097 }
Chris@0 3098
Chris@0 3099 void
Chris@918 3100 SpectrogramLayer::paintVerticalScale(LayerGeometryProvider *v, bool detailed, QPainter &paint, QRect rect) const
Chris@0 3101 {
Chris@0 3102 if (!m_model || !m_model->isOK()) {
Chris@0 3103 return;
Chris@0 3104 }
Chris@0 3105
Chris@382 3106 Profiler profiler("SpectrogramLayer::paintVerticalScale");
Chris@122 3107
Chris@120 3108 //!!! cache this?
Chris@120 3109
Chris@0 3110 int h = rect.height(), w = rect.width();
Chris@0 3111
Chris@1103 3112 int tickw = (m_binScale == BinScale::Log ? 10 : 4);
Chris@1103 3113 int pkw = (m_binScale == BinScale::Log ? 10 : 0);
Chris@40 3114
Chris@1087 3115 int bins = getFFTSize() / 2;
Chris@907 3116 sv_samplerate_t sr = m_model->getSampleRate();
Chris@0 3117
Chris@0 3118 if (m_maxFrequency > 0) {
Chris@1087 3119 bins = int((double(m_maxFrequency) * getFFTSize()) / sr + 0.1);
Chris@1087 3120 if (bins > getFFTSize() / 2) bins = getFFTSize() / 2;
Chris@0 3121 }
Chris@0 3122
Chris@607 3123 int cw = 0;
Chris@607 3124
Chris@607 3125 if (detailed) cw = getColourScaleWidth(paint);
Chris@119 3126 int cbw = paint.fontMetrics().width("dB");
Chris@40 3127
Chris@0 3128 int py = -1;
Chris@0 3129 int textHeight = paint.fontMetrics().height();
Chris@0 3130 int toff = -textHeight + paint.fontMetrics().ascent() + 2;
Chris@0 3131
Chris@607 3132 if (detailed && (h > textHeight * 3 + 10)) {
Chris@119 3133
Chris@119 3134 int topLines = 2;
Chris@1105 3135 if (m_colourScale == ColourScaleType::Phase) topLines = 1;
Chris@119 3136
Chris@119 3137 int ch = h - textHeight * (topLines + 1) - 8;
Chris@119 3138 // paint.drawRect(4, textHeight + 4, cw - 1, ch + 1);
Chris@119 3139 paint.drawRect(4 + cw - cbw, textHeight * topLines + 4, cbw - 1, ch + 1);
Chris@40 3140
Chris@40 3141 QString top, bottom;
Chris@1030 3142 double min = m_viewMags[v->getId()].getMin();
Chris@1030 3143 double max = m_viewMags[v->getId()].getMax();
Chris@905 3144
Chris@905 3145 double dBmin = AudioLevel::multiplier_to_dB(min);
Chris@905 3146 double dBmax = AudioLevel::multiplier_to_dB(max);
Chris@119 3147
Chris@1044 3148 #ifdef DEBUG_SPECTROGRAM_REPAINT
Chris@1044 3149 cerr << "paintVerticalScale: for view id " << v->getId()
Chris@1044 3150 << ": min = " << min << ", max = " << max
Chris@1044 3151 << ", dBmin = " << dBmin << ", dBmax = " << dBmax << endl;
Chris@1044 3152 #endif
Chris@1044 3153
Chris@120 3154 if (dBmax < -60.f) dBmax = -60.f;
Chris@907 3155 else top = QString("%1").arg(lrint(dBmax));
Chris@120 3156
Chris@120 3157 if (dBmin < dBmax - 60.f) dBmin = dBmax - 60.f;
Chris@907 3158 bottom = QString("%1").arg(lrint(dBmin));
Chris@119 3159
Chris@119 3160 //!!! & phase etc
Chris@119 3161
Chris@1105 3162 if (m_colourScale != ColourScaleType::Phase) {
Chris@119 3163 paint.drawText((cw + 6 - paint.fontMetrics().width("dBFS")) / 2,
Chris@119 3164 2 + textHeight + toff, "dBFS");
Chris@119 3165 }
Chris@119 3166
Chris@119 3167 // paint.drawText((cw + 6 - paint.fontMetrics().width(top)) / 2,
Chris@119 3168 paint.drawText(3 + cw - cbw - paint.fontMetrics().width(top),
Chris@119 3169 2 + textHeight * topLines + toff + textHeight/2, top);
Chris@119 3170
Chris@119 3171 paint.drawText(3 + cw - cbw - paint.fontMetrics().width(bottom),
Chris@119 3172 h + toff - 3 - textHeight/2, bottom);
Chris@40 3173
Chris@40 3174 paint.save();
Chris@40 3175 paint.setBrush(Qt::NoBrush);
Chris@119 3176
Chris@119 3177 int lasty = 0;
Chris@119 3178 int lastdb = 0;
Chris@119 3179
Chris@40 3180 for (int i = 0; i < ch; ++i) {
Chris@119 3181
Chris@905 3182 double dBval = dBmin + (((dBmax - dBmin) * i) / (ch - 1));
Chris@119 3183 int idb = int(dBval);
Chris@119 3184
Chris@905 3185 double value = AudioLevel::dB_to_multiplier(dBval);
Chris@119 3186 int colour = getDisplayValue(v, value * m_gain);
Chris@210 3187
Chris@907 3188 paint.setPen(m_palette.getColour((unsigned char)colour));
Chris@119 3189
Chris@119 3190 int y = textHeight * topLines + 4 + ch - i;
Chris@119 3191
Chris@119 3192 paint.drawLine(5 + cw - cbw, y, cw + 2, y);
Chris@119 3193
Chris@119 3194 if (i == 0) {
Chris@119 3195 lasty = y;
Chris@119 3196 lastdb = idb;
Chris@119 3197 } else if (i < ch - paint.fontMetrics().ascent() &&
Chris@120 3198 idb != lastdb &&
Chris@119 3199 ((abs(y - lasty) > textHeight &&
Chris@119 3200 idb % 10 == 0) ||
Chris@119 3201 (abs(y - lasty) > paint.fontMetrics().ascent() &&
Chris@119 3202 idb % 5 == 0))) {
Chris@287 3203 paint.setPen(v->getBackground());
Chris@119 3204 QString text = QString("%1").arg(idb);
Chris@119 3205 paint.drawText(3 + cw - cbw - paint.fontMetrics().width(text),
Chris@119 3206 y + toff + textHeight/2, text);
Chris@287 3207 paint.setPen(v->getForeground());
Chris@119 3208 paint.drawLine(5 + cw - cbw, y, 8 + cw - cbw, y);
Chris@119 3209 lasty = y;
Chris@119 3210 lastdb = idb;
Chris@119 3211 }
Chris@40 3212 }
Chris@40 3213 paint.restore();
Chris@40 3214 }
Chris@40 3215
Chris@40 3216 paint.drawLine(cw + 7, 0, cw + 7, h);
Chris@40 3217
Chris@0 3218 int bin = -1;
Chris@0 3219
Chris@918 3220 for (int y = 0; y < v->getPaintHeight(); ++y) {
Chris@0 3221
Chris@905 3222 double q0, q1;
Chris@918 3223 if (!getYBinRange(v, v->getPaintHeight() - y, q0, q1)) continue;
Chris@0 3224
Chris@0 3225 int vy;
Chris@0 3226
Chris@0 3227 if (int(q0) > bin) {
Chris@0 3228 vy = y;
Chris@0 3229 bin = int(q0);
Chris@0 3230 } else {
Chris@0 3231 continue;
Chris@0 3232 }
Chris@0 3233
Chris@1087 3234 int freq = int((sr * bin) / getFFTSize());
Chris@0 3235
Chris@0 3236 if (py >= 0 && (vy - py) < textHeight - 1) {
Chris@1103 3237 if (m_binScale == BinScale::Linear) {
Chris@40 3238 paint.drawLine(w - tickw, h - vy, w, h - vy);
Chris@40 3239 }
Chris@0 3240 continue;
Chris@0 3241 }
Chris@0 3242
Chris@0 3243 QString text = QString("%1").arg(freq);
Chris@234 3244 if (bin == 1) text = tr("%1Hz").arg(freq); // bin 0 is DC
Chris@40 3245 paint.drawLine(cw + 7, h - vy, w - pkw - 1, h - vy);
Chris@0 3246
Chris@0 3247 if (h - vy - textHeight >= -2) {
Chris@1025 3248 int tx = w - 3 - paint.fontMetrics().width(text) - max(tickw, pkw);
Chris@0 3249 paint.drawText(tx, h - vy + toff, text);
Chris@0 3250 }
Chris@0 3251
Chris@0 3252 py = vy;
Chris@0 3253 }
Chris@40 3254
Chris@1103 3255 if (m_binScale == BinScale::Log) {
Chris@40 3256
Chris@277 3257 // piano keyboard
Chris@277 3258
Chris@690 3259 PianoScale().paintPianoVertical
Chris@690 3260 (v, paint, QRect(w - pkw - 1, 0, pkw, h),
Chris@690 3261 getEffectiveMinFrequency(), getEffectiveMaxFrequency());
Chris@40 3262 }
Chris@608 3263
Chris@608 3264 m_haveDetailedScale = detailed;
Chris@0 3265 }
Chris@0 3266
Chris@187 3267 class SpectrogramRangeMapper : public RangeMapper
Chris@187 3268 {
Chris@187 3269 public:
Chris@901 3270 SpectrogramRangeMapper(sv_samplerate_t sr, int /* fftsize */) :
Chris@901 3271 m_dist(sr / 2),
Chris@901 3272 m_s2(sqrt(sqrt(2))) { }
Chris@187 3273 ~SpectrogramRangeMapper() { }
Chris@187 3274
Chris@901 3275 virtual int getPositionForValue(double value) const {
Chris@901 3276
Chris@901 3277 double dist = m_dist;
Chris@187 3278
Chris@187 3279 int n = 0;
Chris@187 3280
Chris@901 3281 while (dist > (value + 0.00001) && dist > 0.1) {
Chris@187 3282 dist /= m_s2;
Chris@187 3283 ++n;
Chris@187 3284 }
Chris@187 3285
Chris@187 3286 return n;
Chris@187 3287 }
Chris@724 3288
Chris@901 3289 virtual int getPositionForValueUnclamped(double value) const {
Chris@724 3290 // We don't really support this
Chris@724 3291 return getPositionForValue(value);
Chris@724 3292 }
Chris@187 3293
Chris@901 3294 virtual double getValueForPosition(int position) const {
Chris@187 3295
Chris@187 3296 // Vertical zoom step 0 shows the entire range from DC ->
Chris@187 3297 // Nyquist frequency. Step 1 shows 2^(1/4) of the range of
Chris@187 3298 // step 0, and so on until the visible range is smaller than
Chris@187 3299 // the frequency step between bins at the current fft size.
Chris@187 3300
Chris@901 3301 double dist = m_dist;
Chris@187 3302
Chris@187 3303 int n = 0;
Chris@187 3304 while (n < position) {
Chris@187 3305 dist /= m_s2;
Chris@187 3306 ++n;
Chris@187 3307 }
Chris@187 3308
Chris@187 3309 return dist;
Chris@187 3310 }
Chris@187 3311
Chris@901 3312 virtual double getValueForPositionUnclamped(int position) const {
Chris@724 3313 // We don't really support this
Chris@724 3314 return getValueForPosition(position);
Chris@724 3315 }
Chris@724 3316
Chris@187 3317 virtual QString getUnit() const { return "Hz"; }
Chris@187 3318
Chris@187 3319 protected:
Chris@901 3320 double m_dist;
Chris@901 3321 double m_s2;
Chris@187 3322 };
Chris@187 3323
Chris@133 3324 int
Chris@133 3325 SpectrogramLayer::getVerticalZoomSteps(int &defaultStep) const
Chris@133 3326 {
Chris@135 3327 if (!m_model) return 0;
Chris@187 3328
Chris@907 3329 sv_samplerate_t sr = m_model->getSampleRate();
Chris@187 3330
Chris@1087 3331 SpectrogramRangeMapper mapper(sr, getFFTSize());
Chris@1087 3332
Chris@1087 3333 // int maxStep = mapper.getPositionForValue((double(sr) / getFFTSize()) + 0.001);
Chris@187 3334 int maxStep = mapper.getPositionForValue(0);
Chris@905 3335 int minStep = mapper.getPositionForValue(double(sr) / 2);
Chris@250 3336
Chris@805 3337 int initialMax = m_initialMaxFrequency;
Chris@907 3338 if (initialMax == 0) initialMax = int(sr / 2);
Chris@250 3339
Chris@250 3340 defaultStep = mapper.getPositionForValue(initialMax) - minStep;
Chris@250 3341
Chris@587 3342 // SVDEBUG << "SpectrogramLayer::getVerticalZoomSteps: " << maxStep - minStep << " (" << maxStep <<"-" << minStep << "), default is " << defaultStep << " (from initial max freq " << initialMax << ")" << endl;
Chris@187 3343
Chris@187 3344 return maxStep - minStep;
Chris@133 3345 }
Chris@133 3346
Chris@133 3347 int
Chris@133 3348 SpectrogramLayer::getCurrentVerticalZoomStep() const
Chris@133 3349 {
Chris@133 3350 if (!m_model) return 0;
Chris@133 3351
Chris@905 3352 double dmin, dmax;
Chris@133 3353 getDisplayExtents(dmin, dmax);
Chris@133 3354
Chris@1087 3355 SpectrogramRangeMapper mapper(m_model->getSampleRate(), getFFTSize());
Chris@187 3356 int n = mapper.getPositionForValue(dmax - dmin);
Chris@587 3357 // SVDEBUG << "SpectrogramLayer::getCurrentVerticalZoomStep: " << n << endl;
Chris@133 3358 return n;
Chris@133 3359 }
Chris@133 3360
Chris@133 3361 void
Chris@133 3362 SpectrogramLayer::setVerticalZoomStep(int step)
Chris@133 3363 {
Chris@187 3364 if (!m_model) return;
Chris@187 3365
Chris@905 3366 double dmin = m_minFrequency, dmax = m_maxFrequency;
Chris@253 3367 // getDisplayExtents(dmin, dmax);
Chris@253 3368
Chris@682 3369 // cerr << "current range " << dmin << " -> " << dmax << ", range " << dmax-dmin << ", mid " << (dmax + dmin)/2 << endl;
Chris@133 3370
Chris@907 3371 sv_samplerate_t sr = m_model->getSampleRate();
Chris@1087 3372 SpectrogramRangeMapper mapper(sr, getFFTSize());
Chris@905 3373 double newdist = mapper.getValueForPosition(step);
Chris@905 3374
Chris@905 3375 double newmin, newmax;
Chris@253 3376
Chris@1103 3377 if (m_binScale == BinScale::Log) {
Chris@253 3378
Chris@253 3379 // need to pick newmin and newmax such that
Chris@253 3380 //
Chris@253 3381 // (log(newmin) + log(newmax)) / 2 == logmid
Chris@253 3382 // and
Chris@253 3383 // newmax - newmin = newdist
Chris@253 3384 //
Chris@253 3385 // so log(newmax - newdist) + log(newmax) == 2logmid
Chris@253 3386 // log(newmax(newmax - newdist)) == 2logmid
Chris@253 3387 // newmax.newmax - newmax.newdist == exp(2logmid)
Chris@253 3388 // newmax^2 + (-newdist)newmax + -exp(2logmid) == 0
Chris@253 3389 // quadratic with a = 1, b = -newdist, c = -exp(2logmid), all known
Chris@253 3390 //
Chris@253 3391 // positive root
Chris@253 3392 // newmax = (newdist + sqrt(newdist^2 + 4exp(2logmid))) / 2
Chris@253 3393 //
Chris@253 3394 // but logmid = (log(dmin) + log(dmax)) / 2
Chris@253 3395 // so exp(2logmid) = exp(log(dmin) + log(dmax))
Chris@253 3396 // = exp(log(dmin.dmax))
Chris@253 3397 // = dmin.dmax
Chris@253 3398 // so newmax = (newdist + sqrtf(newdist^2 + 4dmin.dmax)) / 2
Chris@253 3399
Chris@907 3400 newmax = (newdist + sqrt(newdist*newdist + 4*dmin*dmax)) / 2;
Chris@253 3401 newmin = newmax - newdist;
Chris@253 3402
Chris@682 3403 // cerr << "newmin = " << newmin << ", newmax = " << newmax << endl;
Chris@253 3404
Chris@253 3405 } else {
Chris@905 3406 double dmid = (dmax + dmin) / 2;
Chris@253 3407 newmin = dmid - newdist / 2;
Chris@253 3408 newmax = dmid + newdist / 2;
Chris@253 3409 }
Chris@187 3410
Chris@905 3411 double mmin, mmax;
Chris@187 3412 mmin = 0;
Chris@905 3413 mmax = double(sr) / 2;
Chris@133 3414
Chris@187 3415 if (newmin < mmin) {
Chris@187 3416 newmax += (mmin - newmin);
Chris@187 3417 newmin = mmin;
Chris@187 3418 }
Chris@187 3419 if (newmax > mmax) {
Chris@187 3420 newmax = mmax;
Chris@187 3421 }
Chris@133 3422
Chris@587 3423 // SVDEBUG << "SpectrogramLayer::setVerticalZoomStep: " << step << ": " << newmin << " -> " << newmax << " (range " << newdist << ")" << endl;
Chris@253 3424
Chris@907 3425 setMinFrequency(int(lrint(newmin)));
Chris@907 3426 setMaxFrequency(int(lrint(newmax)));
Chris@187 3427 }
Chris@187 3428
Chris@187 3429 RangeMapper *
Chris@187 3430 SpectrogramLayer::getNewVerticalZoomRangeMapper() const
Chris@187 3431 {
Chris@187 3432 if (!m_model) return 0;
Chris@1087 3433 return new SpectrogramRangeMapper(m_model->getSampleRate(), getFFTSize());
Chris@133 3434 }
Chris@133 3435
Chris@273 3436 void
Chris@918 3437 SpectrogramLayer::updateMeasureRectYCoords(LayerGeometryProvider *v, const MeasureRect &r) const
Chris@273 3438 {
Chris@273 3439 int y0 = 0;
Chris@907 3440 if (r.startY > 0.0) y0 = int(getYForFrequency(v, r.startY));
Chris@273 3441
Chris@273 3442 int y1 = y0;
Chris@907 3443 if (r.endY > 0.0) y1 = int(getYForFrequency(v, r.endY));
Chris@273 3444
Chris@587 3445 // SVDEBUG << "SpectrogramLayer::updateMeasureRectYCoords: start " << r.startY << " -> " << y0 << ", end " << r.endY << " -> " << y1 << endl;
Chris@273 3446
Chris@273 3447 r.pixrect = QRect(r.pixrect.x(), y0, r.pixrect.width(), y1 - y0);
Chris@273 3448 }
Chris@273 3449
Chris@273 3450 void
Chris@918 3451 SpectrogramLayer::setMeasureRectYCoord(LayerGeometryProvider *v, MeasureRect &r, bool start, int y) const
Chris@273 3452 {
Chris@273 3453 if (start) {
Chris@273 3454 r.startY = getFrequencyForY(v, y);
Chris@273 3455 r.endY = r.startY;
Chris@273 3456 } else {
Chris@273 3457 r.endY = getFrequencyForY(v, y);
Chris@273 3458 }
Chris@587 3459 // SVDEBUG << "SpectrogramLayer::setMeasureRectYCoord: start " << r.startY << " <- " << y << ", end " << r.endY << " <- " << y << endl;
Chris@273 3460
Chris@273 3461 }
Chris@273 3462
Chris@316 3463 void
Chris@316 3464 SpectrogramLayer::toXml(QTextStream &stream,
Chris@316 3465 QString indent, QString extraAttributes) const
Chris@6 3466 {
Chris@6 3467 QString s;
Chris@6 3468
Chris@6 3469 s += QString("channel=\"%1\" "
Chris@6 3470 "windowSize=\"%2\" "
Chris@153 3471 "windowHopLevel=\"%3\" "
Chris@153 3472 "gain=\"%4\" "
Chris@153 3473 "threshold=\"%5\" ")
Chris@6 3474 .arg(m_channel)
Chris@6 3475 .arg(m_windowSize)
Chris@97 3476 .arg(m_windowHopLevel)
Chris@37 3477 .arg(m_gain)
Chris@37 3478 .arg(m_threshold);
Chris@37 3479
Chris@37 3480 s += QString("minFrequency=\"%1\" "
Chris@37 3481 "maxFrequency=\"%2\" "
Chris@37 3482 "colourScale=\"%3\" "
Chris@37 3483 "colourScheme=\"%4\" "
Chris@37 3484 "colourRotation=\"%5\" "
Chris@37 3485 "frequencyScale=\"%6\" "
Chris@761 3486 "binDisplay=\"%7\" ")
Chris@37 3487 .arg(m_minFrequency)
Chris@6 3488 .arg(m_maxFrequency)
Chris@1104 3489 .arg(convertFromColourScale(m_colourScale))
Chris@197 3490 .arg(m_colourMap)
Chris@37 3491 .arg(m_colourRotation)
Chris@1103 3492 .arg(int(m_binScale))
Chris@1103 3493 .arg(int(m_binDisplay));
Chris@761 3494
Chris@1009 3495 // New-style normalization attributes, allowing for more types of
Chris@1009 3496 // normalization in future: write out the column normalization
Chris@1009 3497 // type separately, and then whether we are normalizing visible
Chris@1009 3498 // area as well afterwards
Chris@1009 3499
Chris@1009 3500 s += QString("columnNormalization=\"%1\" ")
Chris@1104 3501 .arg(m_normalization == ColumnNormalization::Max1 ? "peak" :
Chris@1104 3502 m_normalization == ColumnNormalization::Hybrid ? "hybrid" : "none");
Chris@1009 3503
Chris@1009 3504 // Old-style normalization attribute. We *don't* write out
Chris@1009 3505 // normalizeHybrid here because the only release that would accept
Chris@1009 3506 // it (Tony v1.0) has a totally different scale factor for
Chris@1009 3507 // it. We'll just have to accept that session files from Tony
Chris@1009 3508 // v2.0+ will look odd in Tony v1.0
Chris@1009 3509
Chris@1009 3510 s += QString("normalizeColumns=\"%1\" ")
Chris@1104 3511 .arg(m_normalization == ColumnNormalization::Max1 ? "true" : "false");
Chris@1009 3512
Chris@1009 3513 // And this applies to both old- and new-style attributes
Chris@1009 3514
Chris@1009 3515 s += QString("normalizeVisibleArea=\"%1\" ")
Chris@1104 3516 .arg(m_normalizeVisibleArea ? "true" : "false");
Chris@1009 3517
Chris@316 3518 Layer::toXml(stream, indent, extraAttributes + " " + s);
Chris@6 3519 }
Chris@6 3520
Chris@11 3521 void
Chris@11 3522 SpectrogramLayer::setProperties(const QXmlAttributes &attributes)
Chris@11 3523 {
Chris@11 3524 bool ok = false;
Chris@11 3525
Chris@11 3526 int channel = attributes.value("channel").toInt(&ok);
Chris@11 3527 if (ok) setChannel(channel);
Chris@11 3528
Chris@805 3529 int windowSize = attributes.value("windowSize").toUInt(&ok);
Chris@11 3530 if (ok) setWindowSize(windowSize);
Chris@11 3531
Chris@805 3532 int windowHopLevel = attributes.value("windowHopLevel").toUInt(&ok);
Chris@97 3533 if (ok) setWindowHopLevel(windowHopLevel);
Chris@97 3534 else {
Chris@805 3535 int windowOverlap = attributes.value("windowOverlap").toUInt(&ok);
Chris@97 3536 // a percentage value
Chris@97 3537 if (ok) {
Chris@97 3538 if (windowOverlap == 0) setWindowHopLevel(0);
Chris@97 3539 else if (windowOverlap == 25) setWindowHopLevel(1);
Chris@97 3540 else if (windowOverlap == 50) setWindowHopLevel(2);
Chris@97 3541 else if (windowOverlap == 75) setWindowHopLevel(3);
Chris@97 3542 else if (windowOverlap == 90) setWindowHopLevel(4);
Chris@97 3543 }
Chris@97 3544 }
Chris@11 3545
Chris@11 3546 float gain = attributes.value("gain").toFloat(&ok);
Chris@11 3547 if (ok) setGain(gain);
Chris@11 3548
Chris@37 3549 float threshold = attributes.value("threshold").toFloat(&ok);
Chris@37 3550 if (ok) setThreshold(threshold);
Chris@37 3551
Chris@805 3552 int minFrequency = attributes.value("minFrequency").toUInt(&ok);
Chris@187 3553 if (ok) {
Chris@587 3554 SVDEBUG << "SpectrogramLayer::setProperties: setting min freq to " << minFrequency << endl;
Chris@187 3555 setMinFrequency(minFrequency);
Chris@187 3556 }
Chris@37 3557
Chris@805 3558 int maxFrequency = attributes.value("maxFrequency").toUInt(&ok);
Chris@187 3559 if (ok) {
Chris@587 3560 SVDEBUG << "SpectrogramLayer::setProperties: setting max freq to " << maxFrequency << endl;
Chris@187 3561 setMaxFrequency(maxFrequency);
Chris@187 3562 }
Chris@11 3563
Chris@1105 3564 ColourScaleType colourScale = convertToColourScale
Chris@1092 3565 (attributes.value("colourScale").toInt(&ok));
Chris@11 3566 if (ok) setColourScale(colourScale);
Chris@11 3567
Chris@197 3568 int colourMap = attributes.value("colourScheme").toInt(&ok);
Chris@197 3569 if (ok) setColourMap(colourMap);
Chris@11 3570
Chris@37 3571 int colourRotation = attributes.value("colourRotation").toInt(&ok);
Chris@37 3572 if (ok) setColourRotation(colourRotation);
Chris@37 3573
Chris@1103 3574 BinScale binScale = (BinScale)
Chris@11 3575 attributes.value("frequencyScale").toInt(&ok);
Chris@1093 3576 if (ok) setBinScale(binScale);
Chris@1093 3577
Chris@1103 3578 BinDisplay binDisplay = (BinDisplay)
Chris@37 3579 attributes.value("binDisplay").toInt(&ok);
Chris@37 3580 if (ok) setBinDisplay(binDisplay);
Chris@36 3581
Chris@1009 3582 bool haveNewStyleNormalization = false;
Chris@1009 3583
Chris@1009 3584 QString columnNormalization = attributes.value("columnNormalization");
Chris@1009 3585
Chris@1009 3586 if (columnNormalization != "") {
Chris@1009 3587
Chris@1009 3588 haveNewStyleNormalization = true;
Chris@1009 3589
Chris@1009 3590 if (columnNormalization == "peak") {
Chris@1104 3591 setNormalization(ColumnNormalization::Max1);
Chris@1009 3592 } else if (columnNormalization == "hybrid") {
Chris@1104 3593 setNormalization(ColumnNormalization::Hybrid);
Chris@1009 3594 } else if (columnNormalization == "none") {
Chris@1104 3595 setNormalization(ColumnNormalization::None);
Chris@1009 3596 } else {
Chris@1009 3597 cerr << "NOTE: Unknown or unsupported columnNormalization attribute \""
Chris@1009 3598 << columnNormalization << "\"" << endl;
Chris@1009 3599 }
Chris@1009 3600 }
Chris@1009 3601
Chris@1009 3602 if (!haveNewStyleNormalization) {
Chris@1009 3603
Chris@1009 3604 bool normalizeColumns =
Chris@1009 3605 (attributes.value("normalizeColumns").trimmed() == "true");
Chris@1009 3606 if (normalizeColumns) {
Chris@1104 3607 setNormalization(ColumnNormalization::Max1);
Chris@1009 3608 }
Chris@1009 3609
Chris@1009 3610 bool normalizeHybrid =
Chris@1009 3611 (attributes.value("normalizeHybrid").trimmed() == "true");
Chris@1009 3612 if (normalizeHybrid) {
Chris@1104 3613 setNormalization(ColumnNormalization::Hybrid);
Chris@1009 3614 }
Chris@862 3615 }
Chris@153 3616
Chris@153 3617 bool normalizeVisibleArea =
Chris@1099 3618 (attributes.value("normalizeVisibleArea").trimmed() == "true");
Chris@1104 3619 setNormalizeVisibleArea(normalizeVisibleArea);
Chris@1104 3620
Chris@1104 3621 if (!haveNewStyleNormalization && m_normalization == ColumnNormalization::Hybrid) {
Chris@1009 3622 // Tony v1.0 is (and hopefully will remain!) the only released
Chris@1009 3623 // SV-a-like to use old-style attributes when saving sessions
Chris@1009 3624 // that ask for hybrid normalization. It saves them with the
Chris@1009 3625 // wrong gain factor, so hack in a fix for that here -- this
Chris@1009 3626 // gives us backward but not forward compatibility.
Chris@1087 3627 setGain(m_gain / float(getFFTSize() / 2));
Chris@862 3628 }
Chris@11 3629 }
Chris@11 3630