diff layer/SpectrumLayer.cpp @ 1324:13d9b422f7fe zoom

Merge from default branch
author Chris Cannam
date Mon, 17 Sep 2018 13:51:31 +0100
parents fc9d9f1103fa
children d79e21855aef
line wrap: on
line diff
--- a/layer/SpectrumLayer.cpp	Mon Dec 12 15:18:52 2016 +0000
+++ b/layer/SpectrumLayer.cpp	Mon Sep 17 13:51:31 2018 +0100
@@ -25,6 +25,8 @@
 
 #include "ColourMapper.h"
 #include "PaintAssistant.h"
+#include "PianoScale.h"
+#include "HorizontalFrequencyScale.h"
 
 #include <QPainter>
 #include <QTextStream>
@@ -166,7 +168,7 @@
 SpectrumLayer::getPropertyGroupName(const PropertyName &name) const
 {
     if (name == "Window Size" ||
-	name == "Window Increment") return tr("Window");
+        name == "Window Increment") return tr("Window");
     if (name == "Show Peak Frequencies") return tr("Bins");
     return SliceLayer::getPropertyGroupName(name);
 }
@@ -184,20 +186,20 @@
 
     if (name == "Window Size") {
 
-	*min = 0;
-	*max = 15;
+        *min = 0;
+        *max = 15;
         *deflt = 5;
-	
-	val = 0;
-	int ws = m_windowSize;
-	while (ws > 32) { ws >>= 1; val ++; }
+        
+        val = 0;
+        int ws = m_windowSize;
+        while (ws > 32) { ws >>= 1; val ++; }
 
     } else if (name == "Window Increment") {
-	
-	*min = 0;
-	*max = 5;
+        
+        *min = 0;
+        *max = 5;
         *deflt = 2;
-	
+        
         val = m_windowHopLevel;
     
     } else if (name == "Show Peak Frequencies") {
@@ -214,21 +216,21 @@
 
 QString
 SpectrumLayer::getPropertyValueLabel(const PropertyName &name,
-				    int value) const
+                                    int value) const
 {
     if (name == "Window Size") {
-	return QString("%1").arg(32 << value);
+        return QString("%1").arg(32 << value);
     }
     if (name == "Window Increment") {
-	switch (value) {
-	default:
-	case 0: return tr("None");
-	case 1: return tr("25 %");
-	case 2: return tr("50 %");
-	case 3: return tr("75 %");
-	case 4: return tr("87.5 %");
-	case 5: return tr("93.75 %");
-	}
+        switch (value) {
+        default:
+        case 0: return tr("None");
+        case 1: return tr("25 %");
+        case 2: return tr("50 %");
+        case 3: return tr("75 %");
+        case 4: return tr("87.5 %");
+        case 5: return tr("93.75 %");
+        }
     }
     return SliceLayer::getPropertyValueLabel(name, value);
 }
@@ -243,7 +245,7 @@
 SpectrumLayer::setProperty(const PropertyName &name, int value)
 {
     if (name == "Window Size") {
-	setWindowSize(32 << value);
+        setWindowSize(32 << value);
     } else if (name == "Window Increment") {
         setWindowHopLevel(value);
     } else if (name == "Show Peak Frequencies") {
@@ -297,98 +299,34 @@
     }
 }
 
-bool
-SpectrumLayer::getValueExtents(double &, double &, bool &, QString &) const
+double
+SpectrumLayer::getFrequencyForX(const LayerGeometryProvider *v, double x) const
 {
-    return false;
+    if (!m_sliceableModel) return 0;
+    double bin = getBinForX(v, x);
+    // we assume the frequency of a bin corresponds to the centre of
+    // its visual range
+    bin -= 0.5;
+    return (m_sliceableModel->getSampleRate() * bin) /
+        (m_sliceableModel->getHeight() * 2);
 }
 
 double
