annotate layer/SpectrumLayer.cpp @ 1127:9fb8dfd7ce4c spectrogram-minor-refactor

Fix threshold in spectrogram -- it wasn't working in the last release. There is a new protocol for this. Formerly the threshold parameter had a range from -50dB to 0 with the default at -50, and -50 treated internally as "no threshold". However, there was a hardcoded, hidden internal threshold for spectrogram colour mapping at -80dB with anything below this being rounded to zero. Now the threshold parameter has range -81 to -1 with the default at -80, -81 is treated internally as "no threshold", and there is no hidden internal threshold. So the default behaviour is the same as before, an effective -80dB threshold, but it is now possible to change this in both directions. Sessions reloaded from prior versions may look slightly different because, if the session says there should be no threshold, there will now actually be no threshold instead of having the hidden internal one. Still need to do something in the UI to make it apparent that the -81dB setting removes the threshold entirely. This is at least no worse than the previous, also obscured, magic -50dB setting.
author Chris Cannam
date Mon, 01 Aug 2016 16:21:01 +0100
parents ee01a4062747
children 1badacff7ab2
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@1078 24
Chris@376 25 #include "ColourMapper.h"
Chris@1078 26 #include "PaintAssistant.h"
Chris@254 27
Chris@254 28 #include <QPainter>
Chris@316 29 #include <QTextStream>
Chris@316 30
Chris@133 31
Chris@133 32 SpectrumLayer::SpectrumLayer() :
Chris@193 33 m_originModel(0),
Chris@153 34 m_channel(-1),
Chris@153 35 m_channelSet(false),
Chris@290 36 m_windowSize(4096),
Chris@153 37 m_windowType(HanningWindow),
Chris@290 38 m_windowHopLevel(3),
Chris@284 39 m_showPeaks(false),
Chris@275 40 m_newFFTNeeded(true)
Chris@133 41 {
Chris@153 42 Preferences *prefs = Preferences::getInstance();
Chris@153 43 connect(prefs, SIGNAL(propertyChanged(PropertyContainer::PropertyName)),
Chris@153 44 this, SLOT(preferenceChanged(PropertyContainer::PropertyName)));
Chris@153 45 setWindowType(prefs->getWindowType());
Chris@195 46
Chris@195 47 setBinScale(LogBins);
Chris@133 48 }
Chris@133 49
Chris@133 50 SpectrumLayer::~SpectrumLayer()
Chris@133 51 {
Chris@349 52 Model *m = const_cast<Model *>
Chris@349 53 (static_cast<const Model *>(m_sliceableModel));
Chris@458 54 if (m) m->aboutToDelete();
Chris@349 55 m_sliceableModel = 0;
Chris@349 56 delete m;
Chris@133 57 }
Chris@133 58
Chris@133 59 void
Chris@133 60 SpectrumLayer::setModel(DenseTimeValueModel *model)
Chris@133 61 {
Chris@587 62 SVDEBUG << "SpectrumLayer::setModel(" << model << ") from " << m_originModel << endl;
Chris@345 63
Chris@193 64 if (m_originModel == model) return;
Chris@349 65
Chris@193 66 m_originModel = model;
Chris@277 67
Chris@350 68 if (m_sliceableModel) {
Chris@350 69 Model *m = const_cast<Model *>
Chris@350 70 (static_cast<const Model *>(m_sliceableModel));
Chris@350 71 m->aboutToDelete();
Chris@350 72 setSliceableModel(0);
Chris@350 73 delete m;
Chris@350 74 }
Chris@350 75
Chris@349 76 m_newFFTNeeded = true;
Chris@349 77
Chris@349 78 emit layerParametersChanged();
Chris@349 79 }
Chris@349 80
Chris@349 81 void
Chris@349 82 SpectrumLayer::setChannel(int channel)
Chris@349 83 {
Chris@587 84 SVDEBUG << "SpectrumLayer::setChannel(" << channel << ") from " << m_channel << endl;
Chris@349 85
Chris@349 86 m_channelSet = true;
Chris@349 87
Chris@349 88 if (m_channel == channel) return;
Chris@349 89
Chris@349 90 m_channel = channel;
Chris@349 91
Chris@349 92 m_newFFTNeeded = true;
Chris@349 93
Chris@349 94 emit layerParametersChanged();
Chris@153 95 }
Chris@153 96
Chris@153 97 void
Chris@193 98 SpectrumLayer::setupFFT()
Chris@153 99 {
Chris@349 100 if (m_sliceableModel) {
Chris@349 101 Model *m = const_cast<Model *>
Chris@349 102 (static_cast<const Model *>(m_sliceableModel));
Chris@349 103 m->aboutToDelete();
Chris@193 104 setSliceableModel(0);
Chris@349 105 delete m;
Chris@349 106 }
Chris@349 107
Chris@349 108 if (!m_originModel) {
Chris@349 109 return;
Chris@153 110 }
Chris@153 111
Chris@193 112 FFTModel *newFFT = new FFTModel(m_originModel,
Chris@193 113 m_channel,
Chris@193 114 m_windowType,
Chris@193 115 m_windowSize,
Chris@193 116 getWindowIncrement(),
Chris@975 117 m_windowSize);
Chris@153 118
Chris@193 119 setSliceableModel(newFFT);
Chris@193 120
Chris@254 121 m_biasCurve.clear();
Chris@805 122 for (int i = 0; i < m_windowSize; ++i) {
Chris@254 123 m_biasCurve.push_back(1.f / (float(m_windowSize)/2.f));
Chris@254 124 }
Chris@254 125
Chris@349 126 m_newFFTNeeded = false;
Chris@133 127 }
Chris@133 128
Chris@153 129 Layer::PropertyList
Chris@153 130 SpectrumLayer::getProperties() const
Chris@153 131 {
Chris@193 132 PropertyList list = SliceLayer::getProperties();
Chris@153 133 list.push_back("Window Size");
Chris@153 134 list.push_back("Window Increment");
Chris@284 135 list.push_back("Show Peak Frequencies");
Chris@153 136 return list;
Chris@153 137 }
Chris@153 138
Chris@153 139 QString
Chris@153 140 SpectrumLayer::getPropertyLabel(const PropertyName &name) const
Chris@153 141 {
Chris@153 142 if (name == "Window Size") return tr("Window Size");
Chris@153 143 if (name == "Window Increment") return tr("Window Overlap");
Chris@284 144 if (name == "Show Peak Frequencies") return tr("Show Peak Frequencies");
Chris@193 145 return SliceLayer::getPropertyLabel(name);
Chris@153 146 }
Chris@153 147
Chris@335 148 QString
Chris@335 149 SpectrumLayer::getPropertyIconName(const PropertyName &name) const
Chris@335 150 {
Chris@335 151 if (name == "Show Peak Frequencies") return "show-peaks";
Chris@335 152 return SliceLayer::getPropertyIconName(name);
Chris@335 153 }
Chris@335 154
Chris@153 155 Layer::PropertyType
Chris@153 156 SpectrumLayer::getPropertyType(const PropertyName &name) const
Chris@153 157 {
Chris@193 158 if (name == "Window Size") return ValueProperty;
Chris@193 159 if (name == "Window Increment") return ValueProperty;
Chris@284 160 if (name == "Show Peak Frequencies") return ToggleProperty;
Chris@193 161 return SliceLayer::getPropertyType(name);
Chris@153 162 }
Chris@153 163
Chris@153 164 QString
Chris@153 165 SpectrumLayer::getPropertyGroupName(const PropertyName &name) const
Chris@153 166 {
Chris@153 167 if (name == "Window Size" ||
Chris@153 168 name == "Window Increment") return tr("Window");
Chris@557 169 if (name == "Show Peak Frequencies") return tr("Bins");
Chris@193 170 return SliceLayer::getPropertyGroupName(name);
Chris@153 171 }
Chris@153 172
Chris@153 173 int
Chris@153 174 SpectrumLayer::getPropertyRangeAndValue(const PropertyName &name,
Chris@216 175 int *min, int *max, int *deflt) const
Chris@153 176 {
Chris@216 177 int val = 0;
Chris@153 178
Chris@216 179 int garbage0, garbage1, garbage2;
Chris@153 180 if (!min) min = &garbage0;
Chris@153 181 if (!max) max = &garbage1;
Chris@216 182 if (!deflt) deflt = &garbage2;
Chris@153 183
Chris@193 184 if (name == "Window Size") {
Chris@153 185
Chris@153 186 *min = 0;
Chris@254 187 *max = 15;
Chris@216 188 *deflt = 5;
Chris@153 189
Chris@216 190 val = 0;
Chris@153 191 int ws = m_windowSize;
Chris@216 192 while (ws > 32) { ws >>= 1; val ++; }
Chris@153 193
Chris@153 194 } else if (name == "Window Increment") {
Chris@153 195
Chris@153 196 *min = 0;
Chris@153 197 *max = 5;
Chris@216 198 *deflt = 2;
Chris@153 199
Chris@216 200 val = m_windowHopLevel;
Chris@153 201
Chris@284 202 } else if (name == "Show Peak Frequencies") {
Chris@284 203
Chris@284 204 return m_showPeaks ? 1 : 0;
Chris@284 205
Chris@153 206 } else {
Chris@193 207
Chris@216 208 val = SliceLayer::getPropertyRangeAndValue(name, min, max, deflt);
Chris@153 209 }
Chris@153 210
Chris@216 211 return val;
Chris@153 212 }
Chris@153 213
Chris@153 214 QString
Chris@153 215 SpectrumLayer::getPropertyValueLabel(const PropertyName &name,
Chris@153 216 int value) const
Chris@153 217 {
Chris@153 218 if (name == "Window Size") {
Chris@153 219 return QString("%1").arg(32 << value);
Chris@153 220 }
Chris@153 221 if (name == "Window Increment") {
Chris@153 222 switch (value) {
Chris@153 223 default:
Chris@153 224 case 0: return tr("None");
Chris@153 225 case 1: return tr("25 %");
Chris@153 226 case 2: return tr("50 %");
Chris@153 227 case 3: return tr("75 %");
Chris@153 228 case 4: return tr("87.5 %");
Chris@153 229 case 5: return tr("93.75 %");
Chris@153 230 }
Chris@153 231 }
Chris@193 232 return SliceLayer::getPropertyValueLabel(name, value);
Chris@153 233 }
Chris@153 234
Chris@167 235 RangeMapper *
Chris@167 236 SpectrumLayer::getNewPropertyRangeMapper(const PropertyName &name) const
Chris@167 237 {
Chris@193 238 return SliceLayer::getNewPropertyRangeMapper(name);
Chris@167 239 }
Chris@167 240
Chris@133 241 void
Chris@153 242 SpectrumLayer::setProperty(const PropertyName &name, int value)
Chris@133 243 {
Chris@193 244 if (name == "Window Size") {
Chris@153 245 setWindowSize(32 << value);
Chris@153 246 } else if (name == "Window Increment") {
Chris@153 247 setWindowHopLevel(value);
Chris@284 248 } else if (name == "Show Peak Frequencies") {
Chris@284 249 setShowPeaks(value ? true : false);
Chris@193 250 } else {
Chris@193 251 SliceLayer::setProperty(name, value);
Chris@153 252 }
Chris@153 253 }
Chris@153 254
Chris@153 255 void
Chris@805 256 SpectrumLayer::setWindowSize(int ws)
Chris@153 257 {
Chris@153 258 if (m_windowSize == ws) return;
Chris@153 259 m_windowSize = ws;
Chris@275 260 m_newFFTNeeded = true;
Chris@153 261 emit layerParametersChanged();
Chris@153 262 }
Chris@153 263
Chris@153 264 void
Chris@805 265 SpectrumLayer::setWindowHopLevel(int v)
Chris@153 266 {
Chris@153 267 if (m_windowHopLevel == v) return;
Chris@153 268 m_windowHopLevel = v;
Chris@275 269 m_newFFTNeeded = true;
Chris@153 270 emit layerParametersChanged();
Chris@153 271 }
Chris@153 272
Chris@153 273 void
Chris@153 274 SpectrumLayer::setWindowType(WindowType w)
Chris@153 275 {
Chris@153 276 if (m_windowType == w) return;
Chris@153 277 m_windowType = w;
Chris@275 278 m_newFFTNeeded = true;
Chris@153 279 emit layerParametersChanged();
Chris@153 280 }
Chris@153 281
Chris@153 282 void
Chris@284 283 SpectrumLayer::setShowPeaks(bool show)
Chris@284 284 {
Chris@284 285 if (m_showPeaks == show) return;
Chris@284 286 m_showPeaks = show;
Chris@284 287 emit layerParametersChanged();
Chris@284 288 }
Chris@284 289
Chris@284 290 void
Chris@153 291 SpectrumLayer::preferenceChanged(PropertyContainer::PropertyName name)
Chris@153 292 {
Chris@153 293 if (name == "Window Type") {
Chris@153 294 setWindowType(Preferences::getInstance()->getWindowType());
Chris@153 295 return;
Chris@153 296 }
Chris@153 297 }
Chris@153 298
Chris@133 299 bool
Chris@908 300 SpectrumLayer::getValueExtents(double &, double &, bool &, QString &) const
Chris@133 301 {
Chris@133 302 return false;
Chris@133 303 }
Chris@133 304
Chris@908 305 double
Chris@908 306 SpectrumLayer::getXForBin(int bin, int totalBins, double w) const
Chris@265 307 {
Chris@265 308 if (!m_sliceableModel) return SliceLayer::getXForBin(bin, totalBins, w);
Chris@265 309
Chris@908 310 sv_samplerate_t sampleRate = m_sliceableModel->getSampleRate();
Chris@908 311 double binfreq = (sampleRate * bin) / (totalBins * 2);
Chris@265 312
Chris@265 313 return getXForFrequency(binfreq, w);
Chris@265 314 }
Chris@265 315
Chris@265 316 int
Chris@908 317 SpectrumLayer::getBinForX(double x, int totalBins, double w) const
Chris@265 318 {
Chris@265 319 if (!m_sliceableModel) return SliceLayer::getBinForX(x, totalBins, w);
Chris@265 320
Chris@908 321 sv_samplerate_t sampleRate = m_sliceableModel->getSampleRate();
Chris@908 322 double binfreq = getFrequencyForX(x, w);
Chris@265 323
Chris@265 324 return int((binfreq * totalBins * 2) / sampleRate);
Chris@265 325 }
Chris@265 326
Chris@908 327 double
Chris@908 328 SpectrumLayer::getFrequencyForX(double x, double w) const
Chris@254 329 {
Chris@908 330 double freq = 0;
Chris@280 331 if (!m_sliceableModel) return 0;
Chris@254 332
Chris@908 333 sv_samplerate_t sampleRate = m_sliceableModel->getSampleRate();
Chris@254 334
Chris@908 335 double maxfreq = double(sampleRate) / 2;
Chris@254 336
Chris@254 337 switch (m_binScale) {
Chris@254 338
Chris@254 339 case LinearBins:
Chris@254 340 freq = ((x * maxfreq) / w);
Chris@254 341 break;
Chris@254 342
Chris@254 343 case LogBins:
Chris@908 344 freq = pow(10.0, (x * log10(maxfreq)) / w);
Chris@254 345 break;
Chris@254 346
Chris@254 347 case InvertedLogBins:
Chris@908 348 freq = maxfreq - pow(10.0, ((w - x) * log10(maxfreq)) / w);
Chris@254 349 break;
Chris@254 350 }
Chris@254 351
Chris@254 352 return freq;
Chris@254 353 }
Chris@254 354
Chris@908 355 double
Chris@908 356 SpectrumLayer::getXForFrequency(double freq, double w) const
Chris@254 357 {
Chris@908 358 double x = 0;
Chris@280 359 if (!m_sliceableModel) return x;
Chris@254 360
Chris@908 361 sv_samplerate_t sampleRate = m_sliceableModel->getSampleRate();
Chris@254 362
Chris@908 363 double maxfreq = double(sampleRate) / 2;
Chris@254 364
Chris@254 365 switch (m_binScale) {
Chris@254 366
Chris@254 367 case LinearBins:
Chris@254 368 x = (freq * w) / maxfreq;
Chris@254 369 break;
Chris@254 370
Chris@254 371 case LogBins:
Chris@908 372 x = (log10(freq) * w) / log10(maxfreq);
Chris@254 373 break;
Chris@254 374
Chris@254 375 case InvertedLogBins:
Chris@274 376 if (maxfreq == freq) x = w;
Chris@908 377 else x = w - (log10(maxfreq - freq) * w) / log10(maxfreq);
Chris@254 378 break;
Chris@254 379 }
Chris@254 380
Chris@254 381 return x;
Chris@254 382 }
Chris@254 383
Chris@260 384 bool
Chris@918 385 SpectrumLayer::getXScaleValue(const LayerGeometryProvider *v, int x,
Chris@908 386 double &value, QString &unit) const
Chris@260 387 {
Chris@267 388 if (m_xorigins.find(v) == m_xorigins.end()) return false;
Chris@267 389 int xorigin = m_xorigins.find(v)->second;
Chris@918 390 value = getFrequencyForX(x - xorigin, v->getPaintWidth() - xorigin - 1);
Chris@260 391 unit = "Hz";
Chris@260 392 return true;
Chris@260 393 }
Chris@260 394
Chris@264 395 bool
Chris@918 396 SpectrumLayer::getYScaleValue(const LayerGeometryProvider *v, int y,
Chris@908 397 double &value, QString &unit) const
Chris@274 398 {
Chris@274 399 value = getValueForY(y, v);
Chris@274 400
Chris@274 401 if (m_energyScale == dBScale || m_energyScale == MeterScale) {
Chris@274 402
Chris@908 403 if (value > 0.0) {
Chris@908 404 value = 10.0 * log10(value);
Chris@284 405 if (value < m_threshold) value = m_threshold;
Chris@284 406 } else value = m_threshold;
Chris@274 407
Chris@274 408 unit = "dBV";
Chris@274 409
Chris@274 410 } else {
Chris@274 411 unit = "V";
Chris@274 412 }
Chris@274 413
Chris@274 414 return true;
Chris@274 415 }
Chris@274 416
Chris@274 417 bool
Chris@918 418 SpectrumLayer::getYScaleDifference(const LayerGeometryProvider *v, int y0, int y1,
Chris@908 419 double &diff, QString &unit) const
Chris@274 420 {
Chris@274 421 bool rv = SliceLayer::getYScaleDifference(v, y0, y1, diff, unit);
Chris@274 422 if (rv && (unit == "dBV")) unit = "dB";
Chris@274 423 return rv;
Chris@274 424 }
Chris@274 425
Chris@274 426
Chris@274 427 bool
Chris@918 428 SpectrumLayer::getCrosshairExtents(LayerGeometryProvider *v, QPainter &paint,
Chris@264 429 QPoint cursorPos,
Chris@264 430 std::vector<QRect> &extents) const
Chris@264 431 {
Chris@918 432 QRect vertical(cursorPos.x(), cursorPos.y(), 1, v->getPaintHeight() - cursorPos.y());
Chris@264 433 extents.push_back(vertical);
Chris@264 434
Chris@918 435 QRect horizontal(0, cursorPos.y(), v->getPaintWidth(), 12);
Chris@264 436 extents.push_back(horizontal);
Chris@264 437
Chris@280 438 int hoffset = 2;
Chris@280 439 if (m_binScale == LogBins) hoffset = 13;
Chris@278 440
Chris@607 441 int sw = getVerticalScaleWidth(v, false, paint);
Chris@280 442
Chris@280 443 QRect value(sw, cursorPos.y() - paint.fontMetrics().ascent() - 2,
Chris@280 444 paint.fontMetrics().width("0.0000001 V") + 2,
Chris@264 445 paint.fontMetrics().height());
Chris@280 446 extents.push_back(value);
Chris@280 447
Chris@280 448 QRect log(sw, cursorPos.y() + 2,
Chris@280 449 paint.fontMetrics().width("-80.000 dBV") + 2,
Chris@280 450 paint.fontMetrics().height());
Chris@280 451 extents.push_back(log);
Chris@280 452
Chris@280 453 QRect freq(cursorPos.x(),
Chris@918 454 v->getPaintHeight() - paint.fontMetrics().height() - hoffset,
Chris@280 455 paint.fontMetrics().width("123456 Hz") + 2,
Chris@280 456 paint.fontMetrics().height());
Chris@280 457 extents.push_back(freq);
Chris@264 458
Chris@278 459 int w(paint.fontMetrics().width("C#10+50c") + 2);
Chris@278 460 QRect pitch(cursorPos.x() - w,
Chris@918 461 v->getPaintHeight() - paint.fontMetrics().height() - hoffset,
Chris@278 462 w,
Chris@278 463 paint.fontMetrics().height());
Chris@278 464 extents.push_back(pitch);
Chris@278 465
Chris@264 466 return true;
Chris@264 467 }
Chris@264 468
Chris@254 469 void
Chris@918 470 SpectrumLayer::paintCrosshairs(LayerGeometryProvider *v, QPainter &paint,
Chris@254 471 QPoint cursorPos) const
Chris@254 472 {
Chris@280 473 if (!m_sliceableModel) return;
Chris@280 474
Chris@254 475 paint.save();
Chris@282 476 QFont fn = paint.font();
Chris@282 477 if (fn.pointSize() > 8) {
Chris@282 478 fn.setPointSize(fn.pointSize() - 1);
Chris@282 479 paint.setFont(fn);
Chris@282 480 }
Chris@254 481
Chris@254 482 ColourMapper mapper(m_colourMap, 0, 1);
Chris@254 483 paint.setPen(mapper.getContrastingColour());
Chris@254 484
Chris@254 485 int xorigin = m_xorigins[v];
Chris@918 486 int w = v->getPaintWidth() - xorigin - 1;
Chris@254 487
Chris@918 488 paint.drawLine(xorigin, cursorPos.y(), v->getPaintWidth(), cursorPos.y());
Chris@918 489 paint.drawLine(cursorPos.x(), cursorPos.y(), cursorPos.x(), v->getPaintHeight());
Chris@254 490
Chris@908 491 double fundamental = getFrequencyForX(cursorPos.x() - xorigin, w);
Chris@254 492
Chris@280 493 int hoffset = 2;
Chris@280 494 if (m_binScale == LogBins) hoffset = 13;
Chris@278 495
Chris@1078 496 PaintAssistant::drawVisibleText(v, paint,
Chris@278 497 cursorPos.x() + 2,
Chris@918 498 v->getPaintHeight() - 2 - hoffset,
Chris@278 499 QString("%1 Hz").arg(fundamental),
Chris@1078 500 PaintAssistant::OutlinedText);
Chris@278 501
Chris@278 502 if (Pitch::isFrequencyInMidiRange(fundamental)) {
Chris@278 503 QString pitchLabel = Pitch::getPitchLabelForFrequency(fundamental);
Chris@1078 504 PaintAssistant::drawVisibleText(v, paint,
Chris@278 505 cursorPos.x() - paint.fontMetrics().width(pitchLabel) - 2,
Chris@918 506 v->getPaintHeight() - 2 - hoffset,
Chris@278 507 pitchLabel,
Chris@1078 508 PaintAssistant::OutlinedText);
Chris@278 509 }
Chris@264 510
Chris@908 511 double value = getValueForY(cursorPos.y(), v);
Chris@908 512 double thresh = m_threshold;
Chris@908 513 double db = thresh;
Chris@908 514 if (value > 0.0) db = 10.0 * log10(value);
Chris@280 515 if (db < thresh) db = thresh;
Chris@280 516
Chris@1078 517 PaintAssistant::drawVisibleText(v, paint,
Chris@280 518 xorigin + 2,
Chris@280 519 cursorPos.y() - 2,
Chris@280 520 QString("%1 V").arg(value),
Chris@1078 521 PaintAssistant::OutlinedText);
Chris@280 522
Chris@1078 523 PaintAssistant::drawVisibleText(v, paint,
Chris@280 524 xorigin + 2,
Chris@280 525 cursorPos.y() + 2 + paint.fontMetrics().ascent(),
Chris@280 526 QString("%1 dBV").arg(db),
Chris@1078 527 PaintAssistant::OutlinedText);
Chris@280 528
Chris@254 529 int harmonic = 2;
Chris@254 530
Chris@254 531 while (harmonic < 100) {
Chris@254 532
Chris@908 533 int hx = int(lrint(getXForFrequency(fundamental * harmonic, w)));
Chris@254 534 hx += xorigin;
Chris@254 535
Chris@918 536 if (hx < xorigin || hx > v->getPaintWidth()) break;
Chris@254 537
Chris@254 538 int len = 7;
Chris@254 539
Chris@254 540 if (harmonic % 2 == 0) {
Chris@254 541 if (harmonic % 4 == 0) {
Chris@254 542 len = 12;
Chris@254 543 } else {
Chris@254 544 len = 10;
Chris@254 545 }
Chris@254 546 }
Chris@254 547
Chris@908 548 paint.drawLine(hx,
Chris@254 549 cursorPos.y(),
Chris@908 550 hx,
Chris@254 551 cursorPos.y() + len);
Chris@254 552
Chris@254 553 ++harmonic;
Chris@254 554 }
Chris@254 555
Chris@254 556 paint.restore();
Chris@254 557 }
Chris@254 558
Chris@199 559 QString
Chris@918 560 SpectrumLayer::getFeatureDescription(LayerGeometryProvider *v, QPoint &p) const
Chris@199 561 {
Chris@199 562 if (!m_sliceableModel) return "";
Chris@199 563
Chris@199 564 int minbin = 0, maxbin = 0, range = 0;
Chris@805 565 QString genericDesc = SliceLayer::getFeatureDescriptionAux
Chris@199 566 (v, p, false, minbin, maxbin, range);
Chris@199 567
Chris@199 568 if (genericDesc == "") return "";
Chris@199 569
Chris@908 570 double minvalue = 0.f;
Chris@248 571 if (minbin < int(m_values.size())) minvalue = m_values[minbin];
Chris@199 572
Chris@908 573 double maxvalue = minvalue;
Chris@248 574 if (maxbin < int(m_values.size())) maxvalue = m_values[maxbin];
Chris@199 575
Chris@199 576 if (minvalue > maxvalue) std::swap(minvalue, maxvalue);
Chris@199 577
Chris@199 578 QString binstr;
Chris@199 579 QString hzstr;
Chris@908 580 int minfreq = int(lrint((minbin * m_sliceableModel->getSampleRate()) /
Chris@908 581 m_windowSize));
Chris@908 582 int maxfreq = int(lrint((std::max(maxbin, minbin+1)
Chris@908 583 * m_sliceableModel->getSampleRate()) /
Chris@908 584 m_windowSize));
Chris@199 585
Chris@199 586 if (maxbin != minbin) {
Chris@199 587 binstr = tr("%1 - %2").arg(minbin+1).arg(maxbin+1);
Chris@199 588 } else {
Chris@199 589 binstr = QString("%1").arg(minbin+1);
Chris@199 590 }
Chris@199 591 if (minfreq != maxfreq) {
Chris@199 592 hzstr = tr("%1 - %2 Hz").arg(minfreq).arg(maxfreq);
Chris@199 593 } else {
Chris@199 594 hzstr = tr("%1 Hz").arg(minfreq);
Chris@199 595 }
Chris@199 596
Chris@199 597 QString valuestr;
Chris@199 598 if (maxvalue != minvalue) {
Chris@199 599 valuestr = tr("%1 - %2").arg(minvalue).arg(maxvalue);
Chris@199 600 } else {
Chris@199 601 valuestr = QString("%1").arg(minvalue);
Chris@199 602 }
Chris@199 603
Chris@199 604 QString dbstr;
Chris@908 605 double mindb = AudioLevel::multiplier_to_dB(minvalue);
Chris@908 606 double maxdb = AudioLevel::multiplier_to_dB(maxvalue);
Chris@199 607 QString mindbstr;
Chris@199 608 QString maxdbstr;
Chris@199 609 if (mindb == AudioLevel::DB_FLOOR) {
Chris@199 610 mindbstr = tr("-Inf");
Chris@199 611 } else {
Chris@908 612 mindbstr = QString("%1").arg(lrint(mindb));
Chris@199 613 }
Chris@199 614 if (maxdb == AudioLevel::DB_FLOOR) {
Chris@199 615 maxdbstr = tr("-Inf");
Chris@199 616 } else {
Chris@908 617 maxdbstr = QString("%1").arg(lrint(maxdb));
Chris@199 618 }
Chris@908 619 if (lrint(mindb) != lrint(maxdb)) {
Chris@199 620 dbstr = tr("%1 - %2").arg(mindbstr).arg(maxdbstr);
Chris@199 621 } else {
Chris@199 622 dbstr = tr("%1").arg(mindbstr);
Chris@199 623 }
Chris@199 624
Chris@199 625 QString description;
Chris@199 626
Chris@248 627 if (range > int(m_sliceableModel->getResolution())) {
Chris@199 628 description = tr("%1\nBin:\t%2 (%3)\n%4 value:\t%5\ndB:\t%6")
Chris@199 629 .arg(genericDesc)
Chris@199 630 .arg(binstr)
Chris@199 631 .arg(hzstr)
Chris@199 632 .arg(m_samplingMode == NearestSample ? tr("First") :
Chris@199 633 m_samplingMode == SampleMean ? tr("Mean") : tr("Peak"))
Chris@199 634 .arg(valuestr)
Chris@199 635 .arg(dbstr);
Chris@199 636 } else {
Chris@199 637 description = tr("%1\nBin:\t%2 (%3)\nValue:\t%4\ndB:\t%5")
Chris@199 638 .arg(genericDesc)
Chris@199 639 .arg(binstr)
Chris@199 640 .arg(hzstr)
Chris@199 641 .arg(valuestr)
Chris@199 642 .arg(dbstr);
Chris@199 643 }
Chris@199 644
Chris@199 645 return description;
Chris@199 646 }
Chris@199 647
Chris@254 648 void
Chris@916 649 SpectrumLayer::paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const
Chris@275 650 {
Chris@275 651 if (!m_originModel || !m_originModel->isOK() ||
Chris@349 652 !m_originModel->isReady()) {
Chris@587 653 SVDEBUG << "SpectrumLayer::paint: no origin model, or origin model not OK or not ready" << endl;
Chris@349 654 return;
Chris@349 655 }
Chris@275 656
Chris@275 657 if (m_newFFTNeeded) {
Chris@587 658 SVDEBUG << "SpectrumLayer::paint: new FFT needed, calling setupFFT" << endl;
Chris@275 659 const_cast<SpectrumLayer *>(this)->setupFFT(); //ugh
Chris@275 660 }
Chris@277 661
Chris@277 662 FFTModel *fft = dynamic_cast<FFTModel *>
Chris@277 663 (const_cast<DenseThreeDimensionalModel *>(m_sliceableModel));
Chris@277 664
Chris@908 665 double thresh = (pow(10, -6) / m_gain) * (m_windowSize / 2.0); // -60dB adj
Chris@277 666
Chris@607 667 int xorigin = getVerticalScaleWidth(v, false, paint) + 1;
Chris@918 668 int w = v->getPaintWidth() - xorigin - 1;
Chris@277 669
Chris@278 670 int pkh = 0;
Chris@287 671 //!!! if (m_binScale == LogBins) {
Chris@287 672 pkh = 10;
Chris@287 673 //!!! }
Chris@278 674
Chris@345 675 paint.save();
Chris@345 676
Chris@284 677 if (fft && m_showPeaks) {
Chris@277 678
Chris@277 679 // draw peak lines
Chris@277 680
Chris@587 681 // SVDEBUG << "Showing peaks..." << endl;
Chris@456 682
Chris@908 683 int col = int(v->getCentreFrame() / fft->getResolution());
Chris@277 684
Chris@277 685 paint.save();
Chris@277 686 paint.setRenderHint(QPainter::Antialiasing, false);
Chris@277 687 paint.setPen(QColor(160, 160, 160)); //!!!
Chris@277 688
Chris@290 689 int peakminbin = 0;
Chris@290 690 int peakmaxbin = fft->getHeight() - 1;
Chris@908 691 double peakmaxfreq = Pitch::getFrequencyForPitch(128);
Chris@908 692 peakmaxbin = int(((peakmaxfreq * fft->getHeight() * 2) / fft->getSampleRate()));
Chris@290 693
Chris@280 694 FFTModel::PeakSet peaks = fft->getPeakFrequencies
Chris@290 695 (FFTModel::MajorPitchAdaptivePeaks, col, peakminbin, peakmaxbin);
Chris@280 696
Chris@280 697 ColourMapper mapper(ColourMapper::BlackOnWhite, 0, 1);
Chris@277 698
Chris@277 699 BiasCurve curve;
Chris@277 700 getBiasCurve(curve);
Chris@908 701 int cs = int(curve.size());
Chris@280 702
Chris@908 703 std::vector<double> values;
Chris@277 704
Chris@805 705 for (int bin = 0; bin < fft->getHeight(); ++bin) {
Chris@908 706 double value = m_sliceableModel->getValueAt(col, bin);
Chris@280 707 if (bin < cs) value *= curve[bin];
Chris@280 708 values.push_back(value);
Chris@280 709 }
Chris@280 710
Chris@280 711 for (FFTModel::PeakSet::iterator i = peaks.begin();
Chris@280 712 i != peaks.end(); ++i) {
Chris@280 713
Chris@805 714 int bin = i->first;
Chris@277 715
Chris@682 716 // cerr << "bin = " << bin << ", thresh = " << thresh << ", value = " << fft->getMagnitudeAt(col, bin) << endl;
Chris@280 717
Chris@908 718 if (!fft->isOverThreshold(col, bin, float(thresh))) continue;
Chris@277 719
Chris@908 720 double freq = i->second;
Chris@280 721
Chris@908 722 int x = int(lrint(getXForFrequency(freq, w)));
Chris@277 723
Chris@908 724 double norm = 0.f;
Chris@805 725 (void)getYForValue(values[bin], v, norm); // don't need return value, need norm
Chris@277 726
Chris@277 727 paint.setPen(mapper.map(norm));
Chris@918 728 paint.drawLine(xorigin + x, 0, xorigin + x, v->getPaintHeight() - pkh - 1);
Chris@277 729 }
Chris@277 730
Chris@277 731 paint.restore();
Chris@277 732 }
Chris@275 733
Chris@275 734 SliceLayer::paint(v, paint, rect);
Chris@277 735
Chris@278 736 //!!! All of this stuff relating to depicting frequencies
Chris@278 737 //(keyboard, crosshairs etc) should be applicable to any slice
Chris@278 738 //layer whose model has a vertical scale unit of Hz. However, the
Chris@278 739 //dense 3d model at the moment doesn't record its vertical scale
Chris@278 740 //unit -- we need to fix that and hoist this code as appropriate.
Chris@278 741 //Same really goes for any code in SpectrogramLayer that could be
Chris@278 742 //relevant to Colour3DPlotLayer with unit Hz, but that's a bigger
Chris@278 743 //proposition.
Chris@278 744
Chris@287 745 // if (m_binScale == LogBins) {
Chris@277 746
Chris@287 747 // int pkh = 10;
Chris@918 748 int h = v->getPaintHeight();
Chris@277 749
Chris@277 750 // piano keyboard
Chris@277 751 //!!! should be in a new paintHorizontalScale()?
Chris@278 752 // nice to have a piano keyboard class, of course
Chris@277 753
Chris@278 754 paint.drawLine(xorigin, h - pkh - 1, w + xorigin, h - pkh - 1);
Chris@277 755
Chris@277 756 int px = xorigin, ppx = xorigin;
Chris@278 757 paint.setBrush(paint.pen().color());
Chris@277 758
Chris@277 759 for (int i = 0; i < 128; ++i) {
Chris@277 760
Chris@908 761 double f = Pitch::getFrequencyForPitch(i);
Chris@908 762 int x = int(lrint(getXForFrequency(f, w)));
Chris@278 763
Chris@278 764 x += xorigin;
Chris@277 765
Chris@278 766 if (i == 0) {
Chris@278 767 px = ppx = x;
Chris@278 768 }
Chris@278 769 if (i == 1) {
Chris@278 770 ppx = px - (x - px);
Chris@278 771 }
Chris@278 772
Chris@278 773 if (x < xorigin) {
Chris@278 774 ppx = px;
Chris@278 775 px = x;
Chris@277 776 continue;
Chris@277 777 }
Chris@278 778
Chris@278 779 if (x > w) {
Chris@278 780 break;
Chris@278 781 }
Chris@277 782
Chris@277 783 int n = (i % 12);
Chris@277 784
Chris@277 785 if (n == 1) {
Chris@277 786 // C# -- fill the C from here
Chris@284 787 QColor col = Qt::gray;
Chris@284 788 if (i == 61) { // filling middle C
Chris@284 789 col = Qt::blue;
Chris@284 790 col = col.light(150);
Chris@284 791 }
Chris@277 792 if (x - ppx > 2) {
Chris@278 793 paint.fillRect((px + ppx) / 2 + 1,
Chris@277 794 h - pkh,
Chris@278 795 x - (px + ppx) / 2 - 1,
Chris@277 796 pkh,
Chris@284 797 col);
Chris@277 798 }
Chris@277 799 }
Chris@277 800
Chris@277 801 if (n == 1 || n == 3 || n == 6 || n == 8 || n == 10) {
Chris@277 802 // black notes
Chris@277 803 paint.drawLine(x, h - pkh, x, h);
Chris@908 804 int rw = int(lrint(double(x - px) / 4) * 2);
Chris@277 805 if (rw < 2) rw = 2;
Chris@278 806 paint.drawRect(x - rw/2, h - pkh, rw, pkh/2);
Chris@277 807 } else if (n == 0 || n == 5) {
Chris@277 808 // C, F
Chris@277 809 if (px < w) {
Chris@277 810 paint.drawLine((x + px) / 2, h - pkh, (x + px) / 2, h);
Chris@277 811 }
Chris@277 812 }
Chris@277 813
Chris@277 814 ppx = px;
Chris@277 815 px = x;
Chris@277 816 }
Chris@287 817 // }
Chris@345 818
Chris@345 819 paint.restore();
Chris@275 820 }
Chris@275 821
Chris@275 822 void
Chris@254 823 SpectrumLayer::getBiasCurve(BiasCurve &curve) const
Chris@254 824 {
Chris@254 825 curve = m_biasCurve;
Chris@254 826 }
Chris@199 827
Chris@316 828 void
Chris@316 829 SpectrumLayer::toXml(QTextStream &stream,
Chris@316 830 QString indent, QString extraAttributes) const
Chris@220 831 {
Chris@316 832 QString s = QString("windowSize=\"%1\" "
Chris@456 833 "windowHopLevel=\"%2\" "
Chris@456 834 "showPeaks=\"%3\" ")
Chris@220 835 .arg(m_windowSize)
Chris@456 836 .arg(m_windowHopLevel)
Chris@456 837 .arg(m_showPeaks ? "true" : "false");
Chris@220 838
Chris@316 839 SliceLayer::toXml(stream, indent, extraAttributes + " " + s);
Chris@220 840 }
Chris@220 841
Chris@220 842 void
Chris@220 843 SpectrumLayer::setProperties(const QXmlAttributes &attributes)
Chris@220 844 {
Chris@220 845 SliceLayer::setProperties(attributes);
Chris@220 846
Chris@220 847 bool ok = false;
Chris@220 848
Chris@805 849 int windowSize = attributes.value("windowSize").toUInt(&ok);
Chris@220 850 if (ok) setWindowSize(windowSize);
Chris@220 851
Chris@805 852 int windowHopLevel = attributes.value("windowHopLevel").toUInt(&ok);
Chris@220 853 if (ok) setWindowHopLevel(windowHopLevel);
Chris@456 854
Chris@456 855 bool showPeaks = (attributes.value("showPeaks").trimmed() == "true");
Chris@456 856 setShowPeaks(showPeaks);
Chris@220 857 }
Chris@220 858
Chris@220 859