Mercurial > hg > svgui
comparison layer/SpectrumLayer.cpp @ 1238:4d0ca1ab4cd0
Some work to make spectrum layers (and slice layers generally) zoomable in the frequency axis. Also fixes a number of view id mixups in SliceLayer which broke offset calculations for the x axis scale.
author | Chris Cannam |
---|---|
date | Tue, 07 Feb 2017 14:55:19 +0000 |
parents | ff97318e993c |
children | 9e1559b08f0d |
comparison
equal
deleted
inserted
replaced
1237:2cc9e0e5df51 | 1238:4d0ca1ab4cd0 |
---|---|
295 setWindowType(Preferences::getInstance()->getWindowType()); | 295 setWindowType(Preferences::getInstance()->getWindowType()); |
296 return; | 296 return; |
297 } | 297 } |
298 } | 298 } |
299 | 299 |
300 bool | |
301 SpectrumLayer::getValueExtents(double &, double &, bool &, QString &) const | |
302 { | |
303 return false; | |
304 } | |
305 | |
306 double | 300 double |
307 SpectrumLayer::getXForBin(int bin, int totalBins, double w) const | 301 SpectrumLayer::getFrequencyForX(const LayerGeometryProvider *v, double x) const |
308 { | 302 { |
309 if (!m_sliceableModel) return SliceLayer::getXForBin(bin, totalBins, w); | 303 if (!m_sliceableModel) return 0; |
310 | 304 double bin = getBinForX(v, x); |
311 sv_samplerate_t sampleRate = m_sliceableModel->getSampleRate(); | 305 return (m_sliceableModel->getSampleRate() * bin) / |
312 double binfreq = (sampleRate * bin) / (totalBins * 2); | 306 (m_sliceableModel->getHeight() * 2); |
313 | |
314 return getXForFrequency(binfreq, w); | |
315 } | |
316 | |
317 int | |
318 SpectrumLayer::getBinForX(double x, int totalBins, double w) const | |
319 { | |
320 if (!m_sliceableModel) return SliceLayer::getBinForX(x, totalBins, w); | |
321 | |
322 sv_samplerate_t sampleRate = m_sliceableModel->getSampleRate(); | |
323 double binfreq = getFrequencyForX(x, w); | |
324 | |
325 return int((binfreq * totalBins * 2) / sampleRate); | |
326 } | 307 } |
327 | 308 |
328 double | 309 double |
329 SpectrumLayer::getFrequencyForX(double x, double w) const | 310 SpectrumLayer::getXForFrequency(const LayerGeometryProvider *v, double freq) const |
330 { | 311 { |
331 double freq = 0; | |
332 if (!m_sliceableModel) return 0; | 312 if (!m_sliceableModel) return 0; |
333 | 313 double bin = (freq * m_sliceableModel->getHeight() * 2) / |
334 sv_samplerate_t sampleRate = m_sliceableModel->getSampleRate(); | 314 m_sliceableModel->getSampleRate(); |
335 | 315 return getXForBin(v, bin); |
336 double maxfreq = double(sampleRate) / 2; | |
337 | |
338 switch (m_binScale) { | |
339 | |
340 case LinearBins: | |
341 freq = ((x * maxfreq) / w); | |
342 break; | |
343 | |
344 case LogBins: | |
345 freq = pow(10.0, (x * log10(maxfreq)) / w); | |
346 break; | |
347 | |
348 case InvertedLogBins: | |
349 freq = maxfreq - pow(10.0, ((w - x) * log10(maxfreq)) / w); | |
350 break; | |
351 } | |
352 | |
353 return freq; | |
354 } | |
355 | |
356 double | |
357 SpectrumLayer::getXForFrequency(double freq, double w) const | |
358 { | |
359 double x = 0; | |
360 if (!m_sliceableModel) return x; | |
361 | |
362 sv_samplerate_t sampleRate = m_sliceableModel->getSampleRate(); | |
363 | |
364 double maxfreq = double(sampleRate) / 2; | |
365 | |
366 switch (m_binScale) { | |
367 | |
368 case LinearBins: | |
369 x = (freq * w) / maxfreq; | |
370 break; | |
371 | |
372 case LogBins: | |
373 x = (log10(freq) * w) / log10(maxfreq); | |
374 break; | |
375 | |
376 case InvertedLogBins: | |
377 if (maxfreq == freq) x = w; | |
378 else x = w - (log10(maxfreq - freq) * w) / log10(maxfreq); | |
379 break; | |
380 } | |
381 | |
382 return x; | |
383 } | 316 } |
384 | 317 |
385 bool | 318 bool |
386 SpectrumLayer::getXScaleValue(const LayerGeometryProvider *v, int x, | 319 SpectrumLayer::getXScaleValue(const LayerGeometryProvider *v, int x, |
387 double &value, QString &unit) const | 320 double &value, QString &unit) const |
388 { | 321 { |
389 if (m_xorigins.find(v) == m_xorigins.end()) return false; | 322 value = getFrequencyForX(v, x); |
390 int xorigin = m_xorigins.find(v)->second; | |
391 value = getFrequencyForX(x - xorigin, v->getPaintWidth() - xorigin - 1); | |
392 unit = "Hz"; | 323 unit = "Hz"; |
393 return true; | 324 return true; |
394 } | 325 } |
395 | 326 |
396 bool | 327 bool |
397 SpectrumLayer::getYScaleValue(const LayerGeometryProvider *v, int y, | 328 SpectrumLayer::getYScaleValue(const LayerGeometryProvider *v, int y, |
398 double &value, QString &unit) const | 329 double &value, QString &unit) const |
399 { | 330 { |
400 value = getValueForY(y, v); | 331 value = getValueForY(v, y); |
401 | 332 |
402 if (m_energyScale == dBScale || m_energyScale == MeterScale) { | 333 if (m_energyScale == dBScale || m_energyScale == MeterScale) { |
403 | 334 |
404 if (value > 0.0) { | 335 if (value > 0.0) { |
405 value = 10.0 * log10(value); | 336 value = 10.0 * log10(value); |
481 } | 412 } |
482 | 413 |
483 ColourMapper mapper(m_colourMap, 0, 1); | 414 ColourMapper mapper(m_colourMap, 0, 1); |
484 paint.setPen(mapper.getContrastingColour()); | 415 paint.setPen(mapper.getContrastingColour()); |
485 | 416 |
486 int xorigin = m_xorigins[v]; | 417 int xorigin = m_xorigins[v->getId()]; |
487 int w = v->getPaintWidth() - xorigin - 1; | 418 int w = v->getPaintWidth() - xorigin - 1; |
488 | 419 |
489 paint.drawLine(xorigin, cursorPos.y(), v->getPaintWidth(), cursorPos.y()); | 420 paint.drawLine(xorigin, cursorPos.y(), v->getPaintWidth(), cursorPos.y()); |
490 paint.drawLine(cursorPos.x(), cursorPos.y(), cursorPos.x(), v->getPaintHeight()); | 421 paint.drawLine(cursorPos.x(), cursorPos.y(), cursorPos.x(), v->getPaintHeight()); |
491 | 422 |
492 double fundamental = getFrequencyForX(cursorPos.x() - xorigin, w); | 423 double fundamental = getFrequencyForX(v, cursorPos.x()); |
493 | 424 |
494 int hoffset = 2; | 425 int hoffset = 2; |
495 if (m_binScale == LogBins) hoffset = 13; | 426 if (m_binScale == LogBins) hoffset = 13; |
496 | 427 |
497 PaintAssistant::drawVisibleText(v, paint, | 428 PaintAssistant::drawVisibleText(v, paint, |
498 cursorPos.x() + 2, | 429 cursorPos.x() + 2, |
499 v->getPaintHeight() - 2 - hoffset, | 430 v->getPaintHeight() - 2 - hoffset, |
500 QString("%1 Hz").arg(fundamental), | 431 QString("%1 Hz").arg(fundamental), |
501 PaintAssistant::OutlinedText); | 432 PaintAssistant::OutlinedText); |
502 | 433 |
503 if (Pitch::isFrequencyInMidiRange(fundamental)) { | 434 if (Pitch::isFrequencyInMidiRange(fundamental)) { |
504 QString pitchLabel = Pitch::getPitchLabelForFrequency(fundamental); | 435 QString pitchLabel = Pitch::getPitchLabelForFrequency(fundamental); |
505 PaintAssistant::drawVisibleText(v, paint, | 436 PaintAssistant::drawVisibleText(v, paint, |
506 cursorPos.x() - paint.fontMetrics().width(pitchLabel) - 2, | 437 cursorPos.x() - |
507 v->getPaintHeight() - 2 - hoffset, | 438 paint.fontMetrics().width(pitchLabel) - 2, |
508 pitchLabel, | 439 v->getPaintHeight() - 2 - hoffset, |
509 PaintAssistant::OutlinedText); | 440 pitchLabel, |
510 } | 441 PaintAssistant::OutlinedText); |
511 | 442 } |
512 double value = getValueForY(cursorPos.y(), v); | 443 |
444 double value = getValueForY(v, cursorPos.y()); | |
513 double thresh = m_threshold; | 445 double thresh = m_threshold; |
514 double db = thresh; | 446 double db = thresh; |
515 if (value > 0.0) db = 10.0 * log10(value); | 447 if (value > 0.0) db = 10.0 * log10(value); |
516 if (db < thresh) db = thresh; | 448 if (db < thresh) db = thresh; |
517 | 449 |
529 | 461 |
530 int harmonic = 2; | 462 int harmonic = 2; |
531 | 463 |
532 while (harmonic < 100) { | 464 while (harmonic < 100) { |
533 | 465 |
534 int hx = int(lrint(getXForFrequency(fundamental * harmonic, w))); | 466 int hx = int(lrint(getXForFrequency(v, fundamental * harmonic))); |
535 hx += xorigin; | 467 hx += xorigin; |
536 | 468 |
537 if (hx < xorigin || hx > v->getPaintWidth()) break; | 469 if (hx < xorigin || hx > v->getPaintWidth()) break; |
538 | 470 |
539 int len = 7; | 471 int len = 7; |
566 QString genericDesc = SliceLayer::getFeatureDescriptionAux | 498 QString genericDesc = SliceLayer::getFeatureDescriptionAux |
567 (v, p, false, minbin, maxbin, range); | 499 (v, p, false, minbin, maxbin, range); |
568 | 500 |
569 if (genericDesc == "") return ""; | 501 if (genericDesc == "") return ""; |
570 | 502 |
571 double minvalue = 0.f; | 503 int i0 = minbin - m_minbin; |
572 if (minbin < int(m_values.size())) minvalue = m_values[minbin]; | 504 int i1 = maxbin - m_minbin; |
573 | |
574 double maxvalue = minvalue; | |
575 if (maxbin < int(m_values.size())) maxvalue = m_values[maxbin]; | |
576 | 505 |
506 float minvalue = 0.0; | |
507 if (in_range_for(m_values, i0)) minvalue = m_values[i0]; | |
508 | |
509 float maxvalue = minvalue; | |
510 if (in_range_for(m_values, i1)) maxvalue = m_values[i1]; | |
511 | |
577 if (minvalue > maxvalue) std::swap(minvalue, maxvalue); | 512 if (minvalue > maxvalue) std::swap(minvalue, maxvalue); |
578 | 513 |
579 QString binstr; | 514 QString binstr; |
580 QString hzstr; | 515 QString hzstr; |
581 int minfreq = int(lrint((minbin * m_sliceableModel->getSampleRate()) / | 516 int minfreq = int(lrint((minbin * m_sliceableModel->getSampleRate()) / |
716 | 651 |
717 if (!fft->isOverThreshold(col, bin, float(thresh))) continue; | 652 if (!fft->isOverThreshold(col, bin, float(thresh))) continue; |
718 | 653 |
719 double freq = i->second; | 654 double freq = i->second; |
720 | 655 |
721 int x = int(lrint(getXForFrequency(freq, w))); | 656 int x = int(lrint(getXForFrequency(v, freq))); |
722 | 657 |
723 double norm = 0.f; | 658 double norm = 0.f; |
724 (void)getYForValue(values[bin], v, norm); // don't need return value, need norm | 659 (void)getYForValue(v, values[bin], norm); // don't need return value, need norm |
725 | 660 |
726 paint.setPen(mapper.map(norm)); | 661 paint.setPen(mapper.map(norm)); |
727 paint.drawLine(xorigin + x, 0, xorigin + x, v->getPaintHeight() - pkh - 1); | 662 paint.drawLine(xorigin + x, 0, xorigin + x, v->getPaintHeight() - pkh - 1); |
728 } | 663 } |
729 | 664 |
731 } | 666 } |
732 | 667 |
733 SliceLayer::paint(v, paint, rect); | 668 SliceLayer::paint(v, paint, rect); |
734 | 669 |
735 //!!! All of this stuff relating to depicting frequencies | 670 //!!! All of this stuff relating to depicting frequencies |
736 //(keyboard, crosshairs etc) should be applicable to any slice | 671 // (keyboard, crosshairs etc) should be applicable to any slice |
737 //layer whose model has a vertical scale unit of Hz. However, the | 672 // layer whose model has a vertical scale unit of Hz. However, |
738 //dense 3d model at the moment doesn't record its vertical scale | 673 // the dense 3d model at the moment doesn't record its vertical |
739 //unit -- we need to fix that and hoist this code as appropriate. | 674 // scale unit -- we need to fix that and hoist this code as |
740 //Same really goes for any code in SpectrogramLayer that could be | 675 // appropriate. Same really goes for any code in SpectrogramLayer |
741 //relevant to Colour3DPlotLayer with unit Hz, but that's a bigger | 676 // that could be relevant to Colour3DPlotLayer with unit Hz, but |
742 //proposition. | 677 // that's a bigger proposition. |
743 | 678 |
744 // if (m_binScale == LogBins) { | 679 int h = v->getPaintHeight(); |
745 | 680 |
746 // int pkh = 10; | 681 PianoScale().paintPianoHorizontal |
747 int h = v->getPaintHeight(); | 682 (v, this, paint, QRect(xorigin, h - pkh - 1, w + xorigin, pkh)); |
748 | |
749 // piano keyboard | |
750 | |
751 //!!! todo: move to PianoScale::paintPianoHorizontal | |
752 | |
753 paint.drawLine(xorigin, h - pkh - 1, w + xorigin, h - pkh - 1); | |
754 | |
755 int px = xorigin, ppx = xorigin; | |
756 paint.setBrush(paint.pen().color()); | |
757 | |
758 for (int i = 0; i < 128; ++i) { | |
759 | |
760 double f = Pitch::getFrequencyForPitch(i); | |
761 int x = int(lrint(getXForFrequency(f, w))); | |
762 | |
763 x += xorigin; | |
764 | |
765 if (i == 0) { | |
766 px = ppx = x; | |
767 } | |
768 if (i == 1) { | |
769 ppx = px - (x - px); | |
770 } | |
771 | |
772 if (x < xorigin) { | |
773 ppx = px; | |
774 px = x; | |
775 continue; | |
776 } | |
777 | |
778 if (x > w) { | |
779 break; | |
780 } | |
781 | |
782 int n = (i % 12); | |
783 | |
784 if (n == 1) { | |
785 // C# -- fill the C from here | |
786 QColor col = Qt::gray; | |
787 if (i == 61) { // filling middle C | |
788 col = Qt::blue; | |
789 col = col.light(150); | |
790 } | |
791 if (x - ppx > 2) { | |
792 paint.fillRect((px + ppx) / 2 + 1, | |
793 h - pkh, | |
794 x - (px + ppx) / 2 - 1, | |
795 pkh, | |
796 col); | |
797 } | |
798 } | |
799 | |
800 if (n == 1 || n == 3 || n == 6 || n == 8 || n == 10) { | |
801 // black notes | |
802 paint.drawLine(x, h - pkh, x, h); | |
803 int rw = int(lrint(double(x - px) / 4) * 2); | |
804 if (rw < 2) rw = 2; | |
805 paint.drawRect(x - rw/2, h - pkh, rw, pkh/2); | |
806 } else if (n == 0 || n == 5) { | |
807 // C, F | |
808 if (px < w) { | |
809 paint.drawLine((x + px) / 2, h - pkh, (x + px) / 2, h); | |
810 } | |
811 } | |
812 | |
813 ppx = px; | |
814 px = x; | |
815 } | |
816 // } | |
817 | 683 |
818 paint.restore(); | 684 paint.restore(); |
819 } | 685 } |
820 | 686 |
821 void | 687 void |