annotate layer/SpectrogramLayer.cpp @ 299:5c59c433b358

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