annotate layer/SpectrumLayer.cpp @ 1271:d967e21fe995

Avoid crash when scrolling with an empty layer open
author Chris Cannam
date Mon, 23 Apr 2018 16:04:51 +0100
parents a34a2a25907c
children b4cb11ca8233 a04f1012fca2
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@1266 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@1266 187 *min = 0;
Chris@1266 188 *max = 15;
Chris@216 189 *deflt = 5;
Chris@1266 190
Chris@1266 191 val = 0;
Chris@1266 192 int ws = m_windowSize;
Chris@1266 193 while (ws > 32) { ws >>= 1; val ++; }
Chris@153 194
Chris@153 195 } else if (name == "Window Increment") {
Chris@1266 196
Chris@1266 197 *min = 0;
Chris@1266 198 *max = 5;
Chris@216 199 *deflt = 2;
Chris@1266 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@1266 217 int value) const
Chris@153 218 {
Chris@153 219 if (name == "Window Size") {
Chris@1266 220 return QString("%1").arg(32 << value);
Chris@153 221 }
Chris@153 222 if (name == "Window Increment") {
Chris@1266 223 switch (value) {
Chris@1266 224 default:
Chris@1266 225 case 0: return tr("None");
Chris@1266 226 case 1: return tr("25 %");
Chris@1266 227 case 2: return tr("50 %");
Chris@1266 228 case 3: return tr("75 %");
Chris@1266 229 case 4: return tr("87.5 %");
Chris@1266 230 case 5: return tr("93.75 %");
Chris@1266 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@1266 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@1238 300 double
Chris@1238 301 SpectrumLayer::getFrequencyForX(const LayerGeometryProvider *v, double x) const
Chris@133 302 {
Chris@1238 303 if (!m_sliceableModel) return 0;
Chris@1238 304 double bin = getBinForX(v, x);
Chris@1238 305 return (m_sliceableModel->getSampleRate() * bin) /
Chris@1238 306 (m_sliceableModel->getHeight() * 2);
Chris@133 307 }
Chris@133 308
Chris@908 309 double
Chris@1238 310 SpectrumLayer::getXForFrequency(const LayerGeometryProvider *v, double freq) const
Chris@265 311 {
Chris@280 312 if (!m_sliceableModel) return 0;
Chris@1238 313 double bin = (freq * m_sliceableModel->getHeight() * 2) /
Chris@1238 314 m_sliceableModel->getSampleRate();
Chris@1238 315 return getXForBin(v, bin);
Chris@254 316 }
Chris@254 317
Chris@260 318 bool
Chris@918 319 SpectrumLayer::getXScaleValue(const LayerGeometryProvider *v, int x,
Chris@908 320 double &value, QString &unit) const
Chris@260 321 {
Chris@1238 322 value = getFrequencyForX(v, x);
Chris@260 323 unit = "Hz";
Chris@260 324 return true;
Chris@260 325 }
Chris@260 326
Chris@264 327 bool
Chris@918 328 SpectrumLayer::getYScaleValue(const LayerGeometryProvider *v, int y,
Chris@908 329 double &value, QString &unit) const
Chris@274 330 {
Chris@1238 331 value = getValueForY(v, y);
Chris@274 332
Chris@274 333 if (m_energyScale == dBScale || m_energyScale == MeterScale) {
Chris@274 334
Chris@908 335 if (value > 0.0) {
Chris@908 336 value = 10.0 * log10(value);
Chris@284 337 if (value < m_threshold) value = m_threshold;
Chris@284 338 } else value = m_threshold;
Chris@274 339
Chris@274 340 unit = "dBV";
Chris@274 341
Chris@274 342 } else {
Chris@274 343 unit = "V";
Chris@274 344 }
Chris@274 345
Chris@274 346 return true;
Chris@274 347 }
Chris@274 348
Chris@274 349 bool
Chris@918 350 SpectrumLayer::getYScaleDifference(const LayerGeometryProvider *v, int y0, int y1,
Chris@908 351 double &diff, QString &unit) const
Chris@274 352 {
Chris@274 353 bool rv = SliceLayer::getYScaleDifference(v, y0, y1, diff, unit);
Chris@274 354 if (rv && (unit == "dBV")) unit = "dB";
Chris@274 355 return rv;
Chris@274 356 }
Chris@274 357
Chris@274 358
Chris@274 359 bool
Chris@918 360 SpectrumLayer::getCrosshairExtents(LayerGeometryProvider *v, QPainter &paint,
Chris@264 361 QPoint cursorPos,
Chris@264 362 std::vector<QRect> &extents) const
Chris@264 363 {
Chris@918 364 QRect vertical(cursorPos.x(), cursorPos.y(), 1, v->getPaintHeight() - cursorPos.y());
Chris@264 365 extents.push_back(vertical);
Chris@264 366
Chris@918 367 QRect horizontal(0, cursorPos.y(), v->getPaintWidth(), 12);
Chris@264 368 extents.push_back(horizontal);
Chris@264 369
Chris@280 370 int hoffset = 2;
Chris@280 371 if (m_binScale == LogBins) hoffset = 13;
Chris@278 372
Chris@607 373 int sw = getVerticalScaleWidth(v, false, paint);
Chris@280 374
Chris@280 375 QRect value(sw, cursorPos.y() - paint.fontMetrics().ascent() - 2,
Chris@280 376 paint.fontMetrics().width("0.0000001 V") + 2,
Chris@264 377 paint.fontMetrics().height());
Chris@280 378 extents.push_back(value);
Chris@280 379
Chris@280 380 QRect log(sw, cursorPos.y() + 2,
Chris@280 381 paint.fontMetrics().width("-80.000 dBV") + 2,
Chris@280 382 paint.fontMetrics().height());
Chris@280 383 extents.push_back(log);
Chris@280 384
Chris@280 385 QRect freq(cursorPos.x(),
Chris@918 386 v->getPaintHeight() - paint.fontMetrics().height() - hoffset,
Chris@280 387 paint.fontMetrics().width("123456 Hz") + 2,
Chris@280 388 paint.fontMetrics().height());
Chris@280 389 extents.push_back(freq);
Chris@264 390
Chris@278 391 int w(paint.fontMetrics().width("C#10+50c") + 2);
Chris@278 392 QRect pitch(cursorPos.x() - w,
Chris@918 393 v->getPaintHeight() - paint.fontMetrics().height() - hoffset,
Chris@278 394 w,
Chris@278 395 paint.fontMetrics().height());
Chris@278 396 extents.push_back(pitch);
Chris@278 397
Chris@264 398 return true;
Chris@264 399 }
Chris@264 400
Chris@254 401 void
Chris@918 402 SpectrumLayer::paintCrosshairs(LayerGeometryProvider *v, QPainter &paint,
Chris@254 403 QPoint cursorPos) const
Chris@254 404 {
Chris@280 405 if (!m_sliceableModel) return;
Chris@280 406
Chris@254 407 paint.save();
Chris@282 408 QFont fn = paint.font();
Chris@282 409 if (fn.pointSize() > 8) {
Chris@282 410 fn.setPointSize(fn.pointSize() - 1);
Chris@282 411 paint.setFont(fn);
Chris@282 412 }
Chris@254 413
Chris@254 414 ColourMapper mapper(m_colourMap, 0, 1);
Chris@254 415 paint.setPen(mapper.getContrastingColour());
Chris@254 416
Chris@1238 417 int xorigin = m_xorigins[v->getId()];
Chris@918 418 paint.drawLine(xorigin, cursorPos.y(), v->getPaintWidth(), cursorPos.y());
Chris@918 419 paint.drawLine(cursorPos.x(), cursorPos.y(), cursorPos.x(), v->getPaintHeight());
Chris@254 420
Chris@1238 421 double fundamental = getFrequencyForX(v, cursorPos.x());
Chris@254 422
Chris@280 423 int hoffset = 2;
Chris@280 424 if (m_binScale == LogBins) hoffset = 13;
Chris@278 425
Chris@1078 426 PaintAssistant::drawVisibleText(v, paint,
Chris@1238 427 cursorPos.x() + 2,
Chris@1238 428 v->getPaintHeight() - 2 - hoffset,
Chris@1238 429 QString("%1 Hz").arg(fundamental),
Chris@1238 430 PaintAssistant::OutlinedText);
Chris@278 431
Chris@278 432 if (Pitch::isFrequencyInMidiRange(fundamental)) {
Chris@278 433 QString pitchLabel = Pitch::getPitchLabelForFrequency(fundamental);
Chris@1078 434 PaintAssistant::drawVisibleText(v, paint,
Chris@1238 435 cursorPos.x() -
Chris@1238 436 paint.fontMetrics().width(pitchLabel) - 2,
Chris@1238 437 v->getPaintHeight() - 2 - hoffset,
Chris@1238 438 pitchLabel,
Chris@1238 439 PaintAssistant::OutlinedText);
Chris@278 440 }
Chris@264 441
Chris@1238 442 double value = getValueForY(v, cursorPos.y());
Chris@908 443 double thresh = m_threshold;
Chris@908 444 double db = thresh;
Chris@908 445 if (value > 0.0) db = 10.0 * log10(value);
Chris@280 446 if (db < thresh) db = thresh;
Chris@280 447
Chris@1078 448 PaintAssistant::drawVisibleText(v, paint,
Chris@280 449 xorigin + 2,
Chris@280 450 cursorPos.y() - 2,
Chris@280 451 QString("%1 V").arg(value),
Chris@1078 452 PaintAssistant::OutlinedText);
Chris@280 453
Chris@1078 454 PaintAssistant::drawVisibleText(v, paint,
Chris@280 455 xorigin + 2,
Chris@280 456 cursorPos.y() + 2 + paint.fontMetrics().ascent(),
Chris@280 457 QString("%1 dBV").arg(db),
Chris@1078 458 PaintAssistant::OutlinedText);
Chris@280 459
Chris@254 460 int harmonic = 2;
Chris@254 461
Chris@254 462 while (harmonic < 100) {
Chris@254 463
Chris@1238 464 int hx = int(lrint(getXForFrequency(v, fundamental * harmonic)));
Chris@254 465
Chris@918 466 if (hx < xorigin || hx > v->getPaintWidth()) break;
Chris@254 467
Chris@254 468 int len = 7;
Chris@254 469
Chris@254 470 if (harmonic % 2 == 0) {
Chris@254 471 if (harmonic % 4 == 0) {
Chris@254 472 len = 12;
Chris@254 473 } else {
Chris@254 474 len = 10;
Chris@254 475 }
Chris@254 476 }
Chris@254 477
Chris@908 478 paint.drawLine(hx,
Chris@254 479 cursorPos.y(),
Chris@908 480 hx,
Chris@254 481 cursorPos.y() + len);
Chris@254 482
Chris@254 483 ++harmonic;
Chris@254 484 }
Chris@254 485
Chris@254 486 paint.restore();
Chris@254 487 }
Chris@254 488
Chris@199 489 QString
Chris@918 490 SpectrumLayer::getFeatureDescription(LayerGeometryProvider *v, QPoint &p) const
Chris@199 491 {
Chris@199 492 if (!m_sliceableModel) return "";
Chris@199 493
Chris@199 494 int minbin = 0, maxbin = 0, range = 0;
Chris@805 495 QString genericDesc = SliceLayer::getFeatureDescriptionAux
Chris@199 496 (v, p, false, minbin, maxbin, range);
Chris@199 497
Chris@199 498 if (genericDesc == "") return "";
Chris@199 499
Chris@1238 500 int i0 = minbin - m_minbin;
Chris@1238 501 int i1 = maxbin - m_minbin;
Chris@1238 502
Chris@1238 503 float minvalue = 0.0;
Chris@1238 504 if (in_range_for(m_values, i0)) minvalue = m_values[i0];
Chris@199 505
Chris@1238 506 float maxvalue = minvalue;
Chris@1238 507 if (in_range_for(m_values, i1)) maxvalue = m_values[i1];
Chris@1238 508
Chris@199 509 if (minvalue > maxvalue) std::swap(minvalue, maxvalue);
Chris@199 510
Chris@199 511 QString binstr;
Chris@199 512 QString hzstr;
Chris@908 513 int minfreq = int(lrint((minbin * m_sliceableModel->getSampleRate()) /
Chris@908 514 m_windowSize));
Chris@1256 515 int maxfreq = int(lrint((std::max(maxbin, minbin)
Chris@908 516 * m_sliceableModel->getSampleRate()) /
Chris@908 517 m_windowSize));
Chris@199 518
Chris@199 519 if (maxbin != minbin) {
Chris@199 520 binstr = tr("%1 - %2").arg(minbin+1).arg(maxbin+1);
Chris@199 521 } else {
Chris@199 522 binstr = QString("%1").arg(minbin+1);
Chris@199 523 }
Chris@199 524 if (minfreq != maxfreq) {
Chris@199 525 hzstr = tr("%1 - %2 Hz").arg(minfreq).arg(maxfreq);
Chris@199 526 } else {
Chris@199 527 hzstr = tr("%1 Hz").arg(minfreq);
Chris@199 528 }
Chris@199 529
Chris@199 530 QString valuestr;
Chris@199 531 if (maxvalue != minvalue) {
Chris@199 532 valuestr = tr("%1 - %2").arg(minvalue).arg(maxvalue);
Chris@199 533 } else {
Chris@199 534 valuestr = QString("%1").arg(minvalue);
Chris@199 535 }
Chris@199 536
Chris@199 537 QString dbstr;
Chris@908 538 double mindb = AudioLevel::multiplier_to_dB(minvalue);
Chris@908 539 double maxdb = AudioLevel::multiplier_to_dB(maxvalue);
Chris@199 540 QString mindbstr;
Chris@199 541 QString maxdbstr;
Chris@199 542 if (mindb == AudioLevel::DB_FLOOR) {
Chris@1147 543 mindbstr = Strings::minus_infinity;
Chris@199 544 } else {
Chris@908 545 mindbstr = QString("%1").arg(lrint(mindb));
Chris@199 546 }
Chris@199 547 if (maxdb == AudioLevel::DB_FLOOR) {
Chris@1147 548 maxdbstr = Strings::minus_infinity;
Chris@199 549 } else {
Chris@908 550 maxdbstr = QString("%1").arg(lrint(maxdb));
Chris@199 551 }
Chris@908 552 if (lrint(mindb) != lrint(maxdb)) {
Chris@199 553 dbstr = tr("%1 - %2").arg(mindbstr).arg(maxdbstr);
Chris@199 554 } else {
Chris@199 555 dbstr = tr("%1").arg(mindbstr);
Chris@199 556 }
Chris@199 557
Chris@199 558 QString description;
Chris@199 559
Chris@248 560 if (range > int(m_sliceableModel->getResolution())) {
Chris@199 561 description = tr("%1\nBin:\t%2 (%3)\n%4 value:\t%5\ndB:\t%6")
Chris@199 562 .arg(genericDesc)
Chris@199 563 .arg(binstr)
Chris@199 564 .arg(hzstr)
Chris@199 565 .arg(m_samplingMode == NearestSample ? tr("First") :
Chris@199 566 m_samplingMode == SampleMean ? tr("Mean") : tr("Peak"))
Chris@199 567 .arg(valuestr)
Chris@199 568 .arg(dbstr);
Chris@199 569 } else {
Chris@199 570 description = tr("%1\nBin:\t%2 (%3)\nValue:\t%4\ndB:\t%5")
Chris@199 571 .arg(genericDesc)
Chris@199 572 .arg(binstr)
Chris@199 573 .arg(hzstr)
Chris@199 574 .arg(valuestr)
Chris@199 575 .arg(dbstr);
Chris@199 576 }
Chris@199 577
Chris@199 578 return description;
Chris@199 579 }
Chris@199 580
Chris@254 581 void
Chris@916 582 SpectrumLayer::paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const
Chris@275 583 {
Chris@275 584 if (!m_originModel || !m_originModel->isOK() ||
Chris@349 585 !m_originModel->isReady()) {
Chris@587 586 SVDEBUG << "SpectrumLayer::paint: no origin model, or origin model not OK or not ready" << endl;
Chris@349 587 return;
Chris@349 588 }
Chris@275 589
Chris@275 590 if (m_newFFTNeeded) {
Chris@587 591 SVDEBUG << "SpectrumLayer::paint: new FFT needed, calling setupFFT" << endl;
Chris@275 592 const_cast<SpectrumLayer *>(this)->setupFFT(); //ugh
Chris@275 593 }
Chris@277 594
Chris@277 595 FFTModel *fft = dynamic_cast<FFTModel *>
Chris@277 596 (const_cast<DenseThreeDimensionalModel *>(m_sliceableModel));
Chris@277 597
Chris@908 598 double thresh = (pow(10, -6) / m_gain) * (m_windowSize / 2.0); // -60dB adj
Chris@277 599
Chris@607 600 int xorigin = getVerticalScaleWidth(v, false, paint) + 1;
Chris@918 601 int w = v->getPaintWidth() - xorigin - 1;
Chris@277 602
Chris@1231 603 int pkh = int(paint.fontMetrics().height() * 0.7 + 0.5);
Chris@1231 604 if (pkh < 10) pkh = 10;
Chris@278 605
Chris@345 606 paint.save();
Chris@345 607
Chris@284 608 if (fft && m_showPeaks) {
Chris@277 609
Chris@277 610 // draw peak lines
Chris@277 611
Chris@587 612 // SVDEBUG << "Showing peaks..." << endl;
Chris@456 613
Chris@908 614 int col = int(v->getCentreFrame() / fft->getResolution());
Chris@277 615
Chris@277 616 paint.save();
Chris@277 617 paint.setRenderHint(QPainter::Antialiasing, false);
Chris@277 618 paint.setPen(QColor(160, 160, 160)); //!!!
Chris@277 619
Chris@290 620 int peakminbin = 0;
Chris@290 621 int peakmaxbin = fft->getHeight() - 1;
Chris@908 622 double peakmaxfreq = Pitch::getFrequencyForPitch(128);
Chris@908 623 peakmaxbin = int(((peakmaxfreq * fft->getHeight() * 2) / fft->getSampleRate()));
Chris@290 624
Chris@280 625 FFTModel::PeakSet peaks = fft->getPeakFrequencies
Chris@290 626 (FFTModel::MajorPitchAdaptivePeaks, col, peakminbin, peakmaxbin);
Chris@280 627
Chris@280 628 ColourMapper mapper(ColourMapper::BlackOnWhite, 0, 1);
Chris@277 629
Chris@277 630 BiasCurve curve;
Chris@277 631 getBiasCurve(curve);
Chris@908 632 int cs = int(curve.size());
Chris@280 633
Chris@908 634 std::vector<double> values;
Chris@277 635
Chris@805 636 for (int bin = 0; bin < fft->getHeight(); ++bin) {
Chris@908 637 double value = m_sliceableModel->getValueAt(col, bin);
Chris@280 638 if (bin < cs) value *= curve[bin];
Chris@280 639 values.push_back(value);
Chris@280 640 }
Chris@280 641
Chris@280 642 for (FFTModel::PeakSet::iterator i = peaks.begin();
Chris@280 643 i != peaks.end(); ++i) {
Chris@280 644
Chris@805 645 int bin = i->first;
Chris@277 646
Chris@682 647 // cerr << "bin = " << bin << ", thresh = " << thresh << ", value = " << fft->getMagnitudeAt(col, bin) << endl;
Chris@280 648
Chris@908 649 if (!fft->isOverThreshold(col, bin, float(thresh))) continue;
Chris@277 650
Chris@908 651 double freq = i->second;
Chris@280 652
Chris@1238 653 int x = int(lrint(getXForFrequency(v, freq)));
Chris@277 654
Chris@908 655 double norm = 0.f;
Chris@1238 656 (void)getYForValue(v, values[bin], norm); // don't need return value, need norm
Chris@277 657
Chris@277 658 paint.setPen(mapper.map(norm));
Chris@918 659 paint.drawLine(xorigin + x, 0, xorigin + x, v->getPaintHeight() - pkh - 1);
Chris@277 660 }
Chris@277 661
Chris@277 662 paint.restore();
Chris@277 663 }
Chris@275 664
Chris@275 665 SliceLayer::paint(v, paint, rect);
Chris@277 666
Chris@278 667 //!!! All of this stuff relating to depicting frequencies
Chris@1238 668 // (keyboard, crosshairs etc) should be applicable to any slice
Chris@1238 669 // layer whose model has a vertical scale unit of Hz. However,
Chris@1238 670 // the dense 3d model at the moment doesn't record its vertical
Chris@1238 671 // scale unit -- we need to fix that and hoist this code as
Chris@1238 672 // appropriate. Same really goes for any code in SpectrogramLayer
Chris@1238 673 // that could be relevant to Colour3DPlotLayer with unit Hz, but
Chris@1238 674 // that's a bigger proposition.
Chris@278 675
Chris@1238 676 int h = v->getPaintHeight();
Chris@277 677
Chris@1238 678 PianoScale().paintPianoHorizontal
Chris@1238 679 (v, this, paint, QRect(xorigin, h - pkh - 1, w + xorigin, pkh));
Chris@345 680
Chris@345 681 paint.restore();
Chris@275 682 }
Chris@275 683
Chris@275 684 void
Chris@254 685 SpectrumLayer::getBiasCurve(BiasCurve &curve) const
Chris@254 686 {
Chris@254 687 curve = m_biasCurve;
Chris@254 688 }
Chris@199 689
Chris@316 690 void
Chris@316 691 SpectrumLayer::toXml(QTextStream &stream,
Chris@316 692 QString indent, QString extraAttributes) const
Chris@220 693 {
Chris@316 694 QString s = QString("windowSize=\"%1\" "
Chris@456 695 "windowHopLevel=\"%2\" "
Chris@456 696 "showPeaks=\"%3\" ")
Chris@220 697 .arg(m_windowSize)
Chris@456 698 .arg(m_windowHopLevel)
Chris@456 699 .arg(m_showPeaks ? "true" : "false");
Chris@220 700
Chris@316 701 SliceLayer::toXml(stream, indent, extraAttributes + " " + s);
Chris@220 702 }
Chris@220 703
Chris@220 704 void
Chris@220 705 SpectrumLayer::setProperties(const QXmlAttributes &attributes)
Chris@220 706 {
Chris@220 707 SliceLayer::setProperties(attributes);
Chris@220 708
Chris@220 709 bool ok = false;
Chris@220 710
Chris@805 711 int windowSize = attributes.value("windowSize").toUInt(&ok);
Chris@220 712 if (ok) setWindowSize(windowSize);
Chris@220 713
Chris@805 714 int windowHopLevel = attributes.value("windowHopLevel").toUInt(&ok);
Chris@220 715 if (ok) setWindowHopLevel(windowHopLevel);
Chris@456 716
Chris@456 717 bool showPeaks = (attributes.value("showPeaks").trimmed() == "true");
Chris@456 718 setShowPeaks(showPeaks);
Chris@220 719 }
Chris@220 720
Chris@220 721