annotate layer/SpectrogramLayer.cpp @ 182:42118892f428

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