# HG changeset patch # User Chris Cannam # Date 1454584688 0 # Node ID fccee028a522b27adf9dc7cd7ed16da364a94133 # Parent cd9e76e755bfb086bd084d44881fe0cbf2a0a5d8# Parent 25b035362c44a4a402db1cc28fee137966cba326 Merge from branch "spectrogram-minor-refactor" diff -r cd9e76e755bf -r fccee028a522 layer/Colour3DPlotLayer.cpp --- a/layer/Colour3DPlotLayer.cpp Thu Feb 04 11:17:31 2016 +0000 +++ b/layer/Colour3DPlotLayer.cpp Thu Feb 04 11:18:08 2016 +0000 @@ -25,6 +25,7 @@ #include #include #include +#include #include @@ -60,7 +61,10 @@ m_miny(0), m_maxy(0) { - + QSettings settings; + settings.beginGroup("Preferences"); + setColourMap(settings.value("colour-3d-plot-colour", ColourMapper::Green).toInt()); + settings.endGroup(); } Colour3DPlotLayer::~Colour3DPlotLayer() @@ -934,22 +938,24 @@ Profiler profiler("Colour3DPlotLayer::getColumn"); DenseThreeDimensionalModel::Column values = m_model->getColumn(col); - while (values.size() < m_model->getHeight()) values.push_back(0.f); + values.resize(m_model->getHeight(), 0.f); if (!m_normalizeColumns && !m_normalizeHybrid) return values; double colMax = 0.f, colMin = 0.f; double min = 0.f, max = 0.f; + int nv = int(values.size()); + min = m_model->getMinimumLevel(); max = m_model->getMaximumLevel(); - for (int y = 0; y < values.size(); ++y) { + for (int y = 0; y < nv; ++y) { if (y == 0 || values.at(y) > colMax) colMax = values.at(y); if (y == 0 || values.at(y) < colMin) colMin = values.at(y); } if (colMin == colMax) colMax = colMin + 1; - for (int y = 0; y < values.size(); ++y) { + for (int y = 0; y < nv; ++y) { double value = values.at(y); double norm = (value - colMin) / (colMax - colMin); @@ -960,7 +966,7 @@ if (m_normalizeHybrid && (colMax > 0.0)) { double logmax = log10(colMax); - for (int y = 0; y < values.size(); ++y) { + for (int y = 0; y < nv; ++y) { values[y] = float(values[y] * logmax); } } @@ -1132,7 +1138,7 @@ double colMax = 0.f, colMin = 0.f; for (int y = 0; y < cacheHeight; ++y) { - if (y >= values.size()) break; + if (!in_range_for(values, y)) break; if (y == 0 || values[y] > colMax) colMax = values[y]; if (y == 0 || values[y] < colMin) colMin = values[y]; } @@ -1182,7 +1188,7 @@ for (int y = 0; y < cacheHeight; ++y) { double value = min; - if (y < values.size()) { + if (in_range_for(values, y)) { value = values.at(y); } diff -r cd9e76e755bf -r fccee028a522 layer/ColourMapper.cpp --- a/layer/ColourMapper.cpp Thu Feb 04 11:17:31 2016 +0000 +++ b/layer/ColourMapper.cpp Thu Feb 04 11:18:08 2016 +0000 @@ -21,6 +21,44 @@ #include "base/Debug.h" +#include + +using namespace std; + +static vector convertStrings(const vector &strs) +{ + vector converted; + for (const auto &s: strs) converted.push_back(QColor(s)); + reverse(converted.begin(), converted.end()); + return converted; +} + +static vector ice = convertStrings({ + // Based on ColorBrewer ylGnBu + "#ffffff", "#ffff00", "#f7fcf0", "#e0f3db", "#ccebc5", "#a8ddb5", + "#7bccc4", "#4eb3d3", "#2b8cbe", "#0868ac", "#084081", "#042040" + }); + +static vector cherry = convertStrings({ + "#f7f7f7", "#fddbc7", "#f4a582", "#d6604d", "#b2182b", "#dd3497", + "#ae017e", "#7a0177", "#49006a" + }); + +static void +mapDiscrete(double norm, vector &colours, double &r, double &g, double &b) +{ + int n = int(colours.size()); + double m = norm * (n-1); + if (m >= n-1) { colours[n-1].getRgbF(&r, &g, &b, 0); return; } + if (m <= 0) { colours[0].getRgbF(&r, &g, &b, 0); return; } + int base(int(floor(m))); + double prop0 = (base + 1.0) - m, prop1 = m - base; + QColor c0(colours[base]), c1(colours[base+1]); + r = c0.redF() * prop0 + c1.redF() * prop1; + g = c0.greenF() * prop0 + c1.greenF() * prop1; + b = c0.blueF() * prop0 + c1.blueF() * prop1; +} + ColourMapper::ColourMapper(int map, double min, double max) : QObject(), m_map(map), @@ -51,12 +89,12 @@ StandardMap map = (StandardMap)n; switch (map) { - case DefaultColours: return tr("Default"); + case Green: return tr("Green"); case WhiteOnBlack: return tr("White on Black"); case BlackOnWhite: return tr("Black on White"); - case RedOnBlue: return tr("Red on Blue"); - case YellowOnBlack: return tr("Yellow on Black"); - case BlueOnBlack: return tr("Blue on Black"); + case Cherry: return tr("Cherry"); + case Wasp: return tr("Wasp"); + case Ice: return tr("Ice"); case Sunset: return tr("Sunset"); case FruitSalad: return tr("Fruit Salad"); case Banded: return tr("Banded"); @@ -85,7 +123,7 @@ switch (map) { - case DefaultColours: + case Green: h = blue - norm * 2.0 * pieslice; s = 0.5f + norm/2.0; v = norm; @@ -101,30 +139,17 @@ hsv = false; break; - case RedOnBlue: - h = blue - pieslice/4.0 + norm * (pieslice + pieslice/4.0); - s = 1.0; - v = norm; + case Cherry: + hsv = false; + mapDiscrete(norm, cherry, r, g, b); break; - case YellowOnBlack: + case Wasp: h = 0.15; s = 1.0; v = norm; break; - case BlueOnBlack: - h = blue; - s = 1.0; - v = norm * 2.0; - if (v > 1.0) { - v = 1.0; - s = 1.0 - (sqrt(norm) - 0.707) * 3.413; - if (s < 0.0) s = 0.0; - if (s > 1.0) s = 1.0; - } - break; - case Sunset: r = (norm - 0.24) * 2.38; if (r > 1.0) r = 1.0; @@ -207,6 +232,10 @@ hsv = false; */ break; + + case Ice: + hsv = false; + mapDiscrete(norm, ice, r, g, b); } if (hsv) { @@ -224,7 +253,7 @@ switch (map) { - case DefaultColours: + case Green: return QColor(255, 150, 50); case WhiteOnBlack: @@ -233,13 +262,13 @@ case BlackOnWhite: return Qt::darkGreen; - case RedOnBlue: + case Cherry: return Qt::green; - case YellowOnBlack: + case Wasp: return QColor::fromHsv(240, 255, 255); - case BlueOnBlack: + case Ice: return Qt::red; case Sunset: @@ -277,12 +306,12 @@ case HighGain: return true; - case DefaultColours: + case Green: case Sunset: case WhiteOnBlack: - case RedOnBlue: - case YellowOnBlack: - case BlueOnBlack: + case Cherry: + case Wasp: + case Ice: case FruitSalad: case Banded: case Highlight: diff -r cd9e76e755bf -r fccee028a522 layer/ColourMapper.h --- a/layer/ColourMapper.h Thu Feb 04 11:17:31 2016 +0000 +++ b/layer/ColourMapper.h Thu Feb 04 11:18:08 2016 +0000 @@ -33,13 +33,13 @@ virtual ~ColourMapper(); enum StandardMap { - DefaultColours, + Green, Sunset, WhiteOnBlack, BlackOnWhite, - RedOnBlue, - YellowOnBlack, - BlueOnBlack, + Cherry, + Wasp, + Ice, FruitSalad, Banded, Highlight, diff -r cd9e76e755bf -r fccee028a522 layer/ScrollableImageCache.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layer/ScrollableImageCache.cpp Thu Feb 04 11:18:08 2016 +0000 @@ -0,0 +1,206 @@ +/* -*- 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 "ScrollableImageCache.h" + +#include +using namespace std; + +//#define DEBUG_SCROLLABLE_IMAGE_CACHE 1 + +void +ScrollableImageCache::scrollTo(sv_frame_t newStartFrame) +{ + if (!m_v) throw std::logic_error("ScrollableImageCache: not associated with a LayerGeometryProvider"); + + int dx = (m_v->getXForFrame(m_startFrame) - + m_v->getXForFrame(newStartFrame)); + +#ifdef DEBUG_SCROLLABLE_IMAGE_CACHE + cerr << "ScrollableImageCache::scrollTo: start frame " << m_startFrame + << " -> " << newStartFrame << ", dx = " << dx << endl; +#endif + + m_startFrame = newStartFrame; + + if (!isValid()) { + return; + } + + int w = m_image.width(); + + if (dx == 0) { + // haven't moved + return; + } + + if (dx <= -w || dx >= w) { + // scrolled entirely off + invalidate(); + return; + } + + // dx is in range, cache is scrollable + + int dxp = dx; + if (dxp < 0) dxp = -dxp; + + int copylen = (w - dxp) * int(sizeof(QRgb)); + for (int y = 0; y < m_image.height(); ++y) { + QRgb *line = (QRgb *)m_image.scanLine(y); + if (dx < 0) { + memmove(line, line + dxp, copylen); + } else { + memmove(line + dxp, line, copylen); + } + } + + // update valid area + + int px = m_left; + int pw = m_width; + + px += dx; + + if (dx < 0) { + // we scrolled left + if (px < 0) { + pw += px; + px = 0; + if (pw < 0) { + pw = 0; + } + } + } else { + // we scrolled right + if (px + pw > w) { + pw = w - px; + if (pw < 0) { + pw = 0; + } + } + } + + m_left = px; + m_width = pw; +} + +void +ScrollableImageCache::adjustToTouchValidArea(int &left, int &width, + bool &isLeftOfValidArea) const +{ +#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; +#endif + if (left < m_left) { + isLeftOfValidArea = true; + if (left + width <= m_left + m_width) { + width = m_left - 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; + 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; +#endif + } +} + +void +ScrollableImageCache::drawImage(int left, + int width, + QImage image, + int imageLeft, + int imageWidth) +{ + if (image.height() != m_image.height()) { + cerr << "ScrollableImageCache::drawImage: ERROR: Supplied image height " + << image.height() << " does not match cache height " + << m_image.height() << endl; + throw std::logic_error("Image height must match cache height in ScrollableImageCache::drawImage"); + } + if (left < 0 || width < 0 || left + width > m_image.width()) { + cerr << "ScrollableImageCache::drawImage: ERROR: Target area (left = " + << left << ", width = " << width << ", so right = " << left + width + << ") out of bounds for cache of width " << m_image.width() << endl; + throw std::logic_error("Target area out of bounds in ScrollableImageCache::drawImage"); + } + if (imageLeft < 0 || imageWidth < 0 || + imageLeft + imageWidth > image.width()) { + cerr << "ScrollableImageCache::drawImage: ERROR: Source area (left = " + << imageLeft << ", width = " << imageWidth << ", so right = " + << imageLeft + imageWidth << ") out of bounds for image of " + << "width " << image.width() << endl; + throw std::logic_error("Source area out of bounds in ScrollableImageCache::drawImage"); + } + + QPainter painter(&m_image); + painter.drawImage(QRect(left, 0, width, m_image.height()), + image, + QRect(imageLeft, 0, imageWidth, image.height())); + painter.end(); + + if (!isValid()) { + m_left = left; + m_width = width; + return; + } + + if (left < m_left) { + if (left + width > m_left + m_width) { + // 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) { + // 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; + } 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; + } + } else { + if (left > m_left + m_width) { + // 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) { + // 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) + } else { + // new image completely contained within old valid + // area -- leave the old area unchanged + } + } +} + diff -r cd9e76e755bf -r fccee028a522 layer/ScrollableImageCache.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layer/ScrollableImageCache.h Thu Feb 04 11:18:08 2016 +0000 @@ -0,0 +1,145 @@ +/* -*- 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_IMAGE_CACHE_H +#define SCROLLABLE_IMAGE_CACHE_H + +#include "base/BaseTypes.h" + +#include "view/LayerGeometryProvider.h" + +#include +#include +#include + +/** + * A cached image for a view that scrolls horizontally, primarily the + * 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 + * the image, and can report and update which contiguous horizontal + * range of the image is valid. + * + * The only way to *update* the valid area in a cache is to draw to it + * using the drawImage call. + */ +class ScrollableImageCache +{ +public: + ScrollableImageCache(const LayerGeometryProvider *v = 0) : + m_v(v), + m_left(0), + m_width(0), + m_startFrame(0), + m_zoomLevel(0) + {} + + void invalidate() { + m_width = 0; + } + + bool isValid() const { + return m_width > 0; + } + + QSize getSize() const { + return m_image.size(); + } + + void resize(QSize newSize) { + m_image = QImage(newSize, QImage::Format_ARGB32_Premultiplied); + invalidate(); + } + + int getValidLeft() const { + return m_left; + } + + int getValidWidth() const { + return m_width; + } + + int getValidRight() const { + return m_left + m_width; + } + + QRect getValidArea() const { + return QRect(m_left, 0, m_width, m_image.height()); + } + + int getZoomLevel() const { + return m_zoomLevel; + } + + void setZoomLevel(int zoom) { + m_zoomLevel = zoom; + invalidate(); + } + + sv_frame_t getStartFrame() const { + return m_startFrame; + } + + /** + * 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. + */ + void setStartFrame(sv_frame_t frame) { + m_startFrame = frame; + invalidate(); + } + + const QImage &getImage() const { + return m_image; + } + + /** + * Set the new start frame for the cache, 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(sv_frame_t newStartFrame); + + /** + * Take a left coordinate and width describing a region, and + * adjust them so that they are contiguous with the cache valid + * region and so that the union of the adjusted region with the + * cache valid region contains the supplied region. + */ + void adjustToTouchValidArea(int &left, int &width, + bool &isLeftOfValidArea) const; + /** + * Draw from an image onto the cache. The supplied image must have + * the same height as the cache and the full height is always + * drawn. The left and width parameters determine the target + * region of the cache, the imageLeft and imageWidth parameters + * the source region of the image. + */ + void drawImage(int left, + int width, + QImage image, + int imageLeft, + int imageWidth); + +private: + const LayerGeometryProvider *m_v; + QImage m_image; + int m_left; // of valid region + int m_width; // of valid region + sv_frame_t m_startFrame; + int m_zoomLevel; +}; + +#endif diff -r cd9e76e755bf -r fccee028a522 layer/SpectrogramLayer.cpp --- a/layer/SpectrogramLayer.cpp Thu Feb 04 11:17:31 2016 +0000 +++ b/layer/SpectrogramLayer.cpp Thu Feb 04 11:18:08 2016 +0000 @@ -33,11 +33,11 @@ #include #include #include -#include #include #include #include #include +#include #include @@ -50,7 +50,7 @@ //#define DEBUG_SPECTROGRAM_REPAINT 1 -using std::vector; +using namespace std; SpectrogramLayer::SpectrogramLayer(Configuration config) : m_model(0), @@ -77,10 +77,12 @@ m_lastEmittedZoomStep(-1), m_synchronous(false), m_haveDetailedScale(false), - m_lastPaintBlockWidth(0), m_exiting(false), m_sliceableModel(0) { + QString colourConfigName = "spectrogram-colour"; + int colourConfigDefault = int(ColourMapper::Green); + if (config == FullRangeDb) { m_initialMaxFrequency = 0; setMaxFrequency(0); @@ -93,6 +95,8 @@ setColourScale(LinearColourScale); setColourMap(ColourMapper::Sunset); setFrequencyScale(LogFrequencyScale); + colourConfigName = "spectrogram-melodic-colour"; + colourConfigDefault = int(ColourMapper::Sunset); // setGain(20); } else if (config == MelodicPeaks) { setWindowSize(4096); @@ -104,8 +108,15 @@ setColourScale(LinearColourScale); setBinDisplay(PeakFrequencies); setNormalization(NormalizeColumns); + colourConfigName = "spectrogram-melodic-colour"; + colourConfigDefault = int(ColourMapper::Sunset); } + QSettings settings; + settings.beginGroup("Preferences"); + setColourMap(settings.value(colourConfigName, colourConfigDefault).toInt()); + settings.endGroup(); + Preferences *prefs = Preferences::getInstance(); connect(prefs, SIGNAL(propertyChanged(PropertyContainer::PropertyName)), this, SLOT(preferenceChanged(PropertyContainer::PropertyName))); @@ -573,74 +584,7 @@ { for (ViewImageCache::iterator i = m_imageCaches.begin(); i != m_imageCaches.end(); ++i) { - i->second.validArea = QRect(); - } -} - -void -SpectrogramLayer::invalidateImageCaches(sv_frame_t startFrame, sv_frame_t endFrame) -{ - for (ViewImageCache::iterator i = m_imageCaches.begin(); - i != m_imageCaches.end(); ++i) { - - //!!! when are views removed from the map? on setLayerDormant? - const LayerGeometryProvider *v = i->first; - -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "SpectrogramLayer::invalidateImageCaches(" - << startFrame << ", " << endFrame << "): view range is " - << v->getStartFrame() << ", " << v->getEndFrame() - << endl; - - cerr << "Valid area was: " << i->second.validArea.x() << ", " - << i->second.validArea.y() << " " - << i->second.validArea.width() << "x" - << i->second.validArea.height() << endl; -#endif - - if (int(startFrame) > v->getStartFrame()) { - if (startFrame >= v->getEndFrame()) { -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "Modified start frame is off right of view" << endl; -#endif - return; - } - int x = v->getXForFrame(startFrame); -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "clipping from 0 to " << x-1 << endl; -#endif - if (x > 1) { - i->second.validArea &= - QRect(0, 0, x-1, v->getPaintHeight()); - } else { - i->second.validArea = QRect(); - } - } else { - if (int(endFrame) < v->getStartFrame()) { -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "Modified end frame is off left of view" << endl; -#endif - return; - } - int x = v->getXForFrame(endFrame); -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "clipping from " << x+1 << " to " << v->getPaintWidth() - << endl; -#endif - if (x < v->getPaintWidth()) { - i->second.validArea &= - QRect(x+1, 0, v->getPaintWidth()-(x+1), v->getPaintHeight()); - } else { - i->second.validArea = QRect(); - } - } - -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "Valid area is now: " << i->second.validArea.x() << ", " - << i->second.validArea.y() << " " - << i->second.validArea.width() << "x" - << i->second.validArea.height() << endl; -#endif + i->second.invalidate(); } } @@ -977,11 +921,11 @@ invalidateImageCaches(); - m_imageCaches.erase(view); - - if (m_fftModels.find(view) != m_fftModels.end()) { - - if (m_sliceableModel == m_fftModels[view]) { + m_imageCaches.erase(view->getId()); + + if (m_fftModels.find(view->getId()) != m_fftModels.end()) { + + if (m_sliceableModel == m_fftModels[view->getId()]) { bool replaced = false; for (ViewFFTMap::iterator i = m_fftModels.begin(); i != m_fftModels.end(); ++i) { @@ -994,11 +938,11 @@ if (!replaced) emit sliceableModelReplaced(m_sliceableModel, 0); } - delete m_fftModels[view]; - m_fftModels.erase(view); - - delete m_peakCaches[view]; - m_peakCaches.erase(view); + delete m_fftModels[view->getId()]; + m_fftModels.erase(view->getId()); + + delete m_peakCaches[view->getId()]; + m_peakCaches.erase(view->getId()); } } else { @@ -1019,13 +963,25 @@ } void -SpectrogramLayer::cacheInvalid(sv_frame_t from, sv_frame_t to) +SpectrogramLayer::cacheInvalid( +#ifdef DEBUG_SPECTROGRAM_REPAINT + sv_frame_t from, sv_frame_t to +#else + sv_frame_t , sv_frame_t +#endif + ) { #ifdef DEBUG_SPECTROGRAM_REPAINT cerr << "SpectrogramLayer::cacheInvalid(" << from << ", " << to << ")" << endl; #endif - invalidateImageCaches(from, to); + // We used to call invalidateMagnitudes(from, to) to invalidate + // only those caches whose views contained some of the (from, to) + // range. That's the right thing to do; it has been lost in + // pulling out the image cache code, but it might not matter very + // much, since the underlying models for spectrogram layers don't + // change very often. Let's see. + invalidateImageCaches(); invalidateMagnitudes(); } @@ -1091,8 +1047,8 @@ double max = 1.0; if (m_normalization == NormalizeVisibleArea) { - min = m_viewMags[v].getMin(); - max = m_viewMags[v].getMax(); + min = m_viewMags[v->getId()].getMin(); + max = m_viewMags[v->getId()].getMax(); } else if (m_normalization != NormalizeColumns) { if (m_colourScale == LinearColourScale //|| // m_colourScale == MeterColourScale) { @@ -1345,8 +1301,6 @@ for (int s = s0i; s <= s1i; ++s) { - if (!fft->isColumnAvailable(s)) continue; - double binfreq = (double(sr) * q) / m_windowSize; if (q == q0i) freqMin = binfreq; if (q == q1i) freqMax = binfreq; @@ -1420,8 +1374,6 @@ for (int s = s0i; s <= s1i; ++s) { if (s >= 0 && q >= 0 && s < cw && q < ch) { - if (!fft->isColumnAvailable(s)) continue; - double value; value = fft->getPhaseAt(s, q); @@ -1503,30 +1455,30 @@ const View *view = v->getView(); - if (m_fftModels.find(view) != m_fftModels.end()) { - if (m_fftModels[view] == 0) { + if (m_fftModels.find(view->getId()) != m_fftModels.end()) { + if (m_fftModels[view->getId()] == 0) { #ifdef DEBUG_SPECTROGRAM_REPAINT cerr << "SpectrogramLayer::getFFTModel(" << v << "): Found null model" << endl; #endif return 0; } - if (m_fftModels[view]->getHeight() != fftSize / 2 + 1) { + if (m_fftModels[view->getId()]->getHeight() != fftSize / 2 + 1) { #ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "SpectrogramLayer::getFFTModel(" << v << "): Found a model with the wrong height (" << m_fftModels[view]->getHeight() << ", wanted " << (fftSize / 2 + 1) << ")" << endl; + cerr << "SpectrogramLayer::getFFTModel(" << v << "): Found a model with the wrong height (" << m_fftModels[view->getId()]->getHeight() << ", wanted " << (fftSize / 2 + 1) << ")" << endl; #endif - delete m_fftModels[view]; - m_fftModels.erase(view); - delete m_peakCaches[view]; - m_peakCaches.erase(view); + delete m_fftModels[view->getId()]; + m_fftModels.erase(view->getId()); + delete m_peakCaches[view->getId()]; + m_peakCaches.erase(view->getId()); } else { #ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "SpectrogramLayer::getFFTModel(" << v << "): Found a good model of height " << m_fftModels[view]->getHeight() << endl; + cerr << "SpectrogramLayer::getFFTModel(" << v << "): Found a good model of height " << m_fftModels[view->getId()]->getHeight() << endl; #endif - return m_fftModels[view]; + return m_fftModels[view->getId()]; } } - if (m_fftModels.find(view) == m_fftModels.end()) { + if (m_fftModels.find(view->getId()) == m_fftModels.end()) { FFTModel *model = new FFTModel(m_model, m_channel, @@ -1541,7 +1493,7 @@ tr("Failed to create the FFT model for this spectrogram.\n" "There may be insufficient memory or disc space to continue.")); delete model; - m_fftModels[view] = 0; + m_fftModels[view->getId()] = 0; return 0; } @@ -1553,22 +1505,22 @@ m_sliceableModel = model; } - m_fftModels[view] = model; + m_fftModels[view->getId()] = model; } - return m_fftModels[view]; + return m_fftModels[view->getId()]; } Dense3DModelPeakCache * SpectrogramLayer::getPeakCache(const LayerGeometryProvider *v) const { const View *view = v->getView(); - if (!m_peakCaches[view]) { + if (!m_peakCaches[view->getId()]) { FFTModel *f = getFFTModel(v); if (!f) return 0; - m_peakCaches[view] = new Dense3DModelPeakCache(f, 8); + m_peakCaches[view->getId()] = new Dense3DModelPeakCache(f, 8); } - return m_peakCaches[view]; + return m_peakCaches[view->getId()]; } const Model * @@ -1606,7 +1558,7 @@ SpectrogramLayer::invalidateMagnitudes() { m_viewMags.clear(); - for (std::vector::iterator i = m_columnMags.begin(); + for (vector::iterator i = m_columnMags.begin(); i != m_columnMags.end(); ++i) { *i = MagnitudeRange(); } @@ -1628,8 +1580,8 @@ s10 = s11 = double(m_model->getEndFrame()) / getWindowIncrement(); } - int s0 = int(std::min(s00, s10) + 0.0001); - int s1 = int(std::max(s01, s11) + 0.0001); + int s0 = int(min(s00, s10) + 0.0001); + int s1 = int(max(s01, s11) + 0.0001); // SVDEBUG << "SpectrogramLayer::updateViewMagnitudes: x0 = " << x0 << ", x1 = " << x1 << ", s00 = " << s00 << ", s11 = " << s11 << " s0 = " << s0 << ", s1 = " << s1 << endl; @@ -1649,8 +1601,8 @@ #endif if (!mag.isSet()) return false; - if (mag == m_viewMags[v]) return false; - m_viewMags[v] = mag; + if (mag == m_viewMags[v->getId()]) return false; + m_viewMags[v->getId()] = mag; return true; } @@ -1660,18 +1612,24 @@ m_synchronous = synchronous; } +ScrollableImageCache & +SpectrogramLayer::getImageCacheReference(const LayerGeometryProvider *view) const +{ + if (m_imageCaches.find(view->getId()) == m_imageCaches.end()) { + m_imageCaches[view->getId()] = ScrollableImageCache(view); + } + return m_imageCaches.at(view->getId()); +} + void SpectrogramLayer::paint(LayerGeometryProvider *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", false); #ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "SpectrogramLayer::paint(): m_model is " << m_model << ", zoom level is " << v->getZoomLevel() << endl; + cerr << "SpectrogramLayer::paint() entering: m_model is " << m_model << ", zoom level is " << v->getZoomLevel() << endl; - cerr << "rect is " << rect.x() << "," << rect.y() << " " << rect.width() << "x" << rect.height() << endl; + cerr << "SpectrogramLayer::paint(): rect is " << rect.x() << "," << rect.y() << " " << rect.width() << "x" << rect.height() << endl; #endif sv_frame_t startFrame = v->getStartFrame(); @@ -1692,308 +1650,135 @@ const_cast(this)->Layer::setLayerDormant(v, false); int fftSize = getFFTSize(v); -/* - FFTModel *fft = getFFTModel(v); - if (!fft) { - cerr << "ERROR: SpectrogramLayer::paint(): No FFT model, returning" << endl; - return; + + const View *view = v->getView(); + ScrollableImageCache &cache = getImageCacheReference(view); + +#ifdef DEBUG_SPECTROGRAM_REPAINT + cerr << "SpectrogramLayer::paint(): image cache valid area from " << cache.getValidLeft() << " width " << cache.getValidWidth() << ", height " << cache.getSize().height() << endl; + if (rect.x() + rect.width() + 1 < cache.getValidLeft() || + rect.x() > cache.getValidRight()) { + cerr << "SpectrogramLayer: NOTE: requested rect is not contiguous with cache valid area" << endl; } -*/ - - const View *view = v->getView(); - - ImageCache &cache = m_imageCaches[view]; - -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "SpectrogramLayer::paint(): image cache valid area " << cache. - -validArea.x() << ", " << cache.validArea.y() << ", " << cache.validArea.width() << "x" << cache.validArea.height() << endl; #endif int zoomLevel = v->getZoomLevel(); - int x0 = 0; - int x1 = v->getPaintWidth(); - - bool recreateWholeImageCache = true; - - x0 = rect.left(); - x1 = rect.right() + 1; -/* - double xPixelRatio = double(fft->getResolution()) / double(zoomLevel); - cerr << "xPixelRatio = " << xPixelRatio << endl; - if (xPixelRatio < 1.f) xPixelRatio = 1.f; -*/ - if (cache.validArea.width() > 0) { - - int cw = cache.image.width(); - int ch = cache.image.height(); - - if (int(cache.zoomLevel) == zoomLevel && - cw == v->getPaintWidth() && - ch == v->getPaintHeight()) { - - if (v->getXForFrame(cache.startFrame) == - v->getXForFrame(startFrame) && - cache.validArea.x() <= x0 && - cache.validArea.x() + cache.validArea.width() >= x1) { - -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "SpectrogramLayer: image cache good" << endl; -#endif - - paint.drawImage(rect, cache.image, rect); - //!!! -// paint.drawImage(v->rect(), cache.image, -// QRect(QPoint(0, 0), cache.image.size())); - - illuminateLocalFeatures(v, paint); - return; - - } else { - -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "SpectrogramLayer: image cache partially OK" << endl; -#endif - - recreateWholeImageCache = false; - - int dx = v->getXForFrame(cache.startFrame) - - v->getXForFrame(startFrame); - -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "SpectrogramLayer: dx = " << dx << " (image cache " << cw << "x" << ch << ")" << endl; -#endif - - if (dx != 0 && - dx > -cw && - dx < cw) { - - int dxp = dx; - if (dxp < 0) dxp = -dxp; - size_t copy = (cw - dxp) * sizeof(QRgb); - for (int y = 0; y < ch; ++y) { - QRgb *line = (QRgb *)cache.image.scanLine(y); - if (dx < 0) { - memmove(line, line + dxp, copy); - } else { - memmove(line + dxp, line, copy); - } - } - - int px = cache.validArea.x(); - int pw = cache.validArea.width(); - - if (dx < 0) { - x0 = cw + dx; - x1 = cw; - px += dx; - if (px < 0) { - pw += px; - px = 0; - if (pw < 0) pw = 0; - } - } else { - x0 = 0; - x1 = dx; - px += dx; - if (px + pw > cw) { - pw = int(cw) - px; - if (pw < 0) pw = 0; - } - } - - cache.validArea = - QRect(px, cache.validArea.y(), - pw, cache.validArea.height()); - -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "valid area now " - << px << "," << cache.validArea.y() - << " " << pw << "x" << cache.validArea.height() - << endl; -#endif -/* - paint.drawImage(rect & cache.validArea, - cache.image, - rect & cache.validArea); -*/ - } else if (dx != 0) { - - // we scrolled too far to be of use - -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "dx == " << dx << ": scrolled too far for cache to be useful" << endl; -#endif - - cache.validArea = QRect(); - recreateWholeImageCache = true; - } - } - } else { -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "SpectrogramLayer: image cache useless" << endl; - if (int(cache.zoomLevel) != zoomLevel) { - cerr << "(cache zoomLevel " << cache.zoomLevel - << " != " << zoomLevel << ")" << endl; - } - if (cw != v->getPaintWidth()) { - cerr << "(cache width " << cw - << " != " << v->getPaintWidth(); - } - if (ch != v->getPaintHeight()) { - cerr << "(cache height " << ch - << " != " << v->getPaintHeight(); - } -#endif - cache.validArea = QRect(); -// recreateWholeImageCache = true; - } - } + int x0 = v->getXForViewX(rect.x()); + int x1 = v->getXForViewX(rect.x() + rect.width()); + if (x0 < 0) x0 = 0; + if (x1 > v->getPaintWidth()) x1 = v->getPaintWidth(); if (updateViewMagnitudes(v)) { #ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "SpectrogramLayer: magnitude range changed to [" << m_viewMags[v].getMin() << "->" << m_viewMags[v].getMax() << "]" << endl; + cerr << "SpectrogramLayer: magnitude range changed to [" << m_viewMags[v->getId()].getMin() << "->" << m_viewMags[v->getId()].getMax() << "]" << endl; #endif if (m_normalization == NormalizeVisibleArea) { - cache.validArea = QRect(); - recreateWholeImageCache = true; + cache.invalidate(); + } + } + + if (cache.getZoomLevel() != zoomLevel || + cache.getSize() != v->getPaintSize()) { +#ifdef DEBUG_SPECTROGRAM_REPAINT + cerr << "SpectrogramLayer: resizing image cache from " + << cache.getSize().width() << "x" << cache.getSize().height() + << " to " + << v->getPaintSize().width() << "x" << v->getPaintSize().height() + << " and updating zoom level from " << cache.getZoomLevel() + << " to " << zoomLevel + << endl; +#endif + cache.resize(v->getPaintSize()); + cache.setZoomLevel(zoomLevel); + cache.setStartFrame(startFrame); + } + + if (cache.isValid()) { + + if (v->getXForFrame(cache.getStartFrame()) == + v->getXForFrame(startFrame) && + cache.getValidLeft() <= x0 && + cache.getValidRight() >= x1) { + +#ifdef DEBUG_SPECTROGRAM_REPAINT + cerr << "SpectrogramLayer: image cache hit!" << endl; +#endif + + paint.drawImage(rect, cache.getImage(), rect); + + illuminateLocalFeatures(v, paint); + return; + + } else { + + // cache doesn't begin at the right frame or doesn't + // contain the complete view, but might be scrollable or + // partially usable + +#ifdef DEBUG_SPECTROGRAM_REPAINT + cerr << "SpectrogramLayer: scrolling the image cache if applicable" << endl; +#endif + + cache.scrollTo(startFrame); + +#ifdef DEBUG_SPECTROGRAM_REPAINT + cerr << "SpectrogramLayer: after scrolling, cache valid from " + << cache.getValidLeft() << " width " << cache.getValidWidth() + << endl; +#endif + } + } + + bool rightToLeft = false; + + if (!cache.isValid()) { + if (!m_synchronous) { + // When rendering the whole thing, start from somewhere near + // the middle so that the region of interest appears first + + //!!! (perhaps we should have some cunning test to avoid + //!!! doing this if past repaints have appeared fast + //!!! enough to do the whole width in one shot) + if (x0 == 0 && x1 == v->getPaintWidth()) { + x0 = int(x1 * 0.3); + } } } else { -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "No change in magnitude range [" << m_viewMags[v].getMin() << "->" << m_viewMags[v].getMax() << "]" << endl; -#endif + // When rendering only a part of the cache, we need to make + // sure that the part we're rendering is adjacent to (or + // overlapping) a valid area of cache, if we have one. The + // alternative is to ditch the valid area of cache and render + // only the requested area, but that's risky because this can + // happen when just waving the pointer over a small part of + // the view -- if we lose the partly-built cache every time + // the user does that, we'll never finish building it. + int left = x0; + int width = x1 - x0; + bool isLeftOfValidArea = false; + cache.adjustToTouchValidArea(left, width, isLeftOfValidArea); + x0 = left; + x1 = x0 + width; + + // That call also told us whether we should be painting + // sub-regions of our target region in right-to-left order in + // order to ensure contiguity + rightToLeft = isLeftOfValidArea; } - - if (recreateWholeImageCache) { - x0 = 0; - x1 = v->getPaintWidth(); - } - - struct timeval tv; - (void)gettimeofday(&tv, 0); - RealTime mainPaintStart = RealTime::fromTimeval(tv); - - int paintBlockWidth = m_lastPaintBlockWidth; - - if (m_synchronous) { - if (paintBlockWidth < x1 - x0) { - // always paint full width - paintBlockWidth = x1 - x0; - } - } else { - if (paintBlockWidth == 0) { - paintBlockWidth = (300000 / zoomLevel); - } else { - RealTime lastTime = m_lastPaintTime; - while (lastTime > RealTime::fromMilliseconds(200) && - paintBlockWidth > 100) { - paintBlockWidth /= 2; - lastTime = lastTime / 2; - } - while (lastTime < RealTime::fromMilliseconds(90) && - paintBlockWidth < 1500) { - paintBlockWidth *= 2; - lastTime = lastTime * 2; - } - } - - if (paintBlockWidth < 50) paintBlockWidth = 50; - } - -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "[" << this << "]: last paint width: " << m_lastPaintBlockWidth << ", last paint time: " << m_lastPaintTime << ", new paint width: " << paintBlockWidth << endl; -#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. - int h = v->getPaintHeight(); - - if (cache.validArea.width() > 0) { - - // If part of the cache is known to be valid, select a strip - // immediately to left or right of the valid part - - //!!! this really needs to be coordinated with the selection - //!!! of m_drawBuffer boundaries in the bufferBinResolution - //!!! case below - - int vx0 = 0, vx1 = 0; - vx0 = cache.validArea.x(); - vx1 = cache.validArea.x() + cache.validArea.width(); - + + int repaintWidth = x1 - x0; + #ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "x0 " << x0 << ", x1 " << x1 << ", vx0 " << vx0 << ", vx1 " << vx1 << ", paintBlockWidth " << paintBlockWidth << endl; -#endif - if (x0 < vx0) { - if (x0 + paintBlockWidth < vx0) { - x0 = vx0 - paintBlockWidth; - } - x1 = vx0; - } else if (x0 >= vx1) { - x0 = vx1; - if (x1 > x0 + paintBlockWidth) { - x1 = x0 + paintBlockWidth; - } - } else { - // x0 is within the valid area - if (x1 > vx1) { - x0 = vx1; - if (x0 + paintBlockWidth < x1) { - x1 = x0 + paintBlockWidth; - } - } else { - x1 = x0; // it's all valid, paint nothing - } - } - - cache.validArea = QRect - (std::min(vx0, x0), cache.validArea.y(), - std::max(vx1 - std::min(vx0, x0), - x1 - std::min(vx0, x0)), - cache.validArea.height()); - -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "Valid area becomes " << cache.validArea.x() - << ", " << cache.validArea.y() << ", " - << cache.validArea.width() << "x" - << cache.validArea.height() << endl; -#endif - - } else { - if (x1 > x0 + paintBlockWidth) { - int sfx = x1; - if (startFrame < 0) sfx = v->getXForFrame(0); - if (sfx >= x0 && sfx + paintBlockWidth <= x1) { - x0 = sfx; - x1 = x0 + paintBlockWidth; - } else { - int mid = (x1 + x0) / 2; - x0 = mid - paintBlockWidth/2; - x1 = x0 + paintBlockWidth; - } - } -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "Valid area becomes " << x0 << ", 0, " << (x1-x0) - << "x" << h << endl; -#endif - cache.validArea = QRect(x0, 0, x1 - x0, h); - } - -/* - if (xPixelRatio != 1.f) { - x0 = int((int(x0 / xPixelRatio) - 4) * xPixelRatio + 0.0001); - x1 = int((int(x1 / xPixelRatio) + 4) * xPixelRatio + 0.0001); - } -*/ - int w = x1 - x0; - -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "x0 " << x0 << ", x1 " << x1 << ", w " << w << ", h " << h << endl; + cerr << "SpectrogramLayer: x0 " << x0 << ", x1 " << x1 + << ", repaintWidth " << repaintWidth << ", h " << h + << ", rightToLeft " << rightToLeft << endl; #endif sv_samplerate_t sr = m_model->getSampleRate(); @@ -2043,25 +1828,16 @@ int increment = getWindowIncrement(); bool logarithmic = (m_frequencyScale == LogFrequencyScale); -/* - double yforbin[maxbin - minbin + 1]; - - for (int q = minbin; q <= maxbin; ++q) { - double f0 = (double(q) * sr) / fftSize; - yforbin[q - minbin] = - v->getYForFrequency(f0, displayMinFreq, displayMaxFreq, - logarithmic); - } -*/ - MagnitudeRange overallMag = m_viewMags[v]; + + MagnitudeRange overallMag = m_viewMags[v->getId()]; bool overallMagChanged = false; #ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << ((double(v->getFrameForX(1) - v->getFrameForX(0))) / increment) << " bin(s) per pixel" << endl; + cerr << "SpectrogramLayer: " << ((double(v->getFrameForX(1) - v->getFrameForX(0))) / increment) << " bin(s) per pixel" << endl; #endif - if (w == 0) { - SVDEBUG << "*** NOTE: w == 0" << endl; + if (repaintWidth == 0) { + SVDEBUG << "*** NOTE: repaintWidth == 0" << endl; } Profiler outerprof("SpectrogramLayer::paint: all cols"); @@ -2079,28 +1855,34 @@ // such boundaries at either side of the draw buffer -- one which // we draw up to, and one which we subsequently crop at. - bool bufferBinResolution = false; - if (increment > zoomLevel) bufferBinResolution = true; + bool bufferIsBinResolution = false; + if (increment > zoomLevel) bufferIsBinResolution = true; sv_frame_t leftBoundaryFrame = -1, leftCropFrame = -1; sv_frame_t rightBoundaryFrame = -1, rightCropFrame = -1; int bufwid; - if (bufferBinResolution) { + if (bufferIsBinResolution) { for (int x = x0; ; --x) { sv_frame_t f = v->getFrameForX(x); if ((f / increment) * increment == f) { if (leftCropFrame == -1) leftCropFrame = f; - else if (x < x0 - 2) { leftBoundaryFrame = f; break; } + else if (x < x0 - 2) { + leftBoundaryFrame = f; + break; + } } } - for (int x = x0 + w; ; ++x) { + for (int x = x0 + repaintWidth; ; ++x) { sv_frame_t f = v->getFrameForX(x); if ((f / increment) * increment == f) { if (rightCropFrame == -1) rightCropFrame = f; - else if (x > x0 + w + 2) { rightBoundaryFrame = f; break; } + else if (x > x0 + repaintWidth + 2) { + rightBoundaryFrame = f; + break; + } } } #ifdef DEBUG_SPECTROGRAM_REPAINT @@ -2112,7 +1894,7 @@ } else { - bufwid = w; + bufwid = repaintWidth; } vector binforx(bufwid); @@ -2120,10 +1902,9 @@ bool usePeaksCache = false; - if (bufferBinResolution) { + if (bufferIsBinResolution) { for (int x = 0; x < bufwid; ++x) { binforx[x] = int(leftBoundaryFrame / increment) + x; -// cerr << "binforx[" << x << "] = " << binforx[x] << endl; } m_drawBuffer = QImage(bufwid, h, QImage::Format_Indexed8); } else { @@ -2135,21 +1916,47 @@ binforx[x] = -1; //??? } } - if (m_drawBuffer.width() < bufwid || m_drawBuffer.height() < h) { + if (m_drawBuffer.width() < bufwid || m_drawBuffer.height() != h) { m_drawBuffer = QImage(bufwid, h, QImage::Format_Indexed8); } usePeaksCache = (increment * 8) < zoomLevel; if (m_colourScale == PhaseColourScale) usePeaksCache = false; } -// No longer exists in Qt5: m_drawBuffer.setNumColors(256); for (int pixel = 0; pixel < 256; ++pixel) { m_drawBuffer.setColor((unsigned char)pixel, m_palette.getColour((unsigned char)pixel).rgb()); } m_drawBuffer.fill(0); - + int attainedBufwid = bufwid; + + double softTimeLimit; + + if (m_synchronous) { + + // must paint the whole thing for synchronous mode, so give + // "no timeout" + softTimeLimit = 0.0; + + } else if (bufferIsBinResolution) { + + // calculating boundaries later will be too fiddly for partial + // paints, and painting should be fast anyway when this is the + // case because it means we're well zoomed in + softTimeLimit = 0.0; + + } else { + + // neither limitation applies, so use a short soft limit + + if (m_binDisplay == PeakFrequencies) { + softTimeLimit = 0.15; + } else { + softTimeLimit = 0.1; + } + } + if (m_binDisplay != PeakFrequencies) { for (int y = 0; y < h; ++y) { @@ -2158,53 +1965,60 @@ binfory[y] = -1; } else { binfory[y] = q0; -// cerr << "binfory[" << y << "] = " << binfory[y] << endl; } } - paintDrawBuffer(v, bufwid, h, binforx, binfory, usePeaksCache, - overallMag, overallMagChanged); + attainedBufwid = + paintDrawBuffer(v, bufwid, h, binforx, binfory, + usePeaksCache, + overallMag, overallMagChanged, + rightToLeft, + softTimeLimit); } else { - paintDrawBufferPeakFrequencies(v, bufwid, h, binforx, - minbin, maxbin, - displayMinFreq, displayMaxFreq, - logarithmic, - overallMag, overallMagChanged); + attainedBufwid = + paintDrawBufferPeakFrequencies(v, bufwid, h, binforx, + minbin, maxbin, + displayMinFreq, displayMaxFreq, + logarithmic, + overallMag, overallMagChanged, + rightToLeft, + softTimeLimit); } -/* - for (int x = 0; x < w / xPixelRatio; ++x) { - - Profiler innerprof("SpectrogramLayer::paint: 1 pixel column"); - - runOutOfData = !paintColumnValues(v, fft, x0, x, - minbin, maxbin, - displayMinFreq, displayMaxFreq, - xPixelRatio, - h, yforbin); - - if (runOutOfData) { + int failedToRepaint = bufwid - attainedBufwid; + + int paintedLeft = x0; + int paintedWidth = x1 - x0; + + if (failedToRepaint > 0) { + #ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "Run out of data -- dropping out of loop" << endl; + cerr << "SpectrogramLayer::paint(): Failed to repaint " << failedToRepaint << " of " << bufwid + << " columns in time (so managed to repaint " << bufwid - failedToRepaint << ")" << endl; #endif - break; + + if (rightToLeft) { + paintedLeft += failedToRepaint; } + + paintedWidth -= failedToRepaint; + + if (paintedWidth < 0) { + paintedWidth = 0; + } + + } else if (failedToRepaint < 0) { + cerr << "WARNING: failedToRepaint < 0 (= " << failedToRepaint << ")" + << endl; + failedToRepaint = 0; } -*/ + + if (overallMagChanged) { + m_viewMags[v->getId()] = overallMag; #ifdef DEBUG_SPECTROGRAM_REPAINT -// cerr << pixels << " pixels drawn" << endl; -#endif - - if (overallMagChanged) { - m_viewMags[v] = overallMag; -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "Overall mag is now [" << m_viewMags[v].getMin() << "->" << m_viewMags[v].getMax() << "] - will be updating" << endl; -#endif - } else { -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "Overall mag unchanged at [" << m_viewMags[v].getMin() << "->" << m_viewMags[v].getMax() << "]" << endl; + cerr << "SpectrogramLayer: Overall mag is now [" << m_viewMags[v->getId()].getMin() << "->" << m_viewMags[v->getId()].getMax() << "] - will be updating" << endl; #endif } @@ -2212,111 +2026,141 @@ Profiler profiler2("SpectrogramLayer::paint: draw image"); - if (recreateWholeImageCache) { + if (paintedWidth > 0) { + #ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "Recreating image cache: width = " << v->getPaintWidth() - << ", height = " << h << endl; -#endif - cache.image = QImage(v->getPaintWidth(), h, QImage::Format_ARGB32_Premultiplied); - } - - if (w > 0) { -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "Painting " << w << "x" << h - << " from draw buffer at " << 0 << "," << 0 - << " to " << w << "x" << h << " on cache at " + cerr << "SpectrogramLayer: Copying " << paintedWidth << "x" << h + << " from draw buffer at " << paintedLeft - x0 << "," << 0 + << " to " << paintedWidth << "x" << h << " on cache at " << x0 << "," << 0 << endl; #endif - QPainter cachePainter(&cache.image); - - if (bufferBinResolution) { + if (bufferIsBinResolution) { + int scaledLeft = v->getXForFrame(leftBoundaryFrame); int scaledRight = v->getXForFrame(rightBoundaryFrame); + #ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "Rescaling image from " << bufwid + cerr << "SpectrogramLayer: Rescaling image from " << bufwid << "x" << h << " to " << scaledRight-scaledLeft << "x" << h << endl; #endif + Preferences::SpectrogramXSmoothing xsmoothing = Preferences::getInstance()->getSpectrogramXSmoothing(); -// SVDEBUG << "xsmoothing == " << xsmoothing << endl; + QImage scaled = m_drawBuffer.scaled (scaledRight - scaledLeft, h, Qt::IgnoreAspectRatio, ((xsmoothing == Preferences::SpectrogramXInterpolated) ? Qt::SmoothTransformation : Qt::FastTransformation)); + int scaledLeftCrop = v->getXForFrame(leftCropFrame); int scaledRightCrop = v->getXForFrame(rightCropFrame); + #ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "Drawing image region of width " << scaledRightCrop - scaledLeftCrop << " to " + cerr << "SpectrogramLayer: Drawing image region of width " << scaledRightCrop - scaledLeftCrop << " to " << scaledLeftCrop << " from " << scaledLeftCrop - scaledLeft << endl; #endif - cachePainter.drawImage - (QRect(scaledLeftCrop, 0, - scaledRightCrop - scaledLeftCrop, h), - scaled, - QRect(scaledLeftCrop - scaledLeft, 0, - scaledRightCrop - scaledLeftCrop, h)); + + int targetLeft = scaledLeftCrop; + if (targetLeft < 0) { + targetLeft = 0; + } + + int targetWidth = scaledRightCrop - targetLeft; + if (targetLeft + targetWidth > cache.getSize().width()) { + targetWidth = cache.getSize().width() - targetLeft; + } + + int sourceLeft = targetLeft - scaledLeft; + if (sourceLeft < 0) { + sourceLeft = 0; + } + + int sourceWidth = targetWidth; + + if (targetWidth > 0) { + cache.drawImage + (targetLeft, + targetWidth, + scaled, + sourceLeft, + sourceWidth); + } + } else { - cachePainter.drawImage(QRect(x0, 0, w, h), - m_drawBuffer, - QRect(0, 0, w, h)); + + cache.drawImage(paintedLeft, paintedWidth, + m_drawBuffer, + paintedLeft - x0, paintedWidth); } - - cachePainter.end(); } - QRect pr = rect & cache.validArea; - #ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "Painting " << pr.width() << "x" << pr.height() + cerr << "SpectrogramLayer: Cache valid area now from " << cache.getValidLeft() + << " width " << cache.getValidWidth() << ", height " + << cache.getSize().height() << endl; +#endif + + QRect pr = rect & cache.getValidArea(); + +#ifdef DEBUG_SPECTROGRAM_REPAINT + cerr << "SpectrogramLayer: Copying " << pr.width() << "x" << pr.height() << " from cache at " << pr.x() << "," << pr.y() << " to window" << endl; #endif - paint.drawImage(pr.x(), pr.y(), cache.image, + paint.drawImage(pr.x(), pr.y(), cache.getImage(), pr.x(), pr.y(), pr.width(), pr.height()); - //!!! -// paint.drawImage(v->rect(), cache.image, -// QRect(QPoint(0, 0), cache.image.size())); - - cache.startFrame = startFrame; - cache.zoomLevel = zoomLevel; if (!m_synchronous) { if ((m_normalization != NormalizeVisibleArea) || !overallMagChanged) { - - if (cache.validArea.x() > 0) { -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "SpectrogramLayer::paint() updating left (0, " - << cache.validArea.x() << ")" << endl; -#endif - v->getView()->update(0, 0, cache.validArea.x(), h); + + QRect areaLeft(0, 0, cache.getValidLeft(), h); + QRect areaRight(cache.getValidRight(), 0, + cache.getSize().width() - cache.getValidRight(), h); + + bool haveSpaceLeft = (areaLeft.width() > 0); + bool haveSpaceRight = (areaRight.width() > 0); + + bool updateLeft = haveSpaceLeft; + bool updateRight = haveSpaceRight; + + if (updateLeft && updateRight) { + if (rightToLeft) { + // we just did something adjoining the cache on + // its left side, so now do something on its right + updateLeft = false; + } else { + updateRight = false; + } } - if (cache.validArea.x() + cache.validArea.width() < - cache.image.width()) { + if (updateLeft) { +#ifdef DEBUG_SPECTROGRAM_REPAINT + cerr << "SpectrogramLayer::paint() updating left (" + << areaLeft.x() << ", " + << areaLeft.width() << ")" << endl; +#endif + v->updatePaintRect(areaLeft); + } + + if (updateRight) { #ifdef DEBUG_SPECTROGRAM_REPAINT cerr << "SpectrogramLayer::paint() updating right (" - << cache.validArea.x() + cache.validArea.width() - << ", " - << cache.image.width() - (cache.validArea.x() + - cache.validArea.width()) - << ")" << endl; + << areaRight.x() << ", " + << areaRight.width() << ")" << endl; #endif - v->getView()->update(cache.validArea.x() + cache.validArea.width(), - 0, - cache.image.width() - (cache.validArea.x() + - cache.validArea.width()), - h); + v->updatePaintRect(areaRight); } + } else { // overallMagChanged cerr << "\noverallMagChanged - updating all\n" << endl; - cache.validArea = QRect(); - v->getView()->update(); + cache.invalidate(); + v->updatePaintRect(v->getPaintRect()); } } @@ -2325,15 +2169,9 @@ #ifdef DEBUG_SPECTROGRAM_REPAINT cerr << "SpectrogramLayer::paint() returning" << endl; #endif - - if (!m_synchronous) { - m_lastPaintBlockWidth = paintBlockWidth; - (void)gettimeofday(&tv, 0); - m_lastPaintTime = RealTime::fromTimeval(tv) - mainPaintStart; - } } -bool +int SpectrogramLayer::paintDrawBufferPeakFrequencies(LayerGeometryProvider *v, int w, int h, @@ -2344,18 +2182,20 @@ double displayMaxFreq, bool logarithmic, MagnitudeRange &overallMag, - bool &overallMagChanged) const + bool &overallMagChanged, + bool rightToLeft, + double softTimeLimit) const { Profiler profiler("SpectrogramLayer::paintDrawBufferPeakFrequencies"); #ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "minbin " << minbin << ", maxbin " << maxbin << "; w " << w << ", h " << h << endl; + cerr << "SpectrogramLayer::paintDrawBufferPeakFrequencies: minbin " << minbin << ", maxbin " << maxbin << "; w " << w << ", h " << h << endl; #endif if (minbin < 0) minbin = 0; if (maxbin < 0) maxbin = minbin+1; FFTModel *fft = getFFTModel(v); - if (!fft) return false; + if (!fft) return 0; FFTModel::PeakSet peakfreqs; @@ -2367,7 +2207,27 @@ float *values = (float *)alloca((maxbin - minbin + 1) * sizeof(float)); #endif - for (int x = 0; x < w; ++x) { + int minColumns = 4; + bool haveTimeLimits = (softTimeLimit > 0.0); + double hardTimeLimit = softTimeLimit * 2.0; + bool overridingSoftLimit = false; + auto startTime = chrono::steady_clock::now(); + + int start = 0; + int finish = w; + int step = 1; + + if (rightToLeft) { + start = w-1; + finish = -1; + step = -1; + } + + int columnCount = 0; + + for (int x = start; x != finish; x += step) { + + ++columnCount; if (binforx[x] < 0) continue; @@ -2382,15 +2242,6 @@ if (sx < 0 || sx >= int(fft->getWidth())) continue; - if (!m_synchronous) { - if (!fft->isColumnAvailable(sx)) { -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "Met unavailable column at col " << sx << endl; -#endif - return false; - } - } - MagnitudeRange mag; if (sx != psx) { @@ -2456,12 +2307,40 @@ } } } + + if (haveTimeLimits) { + if (columnCount >= minColumns) { + auto t = chrono::steady_clock::now(); + double diff = chrono::duration(t - startTime).count(); + if (diff > hardTimeLimit) { +#ifdef DEBUG_SPECTROGRAM_REPAINT + cerr << "SpectrogramLayer::paintDrawBufferPeakFrequencies: hard limit " << hardTimeLimit << " sec exceeded after " + << columnCount << " columns with time " << diff << endl; +#endif + return columnCount; + } else if (diff > softTimeLimit && !overridingSoftLimit) { + // If we're more than half way through by the time + // we reach the soft limit, ignore it (though + // still respect the hard limit, above). Otherwise + // respect the soft limit and return now. + if (columnCount > w/2) { + overridingSoftLimit = true; + } else { +#ifdef DEBUG_SPECTROGRAM_REPAINT + cerr << "SpectrogramLayer::paintDrawBufferPeakFrequencies: soft limit " << softTimeLimit << " sec exceeded after " + << columnCount << " columns with time " << diff << endl; +#endif + return columnCount; + } + } + } + } } - return true; + return columnCount; } -bool +int SpectrogramLayer::paintDrawBuffer(LayerGeometryProvider *v, int w, int h, @@ -2469,7 +2348,9 @@ const vector &binfory, bool usePeaksCache, MagnitudeRange &overallMag, - bool &overallMagChanged) const + bool &overallMagChanged, + bool rightToLeft, + double softTimeLimit) const { Profiler profiler("SpectrogramLayer::paintDrawBuffer"); @@ -2477,7 +2358,7 @@ int maxbin = int(binfory[h-1]); #ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "minbin " << minbin << ", maxbin " << maxbin << "; w " << w << ", h " << h << endl; + cerr << "SpectrogramLayer::paintDrawBuffer: minbin " << minbin << ", maxbin " << maxbin << "; w " << w << ", h " << h << endl; #endif if (minbin < 0) minbin = 0; if (maxbin < 0) maxbin = minbin+1; @@ -2486,7 +2367,7 @@ FFTModel *fft = 0; int divisor = 1; #ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "Note: bin display = " << m_binDisplay << ", w = " << w << ", binforx[" << w-1 << "] = " << binforx[w-1] << ", binforx[0] = " << binforx[0] << endl; + cerr << "SpectrogramLayer::paintDrawBuffer: Note: bin display = " << m_binDisplay << ", w = " << w << ", binforx[" << w-1 << "] = " << binforx[w-1] << ", binforx[0] = " << binforx[0] << endl; #endif if (usePeaksCache) { //!!! sourceModel = getPeakCache(v); @@ -2497,7 +2378,7 @@ sourceModel = fft = getFFTModel(v); } - if (!sourceModel) return false; + if (!sourceModel) return 0; bool interpolate = false; Preferences::SpectrogramSmoothing smoothing = @@ -2523,7 +2404,27 @@ const float *values = autoarray; DenseThreeDimensionalModel::Column c; - for (int x = 0; x < w; ++x) { + int minColumns = 4; + bool haveTimeLimits = (softTimeLimit > 0.0); + double hardTimeLimit = softTimeLimit * 2.0; + bool overridingSoftLimit = false; + auto startTime = chrono::steady_clock::now(); + + int start = 0; + int finish = w; + int step = 1; + + if (rightToLeft) { + start = w-1; + finish = -1; + step = -1; + } + + int columnCount = 0; + + for (int x = start; x != finish; x += step) { + + ++columnCount; if (binforx[x] < 0) continue; @@ -2547,21 +2448,12 @@ if (sx < 0 || sx >= int(sourceModel->getWidth())) continue; - if (!m_synchronous) { - if (!sourceModel->isColumnAvailable(sx)) { -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "Met unavailable column at col " << sx << endl; -#endif - return false; - } - } - MagnitudeRange mag; if (sx != psx) { if (fft) { #ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "Retrieving column " << sx << " from fft directly" << endl; +// cerr << "Retrieving column " << sx << " from fft directly" << endl; #endif if (m_colourScale == PhaseColourScale) { fft->getPhasesAt(sx, autoarray, minbin, maxbin - minbin + 1); @@ -2579,7 +2471,7 @@ } } else { #ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "Retrieving column " << sx << " from peaks cache" << endl; +// cerr << "Retrieving column " << sx << " from peaks cache" << endl; #endif c = sourceModel->getColumn(sx); if (m_normalization == NormalizeColumns || @@ -2588,7 +2480,7 @@ if (c[y] > columnMax) columnMax = c[y]; } } - values = c.constData() + minbin; + values = c.data() + minbin; } psx = sx; } @@ -2700,9 +2592,37 @@ m_drawBuffer.setPixel(x, h-y-1, peakpix); } + + if (haveTimeLimits) { + if (columnCount >= minColumns) { + auto t = chrono::steady_clock::now(); + double diff = chrono::duration(t - startTime).count(); + if (diff > hardTimeLimit) { +#ifdef DEBUG_SPECTROGRAM_REPAINT + cerr << "SpectrogramLayer::paintDrawBuffer: hard limit " << hardTimeLimit << " sec exceeded after " + << columnCount << " columns with time " << diff << endl; +#endif + return columnCount; + } else if (diff > softTimeLimit && !overridingSoftLimit) { + // If we're more than half way through by the time + // we reach the soft limit, ignore it (though + // still respect the hard limit, above). Otherwise + // respect the soft limit and return now. + if (columnCount > w/2) { + overridingSoftLimit = true; + } else { +#ifdef DEBUG_SPECTROGRAM_REPAINT + cerr << "SpectrogramLayer::paintDrawBuffer: soft limit " << softTimeLimit << " sec exceeded after " + << columnCount << " columns with time " << diff << endl; +#endif + return columnCount; + } + } + } + } } - return true; + return columnCount; } void @@ -2767,9 +2687,9 @@ { const View *view = v->getView(); - if (m_fftModels.find(view) == m_fftModels.end()) return 100; - - int completion = m_fftModels[view]->getCompletion(); + if (m_fftModels.find(view->getId()) == m_fftModels.end()) return 100; + + int completion = m_fftModels[view->getId()]->getCompletion(); #ifdef DEBUG_SPECTROGRAM_REPAINT cerr << "SpectrogramLayer::getCompletion: completion = " << completion << endl; #endif @@ -2780,8 +2700,8 @@ SpectrogramLayer::getError(LayerGeometryProvider *v) const { const View *view = v->getView(); - if (m_fftModels.find(view) == m_fftModels.end()) return ""; - return m_fftModels[view]->getError(); + if (m_fftModels.find(view->getId()) == m_fftModels.end()) return ""; + return m_fftModels[view->getId()]->getError(); } bool @@ -2877,12 +2797,12 @@ SpectrogramLayer::measureDoubleClick(LayerGeometryProvider *v, QMouseEvent *e) { const View *view = v->getView(); - ImageCache &cache = m_imageCaches[view]; - - cerr << "cache width: " << cache.image.width() << ", height: " - << cache.image.height() << endl; - - QImage image = cache.image; + ScrollableImageCache &cache = getImageCacheReference(view); + + cerr << "cache width: " << cache.getSize().width() << ", height: " + << cache.getSize().height() << endl; + + QImage image = cache.getImage(); ImageRegionFinder finder; QRect rect = finder.findRegionExtents(&image, e->pos()); @@ -2897,7 +2817,7 @@ bool SpectrogramLayer::getCrosshairExtents(LayerGeometryProvider *v, QPainter &paint, QPoint cursorPos, - std::vector &extents) const + vector &extents) const { QRect vertical(cursorPos.x() - 12, 0, 12, v->getPaintHeight()); extents.push_back(vertical); @@ -3197,8 +3117,8 @@ paint.drawRect(4 + cw - cbw, textHeight * topLines + 4, cbw - 1, ch + 1); QString top, bottom; - double min = m_viewMags[v].getMin(); - double max = m_viewMags[v].getMax(); + double min = m_viewMags[v->getId()].getMin(); + double max = m_viewMags[v->getId()].getMax(); double dBmin = AudioLevel::multiplier_to_dB(min); double dBmax = AudioLevel::multiplier_to_dB(max); @@ -3297,7 +3217,7 @@ paint.drawLine(cw + 7, h - vy, w - pkw - 1, h - vy); if (h - vy - textHeight >= -2) { - int tx = w - 3 - paint.fontMetrics().width(text) - std::max(tickw, pkw); + int tx = w - 3 - paint.fontMetrics().width(text) - max(tickw, pkw); paint.drawText(tx, h - vy + toff, text); } diff -r cd9e76e755bf -r fccee028a522 layer/SpectrogramLayer.h --- a/layer/SpectrogramLayer.h Thu Feb 04 11:17:31 2016 +0000 +++ b/layer/SpectrogramLayer.h Thu Feb 04 11:18:08 2016 +0000 @@ -13,8 +13,8 @@ COPYING included with this distribution for more information. */ -#ifndef _SPECTROGRAM_LAYER_H_ -#define _SPECTROGRAM_LAYER_H_ +#ifndef SPECTROGRAM_LAYER_H +#define SPECTROGRAM_LAYER_H #include "SliceableLayer.h" #include "base/Window.h" @@ -25,6 +25,8 @@ #include "data/model/DenseTimeValueModel.h" #include "data/model/FFTModel.h" +#include "ScrollableImageCache.h" + #include #include #include @@ -250,20 +252,20 @@ const DenseTimeValueModel *m_model; // I do not own this int m_channel; - int m_windowSize; + int m_windowSize; WindowType m_windowType; - int m_windowHopLevel; - int m_zeroPadLevel; - int m_fftSize; + int m_windowHopLevel; + int m_zeroPadLevel; + int m_fftSize; float m_gain; float m_initialGain; float m_threshold; float m_initialThreshold; int m_colourRotation; int m_initialRotation; - int m_minFrequency; - int m_maxFrequency; - int m_initialMaxFrequency; + int m_minFrequency; + int m_maxFrequency; + int m_initialMaxFrequency; ColourScale m_colourScale; int m_colourMap; QColor m_crosshairColour; @@ -274,8 +276,6 @@ bool m_synchronous; mutable bool m_haveDetailedScale; - mutable int m_lastPaintBlockWidth; - mutable RealTime m_lastPaintTime; enum { NO_VALUE = 0 }; // colour index for unused pixels @@ -296,22 +296,10 @@ Palette m_palette; - /** - * ImageCache covers the area of the view, at view resolution. - * Not all of it is necessarily valid at once (it is refreshed - * in parts when scrolling, for example). - */ - struct ImageCache - { - QImage image; - QRect validArea; - sv_frame_t startFrame; - int zoomLevel; - }; - typedef std::map ViewImageCache; + typedef std::map ViewImageCache; // key is view id void invalidateImageCaches(); - void invalidateImageCaches(sv_frame_t startFrame, sv_frame_t endFrame); mutable ViewImageCache m_imageCaches; + ScrollableImageCache &getImageCacheReference(const LayerGeometryProvider *) const; /** * When painting, we draw directly onto the draw buffer and then @@ -365,8 +353,8 @@ Dense3DModelPeakCache *getPeakCache(const LayerGeometryProvider *v) const; void invalidateFFTModels(); - typedef std::map ViewFFTMap; - typedef std::map PeakCacheMap; + typedef std::map ViewFFTMap; // key is view id + typedef std::map PeakCacheMap; // key is view id mutable ViewFFTMap m_fftModels; mutable PeakCacheMap m_peakCaches; mutable Model *m_sliceableModel; @@ -413,26 +401,30 @@ float m_max; }; - typedef std::map ViewMagMap; + typedef std::map ViewMagMap; // key is view id mutable ViewMagMap m_viewMags; mutable std::vector m_columnMags; void invalidateMagnitudes(); bool updateViewMagnitudes(LayerGeometryProvider *v) const; - bool paintDrawBuffer(LayerGeometryProvider *v, int w, int h, - const std::vector &binforx, - const std::vector &binfory, - bool usePeaksCache, - MagnitudeRange &overallMag, - bool &overallMagChanged) const; - bool paintDrawBufferPeakFrequencies(LayerGeometryProvider *v, int w, int h, - const std::vector &binforx, - int minbin, - int maxbin, - double displayMinFreq, - double displayMaxFreq, - bool logarithmic, - MagnitudeRange &overallMag, - bool &overallMagChanged) const; + int paintDrawBuffer(LayerGeometryProvider *v, int w, int h, + const std::vector &binforx, + const std::vector &binfory, + bool usePeaksCache, + MagnitudeRange &overallMag, + bool &overallMagChanged, + bool rightToLeft, + double softTimeLimit) const; + int paintDrawBufferPeakFrequencies(LayerGeometryProvider *v, int w, int h, + const std::vector &binforx, + int minbin, + int maxbin, + double displayMinFreq, + double displayMaxFreq, + bool logarithmic, + MagnitudeRange &overallMag, + bool &overallMagChanged, + bool rightToLeft, + double softTimeLimit) const; virtual void updateMeasureRectYCoords(LayerGeometryProvider *v, const MeasureRect &r) const; virtual void setMeasureRectYCoord(LayerGeometryProvider *v, MeasureRect &r, bool start, int y) const; diff -r cd9e76e755bf -r fccee028a522 layer/TimeRulerLayer.cpp --- a/layer/TimeRulerLayer.cpp Thu Feb 04 11:17:31 2016 +0000 +++ b/layer/TimeRulerLayer.cpp Thu Feb 04 11:18:08 2016 +0000 @@ -26,12 +26,11 @@ #include #include +#include //#define DEBUG_TIME_RULER_LAYER 1 - - TimeRulerLayer::TimeRulerLayer() : SingleColourLayer(), m_model(0), @@ -182,6 +181,8 @@ } else { incms = 1; int ms = rtGap.msec(); +// cerr << "rtGap.msec = " << ms << ", rtGap = " << rtGap << ", count = " << count << endl; +// cerr << "startFrame = " << startFrame << ", endFrame = " << endFrame << " rtStart = " << rtStart << ", rtEnd = " << rtEnd << endl; if (ms > 0) { incms *= 10; ms /= 10; } if (ms > 0) { incms *= 10; ms /= 10; } if (ms > 0) { incms *= 5; ms /= 5; } @@ -241,6 +242,9 @@ // time < 0 which would cut it in half int minlabel = 1; // ms + // used for a sanity check + sv_frame_t prevframe = 0; + while (1) { // frame is used to determine where to draw the lines, so it @@ -253,10 +257,17 @@ frame /= v->getZoomLevel(); frame *= v->getZoomLevel(); // so frame corresponds to an exact pixel + if (frame == prevframe && prevframe != 0) { + cerr << "ERROR: frame == prevframe (== " << frame + << ") in TimeRulerLayer::paint" << endl; + throw std::logic_error("frame == prevframe in TimeRulerLayer::paint"); + } + prevframe = frame; + int x = v->getXForFrame(frame); #ifdef DEBUG_TIME_RULER_LAYER - SVDEBUG << "Considering frame = " << frame << ", x = " << x << endl; + cerr << "Considering frame = " << frame << ", x = " << x << endl; #endif if (x >= rect.x() + rect.width() + 50) { diff -r cd9e76e755bf -r fccee028a522 svgui.pro --- a/svgui.pro Thu Feb 04 11:17:31 2016 +0000 +++ b/svgui.pro Thu Feb 04 11:18:08 2016 +0000 @@ -52,6 +52,7 @@ layer/PaintAssistant.h \ layer/PianoScale.h \ layer/RegionLayer.h \ + layer/ScrollableImageCache.h \ layer/SingleColourLayer.h \ layer/SliceableLayer.h \ layer/SliceLayer.h \ @@ -79,6 +80,7 @@ layer/PaintAssistant.cpp \ layer/PianoScale.cpp \ layer/RegionLayer.cpp \ + layer/ScrollableImageCache.cpp \ layer/SingleColourLayer.cpp \ layer/SliceLayer.cpp \ layer/SpectrogramLayer.cpp \ diff -r cd9e76e755bf -r fccee028a522 view/LayerGeometryProvider.h --- a/view/LayerGeometryProvider.h Thu Feb 04 11:17:31 2016 +0000 +++ b/view/LayerGeometryProvider.h Thu Feb 04 11:18:08 2016 +0000 @@ -17,6 +17,10 @@ #include "base/BaseTypes.h" +#include +#include +#include + class ViewManager; class View; class Layer; @@ -24,6 +28,20 @@ class LayerGeometryProvider { public: + LayerGeometryProvider() { + static QMutex idMutex; + static int nextId = 1; + QMutexLocker locker(&idMutex); + m_id = nextId; + nextId++; + } + + /** + * Retrieve the id of this object. Each LayerGeometryProvider has + * a separate id. + */ + int getId() const { return m_id; } + /** * Retrieve the first visible sample frame on the widget. * This is a calculated value based on the centre-frame, widget @@ -61,6 +79,18 @@ virtual sv_frame_t getModelsEndFrame() const = 0; /** + * Return the closest pixel x-coordinate corresponding to a given + * view x-coordinate. + */ + virtual int getXForViewX(int viewx) const = 0; + + /** + * Return the closest view x-coordinate corresponding to a given + * pixel x-coordinate. + */ + virtual int getViewXForX(int x) const = 0; + + /** * Return the pixel y-coordinate corresponding to a given * frequency, if the frequency range is as specified. This does * not imply any policy about layer frequency ranges, but it might @@ -123,8 +153,13 @@ virtual void drawMeasurementRect(QPainter &p, const Layer *, QRect rect, bool focus) const = 0; + virtual void updatePaintRect(QRect r) = 0; + virtual View *getView() = 0; virtual const View *getView() const = 0; + +private: + int m_id; }; #endif diff -r cd9e76e755bf -r fccee028a522 view/View.cpp --- a/view/View.cpp Thu Feb 04 11:17:31 2016 +0000 +++ b/view/View.cpp Thu Feb 04 11:18:08 2016 +0000 @@ -49,7 +49,6 @@ //#define DEBUG_VIEW 1 //#define DEBUG_VIEW_WIDGET_PAINT 1 - View::View(QWidget *w, bool showProgress) : QFrame(w), m_centreFrame(0), @@ -365,15 +364,17 @@ sv_frame_t View::getFrameForX(int x) const { - int z = m_zoomLevel; + sv_frame_t z = m_zoomLevel; // nb not just int, or multiplication may overflow sv_frame_t frame = m_centreFrame - (width()/2) * z; + frame = (frame / z) * z; // this is start frame + frame = frame + x * z; + #ifdef DEBUG_VIEW_WIDGET_PAINT - SVDEBUG << "View::getFrameForX(" << x << "): z = " << z << ", m_centreFrame = " << m_centreFrame << ", width() = " << width() << ", frame = " << frame << endl; + cerr << "View::getFrameForX(" << x << "): z = " << z << ", m_centreFrame = " << m_centreFrame << ", width() = " << width() << ", frame = " << frame << endl; #endif - frame = (frame / z) * z; // this is start frame - return frame + x * z; + return frame; } double diff -r cd9e76e755bf -r fccee028a522 view/View.h --- a/view/View.h Thu Feb 04 11:17:31 2016 +0000 +++ b/view/View.h Thu Feb 04 11:18:08 2016 +0000 @@ -62,7 +62,7 @@ * be managed elsewhere (e.g. by the Document). */ virtual ~View(); - + /** * Retrieve the first visible sample frame on the widget. * This is a calculated value based on the centre-frame, widget @@ -108,6 +108,20 @@ sv_frame_t getFrameForX(int x) const; /** + * Return the closest pixel x-coordinate corresponding to a given + * view x-coordinate. Default is no scaling, ViewProxy handles + * scaling case. + */ + int getXForViewX(int viewx) const { return viewx; } + + /** + * Return the closest view x-coordinate corresponding to a given + * pixel x-coordinate. Default is no scaling, ViewProxy handles + * scaling case. + */ + int getViewXForX(int x) const { return x; } + + /** * Return the pixel y-coordinate corresponding to a given * frequency, if the frequency range is as specified. This does * not imply any policy about layer frequency ranges, but it might @@ -333,6 +347,8 @@ sv_frame_t alignToReference(sv_frame_t) const; sv_frame_t getAlignedPlaybackFrame() const; + void updatePaintRect(QRect r) { update(r); } + View *getView() { return this; } const View *getView() const { return this; } @@ -384,6 +400,9 @@ protected: View(QWidget *, bool showProgress); + + int m_id; + virtual void paintEvent(QPaintEvent *e); virtual void drawSelections(QPainter &); virtual bool shouldLabelSelections() const { return true; } diff -r cd9e76e755bf -r fccee028a522 view/ViewProxy.h --- a/view/ViewProxy.h Thu Feb 04 11:17:31 2016 +0000 +++ b/view/ViewProxy.h Thu Feb 04 11:18:08 2016 +0000 @@ -42,6 +42,12 @@ sv_frame_t f1 = m_view->getFrameForX((x / m_scaleFactor) + 1); return f0 + ((f1 - f0) * (x % m_scaleFactor)) / m_scaleFactor; } + virtual int getXForViewX(int viewx) const { + return viewx * m_scaleFactor; + } + virtual int getViewXForX(int x) const { + return x / m_scaleFactor; + } virtual sv_frame_t getModelsStartFrame() const { return m_view->getModelsStartFrame(); } @@ -129,6 +135,13 @@ m_view->drawMeasurementRect(p, layer, rect, focus); } + virtual void updatePaintRect(QRect r) { + m_view->update(r.x() / m_scaleFactor, + r.y() / m_scaleFactor, + r.width() / m_scaleFactor, + r.height() / m_scaleFactor); + } + virtual View *getView() { return m_view; } virtual const View *getView() const { return m_view; }