annotate layer/SpectrogramLayer.cpp @ 162:f32212631b9c

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