annotate layer/SpectrumLayer.cpp @ 1482:c1cae369979d by-id

Comment
author Chris Cannam
date Fri, 12 Jul 2019 16:29:59 +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