annotate layer/SpectrumLayer.cpp @ 1386:fc3d89f88690 spectrogramparam

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