annotate layer/SpectrumLayer.cpp @ 1147:1badacff7ab2 3.0-integration

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