annotate layer/SpectrumLayer.cpp @ 1269:f2894944c6b8

Make the overlays at either end translucent, so they don't completely crop out any underlying text or necessary info (e.g. selection extents)
author Chris Cannam
date Thu, 19 Apr 2018 14:35:59 +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