annotate layer/SpectrumLayer.cpp @ 1363:bbeffb29bf09

Fix inconsistency between centre frame actually set and centre frame notified as set, which caused the start frame location to creep out of place gradually as you page through
author Chris Cannam
date Tue, 30 Oct 2018 14:00:20 +0000
parents d79e21855aef
children 2df1af7ac752
rev   line source
Chris@133 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@133 2
Chris@133 3 /*
Chris@133 4 Sonic Visualiser
Chris@133 5 An audio file viewer and annotation editor.
Chris@133 6 Centre for Digital Music, Queen Mary, University of London.
Chris@195 7 This file copyright 2006-2007 QMUL.
Chris@133 8
Chris@133 9 This program is free software; you can redistribute it and/or
Chris@133 10 modify it under the terms of the GNU General Public License as
Chris@133 11 published by the Free Software Foundation; either version 2 of the
Chris@133 12 License, or (at your option) any later version. See the file
Chris@133 13 COPYING included with this distribution for more information.
Chris@133 14 */
Chris@133 15
Chris@133 16 #include "SpectrumLayer.h"
Chris@133 17
Chris@133 18 #include "data/model/FFTModel.h"
Chris@133 19 #include "view/View.h"
Chris@153 20 #include "base/AudioLevel.h"
Chris@153 21 #include "base/Preferences.h"
Chris@167 22 #include "base/RangeMapper.h"
Chris@277 23 #include "base/Pitch.h"
Chris@1147 24 #include "base/Strings.h"
Chris@1078 25
Chris@376 26 #include "ColourMapper.h"
Chris@1078 27 #include "PaintAssistant.h"
Chris@1276 28 #include "PianoScale.h"
Chris@1281 29 #include "HorizontalFrequencyScale.h"
Chris@254 30
Chris@254 31 #include <QPainter>
Chris@316 32 #include <QTextStream>
Chris@316 33
Chris@133 34
Chris@133 35 SpectrumLayer::SpectrumLayer() :
Chris@193 36 m_originModel(0),
Chris@153 37 m_channel(-1),
Chris@153 38 m_channelSet(false),
Chris@290 39 m_windowSize(4096),
Chris@153 40 m_windowType(HanningWindow),
Chris@290 41 m_windowHopLevel(3),
Chris@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@1281 307 // we assume the frequency of a bin corresponds to the centre of
Chris@1281 308 // its visual range
Chris@1281 309 bin -= 0.5;
Chris@1238 310 return (m_sliceableModel->getSampleRate() * bin) /
Chris@1238 311 (m_sliceableModel->getHeight() * 2);
Chris@133 312 }
Chris@133 313
Chris@908 314 double
Chris@1238 315 SpectrumLayer::getXForFrequency(const LayerGeometryProvider *v, double freq) const
Chris@265 316 {
Chris@280 317 if (!m_sliceableModel) return 0;
Chris@1238 318 double bin = (freq * m_sliceableModel->getHeight() * 2) /
Chris@1238 319 m_sliceableModel->getSampleRate();
Chris@1281 320 // we want the centre of the bin range
Chris@1281 321 bin += 0.5;
Chris@1238 322 return getXForBin(v, bin);
Chris@254 323 }
Chris@254 324
Chris@260 325 bool
Chris@918 326 SpectrumLayer::getXScaleValue(const LayerGeometryProvider *v, int x,
Chris@908 327 double &value, QString &unit) const
Chris@260 328 {
Chris@1238 329 value = getFrequencyForX(v, x);
Chris@260 330 unit = "Hz";
Chris@260 331 return true;
Chris@260 332 }
Chris@260 333
Chris@264 334 bool
Chris@918 335 SpectrumLayer::getYScaleValue(const LayerGeometryProvider *v, int y,
Chris@908 336 double &value, QString &unit) const
Chris@274 337 {
Chris@1238 338 value = getValueForY(v, y);
Chris@274 339
Chris@274 340 if (m_energyScale == dBScale || m_energyScale == MeterScale) {
Chris@274 341
Chris@908 342 if (value > 0.0) {
Chris@908 343 value = 10.0 * log10(value);
Chris@284 344 if (value < m_threshold) value = m_threshold;
Chris@284 345 } else value = m_threshold;
Chris@274 346
Chris@274 347 unit = "dBV";
Chris@274 348
Chris@274 349 } else {
Chris@274 350 unit = "V";
Chris@274 351 }
Chris@274 352
Chris@274 353 return true;
Chris@274 354 }
Chris@274 355
Chris@274 356 bool
Chris@918 357 SpectrumLayer::getYScaleDifference(const LayerGeometryProvider *v, int y0, int y1,
Chris@908 358 double &diff, QString &unit) const
Chris@274 359 {
Chris@274 360 bool rv = SliceLayer::getYScaleDifference(v, y0, y1, diff, unit);
Chris@274 361 if (rv && (unit == "dBV")) unit = "dB";
Chris@274 362 return rv;
Chris@274 363 }
Chris@274 364
Chris@274 365
Chris@274 366 bool
Chris@918 367 SpectrumLayer::getCrosshairExtents(LayerGeometryProvider *v, QPainter &paint,
Chris@264 368 QPoint cursorPos,
Chris@264 369 std::vector<QRect> &extents) const
Chris@264 370 {
Chris@918 371 QRect vertical(cursorPos.x(), cursorPos.y(), 1, v->getPaintHeight() - cursorPos.y());
Chris@264 372 extents.push_back(vertical);
Chris@264 373
Chris@918 374 QRect horizontal(0, cursorPos.y(), v->getPaintWidth(), 12);
Chris@264 375 extents.push_back(horizontal);
Chris@264 376
Chris@280 377 int hoffset = 2;
Chris@280 378 if (m_binScale == LogBins) hoffset = 13;
Chris@278 379
Chris@607 380 int sw = getVerticalScaleWidth(v, false, paint);
Chris@280 381
Chris@280 382 QRect value(sw, cursorPos.y() - paint.fontMetrics().ascent() - 2,
Chris@280 383 paint.fontMetrics().width("0.0000001 V") + 2,
Chris@264 384 paint.fontMetrics().height());
Chris@280 385 extents.push_back(value);
Chris@280 386
Chris@280 387 QRect log(sw, cursorPos.y() + 2,
Chris@280 388 paint.fontMetrics().width("-80.000 dBV") + 2,
Chris@280 389 paint.fontMetrics().height());
Chris@280 390 extents.push_back(log);
Chris@280 391
Chris@280 392 QRect freq(cursorPos.x(),
Chris@918 393 v->getPaintHeight() - paint.fontMetrics().height() - hoffset,
Chris@280 394 paint.fontMetrics().width("123456 Hz") + 2,
Chris@280 395 paint.fontMetrics().height());
Chris@280 396 extents.push_back(freq);
Chris@264 397
Chris@278 398 int w(paint.fontMetrics().width("C#10+50c") + 2);
Chris@278 399 QRect pitch(cursorPos.x() - w,
Chris@918 400 v->getPaintHeight() - paint.fontMetrics().height() - hoffset,
Chris@278 401 w,
Chris@278 402 paint.fontMetrics().height());
Chris@278 403 extents.push_back(pitch);
Chris@278 404
Chris@264 405 return true;
Chris@264 406 }
Chris@264 407
Chris@254 408 void
Chris@918 409 SpectrumLayer::paintCrosshairs(LayerGeometryProvider *v, QPainter &paint,
Chris@254 410 QPoint cursorPos) const
Chris@254 411 {
Chris@280 412 if (!m_sliceableModel) return;
Chris@280 413
Chris@254 414 paint.save();
Chris@282 415 QFont fn = paint.font();
Chris@282 416 if (fn.pointSize() > 8) {
Chris@282 417 fn.setPointSize(fn.pointSize() - 1);
Chris@282 418 paint.setFont(fn);
Chris@282 419 }
Chris@254 420
Chris@1362 421 ColourMapper mapper(m_colourMap, m_colourInverted, 0, 1);
Chris@254 422 paint.setPen(mapper.getContrastingColour());
Chris@254 423
Chris@1238 424 int xorigin = m_xorigins[v->getId()];
Chris@918 425 paint.drawLine(xorigin, cursorPos.y(), v->getPaintWidth(), cursorPos.y());
Chris@918 426 paint.drawLine(cursorPos.x(), cursorPos.y(), cursorPos.x(), v->getPaintHeight());
Chris@254 427
Chris@1238 428 double fundamental = getFrequencyForX(v, cursorPos.x());
Chris@254 429
Chris@280 430 int hoffset = 2;
Chris@280 431 if (m_binScale == LogBins) hoffset = 13;
Chris@278 432
Chris@1078 433 PaintAssistant::drawVisibleText(v, paint,
Chris@1238 434 cursorPos.x() + 2,
Chris@1238 435 v->getPaintHeight() - 2 - hoffset,
Chris@1238 436 QString("%1 Hz").arg(fundamental),
Chris@1238 437 PaintAssistant::OutlinedText);
Chris@278 438
Chris@278 439 if (Pitch::isFrequencyInMidiRange(fundamental)) {
Chris@278 440 QString pitchLabel = Pitch::getPitchLabelForFrequency(fundamental);
Chris@1078 441 PaintAssistant::drawVisibleText(v, paint,
Chris@1238 442 cursorPos.x() -
Chris@1238 443 paint.fontMetrics().width(pitchLabel) - 2,
Chris@1238 444 v->getPaintHeight() - 2 - hoffset,
Chris@1238 445 pitchLabel,
Chris@1238 446 PaintAssistant::OutlinedText);
Chris@278 447 }
Chris@264 448
Chris@1238 449 double value = getValueForY(v, cursorPos.y());
Chris@908 450 double thresh = m_threshold;
Chris@908 451 double db = thresh;
Chris@908 452 if (value > 0.0) db = 10.0 * log10(value);
Chris@280 453 if (db < thresh) db = thresh;
Chris@280 454
Chris@1078 455 PaintAssistant::drawVisibleText(v, paint,
Chris@280 456 xorigin + 2,
Chris@280 457 cursorPos.y() - 2,
Chris@280 458 QString("%1 V").arg(value),
Chris@1078 459 PaintAssistant::OutlinedText);
Chris@280 460
Chris@1078 461 PaintAssistant::drawVisibleText(v, paint,
Chris@280 462 xorigin + 2,
Chris@280 463 cursorPos.y() + 2 + paint.fontMetrics().ascent(),
Chris@280 464 QString("%1 dBV").arg(db),
Chris@1078 465 PaintAssistant::OutlinedText);
Chris@280 466
Chris@254 467 int harmonic = 2;
Chris@254 468
Chris@254 469 while (harmonic < 100) {
Chris@254 470
Chris@1238 471 int hx = int(lrint(getXForFrequency(v, fundamental * harmonic)));
Chris@254 472
Chris@918 473 if (hx < xorigin || hx > v->getPaintWidth()) break;
Chris@254 474
Chris@254 475 int len = 7;
Chris@254 476
Chris@254 477 if (harmonic % 2 == 0) {
Chris@254 478 if (harmonic % 4 == 0) {
Chris@254 479 len = 12;
Chris@254 480 } else {
Chris@254 481 len = 10;
Chris@254 482 }
Chris@254 483 }
Chris@254 484
Chris@908 485 paint.drawLine(hx,
Chris@254 486 cursorPos.y(),
Chris@908 487 hx,
Chris@254 488 cursorPos.y() + len);
Chris@254 489
Chris@254 490 ++harmonic;
Chris@254 491 }
Chris@254 492
Chris@254 493 paint.restore();
Chris@254 494 }
Chris@254 495
Chris@199 496 QString
Chris@918 497 SpectrumLayer::getFeatureDescription(LayerGeometryProvider *v, QPoint &p) const
Chris@199 498 {
Chris@199 499 if (!m_sliceableModel) return "";
Chris@199 500
Chris@199 501 int minbin = 0, maxbin = 0, range = 0;
Chris@805 502 QString genericDesc = SliceLayer::getFeatureDescriptionAux
Chris@199 503 (v, p, false, minbin, maxbin, range);
Chris@199 504
Chris@199 505 if (genericDesc == "") return "";
Chris@199 506
Chris@1238 507 int i0 = minbin - m_minbin;
Chris@1238 508 int i1 = maxbin - m_minbin;
Chris@1238 509
Chris@1238 510 float minvalue = 0.0;
Chris@1238 511 if (in_range_for(m_values, i0)) minvalue = m_values[i0];
Chris@199 512
Chris@1238 513 float maxvalue = minvalue;
Chris@1238 514 if (in_range_for(m_values, i1)) maxvalue = m_values[i1];
Chris@1238 515
Chris@199 516 if (minvalue > maxvalue) std::swap(minvalue, maxvalue);
Chris@199 517
Chris@199 518 QString binstr;
Chris@199 519 QString hzstr;
Chris@908 520 int minfreq = int(lrint((minbin * m_sliceableModel->getSampleRate()) /
Chris@908 521 m_windowSize));
Chris@1256 522 int maxfreq = int(lrint((std::max(maxbin, minbin)
Chris@908 523 * m_sliceableModel->getSampleRate()) /
Chris@908 524 m_windowSize));
Chris@199 525
Chris@199 526 if (maxbin != minbin) {
Chris@199 527 binstr = tr("%1 - %2").arg(minbin+1).arg(maxbin+1);
Chris@199 528 } else {
Chris@199 529 binstr = QString("%1").arg(minbin+1);
Chris@199 530 }
Chris@199 531 if (minfreq != maxfreq) {
Chris@199 532 hzstr = tr("%1 - %2 Hz").arg(minfreq).arg(maxfreq);
Chris@199 533 } else {
Chris@199 534 hzstr = tr("%1 Hz").arg(minfreq);
Chris@199 535 }
Chris@199 536
Chris@199 537 QString valuestr;
Chris@199 538 if (maxvalue != minvalue) {
Chris@199 539 valuestr = tr("%1 - %2").arg(minvalue).arg(maxvalue);
Chris@199 540 } else {
Chris@199 541 valuestr = QString("%1").arg(minvalue);
Chris@199 542 }
Chris@199 543
Chris@199 544 QString dbstr;
Chris@908 545 double mindb = AudioLevel::multiplier_to_dB(minvalue);
Chris@908 546 double maxdb = AudioLevel::multiplier_to_dB(maxvalue);
Chris@199 547 QString mindbstr;
Chris@199 548 QString maxdbstr;
Chris@199 549 if (mindb == AudioLevel::DB_FLOOR) {
Chris@1147 550 mindbstr = Strings::minus_infinity;
Chris@199 551 } else {
Chris@908 552 mindbstr = QString("%1").arg(lrint(mindb));
Chris@199 553 }
Chris@199 554 if (maxdb == AudioLevel::DB_FLOOR) {
Chris@1147 555 maxdbstr = Strings::minus_infinity;
Chris@199 556 } else {
Chris@908 557 maxdbstr = QString("%1").arg(lrint(maxdb));
Chris@199 558 }
Chris@908 559 if (lrint(mindb) != lrint(maxdb)) {
Chris@199 560 dbstr = tr("%1 - %2").arg(mindbstr).arg(maxdbstr);
Chris@199 561 } else {
Chris@199 562 dbstr = tr("%1").arg(mindbstr);
Chris@199 563 }
Chris@199 564
Chris@199 565 QString description;
Chris@199 566
Chris@248 567 if (range > int(m_sliceableModel->getResolution())) {
Chris@199 568 description = tr("%1\nBin:\t%2 (%3)\n%4 value:\t%5\ndB:\t%6")
Chris@199 569 .arg(genericDesc)
Chris@199 570 .arg(binstr)
Chris@199 571 .arg(hzstr)
Chris@199 572 .arg(m_samplingMode == NearestSample ? tr("First") :
Chris@199 573 m_samplingMode == SampleMean ? tr("Mean") : tr("Peak"))
Chris@199 574 .arg(valuestr)
Chris@199 575 .arg(dbstr);
Chris@199 576 } else {
Chris@199 577 description = tr("%1\nBin:\t%2 (%3)\nValue:\t%4\ndB:\t%5")
Chris@199 578 .arg(genericDesc)
Chris@199 579 .arg(binstr)
Chris@199 580 .arg(hzstr)
Chris@199 581 .arg(valuestr)
Chris@199 582 .arg(dbstr);
Chris@199 583 }
Chris@199 584
Chris@199 585 return description;
Chris@199 586 }
Chris@199 587
Chris@254 588 void
Chris@916 589 SpectrumLayer::paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const
Chris@275 590 {
Chris@275 591 if (!m_originModel || !m_originModel->isOK() ||
Chris@349 592 !m_originModel->isReady()) {
Chris@587 593 SVDEBUG << "SpectrumLayer::paint: no origin model, or origin model not OK or not ready" << endl;
Chris@349 594 return;
Chris@349 595 }
Chris@275 596
Chris@275 597 if (m_newFFTNeeded) {
Chris@587 598 SVDEBUG << "SpectrumLayer::paint: new FFT needed, calling setupFFT" << endl;
Chris@275 599 const_cast<SpectrumLayer *>(this)->setupFFT(); //ugh
Chris@275 600 }
Chris@277 601
Chris@277 602 FFTModel *fft = dynamic_cast<FFTModel *>
Chris@277 603 (const_cast<DenseThreeDimensionalModel *>(m_sliceableModel));
Chris@277 604
Chris@908 605 double thresh = (pow(10, -6) / m_gain) * (m_windowSize / 2.0); // -60dB adj
Chris@277 606
Chris@607 607 int xorigin = getVerticalScaleWidth(v, false, paint) + 1;
Chris@1281 608 int scaleHeight = getHorizontalScaleHeight(v, paint);
Chris@345 609
Chris@284 610 if (fft && m_showPeaks) {
Chris@277 611
Chris@277 612 // draw peak lines
Chris@277 613
Chris@908 614 int col = int(v->getCentreFrame() / fft->getResolution());
Chris@277 615
Chris@277 616 paint.save();
Chris@277 617 paint.setRenderHint(QPainter::Antialiasing, false);
Chris@277 618
Chris@1281 619 ColourMapper mapper =
Chris@1281 620 hasLightBackground() ?
Chris@1362 621 ColourMapper(ColourMapper::BlackOnWhite, m_colourInverted, 0, 1) :
Chris@1362 622 ColourMapper(ColourMapper::WhiteOnBlack, m_colourInverted, 0, 1);
Chris@1281 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@277 632 BiasCurve curve;
Chris@277 633 getBiasCurve(curve);
Chris@908 634 int cs = int(curve.size());
Chris@280 635
Chris@908 636 std::vector<double> values;
Chris@277 637
Chris@805 638 for (int bin = 0; bin < fft->getHeight(); ++bin) {
Chris@908 639 double value = m_sliceableModel->getValueAt(col, bin);
Chris@280 640 if (bin < cs) value *= curve[bin];
Chris@280 641 values.push_back(value);
Chris@280 642 }
Chris@280 643
Chris@280 644 for (FFTModel::PeakSet::iterator i = peaks.begin();
Chris@280 645 i != peaks.end(); ++i) {
Chris@280 646
Chris@805 647 int bin = i->first;
Chris@277 648
Chris@682 649 // cerr << "bin = " << bin << ", thresh = " << thresh << ", value = " << fft->getMagnitudeAt(col, bin) << endl;
Chris@280 650
Chris@908 651 if (!fft->isOverThreshold(col, bin, float(thresh))) continue;
Chris@277 652
Chris@908 653 double freq = i->second;
Chris@280 654
Chris@1238 655 int x = int(lrint(getXForFrequency(v, freq)));
Chris@277 656
Chris@908 657 double norm = 0.f;
Chris@1238 658 (void)getYForValue(v, values[bin], norm); // don't need return value, need norm
Chris@277 659
Chris@277 660 paint.setPen(mapper.map(norm));
Chris@1281 661 paint.drawLine(x, 0, x, v->getPaintHeight() - scaleHeight - 1);
Chris@277 662 }
Chris@277 663
Chris@277 664 paint.restore();
Chris@277 665 }
Chris@275 666
Chris@1281 667 paint.save();
Chris@1281 668
Chris@275 669 SliceLayer::paint(v, paint, rect);
Chris@1281 670
Chris@1281 671 paintHorizontalScale(v, paint, xorigin);
Chris@277 672
Chris@1281 673 paint.restore();
Chris@1281 674 }
Chris@1281 675
Chris@1281 676 int
Chris@1281 677 SpectrumLayer::getHorizontalScaleHeight(LayerGeometryProvider *v,
Chris@1281 678 QPainter &paint) const
Chris@1281 679 {
Chris@1281 680 int pkh = int(paint.fontMetrics().height() * 0.7 + 0.5);
Chris@1281 681 if (pkh < 10) pkh = 10;
Chris@1281 682
Chris@1281 683 int scaleh = HorizontalFrequencyScale().getHeight(v, paint);
Chris@1281 684
Chris@1281 685 return pkh + scaleh;
Chris@1281 686 }
Chris@1281 687
Chris@1281 688 void
Chris@1281 689 SpectrumLayer::paintHorizontalScale(LayerGeometryProvider *v,
Chris@1281 690 QPainter &paint,
Chris@1281 691 int xorigin) const
Chris@1281 692 {
Chris@278 693 //!!! All of this stuff relating to depicting frequencies
Chris@1238 694 // (keyboard, crosshairs etc) should be applicable to any slice
Chris@1238 695 // layer whose model has a vertical scale unit of Hz. However,
Chris@1238 696 // the dense 3d model at the moment doesn't record its vertical
Chris@1238 697 // scale unit -- we need to fix that and hoist this code as
Chris@1238 698 // appropriate. Same really goes for any code in SpectrogramLayer
Chris@1238 699 // that could be relevant to Colour3DPlotLayer with unit Hz, but
Chris@1238 700 // that's a bigger proposition.
Chris@278 701
Chris@1281 702 if (!v->getViewManager()->shouldShowHorizontalValueScale()) {
Chris@1281 703 return;
Chris@1281 704 }
Chris@1281 705
Chris@1281 706 int totalScaleHeight = getHorizontalScaleHeight(v, paint); // inc piano
Chris@1281 707 int freqScaleHeight = HorizontalFrequencyScale().getHeight(v, paint);
Chris@1281 708 int paintHeight = v->getPaintHeight();
Chris@1281 709 int paintWidth = v->getPaintWidth();
Chris@277 710
Chris@1238 711 PianoScale().paintPianoHorizontal
Chris@1276 712 (v, this, paint,
Chris@1281 713 QRect(xorigin, paintHeight - totalScaleHeight - 1,
Chris@1281 714 paintWidth - 1, totalScaleHeight - freqScaleHeight));
Chris@345 715
Chris@1281 716 int scaleLeft = int(getXForBin(v, 1));
Chris@1281 717
Chris@1281 718 paint.drawLine(int(getXForBin(v, 0)), paintHeight - freqScaleHeight,
Chris@1281 719 scaleLeft, paintHeight - freqScaleHeight);
Chris@1281 720
Chris@1281 721 QString hz = tr("Hz");
Chris@1281 722 int hzw = paint.fontMetrics().width(hz);
Chris@1281 723 if (scaleLeft > hzw + 5) {
Chris@1281 724 paint.drawText
Chris@1281 725 (scaleLeft - hzw - 5,
Chris@1281 726 paintHeight - freqScaleHeight + paint.fontMetrics().ascent() + 5,
Chris@1281 727 hz);
Chris@1281 728 }
Chris@1281 729
Chris@1281 730 HorizontalFrequencyScale().paintScale
Chris@1276 731 (v, this, paint,
Chris@1281 732 QRect(scaleLeft, paintHeight - freqScaleHeight,
Chris@1281 733 paintWidth, totalScaleHeight),
Chris@1281 734 m_binScale == LogBins);
Chris@275 735 }
Chris@275 736
Chris@275 737 void
Chris@254 738 SpectrumLayer::getBiasCurve(BiasCurve &curve) const
Chris@254 739 {
Chris@254 740 curve = m_biasCurve;
Chris@254 741 }
Chris@199 742
Chris@316 743 void
Chris@316 744 SpectrumLayer::toXml(QTextStream &stream,
Chris@316 745 QString indent, QString extraAttributes) const
Chris@220 746 {
Chris@316 747 QString s = QString("windowSize=\"%1\" "
Chris@456 748 "windowHopLevel=\"%2\" "
Chris@456 749 "showPeaks=\"%3\" ")
Chris@220 750 .arg(m_windowSize)
Chris@456 751 .arg(m_windowHopLevel)
Chris@456 752 .arg(m_showPeaks ? "true" : "false");
Chris@220 753
Chris@316 754 SliceLayer::toXml(stream, indent, extraAttributes + " " + s);
Chris@220 755 }
Chris@220 756
Chris@220 757 void
Chris@220 758 SpectrumLayer::setProperties(const QXmlAttributes &attributes)
Chris@220 759 {
Chris@220 760 SliceLayer::setProperties(attributes);
Chris@220 761
Chris@220 762 bool ok = false;
Chris@220 763
Chris@805 764 int windowSize = attributes.value("windowSize").toUInt(&ok);
Chris@220 765 if (ok) setWindowSize(windowSize);
Chris@220 766
Chris@805 767 int windowHopLevel = attributes.value("windowHopLevel").toUInt(&ok);
Chris@220 768 if (ok) setWindowHopLevel(windowHopLevel);
Chris@456 769
Chris@456 770 bool showPeaks = (attributes.value("showPeaks").trimmed() == "true");
Chris@456 771 setShowPeaks(showPeaks);
Chris@220 772 }
Chris@220 773
Chris@220 774