annotate layer/SpectrumLayer.cpp @ 1245:f0e291fa7b9c

Use Range01 normalisation in Colour 3D Plot. This gives us the same column normalisation behaviour as in 2.5 (better than the Max1 option).
author Chris Cannam
date Tue, 28 Feb 2017 14:06:24 +0000
parents 4d0ca1ab4cd0
children 9e1559b08f0d
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@254 28
Chris@254 29 #include <QPainter>
Chris@316 30 #include <QTextStream>
Chris@316 31
Chris@133 32
Chris@133 33 SpectrumLayer::SpectrumLayer() :
Chris@193 34 m_originModel(0),
Chris@153 35 m_channel(-1),
Chris@153 36 m_channelSet(false),
Chris@290 37 m_windowSize(4096),
Chris@153 38 m_windowType(HanningWindow),
Chris@290 39 m_windowHopLevel(3),
Chris@284 40 m_showPeaks(false),
Chris@275 41 m_newFFTNeeded(true)
Chris@133 42 {
Chris@153 43 Preferences *prefs = Preferences::getInstance();
Chris@153 44 connect(prefs, SIGNAL(propertyChanged(PropertyContainer::PropertyName)),
Chris@153 45 this, SLOT(preferenceChanged(PropertyContainer::PropertyName)));
Chris@153 46 setWindowType(prefs->getWindowType());
Chris@195 47
Chris@195 48 setBinScale(LogBins);
Chris@133 49 }
Chris@133 50
Chris@133 51 SpectrumLayer::~SpectrumLayer()
Chris@133 52 {
Chris@349 53 Model *m = const_cast<Model *>
Chris@349 54 (static_cast<const Model *>(m_sliceableModel));
Chris@458 55 if (m) m->aboutToDelete();
Chris@349 56 m_sliceableModel = 0;
Chris@349 57 delete m;
Chris@133 58 }
Chris@133 59
Chris@133 60 void
Chris@133 61 SpectrumLayer::setModel(DenseTimeValueModel *model)
Chris@133 62 {
Chris@587 63 SVDEBUG << "SpectrumLayer::setModel(" << model << ") from " << m_originModel << endl;
Chris@345 64
Chris@193 65 if (m_originModel == model) return;
Chris@349 66
Chris@193 67 m_originModel = model;
Chris@277 68
Chris@350 69 if (m_sliceableModel) {
Chris@350 70 Model *m = const_cast<Model *>
Chris@350 71 (static_cast<const Model *>(m_sliceableModel));
Chris@350 72 m->aboutToDelete();
Chris@350 73 setSliceableModel(0);
Chris@350 74 delete m;
Chris@350 75 }
Chris@350 76
Chris@349 77 m_newFFTNeeded = true;
Chris@349 78
Chris@349 79 emit layerParametersChanged();
Chris@349 80 }
Chris@349 81
Chris@349 82 void
Chris@349 83 SpectrumLayer::setChannel(int channel)
Chris@349 84 {
Chris@587 85 SVDEBUG << "SpectrumLayer::setChannel(" << channel << ") from " << m_channel << endl;
Chris@349 86
Chris@349 87 m_channelSet = true;
Chris@349 88
Chris@349 89 if (m_channel == channel) return;
Chris@349 90
Chris@349 91 m_channel = channel;
Chris@349 92
Chris@349 93 m_newFFTNeeded = true;
Chris@349 94
Chris@349 95 emit layerParametersChanged();
Chris@153 96 }
Chris@153 97
Chris@153 98 void
Chris@193 99 SpectrumLayer::setupFFT()
Chris@153 100 {
Chris@349 101 if (m_sliceableModel) {
Chris@349 102 Model *m = const_cast<Model *>
Chris@349 103 (static_cast<const Model *>(m_sliceableModel));
Chris@349 104 m->aboutToDelete();
Chris@193 105 setSliceableModel(0);
Chris@349 106 delete m;
Chris@349 107 }
Chris@349 108
Chris@349 109 if (!m_originModel) {
Chris@349 110 return;
Chris@153 111 }
Chris@153 112
Chris@193 113 FFTModel *newFFT = new FFTModel(m_originModel,
Chris@193 114 m_channel,
Chris@193 115 m_windowType,
Chris@193 116 m_windowSize,
Chris@193 117 getWindowIncrement(),
Chris@975 118 m_windowSize);
Chris@153 119
Chris@193 120 setSliceableModel(newFFT);
Chris@193 121
Chris@254 122 m_biasCurve.clear();
Chris@805 123 for (int i = 0; i < m_windowSize; ++i) {
Chris@254 124 m_biasCurve.push_back(1.f / (float(m_windowSize)/2.f));
Chris@254 125 }
Chris@254 126
Chris@349 127 m_newFFTNeeded = false;
Chris@133 128 }
Chris@133 129
Chris@153 130 Layer::PropertyList
Chris@153 131 SpectrumLayer::getProperties() const
Chris@153 132 {
Chris@193 133 PropertyList list = SliceLayer::getProperties();
Chris@153 134 list.push_back("Window Size");
Chris@153 135 list.push_back("Window Increment");
Chris@284 136 list.push_back("Show Peak Frequencies");
Chris@153 137 return list;
Chris@153 138 }
Chris@153 139
Chris@153 140 QString
Chris@153 141 SpectrumLayer::getPropertyLabel(const PropertyName &name) const
Chris@153 142 {
Chris@153 143 if (name == "Window Size") return tr("Window Size");
Chris@153 144 if (name == "Window Increment") return tr("Window Overlap");
Chris@284 145 if (name == "Show Peak Frequencies") return tr("Show Peak Frequencies");
Chris@193 146 return SliceLayer::getPropertyLabel(name);
Chris@153 147 }
Chris@153 148
Chris@335 149 QString
Chris@335 150 SpectrumLayer::getPropertyIconName(const PropertyName &name) const
Chris@335 151 {
Chris@335 152 if (name == "Show Peak Frequencies") return "show-peaks";
Chris@335 153 return SliceLayer::getPropertyIconName(name);
Chris@335 154 }
Chris@335 155
Chris@153 156 Layer::PropertyType
Chris@153 157 SpectrumLayer::getPropertyType(const PropertyName &name) const
Chris@153 158 {
Chris@193 159 if (name == "Window Size") return ValueProperty;
Chris@193 160 if (name == "Window Increment") return ValueProperty;
Chris@284 161 if (name == "Show Peak Frequencies") return ToggleProperty;
Chris@193 162 return SliceLayer::getPropertyType(name);
Chris@153 163 }
Chris@153 164
Chris@153 165 QString
Chris@153 166 SpectrumLayer::getPropertyGroupName(const PropertyName &name) const
Chris@153 167 {
Chris@153 168 if (name == "Window Size" ||
Chris@153 169 name == "Window Increment") return tr("Window");
Chris@557 170 if (name == "Show Peak Frequencies") return tr("Bins");
Chris@193 171 return SliceLayer::getPropertyGroupName(name);
Chris@153 172 }
Chris@153 173
Chris@153 174 int
Chris@153 175 SpectrumLayer::getPropertyRangeAndValue(const PropertyName &name,
Chris@216 176 int *min, int *max, int *deflt) const
Chris@153 177 {
Chris@216 178 int val = 0;
Chris@153 179
Chris@216 180 int garbage0, garbage1, garbage2;
Chris@153 181 if (!min) min = &garbage0;
Chris@153 182 if (!max) max = &garbage1;
Chris@216 183 if (!deflt) deflt = &garbage2;
Chris@153 184
Chris@193 185 if (name == "Window Size") {
Chris@153 186
Chris@153 187 *min = 0;
Chris@254 188 *max = 15;
Chris@216 189 *deflt = 5;
Chris@153 190
Chris@216 191 val = 0;
Chris@153 192 int ws = m_windowSize;
Chris@216 193 while (ws > 32) { ws >>= 1; val ++; }
Chris@153 194
Chris@153 195 } else if (name == "Window Increment") {
Chris@153 196
Chris@153 197 *min = 0;
Chris@153 198 *max = 5;
Chris@216 199 *deflt = 2;
Chris@153 200
Chris@216 201 val = m_windowHopLevel;
Chris@153 202
Chris@284 203 } else if (name == "Show Peak Frequencies") {
Chris@284 204
Chris@284 205 return m_showPeaks ? 1 : 0;
Chris@284 206
Chris@153 207 } else {
Chris@193 208
Chris@216 209 val = SliceLayer::getPropertyRangeAndValue(name, min, max, deflt);
Chris@153 210 }
Chris@153 211
Chris@216 212 return val;
Chris@153 213 }
Chris@153 214
Chris@153 215 QString
Chris@153 216 SpectrumLayer::getPropertyValueLabel(const PropertyName &name,
Chris@153 217 int value) const
Chris@153 218 {
Chris@153 219 if (name == "Window Size") {
Chris@153 220 return QString("%1").arg(32 << value);
Chris@153 221 }
Chris@153 222 if (name == "Window Increment") {
Chris@153 223 switch (value) {
Chris@153 224 default:
Chris@153 225 case 0: return tr("None");
Chris@153 226 case 1: return tr("25 %");
Chris@153 227 case 2: return tr("50 %");
Chris@153 228 case 3: return tr("75 %");
Chris@153 229 case 4: return tr("87.5 %");
Chris@153 230 case 5: return tr("93.75 %");
Chris@153 231 }
Chris@153 232 }
Chris@193 233 return SliceLayer::getPropertyValueLabel(name, value);
Chris@153 234 }
Chris@153 235
Chris@167 236 RangeMapper *
Chris@167 237 SpectrumLayer::getNewPropertyRangeMapper(const PropertyName &name) const
Chris@167 238 {
Chris@193 239 return SliceLayer::getNewPropertyRangeMapper(name);
Chris@167 240 }
Chris@167 241
Chris@133 242 void
Chris@153 243 SpectrumLayer::setProperty(const PropertyName &name, int value)
Chris@133 244 {
Chris@193 245 if (name == "Window Size") {
Chris@153 246 setWindowSize(32 << value);
Chris@153 247 } else if (name == "Window Increment") {
Chris@153 248 setWindowHopLevel(value);
Chris@284 249 } else if (name == "Show Peak Frequencies") {
Chris@284 250 setShowPeaks(value ? true : false);
Chris@193 251 } else {
Chris@193 252 SliceLayer::setProperty(name, value);
Chris@153 253 }
Chris@153 254 }
Chris@153 255
Chris@153 256 void
Chris@805 257 SpectrumLayer::setWindowSize(int ws)
Chris@153 258 {
Chris@153 259 if (m_windowSize == ws) return;
Chris@153 260 m_windowSize = ws;
Chris@275 261 m_newFFTNeeded = true;
Chris@153 262 emit layerParametersChanged();
Chris@153 263 }
Chris@153 264
Chris@153 265 void
Chris@805 266 SpectrumLayer::setWindowHopLevel(int v)
Chris@153 267 {
Chris@153 268 if (m_windowHopLevel == v) return;
Chris@153 269 m_windowHopLevel = v;
Chris@275 270 m_newFFTNeeded = true;
Chris@153 271 emit layerParametersChanged();
Chris@153 272 }
Chris@153 273
Chris@153 274 void
Chris@153 275 SpectrumLayer::setWindowType(WindowType w)
Chris@153 276 {
Chris@153 277 if (m_windowType == w) return;
Chris@153 278 m_windowType = w;
Chris@275 279 m_newFFTNeeded = true;
Chris@153 280 emit layerParametersChanged();
Chris@153 281 }
Chris@153 282
Chris@153 283 void
Chris@284 284 SpectrumLayer::setShowPeaks(bool show)
Chris@284 285 {
Chris@284 286 if (m_showPeaks == show) return;
Chris@284 287 m_showPeaks = show;
Chris@284 288 emit layerParametersChanged();
Chris@284 289 }
Chris@284 290
Chris@284 291 void
Chris@153 292 SpectrumLayer::preferenceChanged(PropertyContainer::PropertyName name)
Chris@153 293 {
Chris@153 294 if (name == "Window Type") {
Chris@153 295 setWindowType(Preferences::getInstance()->getWindowType());
Chris@153 296 return;
Chris@153 297 }
Chris@153 298 }
Chris@153 299
Chris@1238 300 double
Chris@1238 301 SpectrumLayer::getFrequencyForX(const LayerGeometryProvider *v, double x) const
Chris@133 302 {
Chris@1238 303 if (!m_sliceableModel) return 0;
Chris@1238 304 double bin = getBinForX(v, x);
Chris@1238 305 return (m_sliceableModel->getSampleRate() * bin) /
Chris@1238 306 (m_sliceableModel->getHeight() * 2);
Chris@133 307 }
Chris@133 308
Chris@908 309 double
Chris@1238 310 SpectrumLayer::getXForFrequency(const LayerGeometryProvider *v, double freq) const
Chris@265 311 {
Chris@280 312 if (!m_sliceableModel) return 0;
Chris@1238 313 double bin = (freq * m_sliceableModel->getHeight() * 2) /
Chris@1238 314 m_sliceableModel->getSampleRate();
Chris@1238 315 return getXForBin(v, bin);
Chris@254 316 }
Chris@254 317
Chris@260 318 bool
Chris@918 319 SpectrumLayer::getXScaleValue(const LayerGeometryProvider *v, int x,
Chris@908 320 double &value, QString &unit) const
Chris@260 321 {
Chris@1238 322 value = getFrequencyForX(v, x);
Chris@260 323 unit = "Hz";
Chris@260 324 return true;
Chris@260 325 }
Chris@260 326
Chris@264 327 bool
Chris@918 328 SpectrumLayer::getYScaleValue(const LayerGeometryProvider *v, int y,
Chris@908 329 double &value, QString &unit) const
Chris@274 330 {
Chris@1238 331 value = getValueForY(v, y);
Chris@274 332
Chris@274 333 if (m_energyScale == dBScale || m_energyScale == MeterScale) {
Chris@274 334
Chris@908 335 if (value > 0.0) {
Chris@908 336 value = 10.0 * log10(value);
Chris@284 337 if (value < m_threshold) value = m_threshold;
Chris@284 338 } else value = m_threshold;
Chris@274 339
Chris@274 340 unit = "dBV";
Chris@274 341
Chris@274 342 } else {
Chris@274 343 unit = "V";
Chris@274 344 }
Chris@274 345
Chris@274 346 return true;
Chris@274 347 }
Chris@274 348
Chris@274 349 bool
Chris@918 350 SpectrumLayer::getYScaleDifference(const LayerGeometryProvider *v, int y0, int y1,
Chris@908 351 double &diff, QString &unit) const
Chris@274 352 {
Chris@274 353 bool rv = SliceLayer::getYScaleDifference(v, y0, y1, diff, unit);
Chris@274 354 if (rv && (unit == "dBV")) unit = "dB";
Chris@274 355 return rv;
Chris@274 356 }
Chris@274 357
Chris@274 358
Chris@274 359 bool
Chris@918 360 SpectrumLayer::getCrosshairExtents(LayerGeometryProvider *v, QPainter &paint,
Chris@264 361 QPoint cursorPos,
Chris@264 362 std::vector<QRect> &extents) const
Chris@264 363 {
Chris@918 364 QRect vertical(cursorPos.x(), cursorPos.y(), 1, v->getPaintHeight() - cursorPos.y());
Chris@264 365 extents.push_back(vertical);
Chris@264 366
Chris@918 367 QRect horizontal(0, cursorPos.y(), v->getPaintWidth(), 12);
Chris@264 368 extents.push_back(horizontal);
Chris@264 369
Chris@280 370 int hoffset = 2;
Chris@280 371 if (m_binScale == LogBins) hoffset = 13;
Chris@278 372
Chris@607 373 int sw = getVerticalScaleWidth(v, false, paint);
Chris@280 374
Chris@280 375 QRect value(sw, cursorPos.y() - paint.fontMetrics().ascent() - 2,
Chris@280 376 paint.fontMetrics().width("0.0000001 V") + 2,
Chris@264 377 paint.fontMetrics().height());
Chris@280 378 extents.push_back(value);
Chris@280 379
Chris@280 380 QRect log(sw, cursorPos.y() + 2,
Chris@280 381 paint.fontMetrics().width("-80.000 dBV") + 2,
Chris@280 382 paint.fontMetrics().height());
Chris@280 383 extents.push_back(log);
Chris@280 384
Chris@280 385 QRect freq(cursorPos.x(),
Chris@918 386 v->getPaintHeight() - paint.fontMetrics().height() - hoffset,
Chris@280 387 paint.fontMetrics().width("123456 Hz") + 2,
Chris@280 388 paint.fontMetrics().height());
Chris@280 389 extents.push_back(freq);
Chris@264 390
Chris@278 391 int w(paint.fontMetrics().width("C#10+50c") + 2);
Chris@278 392 QRect pitch(cursorPos.x() - w,
Chris@918 393 v->getPaintHeight() - paint.fontMetrics().height() - hoffset,
Chris@278 394 w,
Chris@278 395 paint.fontMetrics().height());
Chris@278 396 extents.push_back(pitch);
Chris@278 397
Chris@264 398 return true;
Chris@264 399 }
Chris@264 400
Chris@254 401 void
Chris@918 402 SpectrumLayer::paintCrosshairs(LayerGeometryProvider *v, QPainter &paint,
Chris@254 403 QPoint cursorPos) const
Chris@254 404 {
Chris@280 405 if (!m_sliceableModel) return;
Chris@280 406
Chris@254 407 paint.save();
Chris@282 408 QFont fn = paint.font();
Chris@282 409 if (fn.pointSize() > 8) {
Chris@282 410 fn.setPointSize(fn.pointSize() - 1);
Chris@282 411 paint.setFont(fn);
Chris@282 412 }
Chris@254 413
Chris@254 414 ColourMapper mapper(m_colourMap, 0, 1);
Chris@254 415 paint.setPen(mapper.getContrastingColour());
Chris@254 416
Chris@1238 417 int xorigin = m_xorigins[v->getId()];
Chris@918 418 int w = v->getPaintWidth() - xorigin - 1;
Chris@254 419
Chris@918 420 paint.drawLine(xorigin, cursorPos.y(), v->getPaintWidth(), cursorPos.y());
Chris@918 421 paint.drawLine(cursorPos.x(), cursorPos.y(), cursorPos.x(), v->getPaintHeight());
Chris@254 422
Chris@1238 423 double fundamental = getFrequencyForX(v, cursorPos.x());
Chris@254 424
Chris@280 425 int hoffset = 2;
Chris@280 426 if (m_binScale == LogBins) hoffset = 13;
Chris@278 427
Chris@1078 428 PaintAssistant::drawVisibleText(v, paint,
Chris@1238 429 cursorPos.x() + 2,
Chris@1238 430 v->getPaintHeight() - 2 - hoffset,
Chris@1238 431 QString("%1 Hz").arg(fundamental),
Chris@1238 432 PaintAssistant::OutlinedText);
Chris@278 433
Chris@278 434 if (Pitch::isFrequencyInMidiRange(fundamental)) {
Chris@278 435 QString pitchLabel = Pitch::getPitchLabelForFrequency(fundamental);
Chris@1078 436 PaintAssistant::drawVisibleText(v, paint,
Chris@1238 437 cursorPos.x() -
Chris@1238 438 paint.fontMetrics().width(pitchLabel) - 2,
Chris@1238 439 v->getPaintHeight() - 2 - hoffset,
Chris@1238 440 pitchLabel,
Chris@1238 441 PaintAssistant::OutlinedText);
Chris@278 442 }
Chris@264 443
Chris@1238 444 double value = getValueForY(v, cursorPos.y());
Chris@908 445 double thresh = m_threshold;
Chris@908 446 double db = thresh;
Chris@908 447 if (value > 0.0) db = 10.0 * log10(value);
Chris@280 448 if (db < thresh) db = thresh;
Chris@280 449
Chris@1078 450 PaintAssistant::drawVisibleText(v, paint,
Chris@280 451 xorigin + 2,
Chris@280 452 cursorPos.y() - 2,
Chris@280 453 QString("%1 V").arg(value),
Chris@1078 454 PaintAssistant::OutlinedText);
Chris@280 455
Chris@1078 456 PaintAssistant::drawVisibleText(v, paint,
Chris@280 457 xorigin + 2,
Chris@280 458 cursorPos.y() + 2 + paint.fontMetrics().ascent(),
Chris@280 459 QString("%1 dBV").arg(db),
Chris@1078 460 PaintAssistant::OutlinedText);
Chris@280 461
Chris@254 462 int harmonic = 2;
Chris@254 463
Chris@254 464 while (harmonic < 100) {
Chris@254 465
Chris@1238 466 int hx = int(lrint(getXForFrequency(v, fundamental * harmonic)));
Chris@254 467 hx += xorigin;
Chris@254 468
Chris@918 469 if (hx < xorigin || hx > v->getPaintWidth()) break;
Chris@254 470
Chris@254 471 int len = 7;
Chris@254 472
Chris@254 473 if (harmonic % 2 == 0) {
Chris@254 474 if (harmonic % 4 == 0) {
Chris@254 475 len = 12;
Chris@254 476 } else {
Chris@254 477 len = 10;
Chris@254 478 }
Chris@254 479 }
Chris@254 480
Chris@908 481 paint.drawLine(hx,
Chris@254 482 cursorPos.y(),
Chris@908 483 hx,
Chris@254 484 cursorPos.y() + len);
Chris@254 485
Chris@254 486 ++harmonic;
Chris@254 487 }
Chris@254 488
Chris@254 489 paint.restore();
Chris@254 490 }
Chris@254 491
Chris@199 492 QString
Chris@918 493 SpectrumLayer::getFeatureDescription(LayerGeometryProvider *v, QPoint &p) const
Chris@199 494 {
Chris@199 495 if (!m_sliceableModel) return "";
Chris@199 496
Chris@199 497 int minbin = 0, maxbin = 0, range = 0;
Chris@805 498 QString genericDesc = SliceLayer::getFeatureDescriptionAux
Chris@199 499 (v, p, false, minbin, maxbin, range);
Chris@199 500
Chris@199 501 if (genericDesc == "") return "";
Chris@199 502
Chris@1238 503 int i0 = minbin - m_minbin;
Chris@1238 504 int i1 = maxbin - m_minbin;
Chris@1238 505
Chris@1238 506 float minvalue = 0.0;
Chris@1238 507 if (in_range_for(m_values, i0)) minvalue = m_values[i0];
Chris@199 508
Chris@1238 509 float maxvalue = minvalue;
Chris@1238 510 if (in_range_for(m_values, i1)) maxvalue = m_values[i1];
Chris@1238 511
Chris@199 512 if (minvalue > maxvalue) std::swap(minvalue, maxvalue);
Chris@199 513
Chris@199 514 QString binstr;
Chris@199 515 QString hzstr;
Chris@908 516 int minfreq = int(lrint((minbin * m_sliceableModel->getSampleRate()) /
Chris@908 517 m_windowSize));
Chris@908 518 int maxfreq = int(lrint((std::max(maxbin, minbin+1)
Chris@908 519 * m_sliceableModel->getSampleRate()) /
Chris@908 520 m_windowSize));
Chris@199 521
Chris@199 522 if (maxbin != minbin) {
Chris@199 523 binstr = tr("%1 - %2").arg(minbin+1).arg(maxbin+1);
Chris@199 524 } else {
Chris@199 525 binstr = QString("%1").arg(minbin+1);
Chris@199 526 }
Chris@199 527 if (minfreq != maxfreq) {
Chris@199 528 hzstr = tr("%1 - %2 Hz").arg(minfreq).arg(maxfreq);
Chris@199 529 } else {
Chris@199 530 hzstr = tr("%1 Hz").arg(minfreq);
Chris@199 531 }
Chris@199 532
Chris@199 533 QString valuestr;
Chris@199 534 if (maxvalue != minvalue) {
Chris@199 535 valuestr = tr("%1 - %2").arg(minvalue).arg(maxvalue);
Chris@199 536 } else {
Chris@199 537 valuestr = QString("%1").arg(minvalue);
Chris@199 538 }
Chris@199 539
Chris@199 540 QString dbstr;
Chris@908 541 double mindb = AudioLevel::multiplier_to_dB(minvalue);
Chris@908 542 double maxdb = AudioLevel::multiplier_to_dB(maxvalue);
Chris@199 543 QString mindbstr;
Chris@199 544 QString maxdbstr;
Chris@199 545 if (mindb == AudioLevel::DB_FLOOR) {
Chris@1147 546 mindbstr = Strings::minus_infinity;
Chris@199 547 } else {
Chris@908 548 mindbstr = QString("%1").arg(lrint(mindb));
Chris@199 549 }
Chris@199 550 if (maxdb == AudioLevel::DB_FLOOR) {
Chris@1147 551 maxdbstr = Strings::minus_infinity;
Chris@199 552 } else {
Chris@908 553 maxdbstr = QString("%1").arg(lrint(maxdb));
Chris@199 554 }
Chris@908 555 if (lrint(mindb) != lrint(maxdb)) {
Chris@199 556 dbstr = tr("%1 - %2").arg(mindbstr).arg(maxdbstr);
Chris@199 557 } else {
Chris@199 558 dbstr = tr("%1").arg(mindbstr);
Chris@199 559 }
Chris@199 560
Chris@199 561 QString description;
Chris@199 562
Chris@248 563 if (range > int(m_sliceableModel->getResolution())) {
Chris@199 564 description = tr("%1\nBin:\t%2 (%3)\n%4 value:\t%5\ndB:\t%6")
Chris@199 565 .arg(genericDesc)
Chris@199 566 .arg(binstr)
Chris@199 567 .arg(hzstr)
Chris@199 568 .arg(m_samplingMode == NearestSample ? tr("First") :
Chris@199 569 m_samplingMode == SampleMean ? tr("Mean") : tr("Peak"))
Chris@199 570 .arg(valuestr)
Chris@199 571 .arg(dbstr);
Chris@199 572 } else {
Chris@199 573 description = tr("%1\nBin:\t%2 (%3)\nValue:\t%4\ndB:\t%5")
Chris@199 574 .arg(genericDesc)
Chris@199 575 .arg(binstr)
Chris@199 576 .arg(hzstr)
Chris@199 577 .arg(valuestr)
Chris@199 578 .arg(dbstr);
Chris@199 579 }
Chris@199 580
Chris@199 581 return description;
Chris@199 582 }
Chris@199 583
Chris@254 584 void
Chris@916 585 SpectrumLayer::paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const
Chris@275 586 {
Chris@275 587 if (!m_originModel || !m_originModel->isOK() ||
Chris@349 588 !m_originModel->isReady()) {
Chris@587 589 SVDEBUG << "SpectrumLayer::paint: no origin model, or origin model not OK or not ready" << endl;
Chris@349 590 return;
Chris@349 591 }
Chris@275 592
Chris@275 593 if (m_newFFTNeeded) {
Chris@587 594 SVDEBUG << "SpectrumLayer::paint: new FFT needed, calling setupFFT" << endl;
Chris@275 595 const_cast<SpectrumLayer *>(this)->setupFFT(); //ugh
Chris@275 596 }
Chris@277 597
Chris@277 598 FFTModel *fft = dynamic_cast<FFTModel *>
Chris@277 599 (const_cast<DenseThreeDimensionalModel *>(m_sliceableModel));
Chris@277 600
Chris@908 601 double thresh = (pow(10, -6) / m_gain) * (m_windowSize / 2.0); // -60dB adj
Chris@277 602
Chris@607 603 int xorigin = getVerticalScaleWidth(v, false, paint) + 1;
Chris@918 604 int w = v->getPaintWidth() - xorigin - 1;
Chris@277 605
Chris@1231 606 int pkh = int(paint.fontMetrics().height() * 0.7 + 0.5);
Chris@1231 607 if (pkh < 10) pkh = 10;
Chris@278 608
Chris@345 609 paint.save();
Chris@345 610
Chris@284 611 if (fft && m_showPeaks) {
Chris@277 612
Chris@277 613 // draw peak lines
Chris@277 614
Chris@587 615 // SVDEBUG << "Showing peaks..." << endl;
Chris@456 616
Chris@908 617 int col = int(v->getCentreFrame() / fft->getResolution());
Chris@277 618
Chris@277 619 paint.save();
Chris@277 620 paint.setRenderHint(QPainter::Antialiasing, false);
Chris@277 621 paint.setPen(QColor(160, 160, 160)); //!!!
Chris@277 622
Chris@290 623 int peakminbin = 0;
Chris@290 624 int peakmaxbin = fft->getHeight() - 1;
Chris@908 625 double peakmaxfreq = Pitch::getFrequencyForPitch(128);
Chris@908 626 peakmaxbin = int(((peakmaxfreq * fft->getHeight() * 2) / fft->getSampleRate()));
Chris@290 627
Chris@280 628 FFTModel::PeakSet peaks = fft->getPeakFrequencies
Chris@290 629 (FFTModel::MajorPitchAdaptivePeaks, col, peakminbin, peakmaxbin);
Chris@280 630
Chris@280 631 ColourMapper mapper(ColourMapper::BlackOnWhite, 0, 1);
Chris@277 632
Chris@277 633 BiasCurve curve;
Chris@277 634 getBiasCurve(curve);
Chris@908 635 int cs = int(curve.size());
Chris@280 636
Chris@908 637 std::vector<double> values;
Chris@277 638
Chris@805 639 for (int bin = 0; bin < fft->getHeight(); ++bin) {
Chris@908 640 double value = m_sliceableModel->getValueAt(col, bin);
Chris@280 641 if (bin < cs) value *= curve[bin];
Chris@280 642 values.push_back(value);
Chris@280 643 }
Chris@280 644
Chris@280 645 for (FFTModel::PeakSet::iterator i = peaks.begin();
Chris@280 646 i != peaks.end(); ++i) {
Chris@280 647
Chris@805 648 int bin = i->first;
Chris@277 649
Chris@682 650 // cerr << "bin = " << bin << ", thresh = " << thresh << ", value = " << fft->getMagnitudeAt(col, bin) << endl;
Chris@280 651
Chris@908 652 if (!fft->isOverThreshold(col, bin, float(thresh))) continue;
Chris@277 653
Chris@908 654 double freq = i->second;
Chris@280 655
Chris@1238 656 int x = int(lrint(getXForFrequency(v, freq)));
Chris@277 657
Chris@908 658 double norm = 0.f;
Chris@1238 659 (void)getYForValue(v, values[bin], norm); // don't need return value, need norm
Chris@277 660
Chris@277 661 paint.setPen(mapper.map(norm));
Chris@918 662 paint.drawLine(xorigin + x, 0, xorigin + x, v->getPaintHeight() - pkh - 1);
Chris@277 663 }
Chris@277 664
Chris@277 665 paint.restore();
Chris@277 666 }
Chris@275 667
Chris@275 668 SliceLayer::paint(v, paint, rect);
Chris@277 669
Chris@278 670 //!!! All of this stuff relating to depicting frequencies
Chris@1238 671 // (keyboard, crosshairs etc) should be applicable to any slice
Chris@1238 672 // layer whose model has a vertical scale unit of Hz. However,
Chris@1238 673 // the dense 3d model at the moment doesn't record its vertical
Chris@1238 674 // scale unit -- we need to fix that and hoist this code as
Chris@1238 675 // appropriate. Same really goes for any code in SpectrogramLayer
Chris@1238 676 // that could be relevant to Colour3DPlotLayer with unit Hz, but
Chris@1238 677 // that's a bigger proposition.
Chris@278 678
Chris@1238 679 int h = v->getPaintHeight();
Chris@277 680
Chris@1238 681 PianoScale().paintPianoHorizontal
Chris@1238 682 (v, this, paint, QRect(xorigin, h - pkh - 1, w + xorigin, pkh));
Chris@345 683
Chris@345 684 paint.restore();
Chris@275 685 }
Chris@275 686
Chris@275 687 void
Chris@254 688 SpectrumLayer::getBiasCurve(BiasCurve &curve) const
Chris@254 689 {
Chris@254 690 curve = m_biasCurve;
Chris@254 691 }
Chris@199 692
Chris@316 693 void
Chris@316 694 SpectrumLayer::toXml(QTextStream &stream,
Chris@316 695 QString indent, QString extraAttributes) const
Chris@220 696 {
Chris@316 697 QString s = QString("windowSize=\"%1\" "
Chris@456 698 "windowHopLevel=\"%2\" "
Chris@456 699 "showPeaks=\"%3\" ")
Chris@220 700 .arg(m_windowSize)
Chris@456 701 .arg(m_windowHopLevel)
Chris@456 702 .arg(m_showPeaks ? "true" : "false");
Chris@220 703
Chris@316 704 SliceLayer::toXml(stream, indent, extraAttributes + " " + s);
Chris@220 705 }
Chris@220 706
Chris@220 707 void
Chris@220 708 SpectrumLayer::setProperties(const QXmlAttributes &attributes)
Chris@220 709 {
Chris@220 710 SliceLayer::setProperties(attributes);
Chris@220 711
Chris@220 712 bool ok = false;
Chris@220 713
Chris@805 714 int windowSize = attributes.value("windowSize").toUInt(&ok);
Chris@220 715 if (ok) setWindowSize(windowSize);
Chris@220 716
Chris@805 717 int windowHopLevel = attributes.value("windowHopLevel").toUInt(&ok);
Chris@220 718 if (ok) setWindowHopLevel(windowHopLevel);
Chris@456 719
Chris@456 720 bool showPeaks = (attributes.value("showPeaks").trimmed() == "true");
Chris@456 721 setShowPeaks(showPeaks);
Chris@220 722 }
Chris@220 723
Chris@220 724