Mercurial > hg > svgui
comparison layer/SpectrumLayer.cpp @ 280:3c402c6052f6
* Pull peak-picker out of SpectrumLayer and into FFTModel; use combined
  peak-picker and frequency estimator for SpectrogramLayer (makes the
  peak frequency spectrogram a bit quicker)
* Add more information to spectrum and spectrogram crosshairs
| author | Chris Cannam | 
|---|---|
| date | Wed, 04 Jul 2007 15:29:16 +0000 | 
| parents | a078aa2932cc | 
| children | 4edaff85875d | 
   comparison
  equal
  deleted
  inserted
  replaced
| 279:47fe0352861e | 280:3c402c6052f6 | 
|---|---|
| 1 | |
| 2 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ | 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ | 
| 3 | 2 | 
| 4 /* | 3 /* | 
| 5 Sonic Visualiser | 4 Sonic Visualiser | 
| 6 An audio file viewer and annotation editor. | 5 An audio file viewer and annotation editor. | 
| 292 | 291 | 
| 293 float | 292 float | 
| 294 SpectrumLayer::getFrequencyForX(float x, float w) const | 293 SpectrumLayer::getFrequencyForX(float x, float w) const | 
| 295 { | 294 { | 
| 296 float freq = 0; | 295 float freq = 0; | 
| 296 if (!m_sliceableModel) return 0; | |
| 297 | 297 | 
| 298 int sampleRate = m_sliceableModel->getSampleRate(); | 298 int sampleRate = m_sliceableModel->getSampleRate(); | 
| 299 | 299 | 
| 300 float maxfreq = float(sampleRate) / 2; | 300 float maxfreq = float(sampleRate) / 2; | 
| 301 | 301 | 
| 319 | 319 | 
| 320 float | 320 float | 
| 321 SpectrumLayer::getXForFrequency(float freq, float w) const | 321 SpectrumLayer::getXForFrequency(float freq, float w) const | 
| 322 { | 322 { | 
| 323 float x = 0; | 323 float x = 0; | 
| 324 if (!m_sliceableModel) return x; | |
| 324 | 325 | 
| 325 int sampleRate = m_sliceableModel->getSampleRate(); | 326 int sampleRate = m_sliceableModel->getSampleRate(); | 
| 326 | 327 | 
| 327 float maxfreq = float(sampleRate) / 2; | 328 float maxfreq = float(sampleRate) / 2; | 
| 328 | 329 | 
| 399 extents.push_back(vertical); | 400 extents.push_back(vertical); | 
| 400 | 401 | 
| 401 QRect horizontal(0, cursorPos.y(), v->width(), 12); | 402 QRect horizontal(0, cursorPos.y(), v->width(), 12); | 
| 402 extents.push_back(horizontal); | 403 extents.push_back(horizontal); | 
| 403 | 404 | 
| 404 int hoffset = 1; | 405 int hoffset = 2; | 
| 405 if (m_binScale == LogBins) hoffset = 12; | 406 if (m_binScale == LogBins) hoffset = 13; | 
| 406 | 407 | 
| 407 QRect label(cursorPos.x(), | 408 int sw = getVerticalScaleWidth(v, paint); | 
| 408 v->height() - paint.fontMetrics().height() - hoffset, | 409 | 
| 409 paint.fontMetrics().width("123456 Hz") + 2, | 410 QRect value(sw, cursorPos.y() - paint.fontMetrics().ascent() - 2, | 
| 411 paint.fontMetrics().width("0.0000001 V") + 2, | |
| 410 paint.fontMetrics().height()); | 412 paint.fontMetrics().height()); | 
| 411 extents.push_back(label); | 413 extents.push_back(value); | 
| 414 | |
| 415 QRect log(sw, cursorPos.y() + 2, | |
| 416 paint.fontMetrics().width("-80.000 dBV") + 2, | |
| 417 paint.fontMetrics().height()); | |
| 418 extents.push_back(log); | |
| 419 | |
| 420 QRect freq(cursorPos.x(), | |
| 421 v->height() - paint.fontMetrics().height() - hoffset, | |
| 422 paint.fontMetrics().width("123456 Hz") + 2, | |
| 423 paint.fontMetrics().height()); | |
| 424 extents.push_back(freq); | |
| 412 | 425 | 
| 413 int w(paint.fontMetrics().width("C#10+50c") + 2); | 426 int w(paint.fontMetrics().width("C#10+50c") + 2); | 
| 414 QRect pitch(cursorPos.x() - w, | 427 QRect pitch(cursorPos.x() - w, | 
| 415 v->height() - paint.fontMetrics().height() - hoffset, | 428 v->height() - paint.fontMetrics().height() - hoffset, | 
| 416 w, | 429 w, | 
| 422 | 435 | 
| 423 void | 436 void | 
| 424 SpectrumLayer::paintCrosshairs(View *v, QPainter &paint, | 437 SpectrumLayer::paintCrosshairs(View *v, QPainter &paint, | 
| 425 QPoint cursorPos) const | 438 QPoint cursorPos) const | 
| 426 { | 439 { | 
| 440 if (!m_sliceableModel) return; | |
| 441 | |
| 427 paint.save(); | 442 paint.save(); | 
| 428 | 443 | 
| 429 ColourMapper mapper(m_colourMap, 0, 1); | 444 ColourMapper mapper(m_colourMap, 0, 1); | 
| 430 paint.setPen(mapper.getContrastingColour()); | 445 paint.setPen(mapper.getContrastingColour()); | 
| 431 | 446 | 
| 435 paint.drawLine(xorigin, cursorPos.y(), v->width(), cursorPos.y()); | 450 paint.drawLine(xorigin, cursorPos.y(), v->width(), cursorPos.y()); | 
| 436 paint.drawLine(cursorPos.x(), cursorPos.y(), cursorPos.x(), v->height()); | 451 paint.drawLine(cursorPos.x(), cursorPos.y(), cursorPos.x(), v->height()); | 
| 437 | 452 | 
| 438 float fundamental = getFrequencyForX(cursorPos.x() - xorigin, w); | 453 float fundamental = getFrequencyForX(cursorPos.x() - xorigin, w); | 
| 439 | 454 | 
| 440 int hoffset = 1; | 455 int hoffset = 2; | 
| 441 if (m_binScale == LogBins) hoffset = 12; | 456 if (m_binScale == LogBins) hoffset = 13; | 
| 442 | 457 | 
| 443 v->drawVisibleText(paint, | 458 v->drawVisibleText(paint, | 
| 444 cursorPos.x() + 2, | 459 cursorPos.x() + 2, | 
| 445 v->height() - 2 - hoffset, | 460 v->height() - 2 - hoffset, | 
| 446 QString("%1 Hz").arg(fundamental), | 461 QString("%1 Hz").arg(fundamental), | 
| 453 v->height() - 2 - hoffset, | 468 v->height() - 2 - hoffset, | 
| 454 pitchLabel, | 469 pitchLabel, | 
| 455 View::OutlinedText); | 470 View::OutlinedText); | 
| 456 } | 471 } | 
| 457 | 472 | 
| 473 float value = getValueForY(cursorPos.y(), v); | |
| 474 float thresh = -80.f; | |
| 475 float db = thresh; | |
| 476 if (value > 0.f) db = 10.f * log10f(value); | |
| 477 if (db < thresh) db = thresh; | |
| 478 | |
| 479 v->drawVisibleText(paint, | |
| 480 xorigin + 2, | |
| 481 cursorPos.y() - 2, | |
| 482 QString("%1 V").arg(value), | |
| 483 View::OutlinedText); | |
| 484 | |
| 485 v->drawVisibleText(paint, | |
| 486 xorigin + 2, | |
| 487 cursorPos.y() + 2 + paint.fontMetrics().ascent(), | |
| 488 QString("%1 dBV").arg(db), | |
| 489 View::OutlinedText); | |
| 490 | |
| 458 int harmonic = 2; | 491 int harmonic = 2; | 
| 459 | 492 | 
| 460 while (harmonic < 100) { | 493 while (harmonic < 100) { | 
| 461 | 494 | 
| 462 float hx = lrintf(getXForFrequency(fundamental * harmonic, w)); | 495 float hx = lrintf(getXForFrequency(fundamental * harmonic, w)); | 
| 586 } | 619 } | 
| 587 | 620 | 
| 588 FFTModel *fft = dynamic_cast<FFTModel *> | 621 FFTModel *fft = dynamic_cast<FFTModel *> | 
| 589 (const_cast<DenseThreeDimensionalModel *>(m_sliceableModel)); | 622 (const_cast<DenseThreeDimensionalModel *>(m_sliceableModel)); | 
| 590 | 623 | 
| 591 float thresh = powf(10, -8) / m_gain; // -80dB | 624 float thresh = (powf(10, -6) / m_gain) * (m_windowSize / 2.f); // -60dB adj | 
| 592 | 625 | 
| 593 int xorigin = getVerticalScaleWidth(v, paint) + 1; | 626 int xorigin = getVerticalScaleWidth(v, paint) + 1; | 
| 594 int w = v->width() - xorigin - 1; | 627 int w = v->width() - xorigin - 1; | 
| 595 | 628 | 
| 596 int pkh = 0; | 629 int pkh = 0; | 
| 605 | 638 | 
| 606 paint.save(); | 639 paint.save(); | 
| 607 paint.setRenderHint(QPainter::Antialiasing, false); | 640 paint.setRenderHint(QPainter::Antialiasing, false); | 
| 608 paint.setPen(QColor(160, 160, 160)); //!!! | 641 paint.setPen(QColor(160, 160, 160)); //!!! | 
| 609 | 642 | 
| 610 ColourMapper mapper(m_colourMap, 0, 1); | 643 FFTModel::PeakSet peaks = fft->getPeakFrequencies | 
| 644 (FFTModel::MajorPitchAdaptivePeaks, col); | |
| 645 | |
| 646 ColourMapper mapper(ColourMapper::BlackOnWhite, 0, 1); | |
| 611 | 647 | 
| 612 BiasCurve curve; | 648 BiasCurve curve; | 
| 613 getBiasCurve(curve); | 649 getBiasCurve(curve); | 
| 614 size_t cs = curve.size(); | 650 size_t cs = curve.size(); | 
| 651 | |
| 652 std::vector<float> values; | |
| 615 | 653 | 
| 616 for (size_t bin = 0; bin < fft->getHeight(); ++bin) { | 654 for (size_t bin = 0; bin < fft->getHeight(); ++bin) { | 
| 655 float value = m_sliceableModel->getValueAt(col, bin); | |
| 656 if (bin < cs) value *= curve[bin]; | |
| 657 values.push_back(value); | |
| 658 } | |
| 659 | |
| 660 for (FFTModel::PeakSet::iterator i = peaks.begin(); | |
| 661 i != peaks.end(); ++i) { | |
| 662 | |
| 663 size_t bin = i->first; | |
| 617 | 664 | 
| 618 if (!fft->isLocalPeak(col, bin)) continue; | 665 // std::cerr << "bin = " << bin << ", thresh = " << thresh << ", value = " << fft->getMagnitudeAt(col, bin) << std::endl; | 
| 666 | |
| 619 if (!fft->isOverThreshold(col, bin, thresh)) continue; | 667 if (!fft->isOverThreshold(col, bin, thresh)) continue; | 
| 620 | 668 | 
| 621 float freq = 0; | 669 float freq = i->second; | 
| 622 bool haveFreq = fft->estimateStableFrequency(col, bin, freq); | 670 | 
| 623 if (!haveFreq) continue; | |
| 624 | |
| 625 int x = lrintf(getXForFrequency(freq, w)); | 671 int x = lrintf(getXForFrequency(freq, w)); | 
| 626 | 672 | 
| 627 float value = m_sliceableModel->getValueAt(col, bin); | |
| 628 if (bin < cs) value *= curve[bin]; | |
| 629 float norm = 0.f; | 673 float norm = 0.f; | 
| 630 float y = getYForValue(value, v, norm); // don't need y, need norm | 674 float y = getYForValue(values[bin], v, norm); // don't need y, need norm | 
| 631 | 675 | 
| 632 paint.setPen(mapper.map(norm)); | 676 paint.setPen(mapper.map(norm)); | 
| 633 paint.drawLine(xorigin + x, 0, xorigin + x, v->height() - pkh - 1); | 677 paint.drawLine(xorigin + x, 0, xorigin + x, v->height() - pkh - 1); | 
| 634 } | 678 } | 
| 635 | 679 | 
