annotate layer/SpectrumLayer.cpp @ 349:369a197737c7

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