changeset 329:bbc9666cb961 spectrogram-cache-rejig

* First hack towards more efficient paint mechanism from cache filled in a background thread. This doesn't work properly.
author Chris Cannam
date Wed, 14 Nov 2007 16:23:17 +0000
parents b6df8b44b98d
children 846746e4e865
files layer/SpectrogramLayer.cpp layer/SpectrogramLayer.h
diffstat 2 files changed, 585 insertions(+), 375 deletions(-) [+]
line wrap: on
line diff
--- a/layer/SpectrogramLayer.cpp	Sun Nov 11 21:47:19 2007 +0000
+++ b/layer/SpectrogramLayer.cpp	Wed Nov 14 16:23:17 2007 +0000
@@ -42,7 +42,7 @@
 #include <cassert>
 #include <cmath>
 
-//#define DEBUG_SPECTROGRAM_REPAINT 1
+#define DEBUG_SPECTROGRAM_REPAINT 1
 
 SpectrogramLayer::SpectrogramLayer(Configuration config) :
     m_model(0),
@@ -105,10 +105,19 @@
     setWindowType(prefs->getWindowType());
 
     initialisePalette();
+
+    m_paintThread = new PaintThread(this);
+    m_paintThread->start();
 }
 
 SpectrogramLayer::~SpectrogramLayer()
 {
+    if (m_paintThread) {
+        m_paintThread->exiting();
+        m_paintThread->wait();
+        delete m_paintThread;
+    }
+
     delete m_updateTimer;
     m_updateTimer = 0;
     
@@ -546,23 +555,28 @@
 void
 SpectrogramLayer::invalidatePixmapCaches()
 {
+    QMutexLocker locker(&m_pixmapCacheMutex);
     for (ViewPixmapCache::iterator i = m_pixmapCaches.begin();
          i != m_pixmapCaches.end(); ++i) {
-        i->second.validArea = QRect();
+        QMutexLocker locker(&i->second->mutex);
+        i->second->validArea = QRect();
     }
 }
 
 void
 SpectrogramLayer::invalidatePixmapCaches(size_t startFrame, size_t endFrame)
 {
+    QMutexLocker locker(&m_pixmapCacheMutex);
     for (ViewPixmapCache::iterator i = m_pixmapCaches.begin();
          i != m_pixmapCaches.end(); ++i) {
 
+        QMutexLocker locker(&i->second->mutex);
+
         //!!! when are views removed from the map? on setLayerDormant?
         const View *v = i->first;
 
         if (startFrame < v->getEndFrame() && int(endFrame) >= v->getStartFrame()) {
-            i->second.validArea = QRect();
+            i->second->validArea = QRect();
         }
     }
 }
@@ -908,7 +922,17 @@
         Layer::setLayerDormant(v, true);
 
 	invalidatePixmapCaches();
+
+        m_pixmapCacheMutex.lock();
+        m_pixmapCaches[v]->mutex.lock();
+        m_pixmapCaches[v]->mutex.unlock();
+        // that only works if the paint thread never locks the
+        // specific mutex more than once for a given instance of a
+        // lock on the general mutex.  really the data structure is
+        // unsuitable here.
+        delete m_pixmapCaches[v];
         m_pixmapCaches.erase(v);
+        m_pixmapCacheMutex.unlock();
 
         if (m_fftModels.find(v) != m_fftModels.end()) {
 
@@ -1509,6 +1533,8 @@
 FFTModel *
 SpectrogramLayer::getFFTModel(const View *v) const
 {
+    //!!! need lock
+
     if (!m_model) return 0;
 
     size_t fftSize = getFFTSize(v);
@@ -1656,15 +1682,159 @@
     return true;
 }
 
+// Plan:
+//
+// - the QImage cache is managed by the GUI thread (which creates,
+// sizes and destroys it).
+//
+// - but the cache is drawn on by another thread (paint thread)
+//
+// - the GUI thread paint method should always just draw straight from
+// cache without any lock, but it needs to check whether the cache is
+// complete and schedule another update if it isn't
+//
+// - we need a mutex between cache construction/destruction activity
+// and the paint thread
+//
+// - the paint thread needs to release the mutex occasionally so that
+// cache positioning adjustments don't take forever.
+
 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.
+    // If the mutex for our cache is held, we should probably schedule
+    // another update and return immediately.  The general cache mutex
+    // should never be held long enough by any other thread for this
+    // to be an issue.  (The paint thread should hold the general
+    // cache mutex only long enough to look up the cache object in the
+    // map, and then release it, retaining the specific mutex while it
+    // paints.  The GUI thread should then never modify the cache
+    // image without holding its specific mutex.)
 
     Profiler profiler("SpectrogramLayer::paint", true);
+
+    QMutexLocker locker(&m_pixmapCacheMutex);
+
+    PixmapCache *cache = m_pixmapCaches[v];
+    if (!cache) {
+        cache = new PixmapCache;
+        cache->pixmap = QImage(v->width(), v->height(),
+                               QImage::Format_RGB32);
 #ifdef DEBUG_SPECTROGRAM_REPAINT
-    std::cerr << "SpectrogramLayer::paint(): m_model is " << m_model << ", zoom level is " << v->getZoomLevel() << ", m_updateTimer " << m_updateTimer << std::endl;
+        std::cerr << "SpectrogramLayer::paint: Created new cache, size "
+                  << v->width() << "x" << v->height() << " ("
+                  << v->width() * v->height() * 4 << " bytes)" << std::endl;
+#endif
+        m_pixmapCaches[v] = cache;
+        return; //!!! prod paint thread
+    }
+
+    if (!cache->mutex.tryLock()) {
+        std::cerr << "lock unavailable" << std::endl;
+        paint.drawImage(rect,// & cache->validArea,
+                        cache->pixmap,
+                        rect);// & cache->validArea);
+//        v->update(rect);
+        return;
+    }
+
+    if (cache->pixmap.width() != v->width() ||
+        cache->pixmap.height() != v->height()) {
+        cache->pixmap = cache->pixmap.scaled(v->width(), v->height());
+#ifdef DEBUG_SPECTROGRAM_REPAINT
+        std::cerr << "SpectrogramLayer::paint: Rescaled cache to size "
+                  << v->width() << "x" << v->height() << " ("
+                  << v->width() * v->height() * 4 << " bytes)" << std::endl;
+#endif
+    }
+
+    paint.drawImage(rect,// & cache->validArea,
+                    cache->pixmap,
+                    rect);// & cache->validArea);
+
+    if (cache->startFrame != v->getStartFrame()) {
+#ifdef DEBUG_SPECTROGRAM_REPAINT
+        std::cerr << "SpectrogramLayer::paint(): Cache start frame "
+                  << cache->startFrame << " differs from view start frame "
+                  << v->getStartFrame() << std::endl;
+#endif
+        cache->mutex.unlock();
+//        v->update(); //!!! and prod
+        return;
+    }
+
+    if (!cache->validArea.contains(rect)) {
+        //!!! don't want to be sending events; just want to flag up
+        //that we need a repaint and let some higher power arrange to
+        //do it
+//        v->update(rect); //!!!??? prod paint thread
+    }
+
+//    paint.drawImage(rect & cache->validArea,
+//                    cache->pixmap,
+//                    rect & cache->validArea);
+
+    cache->mutex.unlock();
+}
+
+
+void
+SpectrogramLayer::PaintThread::run()
+{
+    while (!m_exiting) {
+
+        std::set<View *> views;
+
+        m_layer->m_pixmapCacheMutex.lock();
+        for (ViewPixmapCache::iterator i = m_layer->m_pixmapCaches.begin();
+             i != m_layer->m_pixmapCaches.end(); ++i) {
+            views.insert(const_cast<View *>(i->first)); //!!!
+        }
+        m_layer->m_pixmapCacheMutex.unlock();
+        
+        for (std::set<View *>::iterator vi = views.begin();
+             vi != views.end(); ++vi) {
+            
+            m_layer->paintCache(*vi);
+            if (m_exiting) break;
+        }
+
+        if (!m_exiting) sleep(1);
+    }
+}
+
+// return true if no more work to do, or can do no more
+
+bool
+SpectrogramLayer::paintCache(View *v) const
+{
+    Profiler profiler("SpectrogramLayer::paintCache", true);
+
+    m_pixmapCacheMutex.lock();
+    
+    PixmapCache *cacheptr = m_pixmapCaches[v];
+    if (!cacheptr || cacheptr->pixmap.width() == 0) {
+#ifdef DEBUG_SPECTROGRAM_REPAINT
+        std::cerr << "SpectrogramLayer::paintCache(): No cache to paint onto"
+                  << std::endl;
+#endif
+        m_pixmapCacheMutex.unlock();
+        return false;
+    }
+
+    PixmapCache &cache = *cacheptr;
+    QMutexLocker locker(&cache.mutex);
+    m_pixmapCacheMutex.unlock();
+
+    // establish rect to paint -- we want to paint the whole area that
+    // is not known to be valid
+    
+    QRect rect = QRect(0, 0, cache.pixmap.width(), cache.pixmap.height());
+
+    QPainter paint(&cache.pixmap);
+    
+#ifdef DEBUG_SPECTROGRAM_REPAINT
+    std::cerr << "SpectrogramLayer::paintCache(): m_model is " << m_model << ", zoom level is " << v->getZoomLevel() << ", m_updateTimer " << m_updateTimer << std::endl;
     
     std::cerr << "rect is " << rect.x() << "," << rect.y() << " " << rect.width() << "x" << rect.height() << std::endl;
 #endif
@@ -1674,11 +1844,11 @@
     else m_candidateFillStartFrame = startFrame;
 
     if (!m_model || !m_model->isOK() || !m_model->isReady()) {
-	return;
+	return false;
     }
-
+/*
     if (isLayerDormant(v)) {
-	std::cerr << "SpectrogramLayer::paint(): Layer is dormant, making it undormant again" << std::endl;
+	std::cerr << "SpectrogramLayer::paintCache(): Layer is dormant, making it undormant again" << std::endl;
     }
 
     // Need to do this even if !isLayerDormant, as that could mean v
@@ -1687,23 +1857,17 @@
     // in the cache-fill thread above.
     //!!! no longer use cache-fill thread
     const_cast<SpectrogramLayer *>(this)->Layer::setLayerDormant(v, false);
-
+*/
     size_t fftSize = getFFTSize(v);
     FFTModel *fft = getFFTModel(v);
     if (!fft) {
-	std::cerr << "ERROR: SpectrogramLayer::paint(): No FFT model, returning" << std::endl;
-	return;
+	std::cerr << "ERROR: SpectrogramLayer::paintCache(): No FFT model, returning" << std::endl;
+	return true;
     }
 
-    PixmapCache &cache = m_pixmapCaches[v];
-
 #ifdef DEBUG_SPECTROGRAM_REPAINT
-    std::cerr << "SpectrogramLayer::paint(): pixmap cache valid area " << cache.validArea.x() << ", " << cache.validArea.y() << ", " << cache.validArea.width() << "x" << cache.validArea.height() << std::endl;
-#endif
-
-#ifdef DEBUG_SPECTROGRAM_REPAINT
-    bool stillCacheing = (m_updateTimer != 0);
-    std::cerr << "SpectrogramLayer::paint(): Still cacheing = " << stillCacheing << std::endl;
+    std::cerr << "SpectrogramLayer::paintCache(): pixmap cache size " << cache.pixmap.width() << "x" << cache.pixmap.height() << std::endl;
+    std::cerr << "SpectrogramLayer::paintCache(): pixmap cache valid area " << cache.validArea.x() << ", " << cache.validArea.y() << ", " << cache.validArea.width() << "x" << cache.validArea.height() << std::endl;
 #endif
 
     int zoomLevel = v->getZoomLevel();
@@ -1731,9 +1895,9 @@
 		std::cerr << "SpectrogramLayer: pixmap cache good" << std::endl;
 #endif
 
-		paint.drawPixmap(rect, cache.pixmap, rect);
-                illuminateLocalFeatures(v, paint);
-		return;
+                //!!! set a flag to that effect?
+
+		return true;
 
 	    } else {
 
@@ -1761,25 +1925,22 @@
 
 		    //!!! Need a utility function for this
 
-		    static QPixmap *tmpPixmap = 0;
+		    static QImage *tmpPixmap = 0;
 		    if (!tmpPixmap ||
 			tmpPixmap->width() != cache.pixmap.width() ||
 			tmpPixmap->height() != cache.pixmap.height()) {
 			delete tmpPixmap;
-			tmpPixmap = new QPixmap(cache.pixmap.width(),
-						cache.pixmap.height());
+			tmpPixmap = new QImage(cache.pixmap.width(),
+                                               cache.pixmap.height(),
+                                               QImage::Format_RGB32);
 		    }
-		    QPainter cachePainter;
-		    cachePainter.begin(tmpPixmap);
-		    cachePainter.drawPixmap(0, 0, cache.pixmap);
-		    cachePainter.end();
-		    cachePainter.begin(&cache.pixmap);
-		    cachePainter.drawPixmap(dx, 0, *tmpPixmap);
-		    cachePainter.end();
+		    QPainter tmpPainter;
+		    tmpPainter.begin(tmpPixmap);
+		    tmpPainter.drawImage(0, 0, cache.pixmap);
+		    tmpPainter.end();
+                    paint.drawImage(dx, 0, *tmpPixmap);
 #else
-		    QPainter cachePainter(&cache.pixmap);
-		    cachePainter.drawPixmap(dx, 0, cache.pixmap);
-		    cachePainter.end();
+                    paint.drawImage(dx, 0, cache.pixmap);
 #endif
 
                     int px = cache.validArea.x();
@@ -1807,10 +1968,6 @@
                     cache.validArea =
                         QRect(px, cache.validArea.y(),
                               pw, cache.validArea.height());
-
-		    paint.drawPixmap(rect & cache.validArea,
-                                     cache.pixmap,
-                                     rect & cache.validArea);
 		}
 	    }
 	} else {
@@ -1832,7 +1989,7 @@
             cache.validArea = QRect();
 	}
     }
