annotate layer/SpectrogramLayer.cpp @ 36:c28ebb4ba4de

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