changeset 119:508276c923ba

* Various experiments in spectrogram colour scaling, etc. Nothing final here yet, but some promising developments.
author Chris Cannam
date Fri, 14 Jul 2006 17:12:16 +0000
parents 853a7fc542d0
children 8dfa20f1c70a
files layer/SpectrogramLayer.cpp layer/SpectrogramLayer.h
diffstat 2 files changed, 319 insertions(+), 54 deletions(-) [+]
line wrap: on
line diff
--- a/layer/SpectrogramLayer.cpp	Mon Jul 10 13:54:49 2006 +0000
+++ b/layer/SpectrogramLayer.cpp	Fri Jul 14 17:12:16 2006 +0000
@@ -35,7 +35,7 @@
 #include <cassert>
 #include <cmath>
 
-#define DEBUG_SPECTROGRAM_REPAINT 1
+//#define DEBUG_SPECTROGRAM_REPAINT 1
 
 SpectrogramLayer::SpectrogramLayer(Configuration config) :
     Layer(),
@@ -49,7 +49,7 @@
     m_gain(1.0),
     m_threshold(0.0),
     m_colourRotation(0),
-    m_minFrequency(0),
+    m_minFrequency(10),
     m_maxFrequency(8000),
     m_colourScale(dBColourScale),
     m_colourScheme(DefaultColours),
@@ -348,7 +348,8 @@
 	case 0: return tr("Linear");
 	case 1: return tr("Meter");
 	case 2: return tr("dB");
