annotate layer/SpectrogramLayer.cpp @ 37:21d061e66177

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