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