annotate layer/SpectrogramLayer.cpp @ 317:e251c3599ea8

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