annotate layer/SpectrogramLayer.cpp @ 1064:77564d4fff43 spectrogram-minor-refactor

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