annotate layer/SpectrumLayer.cpp @ 1281:fc9d9f1103fa horizontal-scale

Provide linear horizontal scale in spectrum as well as log; fix bin positioning and colour scale property box updating; ensure proper background colour and visibility of peak lines
author Chris Cannam
date Thu, 03 May 2018 15:15:15 +0100
parents 34394e8c2942
children d79e21855aef
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@254 421 ColourMapper mapper(m_colourMap, 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@1281 621 ColourMapper(ColourMapper::BlackOnWhite, 0, 1) :
Chris@1281 622 ColourMapper(ColourMapper::WhiteOnBlack, 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