annotate layer/SpectrumLayer.cpp @ 1399:ba1f0234efa7

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