annotate layer/SpectrogramLayer.cpp @ 253:1b1e6947c124

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