annotate layer/SpectrogramLayer.cpp @ 1:ab83c415a6cd

* Backed out partially complete changes to make the spectrogram only store results up to the requested max frequency. The speed improvement was minimal at the expense of annoyance when changing frequency limit, and although it did save memory, it wasn't yet reliable and fixing it is not a high enough priority.
author Chris Cannam
date Tue, 10 Jan 2006 17:04:02 +0000
parents 2a4f26e85b4c
children 37b110168acf
rev   line source
Chris@0 1 /* -*- c-basic-offset: 4 -*- vi:set ts=8 sts=4 sw=4: */
Chris@0 2
Chris@0 3 /*
Chris@0 4 A waveform viewer and audio annotation editor.
Chris@0 5 Chris Cannam, Queen Mary University of London, 2005
Chris@0 6
Chris@0 7 This is experimental software. Not for distribution.
Chris@0 8 */
Chris@0 9
Chris@0 10 #include "SpectrogramLayer.h"
Chris@0 11
Chris@0 12 #include "base/View.h"
Chris@0 13 #include "base/Profiler.h"
Chris@0 14 #include "base/AudioLevel.h"
Chris@0 15 #include "base/Window.h"
Chris@0 16
Chris@0 17 #include <QPainter>
Chris@0 18 #include <QImage>
Chris@0 19 #include <QPixmap>
Chris@0 20 #include <QRect>
Chris@0 21 #include <QTimer>
Chris@0 22
Chris@0 23 #include <iostream>
Chris@0 24
Chris@0 25 #include <cassert>
Chris@0 26 #include <cmath>
Chris@0 27
Chris@0 28 //#define DEBUG_SPECTROGRAM_REPAINT 1
Chris@0 29
Chris@0 30
Chris@0 31 SpectrogramLayer::SpectrogramLayer(View *w, Configuration config) :
Chris@0 32 Layer(w),
Chris@0 33 m_model(0),
Chris@0 34 m_channel(0),
Chris@0 35 m_windowSize(1024),
Chris@0 36 m_windowType(HanningWindow),
Chris@0 37 m_windowOverlap(50),
Chris@0 38 m_gain(1.0),
Chris@0 39 m_maxFrequency(8000),
Chris@0 40 m_colourScale(dBColourScale),
Chris@0 41 m_colourScheme(DefaultColours),
Chris@0 42 m_frequencyScale(LinearFrequencyScale),
Chris@0 43 m_cache(0),
Chris@0 44 m_cacheInvalid(true),
Chris@0 45 m_pixmapCache(0),
Chris@0 46 m_pixmapCacheInvalid(true),
Chris@0 47 m_fillThread(0),
Chris@0 48 m_updateTimer(0),
Chris@0 49 m_lastFillExtent(0),
Chris@0 50 m_exiting(false)
Chris@0 51 {
Chris@0 52 if (config == MelodicRange) {
Chris@0 53 setWindowSize(8192);
Chris@0 54 setWindowOverlap(90);
Chris@0 55 setWindowType(ParzenWindow);
Chris@0 56 setMaxFrequency(1000);
Chris@0 57 setColourScale(LinearColourScale);
Chris@0 58 }
Chris@0 59
Chris@0 60 if (m_view) m_view->setLightBackground(false);
Chris@0 61 m_view->addLayer(this);
Chris@0 62 }
Chris@0 63
Chris@0 64 SpectrogramLayer::~SpectrogramLayer()
Chris@0 65 {
Chris@0 66 delete m_updateTimer;
Chris@0 67 m_updateTimer = 0;
Chris@0 68
Chris@0 69 m_exiting = true;
Chris@0 70 m_condition.wakeAll();
Chris@0 71 if (m_fillThread) m_fillThread->wait();
Chris@0 72 delete m_fillThread;
Chris@0 73
Chris@0 74 delete m_cache;
Chris@0 75 }
Chris@0 76
Chris@0 77 void
Chris@0 78 SpectrogramLayer::setModel(const DenseTimeValueModel *model)
Chris@0 79 {
Chris@0 80 m_mutex.lock();
Chris@0 81 m_model = model;
Chris@0 82 m_mutex.unlock();
Chris@0 83
Chris@0 84 if (!m_model || !m_model->isOK()) return;
Chris@0 85
Chris@0 86 connect(m_model, SIGNAL(modelChanged()), this, SIGNAL(modelChanged()));
Chris@0 87 connect(m_model, SIGNAL(modelChanged(size_t, size_t)),
Chris@0 88 this, SIGNAL(modelChanged(size_t, size_t)));
Chris@0 89
Chris@0 90 connect(m_model, SIGNAL(completionChanged()),
Chris@0 91 this, SIGNAL(modelCompletionChanged()));
Chris@0 92
Chris@0 93 connect(m_model, SIGNAL(modelChanged()), this, SLOT(cacheInvalid()));
Chris@0 94 connect(m_model, SIGNAL(modelChanged(size_t, size_t)),
Chris@0 95 this, SLOT(cacheInvalid(size_t, size_t)));
Chris@0 96
Chris@0 97 emit modelReplaced();
Chris@0 98 fillCache();
Chris@0 99 }
Chris@0 100
Chris@0 101 Layer::PropertyList
Chris@0 102 SpectrogramLayer::getProperties() const
Chris@0 103 {
Chris@0 104 PropertyList list;
Chris@0 105 list.push_back(tr("Colour"));
Chris@0 106 list.push_back(tr("Colour Scale"));
Chris@0 107 list.push_back(tr("Window Type"));
Chris@0 108 list.push_back(tr("Window Size"));
Chris@0 109 list.push_back(tr("Window Overlap"));
Chris@0 110 list.push_back(tr("Gain"));
Chris@0 111 list.push_back(tr("Max Frequency"));
Chris@0 112 list.push_back(tr("Frequency Scale"));
Chris@0 113 return list;
Chris@0 114 }
Chris@0 115
Chris@0 116 Layer::PropertyType
Chris@0 117 SpectrogramLayer::getPropertyType(const PropertyName &name) const
Chris@0 118 {
Chris@0 119 if (name == tr("Gain")) return RangeProperty;
Chris@0 120 return ValueProperty;
Chris@0 121 }
Chris@0 122
Chris@0 123 QString
Chris@0 124 SpectrogramLayer::getPropertyGroupName(const PropertyName &name) const
Chris@0 125 {
Chris@0 126 if (name == tr("Window Size") ||
Chris@0 127 name == tr("Window Overlap")) return tr("Window");
Chris@0 128 if (name == tr("Gain") ||
Chris@0 129 name == tr("Colour Scale")) return tr("Scale");
Chris@0 130 if (name == tr("Max Frequency") ||
Chris@0 131 name == tr("Frequency Scale")) return tr("Frequency");
Chris@0 132 return QString();
Chris@0 133 }
Chris@0 134
Chris@0 135 int
Chris@0 136 SpectrogramLayer::getPropertyRangeAndValue(const PropertyName &name,
Chris@0 137 int *min, int *max) const
Chris@0 138 {
Chris@0 139 int deft = 0;
Chris@0 140
Chris@0 141 if (name == tr("Gain")) {
Chris@0 142
Chris@0 143 *min = -50;
Chris@0 144 *max = 50;
Chris@0 145
Chris@0 146 deft = lrint(log10(m_gain) * 20.0);
Chris@0 147 if (deft < *min) deft = *min;
Chris@0 148 if (deft > *max) deft = *max;
Chris@0 149
Chris@0 150 } else if (name == tr("Colour Scale")) {
Chris@0 151
Chris@0 152 *min = 0;
Chris@0 153 *max = 3;
Chris@0 154
Chris@0 155 deft = (int)m_colourScale;
Chris@0 156
Chris@0 157 } else if (name == tr("Colour")) {
Chris@0 158
Chris@0 159 *min = 0;
Chris@0 160 *max = 5;
Chris@0 161
Chris@0 162 deft = (int)m_colourScheme;
Chris@0 163
Chris@0 164 } else if (name == tr("Window Type")) {
Chris@0 165
Chris@0 166 *min = 0;
Chris@0 167 *max = 6;
Chris@0 168
Chris@0 169 deft = (int)m_windowType;
Chris@0 170
Chris@0 171 } else if (name == tr("Window Size")) {
Chris@0 172
Chris@0 173 *min = 0;
Chris@0 174 *max = 10;
Chris@0 175
Chris@0 176 deft = 0;
Chris@0 177 int ws = m_windowSize;
Chris@0 178 while (ws > 32) { ws >>= 1; deft ++; }
Chris@0 179
Chris@0 180 } else if (name == tr("Window Overlap")) {
Chris@0 181
Chris@0 182 *min = 0;
Chris@0 183 *max = 4;
Chris@0 184
Chris@0 185 deft = m_windowOverlap / 25;
Chris@0 186 if (m_windowOverlap == 90) deft = 4;
Chris@0 187
Chris@0 188 } else if (name == tr("Max Frequency")) {
Chris@0 189
Chris@0 190 *min = 0;
Chris@0 191 *max = 9;
Chris@0 192
Chris@0 193 switch (m_maxFrequency) {
Chris@0 194 case 500: deft = 0; break;
Chris@0 195 case 1000: deft = 1; break;
Chris@0 196 case 1500: deft = 2; break;
Chris@0 197 case 2000: deft = 3; break;
Chris@0 198 case 4000: deft = 4; break;
Chris@0 199 case 6000: deft = 5; break;
Chris@0 200 case 8000: deft = 6; break;
Chris@0 201 case 12000: deft = 7; break;
Chris@0 202 case 16000: deft = 8; break;
Chris@0 203 default: deft = 9; break;
Chris@0 204 }
Chris@0 205
Chris@0 206 } else if (name == tr("Frequency Scale")) {
Chris@0 207
Chris@0 208 *min = 0;
Chris@0 209 *max = 1;
Chris@0 210 deft = (int)m_frequencyScale;
Chris@0 211
Chris@0 212 } else {
Chris@0 213 deft = Layer::getPropertyRangeAndValue(name, min, max);
Chris@0 214 }
Chris@0 215
Chris@0 216 return deft;
Chris@0 217 }
Chris@0 218
Chris@0 219 QString
Chris@0 220 SpectrogramLayer::getPropertyValueLabel(const PropertyName &name,
Chris@0 221 int value) const
Chris@0 222 {
Chris@0 223 if (name == tr("Colour")) {
Chris@0 224 switch (value) {
Chris@0 225 default:
Chris@0 226 case 0: return tr("Default");
Chris@0 227 case 1: return tr("White on Black");
Chris@0 228 case 2: return tr("Black on White");
Chris@0 229 case 3: return tr("Red on Blue");
Chris@0 230 case 4: return tr("Yellow on Black");
Chris@0 231 case 5: return tr("Red on Black");
Chris@0 232 }
Chris@0 233 }
Chris@0 234 if (name == tr("Colour Scale")) {
Chris@0 235 switch (value) {
Chris@0 236 default:
Chris@0 237 case 0: return tr("Level Linear");
Chris@0 238 case 1: return tr("Level Meter");
Chris@0 239 case 2: return tr("Level dB");
Chris@0 240 case 3: return tr("Phase");
Chris@0 241 }
Chris@0 242 }
Chris@0 243 if (name == tr("Window Type")) {
Chris@0 244 switch ((WindowType)value) {
Chris@0 245 default:
Chris@0 246 case RectangularWindow: return tr("Rectangular");
Chris@0 247 case BartlettWindow: return tr("Bartlett");
Chris@0 248 case HammingWindow: return tr("Hamming");
Chris@0 249 case HanningWindow: return tr("Hanning");
Chris@0 250 case BlackmanWindow: return tr("Blackman");
Chris@0 251 case GaussianWindow: return tr("Gaussian");
Chris@0 252 case ParzenWindow: return tr("Parzen");
Chris@0 253 }
Chris@0 254 }
Chris@0 255 if (name == tr("Window Size")) {
Chris@0 256 return QString("%1").arg(32 << value);
Chris@0 257 }
Chris@0 258 if (name == tr("Window Overlap")) {
Chris@0 259 switch (value) {
Chris@0 260 default:
Chris@0 261 case 0: return tr("None");
Chris@0 262 case 1: return tr("25 %");
Chris@0 263 case 2: return tr("50 %");
Chris@0 264 case 3: return tr("75 %");
Chris@0 265 case 4: return tr("90 %");
Chris@0 266 }
Chris@0 267 }
Chris@0 268 if (name == tr("Max Frequency")) {
Chris@0 269 switch (value) {
Chris@0 270 default:
Chris@0 271 case 0: return tr("500 Hz");
Chris@0 272 case 1: return tr("1 KHz");
Chris@0 273 case 2: return tr("1.5 KHz");
Chris@0 274 case 3: return tr("2 KHz");
Chris@0 275 case 4: return tr("4 KHz");
Chris@0 276 case 5: return tr("6 KHz");
Chris@0 277 case 6: return tr("8 KHz");
Chris@0 278 case 7: return tr("12 KHz");
Chris@0 279 case 8: return tr("16 KHz");
Chris@0 280 case 9: return tr("All");
Chris@0 281 }
Chris@0 282 }
Chris@0 283 if (name == tr("Frequency Scale")) {
Chris@0 284 switch (value) {
Chris@0 285 default:
Chris@0 286 case 0: return tr("Linear");
Chris@0 287 case 1: return tr("Log");
Chris@0 288 }
Chris@0 289 }
Chris@0 290 return tr("<unknown>");
Chris@0 291 }
Chris@0 292
Chris@0 293 void
Chris@0 294 SpectrogramLayer::setProperty(const PropertyName &name, int value)
Chris@0 295 {
Chris@0 296 if (name == tr("Gain")) {
Chris@0 297 setGain(pow(10, float(value)/20.0));
Chris@0 298 } else if (name == tr("Colour")) {
Chris@0 299 if (m_view) m_view->setLightBackground(value == 2);
Chris@0 300 switch (value) {
Chris@0 301 default:
Chris@0 302 case 0: setColourScheme(DefaultColours); break;
Chris@0 303 case 1: setColourScheme(WhiteOnBlack); break;
Chris@0 304 case 2: setColourScheme(BlackOnWhite); break;
Chris@0 305 case 3: setColourScheme(RedOnBlue); break;
Chris@0 306 case 4: setColourScheme(YellowOnBlack); break;
Chris@0 307 case 5: setColourScheme(RedOnBlack); break;
Chris@0 308 }
Chris@0 309 } else if (name == tr("Window Type")) {
Chris@0 310 setWindowType(WindowType(value));
Chris@0 311 } else if (name == tr("Window Size")) {
Chris@0 312 setWindowSize(32 << value);
Chris@0 313 } else if (name == tr("Window Overlap")) {
Chris@0 314 if (value == 4) setWindowOverlap(90);
Chris@0 315 else setWindowOverlap(25 * value);
Chris@0 316 } else if (name == tr("Max Frequency")) {
Chris@0 317 switch (value) {
Chris@0 318 case 0: setMaxFrequency(500); break;
Chris@0 319 case 1: setMaxFrequency(1000); break;
Chris@0 320 case 2: setMaxFrequency(1500); break;
Chris@0 321 case 3: setMaxFrequency(2000); break;
Chris@0 322 case 4: setMaxFrequency(4000); break;
Chris@0 323 case 5: setMaxFrequency(6000); break;
Chris@0 324 case 6: setMaxFrequency(8000); break;
Chris@0 325 case 7: setMaxFrequency(12000); break;
Chris@0 326 case 8: setMaxFrequency(16000); break;
Chris@0 327 default:
Chris@0 328 case 9: setMaxFrequency(0); break;
Chris@0 329 }
Chris@0 330 } else if (name == tr("Colour Scale")) {
Chris@0 331 switch (value) {
Chris@0 332 default:
Chris@0 333 case 0: setColourScale(LinearColourScale); break;
Chris@0 334 case 1: setColourScale(MeterColourScale); break;
Chris@0 335 case 2: setColourScale(dBColourScale); break;
Chris@0 336 case 3: setColourScale(PhaseColourScale); break;
Chris@0 337 }
Chris@0 338 } else if (name == tr("Frequency Scale")) {
Chris@0 339 switch (value) {
Chris@0 340 default:
Chris@0 341 case 0: setFrequencyScale(LinearFrequencyScale); break;
Chris@0 342 case 1: setFrequencyScale(LogFrequencyScale); break;
Chris@0 343 }
Chris@0 344 }
Chris@0 345 }
Chris@0 346
Chris@0 347 void
Chris@0 348 SpectrogramLayer::setChannel(int ch)
Chris@0 349 {
Chris@0 350 if (m_channel == ch) return;
Chris@0 351
Chris@0 352 m_mutex.lock();
Chris@0 353 m_cacheInvalid = true;
Chris@0 354 m_pixmapCacheInvalid = true;
Chris@0 355
Chris@0 356 m_channel = ch;
Chris@0 357 emit layerParametersChanged();
Chris@0 358
Chris@0 359 m_mutex.unlock();
Chris@0 360 fillCache();
Chris@0 361
Chris@0 362 }
Chris@0 363
Chris@0 364 int
Chris@0 365 SpectrogramLayer::getChannel() const
Chris@0 366 {
Chris@0 367 return m_channel;
Chris@0 368 }
Chris@0 369
Chris@0 370 void
Chris@0 371 SpectrogramLayer::setWindowSize(size_t ws)
Chris@0 372 {
Chris@0 373 if (m_windowSize == ws) return;
Chris@0 374
Chris@0 375 m_mutex.lock();
Chris@0 376 m_cacheInvalid = true;
Chris@0 377 m_pixmapCacheInvalid = true;
Chris@0 378
Chris@0 379 m_windowSize = ws;
Chris@0 380 emit layerParametersChanged();
Chris@0 381
Chris@0 382 m_mutex.unlock();
Chris@0 383 fillCache();
Chris@0 384
Chris@0 385 }
Chris@0 386
Chris@0 387 size_t
Chris@0 388 SpectrogramLayer::getWindowSize() const
Chris@0 389 {
Chris@0 390 return m_windowSize;
Chris@0 391 }
Chris@0 392
Chris@0 393 void
Chris@0 394 SpectrogramLayer::setWindowOverlap(size_t wi)
Chris@0 395 {
Chris@0 396 if (m_windowOverlap == wi) return;
Chris@0 397
Chris@0 398 m_mutex.lock();
Chris@0 399 m_cacheInvalid = true;
Chris@0 400 m_pixmapCacheInvalid = true;
Chris@0 401
Chris@0 402 m_windowOverlap = wi;
Chris@0 403 emit layerParametersChanged();
Chris@0 404
Chris@0 405 m_mutex.unlock();
Chris@0 406 fillCache();
Chris@0 407 }
Chris@0 408
Chris@0 409 size_t
Chris@0 410 SpectrogramLayer::getWindowOverlap() const
Chris@0 411 {
Chris@0 412 return m_windowOverlap;
Chris@0 413 }
Chris@0 414
Chris@0 415 void
Chris@0 416 SpectrogramLayer::setWindowType(WindowType w)
Chris@0 417 {
Chris@0 418 if (m_windowType == w) return;
Chris@0 419
Chris@0 420 m_mutex.lock();
Chris@0 421 m_cacheInvalid = true;
Chris@0 422 m_pixmapCacheInvalid = true;
Chris@0 423
Chris@0 424 m_windowType = w;
Chris@0 425 emit layerParametersChanged();
Chris@0 426
Chris@0 427 m_mutex.unlock();
Chris@0 428 fillCache();
Chris@0 429 }
Chris@0 430
Chris@0 431 WindowType
Chris@0 432 SpectrogramLayer::getWindowType() const
Chris@0 433 {
Chris@0 434 return m_windowType;
Chris@0 435 }
Chris@0 436
Chris@0 437 void
Chris@0 438 SpectrogramLayer::setGain(float gain)
Chris@0 439 {
Chris@0 440 if (m_gain == gain) return; //!!! inadequate for floats!
Chris@0 441
Chris@0 442 m_mutex.lock();
Chris@0 443 m_cacheInvalid = true;
Chris@0 444 m_pixmapCacheInvalid = true;
Chris@0 445
Chris@0 446 m_gain = gain;
Chris@0 447 emit layerParametersChanged();
Chris@0 448
Chris@0 449 m_mutex.unlock();
Chris@0 450 fillCache();
Chris@0 451 }
Chris@0 452
Chris@0 453 float
Chris@0 454 SpectrogramLayer::getGain() const
Chris@0 455 {
Chris@0 456 return m_gain;
Chris@0 457 }
Chris@0 458
Chris@0 459 void
Chris@0 460 SpectrogramLayer::setMaxFrequency(size_t mf)
Chris@0 461 {
Chris@0 462 if (m_maxFrequency == mf) return;
Chris@0 463
Chris@0 464 m_mutex.lock();
Chris@1 465 // don't need to invalidate main cache here
Chris@0 466 m_pixmapCacheInvalid = true;
Chris@0 467
Chris@0 468 m_maxFrequency = mf;
Chris@0 469 emit layerParametersChanged();
Chris@0 470
Chris@0 471 m_mutex.unlock();
Chris@0 472 }
Chris@0 473
Chris@0 474 size_t
Chris@0 475 SpectrogramLayer::getMaxFrequency() const
Chris@0 476 {
Chris@0 477 return m_maxFrequency;
Chris@0 478 }
Chris@0 479
Chris@0 480 void
Chris@0 481 SpectrogramLayer::setColourScale(ColourScale colourScale)
Chris@0 482 {
Chris@0 483 if (m_colourScale == colourScale) return;
Chris@0 484
Chris@0 485 m_mutex.lock();
Chris@0 486 m_cacheInvalid = true;
Chris@0 487 m_pixmapCacheInvalid = true;
Chris@0 488
Chris@0 489 m_colourScale = colourScale;
Chris@0 490 emit layerParametersChanged();
Chris@0 491
Chris@0 492 m_mutex.unlock();
Chris@0 493 fillCache();
Chris@0 494 }
Chris@0 495
Chris@0 496 SpectrogramLayer::ColourScale
Chris@0 497 SpectrogramLayer::getColourScale() const
Chris@0 498 {
Chris@0 499 return m_colourScale;
Chris@0 500 }
Chris@0 501
Chris@0 502 void
Chris@0 503 SpectrogramLayer::setColourScheme(ColourScheme scheme)
Chris@0 504 {
Chris@0 505 if (m_colourScheme == scheme) return;
Chris@0 506
Chris@0 507 m_mutex.lock();
Chris@0 508 // don't need to invalidate main cache here
Chris@0 509 m_pixmapCacheInvalid = true;
Chris@0 510
Chris@0 511 m_colourScheme = scheme;
Chris@0 512 setCacheColourmap();
Chris@0 513 emit layerParametersChanged();
Chris@0 514
Chris@0 515 m_mutex.unlock();
Chris@0 516 }
Chris@0 517
Chris@0 518 SpectrogramLayer::ColourScheme
Chris@0 519 SpectrogramLayer::getColourScheme() const
Chris@0 520 {
Chris@0 521 return m_colourScheme;
Chris@0 522 }
Chris@0 523
Chris@0 524 void
Chris@0 525 SpectrogramLayer::setFrequencyScale(FrequencyScale frequencyScale)
Chris@0 526 {
Chris@0 527 if (m_frequencyScale == frequencyScale) return;
Chris@0 528
Chris@0 529 m_mutex.lock();
Chris@0 530 // don't need to invalidate main cache here
Chris@0 531 m_pixmapCacheInvalid = true;
Chris@0 532
Chris@0 533 m_frequencyScale = frequencyScale;
Chris@0 534 emit layerParametersChanged();
Chris@0 535
Chris@0 536 m_mutex.unlock();
Chris@0 537 }
Chris@0 538
Chris@0 539 SpectrogramLayer::FrequencyScale
Chris@0 540 SpectrogramLayer::getFrequencyScale() const
Chris@0 541 {
Chris@0 542 return m_frequencyScale;
Chris@0 543 }
Chris@0 544
Chris@0 545 void
Chris@0 546 SpectrogramLayer::cacheInvalid()
Chris@0 547 {
Chris@0 548 m_cacheInvalid = true;
Chris@0 549 m_pixmapCacheInvalid = true;
Chris@0 550 m_cachedInitialVisibleArea = false;
Chris@0 551 fillCache();
Chris@0 552 }
Chris@0 553
Chris@0 554 void
Chris@0 555 SpectrogramLayer::cacheInvalid(size_t, size_t)
Chris@0 556 {
Chris@0 557 // for now (or forever?)
Chris@0 558 cacheInvalid();
Chris@0 559 }
Chris@0 560
Chris@0 561 void
Chris@0 562 SpectrogramLayer::fillCache()
Chris@0 563 {
Chris@0 564 #ifdef DEBUG_SPECTROGRAM_REPAINT
Chris@0 565 std::cerr << "SpectrogramLayer::fillCache" << std::endl;
Chris@0 566 #endif
Chris@0 567 QMutexLocker locker(&m_mutex);
Chris@0 568
Chris@0 569 m_lastFillExtent = 0;
Chris@0 570
Chris@0 571 delete m_updateTimer;
Chris@0 572 m_updateTimer = new QTimer(this);
Chris@0 573 connect(m_updateTimer, SIGNAL(timeout()), this, SLOT(fillTimerTimedOut()));
Chris@0 574 m_updateTimer->start(200);
Chris@0 575
Chris@0 576 if (!m_fillThread) {
Chris@0 577 std::cerr << "SpectrogramLayer::fillCache creating thread" << std::endl;
Chris@0 578 m_fillThread = new CacheFillThread(*this);
Chris@0 579 m_fillThread->start();
Chris@0 580 }
Chris@0 581
Chris@0 582 m_condition.wakeAll();
Chris@0 583 }
Chris@0 584
Chris@0 585 void
Chris@0 586 SpectrogramLayer::fillTimerTimedOut()
Chris@0 587 {
Chris@0 588 if (m_fillThread && m_model) {
Chris@0 589 size_t fillExtent = m_fillThread->getFillExtent();
Chris@0 590 #ifdef DEBUG_SPECTROGRAM_REPAINT
Chris@0 591 std::cerr << "SpectrogramLayer::fillTimerTimedOut: extent " << fillExtent << ", last " << m_lastFillExtent << ", total " << m_model->getEndFrame() << std::endl;
Chris@0 592 #endif
Chris@0 593 if (fillExtent >= m_lastFillExtent) {
Chris@0 594 if (fillExtent >= m_model->getEndFrame() && m_lastFillExtent > 0) {
Chris@0 595 #ifdef DEBUG_SPECTROGRAM_REPAINT
Chris@0 596 std::cerr << "complete!" << std::endl;
Chris@0 597 #endif
Chris@0 598 emit modelChanged();
Chris@0 599 m_pixmapCacheInvalid = true;
Chris@0 600 delete m_updateTimer;
Chris@0 601 m_updateTimer = 0;
Chris@0 602 m_lastFillExtent = 0;
Chris@0 603 } else if (fillExtent > m_lastFillExtent) {
Chris@0 604 #ifdef DEBUG_SPECTROGRAM_REPAINT
Chris@0 605 std::cerr << "SpectrogramLayer: emitting modelChanged("
Chris@0 606 << m_lastFillExtent << "," << fillExtent << ")" << std::endl;
Chris@0 607 #endif
Chris@0 608 emit modelChanged(m_lastFillExtent, fillExtent);
Chris@0 609 m_pixmapCacheInvalid = true;
Chris@0 610 m_lastFillExtent = fillExtent;
Chris@0 611 }
Chris@0 612 } else {
Chris@0 613 if (m_view) {
Chris@0 614 size_t sf = 0;
Chris@0 615 if (m_view->getStartFrame() > 0) sf = m_view->getStartFrame();
Chris@0 616 #ifdef DEBUG_SPECTROGRAM_REPAINT
Chris@0 617 std::cerr << "SpectrogramLayer: going backwards, emitting modelChanged("
Chris@0 618 << sf << "," << m_view->getEndFrame() << ")" << std::endl;
Chris@0 619 #endif
Chris@0 620 emit modelChanged(sf, m_view->getEndFrame());
Chris@0 621 m_pixmapCacheInvalid = true;
Chris@0 622 }
Chris@0 623 m_lastFillExtent = fillExtent;
Chris@0 624 }
Chris@0 625 }
Chris@0 626 }
Chris@0 627
Chris@0 628 void
Chris@0 629 SpectrogramLayer::setCacheColourmap()
Chris@0 630 {
Chris@0 631 if (m_cacheInvalid || !m_cache) return;
Chris@0 632
Chris@0 633 m_cache->setNumColors(256);
Chris@0 634
Chris@0 635 m_cache->setColor(0, qRgb(255, 255, 255));
Chris@0 636
Chris@0 637 for (int pixel = 1; pixel < 256; ++pixel) {
Chris@0 638
Chris@0 639 QColor colour;
Chris@0 640 int hue, px;
Chris@0 641
Chris@0 642 switch (m_colourScheme) {
Chris@0 643
Chris@0 644 default:
Chris@0 645 case DefaultColours:
Chris@0 646 hue = 256 - pixel;
Chris@0 647 colour = QColor::fromHsv(hue, pixel/2 + 128, pixel);
Chris@0 648 break;
Chris@0 649
Chris@0 650 case WhiteOnBlack:
Chris@0 651 colour = QColor(pixel, pixel, pixel);
Chris@0 652 break;
Chris@0 653
Chris@0 654 case BlackOnWhite:
Chris@0 655 colour = QColor(256-pixel, 256-pixel, 256-pixel);
Chris@0 656 break;
Chris@0 657
Chris@0 658 case RedOnBlue:
Chris@0 659 colour = QColor(pixel > 128 ? (pixel - 128) * 2 : 0, 0,
Chris@0 660 pixel < 128 ? pixel : (256 - pixel));
Chris@0 661 break;
Chris@0 662
Chris@0 663 case YellowOnBlack:
Chris@0 664 px = 256 - pixel;
Chris@0 665 colour = QColor(px < 64 ? 255 - px/2 :
Chris@0 666 px < 128 ? 224 - (px - 64) :
Chris@0 667 px < 192 ? 160 - (px - 128) * 3 / 2 :
Chris@0 668 256 - px,
Chris@0 669 pixel,
Chris@0 670 pixel / 4);
Chris@0 671 break;
Chris@0 672
Chris@0 673 case RedOnBlack:
Chris@0 674 colour = QColor::fromHsv(10, pixel, pixel);
Chris@0 675 break;
Chris@0 676 }
Chris@0 677
Chris@0 678 m_cache->setColor
Chris@0 679 (pixel, qRgb(colour.red(), colour.green(), colour.blue()));
Chris@0 680 }
Chris@0 681 }
Chris@0 682
Chris@0 683 bool
Chris@0 684 SpectrogramLayer::fillCacheColumn(int column, double *input,
Chris@0 685 fftw_complex *output,
Chris@0 686 fftw_plan plan,
Chris@0 687 const Window<double> &windower,
Chris@0 688 bool lock) const
Chris@0 689 {
Chris@0 690 size_t increment = m_windowSize - m_windowSize * m_windowOverlap / 100;
Chris@0 691 int startFrame = increment * column;
Chris@0 692 int endFrame = startFrame + m_windowSize;
Chris@0 693
Chris@0 694 startFrame -= int(m_windowSize - increment) / 2;
Chris@0 695 endFrame -= int(m_windowSize - increment) / 2;
Chris@0 696 size_t pfx = 0;
Chris@0 697
Chris@0 698 if (startFrame < 0) {
Chris@0 699 pfx = size_t(-startFrame);
Chris@0 700 for (size_t i = 0; i < pfx; ++i) {
Chris@0 701 input[i] = 0.0;
Chris@0 702 }
Chris@0 703 }
Chris@0 704
Chris@0 705 size_t got = m_model->getValues(m_channel, startFrame + pfx,
Chris@0 706 endFrame, input + pfx);
Chris@0 707 while (got + pfx < m_windowSize) {
Chris@0 708 input[got + pfx] = 0.0;
Chris@0 709 ++got;
Chris@0 710 }
Chris@0 711
Chris@0 712 if (m_gain != 1.0) {
Chris@0 713 for (size_t i = 0; i < m_windowSize; ++i) {
Chris@0 714 input[i] *= m_gain;
Chris@0 715 }
Chris@0 716 }
Chris@0 717
Chris@0 718 windower.cut(input);
Chris@0 719
Chris@0 720 fftw_execute(plan);
Chris@0 721
Chris@0 722 if (lock) m_mutex.lock();
Chris@0 723 bool interrupted = false;
Chris@0 724
Chris@0 725 for (size_t i = 0; i < m_windowSize / 2; ++i) {
Chris@0 726
Chris@0 727 int value = 0;
Chris@0 728
Chris@0 729 if (m_colourScale == PhaseColourScale) {
Chris@0 730
Chris@0 731 double phase = atan2(-output[i][1], output[i][0]);
Chris@0 732 value = int((phase * 128 / M_PI) + 128);
Chris@0 733
Chris@0 734 } else {
Chris@1 735
Chris@0 736 double mag = sqrt(output[i][0] * output[i][0] +
Chris@0 737 output[i][1] * output[i][1]);
Chris@0 738 mag /= m_windowSize / 2;
Chris@0 739
Chris@0 740 switch (m_colourScale) {
Chris@0 741
Chris@0 742 default:
Chris@0 743 case LinearColourScale:
Chris@0 744 value = int(mag * 50 * 256);
Chris@0 745 break;
Chris@0 746
Chris@0 747 case MeterColourScale:
Chris@0 748 value = AudioLevel::multiplier_to_preview(mag * 50, 256);
Chris@0 749 break;
Chris@0 750
Chris@0 751 case dBColourScale:
Chris@0 752 mag = 20.0 * log10(mag);
Chris@0 753 mag = (mag + 80.0) / 80.0;
Chris@0 754 if (mag < 0.0) mag = 0.0;
Chris@0 755 if (mag > 1.0) mag = 1.0;
Chris@0 756 value = int(mag * 256);
Chris@0 757 }
Chris@0 758 }
Chris@0 759
Chris@0 760 if (value > 254) value = 254;
Chris@0 761 if (value < 0) value = 0;
Chris@0 762
Chris@0 763 if (m_cacheInvalid || m_exiting) {
Chris@0 764 interrupted = true;
Chris@0 765 break;
Chris@0 766 }
Chris@0 767
Chris@1 768 if (column < m_cache->width() && (int)i < m_cache->height()) {
Chris@0 769 m_cache->setPixel(column, i, value + 1); // 0 is "unset"
Chris@0 770 }
Chris@0 771 }
Chris@0 772
Chris@0 773 if (lock) m_mutex.unlock();
Chris@0 774 return !interrupted;
Chris@0 775 }
Chris@0 776
Chris@0 777 void
Chris@0 778 SpectrogramLayer::CacheFillThread::run()
Chris@0 779 {
Chris@0 780 // std::cerr << "SpectrogramLayer::CacheFillThread::run" << std::endl;
Chris@0 781
Chris@0 782 m_layer.m_mutex.lock();
Chris@0 783
Chris@0 784 while (!m_layer.m_exiting) {
Chris@0 785
Chris@0 786 bool interrupted = false;
Chris@0 787
Chris@0 788 // std::cerr << "SpectrogramLayer::CacheFillThread::run in loop" << std::endl;
Chris@0 789
Chris@1 790 if (m_layer.m_model && m_layer.m_cacheInvalid) {
Chris@0 791
Chris@0 792 // std::cerr << "SpectrogramLayer::CacheFillThread::run: something to do" << std::endl;
Chris@0 793
Chris@0 794 while (!m_layer.m_model->isReady()) {
Chris@0 795 m_layer.m_condition.wait(&m_layer.m_mutex, 100);
Chris@0 796 }
Chris@0 797
Chris@0 798 m_layer.m_cachedInitialVisibleArea = false;
Chris@0 799 m_layer.m_cacheInvalid = false;
Chris@0 800 m_fillExtent = 0;
Chris@0 801 m_fillCompletion = 0;
Chris@0 802
Chris@0 803 std::cerr << "SpectrogramLayer::CacheFillThread::run: model is ready" << std::endl;
Chris@0 804
Chris@0 805 size_t start = m_layer.m_model->getStartFrame();
Chris@0 806 size_t end = m_layer.m_model->getEndFrame();
Chris@0 807 size_t windowSize = m_layer.m_windowSize;
Chris@0 808 size_t windowIncrement = m_layer.getWindowIncrement();
Chris@0 809
Chris@0 810 size_t visibleStart = start;
Chris@0 811 size_t visibleEnd = end;
Chris@0 812
Chris@0 813 if (m_layer.m_view) {
Chris@0 814 if (m_layer.m_view->getStartFrame() < 0) {
Chris@0 815 visibleStart = 0;
Chris@0 816 } else {
Chris@0 817 visibleStart = m_layer.m_view->getStartFrame();
Chris@0 818 visibleStart = (visibleStart / windowIncrement) *
Chris@0 819 windowIncrement;
Chris@0 820 }
Chris@0 821 visibleEnd = m_layer.m_view->getEndFrame();
Chris@0 822 }
Chris@0 823
Chris@0 824 delete m_layer.m_cache;
Chris@0 825 m_layer.m_cache = new QImage((end - start) / windowIncrement + 1,
Chris@1 826 windowSize / 2,
Chris@1 827 QImage::Format_Indexed8);
Chris@0 828
Chris@0 829 m_layer.setCacheColourmap();
Chris@0 830
Chris@0 831 m_layer.m_cache->fill(0);
Chris@0 832 m_layer.m_mutex.unlock();
Chris@0 833
Chris@0 834 double *input = (double *)
Chris@0 835 fftw_malloc(windowSize * sizeof(double));
Chris@0 836
Chris@0 837 fftw_complex *output = (fftw_complex *)
Chris@0 838 fftw_malloc(windowSize * sizeof(fftw_complex));
Chris@0 839
Chris@0 840 fftw_plan plan = fftw_plan_dft_r2c_1d(windowSize, input,
Chris@1 841 output, FFTW_ESTIMATE);
Chris@0 842
Chris@0 843 Window<double> windower(m_layer.m_windowType, m_layer.m_windowSize);
Chris@0 844
Chris@0 845 if (!plan) {
Chris@1 846 std::cerr << "WARNING: fftw_plan_dft_r2c_1d(" << windowSize << ") failed!" << std::endl;
Chris@0 847 fftw_free(input);
Chris@0 848 fftw_free(output);
Chris@0 849 m_layer.m_mutex.lock();
Chris@0 850 continue;
Chris@0 851 }
Chris@0 852
Chris@0 853 int counter = 0;
Chris@0 854 int updateAt = (end / windowIncrement) / 20;
Chris@0 855 if (updateAt < 100) updateAt = 100;
Chris@0 856
Chris@0 857 bool doVisibleFirst = (visibleStart != start && visibleEnd != end);
Chris@0 858
Chris@0 859 if (doVisibleFirst) {
Chris@0 860
Chris@0 861 m_layer.m_mutex.lock();
Chris@0 862
Chris@0 863 for (size_t f = visibleStart; f < visibleEnd; f += windowIncrement) {
Chris@0 864
Chris@0 865 m_layer.fillCacheColumn(int((f - start) / windowIncrement),
Chris@0 866 input, output, plan, windower, false);
Chris@0 867
Chris@0 868 m_layer.m_mutex.unlock();
Chris@0 869 m_layer.m_mutex.lock();
Chris@0 870
Chris@0 871 if (m_layer.m_cacheInvalid || m_layer.m_exiting) {
Chris@0 872 interrupted = true;
Chris@0 873 m_fillExtent = 0;
Chris@0 874 break;
Chris@0 875 }
Chris@0 876
Chris@0 877 if (++counter == updateAt) {
Chris@0 878 if (f < end) m_fillExtent = f;
Chris@0 879 m_fillCompletion = size_t(100 * fabsf(float(f - visibleStart) /
Chris@0 880 float(end - start)));
Chris@0 881 counter = 0;
Chris@0 882 }
Chris@0 883 }
Chris@0 884
Chris@0 885 m_layer.m_mutex.unlock();
Chris@0 886 }
Chris@0 887
Chris@0 888 m_layer.m_cachedInitialVisibleArea = true;
Chris@0 889
Chris@0 890 if (!interrupted && doVisibleFirst) {
Chris@0 891
Chris@0 892 for (size_t f = visibleEnd; f < end; f += windowIncrement) {
Chris@0 893
Chris@0 894 if (!m_layer.fillCacheColumn(int((f - start) / windowIncrement),
Chris@0 895 input, output, plan, windower, true)) {
Chris@0 896 interrupted = true;
Chris@0 897 m_fillExtent = 0;
Chris@0 898 break;
Chris@0 899 }
Chris@0 900
Chris@0 901
Chris@0 902 if (++counter == updateAt) {
Chris@0 903 if (f < end) m_fillExtent = f;
Chris@0 904 m_fillCompletion = size_t(100 * fabsf(float(f - visibleStart) /
Chris@0 905 float(end - start)));
Chris@0 906 counter = 0;
Chris@0 907 }
Chris@0 908 }
Chris@0 909 }
Chris@0 910
Chris@0 911 if (!interrupted) {
Chris@0 912
Chris@0 913 size_t remainingEnd = end;
Chris@0 914 if (doVisibleFirst) {
Chris@0 915 remainingEnd = visibleStart;
Chris@0 916 if (remainingEnd > start) --remainingEnd;
Chris@0 917 else remainingEnd = start;
Chris@0 918 }
Chris@0 919 size_t baseCompletion = m_fillCompletion;
Chris@0 920
Chris@0 921 for (size_t f = start; f < remainingEnd; f += windowIncrement) {
Chris@0 922
Chris@0 923 if (!m_layer.fillCacheColumn(int((f - start) / windowIncrement),
Chris@0 924 input, output, plan, windower, true)) {
Chris@0 925 interrupted = true;
Chris@0 926 m_fillExtent = 0;
Chris@0 927 break;
Chris@0 928 }
Chris@0 929
Chris@0 930 if (++counter == updateAt) {
Chris@0 931 m_fillExtent = f;
Chris@0 932 m_fillCompletion = baseCompletion +
Chris@0 933 size_t(100 * fabsf(float(f - start) /
Chris@0 934 float(end - start)));
Chris@0 935 counter = 0;
Chris@0 936 }
Chris@0 937 }
Chris@0 938 }
Chris@0 939
Chris@0 940 fftw_destroy_plan(plan);
Chris@0 941 fftw_free(output);
Chris@0 942 fftw_free(input);
Chris@0 943
Chris@0 944 if (!interrupted) {
Chris@0 945 m_fillExtent = end;
Chris@0 946 m_fillCompletion = 100;
Chris@0 947 }
Chris@0 948
Chris@0 949 m_layer.m_mutex.lock();
Chris@0 950 }
Chris@0 951
Chris@0 952 if (!interrupted) m_layer.m_condition.wait(&m_layer.m_mutex, 2000);
Chris@0 953 }
Chris@0 954 }
Chris@0 955
Chris@0 956 bool
Chris@0 957 SpectrogramLayer::getYBinRange(int y, float &q0, float &q1) const
Chris@0 958 {
Chris@0 959 int h = m_view->height();
Chris@0 960 if (y < 0 || y >= h) return false;
Chris@0 961
Chris@0 962 // Each pixel in a column is drawn from a possibly non-
Chris@0 963 // integral set of frequency bins.
Chris@0 964
Chris@0 965 if (m_frequencyScale == LinearFrequencyScale) {
Chris@0 966
Chris@0 967 size_t bins = m_windowSize / 2;
Chris@0 968
Chris@0 969 if (m_maxFrequency > 0) {
Chris@0 970 int sr = m_model->getSampleRate();
Chris@0 971 bins = int((double(m_maxFrequency) * m_windowSize) / sr + 0.1);
Chris@0 972 if (bins > m_windowSize / 2) bins = m_windowSize / 2;
Chris@0 973 }
Chris@0 974
Chris@0 975 q0 = float(h - y - 1) * bins / h;
Chris@0 976 q1 = float(h - y) * bins / h;
Chris@0 977
Chris@0 978 } else {
Chris@0 979
Chris@0 980 // This is all most ad-hoc. I'm not at my brightest.
Chris@0 981
Chris@0 982 int sr = m_model->getSampleRate();
Chris@0 983
Chris@0 984 float maxf = m_maxFrequency;
Chris@0 985 if (maxf == 0.0) maxf = float(sr) / 2;
Chris@0 986
Chris@0 987 float minf = float(sr) / m_windowSize;
Chris@0 988
Chris@0 989 float maxlogf = log10f(maxf);
Chris@0 990 float minlogf = log10f(minf);
Chris@0 991
Chris@0 992 float logf0 = minlogf + ((maxlogf - minlogf) * (h - y - 1)) / h;
Chris@0 993 float logf1 = minlogf + ((maxlogf - minlogf) * (h - y)) / h;
Chris@0 994
Chris@0 995 float f0 = pow(10.f, logf0);
Chris@0 996 float f1 = pow(10.f, logf1);
Chris@0 997
Chris@0 998 q0 = ((f0 * m_windowSize) / sr) - 1;
Chris@0 999 q1 = ((f1 * m_windowSize) / sr) - 1;
Chris@0 1000
Chris@0 1001 // std::cout << "y=" << y << " h=" << h << " maxf=" << maxf << " maxlogf="
Chris@0 1002 // << maxlogf << " logf0=" << logf0 << " f0=" << f0 << " q0="
Chris@0 1003 // << q0 << std::endl;
Chris@0 1004 }
Chris@0 1005
Chris@0 1006 return true;
Chris@0 1007 }
Chris@0 1008
Chris@0 1009 bool
Chris@0 1010 SpectrogramLayer::getXBinRange(int x, float &s0, float &s1, LayerRange *range) const
Chris@0 1011 {
Chris@0 1012 long startFrame;
Chris@0 1013 int zoomLevel;
Chris@0 1014 size_t modelStart;
Chris@0 1015 size_t modelEnd;
Chris@0 1016
Chris@0 1017 if (range) {
Chris@0 1018 startFrame = range->startFrame;
Chris@0 1019 zoomLevel = range->zoomLevel;
Chris@0 1020 modelStart = range->modelStart;
Chris@0 1021 modelEnd = range->modelEnd;
Chris@0 1022 } else {
Chris@0 1023 startFrame = m_view->getStartFrame();
Chris@0 1024 zoomLevel = m_view->getZoomLevel();
Chris@0 1025 modelStart = m_model->getStartFrame();
Chris@0 1026 modelEnd = m_model->getEndFrame();
Chris@0 1027 }
Chris@0 1028
Chris@0 1029 // Each pixel column covers an exact range of sample frames:
Chris@0 1030 int f0 = startFrame + x * zoomLevel - modelStart;
Chris@0 1031 int f1 = f0 + zoomLevel - 1;
Chris@0 1032
Chris@0 1033 if (f1 < int(modelStart) || f0 > int(modelEnd)) return false;
Chris@0 1034
Chris@0 1035 // And that range may be drawn from a possibly non-integral
Chris@0 1036 // range of spectrogram windows:
Chris@0 1037
Chris@0 1038 size_t windowIncrement = getWindowIncrement();
Chris@0 1039
Chris@0 1040 s0 = float(f0) / windowIncrement;
Chris@0 1041 s1 = float(f1) / windowIncrement;
Chris@0 1042
Chris@0 1043 return true;
Chris@0 1044 }
Chris@0 1045
Chris@0 1046 bool
Chris@0 1047 SpectrogramLayer::getXBinSourceRange(int x, RealTime &min, RealTime &max) const
Chris@0 1048 {
Chris@0 1049 float s0 = 0, s1 = 0;
Chris@0 1050 if (!getXBinRange(x, s0, s1)) return false;
Chris@0 1051
Chris@0 1052 int s0i = int(s0 + 0.001);
Chris@0 1053 int s1i = int(s1);
Chris@0 1054
Chris@0 1055 int windowIncrement = getWindowIncrement();
Chris@0 1056 int w0 = s0i * windowIncrement - (m_windowSize - windowIncrement)/2;
Chris@0 1057 int w1 = s1i * windowIncrement + windowIncrement +
Chris@0 1058 (m_windowSize - windowIncrement)/2 - 1;
Chris@0 1059
Chris@0 1060 min = RealTime::frame2RealTime(w0, m_model->getSampleRate());
Chris@0 1061 max = RealTime::frame2RealTime(w1, m_model->getSampleRate());
Chris@0 1062 return true;
Chris@0 1063 }
Chris@0 1064
Chris@0 1065 bool
Chris@0 1066 SpectrogramLayer::getYBinSourceRange(int y, float &freqMin, float &freqMax)
Chris@0 1067 const
Chris@0 1068 {
Chris@0 1069 float q0 = 0, q1 = 0;
Chris@0 1070 if (!getYBinRange(y, q0, q1)) return false;
Chris@0 1071
Chris@0 1072 int q0i = int(q0 + 0.001);
Chris@0 1073 int q1i = int(q1);
Chris@0 1074
Chris@0 1075 int sr = m_model->getSampleRate();
Chris@0 1076
Chris@0 1077 for (int q = q0i; q <= q1i; ++q) {
Chris@0 1078 int binfreq = (sr * (q + 1)) / m_windowSize;
Chris@0 1079 if (q == q0i) freqMin = binfreq;
Chris@0 1080 if (q == q1i) freqMax = binfreq;
Chris@0 1081 }
Chris@0 1082 return true;
Chris@0 1083 }
Chris@0 1084
Chris@0 1085 bool
Chris@0 1086 SpectrogramLayer::getXYBinSourceRange(int x, int y, float &dbMin, float &dbMax) const
Chris@0 1087 {
Chris@0 1088 float q0 = 0, q1 = 0;
Chris@0 1089 if (!getYBinRange(y, q0, q1)) return false;
Chris@0 1090
Chris@0 1091 float s0 = 0, s1 = 0;
Chris@0 1092 if (!getXBinRange(x, s0, s1)) return false;
Chris@0 1093
Chris@0 1094 int q0i = int(q0 + 0.001);
Chris@0 1095 int q1i = int(q1);
Chris@0 1096
Chris@0 1097 int s0i = int(s0 + 0.001);
Chris@0 1098 int s1i = int(s1);
Chris@0 1099
Chris@0 1100 if (m_mutex.tryLock()) {
Chris@0 1101 if (m_cache && !m_cacheInvalid) {
Chris@0 1102
Chris@0 1103 int cw = m_cache->width();
Chris@0 1104 int ch = m_cache->height();
Chris@0 1105
Chris@0 1106 int min = -1, max = -1;
Chris@0 1107
Chris@0 1108 for (int q = q0i; q <= q1i; ++q) {
Chris@0 1109 for (int s = s0i; s <= s1i; ++s) {
Chris@0 1110 if (s >= 0 && q >= 0 && s < cw && q < ch) {
Chris@0 1111 int value = m_cache->scanLine(q)[s];
Chris@0 1112 if (min == -1 || value < min) min = value;
Chris@0 1113 if (max == -1 || value > max) max = value;
Chris@0 1114 }
Chris@0 1115 }
Chris@0 1116 }
Chris@0 1117
Chris@0 1118 if (min < 0) return false;
Chris@0 1119
Chris@0 1120 dbMin = (float(min) / 256.0) * 80.0 - 80.0;
Chris@0 1121 dbMax = (float(max + 1) / 256.0) * 80.0 - 80.1;
Chris@0 1122
Chris@0 1123 m_mutex.unlock();
Chris@0 1124 return true;
Chris@0 1125 }
Chris@0 1126
Chris@0 1127 m_mutex.unlock();
Chris@0 1128 }
Chris@0 1129
Chris@0 1130 return false;
Chris@0 1131 }
Chris@0 1132
Chris@0 1133 void
Chris@0 1134 SpectrogramLayer::paint(QPainter &paint, QRect rect) const
Chris@0 1135 {
Chris@0 1136 // Profiler profiler("SpectrogramLayer::paint", true);
Chris@0 1137 #ifdef DEBUG_SPECTROGRAM_REPAINT
Chris@0 1138 std::cerr << "SpectrogramLayer::paint(): m_model is " << m_model << ", zoom level is " << m_view->getZoomLevel() << ", m_updateTimer " << m_updateTimer << ", pixmap cache invalid " << m_pixmapCacheInvalid << std::endl;
Chris@0 1139 #endif
Chris@0 1140
Chris@0 1141 if (!m_model || !m_model->isOK() || !m_model->isReady()) {
Chris@0 1142 return;
Chris@0 1143 }
Chris@0 1144
Chris@0 1145 #ifdef DEBUG_SPECTROGRAM_REPAINT
Chris@0 1146 std::cerr << "SpectrogramLayer::paint(): About to lock" << std::endl;
Chris@0 1147 #endif
Chris@0 1148
Chris@0 1149 /*
Chris@0 1150 if (m_cachedInitialVisibleArea) {
Chris@0 1151 if (!m_mutex.tryLock()) {
Chris@0 1152 m_view->update();
Chris@0 1153 return;
Chris@0 1154 }
Chris@0 1155 } else {
Chris@0 1156 */
Chris@0 1157 m_mutex.lock();
Chris@0 1158 // }
Chris@0 1159
Chris@0 1160 #ifdef DEBUG_SPECTROGRAM_REPAINT
Chris@0 1161 std::cerr << "SpectrogramLayer::paint(): locked" << std::endl;
Chris@0 1162 #endif
Chris@0 1163
Chris@0 1164 if (m_cacheInvalid) { // lock the mutex before checking this
Chris@0 1165 m_mutex.unlock();
Chris@0 1166 #ifdef DEBUG_SPECTROGRAM_REPAINT
Chris@0 1167 std::cerr << "SpectrogramLayer::paint(): Cache invalid, returning" << std::endl;
Chris@0 1168 #endif
Chris@0 1169 return;
Chris@0 1170 }
Chris@0 1171
Chris@0 1172 bool stillCacheing = (m_updateTimer != 0);
Chris@0 1173
Chris@0 1174 #ifdef DEBUG_SPECTROGRAM_REPAINT
Chris@0 1175 std::cerr << "SpectrogramLayer::paint(): Still cacheing = " << stillCacheing << std::endl;
Chris@0 1176 #endif
Chris@0 1177
Chris@0 1178 long startFrame = m_view->getStartFrame();
Chris@0 1179 int zoomLevel = m_view->getZoomLevel();
Chris@0 1180
Chris@0 1181 int x0 = 0;
Chris@0 1182 int x1 = m_view->width();
Chris@0 1183 int y0 = 0;
Chris@0 1184 int y1 = m_view->height();
Chris@0 1185
Chris@0 1186 bool recreateWholePixmapCache = true;
Chris@0 1187
Chris@0 1188 if (!m_pixmapCacheInvalid) {
Chris@0 1189
Chris@0 1190 //!!! This cache may have been obsoleted entirely by the
Chris@0 1191 //scrolling cache in View. Perhaps experiment with
Chris@0 1192 //removing it and see if it makes things even quicker (or else
Chris@0 1193 //make it optional)
Chris@0 1194
Chris@0 1195 if (int(m_pixmapCacheZoomLevel) == zoomLevel &&
Chris@0 1196 m_pixmapCache->width() == m_view->width() &&
Chris@0 1197 m_pixmapCache->height() == m_view->height()) {
Chris@0 1198
Chris@0 1199 if (m_pixmapCacheStartFrame / zoomLevel ==
Chris@0 1200 startFrame / zoomLevel) {
Chris@0 1201
Chris@0 1202 #ifdef DEBUG_SPECTROGRAM_REPAINT
Chris@0 1203 std::cerr << "SpectrogramLayer: pixmap cache good" << std::endl;
Chris@0 1204 #endif
Chris@0 1205
Chris@0 1206 m_mutex.unlock();
Chris@0 1207 paint.drawPixmap(rect, *m_pixmapCache, rect);
Chris@0 1208 return;
Chris@0 1209
Chris@0 1210 } else {
Chris@0 1211
Chris@0 1212 #ifdef DEBUG_SPECTROGRAM_REPAINT
Chris@0 1213 std::cerr << "SpectrogramLayer: pixmap cache partially OK" << std::endl;
Chris@0 1214 #endif
Chris@0 1215
Chris@0 1216 recreateWholePixmapCache = false;
Chris@0 1217
Chris@0 1218 int dx = (m_pixmapCacheStartFrame - startFrame) / zoomLevel;
Chris@0 1219
Chris@0 1220 #ifdef DEBUG_SPECTROGRAM_REPAINT
Chris@0 1221 std::cerr << "SpectrogramLayer: dx = " << dx << " (pixmap cache " << m_pixmapCache->width() << "x" << m_pixmapCache->height() << ")" << std::endl;
Chris@0 1222 #endif
Chris@0 1223
Chris@0 1224 if (dx > -m_pixmapCache->width() && dx < m_pixmapCache->width()) {
Chris@0 1225
Chris@0 1226 #if defined(Q_WS_WIN32) || defined(Q_WS_MAC)
Chris@0 1227 // Copying a pixmap to itself doesn't work
Chris@0 1228 // properly on Windows or Mac (it only works when
Chris@0 1229 // moving in one direction).
Chris@0 1230
Chris@0 1231 //!!! Need a utility function for this
Chris@0 1232
Chris@0 1233 static QPixmap *tmpPixmap = 0;
Chris@0 1234 if (!tmpPixmap ||
Chris@0 1235 tmpPixmap->width() != m_pixmapCache->width() ||
Chris@0 1236 tmpPixmap->height() != m_pixmapCache->height()) {
Chris@0 1237 delete tmpPixmap;
Chris@0 1238 tmpPixmap = new QPixmap(m_pixmapCache->width(),
Chris@0 1239 m_pixmapCache->height());
Chris@0 1240 }
Chris@0 1241 QPainter cachePainter;
Chris@0 1242 cachePainter.begin(tmpPixmap);
Chris@0 1243 cachePainter.drawPixmap(0, 0, *m_pixmapCache);
Chris@0 1244 cachePainter.end();
Chris@0 1245 cachePainter.begin(m_pixmapCache);
Chris@0 1246 cachePainter.drawPixmap(dx, 0, *tmpPixmap);
Chris@0 1247 cachePainter.end();
Chris@0 1248 #else
Chris@0 1249 QPainter cachePainter(m_pixmapCache);
Chris@0 1250 cachePainter.drawPixmap(dx, 0, *m_pixmapCache);
Chris@0 1251 cachePainter.end();
Chris@0 1252 #endif
Chris@0 1253
Chris@0 1254 paint.drawPixmap(rect, *m_pixmapCache, rect);
Chris@0 1255
Chris@0 1256 if (dx < 0) {
Chris@0 1257 x0 = m_pixmapCache->width() + dx;
Chris@0 1258 x1 = m_pixmapCache->width();
Chris@0 1259 } else {
Chris@0 1260 x0 = 0;
Chris@0 1261 x1 = dx;
Chris@0 1262 }
Chris@0 1263 }
Chris@0 1264 }
Chris@0 1265 } else {
Chris@0 1266 #ifdef DEBUG_SPECTROGRAM_REPAINT
Chris@0 1267 std::cerr << "SpectrogramLayer: pixmap cache useless" << std::endl;
Chris@0 1268 #endif
Chris@0 1269 }
Chris@0 1270 }
Chris@0 1271
Chris@0 1272 if (stillCacheing) {
Chris@0 1273 x0 = rect.left();
Chris@0 1274 x1 = rect.right() + 1;
Chris@0 1275 y0 = rect.top();
Chris@0 1276 y1 = rect.bottom() + 1;
Chris@0 1277 }
Chris@0 1278
Chris@0 1279 int w = x1 - x0;
Chris@0 1280 int h = y1 - y0;
Chris@0 1281
Chris@0 1282 // std::cerr << "x0 " << x0 << ", x1 " << x1 << ", w " << w << ", h " << h << std::endl;
Chris@0 1283
Chris@0 1284 QImage scaled(w, h, QImage::Format_RGB32);
Chris@0 1285
Chris@0 1286 LayerRange range = { m_view->getStartFrame(), m_view->getZoomLevel(),
Chris@0 1287 m_model->getStartFrame(), m_model->getEndFrame() };
Chris@0 1288
Chris@0 1289 m_mutex.unlock();
Chris@0 1290
Chris@0 1291 for (int y = 0; y < h; ++y) {
Chris@0 1292
Chris@0 1293 m_mutex.lock();
Chris@0 1294 if (m_cacheInvalid) {
Chris@0 1295 m_mutex.unlock();
Chris@0 1296 break;
Chris@0 1297 }
Chris@0 1298
Chris@0 1299 int cw = m_cache->width();
Chris@0 1300 int ch = m_cache->height();
Chris@0 1301
Chris@0 1302 float q0 = 0, q1 = 0;
Chris@0 1303
Chris@0 1304 if (!getYBinRange(y0 + y, q0, q1)) {
Chris@0 1305 for (int x = 0; x < w; ++x) {
Chris@0 1306 assert(x <= scaled.width());
Chris@0 1307 scaled.setPixel(x, y, qRgb(0, 0, 0));
Chris@0 1308 }
Chris@0 1309 m_mutex.unlock();
Chris@0 1310 continue;
Chris@0 1311 }
Chris@0 1312
Chris@0 1313 int q0i = int(q0 + 0.001);
Chris@0 1314 int q1i = int(q1);
Chris@0 1315
Chris@0 1316 for (int x = 0; x < w; ++x) {
Chris@0 1317
Chris@0 1318 float s0 = 0, s1 = 0;
Chris@0 1319
Chris@0 1320 if (!getXBinRange(x0 + x, s0, s1, &range)) {
Chris@0 1321 assert(x <= scaled.width());
Chris@0 1322 scaled.setPixel(x, y, qRgb(0, 0, 0));
Chris@0 1323 continue;
Chris@0 1324 }
Chris@0 1325
Chris@0 1326 int s0i = int(s0 + 0.001);
Chris@0 1327 int s1i = int(s1);
Chris@0 1328
Chris@0 1329 float total = 0, divisor = 0;
Chris@0 1330
Chris@0 1331 for (int s = s0i; s <= s1i; ++s) {
Chris@0 1332
Chris@0 1333 float sprop = 1.0;
Chris@0 1334 if (s == s0i) sprop *= (s + 1) - s0;
Chris@0 1335 if (s == s1i) sprop *= s1 - s;
Chris@0 1336
Chris@0 1337 for (int q = q0i; q <= q1i; ++q) {
Chris@0 1338
Chris@0 1339 float qprop = sprop;
Chris@0 1340 if (q == q0i) qprop *= (q + 1) - q0;
Chris@0 1341 if (q == q1i) qprop *= q1 - q;
Chris@0 1342
Chris@0 1343 if (s >= 0 && q >= 0 && s < cw && q < ch) {
Chris@0 1344 total += qprop * m_cache->scanLine(q)[s];
Chris@0 1345 divisor += qprop;
Chris@0 1346 }
Chris@0 1347 }
Chris@0 1348 }
Chris@0 1349
Chris@0 1350 if (divisor > 0.0) {
Chris@0 1351 int pixel = int(total / divisor);
Chris@0 1352 if (pixel > 255) pixel = 255;
Chris@0 1353 if (pixel < 1) pixel = 1;
Chris@0 1354 assert(x <= scaled.width());
Chris@0 1355 scaled.setPixel(x, y, m_cache->color(pixel));
Chris@0 1356 } else {
Chris@0 1357 assert(x <= scaled.width());
Chris@0 1358 scaled.setPixel(x, y, qRgb(0, 0, 0));
Chris@0 1359 }
Chris@0 1360 }
Chris@0 1361
Chris@0 1362 m_mutex.unlock();
Chris@0 1363 }
Chris@0 1364
Chris@0 1365 paint.drawImage(x0, y0, scaled);
Chris@0 1366
Chris@0 1367 if (recreateWholePixmapCache) {
Chris@0 1368 delete m_pixmapCache;
Chris@0 1369 m_pixmapCache = new QPixmap(w, h);
Chris@0 1370 }
Chris@0 1371
Chris@0 1372 QPainter cachePainter(m_pixmapCache);
Chris@0 1373 cachePainter.drawImage(x0, y0, scaled);
Chris@0 1374 cachePainter.end();
Chris@0 1375
Chris@0 1376 m_pixmapCacheInvalid = false;
Chris@0 1377 m_pixmapCacheStartFrame = startFrame;
Chris@0 1378 m_pixmapCacheZoomLevel = zoomLevel;
Chris@0 1379
Chris@0 1380 #ifdef DEBUG_SPECTROGRAM_REPAINT
Chris@0 1381 std::cerr << "SpectrogramLayer::paint() returning" << std::endl;
Chris@0 1382 #endif
Chris@0 1383
Chris@0 1384 //!!! drawLocalFeatureDescription(paint);
Chris@0 1385 }
Chris@0 1386
Chris@0 1387 int
Chris@0 1388 SpectrogramLayer::getCompletion() const
Chris@0 1389 {
Chris@0 1390 if (m_updateTimer == 0) return 100;
Chris@0 1391 size_t completion = m_fillThread->getFillCompletion();
Chris@0 1392 // std::cerr << "SpectrogramLayer::getCompletion: completion = " << completion << std::endl;
Chris@0 1393 return completion;
Chris@0 1394 }
Chris@0 1395
Chris@0 1396 QRect
Chris@0 1397 SpectrogramLayer::getFeatureDescriptionRect(QPainter &paint, QPoint pos) const
Chris@0 1398 {
Chris@0 1399 if (!m_model || !m_model->isOK()) return QRect();
Chris@0 1400
Chris@0 1401 QString timeLabel = tr("Time: ");
Chris@0 1402 QString freqLabel = tr("Hz: ");
Chris@0 1403 QString dBLabel = tr("dB: ");
Chris@0 1404
Chris@0 1405 // assume time is widest
Chris@0 1406 RealTime rtMin, rtMax;
Chris@0 1407 if (!getXBinSourceRange(pos.x(), rtMin, rtMax)) return QRect();
Chris@0 1408 QString timeMinText = QString("%1").arg(rtMin.toText(true).c_str());
Chris@0 1409 QString timeMaxText = QString(" - %1").arg(rtMax.toText(true).c_str());
Chris@0 1410
Chris@0 1411 QFontMetrics metrics = paint.fontMetrics();
Chris@0 1412
Chris@0 1413 int labelwidth =
Chris@0 1414 std::max(std::max(metrics.width(timeLabel),
Chris@0 1415 metrics.width(freqLabel)),
Chris@0 1416 metrics.width(dBLabel));
Chris@0 1417
Chris@0 1418 int boxwidth = labelwidth +
Chris@0 1419 metrics.width(timeMinText) + metrics.width(timeMaxText);
Chris@0 1420
Chris@0 1421 int fontHeight = metrics.height();
Chris@0 1422 int boxheight = fontHeight * 3 + 4;
Chris@0 1423
Chris@0 1424 return QRect(0, 0, boxwidth + 20, boxheight + 15);
Chris@0 1425 }
Chris@0 1426
Chris@0 1427 void
Chris@0 1428 SpectrogramLayer::paintLocalFeatureDescription(QPainter &paint,
Chris@0 1429 QRect rect, QPoint pos) const
Chris@0 1430 {
Chris@0 1431 int x = pos.x();
Chris@0 1432 int y = pos.y();
Chris@0 1433
Chris@0 1434 if (!m_model || !m_model->isOK()) return;
Chris@0 1435
Chris@0 1436 float dbMin = 0, dbMax = 0;
Chris@0 1437 float freqMin = 0, freqMax = 0;
Chris@0 1438 RealTime rtMin, rtMax;
Chris@0 1439
Chris@0 1440 bool haveDb = false;
Chris@0 1441
Chris@0 1442 if (!getXBinSourceRange(x, rtMin, rtMax)) return;
Chris@0 1443 if (!getYBinSourceRange(y, freqMin, freqMax)) return;
Chris@0 1444 if (getXYBinSourceRange(x, y, dbMin, dbMax)) haveDb = true;
Chris@0 1445
Chris@0 1446 QString timeLabel = tr("Time: ");
Chris@0 1447 QString freqLabel = tr("Hz: ");
Chris@0 1448 QString dBLabel = tr("dB: ");
Chris@0 1449
Chris@0 1450 QString timeMinText = QString("%1").arg(rtMin.toText(true).c_str());
Chris@0 1451 QString timeMaxText = QString(" - %1").arg(rtMax.toText(true).c_str());
Chris@0 1452
Chris@0 1453 QString freqMinText = QString("%1").arg(freqMin);
Chris@0 1454 QString freqMaxText = "";
Chris@0 1455 if (freqMax != freqMin) {
Chris@0 1456 freqMaxText = QString(" - %1").arg(freqMax);
Chris@0 1457 }
Chris@0 1458
Chris@0 1459 QString dBMinText = "";
Chris@0 1460 QString dBMaxText = "";
Chris@0 1461
Chris@0 1462 if (haveDb) {
Chris@0 1463 int dbmxi = int(dbMax - 0.001);
Chris@0 1464 int dbmni = int(dbMin - 0.001);
Chris@0 1465 dBMinText = QString("%1").arg(dbmni);
Chris@0 1466 if (dbmxi != dbmni) dBMaxText = QString(" - %1").arg(dbmxi);
Chris@0 1467 }
Chris@0 1468
Chris@0 1469 QFontMetrics metrics = paint.fontMetrics();
Chris@0 1470
Chris@0 1471 int labelwidth =
Chris@0 1472 std::max(std::max(metrics.width(timeLabel),
Chris@0 1473 metrics.width(freqLabel)),
Chris@0 1474 metrics.width(dBLabel));
Chris@0 1475
Chris@0 1476 int minwidth =
Chris@0 1477 std::max(std::max(metrics.width(timeMinText),
Chris@0 1478 metrics.width(freqMinText)),
Chris@0 1479 metrics.width(dBMinText));
Chris@0 1480
Chris@0 1481 int maxwidth =
Chris@0 1482 std::max(std::max(metrics.width(timeMaxText),
Chris@0 1483 metrics.width(freqMaxText)),
Chris@0 1484 metrics.width(dBMaxText));
Chris@0 1485
Chris@0 1486 int boxwidth = labelwidth + minwidth + maxwidth;
Chris@0 1487
Chris@0 1488 int fontAscent = metrics.ascent();
Chris@0 1489 int fontHeight = metrics.height();
Chris@0 1490
Chris@0 1491 int boxheight = fontHeight * 3 + 4;
Chris@0 1492
Chris@0 1493 // paint.setPen(Qt::white);
Chris@0 1494 // paint.setBrush(Qt::NoBrush);
Chris@0 1495
Chris@0 1496 //!!! int xbase = m_view->width() - boxwidth - 20;
Chris@0 1497 int xbase = rect.x() + 5;
Chris@0 1498 int ybase = rect.y() + 5;
Chris@0 1499
Chris@0 1500 paint.drawRect(xbase, ybase, boxwidth + 10,
Chris@0 1501 boxheight + 10 - metrics.descent() + 1);
Chris@0 1502
Chris@0 1503 paint.drawText(xbase + 5 + labelwidth - metrics.width(timeLabel),
Chris@0 1504 ybase + 5 + fontAscent, timeLabel);
Chris@0 1505
Chris@0 1506 paint.drawText(xbase + 5 + labelwidth - metrics.width(freqLabel),
Chris@0 1507 ybase + 7 + fontAscent + fontHeight, freqLabel);
Chris@0 1508
Chris@0 1509 paint.drawText(xbase + 5 + labelwidth - metrics.width(dBLabel),
Chris@0 1510 ybase + 9 + fontAscent + fontHeight * 2, dBLabel);
Chris@0 1511
Chris@0 1512 paint.drawText(xbase + 5 + labelwidth + minwidth - metrics.width(timeMinText),
Chris@0 1513 ybase + 5 + fontAscent, timeMinText);
Chris@0 1514
Chris@0 1515 paint.drawText(xbase + 5 + labelwidth + minwidth - metrics.width(freqMinText),
Chris@0 1516 ybase + 7 + fontAscent + fontHeight, freqMinText);
Chris@0 1517
Chris@0 1518 paint.drawText(xbase + 5 + labelwidth + minwidth - metrics.width(dBMinText),
Chris@0 1519 ybase + 9 + fontAscent + fontHeight * 2, dBMinText);
Chris@0 1520
Chris@0 1521 paint.drawText(xbase + 5 + labelwidth + minwidth,
Chris@0 1522 ybase + 5 + fontAscent, timeMaxText);
Chris@0 1523
Chris@0 1524 paint.drawText(xbase + 5 + labelwidth + minwidth,
Chris@0 1525 ybase + 7 + fontAscent + fontHeight, freqMaxText);
Chris@0 1526
Chris@0 1527 paint.drawText(xbase + 5 + labelwidth + minwidth,
Chris@0 1528 ybase + 9 + fontAscent + fontHeight * 2, dBMaxText);
Chris@0 1529 }
Chris@0 1530
Chris@0 1531 /*!!!
Chris@0 1532
Chris@0 1533 bool
Chris@0 1534 SpectrogramLayer::identifyLocalFeatures(bool on, int x, int y)
Chris@0 1535 {
Chris@0 1536 return true; //!!!
Chris@0 1537
Chris@0 1538 m_identify = on;
Chris@0 1539 m_identifyX = x;
Chris@0 1540 m_identifyY = y;
Chris@0 1541
Chris@0 1542 m_view->update();
Chris@0 1543 */
Chris@0 1544 /*
Chris@0 1545 if (!m_model || !m_model->isOK()) return false;
Chris@0 1546
Chris@0 1547 std::cerr << "SpectrogramLayer::identifyLocalFeatures(" << on << "," << x << "," << y << ")" << std::endl;
Chris@0 1548
Chris@0 1549 float dbMin = 0, dbMax = 0;
Chris@0 1550 float freqMin = 0, freqMax = 0;
Chris@0 1551 RealTime rtMin, rtMax;
Chris@0 1552
Chris@0 1553 if (getXBinSourceRange(x, rtMin, rtMax)) {
Chris@0 1554 std::cerr << "Times: " << rtMin << " -> " << rtMax << std::endl;
Chris@0 1555 } else return false;
Chris@0 1556
Chris@0 1557 if (getYBinSourceRange(y, freqMin, freqMax)) {
Chris@0 1558 std::cerr << "Frequencies: " << freqMin << " -> " << freqMax << std::endl;
Chris@0 1559 } else return false;
Chris@0 1560
Chris@0 1561 if (getXYBinSourceRange(x, y, dbMin, dbMax)) {
Chris@0 1562 std::cerr << "dB: " << dbMin << " -> " << dbMax << std::endl;
Chris@0 1563 }
Chris@0 1564
Chris@0 1565 m_identifyX = x;
Chris@0 1566 m_identifyY = y;
Chris@0 1567 m_identify = true;
Chris@0 1568 */
Chris@0 1569 /*!!!
Chris@0 1570 return true;
Chris@0 1571 }
Chris@0 1572 */
Chris@0 1573 int
Chris@0 1574 SpectrogramLayer::getVerticalScaleWidth(QPainter &paint) const
Chris@0 1575 {
Chris@0 1576 if (!m_model || !m_model->isOK()) return 0;
Chris@0 1577
Chris@0 1578 int tw = paint.fontMetrics().width(QString("%1")
Chris@0 1579 .arg(m_maxFrequency > 0 ?
Chris@0 1580 m_maxFrequency - 1 :
Chris@0 1581 m_model->getSampleRate() / 2));
Chris@0 1582
Chris@0 1583 int fw = paint.fontMetrics().width(QString("43Hz"));
Chris@0 1584 if (tw < fw) tw = fw;
Chris@0 1585
Chris@0 1586 return tw + 13;
Chris@0 1587 }
Chris@0 1588
Chris@0 1589 void
Chris@0 1590 SpectrogramLayer::paintVerticalScale(QPainter &paint, QRect rect) const
Chris@0 1591 {
Chris@0 1592 if (!m_model || !m_model->isOK()) {
Chris@0 1593 return;
Chris@0 1594 }
Chris@0 1595
Chris@0 1596 int h = rect.height(), w = rect.width();
Chris@0 1597
Chris@0 1598 size_t bins = m_windowSize / 2;
Chris@0 1599 int sr = m_model->getSampleRate();
Chris@0 1600
Chris@0 1601 if (m_maxFrequency > 0) {
Chris@0 1602 bins = int((double(m_maxFrequency) * m_windowSize) / sr + 0.1);
Chris@0 1603 if (bins > m_windowSize / 2) bins = m_windowSize / 2;
Chris@0 1604 }
Chris@0 1605
Chris@0 1606 int py = -1;
Chris@0 1607 int textHeight = paint.fontMetrics().height();
Chris@0 1608 int toff = -textHeight + paint.fontMetrics().ascent() + 2;
Chris@0 1609
Chris@0 1610 int bin = -1;
Chris@0 1611
Chris@0 1612 for (int y = 0; y < m_view->height(); ++y) {
Chris@0 1613
Chris@0 1614 float q0, q1;
Chris@0 1615 if (!getYBinRange(m_view->height() - y, q0, q1)) continue;
Chris@0 1616
Chris@0 1617 int vy;
Chris@0 1618
Chris@0 1619 if (int(q0) > bin) {
Chris@0 1620 vy = y;
Chris@0 1621 bin = int(q0);
Chris@0 1622 } else {
Chris@0 1623 continue;
Chris@0 1624 }
Chris@0 1625
Chris@0 1626 int freq = (sr * (bin + 1)) / m_windowSize;
Chris@0 1627
Chris@0 1628 if (py >= 0 && (vy - py) < textHeight - 1) {
Chris@0 1629 paint.drawLine(w - 4, h - vy, w, h - vy);
Chris@0 1630 continue;
Chris@0 1631 }
Chris@0 1632
Chris@0 1633 QString text = QString("%1").arg(freq);
Chris@0 1634 if (bin == 0) text = QString("%1Hz").arg(freq);
Chris@0 1635 paint.drawLine(0, h - vy, w, h - vy);
Chris@0 1636
Chris@0 1637 if (h - vy - textHeight >= -2) {
Chris@0 1638 int tx = w - 10 - paint.fontMetrics().width(text);
Chris@0 1639 paint.drawText(tx, h - vy + toff, text);
Chris@0 1640 }
Chris@0 1641
Chris@0 1642 py = vy;
Chris@0 1643 }
Chris@0 1644 }
Chris@0 1645
Chris@0 1646 #ifdef INCLUDE_MOCFILES
Chris@0 1647 #include "SpectrogramLayer.moc.cpp"
Chris@0 1648 #endif
Chris@0 1649