annotate layer/SpectrumLayer.cpp @ 1238:4d0ca1ab4cd0

Some work to make spectrum layers (and slice layers generally) zoomable in the frequency axis. Also fixes a number of view id mixups in SliceLayer which broke offset calculations for the x axis scale.
author Chris Cannam
date Tue, 07 Feb 2017 14:55:19 +0000
parents ff97318e993c
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