changeset 1118:175d4e15884d spectrogram-minor-refactor

Introduce ScrollableMagRangeCache, plus some tidying etc
author Chris Cannam
date Wed, 20 Jul 2016 08:30:20 +0100
parents 64709d4d09ef
children be5b91ec81a0
files layer/Colour3DPlotRenderer.cpp layer/ScrollableImageCache.cpp layer/ScrollableImageCache.h layer/ScrollableMagRangeCache.cpp layer/ScrollableMagRangeCache.h svgui.pro
diffstat 6 files changed, 285 insertions(+), 46 deletions(-) [+]
line wrap: on
line diff
--- a/layer/Colour3DPlotRenderer.cpp	Tue Jul 19 17:28:03 2016 +0100
+++ b/layer/Colour3DPlotRenderer.cpp	Wed Jul 20 08:30:20 2016 +0100
@@ -136,7 +136,7 @@
             }
         }
     } else {
-        // cache completely invalid
+        // cache is completely invalid
         m_cache.setStartFrame(startFrame);
     }
 
--- a/layer/ScrollableImageCache.cpp	Tue Jul 19 17:28:03 2016 +0100
+++ b/layer/ScrollableImageCache.cpp	Wed Jul 20 08:30:20 2016 +0100
@@ -45,7 +45,7 @@
     int w = m_image.width();
 
     if (dx == 0) {
-	// haven't moved visibly
+	// haven't moved visibly (even though start frame may have changed)
 	return;
     }
 
@@ -72,8 +72,8 @@
 	
     // update valid area
         
-    int px = m_left;
-    int pw = m_width;
+    int px = m_validLeft;
+    int pw = m_validWidth;
 	
     px += dx;
 	
@@ -96,8 +96,8 @@
 	}
     }
 
-    m_left = px;
-    m_width = pw;
+    m_validLeft = px;
+    m_validWidth = pw;
 }
 
 void
@@ -107,21 +107,21 @@
 #ifdef DEBUG_SCROLLABLE_IMAGE_CACHE
     cerr << "ScrollableImageCache::adjustToTouchValidArea: left " << left
          << ", width " << width << endl;
-    cerr << "ScrollableImageCache: my left " << m_left
-         << ", width " << m_width << " so right " << (m_left + m_width) << endl;
+    cerr << "ScrollableImageCache: my left " << m_validLeft
+         << ", width " << m_validWidth << " so right " << (m_validLeft + m_validWidth) << endl;
 #endif