-	case 3: return tr("Phase");
+	case 3: return tr("Other");
+	case 4: return tr("Phase");
 	}
     }
     if (name == "Window Type") {
@@ -492,7 +493,8 @@
 	case 0: setColourScale(LinearColourScale); break;
 	case 1: setColourScale(MeterColourScale); break;
 	case 2: setColourScale(dBColourScale); break;
-	case 3: setColourScale(PhaseColourScale); break;
+	case 3: setColourScale(OtherColourScale); break;
+	case 4: setColourScale(PhaseColourScale); break;
 	}
     } else if (name == "Frequency Scale") {
 	switch (value) {
@@ -682,6 +684,7 @@
     if (m_minFrequency == mf) return;
 
     invalidatePixmapCaches();
+    invalidateMagnitudes();
     
     m_minFrequency = mf;
 
@@ -700,6 +703,7 @@
     if (m_maxFrequency == mf) return;
 
     invalidatePixmapCaches();
+    invalidateMagnitudes();
     
     m_maxFrequency = mf;
     
@@ -806,6 +810,7 @@
     if (m_normalizeColumns == n) return;
 
     invalidatePixmapCaches();
+    invalidateMagnitudes();
     m_normalizeColumns = n;
 
     emit layerParametersChanged();
@@ -844,6 +849,7 @@
 SpectrogramLayer::cacheInvalid()
 {
     invalidatePixmapCaches();
+    invalidateMagnitudes();
 }
 
 void
@@ -1056,29 +1062,70 @@
 }
 
 unsigned char
-SpectrogramLayer::getDisplayValue(float input) const
+SpectrogramLayer::getDisplayValue(View *v, float input) const
 {
     int value;
 
+    //!!! for the moment we're always normalizing visible area
+    float min = m_viewMags[v].getMin();
+    float max = m_viewMags[v].getMax();
+    float thresh = -80.f;
+
+    if (max == 0.f) max = 1.f;
+    if (max == min) min = max - 0.0001f;
+
     switch (m_colourScale) {
 	
     default:
     case LinearColourScale:
-	value = int
-	    (input * (m_normalizeColumns ? 1.0 : 50.0) * 255.0) + 1;
+//	value = int
+//	    (input * (m_normalizeColumns ? 1.0 : 50.0) * 255.0) + 1;
+        value = int(((input - min) / (max - min)) * 255.f) + 1;
 	break;
 	
     case MeterColourScale:
-	value = AudioLevel::multiplier_to_preview
-	    (input * (m_normalizeColumns ? 1.0 : 50.0), 255) + 1;
+//	value = AudioLevel::multiplier_to_preview
+//	    (input * (m_normalizeColumns ? 1.0 : 50.0), 255) + 1;
+        value = AudioLevel::multiplier_to_preview((input - min) / (max - min), 255) + 1;
 	break;
 	
     case dBColourScale:
-	input = 20.0 * log10(input);
-	input = (input + 80.0) / 80.0;
-	if (input < 0.0) input = 0.0;
-	if (input > 1.0) input = 1.0;
-	value = int(input * 255.0) + 1;
+        //!!! experiment with normalizing the visible area this way.
+        //In any case, we need to have some indication of what the dB
+        //scale is relative to.
+        input = 10.f * log10f(input / max);
+        if (min > 0.f) {
+            thresh = 10.f * log10f(min);
+            if (thresh < -80.f) thresh = -80.f;
+        }
+	input = (input - thresh) / (-thresh);
+	if (input < 0.f) input = 0.f;
+	if (input > 1.f) input = 1.f;
+	value = int(input * 255.f) + 1;
+	break;
+
+    case OtherColourScale:
+        //!!! the "Other" scale is just where our current experiments go
+        //!!! power rather than v
+        input = 10.f * log10f((input * input) / (max * max));
+        if (min > 0.f) {
+            thresh = 10.f * log10f(min * min);
+            if (thresh < -80.f) thresh = -80.f;
+        }
+	input = (input - thresh) / (-thresh);
+	if (input < 0.f) input = 0.f;
+	if (input > 1.f) input = 1.f;
+	value = int(input * 255.f) + 1;
+	break;
+        
+/*!!!
+	input = 10.f * log10f(input * input);
+        input = 1.f / (1.f + expf(- (input + 20.f) / 10.f));
+
+	if (input < 0.f) input = 0.f;
+	if (input > 1.f) input = 1.f;
+	value = int(input * 255.f) + 1;
+*/
 	break;
 	
     case PhaseColourScale:
@@ -1116,6 +1163,13 @@
 	value = int(input);
 	break;
 
+    case OtherColourScale:
+	input = float(value - 1) / 255.0;
+	input = (input * 80.0) - 80.0;
+	input = powf(10.0, input) / 20.0;
+	value = int(input);
+	break;
+
     case PhaseColourScale:
 	input = float(value - 128) * M_PI / 127.0;
 	break;
@@ -1476,6 +1530,49 @@
 }
 
 void
+SpectrogramLayer::invalidateMagnitudes()
+{
+    m_viewMags.clear();
+    for (std::vector<MagnitudeRange>::iterator i = m_columnMags.begin();
+         i != m_columnMags.end(); ++i) {
+        *i = MagnitudeRange();
+    }
+}
+
+bool
+SpectrogramLayer::updateViewMagnitudes(View *v) const
+{
+    MagnitudeRange mag;
+
+    int x0 = 0, x1 = v->width();
+    float s00 = 0, s01 = 0, s10 = 0, s11 = 0;
+    
+    getXBinRange(v, x0, s00, s01);
+    getXBinRange(v, x1, s10, s11);
+
+    int s0 = int(std::min(s00, s10) + 0.0001);
+    int s1 = int(std::max(s01, s11));
+
+    if (m_columnMags.size() <= s1) {
+        m_columnMags.resize(s1 + 1);
+    }
+
+    for (int s = s0; s <= s1; ++s) {
+        if (m_columnMags[s].isSet()) {
+            mag.sample(m_columnMags[s]);
+        }
+    }
+
+    std::cerr << "SpectrogramLayer::updateViewMagnitudes returning from cols "
+              << s0 << " -> " << s1 << " inclusive" << std::endl;
+
+    if (!mag.isSet()) return false;
+    if (mag == m_viewMags[v]) return false;
+    m_viewMags[v] = mag;
+    return true;
+}
+
+void
 SpectrogramLayer::paint(View *v, QPainter &paint, QRect rect) const
 {
     if (m_colourScheme == BlackOnWhite) {
@@ -1661,6 +1758,12 @@
         x1 = v->width();
     }
 
+    if (updateViewMagnitudes(v)) {
+        std::cerr << "SpectrogramLayer: magnitude range changed to [" << m_viewMags[v].getMin() << "->" << m_viewMags[v].getMax() << "]" << std::endl;
+    } else {
+        std::cerr << "No change in magnitude range [" << m_viewMags[v].getMin() << "->" << m_viewMags[v].getMax() << "]" << std::endl;
+    }
+
     int paintBlockWidth = (300000 / zoomLevel);
     if (paintBlockWidth < 20) paintBlockWidth = 20;
 
@@ -1750,6 +1853,9 @@
         yval[q] = v->getYForFrequency(f0, minFreq, maxFreq, logarithmic);
     }
 
+    MagnitudeRange overallMag = m_viewMags[v];
+    bool overallMagChanged = false;
+
     for (int x = 0; x < w; ++x) {
 
 	for (int y = 0; y < h; ++y) {
@@ -1778,6 +1884,7 @@
         for (int s = s0i; s <= s1i; ++s) {
 
             if (!fft->isColumnReady(s)) continue;
+            MagnitudeRange mag;
 
             for (size_t q = minbin; q < bins; ++q) {
 
@@ -1822,9 +1929,13 @@
                 if (m_colourScale == PhaseColourScale) {
                     value = fft->getPhaseAt(s, q);
                 } else if (m_normalizeColumns) {
-                    value = fft->getNormalizedMagnitudeAt(s, q) * m_gain;
+                    value = fft->getNormalizedMagnitudeAt(s, q);
+                    mag.sample(value);
+                    value *= m_gain;
                 } else {
-                    value = fft->getMagnitudeAt(s, q) * m_gain;
+                    value = fft->getMagnitudeAt(s, q);
+                    mag.sample(value);
+                    value *= m_gain;
                 }
 
 		for (int y = y0i; y <= y1i; ++y) {
@@ -1838,6 +1949,17 @@
 		    ydiv[y] += yprop;
 		}
 	    }
+
+            if (mag.isSet()) {
+
+                m_columnMags[s].sample(mag);
+
+                if (overallMag.sample(mag)) {
+                    //!!! scaling would change here
+                    overallMagChanged = true;
+                    std::cerr << "Overall mag changed (again?) at column " << s << ", to [" << overallMag.getMin() << "->" << overallMag.getMax() << "]" << std::endl;
+                }
+            }
 	}
 
 	for (int y = 0; y < h; ++y) {
@@ -1847,7 +1969,7 @@
 		unsigned char pixel = 0;
 
 		float avg = ymag[y] / ydiv[y];
-		pixel = getDisplayValue(avg);
+		pixel = getDisplayValue(v, avg);
 
 		assert(x <= m_drawBuffer.width());
 		QColor c = m_colourMap.getColour(pixel);
@@ -1857,6 +1979,13 @@
 	}
     }
 
+    if (overallMagChanged) {
+        m_viewMags[v] = overallMag;
+        std::cerr << "Overall mag is now [" << m_viewMags[v].getMin() << "->" << m_viewMags[v].getMax() << "] - will be updating" << std::endl;
+    } else {
+        std::cerr << "Overall mag unchanged at [" << m_viewMags[v].getMin() << "->" << m_viewMags[v].getMax() << "]" << std::endl;
+    }
+
     paint.drawImage(x0, y0, m_drawBuffer, 0, 0, w, h);
 
     if (recreateWholePixmapCache) {
@@ -1866,32 +1995,39 @@
     QPainter cachePainter(&cache.pixmap);
     cachePainter.drawImage(x0, y0, m_drawBuffer, 0, 0, w, h);
     cachePainter.end();
+
+    if (!overallMagChanged) {
     
-    cache.startFrame = startFrame;
-    cache.zoomLevel = zoomLevel;
-
-    if (cache.validArea.x() > 0) {
+        cache.startFrame = startFrame;
+        cache.zoomLevel = zoomLevel;
+
+        if (cache.validArea.x() > 0) {
 #ifdef DEBUG_SPECTROGRAM_REPAINT
-        std::cerr << "SpectrogramLayer::paint() updating left" << std::endl;
+            std::cerr << "SpectrogramLayer::paint() updating left" << std::endl;
 #endif
-        v->update(0, 0, cache.validArea.x(), v->height());
-    }
-
-    if (cache.validArea.x() + cache.validArea.width() <
-        cache.pixmap.width()) {
+            v->update(0, 0, cache.validArea.x(), v->height());
+        }
+        
+        if (cache.validArea.x() + cache.validArea.width() <
+            cache.pixmap.width()) {
 #ifdef DEBUG_SPECTROGRAM_REPAINT
-        std::cerr << "SpectrogramLayer::paint() updating right ("
-                  << cache.validArea.x() + cache.validArea.width()
-                  << ", "
-                  << cache.pixmap.width() - (cache.validArea.x() +
-                                             cache.validArea.width())
-                  << ")" << std::endl;
+            std::cerr << "SpectrogramLayer::paint() updating right ("
+                      << cache.validArea.x() + cache.validArea.width()
+                      << ", "
+                      << cache.pixmap.width() - (cache.validArea.x() +
+                                                 cache.validArea.width())
+                      << ")" << std::endl;
 #endif
-        v->update(cache.validArea.x() + cache.validArea.width(),
-                  0,
-                  cache.pixmap.width() - (cache.validArea.x() +
-                                          cache.validArea.width()),
-                  v->height());
+            v->update(cache.validArea.x() + cache.validArea.width(),
+                      0,
+                      cache.pixmap.width() - (cache.validArea.x() +
+                                              cache.validArea.width()),
+                      v->height());
+        }
+    } else {
+        // overallMagChanged
+        cache.validArea = QRect();
+        v->update();
     }
 
 #ifdef DEBUG_SPECTROGRAM_REPAINT
@@ -2140,6 +2276,9 @@
 {
     int cw;
 
+    cw = paint.fontMetrics().width("-80dB");
+
+/*!!!
     switch (m_colourScale) {
     default:
     case LinearColourScale:
@@ -2148,6 +2287,7 @@
 
     case MeterColourScale:
     case dBColourScale:
+    case OtherColourScale:
 	cw = std::max(paint.fontMetrics().width(tr("-Inf")),
 		      paint.fontMetrics().width(tr("-90")));
 	break;
@@ -2156,6 +2296,8 @@
 	cw = paint.fontMetrics().width(QString("-") + QChar(0x3c0));
 	break;
     }
+*/
+
 
     return cw;
 }
@@ -2201,18 +2343,23 @@
     }
 
     int cw = getColourScaleWidth(paint);
+    int cbw = paint.fontMetrics().width("dB");
 
     int py = -1;
     int textHeight = paint.fontMetrics().height();
     int toff = -textHeight + paint.fontMetrics().ascent() + 2;
 
-    if (h > textHeight * 2 + 10) {
-
-	int ch = h - textHeight * 2 - 8;
-	paint.drawRect(4, textHeight + 4, cw - 1, ch + 1);
+    if (h > textHeight * 3 + 10) {
+
+        int topLines = 2;
+        if (m_colourScale == PhaseColourScale) topLines = 1;
+
+	int ch = h - textHeight * (topLines + 1) - 8;
+//	paint.drawRect(4, textHeight + 4, cw - 1, ch + 1);
+	paint.drawRect(4 + cw - cbw, textHeight * topLines + 4, cbw - 1, ch + 1);
 
 	QString top, bottom;
-
+/*!!!
 	switch (m_colourScale) {
 	default:
 	case LinearColourScale:
@@ -2229,6 +2376,7 @@
 	    break;
 
 	case dBColourScale:
+	case OtherColourScale:
 	    top = "0";
 	    bottom = "-80";
 	    break;
@@ -2238,20 +2386,81 @@
 	    bottom = "-" + top;
 	    break;
 	}
-
-	paint.drawText((cw + 6 - paint.fontMetrics().width(top)) / 2,
-		       2 + textHeight + toff, top);
-
-	paint.drawText((cw + 6 - paint.fontMetrics().width(bottom)) / 2,
-		       h + toff - 3, bottom);
+*/
+        float min = m_viewMags[v].getMin();
+        float max = m_viewMags[v].getMax();
+
+        float dBmin = AudioLevel::multiplier_to_dB(min);
+        float dBmax = AudioLevel::multiplier_to_dB(max);
+
+        if (dBmin < -80.f) dBmin = -80.f;
+        bottom = QString("%1").arg(lrintf(dBmin));
+
+        if (dBmax < -80.f) dBmax = -80.f;
+        else top = QString("%1").arg(lrintf(dBmax));
+
+        //!!! & phase etc
+
+        if (m_colourScale != PhaseColourScale) {
+            paint.drawText((cw + 6 - paint.fontMetrics().width("dBFS")) / 2,
+                           2 + textHeight + toff, "dBFS");
+        }
+
+//	paint.drawText((cw + 6 - paint.fontMetrics().width(top)) / 2,
+	paint.drawText(3 + cw - cbw - paint.fontMetrics().width(top),
+		       2 + textHeight * topLines + toff + textHeight/2, top);
+
+	paint.drawText(3 + cw - cbw - paint.fontMetrics().width(bottom),
+		       h + toff - 3 - textHeight/2, bottom);
 
 	paint.save();
 	paint.setBrush(Qt::NoBrush);
+
+        int lasty = 0;
+        int lastdb = 0;
+
 	for (int i = 0; i < ch; ++i) {
-	    int v = (i * 255) / ch + 1;
-	    paint.setPen(m_colourMap.getColour(v));
-	    paint.drawLine(5, 4 + textHeight + ch - i,
-			   cw + 2, 4 + textHeight + ch - i);
+
+            float dBval = dBmin + (((dBmax - dBmin) * i) / (ch - 1));
+            int idb = int(dBval);
+
+            float value = AudioLevel::dB_to_multiplier(dBval);
+            int colour = getDisplayValue(v, value * m_gain);
+/*
+            float value = min + (((max - min) * i) / (ch - 1));
+            if (value < m_threshold) value = 0.f;
+            int colour = getDisplayValue(v, value * m_gain);
+*/
+/*
+	    int colour = (i * 255) / ch + 1;
+*/
+	    paint.setPen(m_colourMap.getColour(colour));
+
+            int y = textHeight * topLines + 4 + ch - i;
+
+            paint.drawLine(5 + cw - cbw, y, cw + 2, y);
+
+//	    paint.drawLine(5, 4 + textHeight + ch - i,
+//			   cw + 2, 4 + textHeight + ch - i);
+
+
+            if (i == 0) {
+                lasty = y;
+                lastdb = idb;
+            } else if (i < ch - paint.fontMetrics().ascent() &&
+                       ((abs(y - lasty) > textHeight && 
+                         idb % 10 == 0) ||
+                        (abs(y - lasty) > paint.fontMetrics().ascent() && 
+                         idb % 5 == 0))) {
+                paint.setPen(Qt::black);
+                QString text = QString("%1").arg(idb);
+                paint.drawText(3 + cw - cbw - paint.fontMetrics().width(text),
+                               y + toff + textHeight/2, text);
+                paint.setPen(Qt::white);
+                paint.drawLine(5 + cw - cbw, y, 8 + cw - cbw, y);
+                lasty = y;
+                lastdb = idb;
+            }
 	}
 	paint.restore();
     }
--- a/layer/SpectrogramLayer.h	Mon Jul 10 13:54:49 2006 +0000
+++ b/layer/SpectrogramLayer.h	Fri Jul 14 17:12:16 2006 +0000
@@ -129,6 +129,7 @@
 	LinearColourScale,
 	MeterColourScale,
 	dBColourScale,
+        OtherColourScale,
 	PhaseColourScale
     };
 
@@ -277,7 +278,7 @@
 				    float currentPhase,
 				    bool &steadyState);
 
-    unsigned char getDisplayValue(float input) const;
+    unsigned char getDisplayValue(View *v, float input) const;
     float getInputForDisplayValue(unsigned char uc) const;
 
     int getColourScaleWidth(QPainter &) const;
@@ -315,7 +316,62 @@
 
     typedef std::pair<FFTFuzzyAdapter *, int> FFTFillPair; // adapter, last fill
     typedef std::map<const View *, FFTFillPair> ViewFFTMap;
+    typedef std::vector<float> FloatVector;
     mutable ViewFFTMap m_fftAdapters;
+
+    class MagnitudeRange {
+    public:
+        MagnitudeRange() : m_min(0), m_max(0) { }
+        bool operator==(const MagnitudeRange &r) {
+            return r.m_min == m_min && r.m_max == m_max;
+        }
+        bool isSet() const { return (m_min != 0 || m_max != 0); }
+        void set(float min, float max) {
+            m_min = convert(min);
+            m_max = convert(max);
+            if (m_max < m_min) m_max = m_min;
+        }
+        bool sample(float f) {
+            unsigned int ui = convert(f);
+            bool changed = false;
+            if (isSet()) {
+                if (ui < m_min) { m_min = ui; changed = true; }
+                if (ui > m_max) { m_max = ui; changed = true; }
+            } else {
+                m_max = m_min = ui;
+                changed = true;
+            }
+            return changed;
+        }            
+        bool sample(const MagnitudeRange &r) {
+            bool changed = false;
+            if (isSet()) {
+                if (r.m_min < m_min) { m_min = r.m_min; changed = true; }
+                if (r.m_max > m_max) { m_max = r.m_max; changed = true; }
+            } else {
+                m_min = r.m_min;
+                m_max = r.m_max;
+                changed = true;
+            }
+            return changed;
+        }            
+        float getMin() const { return float(m_min) / UINT_MAX; }
+        float getMax() const { return float(m_max) / UINT_MAX; }
+    private:
+        unsigned int m_min;
+        unsigned int m_max;
+        unsigned int convert(float f) {
+            if (f < 0.f) f = 0.f;
+            if (f > 1.f) f = 1.f;
+            return (unsigned int)(f * UINT_MAX);
+        }
+    };
+
+    typedef std::map<const View *, MagnitudeRange> ViewMagMap;
+    mutable ViewMagMap m_viewMags;
+    mutable std::vector<MagnitudeRange> m_columnMags;
+    void invalidateMagnitudes();
+    bool updateViewMagnitudes(View *v) const;
 };
 
 #endif