annotate layer/SpectrumLayer.cpp @ 1204:d421df27e184 3.0-integration

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