diff layer/SpectrogramLayer.cpp @ 253:1b1e6947c124

* FFT: fix invalid write of normalisation factor in compact mode of disc cache * FFT: fix range problem for normalisation factor in compact mode (it was stored as an unsigned scaled from an assumed float range of 0->1, which is not very plausible and not accurate enough even if true -- use a float instead) * Spectrogram: fix vertical zoom behaviour for log frequency spectrograms: make the thing in the middle of the display remain in the middle after zoom * Overview widget: don't update the detailed waveform if still decoding the audio file (too expensive to do all those redraws)
author Chris Cannam
date Fri, 08 Jun 2007 15:19:50 +0000
parents 8d89f8869cfb
children 11021509c4eb
line wrap: on
line diff
--- a/layer/SpectrogramLayer.cpp	Fri Jun 01 13:56:35 2007 +0000
+++ b/layer/SpectrogramLayer.cpp	Fri Jun 08 15:19:50 2007 +0000
@@ -22,6 +22,7 @@
 #include "base/Pitch.h"
 #include "base/Preferences.h"
 #include "base/RangeMapper.h"
+#include "base/LogRange.h"
 #include "ColourMapper.h"
 
 #include <QPainter>
@@ -1399,7 +1400,7 @@
 
 	    if (peaksOnly && !fft->isLocalPeak(s, q)) continue;
 
-	    if (!fft->isOverThreshold(s, q, m_threshold)) continue;
+	    if (!fft->isOverThreshold(s, q, m_threshold * (m_fftSize/2))) continue;
 
 	    float freq = binfreq;
 	    bool steady = false;
@@ -1695,6 +1696,9 @@
 void
 SpectrogramLayer::paint(View *v, QPainter &paint, QRect rect) const
 {
+    // What a lovely, old-fashioned function this is.
+    // It's practically FORTRAN 77 in its clarity and linearity.
+
     Profiler profiler("SpectrogramLayer::paint", true);
 #ifdef DEBUG_SPECTROGRAM_REPAINT
     std::cerr << "SpectrogramLayer::paint(): m_model is " << m_model << ", zoom level is " << v->getZoomLevel() << ", m_updateTimer " << m_updateTimer << std::endl;
@@ -1986,20 +1990,32 @@
     // Set minFreq and maxFreq to the frequency extents of the possibly
     // zero-padded visible bin range, and displayMinFreq and displayMaxFreq
     // to the actual scale frequency extents (presumably not zero padded).
+
+    // If we are zero padding, we want to use the zero-padded
+    // equivalents of the bins that we would be using if not zero
+    // padded, to avoid spaces at the top and bottom of the display.
+
+    // Note fftSize is the actual zero-padded fft size, m_fftSize the
+    // nominal fft size.
     
-    size_t maxbin = fftSize / 2;
+    size_t maxbin = m_fftSize / 2;
     if (m_maxFrequency > 0) {
-	maxbin = int((double(m_maxFrequency) * fftSize) / sr + 0.1);
-	if (maxbin > fftSize / 2) maxbin = fftSize / 2;
+	maxbin = int((double(m_maxFrequency) * m_fftSize) / sr + 0.001);
+	if (maxbin > m_fftSize / 2) maxbin = m_fftSize / 2;
     }
 
     size_t minbin = 1;
     if (m_minFrequency > 0) {
-	minbin = int((double(m_minFrequency) * fftSize) / sr + 0.1);
+	minbin = int((double(m_minFrequency) * m_fftSize) / sr + 0.001);
+//        std::cerr << "m_minFrequency = " << m_minFrequency << " -> minbin = " << minbin << std::endl;
 	if (minbin < 1) minbin = 1;
 	if (minbin >= maxbin) minbin = maxbin - 1;
     }
 
+    int zpl = getZeroPadLevel(v) + 1;
+    minbin = minbin * zpl;
+    maxbin = (maxbin + 1) * zpl - 1;
+
     float minFreq = (float(minbin) * sr) / fftSize;
     float maxFreq = (float(maxbin) * sr) / fftSize;
 
@@ -2011,6 +2027,8 @@
         displayMaxFreq = getEffectiveMaxFrequency();
     }
 
+//    std::cerr << "(giving actual minFreq " << minFreq << " and display minFreq " << displayMinFreq << ")" << std::endl;
+
     float ymag[h];
     float ydiv[h];
     float yval[maxbin + 1]; //!!! cache this?