-
+/*!!!
     if (updateViewMagnitudes(v)) {
 #ifdef DEBUG_SPECTROGRAM_REPAINT
         std::cerr << "SpectrogramLayer: magnitude range changed to [" << m_viewMags[v].getMin() << "->" << m_viewMags[v].getMax() << "]" << std::endl;
@@ -1843,10 +2000,10 @@
         std::cerr << "No change in magnitude range [" << m_viewMags[v].getMin() << "->" << m_viewMags[v].getMax() << "]" << std::endl;
 #endif
     }
-
+*/
     if (recreateWholePixmapCache) {
         x0 = 0;
-        x1 = v->width();
+        x1 = rect.width();
     }
 
     struct timeval tv;
@@ -1878,13 +2035,332 @@
 #endif
 
     // We always paint the full height when refreshing the cache.
-    // Smaller heights can be used when painting direct from cache
-    // (further up in this function), but we want to ensure the cache
-    // is coherent without having to worry about vertical matching of
-    // required and valid areas as well as horizontal.
+    // Smaller heights can be used when painting from cache, but we
+    // want to ensure the cache is coherent without having to worry
+    // about vertical matching of required and valid areas as well as
+    // horizontal.
 
     int h = v->height();
-
+    int w = x1 - x0;
+
+#ifdef DEBUG_SPECTROGRAM_REPAINT
+    std::cerr << "x0 " << x0 << ", x1 " << x1 << ", w " << w << ", h " << h << std::endl;
+#endif
+
+//    if (m_drawBuffer.width() < w || m_drawBuffer.height() < h) {
+//        m_drawBuffer = QImage(w, h, QImage::Format_RGB32);
+//    }
+
+//    m_drawBuffer.fill(m_palette.getColour(0).rgb());
+
+    int sr = m_model->getSampleRate();
+
+    // 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 = m_fftSize / 2;
+    if (m_maxFrequency > 0) {
+	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) * 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;
+
+    float displayMinFreq = minFreq;
+    float displayMaxFreq = maxFreq;
+
+    if (fftSize != m_fftSize) {
+        displayMinFreq = getEffectiveMinFrequency();
+        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?
+
+    size_t increment = getWindowIncrement();
+    
+    bool logarithmic = (m_frequencyScale == LogFrequencyScale);
+
+    for (size_t q = minbin; q <= maxbin; ++q) {
+        float f0 = (float(q) * sr) / fftSize;
+        yval[q] = v->getYForFrequency(f0, displayMinFreq, displayMaxFreq,
+                                      logarithmic);
+//        std::cerr << "min: " << minFreq << ", max: " << maxFreq << ", yval[" << q << "]: " << yval[q] << std::endl;
+    }
+
+    MagnitudeRange overallMag = m_viewMags[v];
+//    bool overallMagChanged = false;
+
+//    bool fftSuspended = false;
+
+    bool interpolate = false;
+    Preferences::SpectrogramSmoothing smoothing = 
+        Preferences::getInstance()->getSpectrogramSmoothing();
+    if (smoothing == Preferences::SpectrogramInterpolated ||
+        smoothing == Preferences::SpectrogramZeroPaddedAndInterpolated) {
+        if (m_binDisplay != PeakBins &&
+            m_binDisplay != PeakFrequencies) {
+            interpolate = true;
+        }
+    }
+
+#ifdef DEBUG_SPECTROGRAM_REPAINT
+    std::cerr << ((float(v->getFrameForX(1) - v->getFrameForX(0))) / increment) << " bin(s) per pixel" << std::endl;
+#endif
+
+    bool runOutOfData = false;
+
+    for (int x = 0; x < w; ++x) {
+
+        if (runOutOfData) break;
+
+	for (int y = 0; y < h; ++y) {
+	    ymag[y] = 0.f;
+	    ydiv[y] = 0.f;
+	}
+
+	float s0 = 0, s1 = 0;
+
+	if (!getXBinRange(v, x0 + x, s0, s1)) {
+//	    assert(x <= m_drawBuffer.width());
+	    continue;
+	}
+
+	int s0i = int(s0 + 0.001);
+	int s1i = int(s1);
+
+	if (s1i >= int(fft->getWidth())) {
+	    if (s0i >= int(fft->getWidth())) {
+		continue;
+	    } else {
+		s1i = s0i;
+	    }
+	}
+
+        for (int s = s0i; s <= s1i; ++s) {
+
+            if (!fft->isColumnAvailable(s)) {
+#ifdef DEBUG_SPECTROGRAM_REPAINT
+                std::cerr << "Met unavailable column at col " << s << std::endl;
+#endif
+//                continue;
+                runOutOfData = true;
+                break;
+            }
+
+/*            
+            if (!fftSuspended) {
+                fft->suspendWrites();
+                fftSuspended = true;
+            }
+*/
+            MagnitudeRange mag;
+
+            FFTModel::PeakSet peaks;
+            if (m_binDisplay == PeakFrequencies &&
+                s < int(fft->getWidth()) - 1) {
+                peaks = fft->getPeakFrequencies(FFTModel::AllPeaks,
+                                                s,
+                                                minbin, maxbin - 1);
+            }
+
+            for (size_t q = minbin; q < maxbin; ++q) {
+
+                float y0 = yval[q + 1];
+                float y1 = yval[q];
+
+		if (m_binDisplay == PeakBins) {
+		    if (!fft->isLocalPeak(s, q)) continue;
+		}
+                if (m_binDisplay == PeakFrequencies) {
+                    if (peaks.find(q) == peaks.end()) continue;
+                }
+
+                if (m_threshold != 0.f &&
+                    !fft->isOverThreshold(s, q, m_threshold * (m_fftSize/2))) {
+                    continue;
+                }
+
+		float sprop = 1.0;
+		if (s == s0i) sprop *= (s + 1) - s0;
+		if (s == s1i) sprop *= s1 - s;
+ 
+		if (m_binDisplay == PeakFrequencies) {
+		    y0 = y1 = v->getYForFrequency
+			(peaks[q], displayMinFreq, displayMaxFreq, logarithmic);
+		}
+		
+		int y0i = int(y0 + 0.001);
+		int y1i = int(y1);
+
+                float value;
+                
+                if (m_colourScale == PhaseColourScale) {
+                    value = fft->getPhaseAt(s, q);
+                } else if (m_normalizeColumns) {
+                    value = fft->getNormalizedMagnitudeAt(s, q);
+                    mag.sample(value);
+                    value *= m_gain;
+                } else {
+                    value = fft->getMagnitudeAt(s, q) / (m_fftSize/2);
+                    mag.sample(value);
+                    value *= m_gain;
+                }
+
+                if (interpolate) {
+                    
+                    int ypi = y0i;
+                    if (q < maxbin - 1) ypi = int(yval[q + 2]);
+
+                    for (int y = ypi; y <= y1i; ++y) {
+                    
+                        if (y < 0 || y >= h) continue;
+                    
+                        float yprop = sprop;
+                        float iprop = yprop;
+
+                        if (ypi < y0i && y <= y0i) {
+
+                            float half = float(y0i - ypi) / 2;
+                            float dist = y - (ypi + half);
+
+                            if (dist >= 0) {
+                                iprop = (iprop * dist) / half;
+                                ymag[y] += iprop * value;
+                            }
+                        } else {
+                            if (y1i > y0i) {
+
+                                float half = float(y1i - y0i) / 2;
+                                float dist = y - (y0i + half);
+                                
+                                if (dist >= 0) {
+                                    iprop = (iprop * (half - dist)) / half;
+                                }
+                            }
+
+                            ymag[y] += iprop * value;
+                            ydiv[y] += yprop;
+                        }
+                    }
+
+                } else {
+
+                    for (int y = y0i; y <= y1i; ++y) {
+                    
+                        if (y < 0 || y >= h) continue;
+                    
+                        float yprop = sprop;
+                        if (y == y0i) yprop *= (y + 1) - y0;
+                        if (y == y1i) yprop *= y1 - y;
+
+                        for (int y = y0i; y <= y1i; ++y) {
+		    
+                            if (y < 0 || y >= h) continue;
+
+                            float yprop = sprop;
+                            if (y == y0i) yprop *= (y + 1) - y0;
+                            if (y == y1i) yprop *= y1 - y;
+                            ymag[y] += yprop * value;
+                            ydiv[y] += yprop;
+                        }
+                    }
+                }
+	    }
+/*
+            if (mag.isSet()) {
+
+                if (s >= int(m_columnMags.size())) {
+                    std::cerr << "INTERNAL ERROR: " << s << " >= "
+                              << m_columnMags.size() << " at SpectrogramLayer.cpp:" << __LINE__ << std::endl;
+                }
+
+                m_columnMags[s].sample(mag);
+
+                if (overallMag.sample(mag)) {
+                    //!!! scaling would change here
+                    overallMagChanged = true;
+#ifdef DEBUG_SPECTROGRAM_REPAINT
+                    std::cerr << "Overall mag changed (again?) at column " << s << ", to [" << overallMag.getMin() << "->" << overallMag.getMax() << "]" << std::endl;
+#endif
+                }
+            }
+*/
+	}
+
+	for (int y = 0; y < h; ++y) {
+
+	    if (ydiv[y] > 0.0) {
+
+		unsigned char pixel = 0;
+
+		float avg = ymag[y] / ydiv[y];
+		pixel = getDisplayValue(v, avg);
+
+//		assert(x <= m_drawBuffer.width());
+                assert(x <= cache.pixmap.width());
+		QColor c = m_palette.getColour(pixel);
+
+                if (x0 + x >= cache.pixmap.width()) {
+                    std::cerr << "INTERNAL ERROR: " << x0 << "+" 
+                              << x << " >= " << cache.pixmap.width()
+                              << " at SpectrogramLayer.cpp:"
+                              << __LINE__ << std::endl;
+                    continue;
+                }
+
+                if (y >= cache.pixmap.height()) {
+                    std::cerr << "INTERNAL ERROR: " << y
+                              << " >= " << cache.pixmap.height()
+                              << " at SpectrogramLayer.cpp:"
+                              << __LINE__ << std::endl;
+                    continue;
+                }
+
+                assert(x0 + x < cache.pixmap.width());
+                assert(y < cache.pixmap.height());
+
+		cache.pixmap.setPixel(x0 + x, y,
+                                      qRgb(c.red(), c.green(), c.blue()));
+	    }
+	}
+    }
+/*
+    if (overallMagChanged) {
+        m_viewMags[v] = overallMag;
+#ifdef DEBUG_SPECTROGRAM_REPAINT
+        std::cerr << "Overall mag is now [" << m_viewMags[v].getMin() << "->" << m_viewMags[v].getMax() << "] - will be updating" << std::endl;
+#endif
+    } else {
+#ifdef DEBUG_SPECTROGRAM_REPAINT
+        std::cerr << "Overall mag unchanged at [" << m_viewMags[v].getMin() << "->" << m_viewMags[v].getMax() << "]" << std::endl;
+#endif
+    }
+*/
     if (cache.validArea.width() > 0) {
 
         int vx0 = 0, vx1 = 0;
@@ -1917,7 +2393,7 @@
         cache.validArea = QRect
             (std::min(vx0, x0), cache.validArea.y(),
              std::max(vx1 - std::min(vx0, x0),
-                      x1 - std::min(vx0, x0)),
+                       x1 - std::min(vx0, x0)),
              cache.validArea.height());
             
     } else {
@@ -1935,305 +2411,8 @@
         }
         cache.validArea = QRect(x0, 0, x1 - x0, h);
     }
