annotate layer/SpectrumLayer.cpp @ 317:e251c3599ea8

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