annotate layer/SpectrumLayer.cpp @ 1439:b22a4df37095 single-point

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