annotate layer/SpectrumLayer.cpp @ 1025:c02de0e34233 spectrogram-minor-refactor

Make paint method do its own timing and abandon if it's taking too long (still needs some work on queueing correct repaints of the remainder)
author Chris Cannam
date Mon, 25 Jan 2016 16:16:21 +0000
parents 64c2b3a4435a
children ee01a4062747
rev   line source
Chris@133 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@133 2
Chris@133 3 /*
Chris@133 4 Sonic Visualiser
Chris@133 5 An audio file viewer and annotation editor.
Chris@133 6 Centre for Digital Music, Queen Mary, University of London.
Chris@195 7 This file copyright 2006-2007 QMUL.
Chris@133 8
Chris@133 9 This program is free software; you can redistribute it and/or
Chris@133 10 modify it under the terms of the GNU General Public License as
Chris@133 11 published by the Free Software Foundation; either version 2 of the
Chris@133 12 License, or (at your option) any later version. See the file
Chris@133 13 COPYING included with this distribution for more information.
Chris@133 14 */
Chris@133 15
Chris@133 16 #include "SpectrumLayer.h"
Chris@133 17
Chris@133 18 #include "data/model/FFTModel.h"
Chris@133 19 #include "view/View.h"
Chris@153 20 #include "base/AudioLevel.h"
Chris@153 21 #include "base/Preferences.h"
Chris@167 22 #include "base/RangeMapper.h"
Chris@277 23 #include "base/Pitch.h"
Chris@376 24 #include "ColourMapper.h"
Chris@254 25
Chris@254 26 #include <QPainter>
Chris@316 27 #include <QTextStream>
Chris@316 28
Chris@133 29
Chris@133 30 SpectrumLayer::SpectrumLayer() :
Chris@193 31 m_originModel(0),
Chris@153 32 m_channel(-1),
Chris@153 33 m_channelSet(false),
Chris@290 34 m_windowSize(4096),
Chris@153 35 m_windowType(HanningWindow),
Chris@290 36 m_windowHopLevel(3),
Chris@284 37 m_showPeaks(false),
Chris@275 38 m_newFFTNeeded(true)
Chris@133 39 {
Chris@153 40 Preferences *prefs = Preferences::getInstance();
Chris@153 41 connect(prefs, SIGNAL(propertyChanged(PropertyContainer::PropertyName)),
Chris@153 42 this, SLOT(preferenceChanged(PropertyContainer::PropertyName)));
Chris@153 43 setWindowType(prefs->getWindowType());
Chris@195 44
Chris@195 45 setBinScale(LogBins);
Chris@133 46 }
Chris@133 47
Chris@133 48 SpectrumLayer::~SpectrumLayer()
Chris@133 49 {
Chris@349 50 Model *m = const_cast<Model *>
Chris@349 51 (static_cast<const Model *>(m_sliceableModel));
Chris@458 52 if (m) m->aboutToDelete();
Chris@349 53 m_sliceableModel = 0;
Chris@349 54 delete m;
Chris@133 55 }
Chris@133 56
Chris@133 57 void
Chris@133 58 SpectrumLayer::setModel(DenseTimeValueModel *model)
Chris@133 59 {
Chris@587 60 SVDEBUG << "SpectrumLayer::setModel(" << model << ") from " << m_originModel << endl;
Chris@345 61
Chris@193 62 if (m_originModel == model) return;
Chris@349 63
Chris@193 64 m_originModel = model;
Chris@277 65
Chris@350 66 if (m_sliceableModel) {
Chris@350 67 Model *m = const_cast<Model *>
Chris@350 68 (static_cast<const Model *>(m_sliceableModel));
Chris@350 69 m->aboutToDelete();
Chris@350 70 setSliceableModel(0);
Chris@350 71 delete m;
Chris@350 72 }
Chris@350 73
Chris@349 74 m_newFFTNeeded = true;
Chris@349 75
Chris@349 76 emit layerParametersChanged();
Chris@349 77 }
Chris@349 78
Chris@349 79 void
Chris@349 80 SpectrumLayer::setChannel(int channel)
Chris@349 81 {
Chris@587 82 SVDEBUG << "SpectrumLayer::setChannel(" << channel << ") from " << m_channel << endl;
Chris@349 83
Chris@349 84 m_channelSet = true;
Chris@349 85
Chris@349 86 if (m_channel == channel) return;
Chris@349 87
Chris@349 88 m_channel = channel;
Chris@349 89
Chris@349 90 m_newFFTNeeded = true;
Chris@349 91
Chris@349 92 emit layerParametersChanged();
Chris@153 93 }
Chris@153 94
Chris@153 95 void
Chris@193 96 SpectrumLayer::setupFFT()
Chris@153 97 {
Chris@349 98 if (m_sliceableModel) {
Chris@349 99 Model *m = const_cast<Model *>
Chris@349 100 (static_cast<const Model *>(m_sliceableModel));
Chris@349 101 m->aboutToDelete();
Chris@193 102 setSliceableModel(0);
Chris@349 103 delete m;
Chris@349 104 }
Chris@349 105
Chris@349 106 if (!m_originModel) {
Chris@349 107 return;
Chris@153 108 }
Chris@153 109
Chris@193 110 FFTModel *newFFT = new FFTModel(m_originModel,
Chris@193 111 m_channel,
Chris@193 112 m_windowType,
Chris@193 113 m_windowSize,
Chris@193 114 getWindowIncrement(),
Chris@975 115 m_windowSize);
Chris@153 116
Chris@193 117 setSliceableModel(newFFT);
Chris@193 118
Chris@254 119 m_biasCurve.clear();
Chris@805 120 for (int i = 0; i < m_windowSize; ++i) {
Chris@254 121 m_biasCurve.push_back(1.f / (float(m_windowSize)/2.f));
Chris@254 122 }
Chris@254 123
Chris@349 124 m_newFFTNeeded = false;
Chris@133 125 }
Chris@133 126
Chris@153 127 Layer::PropertyList
Chris@153 128 SpectrumLayer::getProperties() const
Chris@153 129 {
Chris@193 130 PropertyList list = SliceLayer::getProperties();
Chris@153 131 list.push_back("Window Size");
Chris@153 132 list.push_back("Window Increment");
Chris@284 133 list.push_back("Show Peak Frequencies");
Chris@153 134 return list;
Chris@153 135 }
Chris@153 136
Chris@153 137 QString
Chris@153 138 SpectrumLayer::getPropertyLabel(const PropertyName &name) const
Chris@153 139 {
Chris@153 140 if (name == "Window Size") return tr("Window Size");
Chris@153 141 if (name == "Window Increment") return tr("Window Overlap");
Chris@284 142 if (name == "Show Peak Frequencies") return tr("Show Peak Frequencies");
Chris@193 143 return SliceLayer::getPropertyLabel(name);
Chris@153 144 }
Chris@153 145
Chris@335 146 QString
Chris@335 147 SpectrumLayer::getPropertyIconName(const PropertyName &name) const
Chris@335 148 {
Chris@335 149 if (name == "Show Peak Frequencies") return "show-peaks";
Chris@335 150 return SliceLayer::getPropertyIconName(name);
Chris@335 151 }
Chris@335 152
Chris@153 153 Layer::PropertyType
Chris@153 154 SpectrumLayer::getPropertyType(const PropertyName &name) const
Chris@153 155 {
Chris@193 156 if (name == "Window Size") return ValueProperty;
Chris@193 157 if (name == "Window Increment") return ValueProperty;
Chris@284 158 if (name == "Show Peak Frequencies") return ToggleProperty;
Chris@193 159 return SliceLayer::getPropertyType(name);
Chris@153 160 }
Chris@153 161
Chris@153 162 QString
Chris@153 163 SpectrumLayer::getPropertyGroupName(const PropertyName &name) const
Chris@153 164 {
Chris@153 165 if (name == "Window Size" ||
Chris@153 166 name == "Window Increment") return tr("Window");
Chris@557 167 if (name == "Show Peak Frequencies") return tr("Bins");
Chris@193 168 return SliceLayer::getPropertyGroupName(name);
Chris@153 169 }
Chris@153 170
Chris@153 171 int
Chris@153 172 SpectrumLayer::getPropertyRangeAndValue(const PropertyName &name,
Chris@216 173 int *min, int *max, int *deflt) const
Chris@153 174 {
Chris@216 175 int val = 0;
Chris@153 176
Chris@216 177 int garbage0, garbage1, garbage2;
Chris@153 178 if (!min) min = &garbage0;
Chris@153 179 if (!max) max = &garbage1;
Chris@216 180 if (!deflt) deflt = &garbage2;
Chris@153 181
Chris@193 182 if (name == "Window Size") {
Chris@153 183
Chris@153 184 *min = 0;
Chris@254 185 *max = 15;
Chris@216 186 *deflt = 5;
Chris@153 187
Chris@216 188 val = 0;
Chris@153 189 int ws = m_windowSize;
Chris@216 190 while (ws > 32) { ws >>= 1; val ++; }
Chris@153 191
Chris@153 192 } else if (name == "Window Increment") {
Chris@153 193
Chris@153 194 *min = 0;
Chris@153 195 *max = 5;
Chris@216 196 *deflt = 2;
Chris@153 197
Chris@216 198 val = m_windowHopLevel;
Chris@153 199
Chris@284 200 } else if (name == "Show Peak Frequencies") {
Chris@284 201
Chris@284 202 return m_showPeaks ? 1 : 0;
Chris@284 203
Chris@153 204 } else {
Chris@193 205
Chris@216 206 val = SliceLayer::getPropertyRangeAndValue(name, min, max, deflt);
Chris@153 207 }
Chris@153 208
Chris@216 209 return val;
Chris@153 210 }
Chris@153 211
Chris@153 212 QString
Chris@153 213 SpectrumLayer::getPropertyValueLabel(const PropertyName &name,
Chris@153 214 int value) const
Chris@153 215 {
Chris@153 216 if (name == "Window Size") {
Chris@153 217 return QString("%1").arg(32 << value);
Chris@153 218 }
Chris@153 219 if (name == "Window Increment") {
Chris@153 220 switch (value) {
Chris@153 221 default:
Chris@153 222 case 0: return tr("None");
Chris@153 223 case 1: return tr("25 %");
Chris@153 224 case 2: return tr("50 %");
Chris@153 225 case 3: return tr("75 %");
Chris@153 226 case 4: return tr("87.5 %");
Chris@153 227 case 5: return tr("93.75 %");
Chris@153 228 }
Chris@153 229 }
Chris@193 230 return SliceLayer::getPropertyValueLabel(name, value);
Chris@153 231 }
Chris@153 232
Chris@167 233 RangeMapper *
Chris@167 234 SpectrumLayer::getNewPropertyRangeMapper(const PropertyName &name) const
Chris@167 235 {
Chris@193 236 return SliceLayer::getNewPropertyRangeMapper(name);
Chris@167 237 }
Chris@167 238
Chris@133 239 void
Chris@153 240 SpectrumLayer::setProperty(const PropertyName &name, int value)
Chris@133 241 {
Chris@193 242 if (name == "Window Size") {
Chris@153 243 setWindowSize(32 << value);
Chris@153 244 } else if (name == "Window Increment") {
Chris@153 245 setWindowHopLevel(value);
Chris@284 246 } else if (name == "Show Peak Frequencies") {
Chris@284 247 setShowPeaks(value ? true : false);
Chris@193 248 } else {
Chris@193 249 SliceLayer::setProperty(name, value);
Chris@153 250 }
Chris@153 251 }
Chris@153 252
Chris@153 253 void
Chris@805 254 SpectrumLayer::setWindowSize(int ws)
Chris@153 255 {
Chris@153 256 if (m_windowSize == ws) return;
Chris@153 257 m_windowSize = ws;
Chris@275 258 m_newFFTNeeded = true;
Chris@153 259 emit layerParametersChanged();
Chris@153 260 }
Chris@153 261
Chris@153 262 void
Chris@805 263 SpectrumLayer::setWindowHopLevel(int v)
Chris@153 264 {
Chris@153 265 if (m_windowHopLevel == v) return;
Chris@153 266 m_windowHopLevel = v;
Chris@275 267 m_newFFTNeeded = true;
Chris@153 268 emit layerParametersChanged();
Chris@153 269 }
Chris@153 270
Chris@153 271 void
Chris@153 272 SpectrumLayer::setWindowType(WindowType w)
Chris@153 273 {
Chris@153 274 if (m_windowType == w) return;
Chris@153 275 m_windowType = w;
Chris@275 276 m_newFFTNeeded = true;
Chris@153 277 emit layerParametersChanged();
Chris@153 278 }
Chris@153 279
Chris@153 280 void
Chris@284 281 SpectrumLayer::setShowPeaks(bool show)
Chris@284 282 {
Chris@284 283 if (m_showPeaks == show) return;
Chris@284 284 m_showPeaks = show;
Chris@284 285 emit layerParametersChanged();
Chris@284 286 }
Chris@284 287
Chris@284 288 void
Chris@153 289 SpectrumLayer::preferenceChanged(PropertyContainer::PropertyName name)
Chris@153 290 {
Chris@153 291 if (name == "Window Type") {
Chris@153 292 setWindowType(Preferences::getInstance()->getWindowType());
Chris@153 293 return;
Chris@153 294 }
Chris@153 295 }
Chris@153 296
Chris@133 297 bool
Chris@908 298 SpectrumLayer::getValueExtents(double &, double &, bool &, QString &) const
Chris@133 299 {
Chris@133 300 return false;
Chris@133 301 }
Chris@133 302
Chris@908 303 double
Chris@908 304 SpectrumLayer::getXForBin(int bin, int totalBins, double w) const
Chris@265 305 {
Chris@265 306 if (!m_sliceableModel) return SliceLayer::getXForBin(bin, totalBins, w);
Chris@265 307
Chris@908 308 sv_samplerate_t sampleRate = m_sliceableModel->getSampleRate();
Chris@908 309 double binfreq = (sampleRate * bin) / (totalBins * 2);
Chris@265 310
Chris@265 311 return getXForFrequency(binfreq, w);
Chris@265 312 }
Chris@265 313
Chris@265 314 int
Chris@908 315 SpectrumLayer::getBinForX(double x, int totalBins, double w) const
Chris@265 316 {
Chris@265 317 if (!m_sliceableModel) return SliceLayer::getBinForX(x, totalBins, w);
Chris@265 318
Chris@908 319 sv_samplerate_t sampleRate = m_sliceableModel->getSampleRate();
Chris@908 320 double binfreq = getFrequencyForX(x, w);
Chris@265 321
Chris@265 322 return int((binfreq * totalBins * 2) / sampleRate);
Chris@265 323 }
Chris@265 324
Chris@908 325 double
Chris@908 326 SpectrumLayer::getFrequencyForX(double x, double w) const
Chris@254 327 {
Chris@908 328 double freq = 0;
Chris@280 329 if (!m_sliceableModel) return 0;
Chris@254 330
Chris@908 331 sv_samplerate_t sampleRate = m_sliceableModel->getSampleRate();
Chris@254 332
Chris@908 333 double maxfreq = double(sampleRate) / 2;
Chris@254 334
Chris@254 335 switch (m_binScale) {
Chris@254 336
Chris@254 337 case LinearBins:
Chris@254 338 freq = ((x * maxfreq) / w);
Chris@254 339 break;
Chris@254 340
Chris@254 341 case LogBins:
Chris@908 342 freq = pow(10.0, (x * log10(maxfreq)) / w);
Chris@254 343 break;
Chris@254 344
Chris@254 345 case InvertedLogBins:
Chris@908 346 freq = maxfreq - pow(10.0, ((w - x) * log10(maxfreq)) / w);
Chris@254 347 break;
Chris@254 348 }
Chris@254 349
Chris@254 350 return freq;
Chris@254 351 }
Chris@254 352
Chris@908 353 double
Chris@908 354 SpectrumLayer::getXForFrequency(double freq, double w) const
Chris@254 355 {
Chris@908 356 double x = 0;
Chris@280 357 if (!m_sliceableModel) return x;
Chris@254 358
Chris@908 359 sv_samplerate_t sampleRate = m_sliceableModel->getSampleRate();
Chris@254 360
Chris@908 361 double maxfreq = double(sampleRate) / 2;
Chris@254 362
Chris@254 363 switch (m_binScale) {
Chris@254 364
Chris@254 365 case LinearBins:
Chris@254 366 x = (freq * w) / maxfreq;
Chris@254 367 break;
Chris@254 368
Chris@254 369 case LogBins:
Chris@908 370 x = (log10(freq) * w) / log10(maxfreq);
Chris@254 371 break;
Chris@254 372
Chris@254 373 case InvertedLogBins:
Chris@274 374 if (maxfreq == freq) x = w;
Chris@908 375 else x = w - (log10(maxfreq - freq) * w) / log10(maxfreq);
Chris@254 376 break;
Chris@254 377 }
Chris@254 378
Chris@254 379 return x;
Chris@254 380 }
Chris@254 381
Chris@260 382 bool
Chris@918 383 SpectrumLayer::getXScaleValue(const LayerGeometryProvider *v, int x,
Chris@908 384 double &value, QString &unit) const
Chris@260 385 {
Chris@267 386 if (m_xorigins.find(v) == m_xorigins.end()) return false;
Chris@267 387 int xorigin = m_xorigins.find(v)->second;
Chris@918 388 value = getFrequencyForX(x - xorigin, v->getPaintWidth() - xorigin - 1);
Chris@260 389 unit = "Hz";
Chris@260 390 return true;
Chris@260 391 }
Chris@260 392
Chris@264 393 bool
Chris@918 394 SpectrumLayer::getYScaleValue(const LayerGeometryProvider *v, int y,
Chris@908 395 double &value, QString &unit) const
Chris@274 396 {
Chris@274 397 value = getValueForY(y, v);
Chris@274 398
Chris@274 399 if (m_energyScale == dBScale || m_energyScale == MeterScale) {
Chris@274 400
Chris@908 401 if (value > 0.0) {
Chris@908 402 value = 10.0 * log10(value);
Chris@284 403 if (value < m_threshold) value = m_threshold;
Chris@284 404 } else value = m_threshold;
Chris@274 405
Chris@274 406 unit = "dBV";
Chris@274 407
Chris@274 408 } else {
Chris@274 409 unit = "V";
Chris@274 410 }
Chris@274 411
Chris@274 412 return true;
Chris@274 413 }
Chris@274 414
Chris@274 415 bool
Chris@918 416 SpectrumLayer::getYScaleDifference(const LayerGeometryProvider *v, int y0, int y1,
Chris@908 417 double &diff, QString &unit) const
Chris@274 418 {
Chris@274 419 bool rv = SliceLayer::getYScaleDifference(v, y0, y1, diff, unit);
Chris@274 420 if (rv && (unit == "dBV")) unit = "dB";
Chris@274 421 return rv;
Chris@274 422 }
Chris@274 423
Chris@274 424
Chris@274 425 bool
Chris@918 426 SpectrumLayer::getCrosshairExtents(LayerGeometryProvider *v, QPainter &paint,
Chris@264 427 QPoint cursorPos,
Chris@264 428 std::vector<QRect> &extents) const
Chris@264 429 {
Chris@918 430 QRect vertical(cursorPos.x(), cursorPos.y(), 1, v->getPaintHeight() - cursorPos.y());
Chris@264 431 extents.push_back(vertical);
Chris@264 432
Chris@918 433 QRect horizontal(0, cursorPos.y(), v->getPaintWidth(), 12);
Chris@264 434 extents.push_back(horizontal);
Chris@264 435
Chris@280 436 int hoffset = 2;
Chris@280 437 if (m_binScale == LogBins) hoffset = 13;
Chris@278 438
Chris@607 439 int sw = getVerticalScaleWidth(v, false, paint);
Chris@280 440
Chris@280 441 QRect value(sw, cursorPos.y() - paint.fontMetrics().ascent() - 2,
Chris@280 442 paint.fontMetrics().width("0.0000001 V") + 2,
Chris@264 443 paint.fontMetrics().height());
Chris@280 444 extents.push_back(value);
Chris@280 445
Chris@280 446 QRect log(sw, cursorPos.y() + 2,
Chris@280 447 paint.fontMetrics().width("-80.000 dBV") + 2,
Chris@280 448 paint.fontMetrics().height());
Chris@280 449 extents.push_back(log);
Chris@280 450
Chris@280 451 QRect freq(cursorPos.x(),
Chris@918 452 v->getPaintHeight() - paint.fontMetrics().height() - hoffset,
Chris@280 453 paint.fontMetrics().width("123456 Hz") + 2,
Chris@280 454 paint.fontMetrics().height());
Chris@280 455 extents.push_back(freq);
Chris@264 456
Chris@278 457 int w(paint.fontMetrics().width("C#10+50c") + 2);
Chris@278 458 QRect pitch(cursorPos.x() - w,
Chris@918 459 v->getPaintHeight() - paint.fontMetrics().height() - hoffset,
Chris@278 460 w,
Chris@278 461 paint.fontMetrics().height());
Chris@278 462 extents.push_back(pitch);
Chris@278 463
Chris@264 464 return true;
Chris@264 465 }
Chris@264 466
Chris@254 467 void
Chris@918 468 SpectrumLayer::paintCrosshairs(LayerGeometryProvider *v, QPainter &paint,
Chris@254 469 QPoint cursorPos) const
Chris@254 470 {
Chris@280 471 if (!m_sliceableModel) return;
Chris@280 472
Chris@254 473 paint.save();
Chris@282 474 QFont fn = paint.font();
Chris@282 475 if (fn.pointSize() > 8) {
Chris@282 476 fn.setPointSize(fn.pointSize() - 1);
Chris@282 477 paint.setFont(fn);
Chris@282 478 }
Chris@254 479
Chris@254 480 ColourMapper mapper(m_colourMap, 0, 1);
Chris@254 481 paint.setPen(mapper.getContrastingColour());
Chris@254 482
Chris@254 483 int xorigin = m_xorigins[v];
Chris@918 484 int w = v->getPaintWidth() - xorigin - 1;
Chris@254 485
Chris@918 486 paint.drawLine(xorigin, cursorPos.y(), v->getPaintWidth(), cursorPos.y());
Chris@918 487 paint.drawLine(cursorPos.x(), cursorPos.y(), cursorPos.x(), v->getPaintHeight());
Chris@254 488
Chris@908 489 double fundamental = getFrequencyForX(cursorPos.x() - xorigin, w);
Chris@254 490
Chris@280 491 int hoffset = 2;
Chris@280 492 if (m_binScale == LogBins) hoffset = 13;
Chris@278 493
Chris@278 494 v->drawVisibleText(paint,
Chris@278 495 cursorPos.x() + 2,
Chris@918 496 v->getPaintHeight() - 2 - hoffset,
Chris@278 497 QString("%1 Hz").arg(fundamental),
Chris@278 498 View::OutlinedText);
Chris@278 499
Chris@278 500 if (Pitch::isFrequencyInMidiRange(fundamental)) {
Chris@278 501 QString pitchLabel = Pitch::getPitchLabelForFrequency(fundamental);
Chris@278 502 v->drawVisibleText(paint,
Chris@278 503 cursorPos.x() - paint.fontMetrics().width(pitchLabel) - 2,
Chris@918 504 v->getPaintHeight() - 2 - hoffset,
Chris@278 505 pitchLabel,
Chris@278 506 View::OutlinedText);
Chris@278 507 }
Chris@264 508
Chris@908 509 double value = getValueForY(cursorPos.y(), v);
Chris@908 510 double thresh = m_threshold;
Chris@908 511 double db = thresh;
Chris@908 512 if (value > 0.0) db = 10.0 * log10(value);
Chris@280 513 if (db < thresh) db = thresh;
Chris@280 514
Chris@280 515 v->drawVisibleText(paint,
Chris@280 516 xorigin + 2,
Chris@280 517 cursorPos.y() - 2,
Chris@280 518 QString("%1 V").arg(value),
Chris@280 519 View::OutlinedText);
Chris@280 520
Chris@280 521 v->drawVisibleText(paint,
Chris@280 522 xorigin + 2,
Chris@280 523 cursorPos.y() + 2 + paint.fontMetrics().ascent(),
Chris@280 524 QString("%1 dBV").arg(db),
Chris@280 525 View::OutlinedText);
Chris@280 526
Chris@254 527 int harmonic = 2;
Chris@254 528
Chris@254 529 while (harmonic < 100) {
Chris@254 530
Chris@908 531 int hx = int(lrint(getXForFrequency(fundamental * harmonic, w)));
Chris@254 532 hx += xorigin;
Chris@254 533
Chris@918 534 if (hx < xorigin || hx > v->getPaintWidth()) break;
Chris@254 535
Chris@254 536 int len = 7;
Chris@254 537
Chris@254 538 if (harmonic % 2 == 0) {
Chris@254 539 if (harmonic % 4 == 0) {
Chris@254 540 len = 12;
Chris@254 541 } else {
Chris@254 542 len = 10;
Chris@254 543 }
Chris@254 544 }
Chris@254 545
Chris@908 546 paint.drawLine(hx,
Chris@254 547 cursorPos.y(),
Chris@908 548 hx,
Chris@254 549 cursorPos.y() + len);
Chris@254 550
Chris@254 551 ++harmonic;
Chris@254 552 }
Chris@254 553
Chris@254 554 paint.restore();
Chris@254 555 }
Chris@254 556
Chris@199 557 QString
Chris@918 558 SpectrumLayer::getFeatureDescription(LayerGeometryProvider *v, QPoint &p) const
Chris@199 559 {
Chris@199 560 if (!m_sliceableModel) return "";
Chris@199 561
Chris@199 562 int minbin = 0, maxbin = 0, range = 0;
Chris@805 563 QString genericDesc = SliceLayer::getFeatureDescriptionAux
Chris@199 564 (v, p, false, minbin, maxbin, range);
Chris@199 565
Chris@199 566 if (genericDesc == "") return "";
Chris@199 567
Chris@908 568 double minvalue = 0.f;
Chris@248 569 if (minbin < int(m_values.size())) minvalue = m_values[minbin];
Chris@199 570
Chris@908 571 double maxvalue = minvalue;
Chris@248 572 if (maxbin < int(m_values.size())) maxvalue = m_values[maxbin];
Chris@199 573
Chris@199 574 if (minvalue > maxvalue) std::swap(minvalue, maxvalue);
Chris@199 575
Chris@199 576 QString binstr;
Chris@199 577 QString hzstr;
Chris@908 578 int minfreq = int(lrint((minbin * m_sliceableModel->getSampleRate()) /
Chris@908 579 m_windowSize));
Chris@908 580 int maxfreq = int(lrint((std::max(maxbin, minbin+1)
Chris@908 581 * m_sliceableModel->getSampleRate()) /
Chris@908 582 m_windowSize));
Chris@199 583
Chris@199 584 if (maxbin != minbin) {
Chris@199 585 binstr = tr("%1 - %2").arg(minbin+1).arg(maxbin+1);
Chris@199 586 } else {
Chris@199 587 binstr = QString("%1").arg(minbin+1);
Chris@199 588 }
Chris@199 589 if (minfreq != maxfreq) {
Chris@199 590 hzstr = tr("%1 - %2 Hz").arg(minfreq).arg(maxfreq);
Chris@199 591 } else {
Chris@199 592 hzstr = tr("%1 Hz").arg(minfreq);
Chris@199 593 }
Chris@199 594
Chris@199 595 QString valuestr;
Chris@199 596 if (maxvalue != minvalue) {
Chris@199 597 valuestr = tr("%1 - %2").arg(minvalue).arg(maxvalue);
Chris@199 598 } else {
Chris@199 599 valuestr = QString("%1").arg(minvalue);
Chris@199 600 }
Chris@199 601
Chris@199 602 QString dbstr;
Chris@908 603 double mindb = AudioLevel::multiplier_to_dB(minvalue);
Chris@908 604 double maxdb = AudioLevel::multiplier_to_dB(maxvalue);
Chris@199 605 QString mindbstr;
Chris@199 606 QString maxdbstr;
Chris@199 607 if (mindb == AudioLevel::DB_FLOOR) {
Chris@199 608 mindbstr = tr("-Inf");
Chris@199 609 } else {
Chris@908 610 mindbstr = QString("%1").arg(lrint(mindb));
Chris@199 611 }
Chris@199 612 if (maxdb == AudioLevel::DB_FLOOR) {
Chris@199 613 maxdbstr = tr("-Inf");
Chris@199 614 } else {
Chris@908 615 maxdbstr = QString("%1").arg(lrint(maxdb));
Chris@199 616 }
Chris@908 617 if (lrint(mindb) != lrint(maxdb)) {
Chris@199 618 dbstr = tr("%1 - %2").arg(mindbstr).arg(maxdbstr);
Chris@199 619 } else {
Chris@199 620 dbstr = tr("%1").arg(mindbstr);
Chris@199 621 }
Chris@199 622
Chris@199 623 QString description;
Chris@199 624
Chris@248 625 if (range > int(m_sliceableModel->getResolution())) {
Chris@199 626 description = tr("%1\nBin:\t%2 (%3)\n%4 value:\t%5\ndB:\t%6")
Chris@199 627 .arg(genericDesc)
Chris@199 628 .arg(binstr)
Chris@199 629 .arg(hzstr)
Chris@199 630 .arg(m_samplingMode == NearestSample ? tr("First") :
Chris@199 631 m_samplingMode == SampleMean ? tr("Mean") : tr("Peak"))
Chris@199 632 .arg(valuestr)
Chris@199 633 .arg(dbstr);
Chris@199 634 } else {
Chris@199 635 description = tr("%1\nBin:\t%2 (%3)\nValue:\t%4\ndB:\t%5")
Chris@199 636 .arg(genericDesc)
Chris@199 637 .arg(binstr)
Chris@199 638 .arg(hzstr)
Chris@199 639 .arg(valuestr)
Chris@199 640 .arg(dbstr);
Chris@199 641 }
Chris@199 642
Chris@199 643 return description;
Chris@199 644 }
Chris@199 645
Chris@254 646 void
Chris@916 647 SpectrumLayer::paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const
Chris@275 648 {
Chris@275 649 if (!m_originModel || !m_originModel->isOK() ||
Chris@349 650 !m_originModel->isReady()) {
Chris@587 651 SVDEBUG << "SpectrumLayer::paint: no origin model, or origin model not OK or not ready" << endl;
Chris@349 652 return;
Chris@349 653 }
Chris@275 654
Chris@275 655 if (m_newFFTNeeded) {
Chris@587 656 SVDEBUG << "SpectrumLayer::paint: new FFT needed, calling setupFFT" << endl;
Chris@275 657 const_cast<SpectrumLayer *>(this)->setupFFT(); //ugh
Chris@275 658 }
Chris@277 659
Chris@277 660 FFTModel *fft = dynamic_cast<FFTModel *>
Chris@277 661 (const_cast<DenseThreeDimensionalModel *>(m_sliceableModel));
Chris@277 662
Chris@908 663 double thresh = (pow(10, -6) / m_gain) * (m_windowSize / 2.0); // -60dB adj
Chris@277 664
Chris@607 665 int xorigin = getVerticalScaleWidth(v, false, paint) + 1;
Chris@918 666 int w = v->getPaintWidth() - xorigin - 1;
Chris@277 667
Chris@278 668 int pkh = 0;
Chris@287 669 //!!! if (m_binScale == LogBins) {
Chris@287 670 pkh = 10;
Chris@287 671 //!!! }
Chris@278 672
Chris@345 673 paint.save();
Chris@345 674
Chris@284 675 if (fft && m_showPeaks) {
Chris@277 676
Chris@277 677 // draw peak lines
Chris@277 678
Chris@587 679 // SVDEBUG << "Showing peaks..." << endl;
Chris@456 680
Chris@908 681 int col = int(v->getCentreFrame() / fft->getResolution());
Chris@277 682
Chris@277 683 paint.save();
Chris@277 684 paint.setRenderHint(QPainter::Antialiasing, false);
Chris@277 685 paint.setPen(QColor(160, 160, 160)); //!!!
Chris@277 686
Chris@290 687 int peakminbin = 0;
Chris@290 688 int peakmaxbin = fft->getHeight() - 1;
Chris@908 689 double peakmaxfreq = Pitch::getFrequencyForPitch(128);
Chris@908 690 peakmaxbin = int(((peakmaxfreq * fft->getHeight() * 2) / fft->getSampleRate()));
Chris@290 691
Chris@280 692 FFTModel::PeakSet peaks = fft->getPeakFrequencies
Chris@290 693 (FFTModel::MajorPitchAdaptivePeaks, col, peakminbin, peakmaxbin);
Chris@280 694
Chris@280 695 ColourMapper mapper(ColourMapper::BlackOnWhite, 0, 1);
Chris@277 696
Chris@277 697 BiasCurve curve;
Chris@277 698 getBiasCurve(curve);
Chris@908 699 int cs = int(curve.size());
Chris@280 700
Chris@908 701 std::vector<double> values;
Chris@277 702
Chris@805 703 for (int bin = 0; bin < fft->getHeight(); ++bin) {
Chris@908 704 double value = m_sliceableModel->getValueAt(col, bin);
Chris@280 705 if (bin < cs) value *= curve[bin];
Chris@280 706 values.push_back(value);
Chris@280 707 }
Chris@280 708
Chris@280 709 for (FFTModel::PeakSet::iterator i = peaks.begin();
Chris@280 710 i != peaks.end(); ++i) {
Chris@280 711
Chris@805 712 int bin = i->first;
Chris@277 713
Chris@682 714 // cerr << "bin = " << bin << ", thresh = " << thresh << ", value = " << fft->getMagnitudeAt(col, bin) << endl;
Chris@280 715
Chris@908 716 if (!fft->isOverThreshold(col, bin, float(thresh))) continue;
Chris@277 717
Chris@908 718 double freq = i->second;
Chris@280 719
Chris@908 720 int x = int(lrint(getXForFrequency(freq, w)));
Chris@277 721
Chris@908 722 double norm = 0.f;
Chris@805 723 (void)getYForValue(values[bin], v, norm); // don't need return value, need norm
Chris@277 724
Chris@277 725 paint.setPen(mapper.map(norm));
Chris@918 726 paint.drawLine(xorigin + x, 0, xorigin + x, v->getPaintHeight() - pkh - 1);
Chris@277 727 }
Chris@277 728
Chris@277 729 paint.restore();
Chris@277 730 }
Chris@275 731
Chris@275 732 SliceLayer::paint(v, paint, rect);
Chris@277 733
Chris@278 734 //!!! All of this stuff relating to depicting frequencies
Chris@278 735 //(keyboard, crosshairs etc) should be applicable to any slice
Chris@278 736 //layer whose model has a vertical scale unit of Hz. However, the
Chris@278 737 //dense 3d model at the moment doesn't record its vertical scale
Chris@278 738 //unit -- we need to fix that and hoist this code as appropriate.
Chris@278 739 //Same really goes for any code in SpectrogramLayer that could be
Chris@278 740 //relevant to Colour3DPlotLayer with unit Hz, but that's a bigger
Chris@278 741 //proposition.
Chris@278 742
Chris@287 743 // if (m_binScale == LogBins) {
Chris@277 744
Chris@287 745 // int pkh = 10;
Chris@918 746 int h = v->getPaintHeight();
Chris@277 747
Chris@277 748 // piano keyboard
Chris@277 749 //!!! should be in a new paintHorizontalScale()?
Chris@278 750 // nice to have a piano keyboard class, of course
Chris@277 751
Chris@278 752 paint.drawLine(xorigin, h - pkh - 1, w + xorigin, h - pkh - 1);
Chris@277 753
Chris@277 754 int px = xorigin, ppx = xorigin;
Chris@278 755 paint.setBrush(paint.pen().color());
Chris@277 756
Chris@277 757 for (int i = 0; i < 128; ++i) {
Chris@277 758
Chris@908 759 double f = Pitch::getFrequencyForPitch(i);
Chris@908 760 int x = int(lrint(getXForFrequency(f, w)));
Chris@278 761
Chris@278 762 x += xorigin;
Chris@277 763
Chris@278 764 if (i == 0) {
Chris@278 765 px = ppx = x;
Chris@278 766 }
Chris@278 767 if (i == 1) {
Chris@278 768 ppx = px - (x - px);
Chris@278 769 }
Chris@278 770
Chris@278 771 if (x < xorigin) {
Chris@278 772 ppx = px;
Chris@278 773 px = x;
Chris@277 774 continue;
Chris@277 775 }
Chris@278 776
Chris@278 777 if (x > w) {
Chris@278 778 break;
Chris@278 779 }
Chris@277 780
Chris@277 781 int n = (i % 12);
Chris@277 782
Chris@277 783 if (n == 1) {
Chris@277 784 // C# -- fill the C from here
Chris@284 785 QColor col = Qt::gray;
Chris@284 786 if (i == 61) { // filling middle C
Chris@284 787 col = Qt::blue;
Chris@284 788 col = col.light(150);
Chris@284 789 }
Chris@277 790 if (x - ppx > 2) {
Chris@278 791 paint.fillRect((px + ppx) / 2 + 1,
Chris@277 792 h - pkh,
Chris@278 793 x - (px + ppx) / 2 - 1,
Chris@277 794 pkh,
Chris@284 795 col);
Chris@277 796 }
Chris@277 797 }
Chris@277 798
Chris@277 799 if (n == 1 || n == 3 || n == 6 || n == 8 || n == 10) {
Chris@277 800 // black notes
Chris@277 801 paint.drawLine(x, h - pkh, x, h);
Chris@908 802 int rw = int(lrint(double(x - px) / 4) * 2);
Chris@277 803 if (rw < 2) rw = 2;
Chris@278 804 paint.drawRect(x - rw/2, h - pkh, rw, pkh/2);
Chris@277 805 } else if (n == 0 || n == 5) {
Chris@277 806 // C, F
Chris@277 807 if (px < w) {
Chris@277 808 paint.drawLine((x + px) / 2, h - pkh, (x + px) / 2, h);
Chris@277 809 }
Chris@277 810 }
Chris@277 811
Chris@277 812 ppx = px;
Chris@277 813 px = x;
Chris@277 814 }
Chris@287 815 // }
Chris@345 816
Chris@345 817 paint.restore();
Chris@275 818 }
Chris@275 819
Chris@275 820 void
Chris@254 821 SpectrumLayer::getBiasCurve(BiasCurve &curve) const
Chris@254 822 {
Chris@254 823 curve = m_biasCurve;
Chris@254 824 }
Chris@199 825
Chris@316 826 void
Chris@316 827 SpectrumLayer::toXml(QTextStream &stream,
Chris@316 828 QString indent, QString extraAttributes) const
Chris@220 829 {
Chris@316 830 QString s = QString("windowSize=\"%1\" "
Chris@456 831 "windowHopLevel=\"%2\" "
Chris@456 832 "showPeaks=\"%3\" ")
Chris@220 833 .arg(m_windowSize)
Chris@456 834 .arg(m_windowHopLevel)
Chris@456 835 .arg(m_showPeaks ? "true" : "false");
Chris@220 836
Chris@316 837 SliceLayer::toXml(stream, indent, extraAttributes + " " + s);
Chris@220 838 }
Chris@220 839
Chris@220 840 void
Chris@220 841 SpectrumLayer::setProperties(const QXmlAttributes &attributes)
Chris@220 842 {
Chris@220 843 SliceLayer::setProperties(attributes);
Chris@220 844
Chris@220 845 bool ok = false;
Chris@220 846
Chris@805 847 int windowSize = attributes.value("windowSize").toUInt(&ok);
Chris@220 848 if (ok) setWindowSize(windowSize);
Chris@220 849
Chris@805 850 int windowHopLevel = attributes.value("windowHopLevel").toUInt(&ok);
Chris@220 851 if (ok) setWindowHopLevel(windowHopLevel);
Chris@456 852
Chris@456 853 bool showPeaks = (attributes.value("showPeaks").trimmed() == "true");
Chris@456 854 setShowPeaks(showPeaks);
Chris@220 855 }
Chris@220 856
Chris@220 857