annotate layer/SpectrumLayer.cpp @ 1548:bd6af89982d7

Permit getScaleProvidingLayerForUnit to return a dormant layer if there is no visible alternative. This is necessary to avoid the scale disappearing in Tony when the spectrogram is toggled off.
author Chris Cannam
date Thu, 17 Oct 2019 14:44:22 +0100
parents e540aa5d89cd
children
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@1276 28 #include "PianoScale.h"
Chris@1281 29 #include "HorizontalFrequencyScale.h"
Chris@254 30
Chris@254 31 #include <QPainter>
Chris@316 32 #include <QTextStream>
Chris@316 33
Chris@133 34
Chris@133 35 SpectrumLayer::SpectrumLayer() :
Chris@153 36 m_channel(-1),
Chris@153 37 m_channelSet(false),
Chris@290 38 m_windowSize(4096),
Chris@153 39 m_windowType(HanningWindow),
Chris@290 40 m_windowHopLevel(3),
Chris@1382 41 m_oversampling(1),
Chris@284 42 m_showPeaks(false),
Chris@1403 43 m_newFFTNeeded(true),
Chris@1403 44 m_freqOfMinBin(0.0)
Chris@133 45 {
Chris@1400 46 m_binAlignment = BinsCentredOnScalePoints;
Chris@1400 47
Chris@153 48 Preferences *prefs = Preferences::getInstance();
Chris@153 49 connect(prefs, SIGNAL(propertyChanged(PropertyContainer::PropertyName)),
Chris@153 50 this, SLOT(preferenceChanged(PropertyContainer::PropertyName)));
Chris@153 51 setWindowType(prefs->getWindowType());
Chris@195 52
Chris@195 53 setBinScale(LogBins);
Chris@133 54 }
Chris@133 55
Chris@133 56 SpectrumLayer::~SpectrumLayer()
Chris@133 57 {
Chris@1473 58 ModelById::release(m_sliceableModel);
Chris@133 59 }
Chris@133 60
Chris@133 61 void
Chris@1471 62 SpectrumLayer::setModel(ModelId modelId)
Chris@133 63 {
Chris@1471 64 auto newModel = ModelById::getAs<DenseTimeValueModel>(modelId);
Chris@1471 65 if (!modelId.isNone() && !newModel) {
Chris@1471 66 throw std::logic_error("Not a DenseTimeValueModel");
Chris@1471 67 }
Chris@345 68
Chris@1471 69 if (m_originModel == modelId) return;
Chris@1471 70 m_originModel = modelId;
Chris@349 71
Chris@349 72 m_newFFTNeeded = true;
Chris@349 73
Chris@349 74 emit layerParametersChanged();
Chris@349 75 }
Chris@349 76
Chris@349 77 void
Chris@349 78 SpectrumLayer::setChannel(int channel)
Chris@349 79 {
Chris@587 80 SVDEBUG << "SpectrumLayer::setChannel(" << channel << ") from " << m_channel << endl;
Chris@349 81
Chris@349 82 m_channelSet = true;
Chris@349 83
Chris@349 84 if (m_channel == channel) return;
Chris@349 85
Chris@349 86 m_channel = channel;
Chris@349 87
Chris@349 88 m_newFFTNeeded = true;
Chris@349 89
Chris@349 90 emit layerParametersChanged();
Chris@153 91 }
Chris@153 92
Chris@153 93 void
Chris@193 94 SpectrumLayer::setupFFT()
Chris@153 95 {
Chris@1473 96 ModelById::release(m_sliceableModel);
Chris@1473 97 m_sliceableModel = {};
Chris@349 98
Chris@1473 99 if (m_originModel.isNone()) {
Chris@349 100 return;
Chris@153 101 }
Chris@153 102
Chris@1382 103 int fftSize = getFFTSize();
Chris@1382 104
Chris@1473 105 auto newFFT = std::make_shared<FFTModel>(m_originModel,
Chris@1473 106 m_channel,
Chris@1473 107 m_windowType,
Chris@1473 108 m_windowSize,
Chris@1473 109 getWindowIncrement(),
Chris@1473 110 fftSize);
Chris@153 111
Chris@1399 112 if (m_minbin == 0 && m_maxbin == 0) {
Chris@1399 113 m_minbin = 1;
Chris@1403 114 m_freqOfMinBin = double(m_minbin * newFFT->getSampleRate())
Chris@1403 115 / getFFTSize();
Chris@1399 116 m_maxbin = newFFT->getHeight();
Chris@1399 117 }
Chris@1400 118
Chris@1481 119 setSliceableModel(ModelById::add(newFFT));
Chris@193 120
Chris@254 121 m_biasCurve.clear();
Chris@1382 122 for (int i = 0; i < fftSize; ++i) {
Chris@1403 123 // Scale by the window size, not the FFT size, because we
Chris@1403 124 // don't want to scale down by all the zero bins
Chris@1403 125 m_biasCurve.push_back(1.f / (float(m_windowSize)/2.f));
Chris@254 126 }
Chris@254 127
Chris@349 128 m_newFFTNeeded = false;
Chris@133 129 }
Chris@133 130
Chris@153 131 Layer::PropertyList
Chris@153 132 SpectrumLayer::getProperties() const
Chris@153 133 {
Chris@193 134 PropertyList list = SliceLayer::getProperties();
Chris@153 135 list.push_back("Window Size");
Chris@153 136 list.push_back("Window Increment");
Chris@1382 137 list.push_back("Oversampling");
Chris@284 138 list.push_back("Show Peak Frequencies");
Chris@153 139 return list;
Chris@153 140 }
Chris@153 141
Chris@153 142 QString
Chris@153 143 SpectrumLayer::getPropertyLabel(const PropertyName &name) const
Chris@153 144 {
Chris@153 145 if (name == "Window Size") return tr("Window Size");
Chris@153 146 if (name == "Window Increment") return tr("Window Overlap");
Chris@1382 147 if (name == "Oversampling") return tr("Oversampling");
Chris@284 148 if (name == "Show Peak Frequencies") return tr("Show Peak Frequencies");
Chris@193 149 return SliceLayer::getPropertyLabel(name);
Chris@153 150 }
Chris@153 151
Chris@335 152 QString
Chris@335 153 SpectrumLayer::getPropertyIconName(const PropertyName &name) const
Chris@335 154 {
Chris@335 155 if (name == "Show Peak Frequencies") return "show-peaks";
Chris@335 156 return SliceLayer::getPropertyIconName(name);
Chris@335 157 }
Chris@335 158
Chris@153 159 Layer::PropertyType
Chris@153 160 SpectrumLayer::getPropertyType(const PropertyName &name) const
Chris@153 161 {
Chris@193 162 if (name == "Window Size") return ValueProperty;
Chris@193 163 if (name == "Window Increment") return ValueProperty;
Chris@1382 164 if (name == "Oversampling") return ValueProperty;
Chris@284 165 if (name == "Show Peak Frequencies") return ToggleProperty;
Chris@193 166 return SliceLayer::getPropertyType(name);
Chris@153 167 }
Chris@153 168
Chris@153 169 QString
Chris@153 170 SpectrumLayer::getPropertyGroupName(const PropertyName &name) const
Chris@153 171 {
Chris@153 172 if (name == "Window Size" ||
Chris@1382 173 name == "Window Increment" ||
Chris@1382 174 name == "Oversampling") return tr("Window");
Chris@557 175 if (name == "Show Peak Frequencies") return tr("Bins");
Chris@193 176 return SliceLayer::getPropertyGroupName(name);
Chris@153 177 }
Chris@153 178
Chris@153 179 int
Chris@153 180 SpectrumLayer::getPropertyRangeAndValue(const PropertyName &name,
Chris@216 181 int *min, int *max, int *deflt) const
Chris@153 182 {
Chris@216 183 int val = 0;
Chris@153 184
Chris@216 185 int garbage0, garbage1, garbage2;
Chris@153 186 if (!min) min = &garbage0;
Chris@153 187 if (!max) max = &garbage1;
Chris@216 188 if (!deflt) deflt = &garbage2;
Chris@153 189
Chris@193 190 if (name == "Window Size") {
Chris@153 191
Chris@1266 192 *min = 0;
Chris@1266 193 *max = 15;
Chris@216 194 *deflt = 5;
Chris@1266 195
Chris@1266 196 val = 0;
Chris@1266 197 int ws = m_windowSize;
Chris@1266 198 while (ws > 32) { ws >>= 1; val ++; }
Chris@153 199
Chris@153 200 } else if (name == "Window Increment") {
Chris@1266 201
Chris@1266 202 *min = 0;
Chris@1266 203 *max = 5;
Chris@216 204 *deflt = 2;
Chris@1266 205
Chris@216 206 val = m_windowHopLevel;
Chris@153 207
Chris@1382 208 } else if (name == "Oversampling") {
Chris@1382 209
Chris@1382 210 *min = 0;
Chris@1382 211 *max = 3;
Chris@1382 212 *deflt = 0;
Chris@1382 213
Chris@1382 214 val = 0;
Chris@1382 215 int ov = m_oversampling;
Chris@1382 216 while (ov > 1) { ov >>= 1; val ++; }
Chris@1382 217
Chris@284 218 } else if (name == "Show Peak Frequencies") {
Chris@284 219
Chris@284 220 return m_showPeaks ? 1 : 0;
Chris@284 221
Chris@153 222 } else {
Chris@193 223
Chris@216 224 val = SliceLayer::getPropertyRangeAndValue(name, min, max, deflt);
Chris@153 225 }
Chris@153 226
Chris@216 227 return val;
Chris@153 228 }
Chris@153 229
Chris@153 230 QString
Chris@153 231 SpectrumLayer::getPropertyValueLabel(const PropertyName &name,
Chris@1266 232 int value) const
Chris@153 233 {
Chris@153 234 if (name == "Window Size") {
Chris@1266 235 return QString("%1").arg(32 << value);
Chris@153 236 }
Chris@153 237 if (name == "Window Increment") {
Chris@1266 238 switch (value) {
Chris@1266 239 default:
Chris@1266 240 case 0: return tr("None");
Chris@1266 241 case 1: return tr("25 %");
Chris@1266 242 case 2: return tr("50 %");
Chris@1266 243 case 3: return tr("75 %");
Chris@1266 244 case 4: return tr("87.5 %");
Chris@1266 245 case 5: return tr("93.75 %");
Chris@1266 246 }
Chris@153 247 }
Chris@1382 248 if (name == "Oversampling") {
Chris@1382 249 switch (value) {
Chris@1382 250 default:
Chris@1382 251 case 0: return tr("1x");
Chris@1382 252 case 1: return tr("2x");
Chris@1382 253 case 2: return tr("4x");
Chris@1382 254 case 3: return tr("8x");
Chris@1382 255 }
Chris@1382 256 }
Chris@193 257 return SliceLayer::getPropertyValueLabel(name, value);
Chris@153 258 }
Chris@153 259
Chris@167 260 RangeMapper *
Chris@167 261 SpectrumLayer::getNewPropertyRangeMapper(const PropertyName &name) const
Chris@167 262 {
Chris@193 263 return SliceLayer::getNewPropertyRangeMapper(name);
Chris@167 264 }
Chris@167 265
Chris@133 266 void
Chris@153 267 SpectrumLayer::setProperty(const PropertyName &name, int value)
Chris@133 268 {
Chris@193 269 if (name == "Window Size") {
Chris@1266 270 setWindowSize(32 << value);
Chris@153 271 } else if (name == "Window Increment") {
Chris@153 272 setWindowHopLevel(value);
Chris@1382 273 } else if (name == "Oversampling") {
Chris@1382 274 setOversampling(1 << value);
Chris@284 275 } else if (name == "Show Peak Frequencies") {
Chris@284 276 setShowPeaks(value ? true : false);
Chris@193 277 } else {
Chris@193 278 SliceLayer::setProperty(name, value);
Chris@153 279 }
Chris@153 280 }
Chris@153 281
Chris@153 282 void
Chris@805 283 SpectrumLayer::setWindowSize(int ws)
Chris@153 284 {
Chris@153 285 if (m_windowSize == ws) return;
Chris@1389 286
Chris@1389 287 SVDEBUG << "setWindowSize: from " << m_windowSize
Chris@1389 288 << " to " << ws << ": updating min and max bins from "
Chris@1389 289 << m_minbin << " and " << m_maxbin << " to ";
Chris@1389 290
Chris@1403 291 int previousWs = m_windowSize;
Chris@1400 292 m_windowSize = ws;
Chris@1403 293
Chris@1403 294 m_minbin = int(round(getBinForFrequency(m_freqOfMinBin)));
Chris@1403 295 m_maxbin = int(round((double(m_maxbin) / previousWs) * m_windowSize));
Chris@1400 296
Chris@1400 297 int h = getFFTSize() / 2 + 1;
Chris@1400 298 if (m_minbin > h) m_minbin = h;
Chris@1400 299 if (m_maxbin > h) m_maxbin = h;
Chris@1400 300
Chris@1389 301 SVDEBUG << m_minbin << " and " << m_maxbin << endl;
Chris@1389 302
Chris@275 303 m_newFFTNeeded = true;
Chris@153 304 emit layerParametersChanged();
Chris@153 305 }
Chris@153 306
Chris@153 307 void
Chris@805 308 SpectrumLayer::setWindowHopLevel(int v)
Chris@153 309 {
Chris@153 310 if (m_windowHopLevel == v) return;
Chris@153 311 m_windowHopLevel = v;
Chris@275 312 m_newFFTNeeded = true;
Chris@153 313 emit layerParametersChanged();
Chris@153 314 }
Chris@153 315
Chris@153 316 void
Chris@153 317 SpectrumLayer::setWindowType(WindowType w)
Chris@153 318 {
Chris@153 319 if (m_windowType == w) return;
Chris@153 320 m_windowType = w;
Chris@275 321 m_newFFTNeeded = true;
Chris@153 322 emit layerParametersChanged();
Chris@153 323 }
Chris@153 324
Chris@153 325 void
Chris@1382 326 SpectrumLayer::setOversampling(int oversampling)
Chris@1382 327 {
Chris@1382 328 if (m_oversampling == oversampling) return;
Chris@1389 329
Chris@1389 330 SVDEBUG << "setOversampling: from " << m_oversampling
Chris@1389 331 << " to " << oversampling << ": updating min and max bins from "
Chris@1389 332 << m_minbin << " and " << m_maxbin << " to ";
Chris@1389 333
Chris@1403 334 int previousOversampling = m_oversampling;
Chris@1400 335 m_oversampling = oversampling;
Chris@1403 336
Chris@1403 337 m_minbin = int(round(getBinForFrequency(m_freqOfMinBin)));
Chris@1403 338 m_maxbin = int(round((double(m_maxbin) / previousOversampling) *
Chris@1403 339 m_oversampling));
Chris@1400 340
Chris@1400 341 int h = getFFTSize() / 2 + 1;
Chris@1400 342 if (m_minbin > h) m_minbin = h;
Chris@1400 343 if (m_maxbin > h) m_maxbin = h;
Chris@1400 344
Chris@1389 345 SVDEBUG << m_minbin << " and " << m_maxbin << endl;
Chris@1389 346
Chris@1382 347 m_newFFTNeeded = true;
Chris@1382 348 emit layerParametersChanged();
Chris@1382 349 }
Chris@1382 350
Chris@1382 351 int
Chris@1382 352 SpectrumLayer::getOversampling() const
Chris@1382 353 {
Chris@1382 354 return m_oversampling;
Chris@1382 355 }
Chris@1382 356
Chris@1382 357 void
Chris@284 358 SpectrumLayer::setShowPeaks(bool show)
Chris@284 359 {
Chris@284 360 if (m_showPeaks == show) return;
Chris@284 361 m_showPeaks = show;
Chris@284 362 emit layerParametersChanged();
Chris@284 363 }
Chris@284 364
Chris@284 365 void
Chris@153 366 SpectrumLayer::preferenceChanged(PropertyContainer::PropertyName name)
Chris@153 367 {
Chris@153 368 if (name == "Window Type") {
Chris@1382 369 auto type = Preferences::getInstance()->getWindowType();
Chris@1382 370 SVDEBUG << "SpectrumLayer::preferenceChanged: Window type changed to "
Chris@1382 371 << type << endl;
Chris@1382 372 setWindowType(type);
Chris@153 373 return;
Chris@153 374 }
Chris@153 375 }
Chris@153 376
Chris@1403 377 bool
Chris@1403 378 SpectrumLayer::setDisplayExtents(double min, double max)
Chris@1403 379 {
Chris@1403 380 bool result = SliceLayer::setDisplayExtents(min, max);
Chris@1403 381 if (result) {
Chris@1403 382 m_freqOfMinBin = getFrequencyForBin(m_minbin);
Chris@1403 383 }
Chris@1403 384 return result;
Chris@1403 385 }
Chris@1403 386
Chris@1238 387 double
Chris@1386 388 SpectrumLayer::getBinForFrequency(double freq) const
Chris@1386 389 {
Chris@1473 390 auto sliceableModel = ModelById::getAs<DenseThreeDimensionalModel>
Chris@1473 391 (m_sliceableModel);
Chris@1473 392 if (!sliceableModel) return 0;
Chris@1473 393 double bin = (freq * getFFTSize()) / sliceableModel->getSampleRate();
Chris@1386 394 return bin;
Chris@1386 395 }
Chris@1386 396
Chris@1386 397 double
Chris@1386 398 SpectrumLayer::getBinForX(const LayerGeometryProvider *v, double x) const
Chris@1386 399 {
Chris@1473 400 auto sliceableModel = ModelById::getAs<DenseThreeDimensionalModel>
Chris@1473 401 (m_sliceableModel);
Chris@1473 402 if (!sliceableModel) return 0;
Chris@1386 403 double bin = getBinForFrequency(getFrequencyForX(v, x));
Chris@1386 404 return bin;
Chris@1386 405 }
Chris@1386 406
Chris@1386 407 double
Chris@1238 408 SpectrumLayer::getFrequencyForX(const LayerGeometryProvider *v, double x) const
Chris@133 409 {
Chris@1473 410 auto sliceableModel = ModelById::getAs<DenseThreeDimensionalModel>
Chris@1473 411 (m_sliceableModel);
Chris@1473 412 if (!sliceableModel) return 0;
Chris@1394 413
Chris@1394 414 double fmin = getFrequencyForBin(m_minbin);
Chris@1394 415 double fmax = getFrequencyForBin(m_maxbin);
Chris@1394 416
Chris@1394 417 double freq = getScalePointForX(v, x, fmin, fmax);
Chris@1386 418 return freq;
Chris@1386 419 }
Chris@1386 420
Chris@1386 421 double
Chris@1386 422 SpectrumLayer::getFrequencyForBin(double bin) const
Chris@1386 423 {
Chris@1473 424 auto sliceableModel = ModelById::getAs<DenseThreeDimensionalModel>
Chris@1473 425 (m_sliceableModel);
Chris@1473 426 if (!sliceableModel) return 0;
Chris@1473 427 double freq = (bin * sliceableModel->getSampleRate()) / getFFTSize();
Chris@1386 428 return freq;
Chris@1386 429 }
Chris@1386 430
Chris@1386 431 double
Chris@1386 432 SpectrumLayer::getXForBin(const LayerGeometryProvider *v, double bin) const
Chris@1386 433 {
Chris@1473 434 auto sliceableModel = ModelById::getAs<DenseThreeDimensionalModel>
Chris@1473 435 (m_sliceableModel);
Chris@1473 436 if (!sliceableModel) return 0;
Chris@1386 437 double x = getXForFrequency(v, getFrequencyForBin(bin));
Chris@1386 438 return x;
Chris@133 439 }
Chris@133 440
Chris@908 441 double
Chris@1238 442 SpectrumLayer::getXForFrequency(const LayerGeometryProvider *v, double freq) const
Chris@265 443 {
Chris@1473 444 auto sliceableModel = ModelById::getAs<DenseThreeDimensionalModel>
Chris@1473 445 (m_sliceableModel);
Chris@1473 446 if (!sliceableModel) return 0;
Chris@1394 447
Chris@1394 448 double fmin = getFrequencyForBin(m_minbin);
Chris@1394 449 double fmax = getFrequencyForBin(m_maxbin);
Chris@1394 450 double x = getXForScalePoint(v, freq, fmin, fmax);
Chris@1399 451
Chris@1386 452 return x;
Chris@254 453 }
Chris@254 454
Chris@260 455 bool
Chris@918 456 SpectrumLayer::getXScaleValue(const LayerGeometryProvider *v, int x,
Chris@908 457 double &value, QString &unit) const
Chris@260 458 {
Chris@1238 459 value = getFrequencyForX(v, x);
Chris@260 460 unit = "Hz";
Chris@260 461 return true;
Chris@260 462 }
Chris@260 463
Chris@264 464 bool
Chris@918 465 SpectrumLayer::getYScaleValue(const LayerGeometryProvider *v, int y,
Chris@908 466 double &value, QString &unit) const
Chris@274 467 {
Chris@1238 468 value = getValueForY(v, y);
Chris@274 469
Chris@274 470 if (m_energyScale == dBScale || m_energyScale == MeterScale) {
Chris@274 471
Chris@908 472 if (value > 0.0) {
Chris@908 473 value = 10.0 * log10(value);
Chris@1473 474 if (value < m_threshold) {
Chris@1473 475 value = m_threshold;
Chris@1473 476 }
Chris@1473 477 } else {
Chris@1473 478 value = m_threshold;
Chris@1473 479 }
Chris@274 480
Chris@274 481 unit = "dBV";
Chris@274 482
Chris@274 483 } else {
Chris@274 484 unit = "V";
Chris@274 485 }
Chris@274 486
Chris@274 487 return true;
Chris@274 488 }
Chris@274 489
Chris@274 490 bool
Chris@918 491 SpectrumLayer::getYScaleDifference(const LayerGeometryProvider *v, int y0, int y1,
Chris@908 492 double &diff, QString &unit) const
Chris@274 493 {
Chris@274 494 bool rv = SliceLayer::getYScaleDifference(v, y0, y1, diff, unit);
Chris@274 495 if (rv && (unit == "dBV")) unit = "dB";
Chris@274 496 return rv;
Chris@274 497 }
Chris@274 498
Chris@274 499
Chris@274 500 bool
Chris@918 501 SpectrumLayer::getCrosshairExtents(LayerGeometryProvider *v, QPainter &paint,
Chris@264 502 QPoint cursorPos,
Chris@264 503 std::vector<QRect> &extents) const
Chris@264 504 {
Chris@918 505 QRect vertical(cursorPos.x(), cursorPos.y(), 1, v->getPaintHeight() - cursorPos.y());
Chris@264 506 extents.push_back(vertical);
Chris@264 507
Chris@918 508 QRect horizontal(0, cursorPos.y(), v->getPaintWidth(), 12);
Chris@264 509 extents.push_back(horizontal);
Chris@264 510
Chris@280 511 int hoffset = 2;
Chris@280 512 if (m_binScale == LogBins) hoffset = 13;
Chris@278 513
Chris@607 514 int sw = getVerticalScaleWidth(v, false, paint);
Chris@280 515
Chris@1473 516 // Qt 5.13 deprecates QFontMetrics::width(), but its suggested
Chris@1473 517 // replacement (horizontalAdvance) was only added in Qt 5.11
Chris@1473 518 // which is too new for us
Chris@1473 519 #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
Chris@1473 520
Chris@280 521 QRect value(sw, cursorPos.y() - paint.fontMetrics().ascent() - 2,
Chris@280 522 paint.fontMetrics().width("0.0000001 V") + 2,
Chris@264 523 paint.fontMetrics().height());
Chris@280 524 extents.push_back(value);
Chris@280 525
Chris@280 526 QRect log(sw, cursorPos.y() + 2,
Chris@280 527 paint.fontMetrics().width("-80.000 dBV") + 2,
Chris@280 528 paint.fontMetrics().height());
Chris@280 529 extents.push_back(log);
Chris@280 530
Chris@280 531 QRect freq(cursorPos.x(),
Chris@918 532 v->getPaintHeight() - paint.fontMetrics().height() - hoffset,
Chris@280 533 paint.fontMetrics().width("123456 Hz") + 2,
Chris@280 534 paint.fontMetrics().height());
Chris@280 535 extents.push_back(freq);
Chris@264 536
Chris@278 537 int w(paint.fontMetrics().width("C#10+50c") + 2);
Chris@278 538 QRect pitch(cursorPos.x() - w,
Chris@918 539 v->getPaintHeight() - paint.fontMetrics().height() - hoffset,
Chris@278 540 w,
Chris@278 541 paint.fontMetrics().height());
Chris@278 542 extents.push_back(pitch);
Chris@278 543
Chris@264 544 return true;
Chris@264 545 }
Chris@264 546
Chris@254 547 void
Chris@918 548 SpectrumLayer::paintCrosshairs(LayerGeometryProvider *v, QPainter &paint,
Chris@254 549 QPoint cursorPos) const
Chris@254 550 {
Chris@1473 551 auto sliceableModel = ModelById::getAs<DenseThreeDimensionalModel>
Chris@1473 552 (m_sliceableModel);
Chris@1473 553 if (!sliceableModel) return;
Chris@280 554
Chris@254 555 paint.save();
Chris@282 556 QFont fn = paint.font();
Chris@282 557 if (fn.pointSize() > 8) {
Chris@282 558 fn.setPointSize(fn.pointSize() - 1);
Chris@282 559 paint.setFont(fn);
Chris@282 560 }
Chris@254 561
Chris@1362 562 ColourMapper mapper(m_colourMap, m_colourInverted, 0, 1);
Chris@254 563 paint.setPen(mapper.getContrastingColour());
Chris@254 564
Chris@1238 565 int xorigin = m_xorigins[v->getId()];
Chris@918 566 paint.drawLine(xorigin, cursorPos.y(), v->getPaintWidth(), cursorPos.y());
Chris@918 567 paint.drawLine(cursorPos.x(), cursorPos.y(), cursorPos.x(), v->getPaintHeight());
Chris@254 568
Chris@1238 569 double fundamental = getFrequencyForX(v, cursorPos.x());
Chris@254 570
Chris@1392 571 int hoffset = getHorizontalScaleHeight(v, paint) +
Chris@1392 572 2 * paint.fontMetrics().height();
Chris@278 573
Chris@1078 574 PaintAssistant::drawVisibleText(v, paint,
Chris@1238 575 cursorPos.x() + 2,
Chris@1238 576 v->getPaintHeight() - 2 - hoffset,
Chris@1392 577 tr("%1 Hz").arg(fundamental),
Chris@1238 578 PaintAssistant::OutlinedText);
Chris@278 579
Chris@278 580 if (Pitch::isFrequencyInMidiRange(fundamental)) {
Chris@278 581 QString pitchLabel = Pitch::getPitchLabelForFrequency(fundamental);
Chris@1078 582 PaintAssistant::drawVisibleText(v, paint,
Chris@1238 583 cursorPos.x() -
Chris@1238 584 paint.fontMetrics().width(pitchLabel) - 2,
Chris@1238 585 v->getPaintHeight() - 2 - hoffset,
Chris@1238 586 pitchLabel,
Chris@1238 587 PaintAssistant::OutlinedText);
Chris@278 588 }
Chris@264 589
Chris@1238 590 double value = getValueForY(v, cursorPos.y());
Chris@280 591
Chris@1078 592 PaintAssistant::drawVisibleText(v, paint,
Chris@280 593 xorigin + 2,
Chris@280 594 cursorPos.y() - 2,
Chris@280 595 QString("%1 V").arg(value),
Chris@1078 596 PaintAssistant::OutlinedText);
Chris@280 597
Chris@1392 598 if (value > m_threshold) {
Chris@1392 599 double db = 10.0 * log10(value);
Chris@1392 600 PaintAssistant::drawVisibleText(v, paint,
Chris@1392 601 xorigin + 2,
Chris@1392 602 cursorPos.y() + 2 +
Chris@1392 603 paint.fontMetrics().ascent(),
Chris@1392 604 QString("%1 dBV").arg(db),
Chris@1392 605 PaintAssistant::OutlinedText);
Chris@1392 606 }
Chris@280 607
Chris@254 608 int harmonic = 2;
Chris@254 609
Chris@254 610 while (harmonic < 100) {
Chris@254 611
Chris@1238 612 int hx = int(lrint(getXForFrequency(v, fundamental * harmonic)));
Chris@254 613
Chris@918 614 if (hx < xorigin || hx > v->getPaintWidth()) break;
Chris@254 615
Chris@254 616 int len = 7;
Chris@254 617
Chris@254 618 if (harmonic % 2 == 0) {
Chris@254 619 if (harmonic % 4 == 0) {
Chris@254 620 len = 12;
Chris@254 621 } else {
Chris@254 622 len = 10;
Chris@254 623 }
Chris@254 624 }
Chris@254 625
Chris@908 626 paint.drawLine(hx,
Chris@254 627 cursorPos.y(),
Chris@908 628 hx,
Chris@254 629 cursorPos.y() + len);
Chris@254 630
Chris@254 631 ++harmonic;
Chris@254 632 }
Chris@254 633
Chris@254 634 paint.restore();
Chris@254 635 }
Chris@254 636
Chris@199 637 QString
Chris@918 638 SpectrumLayer::getFeatureDescription(LayerGeometryProvider *v, QPoint &p) const
Chris@199 639 {
Chris@1473 640 auto sliceableModel = ModelById::getAs<DenseThreeDimensionalModel>
Chris@1473 641 (m_sliceableModel);
Chris@1473 642 if (!sliceableModel) return "";
Chris@199 643
Chris@199 644 int minbin = 0, maxbin = 0, range = 0;
Chris@805 645 QString genericDesc = SliceLayer::getFeatureDescriptionAux
Chris@199 646 (v, p, false, minbin, maxbin, range);
Chris@199 647
Chris@199 648 if (genericDesc == "") return "";
Chris@199 649
Chris@1238 650 int i0 = minbin - m_minbin;
Chris@1238 651 int i1 = maxbin - m_minbin;
Chris@1238 652
Chris@1238 653 float minvalue = 0.0;
Chris@1238 654 if (in_range_for(m_values, i0)) minvalue = m_values[i0];
Chris@199 655
Chris@1238 656 float maxvalue = minvalue;
Chris@1238 657 if (in_range_for(m_values, i1)) maxvalue = m_values[i1];
Chris@1238 658
Chris@199 659 if (minvalue > maxvalue) std::swap(minvalue, maxvalue);
Chris@199 660
Chris@199 661 QString binstr;
Chris@199 662 QString hzstr;
Chris@1473 663 int minfreq = int(lrint((minbin * sliceableModel->getSampleRate()) /
Chris@1382 664 getFFTSize()));
Chris@1256 665 int maxfreq = int(lrint((std::max(maxbin, minbin)
Chris@1473 666 * sliceableModel->getSampleRate()) /
Chris@1382 667 getFFTSize()));
Chris@199 668
Chris@199 669 if (maxbin != minbin) {
Chris@199 670 binstr = tr("%1 - %2").arg(minbin+1).arg(maxbin+1);
Chris@199 671 } else {
Chris@199 672 binstr = QString("%1").arg(minbin+1);
Chris@199 673 }
Chris@199 674 if (minfreq != maxfreq) {
Chris@199 675 hzstr = tr("%1 - %2 Hz").arg(minfreq).arg(maxfreq);
Chris@199 676 } else {
Chris@199 677 hzstr = tr("%1 Hz").arg(minfreq);
Chris@199 678 }
Chris@199 679
Chris@199 680 QString valuestr;
Chris@199 681 if (maxvalue != minvalue) {
Chris@199 682 valuestr = tr("%1 - %2").arg(minvalue).arg(maxvalue);
Chris@199 683 } else {
Chris@199 684 valuestr = QString("%1").arg(minvalue);
Chris@199 685 }
Chris@199 686
Chris@199 687 QString dbstr;
Chris@908 688 double mindb = AudioLevel::multiplier_to_dB(minvalue);
Chris@908 689 double maxdb = AudioLevel::multiplier_to_dB(maxvalue);
Chris@199 690 QString mindbstr;
Chris@199 691 QString maxdbstr;
Chris@199 692 if (mindb == AudioLevel::DB_FLOOR) {
Chris@1147 693 mindbstr = Strings::minus_infinity;
Chris@199 694 } else {
Chris@908 695 mindbstr = QString("%1").arg(lrint(mindb));
Chris@199 696 }
Chris@199 697 if (maxdb == AudioLevel::DB_FLOOR) {
Chris@1147 698 maxdbstr = Strings::minus_infinity;
Chris@199 699 } else {
Chris@908 700 maxdbstr = QString("%1").arg(lrint(maxdb));
Chris@199 701 }
Chris@908 702 if (lrint(mindb) != lrint(maxdb)) {
Chris@199 703 dbstr = tr("%1 - %2").arg(mindbstr).arg(maxdbstr);
Chris@199 704 } else {
Chris@199 705 dbstr = tr("%1").arg(mindbstr);
Chris@199 706 }
Chris@199 707
Chris@199 708 QString description;
Chris@199 709
Chris@1473 710 if (range > int(sliceableModel->getResolution())) {
Chris@199 711 description = tr("%1\nBin:\t%2 (%3)\n%4 value:\t%5\ndB:\t%6")
Chris@199 712 .arg(genericDesc)
Chris@199 713 .arg(binstr)
Chris@199 714 .arg(hzstr)
Chris@199 715 .arg(m_samplingMode == NearestSample ? tr("First") :
Chris@199 716 m_samplingMode == SampleMean ? tr("Mean") : tr("Peak"))
Chris@199 717 .arg(valuestr)
Chris@199 718 .arg(dbstr);
Chris@199 719 } else {
Chris@199 720 description = tr("%1\nBin:\t%2 (%3)\nValue:\t%4\ndB:\t%5")
Chris@199 721 .arg(genericDesc)
Chris@199 722 .arg(binstr)
Chris@199 723 .arg(hzstr)
Chris@199 724 .arg(valuestr)
Chris@199 725 .arg(dbstr);
Chris@199 726 }
Chris@199 727
Chris@199 728 return description;
Chris@199 729 }
Chris@199 730
Chris@254 731 void
Chris@916 732 SpectrumLayer::paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const
Chris@275 733 {
Chris@1473 734 auto originModel = ModelById::get(m_originModel);
Chris@1473 735 if (!originModel || !originModel->isOK() || !originModel->isReady()) {
Chris@587 736 SVDEBUG << "SpectrumLayer::paint: no origin model, or origin model not OK or not ready" << endl;
Chris@349 737 return;
Chris@349 738 }
Chris@275 739
Chris@275 740 if (m_newFFTNeeded) {
Chris@587 741 SVDEBUG << "SpectrumLayer::paint: new FFT needed, calling setupFFT" << endl;
Chris@275 742 const_cast<SpectrumLayer *>(this)->setupFFT(); //ugh
Chris@275 743 }
Chris@277 744
Chris@1473 745 auto fft = ModelById::getAs<FFTModel>(m_sliceableModel);
Chris@1473 746 if (!fft) return;
Chris@277 747
Chris@1382 748 double thresh = (pow(10, -6) / m_gain) * (getFFTSize() / 2.0); // -60dB adj
Chris@277 749
Chris@607 750 int xorigin = getVerticalScaleWidth(v, false, paint) + 1;
Chris@1281 751 int scaleHeight = getHorizontalScaleHeight(v, paint);
Chris@345 752
Chris@1391 753 QPoint localPos;
Chris@1391 754 bool shouldIlluminate = v->shouldIlluminateLocalFeatures(this, localPos);
Chris@1391 755
Chris@1398 756 int illuminateX = 0;
Chris@1398 757 double illuminateFreq = 0.0;
Chris@1398 758 double illuminateLevel = 0.0;
Chris@1398 759
Chris@1398 760 ColourMapper mapper =
Chris@1398 761 hasLightBackground() ?
Chris@1398 762 ColourMapper(ColourMapper::BlackOnWhite, m_colourInverted, 0, 1) :
Chris@1398 763 ColourMapper(ColourMapper::WhiteOnBlack, m_colourInverted, 0, 1);
Chris@1398 764
Chris@1392 765 // cerr << "shouldIlluminate = " << shouldIlluminate << ", localPos = " << localPos.x() << "," << localPos.y() << endl;
Chris@1392 766
Chris@284 767 if (fft && m_showPeaks) {
Chris@277 768
Chris@277 769 // draw peak lines
Chris@277 770
Chris@908 771 int col = int(v->getCentreFrame() / fft->getResolution());
Chris@277 772
Chris@277 773 paint.save();
Chris@277 774 paint.setRenderHint(QPainter::Antialiasing, false);
Chris@1281 775
Chris@290 776 int peakminbin = 0;
Chris@290 777 int peakmaxbin = fft->getHeight() - 1;
Chris@908 778 double peakmaxfreq = Pitch::getFrequencyForPitch(128);
Chris@1387 779 peakmaxbin = int(((peakmaxfreq * fft->getHeight() * 2) /
Chris@1387 780 fft->getSampleRate()));
Chris@290 781
Chris@280 782 FFTModel::PeakSet peaks = fft->getPeakFrequencies
Chris@290 783 (FFTModel::MajorPitchAdaptivePeaks, col, peakminbin, peakmaxbin);
Chris@280 784
Chris@277 785 BiasCurve curve;
Chris@277 786 getBiasCurve(curve);
Chris@908 787 int cs = int(curve.size());
Chris@280 788
Chris@1387 789 int px = -1;
Chris@1387 790
Chris@1392 791 int fuzz = ViewManager::scalePixelSize(3);
Chris@1391 792
Chris@280 793 for (FFTModel::PeakSet::iterator i = peaks.begin();
Chris@280 794 i != peaks.end(); ++i) {
Chris@280 795
Chris@1387 796 double freq = i->second;
Chris@1387 797 int x = int(lrint(getXForFrequency(v, freq)));
Chris@1387 798 if (x == px) {
Chris@1387 799 continue;
Chris@1387 800 }
Chris@1391 801
Chris@805 802 int bin = i->first;
Chris@277 803
Chris@682 804 // cerr << "bin = " << bin << ", thresh = " << thresh << ", value = " << fft->getMagnitudeAt(col, bin) << endl;
Chris@280 805
Chris@1385 806 double value = fft->getValueAt(col, bin);
Chris@1385 807 if (value < thresh) continue;
Chris@1385 808 if (bin < cs) value *= curve[bin];
Chris@1392 809
Chris@1392 810 double norm = 0.f;
Chris@1396 811 // we only need the norm here, for the colour map
Chris@1396 812 (void)getYForValue(v, value, norm);
Chris@1391 813
Chris@1392 814 QColor colour = mapper.map(norm);
Chris@1392 815
Chris@1392 816 paint.setPen(QPen(colour, 1));
Chris@1392 817 paint.drawLine(x, 0, x, v->getPaintHeight() - scaleHeight - 1);
Chris@1392 818
Chris@1398 819 if (shouldIlluminate && std::abs(localPos.x() - x) <= fuzz) {
Chris@1398 820 illuminateX = x;
Chris@1398 821 illuminateFreq = freq;
Chris@1398 822 illuminateLevel = norm;
Chris@1391 823 }
Chris@277 824
Chris@1387 825 px = x;
Chris@277 826 }
Chris@277 827
Chris@277 828 paint.restore();
Chris@277 829 }
Chris@275 830
Chris@1281 831 paint.save();
Chris@1281 832
Chris@275 833 SliceLayer::paint(v, paint, rect);
Chris@1281 834
Chris@1281 835 paintHorizontalScale(v, paint, xorigin);
Chris@277 836
Chris@1281 837 paint.restore();
Chris@1398 838
Chris@1398 839 if (illuminateFreq > 0.0) {
Chris@1398 840
Chris@1398 841 QColor colour = mapper.map(illuminateLevel);
Chris@1398 842 paint.setPen(QPen(colour, 1));
Chris@1398 843
Chris@1398 844 int labelY = v->getPaintHeight() -
Chris@1398 845 getHorizontalScaleHeight(v, paint) -
Chris@1398 846 paint.fontMetrics().height() * 4;
Chris@1398 847
Chris@1398 848 QString text = tr("%1 Hz").arg(illuminateFreq);
Chris@1398 849 int lw = paint.fontMetrics().width(text);
Chris@1398 850
Chris@1398 851 int gap = ViewManager::scalePixelSize(v->getXForViewX(3));
Chris@1398 852 double half = double(gap)/2.0;
Chris@1398 853
Chris@1398 854 int labelX = illuminateX - lw - gap;
Chris@1398 855 if (labelX < getVerticalScaleWidth(v, false, paint)) {
Chris@1398 856 labelX = illuminateX + gap;
Chris@1398 857 }
Chris@1398 858
Chris@1398 859 PaintAssistant::drawVisibleText
Chris@1398 860 (v, paint, labelX, labelY,
Chris@1398 861 text, PaintAssistant::OutlinedText);
Chris@1398 862
Chris@1398 863 if (Pitch::isFrequencyInMidiRange(illuminateFreq)) {
Chris@1398 864 QString pitchLabel = Pitch::getPitchLabelForFrequency
Chris@1398 865 (illuminateFreq);
Chris@1398 866 PaintAssistant::drawVisibleText
Chris@1398 867 (v, paint,
Chris@1398 868 labelX, labelY + paint.fontMetrics().ascent() + gap,
Chris@1398 869 pitchLabel, PaintAssistant::OutlinedText);
Chris@1398 870 }
Chris@1398 871 paint.fillRect(QRectF(illuminateX - half, labelY + gap, gap, gap),
Chris@1398 872 colour);
Chris@1398 873 }
Chris@1281 874 }
Chris@1281 875
Chris@1281 876 int
Chris@1281 877 SpectrumLayer::getHorizontalScaleHeight(LayerGeometryProvider *v,
Chris@1281 878 QPainter &paint) const
Chris@1281 879 {
Chris@1281 880 int pkh = int(paint.fontMetrics().height() * 0.7 + 0.5);
Chris@1281 881 if (pkh < 10) pkh = 10;
Chris@1281 882
Chris@1281 883 int scaleh = HorizontalFrequencyScale().getHeight(v, paint);
Chris@1281 884
Chris@1281 885 return pkh + scaleh;
Chris@1281 886 }
Chris@1281 887
Chris@1281 888 void
Chris@1281 889 SpectrumLayer::paintHorizontalScale(LayerGeometryProvider *v,
Chris@1281 890 QPainter &paint,
Chris@1281 891 int xorigin) const
Chris@1281 892 {
Chris@278 893 //!!! All of this stuff relating to depicting frequencies
Chris@1238 894 // (keyboard, crosshairs etc) should be applicable to any slice
Chris@1238 895 // layer whose model has a vertical scale unit of Hz. However,
Chris@1238 896 // the dense 3d model at the moment doesn't record its vertical
Chris@1238 897 // scale unit -- we need to fix that and hoist this code as
Chris@1238 898 // appropriate. Same really goes for any code in SpectrogramLayer
Chris@1238 899 // that could be relevant to Colour3DPlotLayer with unit Hz, but
Chris@1238 900 // that's a bigger proposition.
Chris@278 901
Chris@1281 902 if (!v->getViewManager()->shouldShowHorizontalValueScale()) {
Chris@1281 903 return;
Chris@1281 904 }
Chris@1281 905
Chris@1281 906 int totalScaleHeight = getHorizontalScaleHeight(v, paint); // inc piano
Chris@1281 907 int freqScaleHeight = HorizontalFrequencyScale().getHeight(v, paint);
Chris@1281 908 int paintHeight = v->getPaintHeight();
Chris@1281 909 int paintWidth = v->getPaintWidth();
Chris@277 910
Chris@1238 911 PianoScale().paintPianoHorizontal
Chris@1276 912 (v, this, paint,
Chris@1281 913 QRect(xorigin, paintHeight - totalScaleHeight - 1,
Chris@1281 914 paintWidth - 1, totalScaleHeight - freqScaleHeight));
Chris@345 915
Chris@1281 916 int scaleLeft = int(getXForBin(v, 1));
Chris@1281 917
Chris@1281 918 paint.drawLine(int(getXForBin(v, 0)), paintHeight - freqScaleHeight,
Chris@1281 919 scaleLeft, paintHeight - freqScaleHeight);
Chris@1281 920
Chris@1281 921 QString hz = tr("Hz");
Chris@1281 922 int hzw = paint.fontMetrics().width(hz);
Chris@1281 923 if (scaleLeft > hzw + 5) {
Chris@1281 924 paint.drawText
Chris@1281 925 (scaleLeft - hzw - 5,
Chris@1281 926 paintHeight - freqScaleHeight + paint.fontMetrics().ascent() + 5,
Chris@1281 927 hz);
Chris@1281 928 }
Chris@1281 929
Chris@1281 930 HorizontalFrequencyScale().paintScale
Chris@1276 931 (v, this, paint,
Chris@1281 932 QRect(scaleLeft, paintHeight - freqScaleHeight,
Chris@1281 933 paintWidth, totalScaleHeight),
Chris@1281 934 m_binScale == LogBins);
Chris@275 935 }
Chris@275 936
Chris@275 937 void
Chris@254 938 SpectrumLayer::getBiasCurve(BiasCurve &curve) const
Chris@254 939 {
Chris@254 940 curve = m_biasCurve;
Chris@254 941 }
Chris@199 942
Chris@316 943 void
Chris@316 944 SpectrumLayer::toXml(QTextStream &stream,
Chris@316 945 QString indent, QString extraAttributes) const
Chris@220 946 {
Chris@316 947 QString s = QString("windowSize=\"%1\" "
Chris@456 948 "windowHopLevel=\"%2\" "
Chris@1382 949 "oversampling=\"%3\" "
Chris@1382 950 "showPeaks=\"%4\" ")
Chris@220 951 .arg(m_windowSize)
Chris@456 952 .arg(m_windowHopLevel)
Chris@1382 953 .arg(m_oversampling)
Chris@456 954 .arg(m_showPeaks ? "true" : "false");
Chris@220 955
Chris@316 956 SliceLayer::toXml(stream, indent, extraAttributes + " " + s);
Chris@220 957 }
Chris@220 958
Chris@220 959 void
Chris@220 960 SpectrumLayer::setProperties(const QXmlAttributes &attributes)
Chris@220 961 {
Chris@220 962 SliceLayer::setProperties(attributes);
Chris@220 963
Chris@220 964 bool ok = false;
Chris@220 965
Chris@805 966 int windowSize = attributes.value("windowSize").toUInt(&ok);
Chris@220 967 if (ok) setWindowSize(windowSize);
Chris@220 968
Chris@805 969 int windowHopLevel = attributes.value("windowHopLevel").toUInt(&ok);
Chris@220 970 if (ok) setWindowHopLevel(windowHopLevel);
Chris@456 971
Chris@1382 972 int oversampling = attributes.value("oversampling").toUInt(&ok);
Chris@1382 973 if (ok) setOversampling(oversampling);
Chris@1382 974
Chris@456 975 bool showPeaks = (attributes.value("showPeaks").trimmed() == "true");
Chris@456 976 setShowPeaks(showPeaks);
Chris@220 977 }
Chris@220 978
Chris@220 979