annotate layer/SpectrogramLayer.cpp @ 454:e2a40fdadd8c

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