annotate layer/SpectrogramLayer.cpp @ 1024:3bce4c45b681 spectrogram-minor-refactor

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