annotate layer/SpectrogramLayer.cpp @ 35:10ba9276a315

* Add TextModel and TextLayer types * Make View refresh work better when editing a model (previously edits might not be refreshed if their visible changed area extended beyond the strict frame range that was being modified in the model) * Add phase-adjusted instantaneous frequency display to spectrogram layer (still a work in progress) * Pull maths aliases out into a separate header in dsp/maths so MathUtilities can be included without introducing them
author Chris Cannam
date Mon, 20 Feb 2006 13:33:36 +0000
parents c43f2c4f66f2
children c28ebb4ba4de
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@24 16 #include "base/Pitch.h"
Chris@0 17
Chris@35 18 #include "dsp/maths/MathUtilities.h"
Chris@35 19
Chris@0 20 #include <QPainter>
Chris@0 21 #include <QImage>
Chris@0 22 #include <QPixmap>
Chris@0 23 #include <QRect>
Chris@0 24 #include <QTimer>
Chris@0 25
Chris@0 26 #include <iostream>
Chris@0 27
Chris@0 28 #include <cassert>
Chris@0 29 #include <cmath>
Chris@0 30
Chris@0 31 //#define DEBUG_SPECTROGRAM_REPAINT 1
Chris@0 32
Chris@0 33
Chris@0 34 SpectrogramLayer::SpectrogramLayer(View *w, Configuration config) :
Chris@0 35 Layer(w),
Chris@0 36 m_model(0),
Chris@0 37 m_channel(0),
Chris@0 38 m_windowSize(1024),
Chris@0 39 m_windowType(HanningWindow),
Chris@0 40 m_windowOverlap(50),
Chris@0 41 m_gain(1.0),
Chris@9 42 m_colourRotation(0),
Chris@0 43 m_maxFrequency(8000),
Chris@0 44 m_colourScale(dBColourScale),
Chris@0 45 m_colourScheme(DefaultColours),
Chris@0 46 m_frequencyScale(LinearFrequencyScale),
Chris@35 47 m_frequencyAdjustment(RawFrequency),
Chris@0 48 m_cache(0),
Chris@35 49 m_phaseAdjustCache(0),
Chris@0 50 m_cacheInvalid(true),
Chris@0 51 m_pixmapCache(0),
Chris@0 52 m_pixmapCacheInvalid(true),
Chris@0 53 m_fillThread(0),
Chris@0 54 m_updateTimer(0),
Chris@0 55 m_lastFillExtent(0),
Chris@0 56 m_exiting(false)
Chris@0 57 {
Chris@0 58 if (config == MelodicRange) {
Chris@0 59 setWindowSize(8192);
Chris@0 60 setWindowOverlap(90);
Chris@0 61 setWindowType(ParzenWindow);
Chris@0 62 setMaxFrequency(1000);
Chris@0 63 setColourScale(LinearColourScale);
Chris@0 64 }
Chris@0 65
Chris@0 66 if (m_view) m_view->setLightBackground(false);
Chris@0 67 m_view->addLayer(this);
Chris@0 68 }
Chris@0 69
Chris@0 70 SpectrogramLayer::~SpectrogramLayer()
Chris@0 71 {
Chris@0 72 delete m_updateTimer;
Chris@0 73 m_updateTimer = 0;
Chris@0 74
Chris@0 75 m_exiting = true;
Chris@0 76 m_condition.wakeAll();
Chris@0 77 if (m_fillThread) m_fillThread->wait();
Chris@0 78 delete m_fillThread;
Chris@0 79
Chris@0 80 delete m_cache;
Chris@35 81 delete m_phaseAdjustCache;
Chris@0 82 }
Chris@0 83
Chris@0 84 void
Chris@0 85 SpectrogramLayer::setModel(const DenseTimeValueModel *model)
Chris@0 86 {
Chris@34 87 std::cerr << "SpectrogramLayer(" << this << "): setModel(" << model << ")" << std::endl;
Chris@34 88
Chris@0 89 m_mutex.lock();
Chris@35 90 m_cacheInvalid = true;
Chris@0 91 m_model = model;
Chris@34 92 delete m_cache; //!!! hang on, this isn't safe to do here is it?
Chris@34 93 // we need some sort of guard against the fill
Chris@34 94 // thread trying to read the defunct model too.
Chris@34 95 // should we use a scavenger?
Chris@31 96 m_cache = 0;
Chris@35 97 delete m_phaseAdjustCache; //!!! likewise
Chris@35 98 m_phaseAdjustCache = 0;
Chris@0 99 m_mutex.unlock();
Chris@0 100
Chris@0 101 if (!m_model || !m_model->isOK()) return;
Chris@0 102
Chris@0 103 connect(m_model, SIGNAL(modelChanged()), this, SIGNAL(modelChanged()));
Chris@0 104 connect(m_model, SIGNAL(modelChanged(size_t, size_t)),
Chris@0 105 this, SIGNAL(modelChanged(size_t, size_t)));
Chris@0 106
Chris@0 107 connect(m_model, SIGNAL(completionChanged()),
Chris@0 108 this, SIGNAL(modelCompletionChanged()));
Chris@0 109
Chris@0 110 connect(m_model, SIGNAL(modelChanged()), this, SLOT(cacheInvalid()));
Chris@0 111 connect(m_model, SIGNAL(modelChanged(size_t, size_t)),
Chris@0 112 this, SLOT(cacheInvalid(size_t, size_t)));
Chris@0 113
Chris@0 114 emit modelReplaced();
Chris@0 115 fillCache();
Chris@0 116 }
Chris@0 117
Chris@0 118 Layer::PropertyList
Chris@0 119 SpectrogramLayer::getProperties() const
Chris@0 120 {
Chris@0 121 PropertyList list;
Chris@0 122 list.push_back(tr("Colour"));
Chris@0 123 list.push_back(tr("Colour Scale"));
Chris@0 124 list.push_back(tr("Window Type"));
Chris@0 125 list.push_back(tr("Window Size"));
Chris@0 126 list.push_back(tr("Window Overlap"));
Chris@0 127 list.push_back(tr("Gain"));
Chris@9 128 list.push_back(tr("Colour Rotation"));
Chris@0 129 list.push_back(tr("Max Frequency"));
Chris@0 130 list.push_back(tr("Frequency Scale"));
Chris@35 131 list.push_back(tr("Frequency Adjustment"));
Chris@0 132 return list;
Chris@0 133 }
Chris@0 134
Chris@0 135 Layer::PropertyType
Chris@0 136 SpectrogramLayer::getPropertyType(const PropertyName &name) const
Chris@0 137 {
Chris@0 138 if (name == tr("Gain")) return RangeProperty;
Chris@9 139 if (name == tr("Colour Rotation")) return RangeProperty;
Chris@0 140 return ValueProperty;
Chris@0 141 }
Chris@0 142
Chris@0 143 QString
Chris@0 144 SpectrogramLayer::getPropertyGroupName(const PropertyName &name) const
Chris@0 145 {
Chris@0 146 if (name == tr("Window Size") ||
Chris@35 147 name == tr("Window Type") ||
Chris@0 148 name == tr("Window Overlap")) return tr("Window");
Chris@35 149 if (name == tr("Colour") ||
Chris@35 150 name == tr("Colour Rotation")) return tr("Colour");
Chris@0 151 if (name == tr("Gain") ||
Chris@0 152 name == tr("Colour Scale")) return tr("Scale");
Chris@0 153 if (name == tr("Max Frequency") ||
Chris@35 154 name == tr("Frequency Scale") ||
Chris@35 155 name == tr("Frequency Adjustment")) return tr("Frequency");
Chris@0 156 return QString();
Chris@0 157 }
Chris@0 158
Chris@0 159 int
Chris@0 160 SpectrogramLayer::getPropertyRangeAndValue(const PropertyName &name,
Chris@0 161 int *min, int *max) const
Chris@0 162 {
Chris@0 163 int deft = 0;
Chris@0 164
Chris@10 165 int throwaway;
Chris@10 166 if (!min) min = &throwaway;
Chris@10 167 if (!max) max = &throwaway;
Chris@10 168
Chris@0 169 if (name == tr("Gain")) {
Chris@0 170
Chris@0 171 *min = -50;
Chris@0 172 *max = 50;
Chris@0 173
Chris@0 174 deft = lrint(log10(m_gain) * 20.0);
Chris@0 175 if (deft < *min) deft = *min;
Chris@0 176 if (deft > *max) deft = *max;
Chris@0 177
Chris@9 178 } else if (name == tr("Colour Rotation")) {
Chris@9 179
Chris@9 180 *min = 0;
Chris@9 181 *max = 256;
Chris@9 182
Chris@9 183 deft = m_colourRotation;
Chris@9 184
Chris@0 185 } else if (name == tr("Colour Scale")) {
Chris@0 186
Chris@0 187 *min = 0;
Chris@0 188 *max = 3;
Chris@0 189
Chris@0 190 deft = (int)m_colourScale;
Chris@0 191
Chris@0 192 } else if (name == tr("Colour")) {
Chris@0 193
Chris@0 194 *min = 0;
Chris@0 195 *max = 5;
Chris@0 196
Chris@0 197 deft = (int)m_colourScheme;
Chris@0 198
Chris@0 199 } else if (name == tr("Window Type")) {
Chris@0 200
Chris@0 201 *min = 0;
Chris@0 202 *max = 6;
Chris@0 203
Chris@0 204 deft = (int)m_windowType;
Chris@0 205
Chris@0 206 } else if (name == tr("Window Size")) {
Chris@0 207
Chris@0 208 *min = 0;
Chris@0 209 *max = 10;
Chris@0 210
Chris@0 211 deft = 0;
Chris@0 212 int ws = m_windowSize;
Chris@0 213 while (ws > 32) { ws >>= 1; deft ++; }
Chris@0 214
Chris@0 215 } else if (name == tr("Window Overlap")) {
Chris@0 216
Chris@0 217 *min = 0;
Chris@0 218 *max = 4;
Chris@0 219
Chris@0 220 deft = m_windowOverlap / 25;
Chris@0 221 if (m_windowOverlap == 90) deft = 4;
Chris@0 222
Chris@0 223 } else if (name == tr("Max Frequency")) {
Chris@0 224
Chris@0 225 *min = 0;
Chris@0 226 *max = 9;
Chris@0 227
Chris@0 228 switch (m_maxFrequency) {
Chris@0 229 case 500: deft = 0; break;
Chris@0 230 case 1000: deft = 1; break;
Chris@0 231 case 1500: deft = 2; break;
Chris@0 232 case 2000: deft = 3; break;
Chris@0 233 case 4000: deft = 4; break;
Chris@0 234 case 6000: deft = 5; break;
Chris@0 235 case 8000: deft = 6; break;
Chris@0 236 case 12000: deft = 7; break;
Chris@0 237 case 16000: deft = 8; break;
Chris@0 238 default: deft = 9; break;
Chris@0 239 }
Chris@0 240
Chris@0 241 } else if (name == tr("Frequency Scale")) {
Chris@0 242
Chris@0 243 *min = 0;
Chris@0 244 *max = 1;
Chris@0 245 deft = (int)m_frequencyScale;
Chris@0 246
Chris@35 247 } else if (name == tr("Frequency Adjustment")) {
Chris@35 248
Chris@35 249 *min = 0;
Chris@35 250 *max = 2;
Chris@35 251 deft = (int)m_frequencyAdjustment;
Chris@35 252
Chris@0 253 } else {
Chris@0 254 deft = Layer::getPropertyRangeAndValue(name, min, max);
Chris@0 255 }
Chris@0 256
Chris@0 257 return deft;
Chris@0 258 }
Chris@0 259
Chris@0 260 QString
Chris@0 261 SpectrogramLayer::getPropertyValueLabel(const PropertyName &name,
Chris@9 262 int value) const
Chris@0 263 {
Chris@0 264 if (name == tr("Colour")) {
Chris@0 265 switch (value) {
Chris@0 266 default:
Chris@0 267 case 0: return tr("Default");
Chris@0 268 case 1: return tr("White on Black");
Chris@0 269 case 2: return tr("Black on White");
Chris@0 270 case 3: return tr("Red on Blue");
Chris@0 271 case 4: return tr("Yellow on Black");
Chris@0 272 case 5: return tr("Red on Black");
Chris@0 273 }
Chris@0 274 }
Chris@0 275 if (name == tr("Colour Scale")) {
Chris@0 276 switch (value) {
Chris@0 277 default:
Chris@0 278 case 0: return tr("Level Linear");
Chris@0 279 case 1: return tr("Level Meter");
Chris@0 280 case 2: return tr("Level dB");
Chris@0 281 case 3: return tr("Phase");
Chris@0 282 }
Chris@0 283 }
Chris@0 284 if (name == tr("Window Type")) {
Chris@0 285 switch ((WindowType)value) {
Chris@0 286 default:
Chris@35 287 case RectangularWindow: return tr("Rectangle");
Chris@0 288 case BartlettWindow: return tr("Bartlett");
Chris@0 289 case HammingWindow: return tr("Hamming");
Chris@0 290 case HanningWindow: return tr("Hanning");
Chris@0 291 case BlackmanWindow: return tr("Blackman");
Chris@0 292 case GaussianWindow: return tr("Gaussian");
Chris@0 293 case ParzenWindow: return tr("Parzen");
Chris@0 294 }
Chris@0 295 }
Chris@0 296 if (name == tr("Window Size")) {
Chris@0 297 return QString("%1").arg(32 << value);
Chris@0 298 }
Chris@0 299 if (name == tr("Window Overlap")) {
Chris@0 300 switch (value) {
Chris@0 301 default:
Chris@35 302 case 0: return tr("0%");
Chris@35 303 case 1: return tr("25%");
Chris@35 304 case 2: return tr("50%");
Chris@35 305 case 3: return tr("75%");
Chris@35 306 case 4: return tr("90%");
Chris@0 307 }
Chris@0 308 }
Chris@0 309 if (name == tr("Max Frequency")) {
Chris@0 310 switch (value) {
Chris@0 311 default:
Chris@0 312 case 0: return tr("500 Hz");
Chris@0 313 case 1: return tr("1 KHz");
Chris@0 314 case 2: return tr("1.5 KHz");
Chris@0 315 case 3: return tr("2 KHz");
Chris@0 316 case 4: return tr("4 KHz");
Chris@0 317 case 5: return tr("6 KHz");
Chris@0 318 case 6: return tr("8 KHz");
Chris@0 319 case 7: return tr("12 KHz");
Chris@0 320 case 8: return tr("16 KHz");
Chris@0 321 case 9: return tr("All");
Chris@0 322 }
Chris@0 323 }
Chris@0 324 if (name == tr("Frequency Scale")) {
Chris@0 325 switch (value) {
Chris@0 326 default:
Chris@0 327 case 0: return tr("Linear");
Chris@0 328 case 1: return tr("Log");
Chris@0 329 }
Chris@0 330 }
Chris@35 331 if (name == tr("Frequency Adjustment")) {
Chris@35 332 switch (value) {
Chris@35 333 default:
Chris@35 334 case 0: return tr("Bins");
Chris@35 335 case 1: return tr("Pitches");
Chris@35 336 case 2: return tr("Peaks");
Chris@35 337 }
Chris@35 338 }
Chris@0 339 return tr("<unknown>");
Chris@0 340 }
Chris@0 341
Chris@0 342 void
Chris@0 343 SpectrogramLayer::setProperty(const PropertyName &name, int value)
Chris@0 344 {
Chris@0 345 if (name == tr("Gain")) {
Chris@0 346 setGain(pow(10, float(value)/20.0));
Chris@9 347 } else if (name == tr("Colour Rotation")) {
Chris@9 348 setColourRotation(value);
Chris@0 349 } else if (name == tr("Colour")) {
Chris@0 350 if (m_view) m_view->setLightBackground(value == 2);
Chris@0 351 switch (value) {
Chris@0 352 default:
Chris@0 353 case 0: setColourScheme(DefaultColours); break;
Chris@0 354 case 1: setColourScheme(WhiteOnBlack); break;
Chris@0 355 case 2: setColourScheme(BlackOnWhite); break;
Chris@0 356 case 3: setColourScheme(RedOnBlue); break;
Chris@0 357 case 4: setColourScheme(YellowOnBlack); break;
Chris@0 358 case 5: setColourScheme(RedOnBlack); break;
Chris@0 359 }
Chris@0 360 } else if (name == tr("Window Type")) {
Chris@0 361 setWindowType(WindowType(value));
Chris@0 362 } else if (name == tr("Window Size")) {
Chris@0 363 setWindowSize(32 << value);
Chris@0 364 } else if (name == tr("Window Overlap")) {
Chris@0 365 if (value == 4) setWindowOverlap(90);
Chris@0 366 else setWindowOverlap(25 * value);
Chris@0 367 } else if (name == tr("Max Frequency")) {
Chris@0 368 switch (value) {
Chris@0 369 case 0: setMaxFrequency(500); break;
Chris@0 370 case 1: setMaxFrequency(1000); break;
Chris@0 371 case 2: setMaxFrequency(1500); break;
Chris@0 372 case 3: setMaxFrequency(2000); break;
Chris@0 373 case 4: setMaxFrequency(4000); break;
Chris@0 374 case 5: setMaxFrequency(6000); break;
Chris@0 375 case 6: setMaxFrequency(8000); break;
Chris@0 376 case 7: setMaxFrequency(12000); break;
Chris@0 377 case 8: setMaxFrequency(16000); break;
Chris@0 378 default:
Chris@0 379 case 9: setMaxFrequency(0); break;
Chris@0 380 }
Chris@0 381 } else if (name == tr("Colour Scale")) {
Chris@0 382 switch (value) {
Chris@0 383 default:
Chris@0 384 case 0: setColourScale(LinearColourScale); break;
Chris@0 385 case 1: setColourScale(MeterColourScale); break;
Chris@0 386 case 2: setColourScale(dBColourScale); break;
Chris@0 387 case 3: setColourScale(PhaseColourScale); break;
Chris@0 388 }
Chris@0 389 } else if (name == tr("Frequency Scale")) {
Chris@0 390 switch (value) {
Chris@0 391 default:
Chris@0 392 case 0: setFrequencyScale(LinearFrequencyScale); break;
Chris@0 393 case 1: setFrequencyScale(LogFrequencyScale); break;
Chris@0 394 }
Chris@35 395 } else if (name == tr("Frequency Adjustment")) {
Chris@35 396 switch (value) {
Chris@35 397 default:
Chris@35 398 case 0: setFrequencyAdjustment(RawFrequency); break;
Chris@35 399 case 1: setFrequencyAdjustment(PhaseAdjustedFrequency); break;
Chris@35 400 case 2: setFrequencyAdjustment(PhaseAdjustedPeaks); break;
Chris@35 401 }
Chris@0 402 }
Chris@0 403 }
Chris@0 404
Chris@0 405 void
Chris@0 406 SpectrogramLayer::setChannel(int ch)
Chris@0 407 {
Chris@0 408 if (m_channel == ch) return;
Chris@0 409
Chris@0 410 m_mutex.lock();
Chris@0 411 m_cacheInvalid = true;
Chris@0 412 m_pixmapCacheInvalid = true;
Chris@0 413
Chris@0 414 m_channel = ch;
Chris@9 415
Chris@9 416 m_mutex.unlock();
Chris@9 417
Chris@0 418 emit layerParametersChanged();
Chris@9 419
Chris@0 420 fillCache();
Chris@0 421 }
Chris@0 422
Chris@0 423 int
Chris@0 424 SpectrogramLayer::getChannel() const
Chris@0 425 {
Chris@0 426 return m_channel;
Chris@0 427 }
Chris@0 428
Chris@0 429 void
Chris@0 430 SpectrogramLayer::setWindowSize(size_t ws)
Chris@0 431 {
Chris@0 432 if (m_windowSize == ws) return;
Chris@0 433
Chris@0 434 m_mutex.lock();
Chris@0 435 m_cacheInvalid = true;
Chris@0 436 m_pixmapCacheInvalid = true;
Chris@0 437
Chris@0 438 m_windowSize = ws;
Chris@0 439
Chris@0 440 m_mutex.unlock();
Chris@9 441
Chris@9 442 emit layerParametersChanged();
Chris@9 443
Chris@0 444 fillCache();
Chris@0 445 }
Chris@0 446
Chris@0 447 size_t
Chris@0 448 SpectrogramLayer::getWindowSize() const
Chris@0 449 {
Chris@0 450 return m_windowSize;
Chris@0 451 }
Chris@0 452
Chris@0 453 void
Chris@0 454 SpectrogramLayer::setWindowOverlap(size_t wi)
Chris@0 455 {
Chris@0 456 if (m_windowOverlap == wi) return;
Chris@0 457
Chris@0 458 m_mutex.lock();
Chris@0 459 m_cacheInvalid = true;
Chris@0 460 m_pixmapCacheInvalid = true;
Chris@0 461
Chris@0 462 m_windowOverlap = wi;
Chris@0 463
Chris@0 464 m_mutex.unlock();
Chris@9 465
Chris@9 466 emit layerParametersChanged();
Chris@9 467
Chris@0 468 fillCache();
Chris@0 469 }
Chris@0 470
Chris@0 471 size_t
Chris@0 472 SpectrogramLayer::getWindowOverlap() const
Chris@0 473 {
Chris@0 474 return m_windowOverlap;
Chris@0 475 }
Chris@0 476
Chris@0 477 void
Chris@0 478 SpectrogramLayer::setWindowType(WindowType w)
Chris@0 479 {
Chris@0 480 if (m_windowType == w) return;
Chris@0 481
Chris@0 482 m_mutex.lock();
Chris@0 483 m_cacheInvalid = true;
Chris@0 484 m_pixmapCacheInvalid = true;
Chris@0 485
Chris@0 486 m_windowType = w;
Chris@0 487
Chris@0 488 m_mutex.unlock();
Chris@9 489
Chris@9 490 emit layerParametersChanged();
Chris@9 491
Chris@0 492 fillCache();
Chris@0 493 }
Chris@0 494
Chris@0 495 WindowType
Chris@0 496 SpectrogramLayer::getWindowType() const
Chris@0 497 {
Chris@0 498 return m_windowType;
Chris@0 499 }
Chris@0 500
Chris@0 501 void
Chris@0 502 SpectrogramLayer::setGain(float gain)
Chris@0 503 {
Chris@0 504 if (m_gain == gain) return; //!!! inadequate for floats!
Chris@0 505
Chris@0 506 m_mutex.lock();
Chris@0 507 m_cacheInvalid = true;
Chris@0 508 m_pixmapCacheInvalid = true;
Chris@0 509
Chris@0 510 m_gain = gain;
Chris@0 511
Chris@0 512 m_mutex.unlock();
Chris@9 513
Chris@9 514 emit layerParametersChanged();
Chris@9 515
Chris@0 516 fillCache();
Chris@0 517 }
Chris@0 518
Chris@0 519 float
Chris@0 520 SpectrogramLayer::getGain() const
Chris@0 521 {
Chris@0 522 return m_gain;
Chris@0 523 }
Chris@0 524
Chris@0 525 void
Chris@0 526 SpectrogramLayer::setMaxFrequency(size_t mf)
Chris@0 527 {
Chris@0 528 if (m_maxFrequency == mf) return;
Chris@0 529
Chris@0 530 m_mutex.lock();
Chris@1 531 // don't need to invalidate main cache here
Chris@0 532 m_pixmapCacheInvalid = true;
Chris@0 533
Chris@0 534 m_maxFrequency = mf;
Chris@0 535
Chris@0 536 m_mutex.unlock();
Chris@9 537
Chris@9 538 emit layerParametersChanged();
Chris@0 539 }
Chris@0 540
Chris@0 541 size_t
Chris@0 542 SpectrogramLayer::getMaxFrequency() const
Chris@0 543 {
Chris@0 544 return m_maxFrequency;
Chris@0 545 }
Chris@0 546
Chris@0 547 void
Chris@9 548 SpectrogramLayer::setColourRotation(int r)
Chris@9 549 {
Chris@9 550 m_mutex.lock();
Chris@9 551 // don't need to invalidate main cache here
Chris@9 552 m_pixmapCacheInvalid = true;
Chris@9 553
Chris@9 554 if (r < 0) r = 0;
Chris@9 555 if (r > 256) r = 256;
Chris@9 556 int distance = r - m_colourRotation;
Chris@9 557
Chris@9 558 if (distance != 0) {
Chris@9 559 rotateCacheColourmap(-distance);
Chris@9 560 m_colourRotation = r;
Chris@9 561 }
Chris@9 562
Chris@9 563 m_mutex.unlock();
Chris@9 564
Chris@9 565 emit layerParametersChanged();
Chris@9 566 }
Chris@9 567
Chris@9 568 void
Chris@0 569 SpectrogramLayer::setColourScale(ColourScale colourScale)
Chris@0 570 {
Chris@0 571 if (m_colourScale == colourScale) return;
Chris@0 572
Chris@0 573 m_mutex.lock();
Chris@0 574 m_cacheInvalid = true;
Chris@0 575 m_pixmapCacheInvalid = true;
Chris@0 576
Chris@0 577 m_colourScale = colourScale;
Chris@0 578
Chris@0 579 m_mutex.unlock();
Chris@0 580 fillCache();
Chris@9 581
Chris@9 582 emit layerParametersChanged();
Chris@0 583 }
Chris@0 584
Chris@0 585 SpectrogramLayer::ColourScale
Chris@0 586 SpectrogramLayer::getColourScale() const
Chris@0 587 {
Chris@0 588 return m_colourScale;
Chris@0 589 }
Chris@0 590
Chris@0 591 void
Chris@0 592 SpectrogramLayer::setColourScheme(ColourScheme scheme)
Chris@0 593 {
Chris@0 594 if (m_colourScheme == scheme) return;
Chris@0 595
Chris@0 596 m_mutex.lock();
Chris@0 597 // don't need to invalidate main cache here
Chris@0 598 m_pixmapCacheInvalid = true;
Chris@0 599
Chris@0 600 m_colourScheme = scheme;
Chris@0 601 setCacheColourmap();
Chris@9 602
Chris@9 603 m_mutex.unlock();
Chris@9 604
Chris@0 605 emit layerParametersChanged();
Chris@0 606 }
Chris@0 607
Chris@0 608 SpectrogramLayer::ColourScheme
Chris@0 609 SpectrogramLayer::getColourScheme() const
Chris@0 610 {
Chris@0 611 return m_colourScheme;
Chris@0 612 }
Chris@0 613
Chris@0 614 void
Chris@0 615 SpectrogramLayer::setFrequencyScale(FrequencyScale frequencyScale)
Chris@0 616 {
Chris@0 617 if (m_frequencyScale == frequencyScale) return;
Chris@0 618
Chris@0 619 m_mutex.lock();
Chris@35 620
Chris@0 621 // don't need to invalidate main cache here
Chris@0 622 m_pixmapCacheInvalid = true;
Chris@0 623
Chris@0 624 m_frequencyScale = frequencyScale;
Chris@0 625
Chris@0 626 m_mutex.unlock();
Chris@9 627
Chris@9 628 emit layerParametersChanged();
Chris@0 629 }
Chris@0 630
Chris@0 631 SpectrogramLayer::FrequencyScale
Chris@0 632 SpectrogramLayer::getFrequencyScale() const
Chris@0 633 {
Chris@0 634 return m_frequencyScale;
Chris@0 635 }
Chris@0 636
Chris@0 637 void
Chris@35 638 SpectrogramLayer::setFrequencyAdjustment(FrequencyAdjustment frequencyAdjustment)
Chris@35 639 {
Chris@35 640 if (m_frequencyAdjustment == frequencyAdjustment) return;
Chris@35 641
Chris@35 642 m_mutex.lock();
Chris@35 643
Chris@35 644 m_cacheInvalid = true;
Chris@35 645 m_pixmapCacheInvalid = true;
Chris@35 646
Chris@35 647 m_frequencyAdjustment = frequencyAdjustment;
Chris@35 648
Chris@35 649 m_mutex.unlock();
Chris@35 650
Chris@35 651 fillCache();
Chris@35 652
Chris@35 653 emit layerParametersChanged();
Chris@35 654 }
Chris@35 655
Chris@35 656 SpectrogramLayer::FrequencyAdjustment
Chris@35 657 SpectrogramLayer::getFrequencyAdjustment() const
Chris@35 658 {
Chris@35 659 return m_frequencyAdjustment;
Chris@35 660 }
Chris@35 661
Chris@35 662 void
Chris@33 663 SpectrogramLayer::setLayerDormant(bool dormant)
Chris@29 664 {
Chris@33 665 if (dormant == m_dormant) return;
Chris@33 666
Chris@33 667 if (dormant) {
Chris@33 668
Chris@33 669 m_mutex.lock();
Chris@33 670 m_dormant = true;
Chris@33 671
Chris@34 672 // delete m_cache;
Chris@34 673 // m_cache = 0;
Chris@33 674
Chris@34 675 m_cacheInvalid = true;
Chris@33 676 m_pixmapCacheInvalid = true;
Chris@34 677 m_cachedInitialVisibleArea = false;
Chris@33 678 delete m_pixmapCache;
Chris@33 679 m_pixmapCache = 0;
Chris@33 680
Chris@33 681 m_mutex.unlock();
Chris@33 682
Chris@33 683 } else {
Chris@33 684
Chris@33 685 m_dormant = false;
Chris@33 686 fillCache();
Chris@33 687 }
Chris@29 688 }
Chris@29 689
Chris@29 690 void
Chris@0 691 SpectrogramLayer::cacheInvalid()
Chris@0 692 {
Chris@0 693 m_cacheInvalid = true;
Chris@0 694 m_pixmapCacheInvalid = true;
Chris@0 695 m_cachedInitialVisibleArea = false;
Chris@0 696 fillCache();
Chris@0 697 }
Chris@0 698
Chris@0 699 void
Chris@0 700 SpectrogramLayer::cacheInvalid(size_t, size_t)
Chris@0 701 {
Chris@0 702 // for now (or forever?)
Chris@0 703 cacheInvalid();
Chris@0 704 }
Chris@0 705
Chris@0 706 void
Chris@0 707 SpectrogramLayer::fillCache()
Chris@0 708 {
Chris@0 709 #ifdef DEBUG_SPECTROGRAM_REPAINT
Chris@0 710 std::cerr << "SpectrogramLayer::fillCache" << std::endl;
Chris@0 711 #endif
Chris@0 712 QMutexLocker locker(&m_mutex);
Chris@0 713
Chris@0 714 m_lastFillExtent = 0;
Chris@0 715
Chris@0 716 delete m_updateTimer;
Chris@0 717 m_updateTimer = new QTimer(this);
Chris@0 718 connect(m_updateTimer, SIGNAL(timeout()), this, SLOT(fillTimerTimedOut()));
Chris@0 719 m_updateTimer->start(200);
Chris@0 720
Chris@0 721 if (!m_fillThread) {
Chris@0 722 std::cerr << "SpectrogramLayer::fillCache creating thread" << std::endl;
Chris@0 723 m_fillThread = new CacheFillThread(*this);
Chris@0 724 m_fillThread->start();
Chris@0 725 }
Chris@0 726
Chris@0 727 m_condition.wakeAll();
Chris@0 728 }
Chris@0 729
Chris@0 730 void
Chris@0 731 SpectrogramLayer::fillTimerTimedOut()
Chris@0 732 {
Chris@0 733 if (m_fillThread && m_model) {
Chris@0 734 size_t fillExtent = m_fillThread->getFillExtent();
Chris@0 735 #ifdef DEBUG_SPECTROGRAM_REPAINT
Chris@0 736 std::cerr << "SpectrogramLayer::fillTimerTimedOut: extent " << fillExtent << ", last " << m_lastFillExtent << ", total " << m_model->getEndFrame() << std::endl;
Chris@0 737 #endif
Chris@0 738 if (fillExtent >= m_lastFillExtent) {
Chris@0 739 if (fillExtent >= m_model->getEndFrame() && m_lastFillExtent > 0) {
Chris@0 740 #ifdef DEBUG_SPECTROGRAM_REPAINT
Chris@0 741 std::cerr << "complete!" << std::endl;
Chris@0 742 #endif
Chris@0 743 emit modelChanged();
Chris@0 744 m_pixmapCacheInvalid = true;
Chris@0 745 delete m_updateTimer;
Chris@0 746 m_updateTimer = 0;
Chris@0 747 m_lastFillExtent = 0;
Chris@0 748 } else if (fillExtent > m_lastFillExtent) {
Chris@0 749 #ifdef DEBUG_SPECTROGRAM_REPAINT
Chris@0 750 std::cerr << "SpectrogramLayer: emitting modelChanged("
Chris@0 751 << m_lastFillExtent << "," << fillExtent << ")" << std::endl;
Chris@0 752 #endif
Chris@0 753 emit modelChanged(m_lastFillExtent, fillExtent);
Chris@0 754 m_pixmapCacheInvalid = true;
Chris@0 755 m_lastFillExtent = fillExtent;
Chris@0 756 }
Chris@0 757 } else {
Chris@0 758 if (m_view) {
Chris@0 759 size_t sf = 0;
Chris@0 760 if (m_view->getStartFrame() > 0) sf = m_view->getStartFrame();
Chris@0 761 #ifdef DEBUG_SPECTROGRAM_REPAINT
Chris@0 762 std::cerr << "SpectrogramLayer: going backwards, emitting modelChanged("
Chris@0 763 << sf << "," << m_view->getEndFrame() << ")" << std::endl;
Chris@0 764 #endif
Chris@0 765 emit modelChanged(sf, m_view->getEndFrame());
Chris@0 766 m_pixmapCacheInvalid = true;
Chris@0 767 }
Chris@0 768 m_lastFillExtent = fillExtent;
Chris@0 769 }
Chris@0 770 }
Chris@0 771 }
Chris@0 772
Chris@0 773 void
Chris@0 774 SpectrogramLayer::setCacheColourmap()
Chris@0 775 {
Chris@0 776 if (m_cacheInvalid || !m_cache) return;
Chris@0 777
Chris@10 778 int formerRotation = m_colourRotation;
Chris@10 779
Chris@31 780 m_cache->setColour(0, Qt::white);
Chris@0 781
Chris@0 782 for (int pixel = 1; pixel < 256; ++pixel) {
Chris@0 783
Chris@0 784 QColor colour;
Chris@0 785 int hue, px;
Chris@0 786
Chris@0 787 switch (m_colourScheme) {
Chris@0 788
Chris@0 789 default:
Chris@0 790 case DefaultColours:
Chris@0 791 hue = 256 - pixel;
Chris@0 792 colour = QColor::fromHsv(hue, pixel/2 + 128, pixel);
Chris@0 793 break;
Chris@0 794
Chris@0 795 case WhiteOnBlack:
Chris@0 796 colour = QColor(pixel, pixel, pixel);
Chris@0 797 break;
Chris@0 798
Chris@0 799 case BlackOnWhite:
Chris@0 800 colour = QColor(256-pixel, 256-pixel, 256-pixel);
Chris@0 801 break;
Chris@0 802
Chris@0 803 case RedOnBlue:
Chris@0 804 colour = QColor(pixel > 128 ? (pixel - 128) * 2 : 0, 0,
Chris@0 805 pixel < 128 ? pixel : (256 - pixel));
Chris@0 806 break;
Chris@0 807
Chris@0 808 case YellowOnBlack:
Chris@0 809 px = 256 - pixel;
Chris@0 810 colour = QColor(px < 64 ? 255 - px/2 :
Chris@0 811 px < 128 ? 224 - (px - 64) :
Chris@0 812 px < 192 ? 160 - (px - 128) * 3 / 2 :
Chris@0 813 256 - px,
Chris@0 814 pixel,
Chris@0 815 pixel / 4);
Chris@0 816 break;
Chris@0 817
Chris@0 818 case RedOnBlack:
Chris@0 819 colour = QColor::fromHsv(10, pixel, pixel);
Chris@0 820 break;
Chris@0 821 }
Chris@0 822
Chris@31 823 m_cache->setColour(pixel, colour);
Chris@0 824 }
Chris@9 825
Chris@9 826 m_colourRotation = 0;
Chris@10 827 rotateCacheColourmap(m_colourRotation - formerRotation);
Chris@10 828 m_colourRotation = formerRotation;
Chris@9 829 }
Chris@9 830
Chris@9 831 void
Chris@9 832 SpectrogramLayer::rotateCacheColourmap(int distance)
Chris@9 833 {
Chris@10 834 if (!m_cache) return;
Chris@10 835
Chris@31 836 QColor newPixels[256];
Chris@9 837
Chris@31 838 newPixels[0] = m_cache->getColour(0);
Chris@9 839
Chris@9 840 for (int pixel = 1; pixel < 256; ++pixel) {
Chris@9 841 int target = pixel + distance;
Chris@9 842 while (target < 1) target += 255;
Chris@9 843 while (target > 255) target -= 255;
Chris@31 844 newPixels[target] = m_cache->getColour(pixel);
Chris@9 845 }
Chris@9 846
Chris@9 847 for (int pixel = 0; pixel < 256; ++pixel) {
Chris@31 848 m_cache->setColour(pixel, newPixels[pixel]);
Chris@9 849 }
Chris@0 850 }
Chris@0 851
Chris@0 852 bool
Chris@0 853 SpectrogramLayer::fillCacheColumn(int column, double *input,
Chris@0 854 fftw_complex *output,
Chris@0 855 fftw_plan plan,
Chris@9 856 size_t windowSize,
Chris@9 857 size_t increment,
Chris@0 858 const Window<double> &windower,
Chris@35 859 bool resetStoredPhase) const
Chris@0 860 {
Chris@35 861 static std::vector<double> storedPhase;
Chris@35 862
Chris@35 863 bool phaseAdjust =
Chris@35 864 (m_frequencyAdjustment == PhaseAdjustedFrequency ||
Chris@35 865 m_frequencyAdjustment == PhaseAdjustedPeaks);
Chris@35 866 bool haveStoredPhase = true;
Chris@35 867 size_t sampleRate = 0;
Chris@35 868
Chris@35 869 static int counter = 0;
Chris@35 870
Chris@35 871 if (phaseAdjust) {
Chris@35 872 if (resetStoredPhase || (storedPhase.size() != windowSize / 2)) {
Chris@35 873 haveStoredPhase = false;
Chris@35 874 storedPhase.clear();
Chris@35 875 for (size_t i = 0; i < windowSize / 2; ++i) {
Chris@35 876 storedPhase.push_back(0.0);
Chris@35 877 }
Chris@35 878 counter = 0;
Chris@35 879 }
Chris@35 880 ++counter;
Chris@35 881 sampleRate = m_model->getSampleRate();
Chris@35 882 }
Chris@35 883
Chris@0 884 int startFrame = increment * column;
Chris@9 885 int endFrame = startFrame + windowSize;
Chris@0 886
Chris@9 887 startFrame -= int(windowSize - increment) / 2;
Chris@9 888 endFrame -= int(windowSize - increment) / 2;
Chris@0 889 size_t pfx = 0;
Chris@0 890
Chris@0 891 if (startFrame < 0) {
Chris@0 892 pfx = size_t(-startFrame);
Chris@0 893 for (size_t i = 0; i < pfx; ++i) {
Chris@0 894 input[i] = 0.0;
Chris@0 895 }
Chris@0 896 }
Chris@0 897
Chris@0 898 size_t got = m_model->getValues(m_channel, startFrame + pfx,
Chris@0 899 endFrame, input + pfx);
Chris@9 900 while (got + pfx < windowSize) {
Chris@0 901 input[got + pfx] = 0.0;
Chris@0 902 ++got;
Chris@0 903 }
Chris@0 904
Chris@0 905 if (m_gain != 1.0) {
Chris@9 906 for (size_t i = 0; i < windowSize; ++i) {
Chris@0 907 input[i] *= m_gain;
Chris@0 908 }
Chris@0 909 }
Chris@0 910
Chris@0 911 windower.cut(input);
Chris@0 912
Chris@35 913 for (size_t i = 0; i < windowSize/2; ++i) {
Chris@35 914 double temp = input[i];
Chris@35 915 input[i] = input[i + windowSize/2];
Chris@35 916 input[i + windowSize/2] = temp;
Chris@35 917 }
Chris@35 918
Chris@0 919 fftw_execute(plan);
Chris@0 920
Chris@0 921 bool interrupted = false;
Chris@0 922
Chris@35 923 double prevMag = 0.0;
Chris@35 924
Chris@9 925 for (size_t i = 0; i < windowSize / 2; ++i) {
Chris@0 926
Chris@0 927 int value = 0;
Chris@35 928 double phase = 0.0;
Chris@35 929
Chris@35 930 double mag = sqrt(output[i][0] * output[i][0] +
Chris@35 931 output[i][1] * output[i][1]);
Chris@35 932 mag /= windowSize / 2;
Chris@35 933
Chris@35 934 if (phaseAdjust || (m_colourScale == PhaseColourScale)) {
Chris@35 935
Chris@35 936 // phase = atan2(-output[i][1], output[i][0]);
Chris@35 937 // phase = atan2(output[i][1], output[i][0]);
Chris@35 938 phase = atan2(output[i][0], output[i][1]);
Chris@35 939 // phase = MathUtilities::princarg(phase);
Chris@35 940 }
Chris@35 941
Chris@35 942 if (phaseAdjust && m_phaseAdjustCache && haveStoredPhase) {
Chris@35 943
Chris@35 944 bool peak = true;
Chris@35 945 if (m_frequencyAdjustment == PhaseAdjustedPeaks) {
Chris@35 946 if (mag < prevMag) peak = false;
Chris@35 947 else {
Chris@35 948 double nextMag = 0.0;
Chris@35 949 if (i < windowSize / 2 - 1) {
Chris@35 950 nextMag = sqrt(output[i+1][0] * output[i+1][0] +
Chris@35 951 output[i+1][1] * output[i+1][1]);
Chris@35 952 nextMag /= windowSize / 2;
Chris@35 953 }
Chris@35 954 if (mag < nextMag) peak = false;
Chris@35 955 }
Chris@35 956 prevMag = mag;
Chris@35 957 }
Chris@35 958
Chris@35 959 if (!peak) {
Chris@35 960 if (m_cacheInvalid || m_exiting) {
Chris@35 961 interrupted = true;
Chris@35 962 break;
Chris@35 963 }
Chris@35 964 m_phaseAdjustCache->setValueAt(column, i, SCHAR_MIN);
Chris@35 965 } else {
Chris@35 966
Chris@35 967 // if (i > 45 && i < 55 && counter == 10) {
Chris@35 968
Chris@35 969 double freq = (double(i) * sampleRate) / m_windowSize;
Chris@35 970 // std::cout << "\nbin = " << i << " initial estimate freq = " << freq
Chris@35 971 // << " mag = " << mag << std::endl;
Chris@35 972
Chris@35 973 double prevPhase = storedPhase[i];
Chris@35 974
Chris@35 975 double expectedPhase =
Chris@35 976 prevPhase + (2 * M_PI * i * increment) / m_windowSize;
Chris@35 977
Chris@35 978 double phaseError = MathUtilities::princarg(phase - expectedPhase);
Chris@35 979
Chris@35 980 // if (fabs(phaseError) > (1.2 * (increment * M_PI) / m_windowSize)) {
Chris@35 981 // std::cout << "error > " << (1.2 * (increment * M_PI) / m_windowSize) << std::endl;
Chris@35 982 // }// else {
Chris@35 983
Chris@35 984 // std::cout << "prevPhase = " << prevPhase << ", phase = " << phase
Chris@35 985 // << ", expected = " << MathUtilities::princarg(expectedPhase) << ", error = "
Chris@35 986 // << phaseError << std::endl;
Chris@35 987
Chris@35 988 double newFreq =
Chris@35 989 (sampleRate *
Chris@35 990 (expectedPhase + phaseError - prevPhase)) /
Chris@35 991 //(prevPhase - (expectedPhase + phaseError))) /
Chris@35 992 (2 * M_PI * increment);
Chris@35 993
Chris@35 994 // std::cout << freq << " (" << Pitch::getPitchLabelForFrequency(freq).toStdString() << ") -> " << newFreq << " (" << Pitch::getPitchLabelForFrequency(newFreq).toStdString() << ")" << std::endl;
Chris@35 995 // }
Chris@35 996 //}
Chris@35 997
Chris@35 998 double binRange = (double(i + 1) * sampleRate) / windowSize - freq;
Chris@35 999
Chris@35 1000 int offset = lrint(((newFreq - freq) / binRange) * 10);//!!!
Chris@35 1001
Chris@35 1002 if (m_cacheInvalid || m_exiting) {
Chris@35 1003 interrupted = true;
Chris@35 1004 break;
Chris@35 1005 }
Chris@35 1006 if (offset > SCHAR_MIN && offset <= SCHAR_MAX) {
Chris@35 1007 signed char coff = offset;
Chris@35 1008 m_phaseAdjustCache->setValueAt(column, i, (unsigned char)coff);
Chris@35 1009 } else {
Chris@35 1010 m_phaseAdjustCache->setValueAt(column, i, 0);
Chris@35 1011 }
Chris@35 1012 }
Chris@35 1013 storedPhase[i] = phase;
Chris@35 1014 }
Chris@0 1015
Chris@0 1016 if (m_colourScale == PhaseColourScale) {
Chris@0 1017
Chris@35 1018 phase = MathUtilities::princarg(phase);
Chris@0 1019 value = int((phase * 128 / M_PI) + 128);
Chris@0 1020
Chris@0 1021 } else {
Chris@1 1022
Chris@0 1023 double mag = sqrt(output[i][0] * output[i][0] +
Chris@0 1024 output[i][1] * output[i][1]);
Chris@9 1025 mag /= windowSize / 2;
Chris@0 1026
Chris@0 1027 switch (m_colourScale) {
Chris@0 1028
Chris@0 1029 default:
Chris@0 1030 case LinearColourScale:
Chris@0 1031 value = int(mag * 50 * 256);
Chris@0 1032 break;
Chris@0 1033
Chris@0 1034 case MeterColourScale:
Chris@0 1035 value = AudioLevel::multiplier_to_preview(mag * 50, 256);
Chris@0 1036 break;
Chris@0 1037
Chris@0 1038 case dBColourScale:
Chris@0 1039 mag = 20.0 * log10(mag);
Chris@0 1040 mag = (mag + 80.0) / 80.0;
Chris@0 1041 if (mag < 0.0) mag = 0.0;
Chris@0 1042 if (mag > 1.0) mag = 1.0;
Chris@0 1043 value = int(mag * 256);
Chris@0 1044 }
Chris@0 1045 }
Chris@0 1046
Chris@0 1047 if (value > 254) value = 254;
Chris@0 1048 if (value < 0) value = 0;
Chris@0 1049
Chris@0 1050 if (m_cacheInvalid || m_exiting) {
Chris@0 1051 interrupted = true;
Chris@0 1052 break;
Chris@0 1053 }
Chris@0 1054
Chris@31 1055 m_cache->setValueAt(column, i, value + 1);
Chris@0 1056 }
Chris@0 1057
Chris@0 1058 return !interrupted;
Chris@0 1059 }
Chris@0 1060
Chris@31 1061 SpectrogramLayer::Cache::Cache(size_t width, size_t height) :
Chris@31 1062 m_width(width),
Chris@31 1063 m_height(height)
Chris@31 1064 {
Chris@35 1065 // use malloc rather than new[], because we want to be able to use realloc
Chris@35 1066 m_values = (unsigned char *)
Chris@35 1067 malloc(m_width * m_height * sizeof(unsigned char));
Chris@35 1068 if (!m_values) throw std::bad_alloc();
Chris@31 1069 MUNLOCK(m_values, m_width * m_height * sizeof(unsigned char));
Chris@31 1070 }
Chris@31 1071
Chris@31 1072 SpectrogramLayer::Cache::~Cache()
Chris@31 1073 {
Chris@35 1074 if (m_values) free(m_values);
Chris@31 1075 }
Chris@31 1076
Chris@35 1077 void
Chris@35 1078 SpectrogramLayer::Cache::resize(size_t width, size_t height)
Chris@35 1079 {
Chris@35 1080 m_values = (unsigned char *)
Chris@35 1081 realloc(m_values, m_width * m_height * sizeof(unsigned char));
Chris@35 1082 if (!m_values) throw std::bad_alloc();
Chris@35 1083 MUNLOCK(m_values, m_width * m_height * sizeof(unsigned char));
Chris@35 1084 }
Chris@35 1085
Chris@31 1086 size_t
Chris@31 1087 SpectrogramLayer::Cache::getWidth() const
Chris@31 1088 {
Chris@31 1089 return m_width;
Chris@31 1090 }
Chris@31 1091
Chris@31 1092 size_t
Chris@31 1093 SpectrogramLayer::Cache::getHeight() const
Chris@31 1094 {
Chris@31 1095 return m_height;
Chris@31 1096 }
Chris@31 1097
Chris@31 1098 unsigned char
Chris@31 1099 SpectrogramLayer::Cache::getValueAt(size_t x, size_t y) const
Chris@31 1100 {
Chris@31 1101 if (x >= m_width || y >= m_height) return 0;
Chris@31 1102 return m_values[y * m_width + x];
Chris@31 1103 }
Chris@31 1104
Chris@31 1105 void
Chris@31 1106 SpectrogramLayer::Cache::setValueAt(size_t x, size_t y, unsigned char value)
Chris@31 1107 {
Chris@31 1108 if (x >= m_width || y >= m_height) return;
Chris@31 1109 m_values[y * m_width + x] = value;
Chris@31 1110 }
Chris@31 1111
Chris@31 1112 QColor
Chris@31 1113 SpectrogramLayer::Cache::getColour(unsigned char index) const
Chris@31 1114 {
Chris@31 1115 return m_colours[index];
Chris@31 1116 }
Chris@31 1117
Chris@31 1118 void
Chris@31 1119 SpectrogramLayer::Cache::setColour(unsigned char index, QColor colour)
Chris@31 1120 {
Chris@31 1121 m_colours[index] = colour;
Chris@31 1122 }
Chris@31 1123
Chris@31 1124 void
Chris@31 1125 SpectrogramLayer::Cache::fill(unsigned char value)
Chris@31 1126 {
Chris@31 1127 for (size_t i = 0; i < m_width * m_height; ++i) {
Chris@31 1128 m_values[i] = value;
Chris@31 1129 }
Chris@31 1130 }
Chris@31 1131
Chris@0 1132 void
Chris@0 1133 SpectrogramLayer::CacheFillThread::run()
Chris@0 1134 {
Chris@0 1135 // std::cerr << "SpectrogramLayer::CacheFillThread::run" << std::endl;
Chris@0 1136
Chris@0 1137 m_layer.m_mutex.lock();
Chris@0 1138
Chris@0 1139 while (!m_layer.m_exiting) {
Chris@0 1140
Chris@0 1141 bool interrupted = false;
Chris@0 1142
Chris@0 1143 // std::cerr << "SpectrogramLayer::CacheFillThread::run in loop" << std::endl;
Chris@0 1144
Chris@34 1145 if (m_layer.m_dormant) {
Chris@34 1146
Chris@34 1147 if (m_layer.m_cacheInvalid) {
Chris@34 1148 delete m_layer.m_cache;
Chris@35 1149 delete m_layer.m_phaseAdjustCache;
Chris@34 1150 m_layer.m_cache = 0;
Chris@35 1151 m_layer.m_phaseAdjustCache = 0;
Chris@34 1152 }
Chris@34 1153
Chris@34 1154 } else if (m_layer.m_model && m_layer.m_cacheInvalid) {
Chris@0 1155
Chris@0 1156 // std::cerr << "SpectrogramLayer::CacheFillThread::run: something to do" << std::endl;
Chris@0 1157
Chris@0 1158 while (!m_layer.m_model->isReady()) {
Chris@0 1159 m_layer.m_condition.wait(&m_layer.m_mutex, 100);
Chris@0 1160 }
Chris@0 1161
Chris@0 1162 m_layer.m_cachedInitialVisibleArea = false;
Chris@0 1163 m_layer.m_cacheInvalid = false;
Chris@0 1164 m_fillExtent = 0;
Chris@0 1165 m_fillCompletion = 0;
Chris@0 1166
Chris@0 1167 std::cerr << "SpectrogramLayer::CacheFillThread::run: model is ready" << std::endl;
Chris@0 1168
Chris@0 1169 size_t start = m_layer.m_model->getStartFrame();
Chris@0 1170 size_t end = m_layer.m_model->getEndFrame();
Chris@9 1171
Chris@9 1172 WindowType windowType = m_layer.m_windowType;
Chris@0 1173 size_t windowSize = m_layer.m_windowSize;
Chris@0 1174 size_t windowIncrement = m_layer.getWindowIncrement();
Chris@0 1175
Chris@0 1176 size_t visibleStart = start;
Chris@0 1177 size_t visibleEnd = end;
Chris@0 1178
Chris@0 1179 if (m_layer.m_view) {
Chris@0 1180 if (m_layer.m_view->getStartFrame() < 0) {
Chris@0 1181 visibleStart = 0;
Chris@0 1182 } else {
Chris@0 1183 visibleStart = m_layer.m_view->getStartFrame();
Chris@0 1184 visibleStart = (visibleStart / windowIncrement) *
Chris@0 1185 windowIncrement;
Chris@0 1186 }
Chris@0 1187 visibleEnd = m_layer.m_view->getEndFrame();
Chris@0 1188 }
Chris@0 1189
Chris@9 1190 size_t width = (end - start) / windowIncrement + 1;
Chris@9 1191 size_t height = windowSize / 2;
Chris@35 1192
Chris@35 1193 if (!m_layer.m_cache) {
Chris@35 1194 m_layer.m_cache = new Cache(width, height);
Chris@35 1195 } else if (width != m_layer.m_cache->getWidth() ||
Chris@35 1196 height != m_layer.m_cache->getHeight()) {
Chris@35 1197 m_layer.m_cache->resize(width, height);
Chris@35 1198 }
Chris@9 1199
Chris@0 1200 m_layer.setCacheColourmap();
Chris@33 1201 m_layer.m_cache->fill(0);
Chris@10 1202
Chris@35 1203 if (m_layer.m_frequencyAdjustment == PhaseAdjustedFrequency ||
Chris@35 1204 m_layer.m_frequencyAdjustment == PhaseAdjustedPeaks) {
Chris@35 1205
Chris@35 1206 if (!m_layer.m_phaseAdjustCache) {
Chris@35 1207 m_layer.m_phaseAdjustCache = new Cache(width, height);
Chris@35 1208 } else if (width != m_layer.m_phaseAdjustCache->getWidth() ||
Chris@35 1209 height != m_layer.m_phaseAdjustCache->getHeight()) {
Chris@35 1210 m_layer.m_phaseAdjustCache->resize(width, height);
Chris@35 1211 }
Chris@35 1212
Chris@35 1213 m_layer.m_phaseAdjustCache->fill(0);
Chris@35 1214
Chris@35 1215 } else {
Chris@35 1216 delete m_layer.m_phaseAdjustCache;
Chris@35 1217 m_layer.m_phaseAdjustCache = 0;
Chris@35 1218 }
Chris@35 1219
Chris@33 1220 // We don't need a lock when writing to or reading from
Chris@33 1221 // the pixels in the cache, because it's a fixed size
Chris@33 1222 // array. We do need to ensure we have the width and
Chris@35 1223 // height of the cache and the FFT parameters known before
Chris@33 1224 // we unlock, in case they change in the model while we
Chris@33 1225 // aren't holding a lock. It's safe for us to continue to
Chris@33 1226 // use the "old" values if that happens, because they will
Chris@33 1227 // continue to match the dimensions of the actual cache
Chris@33 1228 // (which we manage, not the model).
Chris@0 1229 m_layer.m_mutex.unlock();
Chris@0 1230
Chris@0 1231 double *input = (double *)
Chris@0 1232 fftw_malloc(windowSize * sizeof(double));
Chris@0 1233
Chris@0 1234 fftw_complex *output = (fftw_complex *)
Chris@0 1235 fftw_malloc(windowSize * sizeof(fftw_complex));
Chris@0 1236
Chris@0 1237 fftw_plan plan = fftw_plan_dft_r2c_1d(windowSize, input,
Chris@1 1238 output, FFTW_ESTIMATE);
Chris@0 1239
Chris@9 1240 Window<double> windower(windowType, windowSize);
Chris@0 1241
Chris@0 1242 if (!plan) {
Chris@1 1243 std::cerr << "WARNING: fftw_plan_dft_r2c_1d(" << windowSize << ") failed!" << std::endl;
Chris@0 1244 fftw_free(input);
Chris@0 1245 fftw_free(output);
Chris@0 1246 continue;
Chris@0 1247 }
Chris@0 1248
Chris@0 1249 int counter = 0;
Chris@0 1250 int updateAt = (end / windowIncrement) / 20;
Chris@0 1251 if (updateAt < 100) updateAt = 100;
Chris@0 1252
Chris@0 1253 bool doVisibleFirst = (visibleStart != start && visibleEnd != end);
Chris@0 1254
Chris@0 1255 if (doVisibleFirst) {
Chris@0 1256
Chris@0 1257 for (size_t f = visibleStart; f < visibleEnd; f += windowIncrement) {
Chris@0 1258
Chris@0 1259 m_layer.fillCacheColumn(int((f - start) / windowIncrement),
Chris@9 1260 input, output, plan,
Chris@9 1261 windowSize, windowIncrement,
Chris@35 1262 //!!! actually if we're doing phase adjustment we also want to fill the column preceding the visible area so that we have the right values for the first visible one (also applies below)
Chris@35 1263 windower, f == visibleStart);
Chris@0 1264
Chris@0 1265 if (m_layer.m_cacheInvalid || m_layer.m_exiting) {
Chris@0 1266 interrupted = true;
Chris@0 1267 m_fillExtent = 0;
Chris@0 1268 break;
Chris@0 1269 }
Chris@0 1270
Chris@0 1271 if (++counter == updateAt) {
Chris@0 1272 if (f < end) m_fillExtent = f;
Chris@0 1273 m_fillCompletion = size_t(100 * fabsf(float(f - visibleStart) /
Chris@0 1274 float(end - start)));
Chris@0 1275 counter = 0;
Chris@0 1276 }
Chris@0 1277 }
Chris@0 1278 }
Chris@0 1279
Chris@0 1280 m_layer.m_cachedInitialVisibleArea = true;
Chris@0 1281
Chris@0 1282 if (!interrupted && doVisibleFirst) {
Chris@0 1283
Chris@0 1284 for (size_t f = visibleEnd; f < end; f += windowIncrement) {
Chris@0 1285
Chris@0 1286 if (!m_layer.fillCacheColumn(int((f - start) / windowIncrement),
Chris@9 1287 input, output, plan,
Chris@9 1288 windowSize, windowIncrement,
Chris@35 1289 windower, f == visibleEnd)) {
Chris@0 1290 interrupted = true;
Chris@0 1291 m_fillExtent = 0;
Chris@0 1292 break;
Chris@0 1293 }
Chris@0 1294
Chris@0 1295
Chris@0 1296 if (++counter == updateAt) {
Chris@0 1297 if (f < end) m_fillExtent = f;
Chris@0 1298 m_fillCompletion = size_t(100 * fabsf(float(f - visibleStart) /
Chris@0 1299 float(end - start)));
Chris@0 1300 counter = 0;
Chris@0 1301 }
Chris@0 1302 }
Chris@0 1303 }
Chris@0 1304
Chris@0 1305 if (!interrupted) {
Chris@0 1306
Chris@0 1307 size_t remainingEnd = end;
Chris@0 1308 if (doVisibleFirst) {
Chris@0 1309 remainingEnd = visibleStart;
Chris@0 1310 if (remainingEnd > start) --remainingEnd;
Chris@0 1311 else remainingEnd = start;
Chris@0 1312 }
Chris@0 1313 size_t baseCompletion = m_fillCompletion;
Chris@0 1314
Chris@0 1315 for (size_t f = start; f < remainingEnd; f += windowIncrement) {
Chris@0 1316
Chris@0 1317 if (!m_layer.fillCacheColumn(int((f - start) / windowIncrement),
Chris@9 1318 input, output, plan,
Chris@9 1319 windowSize, windowIncrement,
Chris@35 1320 windower, f == start)) {
Chris@0 1321 interrupted = true;
Chris@0 1322 m_fillExtent = 0;
Chris@0 1323 break;
Chris@0 1324 }
Chris@0 1325
Chris@0 1326 if (++counter == updateAt) {
Chris@0 1327 m_fillExtent = f;
Chris@0 1328 m_fillCompletion = baseCompletion +
Chris@0 1329 size_t(100 * fabsf(float(f - start) /
Chris@0 1330 float(end - start)));
Chris@0 1331 counter = 0;
Chris@0 1332 }
Chris@0 1333 }
Chris@0 1334 }
Chris@0 1335
Chris@0 1336 fftw_destroy_plan(plan);
Chris@0 1337 fftw_free(output);
Chris@0 1338 fftw_free(input);
Chris@0 1339
Chris@0 1340 if (!interrupted) {
Chris@0 1341 m_fillExtent = end;
Chris@0 1342 m_fillCompletion = 100;
Chris@0 1343 }
Chris@0 1344
Chris@0 1345 m_layer.m_mutex.lock();
Chris@0 1346 }
Chris@0 1347
Chris@0 1348 if (!interrupted) m_layer.m_condition.wait(&m_layer.m_mutex, 2000);
Chris@0 1349 }
Chris@0 1350 }
Chris@0 1351
Chris@0 1352 bool
Chris@0 1353 SpectrogramLayer::getYBinRange(int y, float &q0, float &q1) const
Chris@0 1354 {
Chris@0 1355 int h = m_view->height();
Chris@0 1356 if (y < 0 || y >= h) return false;
Chris@0 1357
Chris@0 1358 // Each pixel in a column is drawn from a possibly non-
Chris@0 1359 // integral set of frequency bins.
Chris@0 1360
Chris@0 1361 if (m_frequencyScale == LinearFrequencyScale) {
Chris@0 1362
Chris@0 1363 size_t bins = m_windowSize / 2;
Chris@0 1364
Chris@0 1365 if (m_maxFrequency > 0) {
Chris@0 1366 int sr = m_model->getSampleRate();
Chris@0 1367 bins = int((double(m_maxFrequency) * m_windowSize) / sr + 0.1);
Chris@0 1368 if (bins > m_windowSize / 2) bins = m_windowSize / 2;
Chris@0 1369 }
Chris@0 1370
Chris@0 1371 q0 = float(h - y - 1) * bins / h;
Chris@0 1372 q1 = float(h - y) * bins / h;
Chris@0 1373
Chris@0 1374 } else {
Chris@0 1375
Chris@0 1376 // This is all most ad-hoc. I'm not at my brightest.
Chris@0 1377
Chris@0 1378 int sr = m_model->getSampleRate();
Chris@0 1379
Chris@0 1380 float maxf = m_maxFrequency;
Chris@0 1381 if (maxf == 0.0) maxf = float(sr) / 2;
Chris@0 1382
Chris@0 1383 float minf = float(sr) / m_windowSize;
Chris@0 1384
Chris@0 1385 float maxlogf = log10f(maxf);
Chris@0 1386 float minlogf = log10f(minf);
Chris@35 1387
Chris@0 1388 float logf0 = minlogf + ((maxlogf - minlogf) * (h - y - 1)) / h;
Chris@0 1389 float logf1 = minlogf + ((maxlogf - minlogf) * (h - y)) / h;
Chris@0 1390
Chris@0 1391 float f0 = pow(10.f, logf0);
Chris@0 1392 float f1 = pow(10.f, logf1);
Chris@0 1393
Chris@0 1394 q0 = ((f0 * m_windowSize) / sr) - 1;
Chris@0 1395 q1 = ((f1 * m_windowSize) / sr) - 1;
Chris@0 1396
Chris@0 1397 // std::cout << "y=" << y << " h=" << h << " maxf=" << maxf << " maxlogf="
Chris@0 1398 // << maxlogf << " logf0=" << logf0 << " f0=" << f0 << " q0="
Chris@0 1399 // << q0 << std::endl;
Chris@0 1400 }
Chris@0 1401
Chris@0 1402 return true;
Chris@0 1403 }
Chris@0 1404
Chris@0 1405 bool
Chris@20 1406 SpectrogramLayer::getXBinRange(int x, float &s0, float &s1) const
Chris@0 1407 {
Chris@21 1408 size_t modelStart = m_model->getStartFrame();
Chris@21 1409 size_t modelEnd = m_model->getEndFrame();
Chris@0 1410
Chris@0 1411 // Each pixel column covers an exact range of sample frames:
Chris@20 1412 int f0 = getFrameForX(x) - modelStart;
Chris@20 1413 int f1 = getFrameForX(x + 1) - modelStart - 1;
Chris@20 1414
Chris@0 1415 if (f1 < int(modelStart) || f0 > int(modelEnd)) return false;
Chris@20 1416
Chris@0 1417 // And that range may be drawn from a possibly non-integral
Chris@0 1418 // range of spectrogram windows:
Chris@0 1419
Chris@0 1420 size_t windowIncrement = getWindowIncrement();
Chris@0 1421 s0 = float(f0) / windowIncrement;
Chris@0 1422 s1 = float(f1) / windowIncrement;
Chris@0 1423
Chris@0 1424 return true;
Chris@0 1425 }
Chris@0 1426
Chris@0 1427 bool
Chris@0 1428 SpectrogramLayer::getXBinSourceRange(int x, RealTime &min, RealTime &max) const
Chris@0 1429 {
Chris@0 1430 float s0 = 0, s1 = 0;
Chris@0 1431 if (!getXBinRange(x, s0, s1)) return false;
Chris@0 1432
Chris@0 1433 int s0i = int(s0 + 0.001);
Chris@0 1434 int s1i = int(s1);
Chris@0 1435
Chris@0 1436 int windowIncrement = getWindowIncrement();
Chris@0 1437 int w0 = s0i * windowIncrement - (m_windowSize - windowIncrement)/2;
Chris@0 1438 int w1 = s1i * windowIncrement + windowIncrement +
Chris@0 1439 (m_windowSize - windowIncrement)/2 - 1;
Chris@0 1440
Chris@0 1441 min = RealTime::frame2RealTime(w0, m_model->getSampleRate());
Chris@0 1442 max = RealTime::frame2RealTime(w1, m_model->getSampleRate());
Chris@0 1443 return true;
Chris@0 1444 }
Chris@0 1445
Chris@0 1446 bool
Chris@0 1447 SpectrogramLayer::getYBinSourceRange(int y, float &freqMin, float &freqMax)
Chris@0 1448 const
Chris@0 1449 {
Chris@0 1450 float q0 = 0, q1 = 0;
Chris@0 1451 if (!getYBinRange(y, q0, q1)) return false;
Chris@0 1452
Chris@0 1453 int q0i = int(q0 + 0.001);
Chris@0 1454 int q1i = int(q1);
Chris@0 1455
Chris@0 1456 int sr = m_model->getSampleRate();
Chris@0 1457
Chris@0 1458 for (int q = q0i; q <= q1i; ++q) {
Chris@35 1459 int binfreq = (sr * q) / m_windowSize;
Chris@0 1460 if (q == q0i) freqMin = binfreq;
Chris@0 1461 if (q == q1i) freqMax = binfreq;
Chris@0 1462 }
Chris@0 1463 return true;
Chris@0 1464 }
Chris@35 1465
Chris@35 1466 bool
Chris@35 1467 SpectrogramLayer::getAdjustedYBinSourceRange(int x, int y,
Chris@35 1468 float &freqMin, float &freqMax,
Chris@35 1469 float &adjFreqMin, float &adjFreqMax)
Chris@35 1470 const
Chris@35 1471 {
Chris@35 1472 float s0 = 0, s1 = 0;
Chris@35 1473 if (!getXBinRange(x, s0, s1)) return false;
Chris@35 1474
Chris@35 1475 float q0 = 0, q1 = 0;
Chris@35 1476 if (!getYBinRange(y, q0, q1)) return false;
Chris@35 1477
Chris@35 1478 int s0i = int(s0 + 0.001);
Chris@35 1479 int s1i = int(s1);
Chris@35 1480
Chris@35 1481 int q0i = int(q0 + 0.001);
Chris@35 1482 int q1i = int(q1);
Chris@35 1483
Chris@35 1484 int sr = m_model->getSampleRate();
Chris@35 1485
Chris@35 1486 bool haveAdj = false;
Chris@35 1487
Chris@35 1488 for (int q = q0i; q <= q1i; ++q) {
Chris@35 1489
Chris@35 1490 for (int s = s0i; s <= s1i; ++s) {
Chris@35 1491
Chris@35 1492 float binfreq = (sr * q) / m_windowSize;
Chris@35 1493 if (q == q0i) freqMin = binfreq;
Chris@35 1494 if (q == q1i) freqMax = binfreq;
Chris@35 1495
Chris@35 1496 if ((m_frequencyAdjustment == PhaseAdjustedFrequency ||
Chris@35 1497 m_frequencyAdjustment == PhaseAdjustedPeaks) &&
Chris@35 1498 m_phaseAdjustCache) {
Chris@35 1499
Chris@35 1500 unsigned char cadj = m_phaseAdjustCache->getValueAt(s, q);
Chris@35 1501 int adjust = int((signed char)cadj);
Chris@35 1502 if (adjust == SCHAR_MIN &&
Chris@35 1503 m_frequencyAdjustment == PhaseAdjustedPeaks) {
Chris@35 1504 continue;
Chris@35 1505 }
Chris@35 1506
Chris@35 1507 float nextBinFreq = (sr * (q + 1)) / m_windowSize;
Chris@35 1508 float fadjust = (adjust * (nextBinFreq - binfreq)) / 10.0;//!!!
Chris@35 1509 float f = binfreq + fadjust;
Chris@35 1510 if (!haveAdj || f < adjFreqMin) adjFreqMin = f;
Chris@35 1511 if (!haveAdj || f > adjFreqMax) adjFreqMax = f;
Chris@35 1512 haveAdj = true;
Chris@35 1513 }
Chris@35 1514 }
Chris@35 1515 }
Chris@35 1516
Chris@35 1517 if (!haveAdj) {
Chris@35 1518 adjFreqMin = adjFreqMax = 0.0f;
Chris@35 1519 }
Chris@35 1520
Chris@35 1521 return haveAdj;
Chris@35 1522 }
Chris@0 1523
Chris@0 1524 bool
Chris@0 1525 SpectrogramLayer::getXYBinSourceRange(int x, int y, float &dbMin, float &dbMax) const
Chris@0 1526 {
Chris@0 1527 float q0 = 0, q1 = 0;
Chris@0 1528 if (!getYBinRange(y, q0, q1)) return false;
Chris@0 1529
Chris@0 1530 float s0 = 0, s1 = 0;
Chris@0 1531 if (!getXBinRange(x, s0, s1)) return false;
Chris@0 1532
Chris@0 1533 int q0i = int(q0 + 0.001);
Chris@0 1534 int q1i = int(q1);
Chris@0 1535
Chris@0 1536 int s0i = int(s0 + 0.001);
Chris@0 1537 int s1i = int(s1);
Chris@0 1538
Chris@0 1539 if (m_mutex.tryLock()) {
Chris@0 1540 if (m_cache && !m_cacheInvalid) {
Chris@0 1541
Chris@31 1542 int cw = m_cache->getWidth();
Chris@31 1543 int ch = m_cache->getHeight();
Chris@0 1544
Chris@0 1545 int min = -1, max = -1;
Chris@0 1546
Chris@0 1547 for (int q = q0i; q <= q1i; ++q) {
Chris@0 1548 for (int s = s0i; s <= s1i; ++s) {
Chris@0 1549 if (s >= 0 && q >= 0 && s < cw && q < ch) {
Chris@31 1550 int value = int(m_cache->getValueAt(s, q));
Chris@0 1551 if (min == -1 || value < min) min = value;
Chris@0 1552 if (max == -1 || value > max) max = value;
Chris@0 1553 }
Chris@0 1554 }
Chris@0 1555 }
Chris@0 1556
Chris@0 1557 if (min < 0) return false;
Chris@0 1558
Chris@0 1559 dbMin = (float(min) / 256.0) * 80.0 - 80.0;
Chris@0 1560 dbMax = (float(max + 1) / 256.0) * 80.0 - 80.1;
Chris@0 1561
Chris@0 1562 m_mutex.unlock();
Chris@0 1563 return true;
Chris@0 1564 }
Chris@0 1565
Chris@0 1566 m_mutex.unlock();
Chris@0 1567 }
Chris@0 1568
Chris@0 1569 return false;
Chris@0 1570 }
Chris@0 1571
Chris@0 1572 void
Chris@0 1573 SpectrogramLayer::paint(QPainter &paint, QRect rect) const
Chris@0 1574 {
Chris@0 1575 // Profiler profiler("SpectrogramLayer::paint", true);
Chris@0 1576 #ifdef DEBUG_SPECTROGRAM_REPAINT
Chris@0 1577 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 1578 #endif
Chris@0 1579
Chris@0 1580 if (!m_model || !m_model->isOK() || !m_model->isReady()) {
Chris@0 1581 return;
Chris@0 1582 }
Chris@0 1583
Chris@29 1584 if (m_dormant) {
Chris@33 1585 std::cerr << "SpectrogramLayer::paint(): Layer is dormant" << std::endl;
Chris@29 1586 return;
Chris@29 1587 }
Chris@29 1588
Chris@0 1589 #ifdef DEBUG_SPECTROGRAM_REPAINT
Chris@0 1590 std::cerr << "SpectrogramLayer::paint(): About to lock" << std::endl;
Chris@0 1591 #endif
Chris@0 1592
Chris@0 1593 /*
Chris@0 1594 if (m_cachedInitialVisibleArea) {
Chris@0 1595 if (!m_mutex.tryLock()) {
Chris@0 1596 m_view->update();
Chris@0 1597 return;
Chris@0 1598 }
Chris@0 1599 } else {
Chris@0 1600 */
Chris@0 1601 m_mutex.lock();
Chris@0 1602 // }
Chris@0 1603
Chris@0 1604 #ifdef DEBUG_SPECTROGRAM_REPAINT
Chris@0 1605 std::cerr << "SpectrogramLayer::paint(): locked" << std::endl;
Chris@0 1606 #endif
Chris@0 1607
Chris@0 1608 if (m_cacheInvalid) { // lock the mutex before checking this
Chris@0 1609 m_mutex.unlock();
Chris@0 1610 #ifdef DEBUG_SPECTROGRAM_REPAINT
Chris@0 1611 std::cerr << "SpectrogramLayer::paint(): Cache invalid, returning" << std::endl;
Chris@0 1612 #endif
Chris@0 1613 return;
Chris@0 1614 }
Chris@0 1615
Chris@0 1616 bool stillCacheing = (m_updateTimer != 0);
Chris@0 1617
Chris@0 1618 #ifdef DEBUG_SPECTROGRAM_REPAINT
Chris@0 1619 std::cerr << "SpectrogramLayer::paint(): Still cacheing = " << stillCacheing << std::endl;
Chris@0 1620 #endif
Chris@0 1621
Chris@0 1622 long startFrame = m_view->getStartFrame();
Chris@0 1623 int zoomLevel = m_view->getZoomLevel();
Chris@0 1624
Chris@0 1625 int x0 = 0;
Chris@0 1626 int x1 = m_view->width();
Chris@0 1627 int y0 = 0;
Chris@0 1628 int y1 = m_view->height();
Chris@0 1629
Chris@0 1630 bool recreateWholePixmapCache = true;
Chris@0 1631
Chris@0 1632 if (!m_pixmapCacheInvalid) {
Chris@0 1633
Chris@0 1634 //!!! This cache may have been obsoleted entirely by the
Chris@0 1635 //scrolling cache in View. Perhaps experiment with
Chris@0 1636 //removing it and see if it makes things even quicker (or else
Chris@0 1637 //make it optional)
Chris@0 1638
Chris@0 1639 if (int(m_pixmapCacheZoomLevel) == zoomLevel &&
Chris@0 1640 m_pixmapCache->width() == m_view->width() &&
Chris@0 1641 m_pixmapCache->height() == m_view->height()) {
Chris@0 1642
Chris@20 1643 if (getXForFrame(m_pixmapCacheStartFrame) ==
Chris@20 1644 getXForFrame(startFrame)) {
Chris@0 1645
Chris@0 1646 #ifdef DEBUG_SPECTROGRAM_REPAINT
Chris@0 1647 std::cerr << "SpectrogramLayer: pixmap cache good" << std::endl;
Chris@0 1648 #endif
Chris@0 1649
Chris@0 1650 m_mutex.unlock();
Chris@0 1651 paint.drawPixmap(rect, *m_pixmapCache, rect);
Chris@0 1652 return;
Chris@0 1653
Chris@0 1654 } else {
Chris@0 1655
Chris@0 1656 #ifdef DEBUG_SPECTROGRAM_REPAINT
Chris@0 1657 std::cerr << "SpectrogramLayer: pixmap cache partially OK" << std::endl;
Chris@0 1658 #endif
Chris@0 1659
Chris@0 1660 recreateWholePixmapCache = false;
Chris@0 1661
Chris@20 1662 int dx = getXForFrame(m_pixmapCacheStartFrame) -
Chris@20 1663 getXForFrame(startFrame);
Chris@0 1664
Chris@0 1665 #ifdef DEBUG_SPECTROGRAM_REPAINT
Chris@0 1666 std::cerr << "SpectrogramLayer: dx = " << dx << " (pixmap cache " << m_pixmapCache->width() << "x" << m_pixmapCache->height() << ")" << std::endl;
Chris@0 1667 #endif
Chris@0 1668
Chris@0 1669 if (dx > -m_pixmapCache->width() && dx < m_pixmapCache->width()) {
Chris@0 1670
Chris@0 1671 #if defined(Q_WS_WIN32) || defined(Q_WS_MAC)
Chris@0 1672 // Copying a pixmap to itself doesn't work
Chris@0 1673 // properly on Windows or Mac (it only works when
Chris@0 1674 // moving in one direction).
Chris@0 1675
Chris@0 1676 //!!! Need a utility function for this
Chris@0 1677
Chris@0 1678 static QPixmap *tmpPixmap = 0;
Chris@0 1679 if (!tmpPixmap ||
Chris@0 1680 tmpPixmap->width() != m_pixmapCache->width() ||
Chris@0 1681 tmpPixmap->height() != m_pixmapCache->height()) {
Chris@0 1682 delete tmpPixmap;
Chris@0 1683 tmpPixmap = new QPixmap(m_pixmapCache->width(),
Chris@0 1684 m_pixmapCache->height());
Chris@0 1685 }
Chris@0 1686 QPainter cachePainter;
Chris@0 1687 cachePainter.begin(tmpPixmap);
Chris@0 1688 cachePainter.drawPixmap(0, 0, *m_pixmapCache);
Chris@0 1689 cachePainter.end();
Chris@0 1690 cachePainter.begin(m_pixmapCache);
Chris@0 1691 cachePainter.drawPixmap(dx, 0, *tmpPixmap);
Chris@0 1692 cachePainter.end();
Chris@0 1693 #else
Chris@0 1694 QPainter cachePainter(m_pixmapCache);
Chris@0 1695 cachePainter.drawPixmap(dx, 0, *m_pixmapCache);
Chris@0 1696 cachePainter.end();
Chris@0 1697 #endif
Chris@0 1698
Chris@0 1699 paint.drawPixmap(rect, *m_pixmapCache, rect);
Chris@0 1700
Chris@0 1701 if (dx < 0) {
Chris@0 1702 x0 = m_pixmapCache->width() + dx;
Chris@0 1703 x1 = m_pixmapCache->width();
Chris@0 1704 } else {
Chris@0 1705 x0 = 0;
Chris@0 1706 x1 = dx;
Chris@0 1707 }
Chris@0 1708 }
Chris@0 1709 }
Chris@0 1710 } else {
Chris@0 1711 #ifdef DEBUG_SPECTROGRAM_REPAINT
Chris@0 1712 std::cerr << "SpectrogramLayer: pixmap cache useless" << std::endl;
Chris@0 1713 #endif
Chris@0 1714 }
Chris@0 1715 }
Chris@0 1716
Chris@0 1717 if (stillCacheing) {
Chris@0 1718 x0 = rect.left();
Chris@0 1719 x1 = rect.right() + 1;
Chris@0 1720 y0 = rect.top();
Chris@0 1721 y1 = rect.bottom() + 1;
Chris@0 1722 }
Chris@0 1723
Chris@0 1724 int w = x1 - x0;
Chris@0 1725 int h = y1 - y0;
Chris@0 1726
Chris@0 1727 // std::cerr << "x0 " << x0 << ", x1 " << x1 << ", w " << w << ", h " << h << std::endl;
Chris@0 1728
Chris@0 1729 QImage scaled(w, h, QImage::Format_RGB32);
Chris@35 1730 scaled.fill(0);
Chris@35 1731
Chris@35 1732 float ymag[h];
Chris@35 1733 float ydiv[h];
Chris@35 1734
Chris@35 1735 size_t bins = m_windowSize / 2;
Chris@35 1736 int sr = m_model->getSampleRate();
Chris@35 1737
Chris@35 1738 if (m_maxFrequency > 0) {
Chris@35 1739 bins = int((double(m_maxFrequency) * m_windowSize) / sr + 0.1);
Chris@35 1740 if (bins > m_windowSize / 2) bins = m_windowSize / 2;
Chris@35 1741 }
Chris@35 1742
Chris@35 1743 float maxFreq = (float(bins) * sr) / m_windowSize;
Chris@0 1744
Chris@0 1745 m_mutex.unlock();
Chris@0 1746
Chris@35 1747 for (int x = 0; x < w; ++x) {
Chris@35 1748
Chris@35 1749 m_mutex.lock();
Chris@35 1750 if (m_cacheInvalid) {
Chris@35 1751 m_mutex.unlock();
Chris@35 1752 break;
Chris@35 1753 }
Chris@35 1754
Chris@35 1755 for (int y = 0; y < h; ++y) {
Chris@35 1756 ymag[y] = 0.0f;
Chris@35 1757 ydiv[y] = 0.0f;
Chris@35 1758 }
Chris@35 1759
Chris@35 1760 float s0 = 0, s1 = 0;
Chris@35 1761
Chris@35 1762 if (!getXBinRange(x0 + x, s0, s1)) {
Chris@35 1763 assert(x <= scaled.width());
Chris@35 1764 for (int y = 0; y < h; ++y) {
Chris@35 1765 scaled.setPixel(x, y, qRgb(0, 0, 0));
Chris@35 1766 }
Chris@35 1767 m_mutex.unlock();
Chris@35 1768 continue;
Chris@35 1769 }
Chris@35 1770
Chris@35 1771 int s0i = int(s0 + 0.001);
Chris@35 1772 int s1i = int(s1);
Chris@35 1773
Chris@35 1774 for (int q = 0; q < bins; ++q) {
Chris@35 1775
Chris@35 1776 for (int s = s0i; s <= s1i; ++s) {
Chris@35 1777
Chris@35 1778 float sprop = 1.0;
Chris@35 1779 if (s == s0i) sprop *= (s + 1) - s0;
Chris@35 1780 if (s == s1i) sprop *= s1 - s;
Chris@35 1781
Chris@35 1782 float f0 = (float(q) * sr) / m_windowSize;
Chris@35 1783 float f1 = (float(q + 1) * sr) / m_windowSize;
Chris@35 1784
Chris@35 1785 if ((m_frequencyAdjustment == PhaseAdjustedFrequency ||
Chris@35 1786 m_frequencyAdjustment == PhaseAdjustedPeaks) &&
Chris@35 1787 m_phaseAdjustCache) {
Chris@35 1788
Chris@35 1789 unsigned char cadj = m_phaseAdjustCache->getValueAt(s, q);
Chris@35 1790 int adjust = int((signed char)cadj);
Chris@35 1791
Chris@35 1792 if (adjust == SCHAR_MIN &&
Chris@35 1793 m_frequencyAdjustment == PhaseAdjustedPeaks) {
Chris@35 1794 continue;
Chris@35 1795 }
Chris@35 1796
Chris@35 1797 float fadjust = (adjust * (f1 - f0)) / 10.0;//!!! was 100
Chris@35 1798 f0 = f1 = f0 + fadjust;
Chris@35 1799 }
Chris@35 1800
Chris@35 1801 float y0 = h - (h * f1) / maxFreq;
Chris@35 1802 float y1 = h - (h * f0) / maxFreq;
Chris@35 1803
Chris@35 1804 if (m_frequencyScale == LogFrequencyScale) {
Chris@35 1805
Chris@35 1806 float maxf = m_maxFrequency;
Chris@35 1807 if (maxf == 0.0) maxf = float(sr) / 2;
Chris@35 1808
Chris@35 1809 float minf = float(sr) / m_windowSize;
Chris@35 1810
Chris@35 1811 float maxlogf = log10f(maxf);
Chris@35 1812 float minlogf = log10f(minf);
Chris@35 1813
Chris@35 1814 y0 = h - (h * (log10f(f1) - minlogf)) / (maxlogf - minlogf);
Chris@35 1815 y1 = h - (h * (log10f(f0) - minlogf)) / (maxlogf - minlogf);
Chris@35 1816 }
Chris@35 1817
Chris@35 1818 int y0i = int(y0 + 0.001);
Chris@35 1819 int y1i = int(y1);
Chris@35 1820
Chris@35 1821 for (int y = y0i; y <= y1i; ++y) {
Chris@35 1822
Chris@35 1823 if (y < 0 || y >= h) continue;
Chris@35 1824
Chris@35 1825 float yprop = sprop;
Chris@35 1826 if (y == y0i) yprop *= (y + 1) - y0;
Chris@35 1827 if (y == y1i) yprop *= y1 - y;
Chris@35 1828
Chris@35 1829 ymag[y] += yprop * m_cache->getValueAt(s, q);
Chris@35 1830 ydiv[y] += yprop;
Chris@35 1831 }
Chris@35 1832 }
Chris@35 1833 }
Chris@35 1834
Chris@35 1835 for (int y = 0; y < h; ++y) {
Chris@35 1836
Chris@35 1837 int pixel = 1;
Chris@35 1838
Chris@35 1839 if (ydiv[y] > 0.0) {
Chris@35 1840 pixel = int(ymag[y] / ydiv[y]);
Chris@35 1841 if (pixel > 255) pixel = 255;
Chris@35 1842 if (pixel < 1) pixel = 1;
Chris@35 1843 }
Chris@35 1844
Chris@35 1845 assert(x <= scaled.width());
Chris@35 1846 QColor c = m_cache->getColour(pixel);
Chris@35 1847 scaled.setPixel(x, y,
Chris@35 1848 qRgb(c.red(), c.green(), c.blue()));
Chris@35 1849 }
Chris@35 1850
Chris@35 1851
Chris@35 1852 m_mutex.unlock();
Chris@35 1853 }
Chris@35 1854
Chris@35 1855 #ifdef NOT_DEFINED
Chris@0 1856 for (int y = 0; y < h; ++y) {
Chris@0 1857
Chris@0 1858 m_mutex.lock();
Chris@0 1859 if (m_cacheInvalid) {
Chris@0 1860 m_mutex.unlock();
Chris@0 1861 break;
Chris@0 1862 }
Chris@0 1863
Chris@31 1864 int cw = m_cache->getWidth();
Chris@31 1865 int ch = m_cache->getHeight();
Chris@0 1866
Chris@0 1867 float q0 = 0, q1 = 0;
Chris@0 1868
Chris@0 1869 if (!getYBinRange(y0 + y, q0, q1)) {
Chris@0 1870 for (int x = 0; x < w; ++x) {
Chris@0 1871 assert(x <= scaled.width());
Chris@0 1872 scaled.setPixel(x, y, qRgb(0, 0, 0));
Chris@0 1873 }
Chris@0 1874 m_mutex.unlock();
Chris@0 1875 continue;
Chris@0 1876 }
Chris@0 1877
Chris@0 1878 int q0i = int(q0 + 0.001);
Chris@0 1879 int q1i = int(q1);
Chris@0 1880
Chris@0 1881 for (int x = 0; x < w; ++x) {
Chris@0 1882
Chris@0 1883 float s0 = 0, s1 = 0;
Chris@0 1884
Chris@20 1885 if (!getXBinRange(x0 + x, s0, s1)) {
Chris@0 1886 assert(x <= scaled.width());
Chris@0 1887 scaled.setPixel(x, y, qRgb(0, 0, 0));
Chris@0 1888 continue;
Chris@0 1889 }
Chris@0 1890
Chris@0 1891 int s0i = int(s0 + 0.001);
Chris@0 1892 int s1i = int(s1);
Chris@0 1893
Chris@0 1894 float total = 0, divisor = 0;
Chris@0 1895
Chris@0 1896 for (int s = s0i; s <= s1i; ++s) {
Chris@0 1897
Chris@0 1898 float sprop = 1.0;
Chris@0 1899 if (s == s0i) sprop *= (s + 1) - s0;
Chris@0 1900 if (s == s1i) sprop *= s1 - s;
Chris@0 1901
Chris@0 1902 for (int q = q0i; q <= q1i; ++q) {
Chris@0 1903
Chris@0 1904 float qprop = sprop;
Chris@0 1905 if (q == q0i) qprop *= (q + 1) - q0;
Chris@0 1906 if (q == q1i) qprop *= q1 - q;
Chris@0 1907
Chris@0 1908 if (s >= 0 && q >= 0 && s < cw && q < ch) {
Chris@31 1909 total += qprop * m_cache->getValueAt(s, q);
Chris@0 1910 divisor += qprop;
Chris@0 1911 }
Chris@0 1912 }
Chris@0 1913 }
Chris@0 1914
Chris@0 1915 if (divisor > 0.0) {
Chris@0 1916 int pixel = int(total / divisor);
Chris@0 1917 if (pixel > 255) pixel = 255;
Chris@0 1918 if (pixel < 1) pixel = 1;
Chris@0 1919 assert(x <= scaled.width());
Chris@31 1920 QColor c = m_cache->getColour(pixel);
Chris@31 1921 scaled.setPixel(x, y,
Chris@31 1922 qRgb(c.red(), c.green(), c.blue()));
Chris@31 1923 /*
Chris@9 1924 float pixel = total / divisor;
Chris@9 1925 float lq = pixel - int(pixel);
Chris@9 1926 float hq = int(pixel) + 1 - pixel;
Chris@9 1927 int pixNum = int(pixel);
Chris@9 1928 QRgb low = m_cache->color(pixNum > 255 ? 255 : pixNum);
Chris@9 1929 QRgb high = m_cache->color(pixNum > 254 ? 255 : pixNum + 1);
Chris@9 1930 QRgb mixed = qRgb
Chris@9 1931 (qRed(low) * lq + qRed(high) * hq + 0.01,
Chris@9 1932 qGreen(low) * lq + qGreen(high) * hq + 0.01,
Chris@9 1933 qBlue(low) * lq + qBlue(high) * hq + 0.01);
Chris@9 1934 scaled.setPixel(x, y, mixed);
Chris@31 1935 */
Chris@0 1936 } else {
Chris@0 1937 assert(x <= scaled.width());
Chris@0 1938 scaled.setPixel(x, y, qRgb(0, 0, 0));
Chris@0 1939 }
Chris@0 1940 }
Chris@0 1941
Chris@0 1942 m_mutex.unlock();
Chris@0 1943 }
Chris@35 1944 #endif
Chris@0 1945
Chris@0 1946 paint.drawImage(x0, y0, scaled);
Chris@0 1947
Chris@0 1948 if (recreateWholePixmapCache) {
Chris@0 1949 delete m_pixmapCache;
Chris@0 1950 m_pixmapCache = new QPixmap(w, h);
Chris@0 1951 }
Chris@0 1952
Chris@0 1953 QPainter cachePainter(m_pixmapCache);
Chris@0 1954 cachePainter.drawImage(x0, y0, scaled);
Chris@0 1955 cachePainter.end();
Chris@0 1956
Chris@0 1957 m_pixmapCacheInvalid = false;
Chris@0 1958 m_pixmapCacheStartFrame = startFrame;
Chris@0 1959 m_pixmapCacheZoomLevel = zoomLevel;
Chris@0 1960
Chris@0 1961 #ifdef DEBUG_SPECTROGRAM_REPAINT
Chris@0 1962 std::cerr << "SpectrogramLayer::paint() returning" << std::endl;
Chris@0 1963 #endif
Chris@0 1964 }
Chris@0 1965
Chris@0 1966 int
Chris@0 1967 SpectrogramLayer::getCompletion() const
Chris@0 1968 {
Chris@0 1969 if (m_updateTimer == 0) return 100;
Chris@0 1970 size_t completion = m_fillThread->getFillCompletion();
Chris@0 1971 // std::cerr << "SpectrogramLayer::getCompletion: completion = " << completion << std::endl;
Chris@0 1972 return completion;
Chris@0 1973 }
Chris@0 1974
Chris@28 1975 bool
Chris@28 1976 SpectrogramLayer::snapToFeatureFrame(int &frame,
Chris@28 1977 size_t &resolution,
Chris@28 1978 SnapType snap) const
Chris@13 1979 {
Chris@13 1980 resolution = getWindowIncrement();
Chris@28 1981 int left = (frame / resolution) * resolution;
Chris@28 1982 int right = left + resolution;
Chris@28 1983
Chris@28 1984 switch (snap) {
Chris@28 1985 case SnapLeft: frame = left; break;
Chris@28 1986 case SnapRight: frame = right; break;
Chris@28 1987 case SnapNearest:
Chris@28 1988 case SnapNeighbouring:
Chris@28 1989 if (frame - left > right - frame) frame = right;
Chris@28 1990 else frame = left;
Chris@28 1991 break;
Chris@28 1992 }
Chris@28 1993
Chris@28 1994 return true;
Chris@28 1995 }
Chris@13 1996
Chris@25 1997 QString
Chris@25 1998 SpectrogramLayer::getFeatureDescription(QPoint &pos) const
Chris@25 1999 {
Chris@25 2000 int x = pos.x();
Chris@25 2001 int y = pos.y();
Chris@0 2002
Chris@25 2003 if (!m_model || !m_model->isOK()) return "";
Chris@0 2004
Chris@0 2005 float dbMin = 0, dbMax = 0;
Chris@0 2006 float freqMin = 0, freqMax = 0;
Chris@35 2007 float adjFreqMin = 0, adjFreqMax = 0;
Chris@25 2008 QString pitchMin, pitchMax;
Chris@0 2009 RealTime rtMin, rtMax;
Chris@0 2010
Chris@25 2011 bool haveDb = false;
Chris@0 2012
Chris@25 2013 if (!getXBinSourceRange(x, rtMin, rtMax)) return "";
Chris@25 2014 if (getXYBinSourceRange(x, y, dbMin, dbMax)) haveDb = true;
Chris@0 2015
Chris@35 2016 QString adjFreqText = "", adjPitchText = "";
Chris@35 2017
Chris@35 2018 if ((m_frequencyAdjustment == PhaseAdjustedFrequency ||
Chris@35 2019 m_frequencyAdjustment == PhaseAdjustedPeaks) &&
Chris@35 2020 m_phaseAdjustCache) {
Chris@35 2021
Chris@35 2022 if (!getAdjustedYBinSourceRange(x, y, freqMin, freqMax,
Chris@35 2023 adjFreqMin, adjFreqMax)) return "";
Chris@35 2024
Chris@35 2025 if (adjFreqMin != adjFreqMax) {
Chris@35 2026 adjFreqText = tr("Adjusted Frequency:\t%1 - %2 Hz\n")
Chris@35 2027 .arg(adjFreqMin).arg(adjFreqMax);
Chris@35 2028 adjPitchText = tr("Adjusted Pitch:\t%3 - %4\n")
Chris@35 2029 .arg(Pitch::getPitchLabelForFrequency(adjFreqMin))
Chris@35 2030 .arg(Pitch::getPitchLabelForFrequency(adjFreqMax));
Chris@35 2031 } else {
Chris@35 2032 adjFreqText = tr("Adjusted Frequency:\t%1 Hz\n")
Chris@35 2033 .arg(adjFreqMin);
Chris@35 2034 adjPitchText = tr("Adjusted Pitch:\t%2\n")
Chris@35 2035 .arg(Pitch::getPitchLabelForFrequency(adjFreqMin));
Chris@35 2036 }
Chris@35 2037
Chris@35 2038 } else {
Chris@35 2039
Chris@35 2040 if (!getYBinSourceRange(y, freqMin, freqMax)) return "";
Chris@35 2041 }
Chris@35 2042
Chris@25 2043 //!!! want to actually do a one-off FFT to recalculate the dB value!
Chris@25 2044
Chris@25 2045 QString text;
Chris@25 2046
Chris@25 2047 if (rtMin != rtMax) {
Chris@25 2048 text += tr("Time:\t%1 - %2\n")
Chris@25 2049 .arg(rtMin.toText(true).c_str())
Chris@25 2050 .arg(rtMax.toText(true).c_str());
Chris@25 2051 } else {
Chris@25 2052 text += tr("Time:\t%1\n")
Chris@25 2053 .arg(rtMin.toText(true).c_str());
Chris@0 2054 }
Chris@0 2055
Chris@25 2056 if (freqMin != freqMax) {
Chris@35 2057 text += tr("Frequency:\t%1 - %2 Hz\n%3Pitch:\t%4 - %5\n%6")
Chris@25 2058 .arg(freqMin)
Chris@25 2059 .arg(freqMax)
Chris@35 2060 .arg(adjFreqText)
Chris@25 2061 .arg(Pitch::getPitchLabelForFrequency(freqMin))
Chris@35 2062 .arg(Pitch::getPitchLabelForFrequency(freqMax))
Chris@35 2063 .arg(adjPitchText);
Chris@25 2064 } else {
Chris@35 2065 text += tr("Frequency:\t%1 Hz\n%2Pitch:\t%3\n%4")
Chris@25 2066 .arg(freqMin)
Chris@35 2067 .arg(adjFreqText)
Chris@35 2068 .arg(Pitch::getPitchLabelForFrequency(freqMin))
Chris@35 2069 .arg(adjPitchText);
Chris@25 2070 }
Chris@25 2071
Chris@25 2072 if (haveDb) {
Chris@25 2073 if (lrintf(dbMin) != lrintf(dbMax)) {
Chris@25 2074 text += tr("dB:\t%1 - %2").arg(lrintf(dbMin)).arg(lrintf(dbMax));
Chris@25 2075 } else {
Chris@25 2076 text += tr("dB:\t%1").arg(lrintf(dbMin));
Chris@25 2077 }
Chris@25 2078 }
Chris@25 2079
Chris@25 2080 return text;
Chris@0 2081 }
Chris@25 2082
Chris@0 2083 int
Chris@0 2084 SpectrogramLayer::getVerticalScaleWidth(QPainter &paint) const
Chris@0 2085 {
Chris@0 2086 if (!m_model || !m_model->isOK()) return 0;
Chris@0 2087
Chris@0 2088 int tw = paint.fontMetrics().width(QString("%1")
Chris@0 2089 .arg(m_maxFrequency > 0 ?
Chris@0 2090 m_maxFrequency - 1 :
Chris@0 2091 m_model->getSampleRate() / 2));
Chris@0 2092
Chris@0 2093 int fw = paint.fontMetrics().width(QString("43Hz"));
Chris@0 2094 if (tw < fw) tw = fw;
Chris@0 2095
Chris@0 2096 return tw + 13;
Chris@0 2097 }
Chris@0 2098
Chris@0 2099 void
Chris@0 2100 SpectrogramLayer::paintVerticalScale(QPainter &paint, QRect rect) const
Chris@0 2101 {
Chris@0 2102 if (!m_model || !m_model->isOK()) {
Chris@0 2103 return;
Chris@0 2104 }
Chris@0 2105
Chris@0 2106 int h = rect.height(), w = rect.width();
Chris@0 2107
Chris@0 2108 size_t bins = m_windowSize / 2;
Chris@0 2109 int sr = m_model->getSampleRate();
Chris@0 2110
Chris@0 2111 if (m_maxFrequency > 0) {
Chris@0 2112 bins = int((double(m_maxFrequency) * m_windowSize) / sr + 0.1);
Chris@0 2113 if (bins > m_windowSize / 2) bins = m_windowSize / 2;
Chris@0 2114 }
Chris@0 2115
Chris@0 2116 int py = -1;
Chris@0 2117 int textHeight = paint.fontMetrics().height();
Chris@0 2118 int toff = -textHeight + paint.fontMetrics().ascent() + 2;
Chris@0 2119
Chris@0 2120 int bin = -1;
Chris@0 2121
Chris@0 2122 for (int y = 0; y < m_view->height(); ++y) {
Chris@0 2123
Chris@0 2124 float q0, q1;
Chris@0 2125 if (!getYBinRange(m_view->height() - y, q0, q1)) continue;
Chris@0 2126
Chris@0 2127 int vy;
Chris@0 2128
Chris@0 2129 if (int(q0) > bin) {
Chris@0 2130 vy = y;
Chris@0 2131 bin = int(q0);
Chris@0 2132 } else {
Chris@0 2133 continue;
Chris@0 2134 }
Chris@0 2135
Chris@0 2136 int freq = (sr * (bin + 1)) / m_windowSize;
Chris@0 2137
Chris@0 2138 if (py >= 0 && (vy - py) < textHeight - 1) {
Chris@0 2139 paint.drawLine(w - 4, h - vy, w, h - vy);
Chris@0 2140 continue;
Chris@0 2141 }
Chris@0 2142
Chris@0 2143 QString text = QString("%1").arg(freq);
Chris@0 2144 if (bin == 0) text = QString("%1Hz").arg(freq);
Chris@0 2145 paint.drawLine(0, h - vy, w, h - vy);
Chris@0 2146
Chris@0 2147 if (h - vy - textHeight >= -2) {
Chris@0 2148 int tx = w - 10 - paint.fontMetrics().width(text);
Chris@0 2149 paint.drawText(tx, h - vy + toff, text);
Chris@0 2150 }
Chris@0 2151
Chris@0 2152 py = vy;
Chris@0 2153 }
Chris@0 2154 }
Chris@0 2155
Chris@6 2156 QString
Chris@6 2157 SpectrogramLayer::toXmlString(QString indent, QString extraAttributes) const
Chris@6 2158 {
Chris@6 2159 QString s;
Chris@6 2160
Chris@6 2161 s += QString("channel=\"%1\" "
Chris@6 2162 "windowSize=\"%2\" "
Chris@6 2163 "windowType=\"%3\" "
Chris@6 2164 "windowOverlap=\"%4\" "
Chris@35 2165 "gain=\"%5\" ")
Chris@6 2166 .arg(m_channel)
Chris@6 2167 .arg(m_windowSize)
Chris@6 2168 .arg(m_windowType)
Chris@6 2169 .arg(m_windowOverlap)
Chris@35 2170 .arg(m_gain);
Chris@35 2171
Chris@35 2172 s += QString("maxFrequency=\"%1\" "
Chris@35 2173 "colourScale=\"%2\" "
Chris@35 2174 "colourScheme=\"%3\" "
Chris@35 2175 "frequencyScale=\"%4\" "
Chris@35 2176 "frequencyAdjustment=\"%5\"")
Chris@6 2177 .arg(m_maxFrequency)
Chris@6 2178 .arg(m_colourScale)
Chris@6 2179 .arg(m_colourScheme)
Chris@35 2180 .arg(m_frequencyScale)
Chris@35 2181 .arg(m_frequencyAdjustment);
Chris@6 2182
Chris@6 2183 return Layer::toXmlString(indent, extraAttributes + " " + s);
Chris@6 2184 }
Chris@6 2185
Chris@11 2186 void
Chris@11 2187 SpectrogramLayer::setProperties(const QXmlAttributes &attributes)
Chris@11 2188 {
Chris@11 2189 bool ok = false;
Chris@11 2190
Chris@11 2191 int channel = attributes.value("channel").toInt(&ok);
Chris@11 2192 if (ok) setChannel(channel);
Chris@11 2193
Chris@11 2194 size_t windowSize = attributes.value("windowSize").toUInt(&ok);
Chris@11 2195 if (ok) setWindowSize(windowSize);
Chris@11 2196
Chris@11 2197 WindowType windowType = (WindowType)
Chris@11 2198 attributes.value("windowType").toInt(&ok);
Chris@11 2199 if (ok) setWindowType(windowType);
Chris@11 2200
Chris@11 2201 size_t windowOverlap = attributes.value("windowOverlap").toUInt(&ok);
Chris@11 2202 if (ok) setWindowOverlap(windowOverlap);
Chris@11 2203
Chris@11 2204 float gain = attributes.value("gain").toFloat(&ok);
Chris@11 2205 if (ok) setGain(gain);
Chris@11 2206
Chris@11 2207 size_t maxFrequency = attributes.value("maxFrequency").toUInt(&ok);
Chris@11 2208 if (ok) setMaxFrequency(maxFrequency);
Chris@11 2209
Chris@11 2210 ColourScale colourScale = (ColourScale)
Chris@11 2211 attributes.value("colourScale").toInt(&ok);
Chris@11 2212 if (ok) setColourScale(colourScale);
Chris@11 2213
Chris@11 2214 ColourScheme colourScheme = (ColourScheme)
Chris@11 2215 attributes.value("colourScheme").toInt(&ok);
Chris@11 2216 if (ok) setColourScheme(colourScheme);
Chris@11 2217
Chris@11 2218 FrequencyScale frequencyScale = (FrequencyScale)
Chris@11 2219 attributes.value("frequencyScale").toInt(&ok);
Chris@11 2220 if (ok) setFrequencyScale(frequencyScale);
Chris@35 2221
Chris@35 2222 FrequencyAdjustment frequencyAdjustment = (FrequencyAdjustment)
Chris@35 2223 attributes.value("frequencyAdjustment").toInt(&ok);
Chris@35 2224 if (ok) setFrequencyAdjustment(frequencyAdjustment);
Chris@11 2225 }
Chris@11 2226
Chris@11 2227
Chris@0 2228 #ifdef INCLUDE_MOCFILES
Chris@0 2229 #include "SpectrogramLayer.moc.cpp"
Chris@0 2230 #endif
Chris@0 2231