changeset 40:3be4438b186d

* More fixes, tidying etc in spectrogram layer. Added a piano keyboard for the log frequency scale mode.
author Chris Cannam
date Fri, 24 Feb 2006 15:46:47 +0000
parents 5ce844ec854a
children f2c416cbdaa9
files layer/SpectrogramLayer.cpp layer/SpectrogramLayer.h
diffstat 2 files changed, 273 insertions(+), 78 deletions(-) [+]
line wrap: on
line diff
--- a/layer/SpectrogramLayer.cpp	Thu Feb 23 18:06:31 2006 +0000
+++ b/layer/SpectrogramLayer.cpp	Fri Feb 24 15:46:47 2006 +0000
@@ -67,7 +67,7 @@
 	setWindowSize(4096);
 	setWindowOverlap(90);
 	setWindowType(BlackmanWindow);
-	setMaxFrequency(1500);
+	setMaxFrequency(2000);
 	setMinFrequency(40);
 	setFrequencyScale(LogFrequencyScale);
 	setColourScale(dBColourScale);
@@ -318,7 +318,7 @@
 	case 2: return tr("Black on White");
 	case 3: return tr("Red on Blue");
 	case 4: return tr("Yellow on Black");
-	case 5: return tr("Red on Black");
+	case 5: return tr("Fruit Salad");
 	}
     }
     if (name == tr("Colour Scale")) {
@@ -422,7 +422,7 @@
 	case 2: setColourScheme(BlackOnWhite); break;
 	case 3: setColourScheme(RedOnBlue); break;
 	case 4: setColourScheme(YellowOnBlack); break;
-	case 5: setColourScheme(RedOnBlack); break;
+	case 5: setColourScheme(Rainbow); break;
 	}
     } else if (name == tr("Window Type")) {
 	setWindowType(WindowType(value));
@@ -584,7 +584,7 @@
 void
 SpectrogramLayer::setGain(float gain)
 {
-    if (m_gain == gain) return; //!!! inadequate for floats!
+    if (m_gain == gain) return;
 
     m_mutex.lock();
     m_pixmapCacheInvalid = true;
@@ -607,7 +607,7 @@
 void
 SpectrogramLayer::setThreshold(float threshold)
 {
-    if (m_threshold == threshold) return; //!!! inadequate for floats!
+    if (m_threshold == threshold) return;
 
     m_mutex.lock();
     m_pixmapCacheInvalid = true;
@@ -957,8 +957,10 @@
 			    pixel / 4);
 	    break;
 
-	case RedOnBlack:
-	    colour = QColor::fromHsv(10, pixel, pixel);
+	case Rainbow:
+	    hue = 250 - pixel;
+	    if (hue < 0) hue += 256;
+	    colour = QColor::fromHsv(pixel, 255, 255);
 	    break;
 	}
 
@@ -1119,30 +1121,30 @@
 {
     int value;
 
-    if (m_colourScale == PhaseColourScale) {
-
-	value = int((input * 127 / M_PI) + 128);
-
-    } else {
-
-	switch (m_colourScale) {
-	    
-	default:
-	case LinearColourScale:
-	    value = int(input * 50 * 255) + 1;
-	    break;
-	    
-	case MeterColourScale:
-	    value = AudioLevel::multiplier_to_preview(input * 50, 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) + 1;
-	}
+    switch (m_colourScale) {
+	
+    default:
+    case LinearColourScale:
+	value = int
+	    (input * (m_normalizeColumns ? 1.0 : 50.0) * 255.0) + 1;
+	break;
+	
+    case MeterColourScale:
+	value = AudioLevel::multiplier_to_preview
+	    (input * (m_normalizeColumns ? 1.0 : 50.0), 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;
+	break;
+	
+    case PhaseColourScale:
+	value = int((input * 127.0 / M_PI) + 128);
+	break;
     }
     
     if (value > UCHAR_MAX) value = UCHAR_MAX;
@@ -1150,6 +1152,39 @@
     return value;
 }
 
+float
+SpectrogramLayer::getInputForDisplayValue(unsigned char uc) const
+{
+    int value = uc;
+    float input;
+
+    switch (m_colourScale) {
+	
+    default:
+    case LinearColourScale:
+	input = float(value - 1) / 255.0 / (m_normalizeColumns ? 1 : 50);
+	break;
+    
+    case MeterColourScale:
+	input = AudioLevel::preview_to_multiplier(value - 1, 255)
+	    / (m_normalizeColumns ? 1.0 : 50.0);
+	break;
+
+    case dBColourScale:
+	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;
+    }
+
+    return input;
+}
+
 
 SpectrogramLayer::Cache::Cache() :
     m_width(0),
@@ -1220,7 +1255,7 @@
 	    m_magnitude[y][x] = 0;
 	    m_phase[y][x] = 0;
 	}
-	m_factor[x] = 1.0f;
+	m_factor[x] = 1.0;
     }
 }	    
 