-    if (left < m_left) {
+    if (left < m_validLeft) {
 	isLeftOfValidArea = true;
-	if (left + width <= m_left + m_width) {
-	    width = m_left - left;
+	if (left + width <= m_validLeft + m_validWidth) {
+	    width = m_validLeft - left;
 	}
 #ifdef DEBUG_SCROLLABLE_IMAGE_CACHE
         cerr << "ScrollableImageCache: we're left of valid area, adjusted width to " << width << endl;
 #endif
     } else {
 	isLeftOfValidArea = false;
-	width = left + width - (m_left + m_width);
-	left = m_left + m_width;
+	width = left + width - (m_validLeft + m_validWidth);
+	left = m_validLeft + m_validWidth;
 	if (width < 0) width = 0;
 #ifdef DEBUG_SCROLLABLE_IMAGE_CACHE
         cerr << "ScrollableImageCache: we're right of valid area, adjusted left to " << left << ", width to " << width << endl;
@@ -164,43 +164,43 @@
     painter.end();
 
     if (!isValid()) {
-	m_left = left;
-	m_width = width;
+	m_validLeft = left;
+	m_validWidth = width;
 	return;
     }
 	
-    if (left < m_left) {
-	if (left + width > m_left + m_width) {
+    if (left < m_validLeft) {
+	if (left + width > m_validLeft + m_validWidth) {
 	    // new image completely contains the old valid area --
 	    // use the new area as is
-	    m_left = left;
-	    m_width = width;
-	} else if (left + width < m_left) {
+	    m_validLeft = left;
+	    m_validWidth = width;
+	} else if (left + width < m_validLeft) {
 	    // new image completely off left of old valid area --
 	    // we can't extend the valid area because the bit in
 	    // between is not valid, so must use the new area only
-	    m_left = left;
-	    m_width = width;
+	    m_validLeft = left;
+	    m_validWidth = width;
 	} else {
 	    // new image overlaps old valid area on left side --
 	    // use new left edge, and extend width to existing
 	    // right edge
-	    m_width = (m_left + m_width) - left;
-	    m_left = left;
+	    m_validWidth = (m_validLeft + m_validWidth) - left;
+	    m_validLeft = left;
 	}
     } else {
-	if (left > m_left + m_width) {
+	if (left > m_validLeft + m_validWidth) {
 	    // new image completely off right of old valid area --
 	    // we can't extend the valid area because the bit in
 	    // between is not valid, so must use the new area only
-	    m_left = left;
-	    m_width = width;
-	} else if (left + width > m_left + m_width) {
+	    m_validLeft = left;
+	    m_validWidth = width;
+	} else if (left + width > m_validLeft + m_validWidth) {
 	    // new image overlaps old valid area on right side --
 	    // use existing left edge, and extend width to new
 	    // right edge
-	    m_width = (left + width) - m_left;
-	    // (m_left unchanged)
+	    m_validWidth = (left + width) - m_validLeft;
+	    // (m_validLeft unchanged)
 	} else {
 	    // new image completely contained within old valid
 	    // area -- leave the old area unchanged
--- a/layer/ScrollableImageCache.h	Tue Jul 19 17:28:03 2016 +0100
+++ b/layer/ScrollableImageCache.h	Wed Jul 20 08:30:20 2016 +0100
@@ -24,7 +24,7 @@
 #include <QPainter>
 
 /**
- * A cached image for a view that scrolls horizontally, primarily the
+ * A cached image for a view that scrolls horizontally, such as a
  * spectrogram. The cache object holds an image, reports the size of
  * the image (likely the same as the underlying view, but it's the
  * caller's responsibility to set the size appropriately), can scroll
@@ -38,24 +38,28 @@
 {
 public:
     ScrollableImageCache() :
-	m_left(0),
-	m_width(0),
+	m_validLeft(0),
+	m_validWidth(0),
 	m_startFrame(0),
 	m_zoomLevel(0)
     {}
 
     void invalidate() {
-	m_width = 0;
+	m_validWidth = 0;
     }
     
     bool isValid() const {
-	return m_width > 0;
+	return m_validWidth > 0;
     }
 
     QSize getSize() const {
 	return m_image.size();
     }
-    
+
+    /**
+     * Set the size of the cache. If the new size differs from the
+     * current size, the cache is invalidated.
+     */
     void resize(QSize newSize) {
         if (getSize() != newSize) {
             m_image = QImage(newSize, QImage::Format_ARGB32_Premultiplied);
@@ -64,25 +68,31 @@
     }
 	
     int getValidLeft() const {
-	return m_left;
+	return m_validLeft;
     }
     
     int getValidWidth() const {
-	return m_width;
+	return m_validWidth;
     }
 
     int getValidRight() const {
-	return m_left + m_width;
+	return m_validLeft + m_validWidth;
     }
 
     QRect getValidArea() const {
-	return QRect(m_left, 0, m_width, m_image.height());
+	return QRect(m_validLeft, 0, m_validWidth, m_image.height());
     }
     
     int getZoomLevel() const {
 	return m_zoomLevel;
     }
-    
+
+    /**
+     * Set the zoom level. If the new zoom level differs from the
+     * current one, the cache is invalidated. (Determining whether to
+     * invalidate the cache here is the only thing the zoom level is
+     * used for.)
+     */
     void setZoomLevel(int zoom) {
         if (m_zoomLevel != zoom) {
             m_zoomLevel = zoom;
@@ -95,9 +105,10 @@
     }
 
     /**
-     * Set the start frame and invalidate the cache. To scroll,
-     * i.e. to set the start frame while retaining cache validity
-     * where possible, use scrollTo() instead.
+     * Set the start frame. If the new start frame differs from the
+     * current one, the cache is invalidated. To scroll, i.e. to set
+     * the start frame while retaining cache validity where possible,
+     * use scrollTo() instead.
      */
     void setStartFrame(sv_frame_t frame) {
         if (m_startFrame != frame) {
@@ -143,8 +154,8 @@
     
 private:
     QImage m_image;
-    int m_left;  // of valid region
-    int m_width; // of valid region
+    int m_validLeft;
+    int m_validWidth;
     sv_frame_t m_startFrame;
     int m_zoomLevel;
 };
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/layer/ScrollableMagRangeCache.cpp	Wed Jul 20 08:30:20 2016 +0100
@@ -0,0 +1,96 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of the
+    License, or (at your option) any later version.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+#include "ScrollableMagRangeCache.h"
+
+#include <iostream>
+using namespace std;
+
+#define DEBUG_SCROLLABLE_MAG_RANGE_CACHE 1
+
+void
+ScrollableMagRangeCache::scrollTo(const LayerGeometryProvider *v,
+				  sv_frame_t newStartFrame)
+{
+    if (m_startFrame == newStartFrame) {
+	// haven't moved
+        return;
+    }
+	
+    int dx = (v->getXForFrame(m_startFrame) -
+	      v->getXForFrame(newStartFrame));
+
+#ifdef DEBUG_SCROLLABLE_MAG_RANGE_CACHE
+    cerr << "ScrollableMagRangeCache::scrollTo: start frame " << m_startFrame
+	 << " -> " << newStartFrame << ", dx = " << dx << endl;
+#endif
+    
+    m_startFrame = newStartFrame;
+
+    if (dx == 0) {
+	// haven't moved visibly (even though start frame may have changed)
+	return;
+    }
+	
+    int w = int(m_ranges.size());
+
+    if (dx <= -w || dx >= w) {
+	// scrolled entirely off
+	invalidate();
+	return;
+    }
+	
+    // dx is in range, cache is scrollable
+
+    if (dx < 0) {
+	// The new start frame is to the left of the old start
+	// frame. We need to add some empty ranges at the left (start)
+	// end and clip the right end. Assemble -dx new values, then
+	// w+dx old values starting at index 0.
+
+	auto newRanges = vector<MagnitudeRange>(-dx);
+	newRanges.insert(newRanges.end(),
+			 m_ranges.begin(), m_ranges.begin() + w + dx);
+	m_ranges = newRanges;
+	
+    } else {
+	// The new start frame is to the right of the old start
+	// frame. We want to clip the left (start) end and add some
+	// empty ranges at the right end. Assemble w-dx old values
+	// starting at index dx, then dx new values.
+
+	auto newRanges = vector<MagnitudeRange>(dx);
+	newRanges.insert(newRanges.begin(),
+			 m_ranges.begin() + dx, m_ranges.end());
+	m_ranges = newRanges;
+    }
+}
+
+void
+ScrollableMagRangeCache::sampleColumn(const LayerGeometryProvider *v,
+				      sv_frame_t frame,
+				      const MagnitudeRange &r)
+{
+    int x = (v->getXForFrame(frame) -
+	     v->getXForFrame(m_startFrame));
+
+    if (!in_range_for(m_ranges, x)) {
+	cerr << "WARNING: ScrollableMagRangeCache::sampleColumn: column " << x
+	     << " arising from frame " << frame << " is out of range for cache "
+	     << "of width " << m_ranges.size() << endl;
+    } else {
+	sampleColumn(x, r);
+    }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/layer/ScrollableMagRangeCache.h	Wed Jul 20 08:30:20 2016 +0100
@@ -0,0 +1,130 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Visualiser
+    An audio file viewer and annotation editor.
+    Centre for Digital Music, Queen Mary, University of London.
+    
+    This program is free software; you can redistribute it and/or
+    modify it under the terms of the GNU General Public License as
+    published by the Free Software Foundation; either version 2 of the
+    License, or (at your option) any later version.  See the file
+    COPYING included with this distribution for more information.
+*/
+
+#ifndef SCROLLABLE_MAG_RANGE_CACHE_H
+#define SCROLLABLE_MAG_RANGE_CACHE_H
+
+#include "base/BaseTypes.h"
+#include "base/MagnitudeRange.h"
+
+#include "LayerGeometryProvider.h"
+
+/**
+ * A cached set of magnitude range records for a view that scrolls
+ * horizontally, such as a spectrogram. The cache object holds a
+ * magnitude range per column of the view, can report width (likely
+ * the same as the underlying view, but it's the caller's
+ * responsibility to set the size appropriately), can scroll the set
+ * of ranges, and can report and update which columns have had a range
+ * specified.
+ *
+ * The only way to *update* the valid area in a cache is to update the
+ * magnitude range for a column using the sampleColumn call.
+ */
+class ScrollableMagRangeCache
+{
+public:
+    ScrollableMagRangeCache() :
+	m_startFrame(0),
+	m_zoomLevel(0)
+    {}
+
+    void invalidate() {
+	m_ranges = std::vector<MagnitudeRange>(m_ranges.size());
+    }
+    
+    int getWidth() const {
+	return int(m_ranges.size());
+    }
+
+    /**
+     * Set the width of the cache in columns. If the new size differs
+     * from the current size, the cache is invalidated.
+     */
+    void resize(int newWidth) {
+        if (getWidth() != newWidth) {
+	    m_ranges = std::vector<MagnitudeRange>(newWidth);
+        }
+    }
+	
+    int getZoomLevel() const {
+	return m_zoomLevel;
+    }
+
+    /**
+     * Set the zoom level. If the new zoom level differs from the
+     * current one, the cache is invalidated. (Determining whether to
+     * invalidate the cache here is the only thing the zoom level is
+     * used for.)
+     */
+    void setZoomLevel(int zoom) {
+        if (m_zoomLevel != zoom) {
+            m_zoomLevel = zoom;
+            invalidate();
+        }
+    }
+
+    sv_frame_t getStartFrame() const {
+	return m_startFrame;
+    }
+
+    /**
+     * Set the start frame. If the new start frame differs from the
+     * current one, the cache is invalidated. To scroll, i.e. to set
+     * the start frame while retaining cache validity where possible,
+     * use scrollTo() instead.
+     */
+    void setStartFrame(sv_frame_t frame) {
+        if (m_startFrame != frame) {
+            m_startFrame = frame;
+            invalidate();
+        }
+    }
+
+    bool isColumnSet(int column) const {
+	return in_range_for(m_ranges, column) && m_ranges.at(column).isSet();
+    }
+    
+    const MagnitudeRange &getRange(int column) const {
+	return m_ranges.at(column);
+    }
+
+    /**
+     * Set the new start frame for the cache, according to the
+     * geometry of the supplied LayerGeometryProvider, if possible
+     * also moving along any existing valid data within the cache so
+     * that it continues to be valid for the new start frame.
+     */
+    void scrollTo(const LayerGeometryProvider *v, sv_frame_t newStartFrame);
+    
+    /**
+     * Update a column in the cache, by column number.
+     */
+    void sampleColumn(int column, const MagnitudeRange &r) {
+	m_ranges[column].sample(r);
+    }
+    
+    /**
+     * Update a column in the cache, by frame.
+     */
+    void sampleColumn(const LayerGeometryProvider *v, sv_frame_t frame,
+		      const MagnitudeRange &r);
+    
+private:
+    std::vector<MagnitudeRange> m_ranges;
+    sv_frame_t m_startFrame;
+    int m_zoomLevel;
+};
+
+#endif
--- a/svgui.pro	Tue Jul 19 17:28:03 2016 +0100
+++ b/svgui.pro	Wed Jul 20 08:30:20 2016 +0100
@@ -60,6 +60,7 @@
            layer/RegionLayer.h \
            layer/RenderTimer.h \
            layer/ScrollableImageCache.h \
+           layer/ScrollableMagRangeCache.h \
            layer/SingleColourLayer.h \
            layer/SliceableLayer.h \
            layer/SliceLayer.h \
@@ -90,6 +91,7 @@
            layer/PianoScale.cpp \
            layer/RegionLayer.cpp \
            layer/ScrollableImageCache.cpp \
+           layer/ScrollableMagRangeCache.cpp \
            layer/SingleColourLayer.cpp \
            layer/SliceLayer.cpp \
            layer/SpectrogramLayer.cpp \