annotate layer/SpectrogramLayer.cpp @ 349:369a197737c7

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