annotate layer/SpectrumLayer.cpp @ 1277:82258db96244 horizontal-scale

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