annotate layer/SpectrogramLayer.cpp @ 329:bbc9666cb961 spectrogram-cache-rejig

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