@@ -2104,7 +2122,7 @@
 		}
 
                 if (m_threshold != 0.f &&
-                    !fft->isOverThreshold(s, q, m_threshold)) {
+                    !fft->isOverThreshold(s, q, m_threshold * (m_fftSize/2))) {
                     continue;
                 }
 
@@ -2413,6 +2431,7 @@
 {
     min = getEffectiveMinFrequency();
     max = getEffectiveMaxFrequency();
+
 //    std::cerr << "SpectrogramLayer::getDisplayExtents: " << min << "->" << max << std::endl;
     return true;
 }    
@@ -2422,7 +2441,7 @@
 {
     if (!m_model) return false;
 
-    std::cerr << "SpectrogramLayer::setDisplayExtents: " << min << "->" << max << std::endl;
+//    std::cerr << "SpectrogramLayer::setDisplayExtents: " << min << "->" << max << std::endl;
 
     if (min < 0) min = 0;
     if (max > m_model->getSampleRate()/2) max = m_model->getSampleRate()/2;
@@ -2956,20 +2975,52 @@
 void
 SpectrogramLayer::setVerticalZoomStep(int step)
 {
-    //!!! does not do the right thing for log scale
-
     if (!m_model) return;
 
-    float dmin, dmax;
-    getDisplayExtents(dmin, dmax);
+    float dmin = m_minFrequency, dmax = m_maxFrequency;
+//    getDisplayExtents(dmin, dmax);
+
+//    std::cerr << "current range " << dmin << " -> " << dmax << ", range " << dmax-dmin << ", mid " << (dmax + dmin)/2 << std::endl;
     
     int sr = m_model->getSampleRate();
     SpectrogramRangeMapper mapper(sr, m_fftSize);
-    float ddist = mapper.getValueForPosition(step);
-
-    float dmid = (dmax + dmin) / 2;
-    float newmin = dmid - ddist / 2;
-    float newmax = dmid + ddist / 2;
+    float newdist = mapper.getValueForPosition(step);
+
+    float newmin, newmax;
+
+    if (m_frequencyScale == LogFrequencyScale) {
+
+        // need to pick newmin and newmax such that
+        //
+        // (log(newmin) + log(newmax)) / 2 == logmid
+        // and
+        // newmax - newmin = newdist
+        //
+        // so log(newmax - newdist) + log(newmax) == 2logmid
+        // log(newmax(newmax - newdist)) == 2logmid
+        // newmax.newmax - newmax.newdist == exp(2logmid)
+        // newmax^2 + (-newdist)newmax + -exp(2logmid) == 0
+        // quadratic with a = 1, b = -newdist, c = -exp(2logmid), all known
+        // 
+        // positive root
+        // newmax = (newdist + sqrt(newdist^2 + 4exp(2logmid))) / 2
+        //
+        // but logmid = (log(dmin) + log(dmax)) / 2
+        // so exp(2logmid) = exp(log(dmin) + log(dmax))
+        // = exp(log(dmin.dmax))
+        // = dmin.dmax
+        // so newmax = (newdist + sqrtf(newdist^2 + 4dmin.dmax)) / 2
+
+        newmax = (newdist + sqrtf(newdist*newdist + 4*dmin*dmax)) / 2;
+        newmin = newmax - newdist;
+
+//        std::cerr << "newmin = " << newmin << ", newmax = " << newmax << std::endl;
+
+    } else {
+        float dmid = (dmax + dmin) / 2;
+        newmin = dmid - newdist / 2;
+        newmax = dmid + newdist / 2;
+    }
 
     float mmin, mmax;
     mmin = 0;
@@ -2983,10 +3034,10 @@
         newmax = mmax;
     }
     
-//    std::cerr << "SpectrogramLayer::setVerticalZoomStep: " << step << ": " << newmin << " -> " << newmax << " (range " << ddist << ")" << std::endl;
-
-    setMinFrequency(int(newmin));
-    setMaxFrequency(int(newmax));
+//    std::cerr << "SpectrogramLayer::setVerticalZoomStep: " << step << ": " << newmin << " -> " << newmax << " (range " << newdist << ")" << std::endl;
+
+    setMinFrequency(lrintf(newmin));
+    setMaxFrequency(lrintf(newmax));
 }
 
 RangeMapper *