-
-    int w = x1 - x0;
-
-#ifdef DEBUG_SPECTROGRAM_REPAINT
-    std::cerr << "x0 " << x0 << ", x1 " << x1 << ", w " << w << ", h " << h << std::endl;
-#endif
-
-    if (m_drawBuffer.width() < w || m_drawBuffer.height() < h) {
-        m_drawBuffer = QImage(w, h, QImage::Format_RGB32);
-    }
-
-    m_drawBuffer.fill(m_palette.getColour(0).rgb());
-
-    int sr = m_model->getSampleRate();
-
-    // 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 = m_fftSize / 2;
-    if (m_maxFrequency > 0) {
-	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) * 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;
-
-    float displayMinFreq = minFreq;
-    float displayMaxFreq = maxFreq;
-
-    if (fftSize != m_fftSize) {
-        displayMinFreq = getEffectiveMinFrequency();
-        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?
-
-    size_t increment = getWindowIncrement();
-    
-    bool logarithmic = (m_frequencyScale == LogFrequencyScale);
-
-    for (size_t q = minbin; q <= maxbin; ++q) {
-        float f0 = (float(q) * sr) / fftSize;
-        yval[q] = v->getYForFrequency(f0, displayMinFreq, displayMaxFreq,
-                                      logarithmic);
-//        std::cerr << "min: " << minFreq << ", max: " << maxFreq << ", yval[" << q << "]: " << yval[q] << std::endl;
-    }
-
-    MagnitudeRange overallMag = m_viewMags[v];
-    bool overallMagChanged = false;
-
-    bool fftSuspended = false;
-
-    bool interpolate = false;
-    Preferences::SpectrogramSmoothing smoothing = 
-        Preferences::getInstance()->getSpectrogramSmoothing();
-    if (smoothing == Preferences::SpectrogramInterpolated ||
-        smoothing == Preferences::SpectrogramZeroPaddedAndInterpolated) {
-        if (m_binDisplay != PeakBins &&
-            m_binDisplay != PeakFrequencies) {
-            interpolate = true;
-        }
-    }
-
-#ifdef DEBUG_SPECTROGRAM_REPAINT
-    std::cerr << ((float(v->getFrameForX(1) - v->getFrameForX(0))) / increment) << " bin(s) per pixel" << std::endl;
-#endif
-
-    bool runOutOfData = false;
-
-    for (int x = 0; x < w; ++x) {
-
-        if (runOutOfData) break;
-
-	for (int y = 0; y < h; ++y) {
-	    ymag[y] = 0.f;
-	    ydiv[y] = 0.f;
-	}
-
-	float s0 = 0, s1 = 0;
-
-	if (!getXBinRange(v, x0 + x, s0, s1)) {
-	    assert(x <= m_drawBuffer.width());
-	    continue;
-	}
-
-	int s0i = int(s0 + 0.001);
-	int s1i = int(s1);
-
-	if (s1i >= int(fft->getWidth())) {
-	    if (s0i >= int(fft->getWidth())) {
-		continue;
-	    } else {
-		s1i = s0i;
-	    }
-	}
-
-        for (int s = s0i; s <= s1i; ++s) {
-
-            if (!fft->isColumnAvailable(s)) {
-#ifdef DEBUG_SPECTROGRAM_REPAINT
-                std::cerr << "Met unavailable column at col " << s << std::endl;
-#endif
-//                continue;
-                runOutOfData = true;
-                break;
-            }
-            
-            if (!fftSuspended) {
-                fft->suspendWrites();
-                fftSuspended = true;
-            }
-
-            MagnitudeRange mag;
-
-            FFTModel::PeakSet peaks;
-            if (m_binDisplay == PeakFrequencies &&
-                s < int(fft->getWidth()) - 1) {
-                peaks = fft->getPeakFrequencies(FFTModel::AllPeaks,
-                                                s,
-                                                minbin, maxbin - 1);
-            }
-
-            for (size_t q = minbin; q < maxbin; ++q) {
-
-                float y0 = yval[q + 1];
-                float y1 = yval[q];
-
-		if (m_binDisplay == PeakBins) {
-		    if (!fft->isLocalPeak(s, q)) continue;
-		}
-                if (m_binDisplay == PeakFrequencies) {
-                    if (peaks.find(q) == peaks.end()) continue;
-                }
-
-                if (m_threshold != 0.f &&
-                    !fft->isOverThreshold(s, q, m_threshold * (m_fftSize/2))) {
-                    continue;
-                }
-
-		float sprop = 1.0;
-		if (s == s0i) sprop *= (s + 1) - s0;
-		if (s == s1i) sprop *= s1 - s;
- 
-		if (m_binDisplay == PeakFrequencies) {
-		    y0 = y1 = v->getYForFrequency
-			(peaks[q], displayMinFreq, displayMaxFreq, logarithmic);
-		}
-		
-		int y0i = int(y0 + 0.001);
-		int y1i = int(y1);
-
-                float value;
-                
-                if (m_colourScale == PhaseColourScale) {
-                    value = fft->getPhaseAt(s, q);
-                } else if (m_normalizeColumns) {
-                    value = fft->getNormalizedMagnitudeAt(s, q);
-                    mag.sample(value);
-                    value *= m_gain;
-                } else {
-                    value = fft->getMagnitudeAt(s, q) / (m_fftSize/2);
-                    mag.sample(value);
-                    value *= m_gain;
-                }
-
-                if (interpolate) {
-                    
-                    int ypi = y0i;
-                    if (q < maxbin - 1) ypi = int(yval[q + 2]);
-
-                    for (int y = ypi; y <= y1i; ++y) {
-                    
-                        if (y < 0 || y >= h) continue;
-                    
-                        float yprop = sprop;
-                        float iprop = yprop;
-
-                        if (ypi < y0i && y <= y0i) {
-
-                            float half = float(y0i - ypi) / 2;
-                            float dist = y - (ypi + half);
-
-                            if (dist >= 0) {
-                                iprop = (iprop * dist) / half;
-                                ymag[y] += iprop * value;
-                            }
-                        } else {
-                            if (y1i > y0i) {
-
-                                float half = float(y1i - y0i) / 2;
-                                float dist = y - (y0i + half);
-                                
-                                if (dist >= 0) {
-                                    iprop = (iprop * (half - dist)) / half;
-                                }
-                            }
-
-                            ymag[y] += iprop * value;
-                            ydiv[y] += yprop;
-                        }
-                    }
-
-                } else {
-
-                    for (int y = y0i; y <= y1i; ++y) {
-                    
-                        if (y < 0 || y >= h) continue;
-                    
-                        float yprop = sprop;
-                        if (y == y0i) yprop *= (y + 1) - y0;
-                        if (y == y1i) yprop *= y1 - y;
-
-                        for (int y = y0i; y <= y1i; ++y) {
-		    
-                            if (y < 0 || y >= h) continue;
-
-                            float yprop = sprop;
-                            if (y == y0i) yprop *= (y + 1) - y0;
-                            if (y == y1i) yprop *= y1 - y;
-                            ymag[y] += yprop * value;
-                            ydiv[y] += yprop;
-                        }
-                    }
-                }
-	    }
-
-            if (mag.isSet()) {
-
-                if (s >= int(m_columnMags.size())) {
-                    std::cerr << "INTERNAL ERROR: " << s << " >= "
-                              << m_columnMags.size() << " at SpectrogramLayer.cpp:2087" << std::endl;
-                }
-
-                m_columnMags[s].sample(mag);
-
-                if (overallMag.sample(mag)) {
-                    //!!! scaling would change here
-                    overallMagChanged = true;
-#ifdef DEBUG_SPECTROGRAM_REPAINT
-                    std::cerr << "Overall mag changed (again?) at column " << s << ", to [" << overallMag.getMin() << "->" << overallMag.getMax() << "]" << std::endl;
-#endif
-                }
-            }
-	}
-
-	for (int y = 0; y < h; ++y) {
-
-	    if (ydiv[y] > 0.0) {
-
-		unsigned char pixel = 0;
-
-		float avg = ymag[y] / ydiv[y];
-		pixel = getDisplayValue(v, avg);
-
-		assert(x <= m_drawBuffer.width());
-		QColor c = m_palette.getColour(pixel);
-		m_drawBuffer.setPixel(x, y,
-                                      qRgb(c.red(), c.green(), c.blue()));
-	    }
-	}
-    }
-
-    if (overallMagChanged) {
-        m_viewMags[v] = overallMag;
-#ifdef DEBUG_SPECTROGRAM_REPAINT
-        std::cerr << "Overall mag is now [" << m_viewMags[v].getMin() << "->" << m_viewMags[v].getMax() << "] - will be updating" << std::endl;
-#endif
-    } else {
-#ifdef DEBUG_SPECTROGRAM_REPAINT
-        std::cerr << "Overall mag unchanged at [" << m_viewMags[v].getMin() << "->" << m_viewMags[v].getMax() << "]" << std::endl;
-#endif
-    }
-
-    Profiler profiler2("SpectrogramLayer::paint: draw image", true);
+/*
+    Profiler profiler2("SpectrogramLayer::paintCache: draw image", true);
 
 #ifdef DEBUG_SPECTROGRAM_REPAINT
     std::cerr << "Painting " << w << "x" << rect.height()
@@ -2244,7 +2423,8 @@
     paint.drawImage(x0, rect.y(), m_drawBuffer, 0, rect.y(), w, rect.height());
 
     if (recreateWholePixmapCache) {
-	cache.pixmap = QPixmap(v->width(), h);
+//	cache.pixmap = QPixmap(v->width(), h);
+        cache.pixmap = QImage(v->width(), h, QImage::Format_RGB32);
     }
 
 #ifdef DEBUG_SPECTROGRAM_REPAINT
@@ -2256,53 +2436,62 @@
     QPainter cachePainter(&cache.pixmap);
     cachePainter.drawImage(x0, 0, m_drawBuffer, 0, 0, w, h);
     cachePainter.end();
-
+*/
+/*
     if (!m_normalizeVisibleArea || !overallMagChanged) {
-    
+*/  
         cache.startFrame = startFrame;
         cache.zoomLevel = zoomLevel;
 
+        bool morePaintingRequired = false;
+
         if (cache.validArea.x() > 0) {
 #ifdef DEBUG_SPECTROGRAM_REPAINT
-            std::cerr << "SpectrogramLayer::paint() updating left (0, "
+            std::cerr << "SpectrogramLayer::paintCache() updating left (0, "
                       << cache.validArea.x() << ")" << std::endl;
 #endif
-            v->update(0, 0, cache.validArea.x(), h);
+//            v->update(0, 0, cache.validArea.x(), h);
+            morePaintingRequired = true;
         }
         
         if (cache.validArea.x() + cache.validArea.width() <
             cache.pixmap.width()) {
 #ifdef DEBUG_SPECTROGRAM_REPAINT
-            std::cerr << "SpectrogramLayer::paint() updating right ("
+            std::cerr << "SpectrogramLayer::paintCache() 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()),
-                      h);
+//            v->update(cache.validArea.x() + cache.validArea.width(),
+//                      0,
+//                      cache.pixmap.width() - (cache.validArea.x() +
+//                                              cache.validArea.width()),
+//                      h);
+            morePaintingRequired = true;
         }