-SpectrumLayer::getXForBin(int bin, int totalBins, double w) const
+SpectrumLayer::getXForFrequency(const LayerGeometryProvider *v, double freq) const
 {
-    if (!m_sliceableModel) return SliceLayer::getXForBin(bin, totalBins, w);
-
-    sv_samplerate_t sampleRate = m_sliceableModel->getSampleRate();
-    double binfreq = (sampleRate * bin) / (totalBins * 2);
-    
-    return getXForFrequency(binfreq, w);
-}
-
-int
-SpectrumLayer::getBinForX(double x, int totalBins, double w) const
-{
-    if (!m_sliceableModel) return SliceLayer::getBinForX(x, totalBins, w);
-
-    sv_samplerate_t sampleRate = m_sliceableModel->getSampleRate();
-    double binfreq = getFrequencyForX(x, w);
-
-    return int((binfreq * totalBins * 2) / sampleRate);
-}
-
-double
-SpectrumLayer::getFrequencyForX(double x, double w) const
-{
-    double freq = 0;
     if (!m_sliceableModel) return 0;
-
-    sv_samplerate_t sampleRate = m_sliceableModel->getSampleRate();
-
-    double maxfreq = double(sampleRate) / 2;
-
-    switch (m_binScale) {
-
-    case LinearBins:
-        freq = ((x * maxfreq) / w);
-        break;
-        
-    case LogBins:
-        freq = pow(10.0, (x * log10(maxfreq)) / w);
-        break;
-
-    case InvertedLogBins:
-        freq = maxfreq - pow(10.0, ((w - x) * log10(maxfreq)) / w);
-        break;
-    }
-
-    return freq;
-}
-
-double
-SpectrumLayer::getXForFrequency(double freq, double w) const
-{
-    double x = 0;
-    if (!m_sliceableModel) return x;
-
-    sv_samplerate_t sampleRate = m_sliceableModel->getSampleRate();
-
-    double maxfreq = double(sampleRate) / 2;
-
-    switch (m_binScale) {
-
-    case LinearBins:
-        x = (freq * w) / maxfreq;
-        break;
-        
-    case LogBins:
-        x = (log10(freq) * w) / log10(maxfreq);
-        break;
-
-    case InvertedLogBins:
-        if (maxfreq == freq) x = w;
-        else x = w - (log10(maxfreq - freq) * w) / log10(maxfreq);
-        break;
-    }
-
-    return x;
+    double bin = (freq * m_sliceableModel->getHeight() * 2) /
+        m_sliceableModel->getSampleRate();
+    // we want the centre of the bin range
+    bin += 0.5;
+    return getXForBin(v, bin);
 }
 
 bool
 SpectrumLayer::getXScaleValue(const LayerGeometryProvider *v, int x, 
                               double &value, QString &unit) const
 {
-    if (m_xorigins.find(v) == m_xorigins.end()) return false;
-    int xorigin = m_xorigins.find(v)->second;
-    value = getFrequencyForX(x - xorigin, v->getPaintWidth() - xorigin - 1);
+    value = getFrequencyForX(v, x);
     unit = "Hz";
     return true;
 }
@@ -397,7 +335,7 @@
 SpectrumLayer::getYScaleValue(const LayerGeometryProvider *v, int y,
                               double &value, QString &unit) const
 {
-    value = getValueForY(y, v);
+    value = getValueForY(v, y);
 
     if (m_energyScale == dBScale || m_energyScale == MeterScale) {
 
@@ -483,33 +421,32 @@
     ColourMapper mapper(m_colourMap, 0, 1);
     paint.setPen(mapper.getContrastingColour());
 
-    int xorigin = m_xorigins[v];
-    int w = v->getPaintWidth() - xorigin - 1;
-    
+    int xorigin = m_xorigins[v->getId()];
     paint.drawLine(xorigin, cursorPos.y(), v->getPaintWidth(), cursorPos.y());
     paint.drawLine(cursorPos.x(), cursorPos.y(), cursorPos.x(), v->getPaintHeight());
     
-    double fundamental = getFrequencyForX(cursorPos.x() - xorigin, w);
+    double fundamental = getFrequencyForX(v, cursorPos.x());
 
     int hoffset = 2;
     if (m_binScale == LogBins) hoffset = 13;
 
     PaintAssistant::drawVisibleText(v, paint,
-                       cursorPos.x() + 2,
-                       v->getPaintHeight() - 2 - hoffset,
-                       QString("%1 Hz").arg(fundamental),
-                       PaintAssistant::OutlinedText);
+                                    cursorPos.x() + 2,
+                                    v->getPaintHeight() - 2 - hoffset,
+                                    QString("%1 Hz").arg(fundamental),
+                                    PaintAssistant::OutlinedText);
 
     if (Pitch::isFrequencyInMidiRange(fundamental)) {
         QString pitchLabel = Pitch::getPitchLabelForFrequency(fundamental);
         PaintAssistant::drawVisibleText(v, paint,
-                           cursorPos.x() - paint.fontMetrics().width(pitchLabel) - 2,
-                           v->getPaintHeight() - 2 - hoffset,
-                           pitchLabel,
-                           PaintAssistant::OutlinedText);
+                                        cursorPos.x() -
+                                        paint.fontMetrics().width(pitchLabel) - 2,
+                                        v->getPaintHeight() - 2 - hoffset,
+                                        pitchLabel,
+                                        PaintAssistant::OutlinedText);
     }
 
-    double value = getValueForY(cursorPos.y(), v);
+    double value = getValueForY(v, cursorPos.y());
     double thresh = m_threshold;
     double db = thresh;
     if (value > 0.0) db = 10.0 * log10(value);
