annotate layer/SpectrumLayer.cpp @ 1447:8afea53332f3 single-point

Add option to make pane sizes auto-resize-only (i.e. remove user control via a splitter); also place alignment views above panes instead of below, meaning the extra bit of space that we currently have for the pane without one at least goes to the primary pane
author Chris Cannam
date Tue, 30 Apr 2019 15:53:21 +0100
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