+/*
     } else {
         // overallMagChanged
         cache.validArea = QRect();
-        v->update();
+        morePaintingRequired = true;
+//        v->update();
     }
-
-    illuminateLocalFeatures(v, paint);
+*/
+//    illuminateLocalFeatures(v, paint);
 
 #ifdef DEBUG_SPECTROGRAM_REPAINT
-    std::cerr << "SpectrogramLayer::paint() returning" << std::endl;
+    std::cerr << "SpectrogramLayer::paintCache() returning" << std::endl;
 #endif
 
     m_lastPaintBlockWidth = paintBlockWidth;
     (void)gettimeofday(&tv, 0);
     m_lastPaintTime = RealTime::fromTimeval(tv) - mainPaintStart;
 
-    if (fftSuspended) fft->resume();
+    return !morePaintingRequired;
+
+//    if (fftSuspended) fft->resume();
 }
 
 void
@@ -2464,12 +2653,16 @@
 void
 SpectrogramLayer::measureDoubleClick(View *v, QMouseEvent *e)
 {
-    PixmapCache &cache = m_pixmapCaches[v];
-
-    std::cerr << "cache width: " << cache.pixmap.width() << ", height: "
-              << cache.pixmap.height() << std::endl;
-
-    QImage image = cache.pixmap.toImage();
+    //!!! check cache consistency
+    QMutexLocker locker(&m_pixmapCacheMutex);
+
+    PixmapCache *cache = m_pixmapCaches[v];
+    if (!cache) return;
+
+    std::cerr << "cache width: " << cache->pixmap.width() << ", height: "
+              << cache->pixmap.height() << std::endl;
+
+    QImage image = cache->pixmap /*.toImage() */;
 
     ImageRegionFinder finder;
     QRect rect = finder.findRegionExtents(&image, e->pos());
--- a/layer/SpectrogramLayer.h	Sun Nov 11 21:47:19 2007 +0000
+++ b/layer/SpectrogramLayer.h	Wed Nov 14 16:23:17 2007 +0000
@@ -32,8 +32,8 @@
 
 class View;
 class QPainter;
-class QImage;
-class QPixmap;
+//class QImage;
+//class QPixmap;
 class QTimer;
 class FFTModel;
 
@@ -282,16 +282,17 @@
 
     struct PixmapCache
     {
-        QPixmap pixmap;
+        QMutex mutex;
+        QImage pixmap;
         QRect validArea;
         long startFrame;
         size_t zoomLevel;
     };
-    typedef std::map<const View *, PixmapCache> ViewPixmapCache;
+    typedef std::map<const View *, PixmapCache *> ViewPixmapCache;
     void invalidatePixmapCaches();
     void invalidatePixmapCaches(size_t startFrame, size_t endFrame);
     mutable ViewPixmapCache m_pixmapCaches;
-    mutable QImage m_drawBuffer;
+    mutable QMutex m_pixmapCacheMutex;
 
     mutable QTimer *m_updateTimer;
 
@@ -339,6 +340,22 @@
     FFTModel *getFFTModel(const View *v) const;
     void invalidateFFTModels();
 
+    class PaintThread : public Thread
+    {
+    public:
+        PaintThread(SpectrogramLayer *layer) :
+            m_layer(layer), m_exiting(false) { }
+        virtual void run();
+        virtual void exiting() { m_exiting = true; }
+        
+    protected:
+        SpectrogramLayer *m_layer;
+        bool m_exiting;
+    };
+
+    PaintThread *m_paintThread;
+    bool paintCache(View *v) const;
+
     typedef std::pair<FFTModel *, int> FFTFillPair; // model, last fill
     typedef std::map<const View *, FFTFillPair> ViewFFTMap;
     typedef std::vector<float> FloatVector;