@@ -1428,6 +1463,36 @@
     }
 }
 
+float
+SpectrogramLayer::getEffectiveMinFrequency() const
+{
+    int sr = m_model->getSampleRate();
+    float minf = float(sr) / m_windowSize;
+
+    if (m_minFrequency > 0.0) {
+	size_t minbin = size_t((double(m_minFrequency) * m_windowSize) / sr + 0.01);
+	if (minbin < 1) minbin = 1;
+	minf = minbin * sr / m_windowSize;
+    }
+
+    return minf;
+}
+
+float
+SpectrogramLayer::getEffectiveMaxFrequency() const
+{
+    int sr = m_model->getSampleRate();
+    float maxf = float(sr) / 2;
+
+    if (m_maxFrequency > 0.0) {
+	size_t maxbin = size_t((double(m_maxFrequency) * m_windowSize) / sr + 0.1);
+	if (maxbin > m_windowSize / 2) maxbin = m_windowSize / 2;
+	maxf = maxbin * sr / m_windowSize;
+    }
+
+    return maxf;
+}
+
 bool
 SpectrogramLayer::getYBinRange(int y, float &q0, float &q1) const
 {
@@ -1435,11 +1500,8 @@
     if (y < 0 || y >= h) return false;
 
     int sr = m_model->getSampleRate();
-    float minf = float(sr) / m_windowSize;
-    float maxf = float(sr) / 2;
-
-    if (m_minFrequency > 0.0) minf = m_minFrequency;
-    if (m_maxFrequency > 0.0) maxf = m_maxFrequency;
+    float minf = getEffectiveMinFrequency();
+    float maxf = getEffectiveMaxFrequency();
 
     bool logarithmic = (m_frequencyScale == LogFrequencyScale);
 
@@ -1448,9 +1510,10 @@
 
     // Now map these on to actual bins
 
-    int b0 = (q0 * m_windowSize) / sr;
-    int b1 = (q1 * m_windowSize) / sr;
+    int b0 = int((q0 * m_windowSize) / sr);
+    int b1 = int((q1 * m_windowSize) / sr);
     
+    //!!! this is supposed to return fractions-of-bins, as it were, hence the floats
     q0 = b0;
     q1 = b1;
     
@@ -1565,8 +1628,8 @@
 
 	    float freq = binfreq;
 	    bool steady = false;
-
-	    if (s < m_cache->getWidth() - 1) {
+	    
+	    if (s < int(m_cache->getWidth()) - 1) {
 
 		freq = calculateFrequency(q, 
 					  windowSize,
@@ -1585,7 +1648,7 @@
     }
 
     if (!haveAdj) {
-	adjFreqMin = adjFreqMax = 0.0f;
+	adjFreqMin = adjFreqMax = 0.0;
     }
 
     return haveAdj;
@@ -1814,9 +1877,10 @@
 	if (bins > m_windowSize / 2) bins = m_windowSize / 2;
     }
 	
-    size_t minbin = 0;
+    size_t minbin = 1;
     if (m_minFrequency > 0) {
 	minbin = int((double(m_minFrequency) * m_windowSize) / sr + 0.1);
+	if (minbin < 1) minbin = 1;
 	if (minbin >= bins) minbin = bins - 1;
     }
 
@@ -1824,6 +1888,8 @@
     float maxFreq = (float(bins) * sr) / m_windowSize;
 
     size_t increment = getWindowIncrement();
+    
+    bool logarithmic = (m_frequencyScale == LogFrequencyScale);
 
     m_mutex.unlock();
 
@@ -1836,8 +1902,8 @@
 	}
 
 	for (int y = 0; y < h; ++y) {
-	    ymag[y] = 0.0f;
-	    ydiv[y] = 0.0f;
+	    ymag[y] = 0.0;
+	    ydiv[y] = 0.0;
 	}
 
 	float s0 = 0, s1 = 0;
@@ -1856,17 +1922,32 @@
 
 	for (size_t q = minbin; q < bins; ++q) {
 
+	    float f0 = (float(q) * sr) / m_windowSize;
+	    float f1 = (float(q + 1) * sr) / m_windowSize;
+
+	    float y0 = 0, y1 = 0;
+
+	    if (m_binDisplay != PeakFrequencies ||
+		s1i >= int(m_cache->getWidth())) {
+		y0 = m_view->getYForFrequency(f1, minFreq, maxFreq, logarithmic);
+		y1 = m_view->getYForFrequency(f0, minFreq, maxFreq, logarithmic);
+	    }
+
 	    for (int s = s0i; s <= s1i; ++s) {
 
+		if (m_binDisplay == PeakBins ||
+		    m_binDisplay == PeakFrequencies) {
+		    if (!m_cache->isLocalPeak(s, q)) continue;
+		}
+		
+		if (!m_cache->isOverThreshold(s, q, m_threshold)) continue;
+
 		float sprop = 1.0;
 		if (s == s0i) sprop *= (s + 1) - s0;
 		if (s == s1i) sprop *= s1 - s;
-
-		float f0 = (float(q) * sr) / m_windowSize;
-		float f1 = (float(q + 1) * sr) / m_windowSize;
  
 		if (m_binDisplay == PeakFrequencies &&
-		    s < m_cache->getWidth() - 1) {
+		    s < int(m_cache->getWidth()) - 1) {
 
 		    bool steady = false;
 		    f0 = f1 = calculateFrequency(q,
@@ -1876,15 +1957,10 @@
 						 m_cache->getPhaseAt(s, q),
 						 m_cache->getPhaseAt(s+1, q),
 						 steady);
+
+		    y0 = y1 = m_view->getYForFrequency
+			(f0, minFreq, maxFreq, logarithmic);
 		}
-	    
-		float y0 = m_view->getYForFrequency
-		    (f1, minFreq, maxFreq, 
-		     m_frequencyScale == LogFrequencyScale);
-	    
-		float y1 = m_view->getYForFrequency
-		    (f0, minFreq, maxFreq, 
-		     m_frequencyScale == LogFrequencyScale);
 		
 		int y0i = int(y0 + 0.001);
 		int y1i = int(y1);
@@ -1897,13 +1973,6 @@
 		    if (y == y0i) yprop *= (y + 1) - y0;
 		    if (y == y1i) yprop *= y1 - y;
 
-		    if (m_binDisplay == PeakBins ||
-			m_binDisplay == PeakFrequencies) {
-			if (!m_cache->isLocalPeak(s, q)) continue;
-		    }
-
-		    if (!m_cache->isOverThreshold(s, q, m_threshold)) continue;
-
 		    float value;
 
 		    if (m_colourScale == PhaseColourScale) {
@@ -1922,17 +1991,18 @@
 
 	for (int y = 0; y < h; ++y) {
 
-	    unsigned char pixel = 0;
-
 	    if (ydiv[y] > 0.0) {
+
+		unsigned char pixel = 0;
+
 		float avg = ymag[y] / ydiv[y];
 		pixel = getDisplayValue(avg);
+
+		assert(x <= scaled.width());
+		QColor c = m_cache->getColour(pixel);
+		scaled.setPixel(x, y,
+				qRgb(c.red(), c.green(), c.blue()));
 	    }
-
-	    assert(x <= scaled.width());
-	    QColor c = m_cache->getColour(pixel);
-	    scaled.setPixel(x, y,
-			    qRgb(c.red(), c.green(), c.blue()));
 	}
 
 	m_mutex.unlock();
@@ -2090,10 +2160,37 @@
 }
 
 int
+SpectrogramLayer::getColourScaleWidth(QPainter &paint) const
+{
+    int cw;
+
+    switch (m_colourScale) {
+    default:
+    case LinearColourScale:
+	cw = paint.fontMetrics().width(QString("0.00"));
+	break;
+
+    case MeterColourScale:
+    case dBColourScale:
+	cw = std::max(paint.fontMetrics().width(tr("-Inf")),
+		      paint.fontMetrics().width(tr("-90")));
+	break;
+
+    case PhaseColourScale:
+	cw = paint.fontMetrics().width(QString("-") + QChar(0x3c0));
+	break;
+    }
+
+    return cw;
+}
+
+int
 SpectrogramLayer::getVerticalScaleWidth(QPainter &paint) const
 {
     if (!m_model || !m_model->isOK()) return 0;
 
+    int cw = getColourScaleWidth(paint);
+
     int tw = paint.fontMetrics().width(QString("%1")
 				     .arg(m_maxFrequency > 0 ?
 					  m_maxFrequency - 1 :
@@ -2101,8 +2198,10 @@
 
     int fw = paint.fontMetrics().width(QString("43Hz"));
     if (tw < fw) tw = fw;
+
+    int tickw = (m_frequencyScale == LogFrequencyScale ? 10 : 4);
     
-    return tw + 13;
+    return cw + tickw + tw + 13;
 }
 
 void
@@ -2114,6 +2213,9 @@
 
     int h = rect.height(), w = rect.width();
 
+    int tickw = (m_frequencyScale == LogFrequencyScale ? 10 : 4);
+    int pkw = (m_frequencyScale == LogFrequencyScale ? 10 : 0);
+
     size_t bins = m_windowSize / 2;
     int sr = m_model->getSampleRate();
 
@@ -2122,10 +2224,64 @@
 	if (bins > m_windowSize / 2) bins = m_windowSize / 2;
     }
 
+    int cw = getColourScaleWidth(paint);
+
     int py = -1;
     int textHeight = paint.fontMetrics().height();
     int toff = -textHeight + paint.fontMetrics().ascent() + 2;
 
+    if (m_cache && !m_cacheInvalid && h > textHeight * 2 + 10) { //!!! lock?
+
+	int ch = h - textHeight * 2 - 8;
+	paint.drawRect(4, textHeight + 4, cw - 1, ch + 1);
+
+	QString top, bottom;
+
+	switch (m_colourScale) {
+	default:
+	case LinearColourScale:
+	    top = (m_normalizeColumns ? "1.0" : "0.02");
+	    bottom = (m_normalizeColumns ? "0.0" : "0.00");
+	    break;
+
+	case MeterColourScale:
+	    top = (m_normalizeColumns ? QString("0") :
+		   QString("%1").arg(int(AudioLevel::multiplier_to_dB(0.02))));
+	    bottom = QString("%1").
+		arg(int(AudioLevel::multiplier_to_dB
+			(AudioLevel::preview_to_multiplier(0, 255))));
+	    break;
+
+	case dBColourScale:
+	    top = "0";
+	    bottom = "-80";
+	    break;
+
+	case PhaseColourScale:
+	    top = QChar(0x3c0);
+	    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);
+
+	paint.save();
+	paint.setBrush(Qt::NoBrush);
+	for (int i = 0; i < ch; ++i) {
+	    int v = (i * 255) / ch + 1;
+	    paint.setPen(m_cache->getColour(v));
+	    paint.drawLine(5, 4 + textHeight + ch - i,
+			   cw + 2, 4 + textHeight + ch - i);
+	}
+	paint.restore();
+    }
+
+    paint.drawLine(cw + 7, 0, cw + 7, h);
+
     int bin = -1;
 
     for (int y = 0; y < m_view->height(); ++y) {
@@ -2142,24 +2298,57 @@
 	    continue;
 	}
 
-	int freq = (sr * (bin + 1)) / m_windowSize;
+	int freq = (sr * bin) / m_windowSize;
 
 	if (py >= 0 && (vy - py) < textHeight - 1) {
-	    paint.drawLine(w - 4, h - vy, w, h - vy);
+	    if (m_frequencyScale == LinearFrequencyScale) {
+		paint.drawLine(w - tickw, h - vy, w, h - vy);
+	    }
 	    continue;
 	}
 
 	QString text = QString("%1").arg(freq);
-	if (bin == 0) text = QString("%1Hz").arg(freq);
-	paint.drawLine(0, h - vy, w, h - vy);
+	if (bin == 1) text = QString("%1Hz").arg(freq); // bin 0 is DC
+	paint.drawLine(cw + 7, h - vy, w - pkw - 1, h - vy);
 
 	if (h - vy - textHeight >= -2) {
-	    int tx = w - 10 - paint.fontMetrics().width(text);
+	    int tx = w - 3 - paint.fontMetrics().width(text) - std::max(tickw, pkw);
 	    paint.drawText(tx, h - vy + toff, text);
 	}
 
 	py = vy;
     }
+
+    if (m_frequencyScale == LogFrequencyScale) {
+
+	paint.drawLine(w - pkw - 1, 0, w - pkw - 1, h);
+
+	int sr = m_model->getSampleRate();//!!! lock?
+	float minf = getEffectiveMinFrequency();
+	float maxf = getEffectiveMaxFrequency();
+
+	int py = h;
+	paint.setBrush(paint.pen().color());
+
+	for (int i = 0; i < 128; ++i) {
+
+	    float f = Pitch::getFrequencyForPitch(i);
+	    int y = lrintf(m_view->getYForFrequency(f, minf, maxf, true));
+	    int n = (i % 12);
+	    if (n == 1 || n == 3 || n == 6 || n == 8 || n == 10) {
+		// black notes
+		paint.drawLine(w - pkw, y, w, y);
+		paint.drawRect(w - pkw, y - (py-y)/4, pkw/2, 2*((py-y)/4));
+	    } else if (n == 0 || n == 5) {
+		// C, A
+		if (py < h) {
+		    paint.drawLine(w - pkw, (y + py) / 2, w, (y + py) / 2);
+		}
+	    }
+
+	    py = y;
+	}
+    }
 }
 
 QString
--- a/layer/SpectrogramLayer.h	Thu Feb 23 18:06:31 2006 +0000
+++ b/layer/SpectrogramLayer.h	Fri Feb 24 15:46:47 2006 +0000
@@ -152,7 +152,7 @@
     bool getNormalizeColumns() const;
 
     enum ColourScheme { DefaultColours, WhiteOnBlack, BlackOnWhite,
-			RedOnBlue, YellowOnBlack, RedOnBlack };
+			RedOnBlue, YellowOnBlack, Rainbow };
 
     void setColourScheme(ColourScheme scheme);
     ColourScheme getColourScheme() const;
@@ -353,6 +353,12 @@
 				    bool &steadyState);
 
     unsigned char getDisplayValue(float input) const;
+    float getInputForDisplayValue(unsigned char uc) const;
+
+    int getColourScaleWidth(QPainter &) const;
+
+    float getEffectiveMinFrequency() const;
+    float getEffectiveMaxFrequency() const;
 
     bool getYBinRange(int y, float &freqBinMin, float &freqBinMax) const;