annotate layer/SpectrumLayer.cpp @ 1477:0769eaacc6bf by-id

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