Mercurial > hg > svgui
changeset 1146:74f2706995b7 3.0-integration
Merge work on unified spectrogram and colour 3d plot caching renderer
author | Chris Cannam |
---|---|
date | Fri, 05 Aug 2016 15:05:02 +0100 |
parents | b4fd6c67fce5 (current diff) 17f999cd0a22 (diff) |
children | 1badacff7ab2 |
files | view/LayerGeometryProvider.h |
diffstat | 42 files changed, 3681 insertions(+), 3154 deletions(-) [+] |
line wrap: on
line diff
--- a/layer/Colour3DPlotLayer.cpp Mon Jun 13 11:44:08 2016 +0100 +++ b/layer/Colour3DPlotLayer.cpp Fri Aug 05 15:05:02 2016 +0100 @@ -15,11 +15,17 @@ #include "Colour3DPlotLayer.h" -#include "view/View.h" #include "base/Profiler.h" #include "base/LogRange.h" #include "base/RangeMapper.h" + #include "ColourMapper.h" +#include "LayerGeometryProvider.h" +#include "PaintAssistant.h" + +#include "data/model/Dense3DModelPeakCache.h" + +#include "view/ViewManager.h" #include <QPainter> #include <QImage> @@ -42,24 +48,22 @@ Colour3DPlotLayer::Colour3DPlotLayer() : m_model(0), - m_cache(0), - m_peaksCache(0), - m_cacheValidStart(0), - m_cacheValidEnd(0), - m_colourScale(LinearScale), + m_colourScale(ColourScaleType::Linear), m_colourScaleSet(false), m_colourMap(0), m_gain(1.0), - m_binScale(LinearBinScale), - m_normalizeColumns(false), + m_binScale(BinScale::Linear), + m_normalization(ColumnNormalization::None), m_normalizeVisibleArea(false), - m_normalizeHybrid(false), m_invertVertical(false), m_opaque(false), m_smooth(false), m_peakResolution(256), m_miny(0), - m_maxy(0) + m_maxy(0), + m_synchronous(false), + m_peakCache(0), + m_peakCacheDivisor(8) { QSettings settings; settings.beginGroup("Preferences"); @@ -69,8 +73,67 @@ Colour3DPlotLayer::~Colour3DPlotLayer() { - delete m_cache; - delete m_peaksCache; + invalidateRenderers(); + delete m_peakCache; +} + +ColourScaleType +Colour3DPlotLayer::convertToColourScale(int value) +{ + switch (value) { + default: + case 0: return ColourScaleType::Linear; + case 1: return ColourScaleType::Log; + case 2: return ColourScaleType::PlusMinusOne; + case 3: return ColourScaleType::Absolute; + } +} + +int +Colour3DPlotLayer::convertFromColourScale(ColourScaleType scale) +{ + switch (scale) { + case ColourScaleType::Linear: return 0; + case ColourScaleType::Log: return 1; + case ColourScaleType::PlusMinusOne: return 2; + case ColourScaleType::Absolute: return 3; + + case ColourScaleType::Meter: + case ColourScaleType::Phase: + default: return 0; + } +} + +std::pair<ColumnNormalization, bool> +Colour3DPlotLayer::convertToColumnNorm(int value) +{ + switch (value) { + default: + case 0: return { ColumnNormalization::None, false }; + case 1: return { ColumnNormalization::Max1, false }; + case 2: return { ColumnNormalization::None, true }; // visible area + case 3: return { ColumnNormalization::Hybrid, false }; + } +} + +int +Colour3DPlotLayer::convertFromColumnNorm(ColumnNormalization norm, bool visible) +{ + if (visible) return 2; + switch (norm) { + case ColumnNormalization::None: return 0; + case ColumnNormalization::Max1: return 1; + case ColumnNormalization::Hybrid: return 3; + + case ColumnNormalization::Sum1: + default: return 0; + } +} + +void +Colour3DPlotLayer::setSynchronousPainting(bool synchronous) +{ + m_synchronous = synchronous; } void @@ -95,7 +158,11 @@ } else if (model->getResolution() > 2) { m_peakResolution = 128; } - cacheInvalid(); + + delete m_peakCache; + m_peakCache = 0; + + invalidateRenderers(); emit modelReplaced(); emit sliceableModelReplaced(oldModel, model); @@ -104,34 +171,46 @@ void Colour3DPlotLayer::cacheInvalid() { - delete m_cache; - delete m_peaksCache; - m_cache = 0; - m_peaksCache = 0; - m_cacheValidStart = 0; - m_cacheValidEnd = 0; + invalidateRenderers(); } void -Colour3DPlotLayer::cacheInvalid(sv_frame_t startFrame, sv_frame_t endFrame) +Colour3DPlotLayer::cacheInvalid(sv_frame_t /* startFrame */, + sv_frame_t /* endFrame */) { - if (!m_cache || !m_model) return; + //!!! should do this only if the range is visible + delete m_peakCache; + m_peakCache = 0; + + invalidateRenderers(); +} - int modelResolution = m_model->getResolution(); - int start = int(startFrame / modelResolution); - int end = int(endFrame / modelResolution + 1); - if (m_cacheValidStart < end) m_cacheValidStart = end; - if (m_cacheValidEnd > start) m_cacheValidEnd = start; - if (m_cacheValidStart > m_cacheValidEnd) m_cacheValidEnd = m_cacheValidStart; +void +Colour3DPlotLayer::invalidateRenderers() +{ + for (ViewRendererMap::iterator i = m_renderers.begin(); + i != m_renderers.end(); ++i) { + delete i->second; + } + m_renderers.clear(); +} + +Dense3DModelPeakCache * +Colour3DPlotLayer::getPeakCache() const +{ + if (!m_peakCache) { + m_peakCache = new Dense3DModelPeakCache(m_model, m_peakCacheDivisor); + } + return m_peakCache; } void Colour3DPlotLayer::modelChanged() { - if (!m_colourScaleSet && m_colourScale == LinearScale) { + if (!m_colourScaleSet && m_colourScale == ColourScaleType::Linear) { if (m_model) { if (m_model->shouldUseLogValueScale()) { - setColourScale(LogScale); + setColourScale(ColourScaleType::Log); } else { m_colourScaleSet = true; } @@ -143,10 +222,10 @@ void Colour3DPlotLayer::modelChangedWithin(sv_frame_t startFrame, sv_frame_t endFrame) { - if (!m_colourScaleSet && m_colourScale == LinearScale) { + if (!m_colourScaleSet && m_colourScale == ColourScaleType::Linear) { if (m_model && m_model->getWidth() > 50) { if (m_model->shouldUseLogValueScale()) { - setColourScale(LogScale); + setColourScale(ColourScaleType::Log); } else { m_colourScaleSet = true; } @@ -161,8 +240,7 @@ PropertyList list; list.push_back("Colour"); list.push_back("Colour Scale"); - list.push_back("Normalize Columns"); - list.push_back("Normalize Visible Area"); + list.push_back("Normalization"); list.push_back("Gain"); list.push_back("Bin Scale"); list.push_back("Invert Vertical Scale"); @@ -176,8 +254,7 @@ { if (name == "Colour") return tr("Colour"); if (name == "Colour Scale") return tr("Scale"); - if (name == "Normalize Columns") return tr("Normalize Columns"); - if (name == "Normalize Visible Area") return tr("Normalize Visible Area"); + if (name == "Normalization") return tr("Normalization"); if (name == "Invert Vertical Scale") return tr("Invert Vertical Scale"); if (name == "Gain") return tr("Gain"); if (name == "Opaque") return tr("Always Opaque"); @@ -189,8 +266,6 @@ QString Colour3DPlotLayer::getPropertyIconName(const PropertyName &name) const { - if (name == "Normalize Columns") return "normalise-columns"; - if (name == "Normalize Visible Area") return "normalise"; if (name == "Invert Vertical Scale") return "invert-vertical"; if (name == "Opaque") return "opaque"; if (name == "Smooth") return "smooth"; @@ -201,8 +276,6 @@ Colour3DPlotLayer::getPropertyType(const PropertyName &name) const { if (name == "Gain") return RangeProperty; - if (name == "Normalize Columns") return ToggleProperty; - if (name == "Normalize Visible Area") return ToggleProperty; if (name == "Invert Vertical Scale") return ToggleProperty; if (name == "Opaque") return ToggleProperty; if (name == "Smooth") return ToggleProperty; @@ -212,9 +285,8 @@ QString Colour3DPlotLayer::getPropertyGroupName(const PropertyName &name) const { - if (name == "Normalize Columns" || - name == "Normalize Visible Area" || - name == "Colour Scale" || + if (name == "Normalization" || + name == "Colour Scale" || name == "Gain") return tr("Scale"); if (name == "Bin Scale" || name == "Invert Vertical Scale") return tr("Bins"); @@ -250,11 +322,12 @@ } else if (name == "Colour Scale") { + // linear, log, +/-1, abs *min = 0; *max = 3; - *deflt = (int)LinearScale; + *deflt = 0; - val = (int)m_colourScale; + val = convertFromColourScale(m_colourScale); } else if (name == "Colour") { @@ -264,15 +337,13 @@ val = m_colourMap; - } else if (name == "Normalize Columns") { + } else if (name == "Normalization") { + *min = 0; + *max = 3; *deflt = 0; - val = (m_normalizeColumns ? 1 : 0); - } else if (name == "Normalize Visible Area") { - - *deflt = 0; - val = (m_normalizeVisibleArea ? 1 : 0); + val = convertFromColumnNorm(m_normalization, m_normalizeVisibleArea); } else if (name == "Invert Vertical Scale") { @@ -283,7 +354,7 @@ *min = 0; *max = 1; - *deflt = int(LinearBinScale); + *deflt = int(BinScale::Linear); val = (int)m_binScale; } else if (name == "Opaque") { @@ -319,6 +390,9 @@ case 3: return tr("Absolute"); } } + if (name == "Normalization") { + return ""; // icon only + } if (name == "Bin Scale") { switch (value) { default: @@ -329,6 +403,22 @@ return tr("<unknown>"); } +QString +Colour3DPlotLayer::getPropertyValueIconName(const PropertyName &name, + int value) const +{ + if (name == "Normalization") { + switch(value) { + default: + case 0: return "normalise-none"; + case 1: return "normalise-columns"; + case 2: return "normalise"; + case 3: return "normalise-hybrid"; + } + } + return ""; +} + RangeMapper * Colour3DPlotLayer::getNewPropertyRangeMapper(const PropertyName &name) const { @@ -344,19 +434,9 @@ if (name == "Gain") { setGain(float(pow(10, value/20.0))); } else if (name == "Colour Scale") { - switch (value) { - default: - case 0: setColourScale(LinearScale); break; - case 1: setColourScale(LogScale); break; - case 2: setColourScale(PlusMinusOneScale); break; - case 3: setColourScale(AbsoluteScale); break; - } + setColourScale(convertToColourScale(value)); } else if (name == "Colour") { setColourMap(value); - } else if (name == "Normalize Columns") { - setNormalizeColumns(value ? true : false); - } else if (name == "Normalize Visible Area") { - setNormalizeVisibleArea(value ? true : false); } else if (name == "Invert Vertical Scale") { setInvertVertical(value ? true : false); } else if (name == "Opaque") { @@ -366,19 +446,23 @@ } else if (name == "Bin Scale") { switch (value) { default: - case 0: setBinScale(LinearBinScale); break; - case 1: setBinScale(LogBinScale); break; + case 0: setBinScale(BinScale::Linear); break; + case 1: setBinScale(BinScale::Log); break; } + } else if (name == "Normalization") { + auto n = convertToColumnNorm(value); + setNormalization(n.first); + setNormalizeVisibleArea(n.second); } } void -Colour3DPlotLayer::setColourScale(ColourScale scale) +Colour3DPlotLayer::setColourScale(ColourScaleType scale) { if (m_colourScale == scale) return; m_colourScale = scale; m_colourScaleSet = true; - cacheInvalid(); + invalidateRenderers(); emit layerParametersChanged(); } @@ -387,7 +471,7 @@ { if (m_colourMap == map) return; m_colourMap = map; - cacheInvalid(); + invalidateRenderers(); emit layerParametersChanged(); } @@ -396,7 +480,7 @@ { if (m_gain == gain) return; m_gain = gain; - cacheInvalid(); + invalidateRenderers(); emit layerParametersChanged(); } @@ -411,52 +495,41 @@ { if (m_binScale == binScale) return; m_binScale = binScale; - cacheInvalid(); + invalidateRenderers(); emit layerParametersChanged(); } -Colour3DPlotLayer::BinScale +BinScale Colour3DPlotLayer::getBinScale() const { return m_binScale; } void -Colour3DPlotLayer::setNormalizeColumns(bool n) +Colour3DPlotLayer::setNormalization(ColumnNormalization n) { - if (m_normalizeColumns == n) return; - m_normalizeColumns = n; - cacheInvalid(); + if (m_normalization == n) return; + + m_normalization = n; + invalidateRenderers(); + emit layerParametersChanged(); } -bool -Colour3DPlotLayer::getNormalizeColumns() const +ColumnNormalization +Colour3DPlotLayer::getNormalization() const { - return m_normalizeColumns; -} - -void -Colour3DPlotLayer::setNormalizeHybrid(bool n) -{ - if (m_normalizeHybrid == n) return; - m_normalizeHybrid = n; - cacheInvalid(); - emit layerParametersChanged(); -} - -bool -Colour3DPlotLayer::getNormalizeHybrid() const -{ - return m_normalizeHybrid; + return m_normalization; } void Colour3DPlotLayer::setNormalizeVisibleArea(bool n) { if (m_normalizeVisibleArea == n) return; + m_normalizeVisibleArea = n; - cacheInvalid(); + invalidateRenderers(); + emit layerParametersChanged(); } @@ -471,7 +544,7 @@ { if (m_invertVertical == n) return; m_invertVertical = n; - cacheInvalid(); + invalidateRenderers(); emit layerParametersChanged(); } @@ -480,6 +553,7 @@ { if (m_opaque == n) return; m_opaque = n; + invalidateRenderers(); emit layerParametersChanged(); } @@ -488,6 +562,7 @@ { if (m_smooth == n) return; m_smooth = n; + invalidateRenderers(); emit layerParametersChanged(); } @@ -534,16 +609,19 @@ } bool -Colour3DPlotLayer::isLayerScrollable(const LayerGeometryProvider *v) const +Colour3DPlotLayer::isLayerScrollable(const LayerGeometryProvider */* v */) const { if (m_normalizeVisibleArea) { return false; } - if (shouldPaintDenseIn(v)) { - return true; - } - QPoint discard; - return !v->shouldIlluminateLocalFeatures(this, discard); + //!!! ah hang on, if we're potentially rendering incrementally + //!!! they we can't be scrollable + return false; +// if (getRenderer(v)->willRenderOpaque(v)) { +// return true; +// } +// QPoint discard; +// return !v->shouldIlluminateLocalFeatures(this, discard); } bool @@ -588,6 +666,8 @@ m_miny = int(lrint(min)); m_maxy = int(lrint(max)); + invalidateRenderers(); + emit layerParametersChanged(); return true; } @@ -634,6 +714,8 @@ m_maxy = m_miny + dist; if (m_maxy > m_model->getHeight()) m_maxy = m_model->getHeight(); + invalidateRenderers(); + // SVDEBUG << "Colour3DPlotLayer::setVerticalZoomStep(" <<step <<"): after: miny = " << m_miny << ", maxy = " << m_maxy << endl; emit layerParametersChanged(); @@ -649,14 +731,14 @@ } double -Colour3DPlotLayer::getYForBin(LayerGeometryProvider *v, double bin) const +Colour3DPlotLayer::getYForBin(const LayerGeometryProvider *v, double bin) const { double y = bin; if (!m_model) return y; double mn = 0, mx = m_model->getHeight(); getDisplayExtents(mn, mx); double h = v->getPaintHeight(); - if (m_binScale == LinearBinScale) { + if (m_binScale == BinScale::Linear) { y = h - (((bin - mn) * h) / (mx - mn)); } else { double logmin = mn + 1, logmax = mx + 1; @@ -666,21 +748,15 @@ return y; } -int -Colour3DPlotLayer::getIYForBin(LayerGeometryProvider *v, int bin) const -{ - return int(round(getYForBin(v, bin))); -} - double -Colour3DPlotLayer::getBinForY(LayerGeometryProvider *v, double y) const +Colour3DPlotLayer::getBinForY(const LayerGeometryProvider *v, double y) const { double bin = y; if (!m_model) return bin; double mn = 0, mx = m_model->getHeight(); getDisplayExtents(mn, mx); double h = v->getPaintHeight(); - if (m_binScale == LinearBinScale) { + if (m_binScale == BinScale::Linear) { bin = mn + ((h - y) * (mx - mn)) / h; } else { double logmin = mn + 1, logmax = mx + 1; @@ -690,12 +766,6 @@ return bin; } -int -Colour3DPlotLayer::getIBinForY(LayerGeometryProvider *v, int y) const -{ - return int(floor(getBinForY(v, y))); -} - QString Colour3DPlotLayer::getFeatureDescription(LayerGeometryProvider *v, QPoint &pos) const { @@ -798,49 +868,20 @@ int cw = getColourScaleWidth(paint); int ch = h - 20; - if (ch > 20 && m_cache) { + if (ch > 20) { - double min = m_model->getMinimumLevel(); - double max = m_model->getMaximumLevel(); + double min = m_viewMags[v->getId()].getMin(); + double max = m_viewMags[v->getId()].getMax(); - double mmin = min; - double mmax = max; + if (max <= min) max = min + 0.1; - if (m_colourScale == LogScale) { - LogRange::mapRange(mmin, mmax); - } else if (m_colourScale == PlusMinusOneScale) { - mmin = -1.f; - mmax = 1.f; - } else if (m_colourScale == AbsoluteScale) { - if (mmin < 0) { - if (fabs(mmin) > fabs(mmax)) mmax = fabs(mmin); - else mmax = fabs(mmax); - mmin = 0; - } else { - mmin = fabs(mmin); - mmax = fabs(mmax); - } - } - - if (max == min) max = min + 1.f; - if (mmax == mmin) mmax = mmin + 1.f; - paint.setPen(v->getForeground()); paint.drawRect(4, 10, cw - 8, ch+1); for (int y = 0; y < ch; ++y) { double value = ((max - min) * (double(ch-y) - 1.0)) / double(ch) + min; - if (m_colourScale == LogScale) { - value = LogRange::map(value); - } - int pixel = int(((value - mmin) * 256) / (mmax - mmin)); - if (pixel >= 0 && pixel < 256) { - QRgb c = m_cache->color(pixel); - paint.setPen(QColor(qRed(c), qGreen(c), qBlue(c))); - paint.drawLine(5, 11 + y, cw - 5, 11 + y); - } else { - cerr << "WARNING: Colour3DPlotLayer::paintVerticalScale: value " << value << ", mmin " << mmin << ", mmax " << mmax << " leads to invalid pixel " << pixel << endl; - } + paint.setPen(getRenderer(v)->getColour(value)); + paint.drawLine(5, 11 + y, cw - 5, 11 + y); } QString minstr = QString("%1").arg(min); @@ -864,12 +905,12 @@ paint.setWorldMatrix(m); - v->drawVisibleText(paint, 2, 0, minstr, View::OutlinedText); + PaintAssistant::drawVisibleText(v, paint, 2, 0, minstr, PaintAssistant::OutlinedText); m.translate(ch - msw - 2, 0); paint.setWorldMatrix(m); - v->drawVisibleText(paint, 0, 0, maxstr, View::OutlinedText); + PaintAssistant::drawVisibleText(v, paint, 0, 0, maxstr, PaintAssistant::OutlinedText); paint.restore(); } @@ -943,7 +984,10 @@ DenseThreeDimensionalModel::Column values = m_model->getColumn(col); values.resize(m_model->getHeight(), 0.f); - if (!m_normalizeColumns && !m_normalizeHybrid) return values; + if (m_normalization != ColumnNormalization::Max1 && + m_normalization != ColumnNormalization::Hybrid) { + return values; + } double colMax = 0.f, colMin = 0.f; double min = 0.f, max = 0.f; @@ -968,7 +1012,8 @@ if (value != newvalue) values[y] = float(newvalue); } - if (m_normalizeHybrid && (colMax > 0.0)) { + if (m_normalization == ColumnNormalization::Hybrid + && (colMax > 0.0)) { double logmax = log10(colMax); for (int y = 0; y < nv; ++y) { values[y] = float(values[y] * logmax); @@ -977,304 +1022,92 @@ return values; } - -void -Colour3DPlotLayer::fillCache(int firstColumn, int lastColumn) const + +Colour3DPlotRenderer * +Colour3DPlotLayer::getRenderer(const LayerGeometryProvider *v) const { - // This call requests a (perhaps partial) fill of the cache - // between model columns firstColumn and lastColumn inclusive. - // The cache itself always has size sufficient to contain the - // whole model, but its validity may be less, depending on which - // regions have been requested via calls to this function. Note - // that firstColumn and lastColumn are *model* column numbers. If - // the model starts at a frame > 0, a firstColumn of zero still - // corresponds to the first column in the model, not the first - // column on the resulting rendered layer. + if (m_renderers.find(v->getId()) == m_renderers.end()) { - Profiler profiler("Colour3DPlotLayer::fillCache", true); + Colour3DPlotRenderer::Sources sources; + sources.verticalBinLayer = this; + sources.fft = 0; + sources.source = m_model; + sources.peaks = getPeakCache(); - int cacheWidth = m_model->getWidth(); - int cacheHeight = m_model->getHeight(); + ColourScale::Parameters cparams; + cparams.colourMap = m_colourMap; + cparams.scaleType = m_colourScale; + cparams.gain = m_gain; -#ifdef DEBUG_COLOUR_3D_PLOT_LAYER_PAINT - cerr << "Colour3DPlotLayer::fillCache: range " << firstColumn << " -> " << lastColumn << " (cache size will be " << cacheWidth << " x " << cacheHeight << ")" << endl; -#endif + if (m_normalization == ColumnNormalization::None) { + cparams.minValue = m_model->getMinimumLevel(); + cparams.maxValue = m_model->getMaximumLevel(); + } else if (m_normalization == ColumnNormalization::Hybrid) { + cparams.minValue = 0; + cparams.maxValue = log10(m_model->getMaximumLevel() + 1.0); + } - if (m_cache && m_cache->height() != cacheHeight) { - // height has changed: delete everything rather than resizing -#ifdef DEBUG_COLOUR_3D_PLOT_LAYER_PAINT - cerr << "Colour3DPlotLayer::fillCache: Cache height has changed, recreating" << endl; -#endif - delete m_cache; - delete m_peaksCache; - m_cache = 0; - m_peaksCache = 0; - } + if (cparams.maxValue <= cparams.minValue) { + cparams.maxValue = cparams.minValue + 0.1; + } + + Colour3DPlotRenderer::Parameters params; + params.colourScale = ColourScale(cparams); + params.normalization = m_normalization; + params.binScale = m_binScale; + params.alwaysOpaque = m_opaque; + params.invertVertical = m_invertVertical; + params.interpolate = m_smooth; - if (m_cache && m_cache->width() != cacheWidth) { - // width has changed and we have an existing cache: resize it -#ifdef DEBUG_COLOUR_3D_PLOT_LAYER_PAINT - cerr << "Colour3DPlotLayer::fillCache: Cache width has changed, resizing existing cache" << endl; -#endif - QImage *newCache = - new QImage(m_cache->copy(0, 0, cacheWidth, cacheHeight)); - delete m_cache; - m_cache = newCache; - if (m_peaksCache) { - QImage *newPeaksCache = - new QImage(m_peaksCache->copy - (0, 0, cacheWidth / m_peakResolution + 1, cacheHeight)); - delete m_peaksCache; - m_peaksCache = newPeaksCache; - } + m_renderers[v->getId()] = new Colour3DPlotRenderer(sources, params); } - if (!m_cache) { -#ifdef DEBUG_COLOUR_3D_PLOT_LAYER_PAINT - cerr << "Colour3DPlotLayer::fillCache: Have no cache, making one" << endl; -#endif - m_cache = new QImage(cacheWidth, cacheHeight, QImage::Format_Indexed8); - m_cache->setColorCount(256); - m_cache->fill(0); - if (!m_normalizeVisibleArea) { - m_peaksCache = new QImage - (cacheWidth / m_peakResolution + 1, cacheHeight, - QImage::Format_Indexed8); - m_peaksCache->setColorCount(256); - m_peaksCache->fill(0); - } else if (m_peaksCache) { - delete m_peaksCache; - m_peaksCache = 0; - } - m_cacheValidStart = 0; - m_cacheValidEnd = 0; - } + return m_renderers[v->getId()]; +} -#ifdef DEBUG_COLOUR_3D_PLOT_LAYER_PAINT - cerr << "cache size = " << m_cache->width() << "x" << m_cache->height() - << " peaks cache size = " << m_peaksCache->width() << "x" << m_peaksCache->height() << endl; -#endif +void +Colour3DPlotLayer::paintWithRenderer(LayerGeometryProvider *v, + QPainter &paint, QRect rect) const +{ + Colour3DPlotRenderer *renderer = getRenderer(v); - if (m_cacheValidStart <= firstColumn && m_cacheValidEnd >= lastColumn) { -#ifdef DEBUG_COLOUR_3D_PLOT_LAYER_PAINT - cerr << "Cache is valid in this region already" << endl; -#endif - return; + Colour3DPlotRenderer::RenderResult result; + MagnitudeRange magRange; + int viewId = v->getId(); + + if (!renderer->geometryChanged(v)) { + magRange = m_viewMags[viewId]; } - int fillStart = firstColumn; - int fillEnd = lastColumn; + if (m_synchronous) { - if (fillStart >= cacheWidth) fillStart = cacheWidth-1; - if (fillStart < 0) fillStart = 0; - if (fillEnd >= cacheWidth) fillEnd = cacheWidth-1; - if (fillEnd < 0) fillEnd = 0; - if (fillEnd < fillStart) fillEnd = fillStart; - - bool normalizeVisible = (m_normalizeVisibleArea && !m_normalizeColumns); - - if (!normalizeVisible && (m_cacheValidStart < m_cacheValidEnd)) { - - if (m_cacheValidEnd < fillStart) { - fillStart = m_cacheValidEnd + 1; - } - if (m_cacheValidStart > fillEnd) { - fillEnd = m_cacheValidStart - 1; - } - - m_cacheValidStart = std::min(fillStart, m_cacheValidStart); - m_cacheValidEnd = std::max(fillEnd, m_cacheValidEnd); + result = renderer->render(v, paint, rect); } else { - // when normalising the visible area, the only valid area, - // ever, is the currently visible one + result = renderer->renderTimeConstrained(v, paint, rect); - m_cacheValidStart = fillStart; - m_cacheValidEnd = fillEnd; - } - -#ifdef DEBUG_COLOUR_3D_PLOT_LAYER_PAINT - cerr << "Cache size " << cacheWidth << "x" << cacheHeight << " will be valid from " << m_cacheValidStart << " to " << m_cacheValidEnd << " (fillStart = " << fillStart << ", fillEnd = " << fillEnd << ")" << endl; -#endif - - DenseThreeDimensionalModel::Column values; - - double min = m_model->getMinimumLevel(); - double max = m_model->getMaximumLevel(); - - if (m_colourScale == LogScale) { - LogRange::mapRange(min, max); - } else if (m_colourScale == PlusMinusOneScale) { - min = -1.f; - max = 1.f; - } else if (m_colourScale == AbsoluteScale) { - if (min < 0) { - if (fabs(min) > fabs(max)) max = fabs(min); - else max = fabs(max); - min = 0; - } else { - min = fabs(min); - max = fabs(max); + QRect uncached = renderer->getLargestUncachedRect(v); + if (uncached.width() > 0) { + v->updatePaintRect(uncached); } } - if (max == min) max = min + 1.f; - - ColourMapper mapper(m_colourMap, 0.f, 255.f); - - for (int index = 0; index < 256; ++index) { - QColor colour = mapper.map(index); - m_cache->setColor - (index, qRgb(colour.red(), colour.green(), colour.blue())); - if (m_peaksCache) { - m_peaksCache->setColor - (index, qRgb(colour.red(), colour.green(), colour.blue())); + magRange.sample(result.range); + + if (magRange.isSet()) { + if (!(m_viewMags[viewId] == magRange)) { + m_viewMags[viewId] = magRange; + //!!! now need to do the normalise-visible thing } } - double visibleMax = 0.f, visibleMin = 0.f; - - if (normalizeVisible) { + cerr << "mag range in this view: " + << m_viewMags[v->getId()].getMin() + << " -> " + << m_viewMags[v->getId()].getMax() + << endl; - for (int c = fillStart; c <= fillEnd; ++c) { - - values = getColumn(c); - - double colMax = 0.f, colMin = 0.f; - - for (int y = 0; y < cacheHeight; ++y) { - 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]; - } - - if (c == fillStart || colMax > visibleMax) visibleMax = colMax; - if (c == fillStart || colMin < visibleMin) visibleMin = colMin; - } - - if (m_colourScale == LogScale) { - visibleMin = LogRange::map(visibleMin); - visibleMax = LogRange::map(visibleMax); - if (visibleMin > visibleMax) std::swap(visibleMin, visibleMax); - } else if (m_colourScale == AbsoluteScale) { - if (visibleMin < 0) { - if (fabs(visibleMin) > fabs(visibleMax)) visibleMax = fabs(visibleMin); - else visibleMax = fabs(visibleMax); - visibleMin = 0; - } else { - visibleMin = fabs(visibleMin); - visibleMax = fabs(visibleMax); - } - } - } - - if (visibleMin == visibleMax) visibleMax = visibleMin + 1; - - int *peaks = 0; - if (m_peaksCache) { - peaks = new int[cacheHeight]; - for (int y = 0; y < cacheHeight; ++y) { - peaks[y] = 0; - } - } - - Profiler profiler2("Colour3DPlotLayer::fillCache: filling", true); - - for (int c = fillStart; c <= fillEnd; ++c) { - - values = getColumn(c); - - if (c >= m_cache->width()) { - cerr << "ERROR: column " << c << " >= cache width " - << m_cache->width() << endl; - continue; - } - - for (int y = 0; y < cacheHeight; ++y) { - - double value = min; - if (in_range_for(values, y)) { - value = values.at(y); - } - - value = value * m_gain; - - if (m_colourScale == LogScale) { - value = LogRange::map(value); - } else if (m_colourScale == AbsoluteScale) { - value = fabs(value); - } - - if (normalizeVisible) { - double norm = (value - visibleMin) / (visibleMax - visibleMin); - value = min + (max - min) * norm; - } - - int pixel = int(((value - min) * 256) / (max - min)); - if (pixel < 0) pixel = 0; - if (pixel > 255) pixel = 255; - if (peaks && (pixel > peaks[y])) peaks[y] = pixel; - - if (m_invertVertical) { - m_cache->setPixel(c, cacheHeight - y - 1, pixel); - } else { - if (y >= m_cache->height()) { - cerr << "ERROR: row " << y << " >= cache height " << m_cache->height() << endl; - } else { - m_cache->setPixel(c, y, pixel); - } - } - } - - if (peaks) { - int notch = (c % m_peakResolution); - if (notch == m_peakResolution-1 || c == fillEnd) { - int pc = c / m_peakResolution; - if (pc >= m_peaksCache->width()) { - cerr << "ERROR: peak column " << pc - << " (from col " << c << ") >= peaks cache width " - << m_peaksCache->width() << endl; - continue; - } - for (int y = 0; y < cacheHeight; ++y) { - if (m_invertVertical) { - m_peaksCache->setPixel(pc, cacheHeight - y - 1, peaks[y]); - } else { - if (y >= m_peaksCache->height()) { - cerr << "ERROR: row " << y - << " >= peaks cache height " - << m_peaksCache->height() << endl; - } else { - m_peaksCache->setPixel(pc, y, peaks[y]); - } - } - } - for (int y = 0; y < cacheHeight; ++y) { - peaks[y] = 0; - } - } - } - } - - delete[] peaks; -} - -bool -Colour3DPlotLayer::shouldPaintDenseIn(const LayerGeometryProvider *v) const -{ - if (!m_model || !v || !(v->getViewManager())) { - return false; - } - double srRatio = - v->getViewManager()->getMainModelSampleRate() / m_model->getSampleRate(); - if (m_opaque || - m_smooth || - m_model->getHeight() >= v->getPaintHeight() || - ((m_model->getResolution() * srRatio) / v->getZoomLevel()) < 2) { - return true; - } - return false; } void @@ -1306,378 +1139,18 @@ #endif return; } + + //!!!??? - if (m_normalizeVisibleArea && !m_normalizeColumns) rect = v->getPaintRect(); - - sv_frame_t modelStart = m_model->getStartFrame(); - sv_frame_t modelEnd = m_model->getEndFrame(); - int modelResolution = m_model->getResolution(); - - // The cache is from the model's start frame to the model's end - // frame at the model's window increment frames per pixel. We - // want to draw from our start frame + x0 * zoomLevel to our start - // frame + x1 * zoomLevel at zoomLevel frames per pixel. - - // We have quite different paint mechanisms for rendering "large" - // bins (more than one bin per pixel in both directions) and - // "small". This is "large"; see paintDense below for "small". - - int x0 = rect.left(); - int x1 = rect.right() + 1; - - int h = v->getPaintHeight(); - - double srRatio = - v->getViewManager()->getMainModelSampleRate() / m_model->getSampleRate(); - - // the s-prefix values are source, i.e. model, column and bin numbers - int sx0 = int((double(v->getFrameForX(x0)) / srRatio - double(modelStart)) - / modelResolution); - int sx1 = int((double(v->getFrameForX(x1)) / srRatio - double(modelStart)) - / modelResolution); - int sh = m_model->getHeight(); - - int symin = m_miny; - int symax = m_maxy; - if (symax <= symin) { - symin = 0; - symax = sh; - } - if (symin < 0) symin = 0; - if (symax > sh) symax = sh; - - if (sx0 > 0) --sx0; - fillCache(sx0 < 0 ? 0 : sx0, - sx1 < 0 ? 0 : sx1); - -#ifdef DEBUG_COLOUR_3D_PLOT_LAYER_PAINT - cerr << "Colour3DPlotLayer::paint: height = "<< m_model->getHeight() << ", modelStart = " << modelStart << ", resolution = " << modelResolution << ", model rate = " << m_model->getSampleRate() << " (zoom level = " << v->getZoomLevel() << ", srRatio = " << srRatio << ")" << endl; -#endif - - if (shouldPaintDenseIn(v)) { -#ifdef DEBUG_COLOUR_3D_PLOT_LAYER_PAINT - cerr << "calling paintDense" << endl; -#endif - paintDense(v, paint, rect); - return; + if (m_normalizeVisibleArea) { + rect = v->getPaintRect(); } -#ifdef DEBUG_COLOUR_3D_PLOT_LAYER_PAINT - cerr << "Colour3DPlotLayer::paint: w " << x1-x0 << ", h " << h << ", sx0 " << sx0 << ", sx1 " << sx1 << ", sw " << sx1-sx0 << ", sh " << sh << endl; - cerr << "Colour3DPlotLayer: sample rate is " << m_model->getSampleRate() << ", resolution " << m_model->getResolution() << endl; -#endif + //!!! why is the setLayerDormant(false) found here in + //!!! SpectrogramLayer not present in Colour3DPlotLayer? + //!!! unnecessary? vestigial? forgotten? - QPoint illuminatePos; - bool illuminate = v->shouldIlluminateLocalFeatures(this, illuminatePos); - - const int buflen = 40; - char labelbuf[buflen]; - - for (int sx = sx0; sx <= sx1; ++sx) { - - sv_frame_t fx = sx * modelResolution + modelStart; - - if (fx + modelResolution <= modelStart || fx > modelEnd) continue; - - int rx0 = v->getXForFrame(int(double(fx) * srRatio)); - int rx1 = v->getXForFrame(int(double(fx + modelResolution + 1) * srRatio)); - - int rw = rx1 - rx0; - if (rw < 1) rw = 1; - - bool showLabel = (rw > 10 && - paint.fontMetrics().width("0.000000") < rw - 3 && - paint.fontMetrics().height() < (h / sh)); - - for (int sy = symin; sy < symax; ++sy) { - - int ry0 = getIYForBin(v, sy); - int ry1 = getIYForBin(v, sy + 1); - QRect r(rx0, ry1, rw, ry0 - ry1); - - QRgb pixel = qRgb(255, 255, 255); - if (sx >= 0 && sx < m_cache->width() && - sy >= 0 && sy < m_cache->height()) { - pixel = m_cache->pixel(sx, sy); - } - - if (rw == 1) { - paint.setPen(pixel); - paint.setBrush(Qt::NoBrush); - paint.drawLine(r.x(), r.y(), r.x(), r.y() + r.height() - 1); - continue; - } - - QColor pen(255, 255, 255, 80); - QColor brush(pixel); - - if (rw > 3 && r.height() > 3) { - brush.setAlpha(160); - } - - paint.setPen(Qt::NoPen); - paint.setBrush(brush); - - if (illuminate) { - if (r.contains(illuminatePos)) { - paint.setPen(v->getForeground()); - } - } - -#ifdef DEBUG_COLOUR_3D_PLOT_LAYER_PAINT -// cerr << "rect " << r.x() << "," << r.y() << " " -// << r.width() << "x" << r.height() << endl; -#endif - - paint.drawRect(r); - - if (showLabel) { - if (sx >= 0 && sx < m_cache->width() && - sy >= 0 && sy < m_cache->height()) { - double value = m_model->getValueAt(sx, sy); - snprintf(labelbuf, buflen, "%06f", value); - QString text(labelbuf); - v->drawVisibleText - (paint, - rx0 + 2, - ry0 - h / sh - 1 + 2 + paint.fontMetrics().ascent(), - text, - View::OutlinedText); - } - } - } - } -} - -void -Colour3DPlotLayer::paintDense(LayerGeometryProvider *v, QPainter &paint, QRect rect) const -{ - Profiler profiler("Colour3DPlotLayer::paintDense", true); - if (!m_cache) return; - - double modelStart = double(m_model->getStartFrame()); - double modelResolution = double(m_model->getResolution()); - - sv_samplerate_t mmsr = v->getViewManager()->getMainModelSampleRate(); - sv_samplerate_t msr = m_model->getSampleRate(); - double srRatio = mmsr / msr; - - int x0 = rect.left(); - int x1 = rect.right() + 1; - - const int w = x1 - x0; // const so it can be used as array size below - int h = v->getPaintHeight(); // we always paint full height - int sh = m_model->getHeight(); - - int symin = m_miny; - int symax = m_maxy; - if (symax <= symin) { - symin = 0; - symax = sh; - } - if (symin < 0) symin = 0; - if (symax > sh) symax = sh; - - QImage img(w, h, QImage::Format_Indexed8); - img.setColorTable(m_cache->colorTable()); - - uchar *peaks = new uchar[w]; - memset(peaks, 0, w); - - int zoomLevel = v->getZoomLevel(); - - QImage *source = m_cache; - -#ifdef DEBUG_COLOUR_3D_PLOT_LAYER_PAINT - cerr << "modelResolution " << modelResolution << ", srRatio " - << srRatio << ", m_peakResolution " << m_peakResolution - << ", zoomLevel " << zoomLevel << ", result " - << ((modelResolution * srRatio * m_peakResolution) / zoomLevel) - << endl; -#endif - - if (m_peaksCache) { - if (((modelResolution * srRatio * m_peakResolution) / zoomLevel) < 1) { -#ifdef DEBUG_COLOUR_3D_PLOT_LAYER_PAINT - cerr << "using peaks cache" << endl; -#endif - source = m_peaksCache; - modelResolution *= m_peakResolution; - } else { -#ifdef DEBUG_COLOUR_3D_PLOT_LAYER_PAINT - cerr << "not using peaks cache" << endl; -#endif - } - } else { -#ifdef DEBUG_COLOUR_3D_PLOT_LAYER_PAINT - cerr << "have no peaks cache" << endl; -#endif - } - - int sw = source->width(); - - sv_frame_t xf = -1; - sv_frame_t nxf = v->getFrameForX(x0); - - double epsilon = 0.000001; - - vector<double> sxa(w*2); - - for (int x = 0; x < w; ++x) { - - xf = nxf; - nxf = xf + zoomLevel; - - double sx0 = (double(xf) / srRatio - modelStart) / modelResolution; - double sx1 = (double(nxf) / srRatio - modelStart) / modelResolution; - - sxa[x*2] = sx0; - sxa[x*2 + 1] = sx1; - } - - double logmin = symin+1, logmax = symax+1; - LogRange::mapRange(logmin, logmax); - -#ifdef DEBUG_COLOUR_3D_PLOT_LAYER_PAINT - cerr << "m_smooth = " << m_smooth << ", w = " << w << ", h = " << h << endl; -#endif - - if (m_smooth) { - - for (int y = 0; y < h; ++y) { - - double sy = getBinForY(v, y) - 0.5; - int syi = int(sy + epsilon); - if (syi < 0 || syi >= source->height()) continue; - - uchar *targetLine = img.scanLine(y); - uchar *sourceLine = source->scanLine(syi); - uchar *nextSource; - if (syi + 1 < source->height()) { - nextSource = source->scanLine(syi + 1); - } else { - nextSource = sourceLine; - } - - for (int x = 0; x < w; ++x) { - - targetLine[x] = 0; - - double sx0 = sxa[x*2]; - if (sx0 < 0) continue; - int sx0i = int(sx0 + epsilon); - if (sx0i >= sw) break; - - double a = sourceLine[sx0i]; - double b = a; - double value; - - double sx1 = sxa[x*2+1]; - if (sx1 > sx0 + 1.f) { - int sx1i = int(sx1); - bool have = false; - for (int sx = sx0i; sx <= sx1i; ++sx) { - if (sx < 0 || sx >= sw) continue; - if (!have) { - a = sourceLine[sx]; - b = nextSource[sx]; - have = true; - } else { - a = std::max(a, double(sourceLine[sx])); - b = std::max(b, double(nextSource[sx])); - } - } - double yprop = sy - syi; - value = (a * (1.f - yprop) + b * yprop); - } else { - a = sourceLine[sx0i]; - b = nextSource[sx0i]; - double yprop = sy - syi; - value = (a * (1.f - yprop) + b * yprop); - int oi = sx0i + 1; - double xprop = sx0 - sx0i; - xprop -= 0.5; - if (xprop < 0) { - oi = sx0i - 1; - xprop = -xprop; - } - if (oi < 0 || oi >= sw) oi = sx0i; - a = sourceLine[oi]; - b = nextSource[oi]; - value = (value * (1.f - xprop) + - (a * (1.f - yprop) + b * yprop) * xprop); - } - - int vi = int(lrint(value)); - if (vi > 255) vi = 255; - if (vi < 0) vi = 0; - targetLine[x] = uchar(vi); - } - } - } else { - - double sy0 = getBinForY(v, 0); - - int psy0i = -1, psy1i = -1; - - for (int y = 0; y < h; ++y) { - - double sy1 = sy0; - sy0 = getBinForY(v, double(y + 1)); - - int sy0i = int(sy0 + epsilon); - int sy1i = int(sy1); - - uchar *targetLine = img.scanLine(y); - - if (sy0i == psy0i && sy1i == psy1i) { - // same source scan line as just computed - goto copy; - } - - psy0i = sy0i; - psy1i = sy1i; - - for (int x = 0; x < w; ++x) { - peaks[x] = 0; - } - - for (int sy = sy0i; sy <= sy1i; ++sy) { - - if (sy < 0 || sy >= source->height()) continue; - - uchar *sourceLine = source->scanLine(sy); - - for (int x = 0; x < w; ++x) { - - double sx1 = sxa[x*2 + 1]; - if (sx1 < 0) continue; - int sx1i = int(sx1); - - double sx0 = sxa[x*2]; - if (sx0 < 0) continue; - int sx0i = int(sx0 + epsilon); - if (sx0i >= sw) break; - - uchar peak = 0; - for (int sx = sx0i; sx <= sx1i; ++sx) { - if (sx < 0 || sx >= sw) continue; - if (sourceLine[sx] > peak) peak = sourceLine[sx]; - } - peaks[x] = peak; - } - } - - copy: - for (int x = 0; x < w; ++x) { - targetLine[x] = peaks[x]; - } - } - } - - delete[] peaks; - - paint.drawImage(x0, 0, img); + paintWithRenderer(v, paint, rect); } bool @@ -1712,25 +1185,40 @@ { QString s = QString("scale=\"%1\" " "colourScheme=\"%2\" " - "normalizeColumns=\"%3\" " - "normalizeVisibleArea=\"%4\" " - "minY=\"%5\" " - "maxY=\"%6\" " - "invertVertical=\"%7\" " - "opaque=\"%8\" %9") - .arg((int)m_colourScale) + "minY=\"%3\" " + "maxY=\"%4\" " + "invertVertical=\"%5\" " + "opaque=\"%6\" %7") + .arg(convertFromColourScale(m_colourScale)) .arg(m_colourMap) - .arg(m_normalizeColumns ? "true" : "false") - .arg(m_normalizeVisibleArea ? "true" : "false") .arg(m_miny) .arg(m_maxy) .arg(m_invertVertical ? "true" : "false") .arg(m_opaque ? "true" : "false") .arg(QString("binScale=\"%1\" smooth=\"%2\" gain=\"%3\" ") - .arg((int)m_binScale) + .arg(int(m_binScale)) .arg(m_smooth ? "true" : "false") .arg(m_gain)); + // New-style normalization attributes, allowing for more types of + // normalization in future: write out the column normalization + // type separately, and then whether we are normalizing visible + // area as well afterwards + + s += QString("columnNormalization=\"%1\" ") + .arg(m_normalization == ColumnNormalization::Max1 ? "peak" : + m_normalization == ColumnNormalization::Hybrid ? "hybrid" : "none"); + + // Old-style normalization attribute, for backward compatibility + + s += QString("normalizeColumns=\"%1\" ") + .arg(m_normalization == ColumnNormalization::Max1 ? "true" : "false"); + + // And this applies to both old- and new-style attributes + + s += QString("normalizeVisibleArea=\"%1\" ") + .arg(m_normalizeVisibleArea ? "true" : "false"); + Layer::toXml(stream, indent, extraAttributes + " " + s); } @@ -1739,22 +1227,16 @@ { bool ok = false, alsoOk = false; - ColourScale scale = (ColourScale)attributes.value("scale").toInt(&ok); - if (ok) setColourScale(scale); + ColourScaleType colourScale = convertToColourScale + (attributes.value("colourScale").toInt(&ok)); + if (ok) setColourScale(colourScale); int colourMap = attributes.value("colourScheme").toInt(&ok); if (ok) setColourMap(colourMap); - BinScale binscale = (BinScale)attributes.value("binScale").toInt(&ok); - if (ok) setBinScale(binscale); - - bool normalizeColumns = - (attributes.value("normalizeColumns").trimmed() == "true"); - setNormalizeColumns(normalizeColumns); - - bool normalizeVisibleArea = - (attributes.value("normalizeVisibleArea").trimmed() == "true"); - setNormalizeVisibleArea(normalizeVisibleArea); + BinScale binScale = (BinScale) + attributes.value("binScale").toInt(&ok); + if (ok) setBinScale(binScale); bool invertVertical = (attributes.value("invertVertical").trimmed() == "true"); @@ -1774,5 +1256,50 @@ float min = attributes.value("minY").toFloat(&ok); float max = attributes.value("maxY").toFloat(&alsoOk); if (ok && alsoOk) setDisplayExtents(min, max); + + bool haveNewStyleNormalization = false; + + QString columnNormalization = attributes.value("columnNormalization"); + + if (columnNormalization != "") { + + haveNewStyleNormalization = true; + + if (columnNormalization == "peak") { + setNormalization(ColumnNormalization::Max1); + } else if (columnNormalization == "hybrid") { + setNormalization(ColumnNormalization::Hybrid); + } else if (columnNormalization == "none") { + setNormalization(ColumnNormalization::None); + } else { + cerr << "NOTE: Unknown or unsupported columnNormalization attribute \"" + << columnNormalization << "\"" << endl; + } + } + + if (!haveNewStyleNormalization) { + + setNormalization(ColumnNormalization::None); + + bool normalizeColumns = + (attributes.value("normalizeColumns").trimmed() == "true"); + if (normalizeColumns) { + setNormalization(ColumnNormalization::Max1); + } + + bool normalizeHybrid = + (attributes.value("normalizeHybrid").trimmed() == "true"); + if (normalizeHybrid) { + setNormalization(ColumnNormalization::Hybrid); + } + } + + bool normalizeVisibleArea = + (attributes.value("normalizeVisibleArea").trimmed() == "true"); + setNormalizeVisibleArea(normalizeVisibleArea); + + //!!! todo: check save/reload scaling, compare with + //!!! SpectrogramLayer, compare with prior SV versions, compare + //!!! with Tony v1 and v2 and their save files }
--- a/layer/Colour3DPlotLayer.h Mon Jun 13 11:44:08 2016 +0100 +++ b/layer/Colour3DPlotLayer.h Fri Aug 05 15:05:02 2016 +0100 @@ -13,10 +13,14 @@ COPYING included with this distribution for more information. */ -#ifndef _COLOUR_3D_PLOT_H_ -#define _COLOUR_3D_PLOT_H_ +#ifndef COLOUR_3D_PLOT_LAYER_H +#define COLOUR_3D_PLOT_LAYER_H #include "SliceableLayer.h" +#include "VerticalBinLayer.h" + +#include "ColourScale.h" +#include "Colour3DPlotRenderer.h" #include "data/model/DenseThreeDimensionalModel.h" @@ -30,14 +34,11 @@ * colour range. Its source is a DenseThreeDimensionalModel. * * This was the original implementation for the spectrogram view, but - * it was replaced with a more efficient implementation that derived - * the spectrogram itself from a DenseTimeValueModel instead of using - * a three-dimensional model. This class is retained in case it - * becomes useful, but it will probably need some cleaning up if it's - * ever actually used. + * it was replaced for that purpose with a more efficient + * implementation that derived the spectrogram itself from a + * DenseTimeValueModel instead of using a three-dimensional model. */ - -class Colour3DPlotLayer : public SliceableLayer +class Colour3DPlotLayer : public VerticalBinLayer { Q_OBJECT @@ -50,6 +51,7 @@ } virtual const Model *getModel() const { return m_model; } virtual void paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const; + virtual void setSynchronousPainting(bool synchronous); virtual int getVerticalScaleWidth(LayerGeometryProvider *v, bool, QPainter &) const; virtual void paintVerticalScale(LayerGeometryProvider *v, bool, QPainter &paint, QRect rect) const; @@ -81,19 +83,14 @@ int *min, int *max, int *deflt) const; virtual QString getPropertyValueLabel(const PropertyName &, int value) const; + virtual QString getPropertyValueIconName(const PropertyName &, + int value) const; virtual RangeMapper *getNewPropertyRangeMapper(const PropertyName &) const; virtual void setProperty(const PropertyName &, int value); virtual void setProperties(const QXmlAttributes &); - enum ColourScale { - LinearScale, - LogScale, - PlusMinusOneScale, - AbsoluteScale - }; - - void setColourScale(ColourScale); - ColourScale getColourScale() const { return m_colourScale; } + void setColourScale(ColourScaleType); + ColourScaleType getColourScale() const { return m_colourScale; } void setColourMap(int map); int getColourMap() const; @@ -104,11 +101,6 @@ */ void setGain(float gain); float getGain() const; - - enum BinScale { - LinearBinScale, - LogBinScale - }; /** * Specify the scale for the y axis. @@ -117,25 +109,17 @@ BinScale getBinScale() const; /** - * Normalize each column to its maximum value, independent of its - * neighbours. + * Specify the normalization mode for individual columns. */ - void setNormalizeColumns(bool n); - bool getNormalizeColumns() const; + void setNormalization(ColumnNormalization); + ColumnNormalization getNormalization() const; /** - * Normalize each value against the maximum in the visible region. + * Specify whether to normalize the visible area. */ - void setNormalizeVisibleArea(bool n); + void setNormalizeVisibleArea(bool); bool getNormalizeVisibleArea() const; - /** - * Normalize each column to its maximum value, and then scale by - * the log of the (absolute) maximum value. - */ - void setNormalizeHybrid(bool n); - bool getNormalizeHybrid() const; - void setInvertVertical(bool i); bool getInvertVertical() const; @@ -173,41 +157,50 @@ protected: const DenseThreeDimensionalModel *m_model; // I do not own this - mutable QImage *m_cache; - mutable QImage *m_peaksCache; - mutable int m_cacheValidStart; - mutable int m_cacheValidEnd; - - ColourScale m_colourScale; - bool m_colourScaleSet; - int m_colourMap; - float m_gain; - BinScale m_binScale; - bool m_normalizeColumns; - bool m_normalizeVisibleArea; - bool m_normalizeHybrid; - bool m_invertVertical; - bool m_opaque; - bool m_smooth; - int m_peakResolution; + ColourScaleType m_colourScale; + bool m_colourScaleSet; + int m_colourMap; + float m_gain; + BinScale m_binScale; + ColumnNormalization m_normalization; // of individual columns + bool m_normalizeVisibleArea; + bool m_invertVertical; + bool m_opaque; + bool m_smooth; + int m_peakResolution; // Minimum and maximum bin numbers visible within the view. We // always snap to whole bins at view edges. - int m_miny; - int m_maxy; + int m_miny; + int m_maxy; + bool m_synchronous; + + static ColourScaleType convertToColourScale(int value); + static int convertFromColourScale(ColourScaleType); + static std::pair<ColumnNormalization, bool> convertToColumnNorm(int value); + static int convertFromColumnNorm(ColumnNormalization norm, bool visible); + + mutable Dense3DModelPeakCache *m_peakCache; + const int m_peakCacheDivisor; + Dense3DModelPeakCache *getPeakCache() const; + + typedef std::map<int, MagnitudeRange> ViewMagMap; // key is view id + mutable ViewMagMap m_viewMags; + + typedef std::map<int, Colour3DPlotRenderer *> ViewRendererMap; // key is view id + mutable ViewRendererMap m_renderers; + + Colour3DPlotRenderer *getRenderer(const LayerGeometryProvider *) const; + void invalidateRenderers(); + /** * Return the y coordinate at which the given bin "starts" * (i.e. at the bottom of the bin, if the given bin is an integer * and the vertical scale is the usual way up). Bin number may be * fractional, to obtain a position part-way through a bin. */ - double getYForBin(LayerGeometryProvider *, double bin) const; - - /** - * As getYForBin, but rounding to integer values. - */ - int getIYForBin(LayerGeometryProvider *, int bin) const; + double getYForBin(const LayerGeometryProvider *, double bin) const; /** * Return the bin number, possibly fractional, at the given y @@ -215,25 +208,13 @@ * at which the bins "start" (i.e. the bottom of the visible bin, * if the vertical scale is the usual way up). */ - double getBinForY(LayerGeometryProvider *, double y) const; - - /** - * As getBinForY, but rounding to integer values. - */ - int getIBinForY(LayerGeometryProvider *, int y) const; + double getBinForY(const LayerGeometryProvider *, double y) const; DenseThreeDimensionalModel::Column getColumn(int col) const; - /** - * True if we have the opaque or smooth flag set, or if the cells - * are so small you can't see their borders. False for big, - * translucent cells. - */ - bool shouldPaintDenseIn(const LayerGeometryProvider *) const; + int getColourScaleWidth(QPainter &) const; - int getColourScaleWidth(QPainter &) const; - void fillCache(int firstBin, int lastBin) const; - void paintDense(LayerGeometryProvider *v, QPainter &paint, QRect rect) const; + void paintWithRenderer(LayerGeometryProvider *v, QPainter &paint, QRect rect) const; }; #endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layer/Colour3DPlotRenderer.cpp Fri Aug 05 15:05:02 2016 +0100 @@ -0,0 +1,1051 @@ +/* -*- 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 file copyright 2006-2016 Chris Cannam and QMUL. + + 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 "Colour3DPlotRenderer.h" +#include "RenderTimer.h" + +#include "base/Profiler.h" + +#include "data/model/DenseThreeDimensionalModel.h" +#include "data/model/Dense3DModelPeakCache.h" +#include "data/model/FFTModel.h" + +#include "LayerGeometryProvider.h" +#include "VerticalBinLayer.h" +#include "PaintAssistant.h" +#include "ImageRegionFinder.h" + +#include "view/ViewManager.h" // for main model sample rate. Pity + +#include <vector> + +//#define DEBUG_COLOUR_PLOT_REPAINT 1 + +using namespace std; + +Colour3DPlotRenderer::RenderResult +Colour3DPlotRenderer::render(const LayerGeometryProvider *v, QPainter &paint, QRect rect) +{ + return render(v, paint, rect, false); +} + +Colour3DPlotRenderer::RenderResult +Colour3DPlotRenderer::renderTimeConstrained(const LayerGeometryProvider *v, + QPainter &paint, QRect rect) +{ + return render(v, paint, rect, true); +} + +QRect +Colour3DPlotRenderer::getLargestUncachedRect(const LayerGeometryProvider *v) +{ + RenderType renderType = decideRenderType(v); + + if (renderType == DirectTranslucent) { + return QRect(); // never cached + } + + int h = m_cache.getSize().height(); + + QRect areaLeft(0, 0, m_cache.getValidLeft(), h); + QRect areaRight(m_cache.getValidRight(), 0, + m_cache.getSize().width() - m_cache.getValidRight(), h); + + if (areaRight.width() > areaLeft.width()) { + return areaRight; + } else { + return areaLeft; + } +} + +bool +Colour3DPlotRenderer::geometryChanged(const LayerGeometryProvider *v) +{ + RenderType renderType = decideRenderType(v); + + if (renderType == DirectTranslucent) { + return true; // never cached + } + + if (m_cache.getSize() == v->getPaintSize() && + m_cache.getZoomLevel() == v->getZoomLevel() && + m_cache.getStartFrame() == v->getStartFrame()) { + return false; + } else { + return true; + } +} + +Colour3DPlotRenderer::RenderResult +Colour3DPlotRenderer::render(const LayerGeometryProvider *v, + QPainter &paint, QRect rect, bool timeConstrained) +{ + RenderType renderType = decideRenderType(v); + + if (renderType != DrawBufferPixelResolution) { + // Rendering should be fast in bin-resolution and direct draw + // cases because we are quite well zoomed-in, and the sums are + // easier this way. Calculating boundaries later will be + // fiddly for partial paints otherwise. + timeConstrained = false; + } + + 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(); + + sv_frame_t startFrame = v->getStartFrame(); + + m_cache.resize(v->getPaintSize()); + m_cache.setZoomLevel(v->getZoomLevel()); + + m_magCache.resize(v->getPaintSize().width()); + m_magCache.setZoomLevel(v->getZoomLevel()); + + if (renderType == DirectTranslucent) { + MagnitudeRange range = renderDirectTranslucent(v, paint, rect); + return { rect, range }; + } + +#ifdef DEBUG_COLOUR_PLOT_REPAINT + cerr << "cache start " << m_cache.getStartFrame() + << " valid left " << m_cache.getValidLeft() + << " valid right " << m_cache.getValidRight() + << endl; + cerr << " view start " << startFrame + << " x0 " << x0 + << " x1 " << x1 + << endl; +#endif + + if (m_cache.isValid()) { // some part of the cache is valid + + if (v->getXForFrame(m_cache.getStartFrame()) == + v->getXForFrame(startFrame) && + m_cache.getValidLeft() <= x0 && + m_cache.getValidRight() >= x1) { + +#ifdef DEBUG_COLOUR_PLOT_REPAINT + cerr << "cache hit" << endl; +#endif + + // cache is valid for the complete requested area + paint.drawImage(rect, m_cache.getImage(), rect); + + MagnitudeRange range = m_magCache.getRange(x0, x1 - x0); + + return { rect, range }; + + } else { +#ifdef DEBUG_COLOUR_PLOT_REPAINT + cerr << "cache partial hit" << endl; +#endif + + // cache doesn't begin at the right frame or doesn't + // contain the complete view, but might be scrollable or + // partially usable + m_cache.scrollTo(v, startFrame); + m_magCache.scrollTo(v, startFrame); + + // if we are not time-constrained, then we want to paint + // the whole area in one go; we don't return a partial + // paint. To avoid providing the more complex logic to + // handle painting discontiguous areas, if the only valid + // part of cache is in the middle, just make the whole + // thing invalid and start again. + if (!timeConstrained) { + if (m_cache.getValidLeft() > x0 && + m_cache.getValidRight() < x1) { + m_cache.invalidate(); + } + } + } + } else { + // cache is completely invalid + m_cache.setStartFrame(startFrame); + m_magCache.setStartFrame(startFrame); + } + + bool rightToLeft = false; + + int reqx0 = x0; + int reqx1 = x1; + + if (!m_cache.isValid() && timeConstrained) { + // When rendering the whole area, in a context where we might + // not be able to complete the work, start from somewhere near + // the middle so that the region of interest appears first + + //!!! (perhaps we should avoid doing this if past repaints + //!!! have been fast enough to do the whole in one shot) + if (x0 == 0 && x1 == v->getPaintWidth()) { + x0 = int(x1 * 0.3); + } + } + + if (m_cache.isValid()) { + + // 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; + m_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; + } + + // Note, we always paint the full height to cache. We want to + // ensure the cache is coherent without having to worry about + // vertical matching of required and valid areas as well as + // horizontal. + + if (renderType == DrawBufferBinResolution) { + + renderToCacheBinResolution(v, x0, x1 - x0); + + } else { // must be DrawBufferPixelResolution, handled DirectTranslucent earlier + + renderToCachePixelResolution(v, x0, x1 - x0, rightToLeft, timeConstrained); + } + + QRect pr = rect & m_cache.getValidArea(); + paint.drawImage(pr.x(), pr.y(), m_cache.getImage(), + pr.x(), pr.y(), pr.width(), pr.height()); + + if (!timeConstrained && (pr != rect)) { + cerr << "WARNING: failed to render entire requested rect " + << "even when not time-constrained" << endl; + } + + MagnitudeRange range = m_magCache.getRange(reqx0, reqx1 - reqx0); + + return { pr, range }; +} + +Colour3DPlotRenderer::RenderType +Colour3DPlotRenderer::decideRenderType(const LayerGeometryProvider *v) const +{ + const DenseThreeDimensionalModel *model = m_sources.source; + if (!model || !v || !(v->getViewManager())) { + return DrawBufferPixelResolution; // or anything + } + + int binResolution = model->getResolution(); + int zoomLevel = v->getZoomLevel(); + sv_samplerate_t modelRate = model->getSampleRate(); + + double rateRatio = v->getViewManager()->getMainModelSampleRate() / modelRate; + double relativeBinResolution = binResolution * rateRatio; + + if (m_params.binDisplay == BinDisplay::PeakFrequencies) { + // no alternative works here + return DrawBufferPixelResolution; + } + + if (!m_params.alwaysOpaque && !m_params.interpolate) { + + // consider translucent option -- only if not smoothing & not + // explicitly requested opaque & sufficiently zoomed-in + + if (model->getHeight() * 3 < v->getPaintHeight() && + relativeBinResolution >= 3 * zoomLevel) { + return DirectTranslucent; + } + } + + if (relativeBinResolution > zoomLevel) { + return DrawBufferBinResolution; + } else { + return DrawBufferPixelResolution; + } +} + +ColumnOp::Column +Colour3DPlotRenderer::getColumn(int sx, int minbin, int nbins) const +{ + // order: + // get column -> scale -> normalise -> record extents -> + // peak pick -> distribute/interpolate -> apply display gain + + // we do the first bit here: + // get column -> scale -> normalise + + ColumnOp::Column column; + + if (m_params.colourScale.getScale() == ColourScaleType::Phase && + m_sources.fft) { + + ColumnOp::Column fullColumn = m_sources.fft->getPhases(sx); + + column = vector<float>(fullColumn.data() + minbin, + fullColumn.data() + minbin + nbins); + + } else { + + ColumnOp::Column fullColumn = m_sources.source->getColumn(sx); + + column = vector<float>(fullColumn.data() + minbin, + fullColumn.data() + minbin + nbins); + + column = ColumnOp::applyGain(column, m_params.scaleFactor); + + column = ColumnOp::normalize(column, m_params.normalization); + } + + return column; +} + +MagnitudeRange +Colour3DPlotRenderer::renderDirectTranslucent(const LayerGeometryProvider *v, + QPainter &paint, + QRect rect) +{ + Profiler profiler("Colour3DPlotRenderer::renderDirectTranslucent"); + + MagnitudeRange magRange; + + QPoint illuminatePos; + bool illuminate = v->shouldIlluminateLocalFeatures + (m_sources.verticalBinLayer, illuminatePos); + + const DenseThreeDimensionalModel *model = m_sources.source; + + int x0 = rect.left(); + int x1 = rect.right() + 1; + + int h = v->getPaintHeight(); + + sv_frame_t modelStart = model->getStartFrame(); + sv_frame_t modelEnd = model->getEndFrame(); + int modelResolution = model->getResolution(); + + double rateRatio = + v->getViewManager()->getMainModelSampleRate() / model->getSampleRate(); + + // the s-prefix values are source, i.e. model, column and bin numbers + int sx0 = int((double(v->getFrameForX(x0)) / rateRatio - double(modelStart)) + / modelResolution); + int sx1 = int((double(v->getFrameForX(x1)) / rateRatio - double(modelStart)) + / modelResolution); + + int sh = model->getHeight(); + + const int buflen = 40; + char labelbuf[buflen]; + + int minbin = m_sources.verticalBinLayer->getIBinForY(v, h); + if (minbin >= sh) minbin = sh - 1; + if (minbin < 0) minbin = 0; + + int nbins = m_sources.verticalBinLayer->getIBinForY(v, 0) - minbin + 1; + if (minbin + nbins > sh) nbins = sh - minbin; + + int psx = -1; + + vector<float> preparedColumn; + + int modelWidth = model->getWidth(); + + for (int sx = sx0; sx <= sx1; ++sx) { + + if (sx < 0 || sx >= modelWidth) { + continue; + } + + if (sx != psx) { + + // order: + // get column -> scale -> normalise -> record extents -> + // peak pick -> distribute/interpolate -> apply display gain + + // this does the first three: + preparedColumn = getColumn(sx, minbin, nbins); + + magRange.sample(preparedColumn); + + if (m_params.binDisplay == BinDisplay::PeakBins) { + preparedColumn = ColumnOp::peakPick(preparedColumn); + } + + // Display gain belongs to the colour scale and is + // applied by the colour scale object when mapping it + + psx = sx; + } + + sv_frame_t fx = sx * modelResolution + modelStart; + + if (fx + modelResolution <= modelStart || fx > modelEnd) continue; + + int rx0 = v->getXForFrame(int(double(fx) * rateRatio)); + int rx1 = v->getXForFrame(int(double(fx + modelResolution + 1) * rateRatio)); + + int rw = rx1 - rx0; + if (rw < 1) rw = 1; + + bool showLabel = (rw > 10 && + paint.fontMetrics().width("0.000000") < rw - 3 && + paint.fontMetrics().height() < (h / sh)); + + for (int sy = minbin; sy < minbin + nbins; ++sy) { + + int ry0 = m_sources.verticalBinLayer->getIYForBin(v, sy); + int ry1 = m_sources.verticalBinLayer->getIYForBin(v, sy + 1); + + if (m_params.invertVertical) { + ry0 = h - ry0 - 1; + ry1 = h - ry1 - 1; + } + + QRect r(rx0, ry1, rw, ry0 - ry1); + + float value = preparedColumn[sy - minbin]; + QColor colour = m_params.colourScale.getColour(value, + m_params.colourRotation); + + if (rw == 1) { + paint.setPen(colour); + paint.setBrush(Qt::NoBrush); + paint.drawLine(r.x(), r.y(), r.x(), r.y() + r.height() - 1); + continue; + } + + QColor pen(255, 255, 255, 80); + QColor brush(colour); + + if (rw > 3 && r.height() > 3) { + brush.setAlpha(160); + } + + paint.setPen(Qt::NoPen); + paint.setBrush(brush); + + if (illuminate) { + if (r.contains(illuminatePos)) { + paint.setPen(v->getForeground()); + } + } + +#ifdef DEBUG_COLOUR_3D_PLOT_LAYER_PAINT +// cerr << "rect " << r.x() << "," << r.y() << " " +// << r.width() << "x" << r.height() << endl; +#endif + + paint.drawRect(r); + + if (showLabel) { + double value = model->getValueAt(sx, sy); + snprintf(labelbuf, buflen, "%06f", value); + QString text(labelbuf); + PaintAssistant::drawVisibleText + (v, + paint, + rx0 + 2, + ry0 - h / sh - 1 + 2 + paint.fontMetrics().ascent(), + text, + PaintAssistant::OutlinedText); + } + } + } + + return magRange; +} + +void +Colour3DPlotRenderer::renderToCachePixelResolution(const LayerGeometryProvider *v, + int x0, int repaintWidth, + bool rightToLeft, + bool timeConstrained) +{ + Profiler profiler("Colour3DPlotRenderer::renderToCachePixelResolution"); +#ifdef DEBUG_COLOUR_PLOT_REPAINT + cerr << "renderToCachePixelResolution" << endl; +#endif + + // Draw to the draw buffer, and then copy from there. The draw + // buffer is at the same resolution as the target in the cache, so + // no extra scaling needed. + + const DenseThreeDimensionalModel *model = m_sources.source; + if (!model || !model->isOK() || !model->isReady()) { + throw std::logic_error("no source model provided, or model not ready"); + } + + int h = v->getPaintHeight(); + + clearDrawBuffer(repaintWidth, h); + + vector<int> binforx(repaintWidth); + vector<double> binfory(h); + + bool usePeaksCache = false; + int binsPerPeak = 1; + int zoomLevel = v->getZoomLevel(); + int binResolution = model->getResolution(); + + for (int x = 0; x < repaintWidth; ++x) { + sv_frame_t f0 = v->getFrameForX(x0 + x); + double s0 = double(f0 - model->getStartFrame()) / binResolution; + binforx[x] = int(s0 + 0.0001); + } + + if (m_sources.peaks) { // peaks cache exists + + binsPerPeak = m_sources.peaks->getColumnsPerPeak(); + usePeaksCache = (binResolution * binsPerPeak) < zoomLevel; + + if (m_params.colourScale.getScale() == + ColourScaleType::Phase) { + usePeaksCache = false; + } + } + +#ifdef DEBUG_COLOUR_PLOT_REPAINT + cerr << "[PIX] zoomLevel = " << zoomLevel + << ", binResolution " << binResolution + << ", binsPerPeak " << binsPerPeak + << ", peak cache " << m_sources.peaks + << ", usePeaksCache = " << usePeaksCache + << endl; +#endif + + for (int y = 0; y < h; ++y) { + binfory[y] = m_sources.verticalBinLayer->getBinForY(v, h - y - 1); + } + + int attainedWidth; + + if (m_params.binDisplay == BinDisplay::PeakFrequencies) { + attainedWidth = renderDrawBufferPeakFrequencies(v, + repaintWidth, + h, + binforx, + binfory, + rightToLeft, + timeConstrained); + + } else { + attainedWidth = renderDrawBuffer(repaintWidth, + h, + binforx, + binfory, + usePeaksCache, + rightToLeft, + timeConstrained); + } + + if (attainedWidth == 0) return; + + // draw buffer is pixel resolution, no scaling factors or padding involved + + int paintedLeft = x0; + if (rightToLeft) { + paintedLeft += (repaintWidth - attainedWidth); + } + + m_cache.drawImage(paintedLeft, attainedWidth, + m_drawBuffer, + paintedLeft - x0, attainedWidth); + + for (int i = 0; in_range_for(m_magRanges, i); ++i) { + m_magCache.sampleColumn(i, m_magRanges.at(i)); + } +} + +void +Colour3DPlotRenderer::renderToCacheBinResolution(const LayerGeometryProvider *v, + int x0, int repaintWidth) +{ + Profiler profiler("Colour3DPlotRenderer::renderToCacheBinResolution"); +#ifdef DEBUG_COLOUR_PLOT_REPAINT + cerr << "renderToCacheBinResolution" << endl; +#endif + + // Draw to the draw buffer, and then scale-copy from there. Draw + // buffer is at bin resolution, i.e. buffer x == source column + // number. We use toolkit smooth scaling for interpolation. + + const DenseThreeDimensionalModel *model = m_sources.source; + if (!model || !model->isOK() || !model->isReady()) { + throw std::logic_error("no source model provided, or model not ready"); + } + + // The draw buffer will contain a fragment at bin resolution. We + // need to ensure that it starts and ends at points where a + // time-bin boundary occurs at an exact pixel boundary, and with a + // certain amount of overlap across existing pixels so that we can + // scale and draw from it without smoothing errors at the edges. + + // If (getFrameForX(x) / increment) * increment == + // getFrameForX(x), then x is a time-bin boundary. We want two + // such boundaries at either side of the draw buffer -- one which + // we draw up to, and one which we subsequently crop at. + + sv_frame_t leftBoundaryFrame = -1, leftCropFrame = -1; + sv_frame_t rightBoundaryFrame = -1, rightCropFrame = -1; + + int drawBufferWidth; + int binResolution = model->getResolution(); + + for (int x = x0; ; --x) { + sv_frame_t f = v->getFrameForX(x); + if ((f / binResolution) * binResolution == f) { + if (leftCropFrame == -1) leftCropFrame = f; + else if (x < x0 - 2) { + leftBoundaryFrame = f; + break; + } + } + } + for (int x = x0 + repaintWidth; ; ++x) { + sv_frame_t f = v->getFrameForX(x); + if ((f / binResolution) * binResolution == f) { + if (rightCropFrame == -1) rightCropFrame = f; + else if (x > x0 + repaintWidth + 2) { + rightBoundaryFrame = f; + break; + } + } + } + drawBufferWidth = int + ((rightBoundaryFrame - leftBoundaryFrame) / binResolution); + + int h = v->getPaintHeight(); + + // For our purposes here, the draw buffer needs to be exactly our + // target size (so we recreate always rather than just clear it) + + recreateDrawBuffer(drawBufferWidth, h); + + vector<int> binforx(drawBufferWidth); + vector<double> binfory(h); + + for (int x = 0; x < drawBufferWidth; ++x) { + binforx[x] = int(leftBoundaryFrame / binResolution) + x; + } + +#ifdef DEBUG_COLOUR_PLOT_REPAINT + cerr << "[BIN] binResolution " << binResolution + << endl; +#endif + + for (int y = 0; y < h; ++y) { + binfory[y] = m_sources.verticalBinLayer->getBinForY(v, h - y - 1); + } + + int attainedWidth = renderDrawBuffer(drawBufferWidth, + h, + binforx, + binfory, + false, + false, + false); + + if (attainedWidth == 0) return; + + int scaledLeft = v->getXForFrame(leftBoundaryFrame); + int scaledRight = v->getXForFrame(rightBoundaryFrame); + +#ifdef DEBUG_COLOUR_PLOT_REPAINT + cerr << "scaling draw buffer from width " << m_drawBuffer.width() + << " to " << (scaledRight - scaledLeft) << " (nb drawBufferWidth = " + << drawBufferWidth << ")" << endl; +#endif + + QImage scaled = m_drawBuffer.scaled + (scaledRight - scaledLeft, h, + Qt::IgnoreAspectRatio, (m_params.interpolate ? + Qt::SmoothTransformation : + Qt::FastTransformation)); + + int scaledLeftCrop = v->getXForFrame(leftCropFrame); + int scaledRightCrop = v->getXForFrame(rightCropFrame); + + int targetLeft = scaledLeftCrop; + if (targetLeft < 0) { + targetLeft = 0; + } + + int targetWidth = scaledRightCrop - targetLeft; + if (targetLeft + targetWidth > m_cache.getSize().width()) { + targetWidth = m_cache.getSize().width() - targetLeft; + } + + int sourceLeft = targetLeft - scaledLeft; + if (sourceLeft < 0) { + sourceLeft = 0; + } + +#ifdef DEBUG_COLOUR_PLOT_REPAINT + cerr << "repaintWidth = " << repaintWidth + << ", targetWidth = " << targetWidth << endl; +#endif + + if (targetWidth > 0) { + // we are copying from an image that has already been scaled, + // hence using the same width in both geometries + m_cache.drawImage(targetLeft, targetWidth, + scaled, + sourceLeft, targetWidth); + } + + for (int i = 0; i < targetWidth; ++i) { + // but the mag range vector has not been scaled + int sourceIx = int((double(i + sourceLeft) / scaled.width()) + * int(m_magRanges.size())); + if (in_range_for(m_magRanges, sourceIx)) { + m_magCache.sampleColumn(i, m_magRanges.at(sourceIx)); + } + } +} + +int +Colour3DPlotRenderer::renderDrawBuffer(int w, int h, + const vector<int> &binforx, + const vector<double> &binfory, + bool usePeaksCache, + bool rightToLeft, + bool timeConstrained) +{ + // Callers must have checked that the appropriate subset of + // Sources data members are set for the supplied flags (e.g. that + // peaks model exists if usePeaksCache) + + RenderTimer timer(timeConstrained ? + RenderTimer::FastRender : + RenderTimer::NoTimeout); + + int divisor = 1; + const DenseThreeDimensionalModel *sourceModel = m_sources.source; + if (usePeaksCache) { + divisor = m_sources.peaks->getColumnsPerPeak(); + sourceModel = m_sources.peaks; + } + + int sh = sourceModel->getHeight(); + + int minbin = int(binfory[0] + 0.0001); + if (minbin >= sh) minbin = sh - 1; + if (minbin < 0) minbin = 0; + + int nbins = int(binfory[h-1]) - minbin + 1; + if (minbin + nbins > sh) nbins = sh - minbin; + + int psx = -1; + + int start = 0; + int finish = w; + int step = 1; + + if (rightToLeft) { + start = w-1; + finish = -1; + step = -1; + } + + int columnCount = 0; + + vector<float> preparedColumn; + + int modelWidth = sourceModel->getWidth(); + +#ifdef DEBUG_COLOUR_PLOT_REPAINT + cerr << "modelWidth " << modelWidth << ", divisor " << divisor << endl; +#endif + + for (int x = start; x != finish; x += step) { + + // x is the on-canvas pixel coord; sx (later) will be the + // source column index + + ++columnCount; + +#ifdef DEBUG_COLOUR_PLOT_REPAINT + cerr << "x = " << x << ", binforx[x] = " << binforx[x] << endl; +#endif + + if (binforx[x] < 0) continue; + + int sx0 = binforx[x] / divisor; + int sx1 = sx0; + if (x+1 < w) sx1 = binforx[x+1] / divisor; + if (sx0 < 0) sx0 = sx1 - 1; + if (sx0 < 0) continue; + if (sx1 <= sx0) sx1 = sx0 + 1; + + vector<float> pixelPeakColumn; + MagnitudeRange magRange; + + for (int sx = sx0; sx < sx1; ++sx) { + +#ifdef DEBUG_COLOUR_PLOT_REPAINT + cerr << "sx = " << sx << endl; +#endif + + if (sx < 0 || sx >= modelWidth) { + continue; + } + + if (sx != psx) { + + // order: + // get column -> scale -> normalise -> record extents -> + // peak pick -> distribute/interpolate -> apply display gain + + // this does the first three: + ColumnOp::Column column = getColumn(sx, minbin, nbins); + + magRange.sample(column); + + if (m_params.binDisplay == BinDisplay::PeakBins) { + column = ColumnOp::peakPick(column); + } + + preparedColumn = + ColumnOp::distribute(column, + h, + binfory, + minbin, + m_params.interpolate); + + // Display gain belongs to the colour scale and is + // applied by the colour scale object when mapping it + + psx = sx; + } + + if (sx == sx0) { + pixelPeakColumn = preparedColumn; + } else { + for (int i = 0; in_range_for(pixelPeakColumn, i); ++i) { + pixelPeakColumn[i] = std::max(pixelPeakColumn[i], + preparedColumn[i]); + } + } + } + + if (!pixelPeakColumn.empty()) { + + for (int y = 0; y < h; ++y) { + int py; + if (m_params.invertVertical) { + py = y; + } else { + py = h - y - 1; + } + m_drawBuffer.setPixel + (x, + py, + m_params.colourScale.getPixel(pixelPeakColumn[y])); + } + + m_magRanges.push_back(magRange); + } + + double fractionComplete = double(columnCount) / double(w); + if (timer.outOfTime(fractionComplete)) { + cerr << "out of time" << endl; + return columnCount; + } + } + + return columnCount; +} + +int +Colour3DPlotRenderer::renderDrawBufferPeakFrequencies(const LayerGeometryProvider *v, + int w, int h, + const vector<int> &binforx, + const vector<double> &binfory, + bool rightToLeft, + bool timeConstrained) +{ + // Callers must have checked that the appropriate subset of + // Sources data members are set for the supplied flags (e.g. that + // fft model exists) + + RenderTimer timer(timeConstrained ? + RenderTimer::FastRender : + RenderTimer::NoTimeout); + + const FFTModel *fft = m_sources.fft; + + int sh = fft->getHeight(); + + int minbin = int(binfory[0] + 0.0001); + if (minbin >= sh) minbin = sh - 1; + if (minbin < 0) minbin = 0; + + int nbins = int(binfory[h-1]) - minbin + 1; + if (minbin + nbins > sh) nbins = sh - minbin; + + FFTModel::PeakSet peakfreqs; + + int psx = -1; + + int start = 0; + int finish = w; + int step = 1; + + if (rightToLeft) { + start = w-1; + finish = -1; + step = -1; + } + + int columnCount = 0; + + vector<float> preparedColumn; + + int modelWidth = fft->getWidth(); +#ifdef DEBUG_COLOUR_PLOT_REPAINT + cerr << "modelWidth " << modelWidth << endl; +#endif + + double minFreq = + (double(minbin) * fft->getSampleRate()) / fft->getFFTSize(); + double maxFreq = + (double(minbin + nbins - 1) * fft->getSampleRate()) / fft->getFFTSize(); + + bool logarithmic = (m_params.binScale == BinScale::Log); + + for (int x = start; x != finish; x += step) { + + // x is the on-canvas pixel coord; sx (later) will be the + // source column index + + ++columnCount; + + if (binforx[x] < 0) continue; + + int sx0 = binforx[x]; + int sx1 = sx0; + if (x+1 < w) sx1 = binforx[x+1]; + if (sx0 < 0) sx0 = sx1 - 1; + if (sx0 < 0) continue; + if (sx1 <= sx0) sx1 = sx0 + 1; + + vector<float> pixelPeakColumn; + MagnitudeRange magRange; + + for (int sx = sx0; sx < sx1; ++sx) { + + if (sx < 0 || sx >= modelWidth) { + continue; + } + + if (sx != psx) { + preparedColumn = getColumn(sx, minbin, nbins); + magRange.sample(preparedColumn); + psx = sx; + } + + if (sx == sx0) { + pixelPeakColumn = preparedColumn; + peakfreqs = fft->getPeakFrequencies(FFTModel::AllPeaks, sx, + minbin, minbin + nbins - 1); + } else { + for (int i = 0; in_range_for(pixelPeakColumn, i); ++i) { + pixelPeakColumn[i] = std::max(pixelPeakColumn[i], + preparedColumn[i]); + } + } + } + + if (!pixelPeakColumn.empty()) { + + for (FFTModel::PeakSet::const_iterator pi = peakfreqs.begin(); + pi != peakfreqs.end(); ++pi) { + + int bin = pi->first; + double freq = pi->second; + + if (bin < minbin) continue; + if (bin >= minbin + nbins) break; + + double value = pixelPeakColumn[bin - minbin]; + + double y = v->getYForFrequency + (freq, minFreq, maxFreq, logarithmic); + + int iy = int(y + 0.5); + if (iy < 0 || iy >= h) continue; + + m_drawBuffer.setPixel + (x, + iy, + m_params.colourScale.getPixel(value)); + } + + m_magRanges.push_back(magRange); + } + + double fractionComplete = double(columnCount) / double(w); + if (timer.outOfTime(fractionComplete)) { + return columnCount; + } + } + + return columnCount; +} + +void +Colour3DPlotRenderer::recreateDrawBuffer(int w, int h) +{ + m_drawBuffer = QImage(w, h, QImage::Format_Indexed8); + + for (int pixel = 0; pixel < 256; ++pixel) { + m_drawBuffer.setColor + ((unsigned char)pixel, + m_params.colourScale.getColourForPixel + (pixel, m_params.colourRotation).rgb()); + } + + m_drawBuffer.fill(0); + m_magRanges.clear(); +} + +void +Colour3DPlotRenderer::clearDrawBuffer(int w, int h) +{ + if (m_drawBuffer.width() < w || m_drawBuffer.height() != h) { + recreateDrawBuffer(w, h); + } else { + m_drawBuffer.fill(0); + m_magRanges.clear(); + } +} + +QRect +Colour3DPlotRenderer::findSimilarRegionExtents(QPoint p) const +{ + QImage image = m_cache.getImage(); + ImageRegionFinder finder; + QRect rect = finder.findRegionExtents(&image, p); + return rect; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layer/Colour3DPlotRenderer.h Fri Aug 05 15:05:02 2016 +0100 @@ -0,0 +1,309 @@ +/* -*- 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 file copyright 2006-2016 Chris Cannam and QMUL. + + 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 COLOUR_3D_PLOT_RENDERER_H +#define COLOUR_3D_PLOT_RENDERER_H + +#include "ColourScale.h" +#include "ScrollableImageCache.h" +#include "ScrollableMagRangeCache.h" + +#include "base/ColumnOp.h" +#include "base/MagnitudeRange.h" + +#include <QRect> +#include <QPainter> +#include <QImage> + +class LayerGeometryProvider; +class VerticalBinLayer; +class DenseThreeDimensionalModel; +class Dense3DModelPeakCache; +class FFTModel; + +enum class BinDisplay { + AllBins, + PeakBins, + PeakFrequencies +}; + +enum class BinScale { + Linear, + Log +}; + +class Colour3DPlotRenderer +{ +public: + struct Sources { + Sources() : verticalBinLayer(0), source(0), peaks(0), fft(0) { } + + // These must all outlive this class + const VerticalBinLayer *verticalBinLayer; // always + const DenseThreeDimensionalModel *source; // always + const Dense3DModelPeakCache *peaks; // optionally + const FFTModel *fft; // optionally + }; + + struct Parameters { + Parameters() : + colourScale(ColourScale::Parameters()), + normalization(ColumnNormalization::None), + binDisplay(BinDisplay::AllBins), + binScale(BinScale::Linear), + alwaysOpaque(false), + interpolate(false), + invertVertical(false), + scaleFactor(1.0), + colourRotation(0) { } + + /** A complete ColourScale object by value, used for colour + * map conversion. Note that the final display gain setting is + * also encapsulated here. */ + ColourScale colourScale; + + /** Type of column normalization. */ + ColumnNormalization normalization; + + /** Selection of bins to display. */ + BinDisplay binDisplay; + + /** Scale for vertical bin spacing (linear or logarithmic). */ + BinScale binScale; + + /** Whether cells should always be opaque. If false, then + * large cells (when zoomed in a long way) will be rendered + * translucent in order not to obscure anything in a layer + * beneath. */ + bool alwaysOpaque; + + /** Whether to apply smoothing when rendering cells at more + * than one pixel per cell. !!! todo: decide about separating + * out x-interpolate and y-interpolate as the spectrogram + * actually does (or used to) + */ + bool interpolate; + + /** Whether to render the whole caboodle upside-down. */ + bool invertVertical; + + /** Initial scale factor (e.g. for FFT scaling). This factor + * is applied to all values read from the underlying model + * *before* magnitude ranges are calculated, in contrast to + * the display gain found in the ColourScale parameter. */ + double scaleFactor; + + /** Colourmap rotation, in the range 0-255. */ + int colourRotation; + }; + + Colour3DPlotRenderer(Sources sources, Parameters parameters) : + m_sources(sources), + m_params(parameters) + { } + + struct RenderResult { + /** + * The rect that was actually rendered. May be equal to the + * rect that was requested to render, or may be smaller if + * time ran out and the complete flag was not set. + */ + QRect rendered; + + /** + * The magnitude range of the data in the rendered area. + */ + MagnitudeRange range; + }; + + /** + * Render the requested area using the given painter, obtaining + * geometry (e.g. start frame) from the given + * LayerGeometryProvider. + * + * The whole of the supplied rect will be rendered and the + * returned QRect will be equal to the supplied QRect. (See + * renderTimeConstrained for an alternative that may render only + * part of the rect in cases where obtaining source data is slow + * and retaining responsiveness is important.) + * + * Note that Colour3DPlotRenderer retains internal cache state + * related to the size and position of the supplied + * LayerGeometryProvider. Although it is valid to call render() + * successively on the same Colour3DPlotRenderer with different + * LayerGeometryProviders, it will be much faster to use a + * dedicated Colour3DPlotRenderer for each LayerGeometryProvider. + * + * If the model to render from is not ready, this will throw a + * std::logic_error exception. The model must be ready and the + * layer requesting the render must not be dormant in its view, so + * that the LayerGeometryProvider returns valid results; it is the + * caller's responsibility to ensure these. + */ + RenderResult render(const LayerGeometryProvider *v, + QPainter &paint, QRect rect); + + /** + * Render the requested area using the given painter, obtaining + * geometry (e.g. start frame) from the stored + * LayerGeometryProvider. + * + * As much of the rect will be rendered as can be managed given + * internal time constraints (using a RenderTimer object + * internally). The returned QRect (the rendered field in the + * RenderResult struct) will contain the area that was + * rendered. Note that we always render the full requested height, + * it's only width that is time-constrained. + * + * Note that Colour3DPlotRenderer retains internal cache state + * related to the size and position of the supplied + * LayerGeometryProvider. Although it is valid to call render() + * successively on the same Colour3DPlotRenderer with different + * LayerGeometryProviders, it will be much faster to use a + * dedicated Colour3DPlotRenderer for each LayerGeometryProvider. + * + * If the model to render from is not ready, this will throw a + * std::logic_error exception. The model must be ready and the + * layer requesting the render must not be dormant in its view, so + * that the LayerGeometryProvider returns valid results; it is the + * caller's responsibility to ensure these. + */ + RenderResult renderTimeConstrained(const LayerGeometryProvider *v, + QPainter &paint, QRect rect); + + /** + * Return the area of the largest rectangle within the entire area + * of the cache that is unavailable in the cache. This is only + * valid in relation to a preceding render() call which is + * presumed to have set the area, start frame, and zoom level for + * the cache. It could be used to establish a suitable region for + * a subsequent paint request (because if an area is not in the + * cache, it cannot have been rendered since the cache was + * cleared). + * + * Returns an empty QRect if the cache is entirely valid. + */ + QRect getLargestUncachedRect(const LayerGeometryProvider *v); + + /** + * Return true if the provider's geometry differs from the cache, + * or if we are not using a cache. i.e. if the cache will be + * regenerated for the next render, or the next render performed + * from scratch. + */ + bool geometryChanged(const LayerGeometryProvider *v); + + /** + * Return true if the rendering will be opaque. This may be used + * by the calling layer to determine whether it can scroll + * directly without regard to any other layers beneath. + */ + bool willRenderOpaque(const LayerGeometryProvider *v) { + return decideRenderType(v) != DirectTranslucent; + } + + /** + * Return the colour corresponding to the given value. + * \see ColourScale::getPixel + * \see ColourScale::getColour + */ + QColor getColour(double value) const { + return m_params.colourScale.getColour(value, m_params.colourRotation); + } + + /** + * Return the enclosing rectangle for the region of similar colour + * to the given point within the cache. Return an empty QRect if + * this is not possible. \see ImageRegionFinder + */ + QRect findSimilarRegionExtents(QPoint point) const; + +private: + Sources m_sources; + Parameters m_params; + + // Draw buffer is the target of each partial repaint. It is always + // at view height (not model height) and is cleared and repainted + // on each fragment render. The only reason it's stored as a data + // member is to avoid reallocation. + QImage m_drawBuffer; + + // A temporary store of magnitude ranges per-column, used when + // rendering to the draw buffer. This always has the same length + // as the width of the draw buffer, and the x coordinates of the + // two containers are equivalent. + std::vector<MagnitudeRange> m_magRanges; + + // The image cache is our persistent record of the visible + // area. It is always the same size as the view (i.e. the paint + // size reported by the LayerGeometryProvider) and is scrolled and + // partially repainted internally as appropriate. A render request + // is carried out by repainting to cache (via the draw buffer) any + // area that is being requested but is not valid in the cache, and + // then repainting from cache to the requested painter. + ScrollableImageCache m_cache; + + // The mag range cache is our record of the column magnitude + // ranges for each of the columns in the cache. It always has the + // same start frame and width as the image cache, and the column + // indices match up across both. Our cache update mechanism + // guarantees that every valid column in the image cache has a + // valid range in the magnitude cache, but not necessarily vice + // versa (as the image cache is limited to contiguous ranges). + ScrollableMagRangeCache m_magCache; + + RenderResult render(const LayerGeometryProvider *v, + QPainter &paint, QRect rect, bool timeConstrained); + + MagnitudeRange renderDirectTranslucent(const LayerGeometryProvider *v, + QPainter &paint, QRect rect); + + void renderToCachePixelResolution(const LayerGeometryProvider *v, int x0, + int repaintWidth, bool rightToLeft, + bool timeConstrained); + + void renderToCacheBinResolution(const LayerGeometryProvider *v, int x0, + int repaintWidth); + + int renderDrawBuffer(int w, int h, + const std::vector<int> &binforx, + const std::vector<double> &binfory, + bool usePeaksCache, + bool rightToLeft, + bool timeConstrained); + + int renderDrawBufferPeakFrequencies(const LayerGeometryProvider *v, + int w, int h, + const std::vector<int> &binforx, + const std::vector<double> &binfory, + bool rightToLeft, + bool timeConstrained); + + void recreateDrawBuffer(int w, int h); + void clearDrawBuffer(int w, int h); + + enum RenderType { + DrawBufferPixelResolution, + DrawBufferBinResolution, + DirectTranslucent + }; + + RenderType decideRenderType(const LayerGeometryProvider *) const; + + ColumnOp::Column getColumn(int sx, int minbin, int nbins) const; +}; + +#endif +
--- a/layer/ColourMapper.cpp Mon Jun 13 11:44:08 2016 +0100 +++ b/layer/ColourMapper.cpp Fri Aug 05 15:05:02 2016 +0100 @@ -60,7 +60,6 @@ } ColourMapper::ColourMapper(int map, double min, double max) : - QObject(), m_map(map), m_min(min), m_max(max) @@ -85,25 +84,25 @@ QString ColourMapper::getColourMapName(int n) { - if (n >= getColourMapCount()) return tr("<unknown>"); + if (n >= getColourMapCount()) return QObject::tr("<unknown>"); StandardMap map = (StandardMap)n; switch (map) { - case Green: return tr("Green"); - case WhiteOnBlack: return tr("White on Black"); - case BlackOnWhite: return tr("Black on White"); - 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"); - case Highlight: return tr("Highlight"); - case Printer: return tr("Printer"); - case HighGain: return tr("High Gain"); + case Green: return QObject::tr("Green"); + case WhiteOnBlack: return QObject::tr("White on Black"); + case BlackOnWhite: return QObject::tr("Black on White"); + case Cherry: return QObject::tr("Cherry"); + case Wasp: return QObject::tr("Wasp"); + case Ice: return QObject::tr("Ice"); + case Sunset: return QObject::tr("Sunset"); + case FruitSalad: return QObject::tr("Fruit Salad"); + case Banded: return QObject::tr("Banded"); + case Highlight: return QObject::tr("Highlight"); + case Printer: return QObject::tr("Printer"); + case HighGain: return QObject::tr("High Gain"); } - return tr("<unknown>"); + return QObject::tr("<unknown>"); } QColor
--- a/layer/ColourMapper.h Mon Jun 13 11:44:08 2016 +0100 +++ b/layer/ColourMapper.h Fri Aug 05 15:05:02 2016 +0100 @@ -23,14 +23,14 @@ /** * A class for mapping intensity values onto various colour maps. */ - -class ColourMapper : public QObject +class ColourMapper { - Q_OBJECT - public: ColourMapper(int map, double minValue, double maxValue); - virtual ~ColourMapper(); + ~ColourMapper(); + + ColourMapper(const ColourMapper &) = default; + ColourMapper &operator=(const ColourMapper &) = default; enum StandardMap { Green,
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layer/ColourScale.cpp Fri Aug 05 15:05:02 2016 +0100 @@ -0,0 +1,160 @@ +/* -*- 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 file copyright 2006-2016 Chris Cannam and QMUL. + + 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 "ColourScale.h" + +#include "base/AudioLevel.h" +#include "base/LogRange.h" + +#include <cmath> +#include <iostream> + +using namespace std; + +int ColourScale::m_maxPixel = 255; + +ColourScale::ColourScale(Parameters parameters) : + m_params(parameters), + m_mapper(m_params.colourMap, 1.f, double(m_maxPixel)) +{ + if (m_params.minValue >= m_params.maxValue) { + cerr << "ERROR: ColourScale::ColourScale: minValue = " + << m_params.minValue << ", maxValue = " << m_params.maxValue << endl; + throw std::logic_error("maxValue must be greater than minValue"); + } + + m_mappedMin = m_params.minValue; + m_mappedMax = m_params.maxValue; + + if (m_mappedMin < m_params.threshold) { + m_mappedMin = m_params.threshold; + } + + if (m_params.scaleType == ColourScaleType::Log) { + + LogRange::mapRange(m_mappedMin, m_mappedMax); + + } else if (m_params.scaleType == ColourScaleType::PlusMinusOne) { + + m_mappedMin = -1.0; + m_mappedMax = 1.0; + + } else if (m_params.scaleType == ColourScaleType::Absolute) { + + m_mappedMin = fabs(m_mappedMin); + m_mappedMax = fabs(m_mappedMax); + if (m_mappedMin >= m_mappedMax) { + std::swap(m_mappedMin, m_mappedMax); + } + } + + if (m_mappedMin >= m_mappedMax) { + cerr << "ERROR: ColourScale::ColourScale: minValue = " << m_params.minValue + << ", maxValue = " << m_params.maxValue + << ", threshold = " << m_params.threshold + << ", scale = " << int(m_params.scaleType) + << " resulting in mapped minValue = " << m_mappedMin + << ", mapped maxValue = " << m_mappedMax << endl; + throw std::logic_error("maxValue must be greater than minValue [after mapping]"); + } +} + +ColourScale::~ColourScale() +{ +} + +ColourScaleType +ColourScale::getScale() const +{ + return m_params.scaleType; +} + +int +ColourScale::getPixel(double value) const +{ + double maxPixF = m_maxPixel; + + if (m_params.scaleType == ColourScaleType::Phase) { + double half = (maxPixF - 1.f) / 2.f; + int pixel = 1 + int((value * half) / M_PI + half); +// cerr << "phase = " << value << " pixel = " << pixel << endl; + return pixel; + } + + value *= m_params.gain; + + if (value < m_params.threshold) return 0; + + double mapped = value; + + if (m_params.scaleType == ColourScaleType::Log) { + mapped = LogRange::map(value); + } else if (m_params.scaleType == ColourScaleType::PlusMinusOne) { + if (mapped < -1.f) mapped = -1.f; + if (mapped > 1.f) mapped = 1.f; + } else if (m_params.scaleType == ColourScaleType::Absolute) { + if (mapped < 0.f) mapped = -mapped; + } + + mapped *= m_params.multiple; + + if (mapped < m_mappedMin) { + mapped = m_mappedMin; + } + if (mapped > m_mappedMax) { + mapped = m_mappedMax; + } + + double proportion = (mapped - m_mappedMin) / (m_mappedMax - m_mappedMin); + + int pixel = 0; + + if (m_params.scaleType == ColourScaleType::Meter) { + pixel = AudioLevel::multiplier_to_preview(proportion, m_maxPixel-1) + 1; + } else { + pixel = int(proportion * maxPixF) + 1; + } + + if (pixel < 0) { + pixel = 0; + } + if (pixel > m_maxPixel) { + pixel = m_maxPixel; + } + return pixel; +} + +QColor +ColourScale::getColourForPixel(int pixel, int rotation) const +{ + if (pixel < 0) { + pixel = 0; + } + if (pixel > m_maxPixel) { + pixel = m_maxPixel; + } + if (pixel == 0) { + if (m_mapper.hasLightBackground()) { + return Qt::white; + } else { + return Qt::black; + } + } else { + int target = int(pixel) + rotation; + while (target < 1) target += m_maxPixel; + while (target > m_maxPixel) target -= m_maxPixel; + return m_mapper.map(double(target)); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layer/ColourScale.h Fri Aug 05 15:05:02 2016 +0100 @@ -0,0 +1,120 @@ +/* -*- 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 file copyright 2006-2016 Chris Cannam and QMUL. + + 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 COLOUR_SCALE_H +#define COLOUR_SCALE_H + +#include "ColourMapper.h" + +enum class ColourScaleType { + Linear, + Meter, + Log, + Phase, + PlusMinusOne, + Absolute +}; + +/** + * Map values within a range onto a set of colours, with a given + * distribution (linear, log etc) and optional colourmap rotation. + */ +class ColourScale +{ +public: + struct Parameters { + Parameters() : colourMap(0), scaleType(ColourScaleType::Linear), + minValue(0.0), maxValue(1.0), + threshold(0.0), gain(1.0), multiple(1.0) { } + + /** A colour map index as used by ColourMapper */ + int colourMap; + + /** Distribution for the scale */ + ColourScaleType scaleType; + + /** Minimum value in source range */ + double minValue; + + /** Maximum value in source range. Must be > minValue */ + double maxValue; + + /** Threshold below which every value is mapped to background + pixel 0 */ + double threshold; + + /** Gain to apply before thresholding, mapping, and clamping */ + double gain; + + /** Multiple to apply after thresholding and mapping. In most + * cases the gain parameter is the one you want instead of + * this, but this can be used for example with Log scale to + * produce the log of some power of the original value, + * e.g. multiple = 2 gives log(x^2). */ + double multiple; + }; + + /** + * Create a ColourScale with the given parameters. + * + * Note that some parameters may be ignored for some scale + * distribution settings. For example, min and max are ignored for + * PlusMinusOneScale and PhaseColourScale and threshold and gain + * are ignored for PhaseColourScale. + */ + ColourScale(Parameters parameters); + ~ColourScale(); + + ColourScale(const ColourScale &) = default; + ColourScale &operator=(const ColourScale &) = default; + + /** + * Return the general type of scale this is. + */ + ColourScaleType getScale() const; + + /** + * Return a pixel number (in the range 0-255 inclusive) + * corresponding to the given value. The pixel 0 is used only for + * values below the threshold supplied in the constructor. All + * other values are mapped onto the range 1-255. + */ + int getPixel(double value) const; + + /** + * Return the colour for the given pixel number (which must be in + * the range 0-255). The pixel 0 is always the background + * colour. Other pixels are mapped taking into account the given + * colourmap rotation (which is also a value in the range 0-255). + */ + QColor getColourForPixel(int pixel, int rotation) const; + + /** + * Return the colour corresponding to the given value. This is + * equivalent to getColourForPixel(getPixel(value), rotation). + */ + QColor getColour(double value, int rotation) const { + return getColourForPixel(getPixel(value), rotation); + } + +private: + Parameters m_params; + ColourMapper m_mapper; + double m_mappedMin; + double m_mappedMax; + static int m_maxPixel; +}; + +#endif
--- a/layer/ColourScaleLayer.h Mon Jun 13 11:44:08 2016 +0100 +++ b/layer/ColourScaleLayer.h Fri Aug 05 15:05:02 2016 +0100 @@ -21,6 +21,13 @@ class LayerGeometryProvider; +/** + * Interface for layers in which a colour scale represents (or can + * sometimes represent, depending on the display mode) the sample + * value. For example, TimeValueLayer uses colour scale when in + * segment mode and so provides this interface for use by the + * LogColourScale or LinearColourScale scale renderers. + */ class ColourScaleLayer { public:
--- a/layer/FlexiNoteLayer.cpp Mon Jun 13 11:44:08 2016 +0100 +++ b/layer/FlexiNoteLayer.cpp Fri Aug 05 15:05:02 2016 +0100 @@ -22,15 +22,18 @@ #include "base/Pitch.h" #include "base/LogRange.h" #include "base/RangeMapper.h" + #include "ColourDatabase.h" -#include "view/View.h" - +#include "LayerGeometryProvider.h" #include "PianoScale.h" #include "LinearNumericalScale.h" #include "LogNumericalScale.h" +#include "PaintAssistant.h" #include "data/model/FlexiNoteModel.h" +#include "view/View.h" + #include "widgets/ItemEditDialog.h" #include "widgets/TextAbbrev.h" @@ -867,34 +870,34 @@ // paint.setBrush(v->getForeground()); QString vlabel = QString("freq: %1%2").arg(p.value).arg(m_model->getScaleUnits()); - // v->drawVisibleText(paint, + // PaintAssistant::drawVisibleText(v, paint, // x - paint.fontMetrics().width(vlabel) - 2, // y + paint.fontMetrics().height()/2 // - paint.fontMetrics().descent(), - // vlabel, View::OutlinedText); - v->drawVisibleText(paint, + // vlabel, PaintAssistant::OutlinedText); + PaintAssistant::drawVisibleText(v, paint, x, y - h/2 - 2 - paint.fontMetrics().height() - paint.fontMetrics().descent(), - vlabel, View::OutlinedText); + vlabel, PaintAssistant::OutlinedText); QString hlabel = "dur: " + QString(RealTime::frame2RealTime (p.duration, m_model->getSampleRate()).toText(true).c_str()); - v->drawVisibleText(paint, + PaintAssistant::drawVisibleText(v, paint, x, y - h/2 - paint.fontMetrics().descent() - 2, - hlabel, View::OutlinedText); + hlabel, PaintAssistant::OutlinedText); QString llabel = QString("%1").arg(p.label); - v->drawVisibleText(paint, + PaintAssistant::drawVisibleText(v, paint, x, y + h + 2 + paint.fontMetrics().descent(), - llabel, View::OutlinedText); + llabel, PaintAssistant::OutlinedText); QString nlabel = QString("%1").arg(noteNumber); - v->drawVisibleText(paint, + PaintAssistant::drawVisibleText(v, paint, x + paint.fontMetrics().averageCharWidth() / 2, y + h/2 - paint.fontMetrics().descent(), - nlabel, View::OutlinedText); + nlabel, PaintAssistant::OutlinedText); } paint.drawRect(x, y - h/2, w, h);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layer/LayerGeometryProvider.h Fri Aug 05 15:05:02 2016 +0100 @@ -0,0 +1,184 @@ +/* -*- 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 LAYER_GEOMETRY_PROVIDER_H +#define LAYER_GEOMETRY_PROVIDER_H + +#include "base/BaseTypes.h" + +#include <QMutex> +#include <QMutexLocker> +#include <QPainter> + +class ViewManager; +class View; +class Layer; + +/** + * Interface for classes that provide geometry information (such as + * size, start frame, and a large number of other properties) about + * the disposition of a layer. The main implementor of this interface + * is the View class, but other implementations may be used in + * different circumstances, e.g. as a proxy to handle hi-dpi + * coordinate mapping. + * + * Note it is expected that some implementations of this may be + * disposable, created on-the-fly for a single use. Code that receives + * a LayerGeometryProvider pointer as an argument to something should + * not, in general, store that pointer as it may be invalidated before + * the next use. Use getId() to instead obtain a persistent identifier + * for a LayerGeometryProvider, for example to establish whether the + * same one is being provided in two separate calls. + */ +class LayerGeometryProvider +{ +protected: + static int getNextId() { + static QMutex idMutex; + static int nextId = 1; + static int maxId = INT_MAX; + QMutexLocker locker(&idMutex); + int id = nextId; + if (nextId == maxId) { + // we don't expect this to happen in the lifetime of a + // process, but it would be undefined behaviour if it did + // since we're using a signed int, so we should really + // guard for it... + nextId = 1; + } else { + nextId++; + } + return id; + } + +public: + LayerGeometryProvider() { } + + /** + * Retrieve the id of this object. + */ + virtual int getId() const = 0; + + /** + * Retrieve the first visible sample frame on the widget. + * This is a calculated value based on the centre-frame, widget + * width and zoom level. The result may be negative. + */ + virtual sv_frame_t getStartFrame() const = 0; + + /** + * Return the centre frame of the visible widget. This is an + * exact value that does not depend on the zoom block size. Other + * frame values (start, end) are calculated from this based on the + * zoom and other factors. + */ + virtual sv_frame_t getCentreFrame() const = 0; + + /** + * Retrieve the last visible sample frame on the widget. + * This is a calculated value based on the centre-frame, widget + * width and zoom level. + */ + virtual sv_frame_t getEndFrame() const = 0; + + /** + * Return the pixel x-coordinate corresponding to a given sample + * frame (which may be negative). + */ + virtual int getXForFrame(sv_frame_t frame) const = 0; + + /** + * Return the closest frame to the given pixel x-coordinate. + */ + virtual sv_frame_t getFrameForX(int x) const = 0; + + virtual sv_frame_t getModelsStartFrame() const = 0; + 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 (maybe fractional) 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 be useful for layers to match theirs up if + * desired. + * + * Not thread-safe in logarithmic mode. Call only from GUI thread. + */ + virtual double getYForFrequency(double frequency, + double minFreq, double maxFreq, + bool logarithmic) const = 0; + + /** + * Return the closest frequency to the given (maybe fractional) + * pixel y-coordinate, if the frequency range is as specified. + * + * Not thread-safe in logarithmic mode. Call only from GUI thread. + */ + virtual double getFrequencyForY(double y, + double minFreq, double maxFreq, + bool logarithmic) const = 0; + + virtual int getTextLabelHeight(const Layer *layer, QPainter &) const = 0; + + virtual bool getValueExtents(QString unit, double &min, double &max, + bool &log) const = 0; + + /** + * Return the zoom level, i.e. the number of frames per pixel + */ + virtual int getZoomLevel() const = 0; + + /** + * To be called from a layer, to obtain the extent of the surface + * that the layer is currently painting to. This may be the extent + * of the view (if 1x display scaling is in effect) or of a larger + * cached pixmap (if greater display scaling is in effect). + */ + virtual QRect getPaintRect() const = 0; + + virtual QSize getPaintSize() const { return getPaintRect().size(); } + virtual int getPaintWidth() const { return getPaintRect().width(); } + virtual int getPaintHeight() const { return getPaintRect().height(); } + + virtual bool hasLightBackground() const = 0; + virtual QColor getForeground() const = 0; + virtual QColor getBackground() const = 0; + + virtual ViewManager *getViewManager() const = 0; + + virtual bool shouldIlluminateLocalFeatures(const Layer *, QPoint &) const = 0; + virtual bool shouldShowFeatureLabels() const = 0; + + 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; +}; + +#endif
--- a/layer/LinearColourScale.cpp Mon Jun 13 11:44:08 2016 +0100 +++ b/layer/LinearColourScale.cpp Fri Aug 05 15:05:02 2016 +0100 @@ -20,7 +20,7 @@ #include <cmath> -#include "view/LayerGeometryProvider.h" +#include "LayerGeometryProvider.h" int LinearColourScale::getWidth(LayerGeometryProvider *,
--- a/layer/LinearNumericalScale.cpp Mon Jun 13 11:44:08 2016 +0100 +++ b/layer/LinearNumericalScale.cpp Fri Aug 05 15:05:02 2016 +0100 @@ -20,7 +20,7 @@ #include <cmath> -#include "view/View.h" +#include "LayerGeometryProvider.h" int LinearNumericalScale::getWidth(LayerGeometryProvider *,
--- a/layer/LogColourScale.cpp Mon Jun 13 11:44:08 2016 +0100 +++ b/layer/LogColourScale.cpp Fri Aug 05 15:05:02 2016 +0100 @@ -22,7 +22,7 @@ #include <cmath> -#include "view/View.h" +#include "LayerGeometryProvider.h" int LogColourScale::getWidth(LayerGeometryProvider *,
--- a/layer/LogNumericalScale.cpp Mon Jun 13 11:44:08 2016 +0100 +++ b/layer/LogNumericalScale.cpp Fri Aug 05 15:05:02 2016 +0100 @@ -22,7 +22,7 @@ #include <cmath> -#include "view/View.h" +#include "LayerGeometryProvider.h" //#define DEBUG_TIME_VALUE_LAYER 1
--- a/layer/NoteLayer.cpp Mon Jun 13 11:44:08 2016 +0100 +++ b/layer/NoteLayer.cpp Fri Aug 05 15:05:02 2016 +0100 @@ -21,12 +21,13 @@ #include "base/Pitch.h" #include "base/LogRange.h" #include "base/RangeMapper.h" -#include "ColourDatabase.h" #include "view/View.h" +#include "ColourDatabase.h" #include "PianoScale.h" #include "LinearNumericalScale.h" #include "LogNumericalScale.h" +#include "PaintAssistant.h" #include "data/model/NoteModel.h" @@ -809,18 +810,18 @@ paint.setBrush(v->getForeground()); QString vlabel = QString("%1%2").arg(p.value).arg(getScaleUnits()); - v->drawVisibleText(paint, + PaintAssistant::drawVisibleText(v, paint, x - paint.fontMetrics().width(vlabel) - 2, y + paint.fontMetrics().height()/2 - paint.fontMetrics().descent(), - vlabel, View::OutlinedText); + vlabel, PaintAssistant::OutlinedText); QString hlabel = RealTime::frame2RealTime (p.frame, m_model->getSampleRate()).toText(true).c_str(); - v->drawVisibleText(paint, + PaintAssistant::drawVisibleText(v, paint, x, y - h/2 - paint.fontMetrics().descent() - 2, - hlabel, View::OutlinedText); + hlabel, PaintAssistant::OutlinedText); } paint.drawRect(x, y - h/2, w, h);
--- a/layer/PaintAssistant.cpp Mon Jun 13 11:44:08 2016 +0100 +++ b/layer/PaintAssistant.cpp Fri Aug 05 15:05:02 2016 +0100 @@ -15,6 +15,8 @@ #include "PaintAssistant.h" +#include "LayerGeometryProvider.h" + #include "base/AudioLevel.h" #include <QPaintDevice> @@ -207,3 +209,55 @@ return vy; } + +void +PaintAssistant::drawVisibleText(const LayerGeometryProvider *v, + QPainter &paint, int x, int y, + QString text, TextStyle style) +{ + if (style == OutlinedText || style == OutlinedItalicText) { + + paint.save(); + + if (style == OutlinedItalicText) { + QFont f(paint.font()); + f.setItalic(true); + paint.setFont(f); + } + + QColor penColour, surroundColour, boxColour; + + penColour = v->getForeground(); + surroundColour = v->getBackground(); + boxColour = surroundColour; + boxColour.setAlpha(127); + + paint.setPen(Qt::NoPen); + paint.setBrush(boxColour); + + QRect r = paint.fontMetrics().boundingRect(text); + r.translate(QPoint(x, y)); +// cerr << "drawVisibleText: r = " << r.x() << "," <<r.y() << " " << r.width() << "x" << r.height() << endl; + paint.drawRect(r); + paint.setBrush(Qt::NoBrush); + + paint.setPen(surroundColour); + + for (int dx = -1; dx <= 1; ++dx) { + for (int dy = -1; dy <= 1; ++dy) { + if (!(dx || dy)) continue; + paint.drawText(x + dx, y + dy, text); + } + } + + paint.setPen(penColour); + + paint.drawText(x, y, text); + + paint.restore(); + + } else { + + std::cerr << "ERROR: PaintAssistant::drawVisibleText: Boxed style not yet implemented!" << std::endl; + } +}
--- a/layer/PaintAssistant.h Mon Jun 13 11:44:08 2016 +0100 +++ b/layer/PaintAssistant.h Fri Aug 05 15:05:02 2016 +0100 @@ -13,13 +13,15 @@ COPYING included with this distribution for more information. */ -#ifndef _PAINT_ASSISTANT_H_ -#define _PAINT_ASSISTANT_H_ +#ifndef PAINT_ASSISTANT_H +#define PAINT_ASSISTANT_H #include <QRect> #include <vector> class QPainter; +class Layer; +class LayerGeometryProvider; class PaintAssistant { @@ -34,6 +36,16 @@ static int getYForValue(Scale scale, double value, double minVal, double maxVal, int minY, int height); + + enum TextStyle { + BoxedText, + OutlinedText, + OutlinedItalicText + }; + + static void drawVisibleText(const LayerGeometryProvider *, + QPainter &p, int x, int y, + QString text, TextStyle style); }; #endif
--- a/layer/PianoScale.cpp Mon Jun 13 11:44:08 2016 +0100 +++ b/layer/PianoScale.cpp Fri Aug 05 15:05:02 2016 +0100 @@ -21,7 +21,7 @@ #include "base/Pitch.h" -#include "view/LayerGeometryProvider.h" +#include "LayerGeometryProvider.h" void PianoScale::paintPianoVertical(LayerGeometryProvider *v,
--- a/layer/RegionLayer.cpp Mon Jun 13 11:44:08 2016 +0100 +++ b/layer/RegionLayer.cpp Fri Aug 05 15:05:02 2016 +0100 @@ -19,13 +19,14 @@ #include "base/RealTime.h" #include "base/Profiler.h" #include "base/LogRange.h" + #include "ColourDatabase.h" - #include "ColourMapper.h" #include "LinearNumericalScale.h" #include "LogNumericalScale.h" #include "LinearColourScale.h" #include "LogColourScale.h" +#include "PaintAssistant.h" #include "view/View.h" @@ -979,18 +980,18 @@ paint.setBrush(v->getForeground()); QString vlabel = QString("%1%2").arg(p.value).arg(getScaleUnits()); - v->drawVisibleText(paint, + PaintAssistant::drawVisibleText(v, paint, x - paint.fontMetrics().width(vlabel) - 2, y + paint.fontMetrics().height()/2 - paint.fontMetrics().descent(), - vlabel, View::OutlinedText); + vlabel, PaintAssistant::OutlinedText); QString hlabel = RealTime::frame2RealTime (p.frame, m_model->getSampleRate()).toText(true).c_str(); - v->drawVisibleText(paint, + PaintAssistant::drawVisibleText(v, paint, x, y - h/2 - paint.fontMetrics().descent() - 2, - hlabel, View::OutlinedText); + hlabel, PaintAssistant::OutlinedText); } paint.drawLine(x, y-1, x + w, y-1); @@ -1048,7 +1049,7 @@ nextLabelMinX = labelX + paint.fontMetrics().width(label); } - v->drawVisibleText(paint, labelX, labelY, label, View::OutlinedText); + PaintAssistant::drawVisibleText(v, paint, labelX, labelY, label, PaintAssistant::OutlinedText); } }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layer/RenderTimer.h Fri Aug 05 15:05:02 2016 +0100 @@ -0,0 +1,102 @@ +/* -*- 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 RENDER_TIMER_H +#define RENDER_TIMER_H + +#include <chrono> + +class RenderTimer +{ +public: + enum Type { + /// A normal rendering operation with normal responsiveness demands + FastRender, + + /// An operation that the user might accept being slower + SlowRender, + + /// An operation that should always complete, i.e. as if there + /// were no RenderTimer in use, but without having to change + /// client code structurally + NoTimeout + }; + + /** + * Create a new RenderTimer and start timing. Make one of these + * before rendering, and then call outOfTime() regularly during + * rendering. If outOfTime() returns true, abandon rendering! and + * schedule the rest for after some user responsiveness has + * happened. + */ + RenderTimer(Type t) : + m_start(std::chrono::steady_clock::now()), + m_haveLimits(true), + m_minFraction(0.1), + m_softLimit(0.1), + m_hardLimit(0.2), + m_softLimitOverridden(false) { + + if (t == NoTimeout) { + m_haveLimits = false; + } else if (t == SlowRender) { + m_softLimit = 0.2; + m_hardLimit = 0.4; + } + } + + + /** + * Return true if we have run out of time and should suspend + * rendering and handle user events instead. Call this regularly + * during rendering work: fractionComplete should be an estimate + * of how much of the work has been done as of this call, as a + * number between 0.0 (none of it) and 1.0 (all of it). + */ + bool outOfTime(double fractionComplete) { + + if (!m_haveLimits || fractionComplete < m_minFraction) { + return false; + } + + auto t = std::chrono::steady_clock::now(); + double elapsed = std::chrono::duration<double>(t - m_start).count(); + + if (elapsed > m_hardLimit) { + return true; + } else if (!m_softLimitOverridden && elapsed > m_softLimit) { + if (fractionComplete > 0.6) { + // If we're significantly more than half way by the + // time we reach the soft limit, ignore it (though + // always respect the hard limit, above). Otherwise + // respect the soft limit and report out of time now. + m_softLimitOverridden = true; + } else { + return true; + } + } + + return false; + } + +private: + std::chrono::time_point<std::chrono::steady_clock> m_start; + bool m_haveLimits; + double m_minFraction; + double m_softLimit; + double m_hardLimit; + bool m_softLimitOverridden; +}; + +#endif
--- a/layer/ScrollableImageCache.cpp Mon Jun 13 11:44:08 2016 +0100 +++ b/layer/ScrollableImageCache.cpp Fri Aug 05 15:05:02 2016 +0100 @@ -20,18 +20,22 @@ //#define DEBUG_SCROLLABLE_IMAGE_CACHE 1 void -ScrollableImageCache::scrollTo(sv_frame_t newStartFrame) +ScrollableImageCache::scrollTo(const LayerGeometryProvider *v, + 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)); - + int dx = (v->getXForFrame(m_startFrame) - + v->getXForFrame(newStartFrame)); + #ifdef DEBUG_SCROLLABLE_IMAGE_CACHE cerr << "ScrollableImageCache::scrollTo: start frame " << m_startFrame << " -> " << newStartFrame << ", dx = " << dx << endl; #endif - + + if (m_startFrame == newStartFrame) { + // haven't moved + return; + } + m_startFrame = newStartFrame; if (!isValid()) { @@ -41,7 +45,7 @@ int w = m_image.width(); if (dx == 0) { - // haven't moved + // haven't moved visibly (even though start frame may have changed) return; } @@ -68,8 +72,8 @@ // update valid area - int px = m_left; - int pw = m_width; + int px = m_validLeft; + int pw = m_validWidth; px += dx; @@ -92,8 +96,8 @@ } } - m_left = px; - m_width = pw; + m_validLeft = px; + m_validWidth = pw; } void @@ -103,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; @@ -160,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 Mon Jun 13 11:44:08 2016 +0100 +++ b/layer/ScrollableImageCache.h Fri Aug 05 15:05:02 2016 +0100 @@ -17,14 +17,14 @@ #include "base/BaseTypes.h" -#include "view/LayerGeometryProvider.h" +#include "LayerGeometryProvider.h" #include <QImage> #include <QRect> #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 @@ -37,54 +37,67 @@ class ScrollableImageCache { public: - ScrollableImageCache(const LayerGeometryProvider *v = 0) : - m_v(v), - m_left(0), - m_width(0), + ScrollableImageCache() : + 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) { - m_image = QImage(newSize, QImage::Format_ARGB32_Premultiplied); - invalidate(); + if (getSize() != newSize) { + m_image = QImage(newSize, QImage::Format_ARGB32_Premultiplied); + invalidate(); + } } 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) { - m_zoomLevel = zoom; - invalidate(); + if (m_zoomLevel != zoom) { + m_zoomLevel = zoom; + invalidate(); + } } sv_frame_t getStartFrame() const { @@ -92,13 +105,16 @@ } /** - * 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) { - m_startFrame = frame; - invalidate(); + if (m_startFrame != frame) { + m_startFrame = frame; + invalidate(); + } } const QImage &getImage() const { @@ -106,20 +122,23 @@ } /** - * 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. + * 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(sv_frame_t newStartFrame); + void scrollTo(const LayerGeometryProvider *v, 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. + * cache valid region contains the supplied region. Does not + * modify anything about the cache, only about the arguments. */ 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 @@ -134,10 +153,9 @@ int imageWidth); private: - const LayerGeometryProvider *m_v; 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 Fri Aug 05 15:05:02 2016 +0100 @@ -0,0 +1,113 @@ +/* -*- 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) +{ + 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 + + if (m_startFrame == newStartFrame) { + // haven't moved + return; + } + + 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; + } + +#ifdef DEBUG_SCROLLABLE_MAG_RANGE_CACHE + cerr << "maxes (" << m_ranges.size() << ") now: "; + for (int i = 0; in_range_for(m_ranges, i); ++i) { + cerr << m_ranges[i].getMax() << " "; + } + cerr << endl; +#endif +} + +MagnitudeRange +ScrollableMagRangeCache::getRange(int x, int count) const +{ + MagnitudeRange r; +#ifdef DEBUG_SCROLLABLE_MAG_RANGE_CACHE + cerr << "ScrollableMagRangeCache::getRange(" << x << ", " << count << ")" << endl; +#endif + for (int i = 0; i < count; ++i) { + r.sample(m_ranges.at(x + i)); + } + return r; +} + +void +ScrollableMagRangeCache::sampleColumn(int column, const MagnitudeRange &r) +{ + if (!in_range_for(m_ranges, column)) { + cerr << "ERROR: ScrollableMagRangeCache::sampleColumn: column " << column + << " is out of range for cache of width " << m_ranges.size() + << " (with start frame " << m_startFrame << ")" << endl; + throw logic_error("column out of range"); + } else { + m_ranges[column].sample(r); + } +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layer/ScrollableMagRangeCache.h Fri Aug 05 15:05:02 2016 +0100 @@ -0,0 +1,139 @@ +/* -*- 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(); + } + + bool areColumnsSet(int x, int count) const { + for (int i = 0; i < count; ++i) { + if (!isColumnSet(x + i)) return false; + } + return true; + } + + /** + * Get the magnitude range for a single column. + */ + MagnitudeRange getRange(int column) const { + return m_ranges.at(column); + } + + /** + * Get the magnitude range for a range of columns. + */ + MagnitudeRange getRange(int x, int count) const; + + /** + * 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 index. (Column zero is + * the first column in the cache, it has nothing to do with any + * underlying model that the cache may be used with.) + */ + void sampleColumn(int column, const MagnitudeRange &r); + +private: + std::vector<MagnitudeRange> m_ranges; + sv_frame_t m_startFrame; + int m_zoomLevel; +}; + +#endif
--- a/layer/SliceLayer.cpp Mon Jun 13 11:44:08 2016 +0100 +++ b/layer/SliceLayer.cpp Fri Aug 05 15:05:02 2016 +0100 @@ -485,17 +485,17 @@ v->drawVisibleText (paint, xorigin + 5, paint.fontMetrics().ascent() + 5, - startText, View::OutlinedText); + startText, PaintAssistant::OutlinedText); v->drawVisibleText (paint, xorigin + 5, paint.fontMetrics().ascent() + paint.fontMetrics().height() + 10, - endText, View::OutlinedText); + endText, PaintAssistant::OutlinedText); v->drawVisibleText (paint, xorigin + 5, paint.fontMetrics().ascent() + 2*paint.fontMetrics().height() + 15, - durationText, View::OutlinedText); + durationText, PaintAssistant::OutlinedText); } */ }
--- a/layer/SpectrogramLayer.cpp Mon Jun 13 11:44:08 2016 +0100 +++ b/layer/SpectrogramLayer.cpp Fri Aug 05 15:05:02 2016 +0100 @@ -23,11 +23,14 @@ #include "base/Preferences.h" #include "base/RangeMapper.h" #include "base/LogRange.h" +#include "base/ColumnOp.h" #include "widgets/CommandHistory.h" +#include "data/model/Dense3DModelPeakCache.h" + #include "ColourMapper.h" -#include "ImageRegionFinder.h" -#include "data/model/Dense3DModelPeakCache.h" #include "PianoScale.h" +#include "PaintAssistant.h" +#include "Colour3DPlotRenderer.h" #include <QPainter> #include <QImage> @@ -48,7 +51,8 @@ #include <alloca.h> #endif -#define DEBUG_SPECTROGRAM_REPAINT 1 +//#define DEBUG_SPECTROGRAM 1 +//#define DEBUG_SPECTROGRAM_REPAINT 1 using namespace std; @@ -58,28 +62,29 @@ m_windowSize(1024), m_windowType(HanningWindow), m_windowHopLevel(2), - m_zeroPadLevel(0), - m_fftSize(1024), m_gain(1.0), m_initialGain(1.0), - m_threshold(0.0), - m_initialThreshold(0.0), + m_threshold(1.0e-8f), + m_initialThreshold(1.0e-8f), m_colourRotation(0), m_initialRotation(0), m_minFrequency(10), m_maxFrequency(8000), m_initialMaxFrequency(8000), - m_colourScale(dBColourScale), + m_colourScale(ColourScaleType::Log), + m_colourScaleMultiple(1.0), m_colourMap(0), - m_frequencyScale(LinearFrequencyScale), - m_binDisplay(AllBins), - m_normalization(NoNormalization), + m_binScale(BinScale::Linear), + m_binDisplay(BinDisplay::AllBins), + m_normalization(ColumnNormalization::None), + m_normalizeVisibleArea(false), m_lastEmittedZoomStep(-1), m_synchronous(false), m_haveDetailedScale(false), m_exiting(false), - m_peakCacheDivisor(8), - m_sliceableModel(0) + m_fftModel(0), + m_peakCache(0), + m_peakCacheDivisor(8) { QString colourConfigName = "spectrogram-colour"; int colourConfigDefault = int(ColourMapper::Green); @@ -93,9 +98,9 @@ m_initialMaxFrequency = 1500; setMaxFrequency(1500); setMinFrequency(40); - setColourScale(LinearColourScale); + setColourScale(ColourScaleType::Linear); setColourMap(ColourMapper::Sunset); - setFrequencyScale(LogFrequencyScale); + setBinScale(BinScale::Log); colourConfigName = "spectrogram-melodic-colour"; colourConfigDefault = int(ColourMapper::Sunset); // setGain(20); @@ -105,10 +110,10 @@ m_initialMaxFrequency = 2000; setMaxFrequency(2000); setMinFrequency(40); - setFrequencyScale(LogFrequencyScale); - setColourScale(LinearColourScale); - setBinDisplay(PeakFrequencies); - setNormalization(NormalizeColumns); + setBinScale(BinScale::Log); + setColourScale(ColourScaleType::Linear); + setBinDisplay(BinDisplay::PeakFrequencies); + setNormalization(ColumnNormalization::Max1); colourConfigName = "spectrogram-melodic-colour"; colourConfigDefault = int(ColourMapper::Sunset); } @@ -122,13 +127,65 @@ connect(prefs, SIGNAL(propertyChanged(PropertyContainer::PropertyName)), this, SLOT(preferenceChanged(PropertyContainer::PropertyName))); setWindowType(prefs->getWindowType()); - - initialisePalette(); } SpectrogramLayer::~SpectrogramLayer() { - invalidateFFTModels(); + invalidateRenderers(); + invalidateFFTModel(); +} + +pair<ColourScaleType, double> +SpectrogramLayer::convertToColourScale(int value) +{ + switch (value) { + case 0: return { ColourScaleType::Linear, 1.0 }; + case 1: return { ColourScaleType::Meter, 1.0 }; + case 2: return { ColourScaleType::Log, 2.0 }; // dB^2 (i.e. log of power) + case 3: return { ColourScaleType::Log, 1.0 }; // dB (of magnitude) + case 4: return { ColourScaleType::Phase, 1.0 }; + default: return { ColourScaleType::Linear, 1.0 }; + } +} + +int +SpectrogramLayer::convertFromColourScale(ColourScaleType scale, double multiple) +{ + switch (scale) { + case ColourScaleType::Linear: return 0; + case ColourScaleType::Meter: return 1; + case ColourScaleType::Log: return (multiple > 1.5 ? 2 : 3); + case ColourScaleType::Phase: return 4; + case ColourScaleType::PlusMinusOne: + case ColourScaleType::Absolute: + default: return 0; + } +} + +std::pair<ColumnNormalization, bool> +SpectrogramLayer::convertToColumnNorm(int value) +{ + switch (value) { + default: + case 0: return { ColumnNormalization::None, false }; + case 1: return { ColumnNormalization::Max1, false }; + case 2: return { ColumnNormalization::None, true }; // visible area + case 3: return { ColumnNormalization::Hybrid, false }; + } +} + +int +SpectrogramLayer::convertFromColumnNorm(ColumnNormalization norm, bool visible) +{ + if (visible) return 2; + switch (norm) { + case ColumnNormalization::None: return 0; + case ColumnNormalization::Max1: return 1; + case ColumnNormalization::Hybrid: return 3; + + case ColumnNormalization::Sum1: + default: return 0; + } } void @@ -139,7 +196,7 @@ if (model == m_model) return; m_model = model; - invalidateFFTModels(); + invalidateFFTModel(); if (!m_model || !m_model->isOK()) return; @@ -168,7 +225,6 @@ // list.push_back("Min Frequency"); // list.push_back("Max Frequency"); list.push_back("Frequency Scale"); -//// list.push_back("Zero Padding"); return list; } @@ -187,7 +243,6 @@ if (name == "Min Frequency") return tr("Min Frequency"); if (name == "Max Frequency") return tr("Max Frequency"); if (name == "Frequency Scale") return tr("Frequency Scale"); - if (name == "Zero Padding") return tr("Smoothing"); return ""; } @@ -203,7 +258,6 @@ if (name == "Gain") return RangeProperty; if (name == "Colour Rotation") return RangeProperty; if (name == "Threshold") return RangeProperty; - if (name == "Zero Padding") return ToggleProperty; return ValueProperty; } @@ -213,8 +267,7 @@ if (name == "Bin Display" || name == "Frequency Scale") return tr("Bins"); if (name == "Window Size" || - name == "Window Increment" || - name == "Zero Padding") return tr("Window"); + name == "Window Increment") return tr("Window"); if (name == "Colour" || name == "Threshold" || name == "Colour Rotation") return tr("Colour"); @@ -250,8 +303,8 @@ } else if (name == "Threshold") { - *min = -50; - *max = 0; + *min = -81; + *max = -1; *deflt = int(lrint(AudioLevel::multiplier_to_dB(m_initialThreshold))); if (*deflt < *min) *deflt = *min; @@ -271,11 +324,12 @@ } else if (name == "Colour Scale") { + // linear, meter, db^2, db, phase *min = 0; *max = 4; - *deflt = int(dBColourScale); - - val = (int)m_colourScale; + *deflt = 2; + + val = convertFromColourScale(m_colourScale, m_colourScaleMultiple); } else if (name == "Colour") { @@ -303,14 +357,6 @@ val = m_windowHopLevel; - } else if (name == "Zero Padding") { - - *min = 0; - *max = 1; - *deflt = 0; - - val = m_zeroPadLevel > 0 ? 1 : 0; - } else if (name == "Min Frequency") { *min = 0; @@ -353,22 +399,23 @@ *min = 0; *max = 1; - *deflt = int(LinearFrequencyScale); - val = (int)m_frequencyScale; + *deflt = int(BinScale::Linear); + val = (int)m_binScale; } else if (name == "Bin Display") { *min = 0; *max = 2; - *deflt = int(AllBins); + *deflt = int(BinDisplay::AllBins); val = (int)m_binDisplay; } else if (name == "Normalization") { *min = 0; *max = 3; - *deflt = int(NoNormalization); - val = (int)m_normalization; + *deflt = 0; + + val = convertFromColumnNorm(m_normalization, m_normalizeVisibleArea); } else { val = Layer::getPropertyRangeAndValue(name, min, max, deflt); @@ -411,10 +458,6 @@ case 5: return tr("93.75 %"); } } - if (name == "Zero Padding") { - if (value == 0) return tr("None"); - return QString("%1x").arg(value + 1); - } if (name == "Min Frequency") { switch (value) { default: @@ -486,7 +529,7 @@ return new LinearRangeMapper(-50, 50, -25, 25, tr("dB")); } if (name == "Threshold") { - return new LinearRangeMapper(-50, 0, -50, 0, tr("dB")); + return new LinearRangeMapper(-81, -1, -81, -1, tr("dB")); } return 0; } @@ -497,7 +540,7 @@ if (name == "Gain") { setGain(float(pow(10, float(value)/20.0))); } else if (name == "Threshold") { - if (value == -50) setThreshold(0.0); + if (value == -81) setThreshold(0.0); else setThreshold(float(AudioLevel::dB_to_multiplier(value))); } else if (name == "Colour Rotation") { setColourRotation(value); @@ -507,8 +550,6 @@ setWindowSize(32 << value); } else if (name == "Window Increment") { setWindowHopLevel(value); - } else if (name == "Zero Padding") { - setZeroPadLevel(value > 0.1 ? 3 : 0); } else if (name == "Min Frequency") { switch (value) { default: @@ -548,48 +589,50 @@ m_lastEmittedZoomStep = vs; } } else if (name == "Colour Scale") { + setColourScaleMultiple(1.0); switch (value) { default: - case 0: setColourScale(LinearColourScale); break; - case 1: setColourScale(MeterColourScale); break; - case 2: setColourScale(dBSquaredColourScale); break; - case 3: setColourScale(dBColourScale); break; - case 4: setColourScale(PhaseColourScale); break; + case 0: setColourScale(ColourScaleType::Linear); break; + case 1: setColourScale(ColourScaleType::Meter); break; + case 2: + setColourScale(ColourScaleType::Log); + setColourScaleMultiple(2.0); + break; + case 3: setColourScale(ColourScaleType::Log); break; + case 4: setColourScale(ColourScaleType::Phase); break; } } else if (name == "Frequency Scale") { switch (value) { default: - case 0: setFrequencyScale(LinearFrequencyScale); break; - case 1: setFrequencyScale(LogFrequencyScale); break; + case 0: setBinScale(BinScale::Linear); break; + case 1: setBinScale(BinScale::Log); break; } } else if (name == "Bin Display") { switch (value) { default: - case 0: setBinDisplay(AllBins); break; - case 1: setBinDisplay(PeakBins); break; - case 2: setBinDisplay(PeakFrequencies); break; + case 0: setBinDisplay(BinDisplay::AllBins); break; + case 1: setBinDisplay(BinDisplay::PeakBins); break; + case 2: setBinDisplay(BinDisplay::PeakFrequencies); break; } } else if (name == "Normalization") { - switch (value) { - default: - case 0: setNormalization(NoNormalization); break; - case 1: setNormalization(NormalizeColumns); break; - case 2: setNormalization(NormalizeVisibleArea); break; - case 3: setNormalization(NormalizeHybrid); break; - } + auto n = convertToColumnNorm(value); + setNormalization(n.first); + setNormalizeVisibleArea(n.second); } } void -SpectrogramLayer::invalidateImageCaches() +SpectrogramLayer::invalidateRenderers() { #ifdef DEBUG_SPECTROGRAM - cerr << "SpectrogramLayer::invalidateImageCaches called" << endl; + cerr << "SpectrogramLayer::invalidateRenderers called" << endl; #endif - for (ViewImageCache::iterator i = m_imageCaches.begin(); - i != m_imageCaches.end(); ++i) { - i->second.invalidate(); + + for (ViewRendererMap::iterator i = m_renderers.begin(); + i != m_renderers.end(); ++i) { + delete i->second; } + m_renderers.clear(); } void @@ -602,12 +645,13 @@ return; } if (name == "Spectrogram Y Smoothing") { - invalidateImageCaches(); + setWindowSize(m_windowSize); + invalidateRenderers(); invalidateMagnitudes(); emit layerParametersChanged(); } if (name == "Spectrogram X Smoothing") { - invalidateImageCaches(); + invalidateRenderers(); invalidateMagnitudes(); emit layerParametersChanged(); } @@ -621,9 +665,9 @@ { if (m_channel == ch) return; - invalidateImageCaches(); + invalidateRenderers(); m_channel = ch; - invalidateFFTModels(); + invalidateFFTModel(); emit layerParametersChanged(); } @@ -634,17 +678,40 @@ return m_channel; } +int +SpectrogramLayer::getFFTOversampling() const +{ + if (m_binDisplay != BinDisplay::AllBins) { + return 1; + } + + Preferences::SpectrogramSmoothing smoothing = + Preferences::getInstance()->getSpectrogramSmoothing(); + + if (smoothing == Preferences::NoSpectrogramSmoothing || + smoothing == Preferences::SpectrogramInterpolated) { + return 1; + } + + return 4; +} + +int +SpectrogramLayer::getFFTSize() const +{ + return m_windowSize * getFFTOversampling(); +} + void SpectrogramLayer::setWindowSize(int ws) { if (m_windowSize == ws) return; - invalidateImageCaches(); + invalidateRenderers(); m_windowSize = ws; - m_fftSize = ws * (m_zeroPadLevel + 1); - invalidateFFTModels(); + invalidateFFTModel(); emit layerParametersChanged(); } @@ -660,11 +727,11 @@ { if (m_windowHopLevel == v) return; - invalidateImageCaches(); + invalidateRenderers(); m_windowHopLevel = v; - invalidateFFTModels(); + invalidateFFTModel(); emit layerParametersChanged(); @@ -678,36 +745,15 @@ } void -SpectrogramLayer::setZeroPadLevel(int v) -{ - if (m_zeroPadLevel == v) return; - - invalidateImageCaches(); - - m_zeroPadLevel = v; - m_fftSize = m_windowSize * (v + 1); - - invalidateFFTModels(); - - emit layerParametersChanged(); -} - -int -SpectrogramLayer::getZeroPadLevel() const -{ - return m_zeroPadLevel; -} - -void SpectrogramLayer::setWindowType(WindowType w) { if (m_windowType == w) return; - invalidateImageCaches(); + invalidateRenderers(); m_windowType = w; - invalidateFFTModels(); + invalidateFFTModel(); emit layerParametersChanged(); } @@ -726,7 +772,7 @@ if (m_gain == gain) return; - invalidateImageCaches(); + invalidateRenderers(); m_gain = gain; @@ -744,7 +790,7 @@ { if (m_threshold == threshold) return; - invalidateImageCaches(); + invalidateRenderers(); m_threshold = threshold; @@ -764,7 +810,7 @@ // SVDEBUG << "SpectrogramLayer::setMinFrequency: " << mf << endl; - invalidateImageCaches(); + invalidateRenderers(); invalidateMagnitudes(); m_minFrequency = mf; @@ -785,7 +831,7 @@ // SVDEBUG << "SpectrogramLayer::setMaxFrequency: " << mf << endl; - invalidateImageCaches(); + invalidateRenderers(); invalidateMagnitudes(); m_maxFrequency = mf; @@ -802,47 +848,67 @@ void SpectrogramLayer::setColourRotation(int r) { - invalidateImageCaches(); - if (r < 0) r = 0; if (r > 256) r = 256; int distance = r - m_colourRotation; if (distance != 0) { - rotatePalette(-distance); m_colourRotation = r; } + + // Initially the idea with colour rotation was that we would just + // rotate the palette of an already-generated cache. That's not + // really practical now that cacheing is handled in a separate + // class in which the main cache no longer has a palette. + invalidateRenderers(); emit layerParametersChanged(); } void -SpectrogramLayer::setColourScale(ColourScale colourScale) +SpectrogramLayer::setColourScale(ColourScaleType colourScale) { if (m_colourScale == colourScale) return; - invalidateImageCaches(); + invalidateRenderers(); m_colourScale = colourScale; emit layerParametersChanged(); } -SpectrogramLayer::ColourScale +ColourScaleType SpectrogramLayer::getColourScale() const { return m_colourScale; } void +SpectrogramLayer::setColourScaleMultiple(double multiple) +{ + if (m_colourScaleMultiple == multiple) return; + + invalidateRenderers(); + + m_colourScaleMultiple = multiple; + + emit layerParametersChanged(); +} + +double +SpectrogramLayer::getColourScaleMultiple() const +{ + return m_colourScaleMultiple; +} + +void SpectrogramLayer::setColourMap(int map) { if (m_colourMap == map) return; - invalidateImageCaches(); + invalidateRenderers(); m_colourMap = map; - initialisePalette(); emit layerParametersChanged(); } @@ -854,20 +920,20 @@ } void -SpectrogramLayer::setFrequencyScale(FrequencyScale frequencyScale) +SpectrogramLayer::setBinScale(BinScale binScale) { - if (m_frequencyScale == frequencyScale) return; - - invalidateImageCaches(); - m_frequencyScale = frequencyScale; + if (m_binScale == binScale) return; + + invalidateRenderers(); + m_binScale = binScale; emit layerParametersChanged(); } -SpectrogramLayer::FrequencyScale -SpectrogramLayer::getFrequencyScale() const +BinScale +SpectrogramLayer::getBinScale() const { - return m_frequencyScale; + return m_binScale; } void @@ -875,37 +941,55 @@ { if (m_binDisplay == binDisplay) return; - invalidateImageCaches(); + invalidateRenderers(); m_binDisplay = binDisplay; emit layerParametersChanged(); } -SpectrogramLayer::BinDisplay +BinDisplay SpectrogramLayer::getBinDisplay() const { return m_binDisplay; } void -SpectrogramLayer::setNormalization(Normalization n) +SpectrogramLayer::setNormalization(ColumnNormalization n) { if (m_normalization == n) return; - invalidateImageCaches(); + invalidateRenderers(); invalidateMagnitudes(); m_normalization = n; emit layerParametersChanged(); } -SpectrogramLayer::Normalization +ColumnNormalization SpectrogramLayer::getNormalization() const { return m_normalization; } void +SpectrogramLayer::setNormalizeVisibleArea(bool n) +{ + if (m_normalizeVisibleArea == n) return; + + invalidateRenderers(); + invalidateMagnitudes(); + m_normalizeVisibleArea = n; + + emit layerParametersChanged(); +} + +bool +SpectrogramLayer::getNormalizeVisibleArea() const +{ + return m_normalizeVisibleArea; +} + +void SpectrogramLayer::setLayerDormant(const LayerGeometryProvider *v, bool dormant) { if (dormant) { @@ -921,33 +1005,7 @@ Layer::setLayerDormant(v, true); - const View *view = v->getView(); - - invalidateImageCaches(); - - 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) { - if (i->second != m_sliceableModel) { - emit sliceableModelReplaced(m_sliceableModel, i->second); - replaced = true; - break; - } - } - if (!replaced) emit sliceableModelReplaced(m_sliceableModel, 0); - } - - delete m_fftModels[view->getId()]; - m_fftModels.erase(view->getId()); - - delete m_peakCaches[view->getId()]; - m_peakCaches.erase(view->getId()); - } + invalidateRenderers(); } else { @@ -962,7 +1020,7 @@ cerr << "SpectrogramLayer::cacheInvalid()" << endl; #endif - invalidateImageCaches(); + invalidateRenderers(); invalidateMagnitudes(); } @@ -985,7 +1043,7 @@ // 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(); + invalidateRenderers(); invalidateMagnitudes(); } @@ -995,146 +1053,16 @@ return ColourMapper(m_colourMap, 1.f, 255.f).hasLightBackground(); } -void -SpectrogramLayer::initialisePalette() -{ - int formerRotation = m_colourRotation; - - if (m_colourMap == (int)ColourMapper::BlackOnWhite) { - m_palette.setColour(NO_VALUE, Qt::white); - } else { - m_palette.setColour(NO_VALUE, Qt::black); - } - - ColourMapper mapper(m_colourMap, 1.f, 255.f); - - for (int pixel = 1; pixel < 256; ++pixel) { - m_palette.setColour((unsigned char)pixel, mapper.map(pixel)); - } - - m_crosshairColour = mapper.getContrastingColour(); - - m_colourRotation = 0; - rotatePalette(m_colourRotation - formerRotation); - m_colourRotation = formerRotation; - - m_drawBuffer = QImage(); -} - -void -SpectrogramLayer::rotatePalette(int distance) -{ - QColor newPixels[256]; - - newPixels[NO_VALUE] = m_palette.getColour(NO_VALUE); - - for (int pixel = 1; pixel < 256; ++pixel) { - int target = pixel + distance; - while (target < 1) target += 255; - while (target > 255) target -= 255; - newPixels[target] = m_palette.getColour((unsigned char)pixel); - } - - for (int pixel = 0; pixel < 256; ++pixel) { - m_palette.setColour((unsigned char)pixel, newPixels[pixel]); - } - - m_drawBuffer = QImage(); -} - -unsigned char -SpectrogramLayer::getDisplayValue(LayerGeometryProvider *v, double input) const -{ - int value; - - double min = 0.0; - double max = 1.0; - - if (m_normalization == NormalizeVisibleArea) { - min = m_viewMags[v->getId()].getMin(); - max = m_viewMags[v->getId()].getMax(); - } else if (m_normalization != NormalizeColumns) { - if (m_colourScale == LinearColourScale //|| -// m_colourScale == MeterColourScale) { - ) { - max = 0.1; - } - } - - double thresh = -80.0; - - if (max == 0.0) max = 1.0; - if (max == min) min = max - 0.0001; - - switch (m_colourScale) { - - default: - case LinearColourScale: - value = int(((input - min) / (max - min)) * 255.0) + 1; - break; - - case MeterColourScale: - value = AudioLevel::multiplier_to_preview - ((input - min) / (max - min), 254) + 1; - break; - - case dBSquaredColourScale: - input = ((input - min) * (input - min)) / ((max - min) * (max - min)); - if (input > 0.0) { - input = 10.0 * log10(input); - } else { - input = thresh; - } - if (min > 0.0) { - thresh = 10.0 * log10(min * min); - if (thresh < -80.0) thresh = -80.0; - } - input = (input - thresh) / (-thresh); - if (input < 0.0) input = 0.0; - if (input > 1.0) input = 1.0; - value = int(input * 255.0) + 1; - break; - - case dBColourScale: - //!!! experiment with normalizing the visible area this way. - //In any case, we need to have some indication of what the dB - //scale is relative to. - input = (input - min) / (max - min); - if (input > 0.0) { - input = 10.0 * log10(input); - } else { - input = thresh; - } - if (min > 0.0) { - thresh = 10.0 * log10(min); - if (thresh < -80.0) thresh = -80.0; - } - input = (input - thresh) / (-thresh); - if (input < 0.0) input = 0.0; - if (input > 1.0) input = 1.0; - value = int(input * 255.0) + 1; - break; - - case PhaseColourScale: - value = int((input * 127.0 / M_PI) + 128); - break; - } - - if (value > UCHAR_MAX) value = UCHAR_MAX; - if (value < 0) value = 0; - return (unsigned char)value; -} - double SpectrogramLayer::getEffectiveMinFrequency() const { sv_samplerate_t sr = m_model->getSampleRate(); - double minf = double(sr) / m_fftSize; + double minf = double(sr) / getFFTSize(); if (m_minFrequency > 0.0) { - int minbin = int((double(m_minFrequency) * m_fftSize) / sr + 0.01); + int minbin = int((double(m_minFrequency) * getFFTSize()) / sr + 0.01); if (minbin < 1) minbin = 1; - minf = minbin * sr / m_fftSize; + minf = minbin * sr / getFFTSize(); } return minf; @@ -1147,9 +1075,9 @@ double maxf = double(sr) / 2; if (m_maxFrequency > 0.0) { - int maxbin = int((double(m_maxFrequency) * m_fftSize) / sr + 0.1); - if (maxbin > m_fftSize / 2) maxbin = m_fftSize / 2; - maxf = maxbin * sr / m_fftSize; + int maxbin = int((double(m_maxFrequency) * getFFTSize()) / sr + 0.1); + if (maxbin > getFFTSize() / 2) maxbin = getFFTSize() / 2; + maxf = maxbin * sr / getFFTSize(); } return maxf; @@ -1159,55 +1087,46 @@ SpectrogramLayer::getYBinRange(LayerGeometryProvider *v, int y, double &q0, double &q1) const { Profiler profiler("SpectrogramLayer::getYBinRange"); - int h = v->getPaintHeight(); if (y < 0 || y >= h) return false; - + q0 = getBinForY(v, y); + q1 = getBinForY(v, y-1); + return true; +} + +double +SpectrogramLayer::getYForBin(const LayerGeometryProvider *v, double bin) const +{ + double minf = getEffectiveMinFrequency(); + double maxf = getEffectiveMaxFrequency(); + bool logarithmic = (m_binScale == BinScale::Log); + sv_samplerate_t sr = m_model->getSampleRate(); + + double freq = (bin * sr) / getFFTSize(); + + double y = v->getYForFrequency(freq, minf, maxf, logarithmic); + + return y; +} + +double +SpectrogramLayer::getBinForY(const LayerGeometryProvider *v, double y) const +{ sv_samplerate_t sr = m_model->getSampleRate(); double minf = getEffectiveMinFrequency(); double maxf = getEffectiveMaxFrequency(); - bool logarithmic = (m_frequencyScale == LogFrequencyScale); - - q0 = v->getFrequencyForY(y, minf, maxf, logarithmic); - q1 = v->getFrequencyForY(y - 1, minf, maxf, logarithmic); - - // Now map these on to ("proportions of") actual bins, using raw - // FFT size (unsmoothed) - - q0 = (q0 * m_fftSize) / sr; - q1 = (q1 * m_fftSize) / sr; - - return true; + bool logarithmic = (m_binScale == BinScale::Log); + + double freq = v->getFrequencyForY(y, minf, maxf, logarithmic); + + // Now map on to ("proportion of") actual bins + double bin = (freq * getFFTSize()) / sr; + + return bin; } bool -SpectrogramLayer::getSmoothedYBinRange(LayerGeometryProvider *v, int y, double &q0, double &q1) const -{ - Profiler profiler("SpectrogramLayer::getSmoothedYBinRange"); - - int h = v->getPaintHeight(); - if (y < 0 || y >= h) return false; - - sv_samplerate_t sr = m_model->getSampleRate(); - double minf = getEffectiveMinFrequency(); - double maxf = getEffectiveMaxFrequency(); - - bool logarithmic = (m_frequencyScale == LogFrequencyScale); - - q0 = v->getFrequencyForY(y, minf, maxf, logarithmic); - q1 = v->getFrequencyForY(y - 1, minf, maxf, logarithmic); - - // Now map these on to ("proportions of") actual bins, using raw - // FFT size (unsmoothed) - - q0 = (q0 * getFFTSize(v)) / sr; - q1 = (q1 * getFFTSize(v)) / sr; - - return true; -} - -bool SpectrogramLayer::getXBinRange(LayerGeometryProvider *v, int x, double &s0, double &s1) const { sv_frame_t modelStart = m_model->getStartFrame(); @@ -1263,8 +1182,8 @@ sv_samplerate_t sr = m_model->getSampleRate(); for (int q = q0i; q <= q1i; ++q) { - if (q == q0i) freqMin = (sr * q) / m_fftSize; - if (q == q1i) freqMax = (sr * (q+1)) / m_fftSize; + if (q == q0i) freqMin = (sr * q) / getFFTSize(); + if (q == q1i) freqMax = (sr * (q+1)) / getFFTSize(); } return true; } @@ -1279,7 +1198,7 @@ return false; } - FFTModel *fft = getFFTModel(v); + FFTModel *fft = getFFTModel(); if (!fft) return false; double s0 = 0, s1 = 0; @@ -1298,8 +1217,8 @@ bool haveAdj = false; - bool peaksOnly = (m_binDisplay == PeakBins || - m_binDisplay == PeakFrequencies); + bool peaksOnly = (m_binDisplay == BinDisplay::PeakBins || + m_binDisplay == BinDisplay::PeakFrequencies); for (int q = q0i; q <= q1i; ++q) { @@ -1311,7 +1230,10 @@ if (peaksOnly && !fft->isLocalPeak(s, q)) continue; - if (!fft->isOverThreshold(s, q, float(m_threshold * double(m_fftSize)/2.0))) continue; + if (!fft->isOverThreshold + (s, q, float(m_threshold * double(getFFTSize())/2.0))) { + continue; + } double freq = binfreq; @@ -1357,11 +1279,7 @@ bool rv = false; - int zp = getZeroPadLevel(v); - q0i *= zp + 1; - q1i *= zp + 1; - - FFTModel *fft = getFFTModel(v); + FFTModel *fft = getFFTModel(); if (fft) { @@ -1384,7 +1302,7 @@ if (!have || value < phaseMin) { phaseMin = value; } if (!have || value > phaseMax) { phaseMax = value; } - value = fft->getMagnitudeAt(s, q) / (m_fftSize/2.0); + value = fft->getMagnitudeAt(s, q) / (getFFTSize()/2.0); if (!have || value < min) { min = value; } if (!have || value > max) { max = value; } @@ -1400,166 +1318,83 @@ return rv; } - -int -SpectrogramLayer::getZeroPadLevel(const LayerGeometryProvider *v) const -{ - //!!! tidy all this stuff - - if (m_binDisplay != AllBins) return 0; - - Preferences::SpectrogramSmoothing smoothing = - Preferences::getInstance()->getSpectrogramSmoothing(); - - if (smoothing == Preferences::NoSpectrogramSmoothing || - smoothing == Preferences::SpectrogramInterpolated) return 0; - - if (m_frequencyScale == LogFrequencyScale) return 3; - - sv_samplerate_t sr = m_model->getSampleRate(); - - int maxbin = m_fftSize / 2; - if (m_maxFrequency > 0) { - maxbin = int((double(m_maxFrequency) * m_fftSize) / sr + 0.1); - if (maxbin > m_fftSize / 2) maxbin = m_fftSize / 2; - } - - int minbin = 1; - if (m_minFrequency > 0) { - minbin = int((double(m_minFrequency) * m_fftSize) / sr + 0.1); - if (minbin < 1) minbin = 1; - if (minbin >= maxbin) minbin = maxbin - 1; - } - - double perPixel = - double(v->getPaintHeight()) / - double((maxbin - minbin) / (m_zeroPadLevel + 1)); - - if (perPixel > 2.8) { - return 3; // 4x oversampling - } else if (perPixel > 1.5) { - return 1; // 2x - } else { - return 0; // 1x - } -} - -int -SpectrogramLayer::getFFTSize(const LayerGeometryProvider *v) const -{ - return m_fftSize * (getZeroPadLevel(v) + 1); -} FFTModel * -SpectrogramLayer::getFFTModel(const LayerGeometryProvider *v) const +SpectrogramLayer::getFFTModel() const { if (!m_model) return 0; - int fftSize = getFFTSize(v); - - const View *view = v->getView(); + int fftSize = getFFTSize(); + + //!!! it is now surely slower to do this on every getFFTModel() + //!!! request than it would be to recreate the model immediately + //!!! when something changes instead of just invalidating it - 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->getId()]->getHeight() != fftSize / 2 + 1) { -#ifdef DEBUG_SPECTROGRAM_REPAINT - 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->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->getId()]->getHeight() << endl; -#endif - return m_fftModels[view->getId()]; - } + if (m_fftModel && + m_fftModel->getHeight() == fftSize / 2 + 1 && + m_fftModel->getWindowIncrement() == getWindowIncrement()) { + return m_fftModel; } - - if (m_fftModels.find(view->getId()) == m_fftModels.end()) { - - FFTModel *model = new FFTModel(m_model, - m_channel, - m_windowType, - m_windowSize, - getWindowIncrement(), - fftSize); - - if (!model->isOK()) { - QMessageBox::critical - (0, tr("FFT cache failed"), - 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->getId()] = 0; - return 0; - } - - if (!m_sliceableModel) { -#ifdef DEBUG_SPECTROGRAM - cerr << "SpectrogramLayer: emitting sliceableModelReplaced(0, " << model << ")" << endl; -#endif - ((SpectrogramLayer *)this)->sliceableModelReplaced(0, model); - m_sliceableModel = model; - } - - m_fftModels[view->getId()] = model; + + delete m_peakCache; + m_peakCache = 0; + + delete m_fftModel; + m_fftModel = new FFTModel(m_model, + m_channel, + m_windowType, + m_windowSize, + getWindowIncrement(), + fftSize); + + if (!m_fftModel->isOK()) { + QMessageBox::critical + (0, tr("FFT cache failed"), + tr("Failed to create the FFT model for this spectrogram.\n" + "There may be insufficient memory or disc space to continue.")); + delete m_fftModel; + m_fftModel = 0; + return 0; } - return m_fftModels[view->getId()]; + ((SpectrogramLayer *)this)->sliceableModelReplaced(0, m_fftModel); + + return m_fftModel; } Dense3DModelPeakCache * -SpectrogramLayer::getPeakCache(const LayerGeometryProvider *v) const +SpectrogramLayer::getPeakCache() const { - const View *view = v->getView(); - if (!m_peakCaches[view->getId()]) { - FFTModel *f = getFFTModel(v); + //!!! see comment in getFFTModel + + if (!m_peakCache) { + FFTModel *f = getFFTModel(); if (!f) return 0; - m_peakCaches[view->getId()] = - new Dense3DModelPeakCache(f, m_peakCacheDivisor); + m_peakCache = new Dense3DModelPeakCache(f, m_peakCacheDivisor); } - return m_peakCaches[view->getId()]; + return m_peakCache; } const Model * SpectrogramLayer::getSliceableModel() const { - if (m_sliceableModel) return m_sliceableModel; - if (m_fftModels.empty()) return 0; - m_sliceableModel = m_fftModels.begin()->second; - return m_sliceableModel; + return m_fftModel; } void -SpectrogramLayer::invalidateFFTModels() +SpectrogramLayer::invalidateFFTModel() { #ifdef DEBUG_SPECTROGRAM - cerr << "SpectrogramLayer::invalidateFFTModels called" << endl; + cerr << "SpectrogramLayer::invalidateFFTModel called" << endl; #endif - for (ViewFFTMap::iterator i = m_fftModels.begin(); - i != m_fftModels.end(); ++i) { - delete i->second; - } - for (PeakCacheMap::iterator i = m_peakCaches.begin(); - i != m_peakCaches.end(); ++i) { - delete i->second; - } - - m_fftModels.clear(); - m_peakCaches.clear(); - - if (m_sliceableModel) { - cerr << "SpectrogramLayer: emitting sliceableModelReplaced(" << m_sliceableModel << ", 0)" << endl; - emit sliceableModelReplaced(m_sliceableModel, 0); - m_sliceableModel = 0; - } + + emit sliceableModelReplaced(m_fftModel, 0); + + delete m_fftModel; + delete m_peakCache; + + m_fftModel = 0; + m_peakCache = 0; } void @@ -1569,54 +1404,6 @@ cerr << "SpectrogramLayer::invalidateMagnitudes called" << endl; #endif m_viewMags.clear(); - for (vector<MagnitudeRange>::iterator i = m_columnMags.begin(); - i != m_columnMags.end(); ++i) { - *i = MagnitudeRange(); - } -} - -bool -SpectrogramLayer::updateViewMagnitudes(LayerGeometryProvider *v) const -{ - MagnitudeRange mag; - - int x0 = 0, x1 = v->getPaintWidth(); - double s00 = 0, s01 = 0, s10 = 0, s11 = 0; - - if (!getXBinRange(v, x0, s00, s01)) { - s00 = s01 = double(m_model->getStartFrame()) / getWindowIncrement(); - } - - if (!getXBinRange(v, x1, s10, s11)) { - s10 = s11 = double(m_model->getEndFrame()) / getWindowIncrement(); - } - - 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; - - if (int(m_columnMags.size()) <= s1) { - m_columnMags.resize(s1 + 1); - } - - for (int s = s0; s <= s1; ++s) { - if (m_columnMags[s].isSet()) { - mag.sample(m_columnMags[s]); - } - } - -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "SpectrogramLayer::updateViewMagnitudes returning from cols " - << s0 << " -> " << s1 << " inclusive" << endl; - cerr << "SpectrogramLayer::updateViewMagnitudes: for view id " << v->getId() - << ": min is " << mag.getMin() << ", max is " << mag.getMax() << endl; -#endif - - if (!mag.isSet()) return false; - if (mag == m_viewMags[v->getId()]) return false; - m_viewMags[v->getId()] = mag; - return true; } void @@ -1625,13 +1412,136 @@ m_synchronous = synchronous; } -ScrollableImageCache & -SpectrogramLayer::getImageCacheReference(const LayerGeometryProvider *view) const +Colour3DPlotRenderer * +SpectrogramLayer::getRenderer(LayerGeometryProvider *v) const { - if (m_imageCaches.find(view->getId()) == m_imageCaches.end()) { - m_imageCaches[view->getId()] = ScrollableImageCache(view); + int viewId = v->getId(); + + if (m_renderers.find(viewId) == m_renderers.end()) { + + Colour3DPlotRenderer::Sources sources; + sources.verticalBinLayer = this; + sources.fft = getFFTModel(); + sources.source = sources.fft; + sources.peaks = getPeakCache(); + + ColourScale::Parameters cparams; + cparams.colourMap = m_colourMap; + cparams.scaleType = m_colourScale; + cparams.multiple = m_colourScaleMultiple; + + if (m_colourScale != ColourScaleType::Phase) { + cparams.gain = m_gain; + cparams.threshold = m_threshold; + } + + float minValue = 0.0f; + float maxValue = 1.0f; + + if (m_normalizeVisibleArea && m_viewMags[viewId].isSet()) { + minValue = m_viewMags[viewId].getMin(); + maxValue = m_viewMags[viewId].getMax(); + } else if (m_colourScale == ColourScaleType::Linear && + m_normalization == ColumnNormalization::None) { + maxValue = 0.1f; + } + + if (maxValue <= minValue) { + maxValue = minValue + 0.1f; + } + if (maxValue <= m_threshold) { + maxValue = m_threshold + 0.1f; + } + + cparams.minValue = minValue; + cparams.maxValue = maxValue; + + m_lastRenderedMags[viewId] = MagnitudeRange(minValue, maxValue); + + Colour3DPlotRenderer::Parameters params; + params.colourScale = ColourScale(cparams); + params.normalization = m_normalization; + params.binDisplay = m_binDisplay; + params.binScale = m_binScale; + params.alwaysOpaque = true; + params.invertVertical = false; + params.scaleFactor = 1.0; + params.colourRotation = m_colourRotation; + + if (m_colourScale != ColourScaleType::Phase && + m_normalization != ColumnNormalization::Hybrid) { + params.scaleFactor *= 2.f / float(getFFTSize()); + } + + Preferences::SpectrogramSmoothing smoothing = + Preferences::getInstance()->getSpectrogramSmoothing(); + params.interpolate = + (smoothing == Preferences::SpectrogramInterpolated || + smoothing == Preferences::SpectrogramZeroPaddedAndInterpolated); + + m_renderers[v->getId()] = new Colour3DPlotRenderer(sources, params); } - return m_imageCaches.at(view->getId()); + + return m_renderers[v->getId()]; +} + +void +SpectrogramLayer::paintWithRenderer(LayerGeometryProvider *v, QPainter &paint, QRect rect) const +{ + Colour3DPlotRenderer *renderer = getRenderer(v); + + Colour3DPlotRenderer::RenderResult result; + MagnitudeRange magRange; + int viewId = v->getId(); + + bool continuingPaint = !renderer->geometryChanged(v); + + if (continuingPaint) { + magRange = m_viewMags[viewId]; + } + + if (m_synchronous) { + + result = renderer->render(v, paint, rect); + + } else { + + result = renderer->renderTimeConstrained(v, paint, rect); + +#ifdef DEBUG_SPECTROGRAM_REPAINT + cerr << "rect width from this paint: " << result.rendered.width() + << ", mag range in this paint: " << result.range.getMin() << " -> " + << result.range.getMax() << endl; +#endif + + QRect uncached = renderer->getLargestUncachedRect(v); + if (uncached.width() > 0) { + v->updatePaintRect(uncached); + } + } + + magRange.sample(result.range); + + if (magRange.isSet()) { + if (m_viewMags[viewId] != magRange) { + m_viewMags[viewId] = magRange; +#ifdef DEBUG_SPECTROGRAM_REPAINT + cerr << "mag range in this view has changed: " + << magRange.getMin() << " -> " << magRange.getMax() << endl; +#endif + } + } + + if (!continuingPaint && m_normalizeVisibleArea && + m_viewMags[viewId] != m_lastRenderedMags[viewId]) { +#ifdef DEBUG_SPECTROGRAM_REPAINT + cerr << "mag range has changed from last rendered range: re-rendering" + << endl; +#endif + delete m_renderers[viewId]; + m_renderers.erase(viewId); + v->updatePaintRect(v->getPaintRect()); + } } void @@ -1645,8 +1555,6 @@ cerr << "SpectrogramLayer::paint(): rect is " << rect.x() << "," << rect.y() << " " << rect.width() << "x" << rect.height() << endl; #endif - sv_frame_t startFrame = v->getStartFrame(); - if (!m_model || !m_model->isOK() || !m_model->isReady()) { return; } @@ -1655,995 +1563,9 @@ SVDEBUG << "SpectrogramLayer::paint(): Layer is dormant, making it undormant again" << endl; } - // Need to do this even if !isLayerDormant, as that could mean v - // is not in the dormancy map at all -- we need it to be present - // and accountable for when determining whether we need the cache - // in the cache-fill thread above. - //!!! no inter use cache-fill thread - const_cast<SpectrogramLayer *>(this)->Layer::setLayerDormant(v, false); - - int fftSize = getFFTSize(v); - - 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; - } -#endif - - int zoomLevel = v->getZoomLevel(); - - 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->getId()].getMin() << "->" << m_viewMags[v->getId()].getMax() << "]" << endl; -#endif - if (m_normalization == NormalizeVisibleArea) { - 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 { - // 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; - } - - // 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(); - - int repaintWidth = x1 - x0; - -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "SpectrogramLayer: x0 " << x0 << ", x1 " << x1 - << ", repaintWidth " << repaintWidth << ", h " << h - << ", rightToLeft " << rightToLeft << endl; -#endif - - sv_samplerate_t sr = m_model->getSampleRate(); - - // Set minFreq and maxFreq to the frequency extents of the possibly - // zero-padded visible bin range, and displayMinFreq and displayMaxFreq - // to the actual scale frequency extents (presumably not zero padded). - - // If we are zero padding, we want to use the zero-padded - // equivalents of the bins that we would be using if not zero - // padded, to avoid spaces at the top and bottom of the display. - - // Note fftSize is the actual zero-padded fft size, m_fftSize the - // nominal fft size. - - int maxbin = m_fftSize / 2; - if (m_maxFrequency > 0) { - maxbin = int((double(m_maxFrequency) * m_fftSize) / sr + 0.001); - if (maxbin > m_fftSize / 2) maxbin = m_fftSize / 2; - } - - int minbin = 1; - if (m_minFrequency > 0) { - minbin = int((double(m_minFrequency) * m_fftSize) / sr + 0.001); -// cerr << "m_minFrequency = " << m_minFrequency << " -> minbin = " << minbin << endl; - if (minbin < 1) minbin = 1; - if (minbin >= maxbin) minbin = maxbin - 1; - } - - int zpl = getZeroPadLevel(v) + 1; - minbin = minbin * zpl; - maxbin = (maxbin + 1) * zpl - 1; - - double minFreq = (double(minbin) * sr) / fftSize; - double maxFreq = (double(maxbin) * sr) / fftSize; - - double displayMinFreq = minFreq; - double displayMaxFreq = maxFreq; - - if (fftSize != m_fftSize) { - displayMinFreq = getEffectiveMinFrequency(); - displayMaxFreq = getEffectiveMaxFrequency(); - } - -// cerr << "(giving actual minFreq " << minFreq << " and display minFreq " << displayMinFreq << ")" << endl; - - int increment = getWindowIncrement(); - - bool logarithmic = (m_frequencyScale == LogFrequencyScale); - - MagnitudeRange overallMag = m_viewMags[v->getId()]; - bool overallMagChanged = false; - -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "SpectrogramLayer: " << ((double(v->getFrameForX(1) - v->getFrameForX(0))) / increment) << " bin(s) per pixel" << endl; -#endif - - if (repaintWidth == 0) { - SVDEBUG << "*** NOTE: repaintWidth == 0" << endl; - } - - Profiler outerprof("SpectrogramLayer::paint: all cols"); - - // The draw buffer contains a fragment at either our pixel - // resolution (if there is more than one time-bin per pixel) or - // time-bin resolution (if a time-bin spans more than one pixel). - // We need to ensure that it starts and ends at points where a - // time-bin boundary occurs at an exact pixel boundary, and with a - // certain amount of overlap across existing pixels so that we can - // scale and draw from it without smoothing errors at the edges. - - // If (getFrameForX(x) / increment) * increment == - // getFrameForX(x), then x is a time-bin boundary. We want two - // such boundaries at either side of the draw buffer -- one which - // we draw up to, and one which we subsequently crop at. - - 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 (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; - } - } - } - 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 + repaintWidth + 2) { - rightBoundaryFrame = f; - break; - } - } - } -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "Left: crop: " << leftCropFrame << " (bin " << leftCropFrame/increment << "); boundary: " << leftBoundaryFrame << " (bin " << leftBoundaryFrame/increment << ")" << endl; - cerr << "Right: crop: " << rightCropFrame << " (bin " << rightCropFrame/increment << "); boundary: " << rightBoundaryFrame << " (bin " << rightBoundaryFrame/increment << ")" << endl; -#endif - - bufwid = int((rightBoundaryFrame - leftBoundaryFrame) / increment); - - } else { - - bufwid = repaintWidth; - } - - vector<int> binforx(bufwid); - vector<double> binfory(h); - - bool usePeaksCache = false; - - if (bufferIsBinResolution) { - for (int x = 0; x < bufwid; ++x) { - binforx[x] = int(leftBoundaryFrame / increment) + x; - } - m_drawBuffer = QImage(bufwid, h, QImage::Format_Indexed8); - } else { - for (int x = 0; x < bufwid; ++x) { - double s0 = 0, s1 = 0; - if (getXBinRange(v, x + x0, s0, s1)) { - binforx[x] = int(s0 + 0.0001); - } else { - binforx[x] = -1; //??? - } - } - if (m_drawBuffer.width() < bufwid || m_drawBuffer.height() != h) { - m_drawBuffer = QImage(bufwid, h, QImage::Format_Indexed8); - } - usePeaksCache = (increment * m_peakCacheDivisor) < zoomLevel; - if (m_colourScale == PhaseColourScale) usePeaksCache = false; - } - - 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) { - double q0 = 0, q1 = 0; - if (!getSmoothedYBinRange(v, h-y-1, q0, q1)) { - binfory[y] = -1; - } else { - binfory[y] = q0; - } - } - - attainedBufwid = - paintDrawBuffer(v, bufwid, h, binforx, binfory, - usePeaksCache, - overallMag, overallMagChanged, - rightToLeft, - softTimeLimit); - - } else { - - attainedBufwid = - paintDrawBufferPeakFrequencies(v, bufwid, h, binforx, - minbin, maxbin, - displayMinFreq, displayMaxFreq, - logarithmic, - overallMag, overallMagChanged, - rightToLeft, - softTimeLimit); - } - - int failedToRepaint = bufwid - attainedBufwid; - - int paintedLeft = x0; - int paintedWidth = x1 - x0; - - if (failedToRepaint > 0) { - -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "SpectrogramLayer::paint(): Failed to repaint " << failedToRepaint << " of " << bufwid - << " columns in time (so managed to repaint " << bufwid - failedToRepaint << ")" << endl; -#endif - - 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 << "SpectrogramLayer: Overall mag is now [" << m_viewMags[v->getId()].getMin() << "->" << m_viewMags[v->getId()].getMax() << "] - will be updating" << endl; -#endif - } - - outerprof.end(); - - Profiler profiler2("SpectrogramLayer::paint: draw image"); - - if (paintedWidth > 0) { - -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "SpectrogramLayer: Copying " << paintedWidth << "x" << h - << " from draw buffer at " << paintedLeft - x0 << "," << 0 - << " to " << paintedWidth << "x" << h << " on cache at " - << x0 << "," << 0 << endl; -#endif - - if (bufferIsBinResolution) { - - int scaledLeft = v->getXForFrame(leftBoundaryFrame); - int scaledRight = v->getXForFrame(rightBoundaryFrame); - -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "SpectrogramLayer: Rescaling image from " << bufwid - << "x" << h << " to " - << scaledRight-scaledLeft << "x" << h << endl; -#endif - - Preferences::SpectrogramXSmoothing xsmoothing = - Preferences::getInstance()->getSpectrogramXSmoothing(); - - 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 << "SpectrogramLayer: Drawing image region of width " << scaledRightCrop - scaledLeftCrop << " to " - << scaledLeftCrop << " from " << scaledLeftCrop - scaledLeft << endl; -#endif - - 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 { - - cache.drawImage(paintedLeft, paintedWidth, - m_drawBuffer, - paintedLeft - x0, paintedWidth); - } - } - -#ifdef DEBUG_SPECTROGRAM_REPAINT - 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.getImage(), - pr.x(), pr.y(), pr.width(), pr.height()); - - if (!m_synchronous) { - - if ((m_normalization != NormalizeVisibleArea) || !overallMagChanged) { - - 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 (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 (" - << areaRight.x() << ", " - << areaRight.width() << ")" << endl; -#endif - v->updatePaintRect(areaRight); - } - - } else { - // overallMagChanged - cerr << "\noverallMagChanged - updating all\n" << endl; - cache.invalidate(); - v->updatePaintRect(v->getPaintRect()); - } - } + paintWithRenderer(v, paint, rect); illuminateLocalFeatures(v, paint); - -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "SpectrogramLayer::paint() returning" << endl; -#endif -} - -int -SpectrogramLayer::paintDrawBufferPeakFrequencies(LayerGeometryProvider *v, - int w, - int h, - const vector<int> &binforx, - int minbin, - int maxbin, - double displayMinFreq, - double displayMaxFreq, - bool logarithmic, - MagnitudeRange &overallMag, - bool &overallMagChanged, - bool rightToLeft, - double softTimeLimit) const -{ - Profiler profiler("SpectrogramLayer::paintDrawBufferPeakFrequencies"); - -#ifdef DEBUG_SPECTROGRAM_REPAINT - 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 0; - - FFTModel::PeakSet peakfreqs; - - int psx = -1; - -#ifdef __GNUC__ - float values[maxbin - minbin + 1]; -#else - float *values = (float *)alloca((maxbin - minbin + 1) * sizeof(float)); -#endif - - 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; - - int sx0 = binforx[x]; - int sx1 = sx0; - if (x+1 < w) sx1 = binforx[x+1]; - if (sx0 < 0) sx0 = sx1 - 1; - if (sx0 < 0) continue; - if (sx1 <= sx0) sx1 = sx0 + 1; - - for (int sx = sx0; sx < sx1; ++sx) { - - if (sx < 0 || sx >= int(fft->getWidth())) continue; - - MagnitudeRange mag; - - if (sx != psx) { - peakfreqs = fft->getPeakFrequencies(FFTModel::AllPeaks, sx, - minbin, maxbin - 1); - if (m_colourScale == PhaseColourScale) { - fft->getPhasesAt(sx, values, minbin, maxbin - minbin + 1); - } else if (m_normalization == NormalizeColumns) { - fft->getNormalizedMagnitudesAt(sx, values, minbin, maxbin - minbin + 1); - } else if (m_normalization == NormalizeHybrid) { - float max = fft->getNormalizedMagnitudesAt - (sx, values, minbin, maxbin - minbin + 1); - float scale = log10f(max + 1.f); - for (int i = minbin; i <= maxbin; ++i) { - values[i - minbin] *= scale; - } - } else { - fft->getMagnitudesAt(sx, values, minbin, maxbin - minbin + 1); - } - psx = sx; - } - - for (FFTModel::PeakSet::const_iterator pi = peakfreqs.begin(); - pi != peakfreqs.end(); ++pi) { - - int bin = pi->first; - double freq = pi->second; - - if (bin < minbin) continue; - if (bin > maxbin) break; - - double value = values[bin - minbin]; - - if (m_colourScale != PhaseColourScale) { - if (m_normalization != NormalizeColumns) { - value /= (m_fftSize/2.0); - } - mag.sample(float(value)); - value *= m_gain; - } - - double y = v->getYForFrequency - (freq, displayMinFreq, displayMaxFreq, logarithmic); - - int iy = int(y + 0.5); - if (iy < 0 || iy >= h) continue; - - m_drawBuffer.setPixel(x, iy, getDisplayValue(v, value)); - } - - if (mag.isSet()) { - if (sx >= int(m_columnMags.size())) { -#ifdef DEBUG_SPECTROGRAM - cerr << "INTERNAL ERROR: " << sx << " >= " - << m_columnMags.size() - << " at SpectrogramLayer.cpp::paintDrawBuffer" - << endl; -#endif - } else { - m_columnMags[sx].sample(mag); - if (overallMag.sample(mag)) overallMagChanged = true; - } - } - } - - if (haveTimeLimits) { - if (columnCount >= minColumns) { - auto t = chrono::steady_clock::now(); - double diff = chrono::duration<double>(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 columnCount; -} - -int -SpectrogramLayer::paintDrawBuffer(LayerGeometryProvider *v, - int w, - int h, - const vector<int> &binforx, - const vector<double> &binfory, - bool usePeaksCache, - MagnitudeRange &overallMag, - bool &overallMagChanged, - bool rightToLeft, - double softTimeLimit) const -{ - Profiler profiler("SpectrogramLayer::paintDrawBuffer"); - - int minbin = int(binfory[0] + 0.0001); - int maxbin = int(binfory[h-1]); - -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "SpectrogramLayer::paintDrawBuffer: minbin " << minbin << ", maxbin " << maxbin << "; w " << w << ", h " << h << endl; -#endif - if (minbin < 0) minbin = 0; - if (maxbin < 0) maxbin = minbin+1; - - DenseThreeDimensionalModel *sourceModel = 0; - FFTModel *fft = 0; - int divisor = 1; -#ifdef DEBUG_SPECTROGRAM_REPAINT - 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); - divisor = m_peakCacheDivisor; - minbin = 0; - maxbin = sourceModel->getHeight(); - } else { - sourceModel = fft = getFFTModel(v); - } - - if (!sourceModel) return 0; - - bool interpolate = false; - Preferences::SpectrogramSmoothing smoothing = - Preferences::getInstance()->getSpectrogramSmoothing(); - if (smoothing == Preferences::SpectrogramInterpolated || - smoothing == Preferences::SpectrogramZeroPaddedAndInterpolated) { - if (m_binDisplay != PeakBins && - m_binDisplay != PeakFrequencies) { - interpolate = true; - } - } - - int psx = -1; - -#ifdef __GNUC__ - float autoarray[maxbin - minbin + 1]; - float peaks[h]; -#else - float *autoarray = (float *)alloca((maxbin - minbin + 1) * sizeof(float)); - float *peaks = (float *)alloca(h * sizeof(float)); -#endif - - const float *values = autoarray; - DenseThreeDimensionalModel::Column c; - - 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) { - - // x is the on-canvas pixel coord; sx (later) will be the - // source column index - - ++columnCount; - - if (binforx[x] < 0) continue; - - float columnMax = 0.f; - - int sx0 = binforx[x] / divisor; - int sx1 = sx0; - if (x+1 < w) sx1 = binforx[x+1] / divisor; - if (sx0 < 0) sx0 = sx1 - 1; - if (sx0 < 0) continue; - if (sx1 <= sx0) sx1 = sx0 + 1; - - for (int y = 0; y < h; ++y) peaks[y] = 0.f; - - for (int sx = sx0; sx < sx1; ++sx) { - -#ifdef DEBUG_SPECTROGRAM_REPAINT -// cerr << "sx = " << sx << endl; -#endif - - if (sx < 0 || sx >= int(sourceModel->getWidth())) continue; - - MagnitudeRange mag; - - if (sx != psx) { - if (fft) { -#ifdef DEBUG_SPECTROGRAM_REPAINT -// cerr << "Retrieving column " << sx << " from fft directly" << endl; -#endif - if (m_colourScale == PhaseColourScale) { - fft->getPhasesAt(sx, autoarray, minbin, maxbin - minbin + 1); - } else if (m_normalization == NormalizeColumns) { - fft->getNormalizedMagnitudesAt(sx, autoarray, minbin, maxbin - minbin + 1); - } else if (m_normalization == NormalizeHybrid) { - float max = fft->getNormalizedMagnitudesAt - (sx, autoarray, minbin, maxbin - minbin + 1); - float scale = log10f(max + 1.f); - for (int i = minbin; i <= maxbin; ++i) { - autoarray[i - minbin] *= scale; - } - } else { - fft->getMagnitudesAt(sx, autoarray, minbin, maxbin - minbin + 1); - } - } else { -#ifdef DEBUG_SPECTROGRAM_REPAINT -// cerr << "Retrieving column " << sx << " from peaks cache" << endl; -#endif - c = sourceModel->getColumn(sx); - if (m_normalization == NormalizeColumns || - m_normalization == NormalizeHybrid) { - for (int y = 0; y < h; ++y) { - if (c[y] > columnMax) columnMax = c[y]; - } - } - values = c.data() + minbin; - } - psx = sx; - } - - for (int y = 0; y < h; ++y) { - - double sy0 = binfory[y]; - double sy1 = sy0 + 1; - if (y+1 < h) sy1 = binfory[y+1]; - - double value = 0.0; - - if (interpolate && fabs(sy1 - sy0) < 1.0) { - - double centre = (sy0 + sy1) / 2; - double dist = (centre - 0.5) - rint(centre - 0.5); - int bin = int(centre); - int other = (dist < 0 ? (bin-1) : (bin+1)); - if (bin < minbin) bin = minbin; - if (bin > maxbin) bin = maxbin; - if (other < minbin || other > maxbin) other = bin; - double prop = 1.0 - fabs(dist); - - double v0 = values[bin - minbin]; - double v1 = values[other - minbin]; - if (m_binDisplay == PeakBins) { - if (bin == minbin || bin == maxbin || - v0 < values[bin-minbin-1] || - v0 < values[bin-minbin+1]) v0 = 0.0; - if (other == minbin || other == maxbin || - v1 < values[other-minbin-1] || - v1 < values[other-minbin+1]) v1 = 0.0; - } - if (v0 == 0.0 && v1 == 0.0) continue; - value = prop * v0 + (1.0 - prop) * v1; - - if (m_colourScale != PhaseColourScale) { - if (m_normalization != NormalizeColumns && - m_normalization != NormalizeHybrid) { - value /= (m_fftSize/2.0); - } - mag.sample(float(value)); - value *= m_gain; - } - - peaks[y] = float(value); - - } else { - - int by0 = int(sy0 + 0.0001); - int by1 = int(sy1 + 0.0001); - if (by1 < by0 + 1) by1 = by0 + 1; - - for (int bin = by0; bin < by1; ++bin) { - - value = values[bin - minbin]; - if (m_binDisplay == PeakBins) { - if (bin == minbin || bin == maxbin || - value < values[bin-minbin-1] || - value < values[bin-minbin+1]) continue; - } - - if (m_colourScale != PhaseColourScale) { - if (m_normalization != NormalizeColumns && - m_normalization != NormalizeHybrid) { - value /= (m_fftSize/2.0); - } - mag.sample(float(value)); - value *= m_gain; - } - - if (value > peaks[y]) { - peaks[y] = float(value); //!!! not right for phase! - } - } - } - } - - if (mag.isSet()) { - if (sx >= int(m_columnMags.size())) { -#ifdef DEBUG_SPECTROGRAM - cerr << "INTERNAL ERROR: " << sx << " >= " - << m_columnMags.size() - << " at SpectrogramLayer.cpp::paintDrawBuffer" - << endl; -#endif - } else { - m_columnMags[sx].sample(mag); - if (overallMag.sample(mag)) overallMagChanged = true; - } - } - } - - // at this point we have updated m_columnMags and overallMag - // -- used elsewhere for calculating the overall view range - // for NormalizeVisibleArea mode -- and calculated "peaks" - // (the possibly scaled and interpolated value array from - // which we actually draw the column) and "columnMax" (maximum - // value used for normalisation) - - for (int y = 0; y < h; ++y) { - - double peak = peaks[y]; - - if (m_colourScale != PhaseColourScale && - (m_normalization == NormalizeColumns || - m_normalization == NormalizeHybrid) && - columnMax > 0.f) { - peak /= columnMax; - if (m_normalization == NormalizeHybrid) { - peak *= log10(columnMax + 1.f); - } - } - - unsigned char peakpix = getDisplayValue(v, peak); - - m_drawBuffer.setPixel(x, h-y-1, peakpix); - } - - if (haveTimeLimits) { - if (columnCount >= minColumns) { - auto t = chrono::steady_clock::now(); - double diff = chrono::duration<double>(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 columnCount; } void @@ -2656,8 +1578,10 @@ return; } -// cerr << "SpectrogramLayer: illuminateLocalFeatures(" -// << localPos.x() << "," << localPos.y() << ")" << endl; +#ifdef DEBUG_SPECTROGRAM_REPAINT + cerr << "SpectrogramLayer: illuminateLocalFeatures(" + << localPos.x() << "," << localPos.y() << ")" << endl; +#endif double s0, s1; double f0, f1; @@ -2674,8 +1598,10 @@ int y1 = int(getYForFrequency(v, f1)); int y0 = int(getYForFrequency(v, f0)); -// cerr << "SpectrogramLayer: illuminate " -// << x0 << "," << y1 << " -> " << x1 << "," << y0 << endl; +#ifdef DEBUG_SPECTROGRAM_REPAINT + cerr << "SpectrogramLayer: illuminate " + << x0 << "," << y1 << " -> " << x1 << "," << y0 << endl; +#endif paint.setPen(v->getForeground()); @@ -2691,7 +1617,7 @@ return v->getYForFrequency(frequency, getEffectiveMinFrequency(), getEffectiveMaxFrequency(), - m_frequencyScale == LogFrequencyScale); + m_binScale == BinScale::Log); } double @@ -2700,17 +1626,14 @@ return v->getFrequencyForY(y, getEffectiveMinFrequency(), getEffectiveMaxFrequency(), - m_frequencyScale == LogFrequencyScale); + m_binScale == BinScale::Log); } int -SpectrogramLayer::getCompletion(LayerGeometryProvider *v) const +SpectrogramLayer::getCompletion(LayerGeometryProvider *) const { - const View *view = v->getView(); - - if (m_fftModels.find(view->getId()) == m_fftModels.end()) return 100; - - int completion = m_fftModels[view->getId()]->getCompletion(); + if (!m_fftModel) return 100; + int completion = m_fftModel->getCompletion(); #ifdef DEBUG_SPECTROGRAM_REPAINT cerr << "SpectrogramLayer::getCompletion: completion = " << completion << endl; #endif @@ -2718,11 +1641,10 @@ } QString -SpectrogramLayer::getError(LayerGeometryProvider *v) const +SpectrogramLayer::getError(LayerGeometryProvider *) const { - const View *view = v->getView(); - if (m_fftModels.find(view->getId()) == m_fftModels.end()) return ""; - return m_fftModels[view->getId()]->getError(); + if (!m_fftModel) return ""; + return m_fftModel->getError(); } bool @@ -2732,10 +1654,10 @@ if (!m_model) return false; sv_samplerate_t sr = m_model->getSampleRate(); - min = double(sr) / m_fftSize; + min = double(sr) / getFFTSize(); max = double(sr) / 2; - logarithmic = (m_frequencyScale == LogFrequencyScale); + logarithmic = (m_binScale == BinScale::Log); unit = "Hz"; return true; } @@ -2765,7 +1687,7 @@ if (m_minFrequency == minf && m_maxFrequency == maxf) return true; - invalidateImageCaches(); + invalidateRenderers(); invalidateMagnitudes(); m_minFrequency = minf; @@ -2817,16 +1739,10 @@ void SpectrogramLayer::measureDoubleClick(LayerGeometryProvider *v, QMouseEvent *e) { - const View *view = v->getView(); - 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()); + const Colour3DPlotRenderer *renderer = getRenderer(v); + if (!renderer) return; + + QRect rect = renderer->findSimilarRegionExtents(e->pos()); if (rect.isValid()) { MeasureRect mr; setMeasureRectFromPixrect(v, mr, rect); @@ -2894,35 +1810,35 @@ double fundamental = getFrequencyForY(v, cursorPos.y()); - v->drawVisibleText(paint, + PaintAssistant::drawVisibleText(v, paint, sw + 2, cursorPos.y() - 2, QString("%1 Hz").arg(fundamental), - View::OutlinedText); + PaintAssistant::OutlinedText); if (Pitch::isFrequencyInMidiRange(fundamental)) { QString pitchLabel = Pitch::getPitchLabelForFrequency(fundamental); - v->drawVisibleText(paint, + PaintAssistant::drawVisibleText(v, paint, sw + 2, cursorPos.y() + paint.fontMetrics().ascent() + 2, pitchLabel, - View::OutlinedText); + PaintAssistant::OutlinedText); } sv_frame_t frame = v->getFrameForX(cursorPos.x()); RealTime rt = RealTime::frame2RealTime(frame, m_model->getSampleRate()); QString rtLabel = QString("%1 s").arg(rt.toText(true).c_str()); QString frameLabel = QString("%1").arg(frame); - v->drawVisibleText(paint, + PaintAssistant::drawVisibleText(v, paint, cursorPos.x() - paint.fontMetrics().width(frameLabel) - 2, v->getPaintHeight() - 2, frameLabel, - View::OutlinedText); - v->drawVisibleText(paint, + PaintAssistant::OutlinedText); + PaintAssistant::drawVisibleText(v, paint, cursorPos.x() + 2, v->getPaintHeight() - 2, rtLabel, - View::OutlinedText); + PaintAssistant::OutlinedText); int harmonic = 2; @@ -2978,7 +1894,7 @@ QString adjFreqText = "", adjPitchText = ""; - if (m_binDisplay == PeakFrequencies) { + if (m_binDisplay == BinDisplay::PeakFrequencies) { if (!getAdjustedYBinSourceRange(v, x, y, freqMin, freqMax, adjFreqMin, adjFreqMax)) { @@ -3090,13 +2006,14 @@ int fw = paint.fontMetrics().width(tr("43Hz")); if (tw < fw) tw = fw; - int tickw = (m_frequencyScale == LogFrequencyScale ? 10 : 4); + int tickw = (m_binScale == BinScale::Log ? 10 : 4); return cw + tickw + tw + 13; } void -SpectrogramLayer::paintVerticalScale(LayerGeometryProvider *v, bool detailed, QPainter &paint, QRect rect) const +SpectrogramLayer::paintVerticalScale(LayerGeometryProvider *v, bool detailed, + QPainter &paint, QRect rect) const { if (!m_model || !m_model->isOK()) { return; @@ -3105,113 +2022,32 @@ Profiler profiler("SpectrogramLayer::paintVerticalScale"); //!!! cache this? - + int h = rect.height(), w = rect.width(); - - int tickw = (m_frequencyScale == LogFrequencyScale ? 10 : 4); - int pkw = (m_frequencyScale == LogFrequencyScale ? 10 : 0); - - int bins = m_fftSize / 2; + int textHeight = paint.fontMetrics().height(); + + if (detailed && (h > textHeight * 3 + 10)) { + paintDetailedScale(v, paint, rect); + } + m_haveDetailedScale = detailed; + + int tickw = (m_binScale == BinScale::Log ? 10 : 4); + int pkw = (m_binScale == BinScale::Log ? 10 : 0); + + int bins = getFFTSize() / 2; sv_samplerate_t sr = m_model->getSampleRate(); if (m_maxFrequency > 0) { - bins = int((double(m_maxFrequency) * m_fftSize) / sr + 0.1); - if (bins > m_fftSize / 2) bins = m_fftSize / 2; + bins = int((double(m_maxFrequency) * getFFTSize()) / sr + 0.1); + if (bins > getFFTSize() / 2) bins = getFFTSize() / 2; } int cw = 0; - if (detailed) cw = getColourScaleWidth(paint); - int cbw = paint.fontMetrics().width("dB"); int py = -1; - int textHeight = paint.fontMetrics().height(); int toff = -textHeight + paint.fontMetrics().ascent() + 2; - if (detailed && (h > textHeight * 3 + 10)) { - - int topLines = 2; - if (m_colourScale == PhaseColourScale) topLines = 1; - - int ch = h - textHeight * (topLines + 1) - 8; -// paint.drawRect(4, textHeight + 4, cw - 1, ch + 1); - paint.drawRect(4 + cw - cbw, textHeight * topLines + 4, cbw - 1, ch + 1); - - QString top, bottom; - 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); - -#ifdef DEBUG_SPECTROGRAM_REPAINT - cerr << "paintVerticalScale: for view id " << v->getId() - << ": min = " << min << ", max = " << max - << ", dBmin = " << dBmin << ", dBmax = " << dBmax << endl; -#endif - - if (dBmax < -60.f) dBmax = -60.f; - else top = QString("%1").arg(lrint(dBmax)); - - if (dBmin < dBmax - 60.f) dBmin = dBmax - 60.f; - bottom = QString("%1").arg(lrint(dBmin)); - - //!!! & phase etc - - if (m_colourScale != PhaseColourScale) { - paint.drawText((cw + 6 - paint.fontMetrics().width("dBFS")) / 2, - 2 + textHeight + toff, "dBFS"); - } - -// paint.drawText((cw + 6 - paint.fontMetrics().width(top)) / 2, - paint.drawText(3 + cw - cbw - paint.fontMetrics().width(top), - 2 + textHeight * topLines + toff + textHeight/2, top); - - paint.drawText(3 + cw - cbw - paint.fontMetrics().width(bottom), - h + toff - 3 - textHeight/2, bottom); - - paint.save(); - paint.setBrush(Qt::NoBrush); - - int lasty = 0; - int lastdb = 0; - - for (int i = 0; i < ch; ++i) { - - double dBval = dBmin + (((dBmax - dBmin) * i) / (ch - 1)); - int idb = int(dBval); - - double value = AudioLevel::dB_to_multiplier(dBval); - int colour = getDisplayValue(v, value * m_gain); - - paint.setPen(m_palette.getColour((unsigned char)colour)); - - int y = textHeight * topLines + 4 + ch - i; - - paint.drawLine(5 + cw - cbw, y, cw + 2, y); - - if (i == 0) { - lasty = y; - lastdb = idb; - } else if (i < ch - paint.fontMetrics().ascent() && - idb != lastdb && - ((abs(y - lasty) > textHeight && - idb % 10 == 0) || - (abs(y - lasty) > paint.fontMetrics().ascent() && - idb % 5 == 0))) { - paint.setPen(v->getBackground()); - QString text = QString("%1").arg(idb); - paint.drawText(3 + cw - cbw - paint.fontMetrics().width(text), - y + toff + textHeight/2, text); - paint.setPen(v->getForeground()); - paint.drawLine(5 + cw - cbw, y, 8 + cw - cbw, y); - lasty = y; - lastdb = idb; - } - } - paint.restore(); - } - paint.drawLine(cw + 7, 0, cw + 7, h); int bin = -1; @@ -3230,10 +2066,10 @@ continue; } - int freq = int((sr * bin) / m_fftSize); + int freq = int((sr * bin) / getFFTSize()); if (py >= 0 && (vy - py) < textHeight - 1) { - if (m_frequencyScale == LinearFrequencyScale) { + if (m_binScale == BinScale::Linear) { paint.drawLine(w - tickw, h - vy, w, h - vy); } continue; @@ -3251,7 +2087,7 @@ py = vy; } - if (m_frequencyScale == LogFrequencyScale) { + if (m_binScale == BinScale::Log) { // piano keyboard @@ -3263,6 +2099,155 @@ m_haveDetailedScale = detailed; } +void +SpectrogramLayer::paintDetailedScale(LayerGeometryProvider *v, + QPainter &paint, QRect rect) const +{ + // The colour scale + + if (m_colourScale == ColourScaleType::Phase) { + paintDetailedScalePhase(v, paint, rect); + return; + } + + int h = rect.height(); + int textHeight = paint.fontMetrics().height(); + int toff = -textHeight + paint.fontMetrics().ascent() + 2; + + int cw = getColourScaleWidth(paint); + int cbw = paint.fontMetrics().width("dB"); + + int topLines = 2; + + int ch = h - textHeight * (topLines + 1) - 8; +// paint.drawRect(4, textHeight + 4, cw - 1, ch + 1); + paint.drawRect(4 + cw - cbw, textHeight * topLines + 4, cbw - 1, ch + 1); + + QString top, bottom; + double min = m_viewMags[v->getId()].getMin(); + double max = m_viewMags[v->getId()].getMax(); + + if (min < m_threshold) min = m_threshold; + if (max <= min) max = min + 0.1; + + double dBmin = AudioLevel::multiplier_to_dB(min); + double dBmax = AudioLevel::multiplier_to_dB(max); + +#ifdef DEBUG_SPECTROGRAM_REPAINT + cerr << "paintVerticalScale: for view id " << v->getId() + << ": min = " << min << ", max = " << max + << ", dBmin = " << dBmin << ", dBmax = " << dBmax << endl; +#endif + + if (dBmax < -60.f) dBmax = -60.f; + else top = QString("%1").arg(lrint(dBmax)); + + if (dBmin < dBmax - 60.f) dBmin = dBmax - 60.f; + bottom = QString("%1").arg(lrint(dBmin)); + +#ifdef DEBUG_SPECTROGRAM_REPAINT + cerr << "adjusted dB range to min = " << dBmin << ", max = " << dBmax + << endl; +#endif + + paint.drawText((cw + 6 - paint.fontMetrics().width("dBFS")) / 2, + 2 + textHeight + toff, "dBFS"); + + paint.drawText(3 + cw - cbw - paint.fontMetrics().width(top), + 2 + textHeight * topLines + toff + textHeight/2, top); + + paint.drawText(3 + cw - cbw - paint.fontMetrics().width(bottom), + h + toff - 3 - textHeight/2, bottom); + + paint.save(); + paint.setBrush(Qt::NoBrush); + + int lasty = 0; + int lastdb = 0; + + for (int i = 0; i < ch; ++i) { + + double dBval = dBmin + (((dBmax - dBmin) * i) / (ch - 1)); + int idb = int(dBval); + + double value = AudioLevel::dB_to_multiplier(dBval); + paint.setPen(getRenderer(v)->getColour(value)); + + int y = textHeight * topLines + 4 + ch - i; + + paint.drawLine(5 + cw - cbw, y, cw + 2, y); + + if (i == 0) { + lasty = y; + lastdb = idb; + } else if (i < ch - paint.fontMetrics().ascent() && + idb != lastdb && + ((abs(y - lasty) > textHeight && + idb % 10 == 0) || + (abs(y - lasty) > paint.fontMetrics().ascent() && + idb % 5 == 0))) { + paint.setPen(v->getForeground()); + QString text = QString("%1").arg(idb); + paint.drawText(3 + cw - cbw - paint.fontMetrics().width(text), + y + toff + textHeight/2, text); + paint.drawLine(5 + cw - cbw, y, 8 + cw - cbw, y); + lasty = y; + lastdb = idb; + } + } + paint.restore(); +} + +void +SpectrogramLayer::paintDetailedScalePhase(LayerGeometryProvider *v, + QPainter &paint, QRect rect) const +{ + // The colour scale in phase mode + + int h = rect.height(); + int textHeight = paint.fontMetrics().height(); + int toff = -textHeight + paint.fontMetrics().ascent() + 2; + + int cw = getColourScaleWidth(paint); + + // Phase is not measured in dB of course, but this places the + // scale at the same position as in the magnitude spectrogram + int cbw = paint.fontMetrics().width("dB"); + + int topLines = 1; + + int ch = h - textHeight * (topLines + 1) - 8; + paint.drawRect(4 + cw - cbw, textHeight * topLines + 4, cbw - 1, ch + 1); + + QString top, bottom, middle; + top = QString("%1").arg(QChar(0x3c0)); // pi + bottom = "-" + top; + middle = "0"; + + double min = -M_PI; + double max = M_PI; + + paint.drawText(3 + cw - cbw - paint.fontMetrics().width(top), + 2 + textHeight * topLines + toff + textHeight/2, top); + + paint.drawText(3 + cw - cbw - paint.fontMetrics().width(middle), + 2 + textHeight * topLines + ch/2 + toff + textHeight/2, middle); + + paint.drawText(3 + cw - cbw - paint.fontMetrics().width(bottom), + h + toff - 3 - textHeight/2, bottom); + + paint.save(); + paint.setBrush(Qt::NoBrush); + + for (int i = 0; i < ch; ++i) { + double val = min + (((max - min) * i) / (ch - 1)); + paint.setPen(getRenderer(v)->getColour(val)); + int y = textHeight * topLines + 4 + ch - i; + paint.drawLine(5 + cw - cbw, y, cw + 2, y); + } + paint.restore(); +} + class SpectrogramRangeMapper : public RangeMapper { public: @@ -3327,9 +2312,9 @@ sv_samplerate_t sr = m_model->getSampleRate(); - SpectrogramRangeMapper mapper(sr, m_fftSize); - -// int maxStep = mapper.getPositionForValue((double(sr) / m_fftSize) + 0.001); + SpectrogramRangeMapper mapper(sr, getFFTSize()); + +// int maxStep = mapper.getPositionForValue((double(sr) / getFFTSize()) + 0.001); int maxStep = mapper.getPositionForValue(0); int minStep = mapper.getPositionForValue(double(sr) / 2); @@ -3351,7 +2336,7 @@ double dmin, dmax; getDisplayExtents(dmin, dmax); - SpectrogramRangeMapper mapper(m_model->getSampleRate(), m_fftSize); + SpectrogramRangeMapper mapper(m_model->getSampleRate(), getFFTSize()); int n = mapper.getPositionForValue(dmax - dmin); // SVDEBUG << "SpectrogramLayer::getCurrentVerticalZoomStep: " << n << endl; return n; @@ -3368,12 +2353,12 @@ // cerr << "current range " << dmin << " -> " << dmax << ", range " << dmax-dmin << ", mid " << (dmax + dmin)/2 << endl; sv_samplerate_t sr = m_model->getSampleRate(); - SpectrogramRangeMapper mapper(sr, m_fftSize); + SpectrogramRangeMapper mapper(sr, getFFTSize()); double newdist = mapper.getValueForPosition(step); double newmin, newmax; - if (m_frequencyScale == LogFrequencyScale) { + if (m_binScale == BinScale::Log) { // need to pick newmin and newmax such that // @@ -3429,7 +2414,7 @@ SpectrogramLayer::getNewVerticalZoomRangeMapper() const { if (!m_model) return 0; - return new SpectrogramRangeMapper(m_model->getSampleRate(), m_fftSize); + return new SpectrogramRangeMapper(m_model->getSampleRate(), getFFTSize()); } void @@ -3485,11 +2470,11 @@ "binDisplay=\"%7\" ") .arg(m_minFrequency) .arg(m_maxFrequency) - .arg(m_colourScale) + .arg(convertFromColourScale(m_colourScale, m_colourScaleMultiple)) .arg(m_colourMap) .arg(m_colourRotation) - .arg(m_frequencyScale) - .arg(m_binDisplay); + .arg(int(m_binScale)) + .arg(int(m_binDisplay)); // New-style normalization attributes, allowing for more types of // normalization in future: write out the column normalization @@ -3497,8 +2482,8 @@ // area as well afterwards s += QString("columnNormalization=\"%1\" ") - .arg(m_normalization == NormalizeColumns ? "peak" : - m_normalization == NormalizeHybrid ? "hybrid" : "none"); + .arg(m_normalization == ColumnNormalization::Max1 ? "peak" : + m_normalization == ColumnNormalization::Hybrid ? "hybrid" : "none"); // Old-style normalization attribute. We *don't* write out // normalizeHybrid here because the only release that would accept @@ -3507,12 +2492,12 @@ // v2.0+ will look odd in Tony v1.0 s += QString("normalizeColumns=\"%1\" ") - .arg(m_normalization == NormalizeColumns ? "true" : "false"); + .arg(m_normalization == ColumnNormalization::Max1 ? "true" : "false"); // And this applies to both old- and new-style attributes s += QString("normalizeVisibleArea=\"%1\" ") - .arg(m_normalization == NormalizeVisibleArea ? "true" : "false"); + .arg(m_normalizeVisibleArea ? "true" : "false"); Layer::toXml(stream, indent, extraAttributes + " " + s); } @@ -3560,9 +2545,12 @@ setMaxFrequency(maxFrequency); } - ColourScale colourScale = (ColourScale) - attributes.value("colourScale").toInt(&ok); - if (ok) setColourScale(colourScale); + auto colourScale = convertToColourScale + (attributes.value("colourScale").toInt(&ok)); + if (ok) { + setColourScale(colourScale.first); + setColourScaleMultiple(colourScale.second); + } int colourMap = attributes.value("colourScheme").toInt(&ok); if (ok) setColourMap(colourMap); @@ -3570,9 +2558,9 @@ int colourRotation = attributes.value("colourRotation").toInt(&ok); if (ok) setColourRotation(colourRotation); - FrequencyScale frequencyScale = (FrequencyScale) + BinScale binScale = (BinScale) attributes.value("frequencyScale").toInt(&ok); - if (ok) setFrequencyScale(frequencyScale); + if (ok) setBinScale(binScale); BinDisplay binDisplay = (BinDisplay) attributes.value("binDisplay").toInt(&ok); @@ -3587,11 +2575,11 @@ haveNewStyleNormalization = true; if (columnNormalization == "peak") { - setNormalization(NormalizeColumns); + setNormalization(ColumnNormalization::Max1); } else if (columnNormalization == "hybrid") { - setNormalization(NormalizeHybrid); + setNormalization(ColumnNormalization::Hybrid); } else if (columnNormalization == "none") { - // do nothing + setNormalization(ColumnNormalization::None); } else { cerr << "NOTE: Unknown or unsupported columnNormalization attribute \"" << columnNormalization << "\"" << endl; @@ -3603,29 +2591,27 @@ bool normalizeColumns = (attributes.value("normalizeColumns").trimmed() == "true"); if (normalizeColumns) { - setNormalization(NormalizeColumns); + setNormalization(ColumnNormalization::Max1); } bool normalizeHybrid = (attributes.value("normalizeHybrid").trimmed() == "true"); if (normalizeHybrid) { - setNormalization(NormalizeHybrid); + setNormalization(ColumnNormalization::Hybrid); } } bool normalizeVisibleArea = - (attributes.value("normalizeVisibleArea").trimmed() == "true"); - if (normalizeVisibleArea) { - setNormalization(NormalizeVisibleArea); - } - - if (!haveNewStyleNormalization && m_normalization == NormalizeHybrid) { + (attributes.value("normalizeVisibleArea").trimmed() == "true"); + setNormalizeVisibleArea(normalizeVisibleArea); + + if (!haveNewStyleNormalization && m_normalization == ColumnNormalization::Hybrid) { // Tony v1.0 is (and hopefully will remain!) the only released // SV-a-like to use old-style attributes when saving sessions // that ask for hybrid normalization. It saves them with the // wrong gain factor, so hack in a fix for that here -- this // gives us backward but not forward compatibility. - setGain(m_gain / float(m_fftSize / 2)); + setGain(m_gain / float(getFFTSize() / 2)); } }
--- a/layer/SpectrogramLayer.h Mon Jun 13 11:44:08 2016 +0100 +++ b/layer/SpectrogramLayer.h Fri Aug 05 15:05:02 2016 +0100 @@ -18,6 +18,7 @@ #include "SliceableLayer.h" #include "base/Window.h" +#include "base/MagnitudeRange.h" #include "base/RealTime.h" #include "base/Thread.h" #include "base/PropertyContainer.h" @@ -25,7 +26,9 @@ #include "data/model/DenseTimeValueModel.h" #include "data/model/FFTModel.h" -#include "ScrollableImageCache.h" +#include "VerticalBinLayer.h" +#include "ColourScale.h" +#include "Colour3DPlotRenderer.h" #include <QMutex> #include <QWaitCondition> @@ -40,13 +43,12 @@ class FFTModel; class Dense3DModelPeakCache; - /** * SpectrogramLayer represents waveform data (obtained from a * DenseTimeValueModel) in spectrogram form. */ -class SpectrogramLayer : public SliceableLayer, +class SpectrogramLayer : public VerticalBinLayer, public PowerOfSqrtTwoZoomConstraint { Q_OBJECT @@ -112,9 +114,6 @@ void setWindowType(WindowType type); WindowType getWindowType() const; - void setZeroPadLevel(int level); - int getZeroPadLevel() const; - /** * Set the gain multiplier for sample values in this view. * The default is 1.0. @@ -126,7 +125,7 @@ * Set the threshold for sample values to qualify for being shown * in the FFT, in voltage units. * - * The default is 0.0. + * The default is 10^-8 (-80dB). */ void setThreshold(float threshold); float getThreshold() const; @@ -137,56 +136,44 @@ void setMaxFrequency(int); // 0 -> no maximum int getMaxFrequency() const; - enum ColourScale { - LinearColourScale, - MeterColourScale, - dBSquaredColourScale, - dBColourScale, - PhaseColourScale - }; + /** + * Specify the scale for sample levels. See ColourScale and + * WaveformLayer for comparison and details of meter and dB + * scaling. The default is LogColourScale. + */ + void setColourScale(ColourScaleType); + ColourScaleType getColourScale() const; /** - * Specify the scale for sample levels. See WaveformLayer for - * details of meter and dB scaling. The default is dBColourScale. + * Specify multiple factor for colour scale. This is 2.0 for + * log-power spectrogram and 1.0 otherwise. */ - void setColourScale(ColourScale); - ColourScale getColourScale() const; - - enum FrequencyScale { - LinearFrequencyScale, - LogFrequencyScale - }; + void setColourScaleMultiple(double); + double getColourScaleMultiple() const; /** * Specify the scale for the y axis. */ - void setFrequencyScale(FrequencyScale); - FrequencyScale getFrequencyScale() const; + void setBinScale(BinScale); + BinScale getBinScale() const; - enum BinDisplay { - AllBins, - PeakBins, - PeakFrequencies - }; - /** * Specify the processing of frequency bins for the y axis. */ void setBinDisplay(BinDisplay); BinDisplay getBinDisplay() const; - enum Normalization { - NoNormalization, - NormalizeColumns, - NormalizeVisibleArea, - NormalizeHybrid - }; + /** + * Specify the normalization mode for individual columns. + */ + void setNormalization(ColumnNormalization); + ColumnNormalization getNormalization() const; /** - * Specify the normalization mode for bin values. + * Specify whether to normalize the visible area. */ - void setNormalization(Normalization); - Normalization getNormalization() const; + void setNormalizeVisibleArea(bool); + bool getNormalizeVisibleArea() const; /** * Specify the colour map. See ColourMapper for the colour map @@ -214,6 +201,10 @@ double getYForFrequency(const LayerGeometryProvider *v, double frequency) const; double getFrequencyForY(const LayerGeometryProvider *v, int y) const; + //!!! VerticalBinLayer methods. Note overlap with get*BinRange() + double getYForBin(const LayerGeometryProvider *, double bin) const; + double getBinForY(const LayerGeometryProvider *, double y) const; + virtual int getCompletion(LayerGeometryProvider *v) const; virtual QString getError(LayerGeometryProvider *v) const; @@ -255,8 +246,6 @@ int m_windowSize; WindowType m_windowType; int m_windowHopLevel; - int m_zeroPadLevel; - int m_fftSize; float m_gain; float m_initialGain; float m_threshold; @@ -266,56 +255,26 @@ int m_minFrequency; int m_maxFrequency; int m_initialMaxFrequency; - ColourScale m_colourScale; + ColourScaleType m_colourScale; + double m_colourScaleMultiple; int m_colourMap; QColor m_crosshairColour; - FrequencyScale m_frequencyScale; + BinScale m_binScale; BinDisplay m_binDisplay; - Normalization m_normalization; + ColumnNormalization m_normalization; // of individual columns + bool m_normalizeVisibleArea; int m_lastEmittedZoomStep; bool m_synchronous; mutable bool m_haveDetailedScale; - enum { NO_VALUE = 0 }; // colour index for unused pixels - - class Palette - { - public: - QColor getColour(unsigned char index) const { - return m_colours[index]; - } - - void setColour(unsigned char index, QColor colour) { - m_colours[index] = colour; - } - - private: - QColor m_colours[256]; - }; - - Palette m_palette; - - typedef std::map<int, ScrollableImageCache> ViewImageCache; // key is view id - void invalidateImageCaches(); - mutable ViewImageCache m_imageCaches; - ScrollableImageCache &getImageCacheReference(const LayerGeometryProvider *) const; - - /** - * When painting, we draw directly onto the draw buffer and then - * copy this to the part of the image cache that needed refreshing - * before copying the image cache onto the window. (Remind me why - * we don't draw directly onto the cache?) - */ - mutable QImage m_drawBuffer; + static std::pair<ColourScaleType, double> convertToColourScale(int value); + static int convertFromColourScale(ColourScaleType type, double multiple); + static std::pair<ColumnNormalization, bool> convertToColumnNorm(int value); + static int convertFromColumnNorm(ColumnNormalization norm, bool visible); bool m_exiting; - void initialisePalette(); - void rotatePalette(int distance); - - unsigned char getDisplayValue(LayerGeometryProvider *v, double input) const; - int getColourScaleWidth(QPainter &) const; void illuminateLocalFeatures(LayerGeometryProvider *v, QPainter &painter) const; @@ -323,15 +282,8 @@ double getEffectiveMinFrequency() const; double getEffectiveMaxFrequency() const; - // Note that the getYBin... methods return the nominal bin in the - // un-smoothed spectrogram. This is not necessarily the same bin - // as is pulled from the spectrogram and drawn at the given - // position, if the spectrogram has oversampling smoothing. Use - // getSmoothedYBinRange to obtain that. - bool getXBinRange(LayerGeometryProvider *v, int x, double &windowMin, double &windowMax) const; bool getYBinRange(LayerGeometryProvider *v, int y, double &freqBinMin, double &freqBinMax) const; - bool getSmoothedYBinRange(LayerGeometryProvider *v, int y, double &freqBinMin, double &freqBinMax) const; bool getYBinSourceRange(LayerGeometryProvider *v, int y, double &freqMin, double &freqMax) const; bool getAdjustedYBinSourceRange(LayerGeometryProvider *v, int x, int y, @@ -347,88 +299,38 @@ else return m_windowSize / (1 << (m_windowHopLevel - 1)); } - int getZeroPadLevel(const LayerGeometryProvider *v) const; - int getFFTSize(const LayerGeometryProvider *v) const; - FFTModel *getFFTModel(const LayerGeometryProvider *v) const; - Dense3DModelPeakCache *getPeakCache(const LayerGeometryProvider *v) const; - void invalidateFFTModels(); + int getFFTOversampling() const; + int getFFTSize() const; // m_windowSize * getFFTOversampling() - typedef std::map<int, FFTModel *> ViewFFTMap; // key is view id - typedef std::map<int, Dense3DModelPeakCache *> PeakCacheMap; // key is view id - mutable ViewFFTMap m_fftModels; - mutable PeakCacheMap m_peakCaches; + mutable FFTModel *m_fftModel; //!!! should not be mutable, see getFFTModel()? + mutable Dense3DModelPeakCache *m_peakCache; const int m_peakCacheDivisor; - mutable Model *m_sliceableModel; - - class MagnitudeRange { - public: - MagnitudeRange() : m_min(0), m_max(0) { } - bool operator==(const MagnitudeRange &r) { - return r.m_min == m_min && r.m_max == m_max; - } - bool isSet() const { return (m_min != 0.f || m_max != 0.f); } - void set(float min, float max) { - m_min = min; - m_max = max; - if (m_max < m_min) m_max = m_min; - } - bool sample(float f) { - bool changed = false; - if (isSet()) { - if (f < m_min) { m_min = f; changed = true; } - if (f > m_max) { m_max = f; changed = true; } - } else { - m_max = m_min = f; - changed = true; - } - return changed; - } - bool sample(const MagnitudeRange &r) { - bool changed = false; - if (isSet()) { - if (r.m_min < m_min) { m_min = r.m_min; changed = true; } - if (r.m_max > m_max) { m_max = r.m_max; changed = true; } - } else { - m_min = r.m_min; - m_max = r.m_max; - changed = true; - } - return changed; - } - float getMin() const { return m_min; } - float getMax() const { return m_max; } - private: - float m_min; - float m_max; - }; typedef std::map<int, MagnitudeRange> ViewMagMap; // key is view id mutable ViewMagMap m_viewMags; - mutable std::vector<MagnitudeRange> m_columnMags; + mutable ViewMagMap m_lastRenderedMags; // when in normalizeVisibleArea mode void invalidateMagnitudes(); - bool updateViewMagnitudes(LayerGeometryProvider *v) const; - int paintDrawBuffer(LayerGeometryProvider *v, int w, int h, - const std::vector<int> &binforx, - const std::vector<double> &binfory, - bool usePeaksCache, - MagnitudeRange &overallMag, - bool &overallMagChanged, - bool rightToLeft, - double softTimeLimit) const; - int paintDrawBufferPeakFrequencies(LayerGeometryProvider *v, int w, int h, - const std::vector<int> &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; + typedef std::map<int, Colour3DPlotRenderer *> ViewRendererMap; // key is view id + mutable ViewRendererMap m_renderers; + Colour3DPlotRenderer *getRenderer(LayerGeometryProvider *) const; + void invalidateRenderers(); + + FFTModel *getFFTModel() const; + Dense3DModelPeakCache *getPeakCache() const; + void invalidateFFTModel(); + + void paintWithRenderer(LayerGeometryProvider *v, QPainter &paint, QRect rect) const; + + void paintDetailedScale(LayerGeometryProvider *v, + QPainter &paint, QRect rect) const; + void paintDetailedScalePhase(LayerGeometryProvider *v, + QPainter &paint, QRect rect) const; + + virtual void updateMeasureRectYCoords(LayerGeometryProvider *v, + const MeasureRect &r) const; + virtual void setMeasureRectYCoord(LayerGeometryProvider *v, + MeasureRect &r, bool start, int y) const; }; #endif
--- a/layer/SpectrumLayer.cpp Mon Jun 13 11:44:08 2016 +0100 +++ b/layer/SpectrumLayer.cpp Fri Aug 05 15:05:02 2016 +0100 @@ -21,7 +21,9 @@ #include "base/Preferences.h" #include "base/RangeMapper.h" #include "base/Pitch.h" + #include "ColourMapper.h" +#include "PaintAssistant.h" #include <QPainter> #include <QTextStream> @@ -491,19 +493,19 @@ int hoffset = 2; if (m_binScale == LogBins) hoffset = 13; - v->drawVisibleText(paint, + PaintAssistant::drawVisibleText(v, paint, cursorPos.x() + 2, v->getPaintHeight() - 2 - hoffset, QString("%1 Hz").arg(fundamental), - View::OutlinedText); + PaintAssistant::OutlinedText); if (Pitch::isFrequencyInMidiRange(fundamental)) { QString pitchLabel = Pitch::getPitchLabelForFrequency(fundamental); - v->drawVisibleText(paint, + PaintAssistant::drawVisibleText(v, paint, cursorPos.x() - paint.fontMetrics().width(pitchLabel) - 2, v->getPaintHeight() - 2 - hoffset, pitchLabel, - View::OutlinedText); + PaintAssistant::OutlinedText); } double value = getValueForY(cursorPos.y(), v); @@ -512,17 +514,17 @@ if (value > 0.0) db = 10.0 * log10(value); if (db < thresh) db = thresh; - v->drawVisibleText(paint, + PaintAssistant::drawVisibleText(v, paint, xorigin + 2, cursorPos.y() - 2, QString("%1 V").arg(value), - View::OutlinedText); + PaintAssistant::OutlinedText); - v->drawVisibleText(paint, + PaintAssistant::drawVisibleText(v, paint, xorigin + 2, cursorPos.y() + 2 + paint.fontMetrics().ascent(), QString("%1 dBV").arg(db), - View::OutlinedText); + PaintAssistant::OutlinedText); int harmonic = 2;
--- a/layer/TimeInstantLayer.cpp Mon Jun 13 11:44:08 2016 +0100 +++ b/layer/TimeInstantLayer.cpp Fri Aug 05 15:05:02 2016 +0100 @@ -20,7 +20,9 @@ #include "view/View.h" #include "base/Profiler.h" #include "base/Clipboard.h" + #include "ColourDatabase.h" +#include "PaintAssistant.h" #include "data/model/SparseOneDimensionalModel.h" @@ -456,7 +458,7 @@ } if (good) { - v->drawVisibleText(paint, x + iw + 2, textY, p.label, View::OutlinedText); + PaintAssistant::drawVisibleText(v, paint, x + iw + 2, textY, p.label, PaintAssistant::OutlinedText); // paint.drawText(x + iw + 2, textY, p.label); } }
--- a/layer/TimeRulerLayer.cpp Mon Jun 13 11:44:08 2016 +0100 +++ b/layer/TimeRulerLayer.cpp Fri Aug 05 15:05:02 2016 +0100 @@ -19,8 +19,10 @@ #include "data/model/Model.h" #include "base/RealTime.h" +#include "view/View.h" + #include "ColourDatabase.h" -#include "view/View.h" +#include "PaintAssistant.h" #include <QPainter> @@ -324,7 +326,7 @@ // backmost layer, don't worry about outlining the text paint.drawText(x+2 - tw/2, y, text); } else { - v->drawVisibleText(paint, x+2 - tw/2, y, text, View::OutlinedText); + PaintAssistant::drawVisibleText(v, paint, x+2 - tw/2, y, text, PaintAssistant::OutlinedText); } } }
--- a/layer/TimeValueLayer.cpp Mon Jun 13 11:44:08 2016 +0100 +++ b/layer/TimeValueLayer.cpp Fri Aug 05 15:05:02 2016 +0100 @@ -21,7 +21,6 @@ #include "base/LogRange.h" #include "base/RangeMapper.h" #include "base/Pitch.h" -#include "ColourDatabase.h" #include "view/View.h" #include "data/model/SparseTimeValueModel.h" @@ -31,12 +30,14 @@ #include "widgets/ListInputDialog.h" #include "widgets/TextAbbrev.h" +#include "ColourDatabase.h" #include "ColourMapper.h" #include "PianoScale.h" #include "LinearNumericalScale.h" #include "LogNumericalScale.h" #include "LinearColourScale.h" #include "LogColourScale.h" +#include "PaintAssistant.h" #include <QPainter> #include <QPainterPath> @@ -1214,10 +1215,10 @@ if (haveRoom || (!haveNext && (pointCount == 0 || !italic))) { - v->drawVisibleText(paint, x + 5, textY, label, + PaintAssistant::drawVisibleText(v, paint, x + 5, textY, label, italic ? - View::OutlinedItalicText : - View::OutlinedText); + PaintAssistant::OutlinedItalicText : + PaintAssistant::OutlinedText); } } }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layer/VerticalBinLayer.h Fri Aug 05 15:05:02 2016 +0100 @@ -0,0 +1,62 @@ +/* -*- 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 file copyright 2006-2016 Chris Cannam and QMUL. + + 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 VERTICAL_BIN_LAYER_H +#define VERTICAL_BIN_LAYER_H + +#include "SliceableLayer.h" + +/** + * Interface for layers in which the Y axis corresponds to bin number + * rather than scale value. Colour3DPlotLayer is the obvious example. + * Conceptually these are always SliceableLayers as well, and this + * subclasses from SliceableLayer to avoid a big inheritance mess. + */ +class VerticalBinLayer : public SliceableLayer +{ +public: + /** + * Return the y coordinate at which the given bin "starts" + * (i.e. at the bottom of the bin, if the given bin is an integer + * and the vertical scale is the usual way up). Bin number may be + * fractional, to obtain a position part-way through a bin. + */ + virtual double getYForBin(const LayerGeometryProvider *, double bin) const = 0; + + /** + * As getYForBin, but rounding to integer values. + */ + virtual int getIYForBin(const LayerGeometryProvider *v, int bin) const { + return int(round(getYForBin(v, bin))); + } + + /** + * Return the bin number, possibly fractional, at the given y + * coordinate. Note that the whole numbers occur at the positions + * at which the bins "start" (i.e. the bottom of the visible bin, + * if the vertical scale is the usual way up). + */ + virtual double getBinForY(const LayerGeometryProvider *, double y) const = 0; + + /** + * As getBinForY, but rounding to integer values. + */ + virtual int getIBinForY(const LayerGeometryProvider *v, int y) const { + return int(floor(getBinForY(v, y))); + } +}; + +#endif +
--- a/layer/VerticalScaleLayer.h Mon Jun 13 11:44:08 2016 +0100 +++ b/layer/VerticalScaleLayer.h Fri Aug 05 15:05:02 2016 +0100 @@ -16,6 +16,12 @@ #ifndef VERTICAL_SCALE_LAYER_H #define VERTICAL_SCALE_LAYER_H +/** + * Interface for layers in which the Y axis represents (or can + * sometimes represent, depending on the display mode) the sample + * value. For example, TimeValueLayer uses vertical scale when in + * point mode and so provides this interface. + */ class VerticalScaleLayer { public:
--- a/layer/WaveformLayer.cpp Mon Jun 13 11:44:08 2016 +0100 +++ b/layer/WaveformLayer.cpp Fri Aug 05 15:05:02 2016 +0100 @@ -19,7 +19,9 @@ #include "view/View.h" #include "base/Profiler.h" #include "base/RangeMapper.h" + #include "ColourDatabase.h" +#include "PaintAssistant.h" #include <QPainter> #include <QPixmap>
--- a/svgui.pro Mon Jun 13 11:44:08 2016 +0100 +++ b/svgui.pro Fri Aug 05 15:05:02 2016 +0100 @@ -39,14 +39,17 @@ MOC_DIR = o HEADERS += layer/Colour3DPlotLayer.h \ + layer/Colour3DPlotRenderer.h \ layer/ColourDatabase.h \ layer/ColourMapper.h \ + layer/ColourScale.h \ layer/ColourScaleLayer.h \ layer/FlexiNoteLayer.h \ layer/ImageLayer.h \ layer/ImageRegionFinder.h \ layer/Layer.h \ layer/LayerFactory.h \ + layer/LayerGeometryProvider.h \ layer/LinearNumericalScale.h \ layer/LogNumericalScale.h \ layer/LinearColourScale.h \ @@ -55,7 +58,9 @@ layer/PaintAssistant.h \ layer/PianoScale.h \ layer/RegionLayer.h \ + layer/RenderTimer.h \ layer/ScrollableImageCache.h \ + layer/ScrollableMagRangeCache.h \ layer/SingleColourLayer.h \ layer/SliceableLayer.h \ layer/SliceLayer.h \ @@ -68,8 +73,10 @@ layer/VerticalScaleLayer.h \ layer/WaveformLayer.h SOURCES += layer/Colour3DPlotLayer.cpp \ + layer/Colour3DPlotRenderer.cpp \ layer/ColourDatabase.cpp \ layer/ColourMapper.cpp \ + layer/ColourScale.cpp \ layer/FlexiNoteLayer.cpp \ layer/ImageLayer.cpp \ layer/ImageRegionFinder.cpp \ @@ -84,6 +91,7 @@ layer/PianoScale.cpp \ layer/RegionLayer.cpp \ layer/ScrollableImageCache.cpp \ + layer/ScrollableMagRangeCache.cpp \ layer/SingleColourLayer.cpp \ layer/SliceLayer.cpp \ layer/SpectrogramLayer.cpp \
--- a/view/LayerGeometryProvider.h Mon Jun 13 11:44:08 2016 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,174 +0,0 @@ -/* -*- 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 LAYER_GEOMETRY_PROVIDER_H -#define LAYER_GEOMETRY_PROVIDER_H - -#include "base/BaseTypes.h" - -#include <QMutex> -#include <QMutexLocker> -#include <QPainter> - -class ViewManager; -class View; -class Layer; - -class LayerGeometryProvider -{ -protected: - static int getNextId() { - static QMutex idMutex; - static int nextId = 1; - static int maxId = INT_MAX; - QMutexLocker locker(&idMutex); - int id = nextId; - if (nextId == maxId) { - // we don't expect this to happen in the lifetime of a - // process, but it would be undefined behaviour if it did - // since we're using a signed int, so we should really - // guard for it... - nextId = 1; - } else { - nextId++; - } - return id; - } - -public: - LayerGeometryProvider() { } - - /** - * Retrieve the id of this object. - */ - virtual int getId() const = 0; - - /** - * Retrieve the first visible sample frame on the widget. - * This is a calculated value based on the centre-frame, widget - * width and zoom level. The result may be negative. - */ - virtual sv_frame_t getStartFrame() const = 0; - - /** - * Return the centre frame of the visible widget. This is an - * exact value that does not depend on the zoom block size. Other - * frame values (start, end) are calculated from this based on the - * zoom and other factors. - */ - virtual sv_frame_t getCentreFrame() const = 0; - - /** - * Retrieve the last visible sample frame on the widget. - * This is a calculated value based on the centre-frame, widget - * width and zoom level. - */ - virtual sv_frame_t getEndFrame() const = 0; - - /** - * Return the pixel x-coordinate corresponding to a given sample - * frame (which may be negative). - */ - virtual int getXForFrame(sv_frame_t frame) const = 0; - - /** - * Return the closest frame to the given pixel x-coordinate. - */ - virtual sv_frame_t getFrameForX(int x) const = 0; - - virtual sv_frame_t getModelsStartFrame() const = 0; - 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 - * be useful for layers to match theirs up if desired. - * - * Not thread-safe in logarithmic mode. Call only from GUI thread. - */ - virtual double getYForFrequency(double frequency, double minFreq, double maxFreq, - bool logarithmic) const = 0; - - /** - * Return the closest frequency to the given pixel y-coordinate, - * if the frequency range is as specified. - * - * Not thread-safe in logarithmic mode. Call only from GUI thread. - */ - virtual double getFrequencyForY(int y, double minFreq, double maxFreq, - bool logarithmic) const = 0; - - virtual int getTextLabelHeight(const Layer *layer, QPainter &) const = 0; - - virtual bool getValueExtents(QString unit, double &min, double &max, - bool &log) const = 0; - - /** - * Return the zoom level, i.e. the number of frames per pixel - */ - virtual int getZoomLevel() const = 0; - - /** - * To be called from a layer, to obtain the extent of the surface - * that the layer is currently painting to. This may be the extent - * of the view (if 1x display scaling is in effect) or of a larger - * cached pixmap (if greater display scaling is in effect). - */ - virtual QRect getPaintRect() const = 0; - - virtual QSize getPaintSize() const { return getPaintRect().size(); } - virtual int getPaintWidth() const { return getPaintRect().width(); } - virtual int getPaintHeight() const { return getPaintRect().height(); } - - virtual bool hasLightBackground() const = 0; - virtual QColor getForeground() const = 0; - virtual QColor getBackground() const = 0; - - virtual ViewManager *getViewManager() const = 0; - - virtual bool shouldIlluminateLocalFeatures(const Layer *, QPoint &) const = 0; - virtual bool shouldShowFeatureLabels() const = 0; - - enum TextStyle { - BoxedText, - OutlinedText, - OutlinedItalicText - }; - - virtual void drawVisibleText(QPainter &p, int x, int y, - QString text, TextStyle style) const = 0; - - 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; -}; - -#endif
--- a/view/Pane.cpp Mon Jun 13 11:44:08 2016 +0100 +++ b/view/Pane.cpp Fri Aug 05 15:05:02 2016 +0100 @@ -25,6 +25,7 @@ #include "base/Preferences.h" #include "layer/WaveformLayer.h" #include "layer/TimeRulerLayer.h" +#include "layer/PaintAssistant.h" // GF: added so we can propagate the mouse move event to the note layer for context handling. #include "layer/LayerFactory.h" @@ -777,14 +778,14 @@ int tw = paint.fontMetrics().width(text); int x = width()/2 - 4 - tw; - drawVisibleText(paint, x, y, text, OutlinedText); + PaintAssistant::drawVisibleText(this, paint, x, y, text, PaintAssistant::OutlinedText); } QString text = QString("%1").arg(m_centreFrame); int x = width()/2 + 4; - drawVisibleText(paint, x, y, text, OutlinedText); + PaintAssistant::drawVisibleText(this, paint, x, y, text, PaintAssistant::OutlinedText); } } @@ -866,8 +867,8 @@ return; } - drawVisibleText(paint, m_scaleWidth + 5, - paint.fontMetrics().ascent() + y, text, OutlinedText); + PaintAssistant::drawVisibleText(this, paint, m_scaleWidth + 5, + paint.fontMetrics().ascent() + y, text, PaintAssistant::OutlinedText); paint.restore(); } @@ -905,8 +906,8 @@ return; } - drawVisibleText(paint, m_scaleWidth + 5, - paint.fontMetrics().ascent() + y, text, OutlinedText); + PaintAssistant::drawVisibleText(this, paint, m_scaleWidth + 5, + paint.fontMetrics().ascent() + y, text, PaintAssistant::OutlinedText); paint.restore(); } @@ -954,9 +955,9 @@ paint.setPen(getForeground()); } - drawVisibleText(paint, llx, + PaintAssistant::drawVisibleText(this, paint, llx, lly - fontHeight + fontAscent, - texts[i], OutlinedText); + texts[i], PaintAssistant::OutlinedText); if (!pixmaps[i].isNull()) { paint.drawPixmap(llx - fontAscent - 3, @@ -1018,10 +1019,10 @@ offsetText = tr("+%1").arg(offsetText); } } - drawVisibleText(paint, p0 + 2, fontAscent + fontHeight + 4, startText, OutlinedText); - drawVisibleText(paint, p1 + 2, fontAscent + fontHeight + 4, endText, OutlinedText); - drawVisibleText(paint, p0 + 2, fontAscent + fontHeight*2 + 4, offsetText, OutlinedText); - drawVisibleText(paint, p1 + 2, fontAscent + fontHeight*2 + 4, offsetText, OutlinedText); + PaintAssistant::drawVisibleText(this, paint, p0 + 2, fontAscent + fontHeight + 4, startText, PaintAssistant::OutlinedText); + PaintAssistant::drawVisibleText(this, paint, p1 + 2, fontAscent + fontHeight + 4, endText, PaintAssistant::OutlinedText); + PaintAssistant::drawVisibleText(this, paint, p0 + 2, fontAscent + fontHeight*2 + 4, offsetText, PaintAssistant::OutlinedText); + PaintAssistant::drawVisibleText(this, paint, p1 + 2, fontAscent + fontHeight*2 + 4, offsetText, PaintAssistant::OutlinedText); //!!! duplicating display policy with View::drawSelections @@ -1084,9 +1085,9 @@ if (x < pbw + 5) x = pbw + 5; if (r.x() < x + paint.fontMetrics().width(desc)) { - drawVisibleText(paint, x, + PaintAssistant::drawVisibleText(this, paint, x, height() - fontHeight + fontAscent - 6, - desc, OutlinedText); + desc, PaintAssistant::OutlinedText); } }
--- a/view/View.cpp Mon Jun 13 11:44:08 2016 +0100 +++ b/view/View.cpp Fri Aug 05 15:05:02 2016 +0100 @@ -24,6 +24,8 @@ #include "layer/TimeRulerLayer.h" #include "layer/SingleColourLayer.h" +#include "layer/PaintAssistant.h" + #include "data/model/PowerOfSqrtTwoZoomConstraint.h" #include "data/model/RangeSummarisableTimeValueModel.h" @@ -413,12 +415,12 @@ } double -View::getFrequencyForY(int y, +View::getFrequencyForY(double y, double minf, double maxf, bool logarithmic) const { - int h = height(); + double h = height(); if (logarithmic) { @@ -810,56 +812,6 @@ } void -View::drawVisibleText(QPainter &paint, int x, int y, QString text, TextStyle style) const -{ - if (style == OutlinedText || style == OutlinedItalicText) { - - paint.save(); - - if (style == OutlinedItalicText) { - QFont f(paint.font()); - f.setItalic(true); - paint.setFont(f); - } - - QColor penColour, surroundColour, boxColour; - - penColour = getForeground(); - surroundColour = getBackground(); - boxColour = surroundColour; - boxColour.setAlpha(127); - - paint.setPen(Qt::NoPen); - paint.setBrush(boxColour); - - QRect r = paint.fontMetrics().boundingRect(text); - r.translate(QPoint(x, y)); -// cerr << "drawVisibleText: r = " << r.x() << "," <<r.y() << " " << r.width() << "x" << r.height() << endl; - paint.drawRect(r); - paint.setBrush(Qt::NoBrush); - - paint.setPen(surroundColour); - - for (int dx = -1; dx <= 1; ++dx) { - for (int dy = -1; dy <= 1; ++dy) { - if (!(dx || dy)) continue; - paint.drawText(x + dx, y + dy, text); - } - } - - paint.setPen(penColour); - - paint.drawText(x, y, text); - - paint.restore(); - - } else { - - cerr << "ERROR: View::drawVisibleText: Boxed style not yet implemented!" << endl; - } -} - -void View::setPlaybackFollow(PlaybackFollowMode m) { m_followPlay = m; @@ -2369,32 +2321,32 @@ } if (axs != "") { - drawVisibleText(paint, axx, axy, axs, OutlinedText); + PaintAssistant::drawVisibleText(this, paint, axx, axy, axs, PaintAssistant::OutlinedText); axy += fontHeight; } if (ays != "") { - drawVisibleText(paint, axx, axy, ays, OutlinedText); + PaintAssistant::drawVisibleText(this, paint, axx, axy, ays, PaintAssistant::OutlinedText); axy += fontHeight; } if (bxs != "") { - drawVisibleText(paint, bxx, bxy, bxs, OutlinedText); + PaintAssistant::drawVisibleText(this, paint, bxx, bxy, bxs, PaintAssistant::OutlinedText); bxy += fontHeight; } if (bys != "") { - drawVisibleText(paint, bxx, bxy, bys, OutlinedText); + PaintAssistant::drawVisibleText(this, paint, bxx, bxy, bys, PaintAssistant::OutlinedText); bxy += fontHeight; } if (dxs != "") { - drawVisibleText(paint, dxx, dxy, dxs, OutlinedText); + PaintAssistant::drawVisibleText(this, paint, dxx, dxy, dxs, PaintAssistant::OutlinedText); dxy += fontHeight; } if (dys != "") { - drawVisibleText(paint, dxx, dxy, dys, OutlinedText); + PaintAssistant::drawVisibleText(this, paint, dxx, dxy, dys, PaintAssistant::OutlinedText); dxy += fontHeight; }
--- a/view/View.h Mon Jun 13 11:44:08 2016 +0100 +++ b/view/View.h Fri Aug 05 15:05:02 2016 +0100 @@ -19,7 +19,7 @@ #include <QFrame> #include <QProgressBar> -#include "LayerGeometryProvider.h" +#include "layer/LayerGeometryProvider.h" #include "base/ZoomConstraint.h" #include "base/PropertyContainer.h" @@ -144,8 +144,8 @@ * * Not thread-safe in logarithmic mode. Call only from GUI thread. */ - double getFrequencyForY(int y, double minFreq, double maxFreq, - bool logarithmic) const; + double getFrequencyForY(double y, double minFreq, double maxFreq, + bool logarithmic) const; /** * Return the zoom level, i.e. the number of frames per pixel @@ -266,9 +266,6 @@ virtual QColor getForeground() const; virtual QColor getBackground() const; - virtual void drawVisibleText(QPainter &p, int x, int y, - QString text, TextStyle style) const; - virtual void drawMeasurementRect(QPainter &p, const Layer *, QRect rect, bool focus) const;
--- a/view/ViewProxy.h Mon Jun 13 11:44:08 2016 +0100 +++ b/view/ViewProxy.h Fri Aug 05 15:05:02 2016 +0100 @@ -15,7 +15,7 @@ #ifndef VIEW_PROXY_H #define VIEW_PROXY_H -#include "LayerGeometryProvider.h" +#include "layer/LayerGeometryProvider.h" class ViewProxy : public LayerGeometryProvider { @@ -63,14 +63,10 @@ return m_scaleFactor * m_view->getYForFrequency(frequency, minFreq, maxFreq, logarithmic); } - virtual double getFrequencyForY(int y, double minFreq, double maxFreq, + virtual double getFrequencyForY(double y, double minFreq, double maxFreq, bool logarithmic) const { - double f0 = m_view->getFrequencyForY + return m_view->getFrequencyForY (y / m_scaleFactor, minFreq, maxFreq, logarithmic); - if (m_scaleFactor == 1) return f0; - double f1 = m_view->getFrequencyForY - ((y / m_scaleFactor) + 1, minFreq, maxFreq, logarithmic); - return f0 + ((f1 - f0) * (y % m_scaleFactor)) / m_scaleFactor; } virtual int getTextLabelHeight(const Layer *layer, QPainter &paint) const { return m_scaleFactor * m_view->getTextLabelHeight(layer, paint); @@ -128,11 +124,6 @@ return m_view->shouldShowFeatureLabels(); } - virtual void drawVisibleText(QPainter &p, int x, int y, - QString text, TextStyle style) const { - m_view->drawVisibleText(p, x, y, text, style); - } - virtual void drawMeasurementRect(QPainter &p, const Layer *layer, QRect rect, bool focus) const { m_view->drawMeasurementRect(p, layer, rect, focus);
--- a/widgets/PropertyBox.cpp Mon Jun 13 11:44:08 2016 +0100 +++ b/widgets/PropertyBox.cpp Fri Aug 05 15:05:02 2016 +0100 @@ -212,10 +212,10 @@ gainDial->setFixedWidth(24); gainDial->setFixedHeight(24); gainDial->setNotchesVisible(false); - gainDial->setDefaultValue(0); gainDial->setObjectName(tr("Playback Gain")); gainDial->setRangeMapper(new LinearRangeMapper (-50, 50, -25, 25, tr("dB"))); + gainDial->setDefaultValue(0); gainDial->setShowToolTip(true); connect(gainDial, SIGNAL(valueChanged(int)), this, SLOT(playGainDialChanged(int))); @@ -384,8 +384,10 @@ dial->setMaximum(max); dial->setPageStep(1); dial->setNotchesVisible((max - min) <= 12); + // important to set the range mapper before the default, + // because the range mapper is used to map the default + dial->setRangeMapper(m_container->getNewPropertyRangeMapper(name)); dial->setDefaultValue(deflt); - dial->setRangeMapper(m_container->getNewPropertyRangeMapper(name)); dial->setShowToolTip(true); connect(dial, SIGNAL(valueChanged(int)), this, SLOT(propertyControllerChanged(int)));