annotate layer/SpectrumLayer.cpp @ 561:aced8ec09bc8

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