@@ -531,8 +468,7 @@
 
     while (harmonic < 100) {
 
-        int hx = int(lrint(getXForFrequency(fundamental * harmonic, w)));
-        hx += xorigin;
+        int hx = int(lrint(getXForFrequency(v, fundamental * harmonic)));
 
         if (hx < xorigin || hx > v->getPaintWidth()) break;
         
@@ -568,19 +504,22 @@
 
     if (genericDesc == "") return "";
 
-    double minvalue = 0.f;
-    if (minbin < int(m_values.size())) minvalue = m_values[minbin];
+    int i0 = minbin - m_minbin;
+    int i1 = maxbin - m_minbin;
+        
+    float minvalue = 0.0;
+    if (in_range_for(m_values, i0)) minvalue = m_values[i0];
 
-    double maxvalue = minvalue;
-    if (maxbin < int(m_values.size())) maxvalue = m_values[maxbin];
-        
+    float maxvalue = minvalue;
+    if (in_range_for(m_values, i1)) maxvalue = m_values[i1];
+    
     if (minvalue > maxvalue) std::swap(minvalue, maxvalue);
     
     QString binstr;
     QString hzstr;
     int minfreq = int(lrint((minbin * m_sliceableModel->getSampleRate()) /
                             m_windowSize));
-    int maxfreq = int(lrint((std::max(maxbin, minbin+1)
+    int maxfreq = int(lrint((std::max(maxbin, minbin)
                              * m_sliceableModel->getSampleRate()) /
                             m_windowSize));
 
@@ -666,27 +605,22 @@
     double thresh = (pow(10, -6) / m_gain) * (m_windowSize / 2.0); // -60dB adj
 
     int xorigin = getVerticalScaleWidth(v, false, paint) + 1;
-    int w = v->getPaintWidth() - xorigin - 1;
-
-    int pkh = 0;
-//!!!    if (m_binScale == LogBins) {
-        pkh = 10;
-//!!!    }
-
-    paint.save();
+    int scaleHeight = getHorizontalScaleHeight(v, paint);
 
     if (fft && m_showPeaks) {
 
         // draw peak lines
 
-//        SVDEBUG << "Showing peaks..." << endl;
-
         int col = int(v->getCentreFrame() / fft->getResolution());
 
         paint.save();
         paint.setRenderHint(QPainter::Antialiasing, false);
-        paint.setPen(QColor(160, 160, 160)); //!!!
 
+        ColourMapper mapper =
+            hasLightBackground() ?
+            ColourMapper(ColourMapper::BlackOnWhite, 0, 1) :
+            ColourMapper(ColourMapper::WhiteOnBlack, 0, 1);
+        
         int peakminbin = 0;
         int peakmaxbin = fft->getHeight() - 1;
         double peakmaxfreq = Pitch::getFrequencyForPitch(128);
@@ -695,8 +629,6 @@
         FFTModel::PeakSet peaks = fft->getPeakFrequencies
             (FFTModel::MajorPitchAdaptivePeaks, col, peakminbin, peakmaxbin);
 
-        ColourMapper mapper(ColourMapper::BlackOnWhite, 0, 1);
-
         BiasCurve curve;
         getBiasCurve(curve);
         int cs = int(curve.size());
@@ -720,106 +652,88 @@
             
             double freq = i->second;
           
-            int x = int(lrint(getXForFrequency(freq, w)));
+            int x = int(lrint(getXForFrequency(v, freq)));
 
             double norm = 0.f;
-            (void)getYForValue(values[bin], v, norm); // don't need return value, need norm
+            (void)getYForValue(v, values[bin], norm); // don't need return value, need norm
 
             paint.setPen(mapper.map(norm));
-            paint.drawLine(xorigin + x, 0, xorigin + x, v->getPaintHeight() - pkh - 1);
+            paint.drawLine(x, 0, x, v->getPaintHeight() - scaleHeight - 1);
         }
 
         paint.restore();
     }
     
+    paint.save();
+    
     SliceLayer::paint(v, paint, rect);
-
-    //!!! All of this stuff relating to depicting frequencies
-    //(keyboard, crosshairs etc) should be applicable to any slice
-    //layer whose model has a vertical scale unit of Hz.  However, the
-    //dense 3d model at the moment doesn't record its vertical scale
-    //unit -- we need to fix that and hoist this code as appropriate.
-    //Same really goes for any code in SpectrogramLayer that could be
-    //relevant to Colour3DPlotLayer with unit Hz, but that's a bigger
-    //proposition.
-
-//    if (m_binScale == LogBins) {
-
-//        int pkh = 10;
-        int h = v->getPaintHeight();
-
-        // piano keyboard
-        //!!! should be in a new paintHorizontalScale()?
-        // nice to have a piano keyboard class, of course
-
-	paint.drawLine(xorigin, h - pkh - 1, w + xorigin, h - pkh - 1);
-
-	int px = xorigin, ppx = xorigin;
-	paint.setBrush(paint.pen().color());
-
-	for (int i = 0; i < 128; ++i) {
-
-	    double f = Pitch::getFrequencyForPitch(i);
-	    int x = int(lrint(getXForFrequency(f, w)));
-                           
-            x += xorigin;
-
-            if (i == 0) {
-                px = ppx = x;
-            }
-            if (i == 1) {
-                ppx = px - (x - px);
-            }
-
-            if (x < xorigin) {
-                ppx = px;
-                px = x;
-                continue;
-            }
-                
-            if (x > w) {
-                break;
-            }
-
-	    int n = (i % 12);
-
-            if (n == 1) {
-                // C# -- fill the C from here
-                QColor col = Qt::gray;
-                if (i == 61) { // filling middle C
-                    col = Qt::blue;
-                    col = col.light(150);
-                }
-                if (x - ppx > 2) {
-                    paint.fillRect((px + ppx) / 2 + 1,
-                                   h - pkh,
-                                   x - (px + ppx) / 2 - 1,
-                                   pkh,
-                                   col);
-                }
-            }
-
-	    if (n == 1 || n == 3 || n == 6 || n == 8 || n == 10) {
-		// black notes
-		paint.drawLine(x, h - pkh, x, h);
-		int rw = int(lrint(double(x - px) / 4) * 2);
-		if (rw < 2) rw = 2;
-		paint.drawRect(x - rw/2, h - pkh, rw, pkh/2);
-	    } else if (n == 0 || n == 5) {
-		// C, F
-		if (px < w) {
-		    paint.drawLine((x + px) / 2, h - pkh, (x + px) / 2, h);
-		}
-	    }
-
-            ppx = px;
-	    px = x;
-	}
-//    }
+    
+    paintHorizontalScale(v, paint, xorigin);
 
     paint.restore();
 }
 
+int
+SpectrumLayer::getHorizontalScaleHeight(LayerGeometryProvider *v,
+                                        QPainter &paint) const
+{
+    int pkh = int(paint.fontMetrics().height() * 0.7 + 0.5);
+    if (pkh < 10) pkh = 10;
+
+    int scaleh = HorizontalFrequencyScale().getHeight(v, paint);
+
+    return pkh + scaleh;
+}
+
+void
+SpectrumLayer::paintHorizontalScale(LayerGeometryProvider *v,
+                                    QPainter &paint,
+                                    int xorigin) const
+{
+    //!!! All of this stuff relating to depicting frequencies
+    // (keyboard, crosshairs etc) should be applicable to any slice
+    // layer whose model has a vertical scale unit of Hz.  However,
+    // the dense 3d model at the moment doesn't record its vertical
+    // scale unit -- we need to fix that and hoist this code as
+    // appropriate.  Same really goes for any code in SpectrogramLayer
+    // that could be relevant to Colour3DPlotLayer with unit Hz, but
+    // that's a bigger proposition.
+
+    if (!v->getViewManager()->shouldShowHorizontalValueScale()) {
+        return;
+    }
+    
+    int totalScaleHeight = getHorizontalScaleHeight(v, paint); // inc piano
+    int freqScaleHeight = HorizontalFrequencyScale().getHeight(v, paint);
+    int paintHeight = v->getPaintHeight();
+    int paintWidth = v->getPaintWidth();
+
+    PianoScale().paintPianoHorizontal
+        (v, this, paint,
+         QRect(xorigin, paintHeight - totalScaleHeight - 1,
+               paintWidth - 1, totalScaleHeight - freqScaleHeight));
+
+    int scaleLeft = int(getXForBin(v, 1));
+    
+    paint.drawLine(int(getXForBin(v, 0)), paintHeight - freqScaleHeight,
+                   scaleLeft, paintHeight - freqScaleHeight);
+
+    QString hz = tr("Hz");
+    int hzw = paint.fontMetrics().width(hz);
+    if (scaleLeft > hzw + 5) {
+        paint.drawText
+            (scaleLeft - hzw - 5,
+             paintHeight - freqScaleHeight + paint.fontMetrics().ascent() + 5,
+             hz);
+    }
+
+    HorizontalFrequencyScale().paintScale
+        (v, this, paint,
+         QRect(scaleLeft, paintHeight - freqScaleHeight,
+               paintWidth, totalScaleHeight),
+         m_binScale == LogBins);
+}
+
 void
 SpectrumLayer::getBiasCurve(BiasCurve &curve) const
 {