annotate layer/SpectrumLayer.cpp @ 1389:1eb560b363e7 spectrogramparam

Make "zoom to region" work sensibly for slice/spectrum layers; ensure that min/max bin are remapped properly when changing fft size
author Chris Cannam
date Tue, 13 Nov 2018 14:06:48 +0000
parents bca9870301b7
children c39f2d439d59
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@1389 290
Chris@1389 291 SVDEBUG << "setWindowSize: from " << m_windowSize
Chris@1389 292 << " to " << ws << ": updating min and max bins from "
Chris@1389 293 << m_minbin << " and " << m_maxbin << " to ";
Chris@1389 294
Chris@1389 295 m_minbin = int(round((double(m_minbin) / m_windowSize) * ws));
Chris@1389 296 m_maxbin = int(round((double(m_maxbin) / m_windowSize) * ws));
Chris@1389 297
Chris@1389 298 SVDEBUG << m_minbin << " and " << m_maxbin << endl;
Chris@1389 299
Chris@153 300 m_windowSize = ws;
Chris@275 301 m_newFFTNeeded = true;
Chris@153 302 emit layerParametersChanged();
Chris@153 303 }
Chris@153 304
Chris@153 305 void
Chris@805 306 SpectrumLayer::setWindowHopLevel(int v)
Chris@153 307 {
Chris@153 308 if (m_windowHopLevel == v) return;
Chris@153 309 m_windowHopLevel = v;
Chris@275 310 m_newFFTNeeded = true;
Chris@153 311 emit layerParametersChanged();
Chris@153 312 }
Chris@153 313
Chris@153 314 void
Chris@153 315 SpectrumLayer::setWindowType(WindowType w)
Chris@153 316 {
Chris@153 317 if (m_windowType == w) return;
Chris@153 318 m_windowType = w;
Chris@275 319 m_newFFTNeeded = true;
Chris@153 320 emit layerParametersChanged();
Chris@153 321 }
Chris@153 322
Chris@153 323 void
Chris@1382 324 SpectrumLayer::setOversampling(int oversampling)
Chris@1382 325 {
Chris@1382 326 if (m_oversampling == oversampling) return;
Chris@1389 327
Chris@1389 328 SVDEBUG << "setOversampling: from " << m_oversampling
Chris@1389 329 << " to " << oversampling << ": updating min and max bins from "
Chris@1389 330 << m_minbin << " and " << m_maxbin << " to ";
Chris@1389 331
Chris@1389 332 m_minbin = int(round((double(m_minbin) / m_oversampling) * oversampling));
Chris@1389 333 m_maxbin = int(round((double(m_maxbin) / m_oversampling) * oversampling));
Chris@1389 334
Chris@1389 335 SVDEBUG << m_minbin << " and " << m_maxbin << endl;
Chris@1389 336
Chris@1382 337 m_oversampling = oversampling;
Chris@1382 338 m_newFFTNeeded = true;
Chris@1389 339
Chris@1382 340 emit layerParametersChanged();
Chris@1382 341 }
Chris@1382 342
Chris@1382 343 int
Chris@1382 344 SpectrumLayer::getOversampling() const
Chris@1382 345 {
Chris@1382 346 return m_oversampling;
Chris@1382 347 }
Chris@1382 348
Chris@1382 349 void
Chris@284 350 SpectrumLayer::setShowPeaks(bool show)
Chris@284 351 {
Chris@284 352 if (m_showPeaks == show) return;
Chris@284 353 m_showPeaks = show;
Chris@284 354 emit layerParametersChanged();
Chris@284 355 }
Chris@284 356
Chris@284 357 void
Chris@153 358 SpectrumLayer::preferenceChanged(PropertyContainer::PropertyName name)
Chris@153 359 {
Chris@153 360 if (name == "Window Type") {
Chris@1382 361 auto type = Preferences::getInstance()->getWindowType();
Chris@1382 362 SVDEBUG << "SpectrumLayer::preferenceChanged: Window type changed to "
Chris@1382 363 << type << endl;
Chris@1382 364 setWindowType(type);
Chris@153 365 return;
Chris@153 366 }
Chris@153 367 }
Chris@153 368
Chris@1238 369 double
Chris@1386 370 SpectrumLayer::getBinForFrequency(double freq) const
Chris@1386 371 {
Chris@1386 372 if (!m_sliceableModel) return 0;
Chris@1386 373 double bin = (freq * getFFTSize()) / m_sliceableModel->getSampleRate();
Chris@1386 374 // we assume the frequency of a bin corresponds to the centre of
Chris@1386 375 // its visual range
Chris@1386 376 bin += 0.5;
Chris@1386 377 return bin;
Chris@1386 378 }
Chris@1386 379
Chris@1386 380 double
Chris@1386 381 SpectrumLayer::getBinForX(const LayerGeometryProvider *v, double x) const
Chris@1386 382 {
Chris@1386 383 if (!m_sliceableModel) return 0;
Chris@1386 384 double bin = getBinForFrequency(getFrequencyForX(v, x));
Chris@1386 385 return bin;
Chris@1386 386 }
Chris@1386 387
Chris@1386 388 double
Chris@1238 389 SpectrumLayer::getFrequencyForX(const LayerGeometryProvider *v, double x) const
Chris@133 390 {
Chris@1238 391 if (!m_sliceableModel) return 0;
Chris@1386 392 double freq = getScalePointForX(v, x,
Chris@1386 393 getFrequencyForBin(m_minbin),
Chris@1386 394 getFrequencyForBin(m_maxbin));
Chris@1386 395 return freq;
Chris@1386 396 }
Chris@1386 397
Chris@1386 398 double
Chris@1386 399 SpectrumLayer::getFrequencyForBin(double bin) const
Chris@1386 400 {
Chris@1386 401 if (!m_sliceableModel) return 0;
Chris@1281 402 // we assume the frequency of a bin corresponds to the centre of
Chris@1281 403 // its visual range
Chris@1281 404 bin -= 0.5;
Chris@1386 405 double freq = (bin * m_sliceableModel->getSampleRate()) / getFFTSize();
Chris@1386 406 return freq;
Chris@1386 407 }
Chris@1386 408
Chris@1386 409 double
Chris@1386 410 SpectrumLayer::getXForBin(const LayerGeometryProvider *v, double bin) const
Chris@1386 411 {
Chris@1386 412 if (!m_sliceableModel) return 0;
Chris@1386 413 double x = getXForFrequency(v, getFrequencyForBin(bin));
Chris@1386 414 return x;
Chris@133 415 }
Chris@133 416
Chris@908 417 double
Chris@1238 418 SpectrumLayer::getXForFrequency(const LayerGeometryProvider *v, double freq) const
Chris@265 419 {
Chris@280 420 if (!m_sliceableModel) return 0;
Chris@1386 421 double x = getXForScalePoint(v, freq,
Chris@1386 422 getFrequencyForBin(m_minbin),
Chris@1386 423 getFrequencyForBin(m_maxbin));
Chris@1386 424 return x;
Chris@254 425 }
Chris@254 426
Chris@260 427 bool
Chris@918 428 SpectrumLayer::getXScaleValue(const LayerGeometryProvider *v, int x,
Chris@908 429 double &value, QString &unit) const
Chris@260 430 {
Chris@1238 431 value = getFrequencyForX(v, x);
Chris@260 432 unit = "Hz";
Chris@260 433 return true;
Chris@260 434 }
Chris@260 435
Chris@264 436 bool
Chris@918 437 SpectrumLayer::getYScaleValue(const LayerGeometryProvider *v, int y,
Chris@908 438 double &value, QString &unit) const
Chris@274 439 {
Chris@1238 440 value = getValueForY(v, y);
Chris@274 441
Chris@274 442 if (m_energyScale == dBScale || m_energyScale == MeterScale) {
Chris@274 443
Chris@908 444 if (value > 0.0) {
Chris@908 445 value = 10.0 * log10(value);
Chris@284 446 if (value < m_threshold) value = m_threshold;
Chris@284 447 } else value = m_threshold;
Chris@274 448
Chris@274 449 unit = "dBV";
Chris@274 450
Chris@274 451 } else {
Chris@274 452 unit = "V";
Chris@274 453 }
Chris@274 454
Chris@274 455 return true;
Chris@274 456 }
Chris@274 457
Chris@274 458 bool
Chris@918 459 SpectrumLayer::getYScaleDifference(const LayerGeometryProvider *v, int y0, int y1,
Chris@908 460 double &diff, QString &unit) const
Chris@274 461 {
Chris@274 462 bool rv = SliceLayer::getYScaleDifference(v, y0, y1, diff, unit);
Chris@274 463 if (rv && (unit == "dBV")) unit = "dB";
Chris@274 464 return rv;
Chris@274 465 }
Chris@274 466
Chris@274 467
Chris@274 468 bool
Chris@918 469 SpectrumLayer::getCrosshairExtents(LayerGeometryProvider *v, QPainter &paint,
Chris@264 470 QPoint cursorPos,
Chris@264 471 std::vector<QRect> &extents) const
Chris@264 472 {
Chris@918 473 QRect vertical(cursorPos.x(), cursorPos.y(), 1, v->getPaintHeight() - cursorPos.y());
Chris@264 474 extents.push_back(vertical);
Chris@264 475
Chris@918 476 QRect horizontal(0, cursorPos.y(), v->getPaintWidth(), 12);
Chris@264 477 extents.push_back(horizontal);
Chris@264 478
Chris@280 479 int hoffset = 2;
Chris@280 480 if (m_binScale == LogBins) hoffset = 13;
Chris@278 481
Chris@607 482 int sw = getVerticalScaleWidth(v, false, paint);
Chris@280 483
Chris@280 484 QRect value(sw, cursorPos.y() - paint.fontMetrics().ascent() - 2,
Chris@280 485 paint.fontMetrics().width("0.0000001 V") + 2,
Chris@264 486 paint.fontMetrics().height());
Chris@280 487 extents.push_back(value);
Chris@280 488
Chris@280 489 QRect log(sw, cursorPos.y() + 2,
Chris@280 490 paint.fontMetrics().width("-80.000 dBV") + 2,
Chris@280 491 paint.fontMetrics().height());
Chris@280 492 extents.push_back(log);
Chris@280 493
Chris@280 494 QRect freq(cursorPos.x(),
Chris@918 495 v->getPaintHeight() - paint.fontMetrics().height() - hoffset,
Chris@280 496 paint.fontMetrics().width("123456 Hz") + 2,
Chris@280 497 paint.fontMetrics().height());
Chris@280 498 extents.push_back(freq);
Chris@264 499
Chris@278 500 int w(paint.fontMetrics().width("C#10+50c") + 2);
Chris@278 501 QRect pitch(cursorPos.x() - w,
Chris@918 502 v->getPaintHeight() - paint.fontMetrics().height() - hoffset,
Chris@278 503 w,
Chris@278 504 paint.fontMetrics().height());
Chris@278 505 extents.push_back(pitch);
Chris@278 506
Chris@264 507 return true;
Chris@264 508 }
Chris@264 509
Chris@254 510 void
Chris@918 511 SpectrumLayer::paintCrosshairs(LayerGeometryProvider *v, QPainter &paint,
Chris@254 512 QPoint cursorPos) const
Chris@254 513 {
Chris@280 514 if (!m_sliceableModel) return;
Chris@280 515
Chris@254 516 paint.save();
Chris@282 517 QFont fn = paint.font();
Chris@282 518 if (fn.pointSize() > 8) {
Chris@282 519 fn.setPointSize(fn.pointSize() - 1);
Chris@282 520 paint.setFont(fn);
Chris@282 521 }
Chris@254 522
Chris@1362 523 ColourMapper mapper(m_colourMap, m_colourInverted, 0, 1);
Chris@254 524 paint.setPen(mapper.getContrastingColour());
Chris@254 525
Chris@1238 526 int xorigin = m_xorigins[v->getId()];
Chris@918 527 paint.drawLine(xorigin, cursorPos.y(), v->getPaintWidth(), cursorPos.y());
Chris@918 528 paint.drawLine(cursorPos.x(), cursorPos.y(), cursorPos.x(), v->getPaintHeight());
Chris@254 529
Chris@1238 530 double fundamental = getFrequencyForX(v, cursorPos.x());
Chris@254 531
Chris@280 532 int hoffset = 2;
Chris@280 533 if (m_binScale == LogBins) hoffset = 13;
Chris@278 534
Chris@1078 535 PaintAssistant::drawVisibleText(v, paint,
Chris@1238 536 cursorPos.x() + 2,
Chris@1238 537 v->getPaintHeight() - 2 - hoffset,
Chris@1238 538 QString("%1 Hz").arg(fundamental),
Chris@1238 539 PaintAssistant::OutlinedText);
Chris@278 540
Chris@278 541 if (Pitch::isFrequencyInMidiRange(fundamental)) {
Chris@278 542 QString pitchLabel = Pitch::getPitchLabelForFrequency(fundamental);
Chris@1078 543 PaintAssistant::drawVisibleText(v, paint,
Chris@1238 544 cursorPos.x() -
Chris@1238 545 paint.fontMetrics().width(pitchLabel) - 2,
Chris@1238 546 v->getPaintHeight() - 2 - hoffset,
Chris@1238 547 pitchLabel,
Chris@1238 548 PaintAssistant::OutlinedText);
Chris@278 549 }
Chris@264 550
Chris@1238 551 double value = getValueForY(v, cursorPos.y());
Chris@908 552 double thresh = m_threshold;
Chris@908 553 double db = thresh;
Chris@908 554 if (value > 0.0) db = 10.0 * log10(value);
Chris@280 555 if (db < thresh) db = thresh;
Chris@280 556
Chris@1078 557 PaintAssistant::drawVisibleText(v, paint,
Chris@280 558 xorigin + 2,
Chris@280 559 cursorPos.y() - 2,
Chris@280 560 QString("%1 V").arg(value),
Chris@1078 561 PaintAssistant::OutlinedText);
Chris@280 562
Chris@1078 563 PaintAssistant::drawVisibleText(v, paint,
Chris@280 564 xorigin + 2,
Chris@280 565 cursorPos.y() + 2 + paint.fontMetrics().ascent(),
Chris@280 566 QString("%1 dBV").arg(db),
Chris@1078 567 PaintAssistant::OutlinedText);
Chris@280 568
Chris@254 569 int harmonic = 2;
Chris@254 570
Chris@254 571 while (harmonic < 100) {
Chris@254 572
Chris@1238 573 int hx = int(lrint(getXForFrequency(v, fundamental * harmonic)));
Chris@254 574
Chris@918 575 if (hx < xorigin || hx > v->getPaintWidth()) break;
Chris@254 576
Chris@254 577 int len = 7;
Chris@254 578
Chris@254 579 if (harmonic % 2 == 0) {
Chris@254 580 if (harmonic % 4 == 0) {
Chris@254 581 len = 12;
Chris@254 582 } else {
Chris@254 583 len = 10;
Chris@254 584 }
Chris@254 585 }
Chris@254 586
Chris@908 587 paint.drawLine(hx,
Chris@254 588 cursorPos.y(),
Chris@908 589 hx,
Chris@254 590 cursorPos.y() + len);
Chris@254 591
Chris@254 592 ++harmonic;
Chris@254 593 }
Chris@254 594
Chris@254 595 paint.restore();
Chris@254 596 }
Chris@254 597
Chris@199 598 QString
Chris@918 599 SpectrumLayer::getFeatureDescription(LayerGeometryProvider *v, QPoint &p) const
Chris@199 600 {
Chris@199 601 if (!m_sliceableModel) return "";
Chris@199 602
Chris@199 603 int minbin = 0, maxbin = 0, range = 0;
Chris@805 604 QString genericDesc = SliceLayer::getFeatureDescriptionAux
Chris@199 605 (v, p, false, minbin, maxbin, range);
Chris@199 606
Chris@199 607 if (genericDesc == "") return "";
Chris@199 608
Chris@1238 609 int i0 = minbin - m_minbin;
Chris@1238 610 int i1 = maxbin - m_minbin;
Chris@1238 611
Chris@1238 612 float minvalue = 0.0;
Chris@1238 613 if (in_range_for(m_values, i0)) minvalue = m_values[i0];
Chris@199 614
Chris@1238 615 float maxvalue = minvalue;
Chris@1238 616 if (in_range_for(m_values, i1)) maxvalue = m_values[i1];
Chris@1238 617
Chris@199 618 if (minvalue > maxvalue) std::swap(minvalue, maxvalue);
Chris@199 619
Chris@199 620 QString binstr;
Chris@199 621 QString hzstr;
Chris@908 622 int minfreq = int(lrint((minbin * m_sliceableModel->getSampleRate()) /
Chris@1382 623 getFFTSize()));
Chris@1256 624 int maxfreq = int(lrint((std::max(maxbin, minbin)
Chris@908 625 * m_sliceableModel->getSampleRate()) /
Chris@1382 626 getFFTSize()));
Chris@199 627
Chris@199 628 if (maxbin != minbin) {
Chris@199 629 binstr = tr("%1 - %2").arg(minbin+1).arg(maxbin+1);
Chris@199 630 } else {
Chris@199 631 binstr = QString("%1").arg(minbin+1);
Chris@199 632 }
Chris@199 633 if (minfreq != maxfreq) {
Chris@199 634 hzstr = tr("%1 - %2 Hz").arg(minfreq).arg(maxfreq);
Chris@199 635 } else {
Chris@199 636 hzstr = tr("%1 Hz").arg(minfreq);
Chris@199 637 }
Chris@199 638
Chris@199 639 QString valuestr;
Chris@199 640 if (maxvalue != minvalue) {
Chris@199 641 valuestr = tr("%1 - %2").arg(minvalue).arg(maxvalue);
Chris@199 642 } else {
Chris@199 643 valuestr = QString("%1").arg(minvalue);
Chris@199 644 }
Chris@199 645
Chris@199 646 QString dbstr;
Chris@908 647 double mindb = AudioLevel::multiplier_to_dB(minvalue);
Chris@908 648 double maxdb = AudioLevel::multiplier_to_dB(maxvalue);
Chris@199 649 QString mindbstr;
Chris@199 650 QString maxdbstr;
Chris@199 651 if (mindb == AudioLevel::DB_FLOOR) {
Chris@1147 652 mindbstr = Strings::minus_infinity;
Chris@199 653 } else {
Chris@908 654 mindbstr = QString("%1").arg(lrint(mindb));
Chris@199 655 }
Chris@199 656 if (maxdb == AudioLevel::DB_FLOOR) {
Chris@1147 657 maxdbstr = Strings::minus_infinity;
Chris@199 658 } else {
Chris@908 659 maxdbstr = QString("%1").arg(lrint(maxdb));
Chris@199 660 }
Chris@908 661 if (lrint(mindb) != lrint(maxdb)) {
Chris@199 662 dbstr = tr("%1 - %2").arg(mindbstr).arg(maxdbstr);
Chris@199 663 } else {
Chris@199 664 dbstr = tr("%1").arg(mindbstr);
Chris@199 665 }
Chris@199 666
Chris@199 667 QString description;
Chris@199 668
Chris@248 669 if (range > int(m_sliceableModel->getResolution())) {
Chris@199 670 description = tr("%1\nBin:\t%2 (%3)\n%4 value:\t%5\ndB:\t%6")
Chris@199 671 .arg(genericDesc)
Chris@199 672 .arg(binstr)
Chris@199 673 .arg(hzstr)
Chris@199 674 .arg(m_samplingMode == NearestSample ? tr("First") :
Chris@199 675 m_samplingMode == SampleMean ? tr("Mean") : tr("Peak"))
Chris@199 676 .arg(valuestr)
Chris@199 677 .arg(dbstr);
Chris@199 678 } else {
Chris@199 679 description = tr("%1\nBin:\t%2 (%3)\nValue:\t%4\ndB:\t%5")
Chris@199 680 .arg(genericDesc)
Chris@199 681 .arg(binstr)
Chris@199 682 .arg(hzstr)
Chris@199 683 .arg(valuestr)
Chris@199 684 .arg(dbstr);
Chris@199 685 }
Chris@199 686
Chris@199 687 return description;
Chris@199 688 }
Chris@199 689
Chris@254 690 void
Chris@916 691 SpectrumLayer::paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const
Chris@275 692 {
Chris@275 693 if (!m_originModel || !m_originModel->isOK() ||
Chris@349 694 !m_originModel->isReady()) {
Chris@587 695 SVDEBUG << "SpectrumLayer::paint: no origin model, or origin model not OK or not ready" << endl;
Chris@349 696 return;
Chris@349 697 }
Chris@275 698
Chris@275 699 if (m_newFFTNeeded) {
Chris@587 700 SVDEBUG << "SpectrumLayer::paint: new FFT needed, calling setupFFT" << endl;
Chris@275 701 const_cast<SpectrumLayer *>(this)->setupFFT(); //ugh
Chris@275 702 }
Chris@277 703
Chris@277 704 FFTModel *fft = dynamic_cast<FFTModel *>
Chris@277 705 (const_cast<DenseThreeDimensionalModel *>(m_sliceableModel));
Chris@277 706
Chris@1382 707 double thresh = (pow(10, -6) / m_gain) * (getFFTSize() / 2.0); // -60dB adj
Chris@277 708
Chris@607 709 int xorigin = getVerticalScaleWidth(v, false, paint) + 1;
Chris@1281 710 int scaleHeight = getHorizontalScaleHeight(v, paint);
Chris@345 711
Chris@284 712 if (fft && m_showPeaks) {
Chris@277 713
Chris@277 714 // draw peak lines
Chris@277 715
Chris@908 716 int col = int(v->getCentreFrame() / fft->getResolution());
Chris@277 717
Chris@277 718 paint.save();
Chris@277 719 paint.setRenderHint(QPainter::Antialiasing, false);
Chris@277 720
Chris@1281 721 ColourMapper mapper =
Chris@1281 722 hasLightBackground() ?
Chris@1362 723 ColourMapper(ColourMapper::BlackOnWhite, m_colourInverted, 0, 1) :
Chris@1362 724 ColourMapper(ColourMapper::WhiteOnBlack, m_colourInverted, 0, 1);
Chris@1281 725
Chris@290 726 int peakminbin = 0;
Chris@290 727 int peakmaxbin = fft->getHeight() - 1;
Chris@908 728 double peakmaxfreq = Pitch::getFrequencyForPitch(128);
Chris@1387 729 peakmaxbin = int(((peakmaxfreq * fft->getHeight() * 2) /
Chris@1387 730 fft->getSampleRate()));
Chris@290 731
Chris@280 732 FFTModel::PeakSet peaks = fft->getPeakFrequencies
Chris@290 733 (FFTModel::MajorPitchAdaptivePeaks, col, peakminbin, peakmaxbin);
Chris@280 734
Chris@277 735 BiasCurve curve;
Chris@277 736 getBiasCurve(curve);
Chris@908 737 int cs = int(curve.size());
Chris@280 738
Chris@1387 739 int px = -1;
Chris@1387 740
Chris@280 741 for (FFTModel::PeakSet::iterator i = peaks.begin();
Chris@280 742 i != peaks.end(); ++i) {
Chris@280 743
Chris@1387 744 double freq = i->second;
Chris@1387 745 int x = int(lrint(getXForFrequency(v, freq)));
Chris@1387 746 if (x == px) {
Chris@1387 747 continue;
Chris@1387 748 }
Chris@1387 749
Chris@805 750 int bin = i->first;
Chris@277 751
Chris@682 752 // cerr << "bin = " << bin << ", thresh = " << thresh << ", value = " << fft->getMagnitudeAt(col, bin) << endl;
Chris@280 753
Chris@1385 754 double value = fft->getValueAt(col, bin);
Chris@1385 755 if (value < thresh) continue;
Chris@1385 756 if (bin < cs) value *= curve[bin];
Chris@277 757
Chris@908 758 double norm = 0.f;
Chris@1385 759 // don't need return value, need norm:
Chris@1385 760 (void)getYForValue(v, value, norm);
Chris@277 761
Chris@1387 762 paint.setPen(QPen(mapper.map(norm), 1));
Chris@1281 763 paint.drawLine(x, 0, x, v->getPaintHeight() - scaleHeight - 1);
Chris@1387 764
Chris@1387 765 px = x;
Chris@277 766 }
Chris@277 767
Chris@277 768 paint.restore();
Chris@277 769 }
Chris@275 770
Chris@1281 771 paint.save();
Chris@1281 772
Chris@275 773 SliceLayer::paint(v, paint, rect);
Chris@1281 774
Chris@1281 775 paintHorizontalScale(v, paint, xorigin);
Chris@277 776
Chris@1281 777 paint.restore();
Chris@1281 778 }
Chris@1281 779
Chris@1281 780 int
Chris@1281 781 SpectrumLayer::getHorizontalScaleHeight(LayerGeometryProvider *v,
Chris@1281 782 QPainter &paint) const
Chris@1281 783 {
Chris@1281 784 int pkh = int(paint.fontMetrics().height() * 0.7 + 0.5);
Chris@1281 785 if (pkh < 10) pkh = 10;
Chris@1281 786
Chris@1281 787 int scaleh = HorizontalFrequencyScale().getHeight(v, paint);
Chris@1281 788
Chris@1281 789 return pkh + scaleh;
Chris@1281 790 }
Chris@1281 791
Chris@1281 792 void
Chris@1281 793 SpectrumLayer::paintHorizontalScale(LayerGeometryProvider *v,
Chris@1281 794 QPainter &paint,
Chris@1281 795 int xorigin) const
Chris@1281 796 {
Chris@278 797 //!!! All of this stuff relating to depicting frequencies
Chris@1238 798 // (keyboard, crosshairs etc) should be applicable to any slice
Chris@1238 799 // layer whose model has a vertical scale unit of Hz. However,
Chris@1238 800 // the dense 3d model at the moment doesn't record its vertical
Chris@1238 801 // scale unit -- we need to fix that and hoist this code as
Chris@1238 802 // appropriate. Same really goes for any code in SpectrogramLayer
Chris@1238 803 // that could be relevant to Colour3DPlotLayer with unit Hz, but
Chris@1238 804 // that's a bigger proposition.
Chris@278 805
Chris@1281 806 if (!v->getViewManager()->shouldShowHorizontalValueScale()) {
Chris@1281 807 return;
Chris@1281 808 }
Chris@1281 809
Chris@1281 810 int totalScaleHeight = getHorizontalScaleHeight(v, paint); // inc piano
Chris@1281 811 int freqScaleHeight = HorizontalFrequencyScale().getHeight(v, paint);
Chris@1281 812 int paintHeight = v->getPaintHeight();
Chris@1281 813 int paintWidth = v->getPaintWidth();
Chris@277 814
Chris@1238 815 PianoScale().paintPianoHorizontal
Chris@1276 816 (v, this, paint,
Chris@1281 817 QRect(xorigin, paintHeight - totalScaleHeight - 1,
Chris@1281 818 paintWidth - 1, totalScaleHeight - freqScaleHeight));
Chris@345 819
Chris@1281 820 int scaleLeft = int(getXForBin(v, 1));
Chris@1281 821
Chris@1281 822 paint.drawLine(int(getXForBin(v, 0)), paintHeight - freqScaleHeight,
Chris@1281 823 scaleLeft, paintHeight - freqScaleHeight);
Chris@1281 824
Chris@1281 825 QString hz = tr("Hz");
Chris@1281 826 int hzw = paint.fontMetrics().width(hz);
Chris@1281 827 if (scaleLeft > hzw + 5) {
Chris@1281 828 paint.drawText
Chris@1281 829 (scaleLeft - hzw - 5,
Chris@1281 830 paintHeight - freqScaleHeight + paint.fontMetrics().ascent() + 5,
Chris@1281 831 hz);
Chris@1281 832 }
Chris@1281 833
Chris@1281 834 HorizontalFrequencyScale().paintScale
Chris@1276 835 (v, this, paint,
Chris@1281 836 QRect(scaleLeft, paintHeight - freqScaleHeight,
Chris@1281 837 paintWidth, totalScaleHeight),
Chris@1281 838 m_binScale == LogBins);
Chris@275 839 }
Chris@275 840
Chris@275 841 void
Chris@254 842 SpectrumLayer::getBiasCurve(BiasCurve &curve) const
Chris@254 843 {
Chris@254 844 curve = m_biasCurve;
Chris@254 845 }
Chris@199 846
Chris@316 847 void
Chris@316 848 SpectrumLayer::toXml(QTextStream &stream,
Chris@316 849 QString indent, QString extraAttributes) const
Chris@220 850 {
Chris@316 851 QString s = QString("windowSize=\"%1\" "
Chris@456 852 "windowHopLevel=\"%2\" "
Chris@1382 853 "oversampling=\"%3\" "
Chris@1382 854 "showPeaks=\"%4\" ")
Chris@220 855 .arg(m_windowSize)
Chris@456 856 .arg(m_windowHopLevel)
Chris@1382 857 .arg(m_oversampling)
Chris@456 858 .arg(m_showPeaks ? "true" : "false");
Chris@220 859
Chris@316 860 SliceLayer::toXml(stream, indent, extraAttributes + " " + s);
Chris@220 861 }
Chris@220 862
Chris@220 863 void
Chris@220 864 SpectrumLayer::setProperties(const QXmlAttributes &attributes)
Chris@220 865 {
Chris@220 866 SliceLayer::setProperties(attributes);
Chris@220 867
Chris@220 868 bool ok = false;
Chris@220 869
Chris@805 870 int windowSize = attributes.value("windowSize").toUInt(&ok);
Chris@220 871 if (ok) setWindowSize(windowSize);
Chris@220 872
Chris@805 873 int windowHopLevel = attributes.value("windowHopLevel").toUInt(&ok);
Chris@220 874 if (ok) setWindowHopLevel(windowHopLevel);
Chris@456 875
Chris@1382 876 int oversampling = attributes.value("oversampling").toUInt(&ok);
Chris@1382 877 if (ok) setOversampling(oversampling);
Chris@1382 878
Chris@456 879 bool showPeaks = (attributes.value("showPeaks").trimmed() == "true");
Chris@456 880 setShowPeaks(showPeaks);
Chris@220 881 }
Chris@220 882
Chris@220 883