annotate layer/SpectrogramLayer.cpp @ 10:8f5b812baaee

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