annotate layer/SpectrumLayer.cpp @ 1402:b0eeec95ab5b

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