# HG changeset patch # User Chris Cannam # Date 1468999820 -3600 # Node ID 175d4e15884df680c6f4d1859d1379d5d15f4f26 # Parent 64709d4d09ef44c1e9be18d8ff5e42df4e818544 Introduce ScrollableMagRangeCache, plus some tidying etc diff -r 64709d4d09ef -r 175d4e15884d layer/Colour3DPlotRenderer.cpp --- 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); } diff -r 64709d4d09ef -r 175d4e15884d layer/ScrollableImageCache.cpp --- 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 diff -r 64709d4d09ef -r 175d4e15884d layer/ScrollableImageCache.h --- 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 /** - * 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; }; diff -r 64709d4d09ef -r 175d4e15884d layer/ScrollableMagRangeCache.cpp --- /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 +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(-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(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); + } +} + diff -r 64709d4d09ef -r 175d4e15884d layer/ScrollableMagRangeCache.h --- /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(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(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 m_ranges; + sv_frame_t m_startFrame; + int m_zoomLevel; +}; + +#endif diff -r 64709d4d09ef -r 175d4e15884d svgui.pro --- 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 \