annotate layer/SpectrogramLayer.cpp @ 333:e74b56f07c73

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