# HG changeset patch # User Chris Cannam # Date 1539157467 -3600 # Node ID 86429ff00f0574bce40cf3a3d9d26e8b0fd516ee # Parent 9fb7133dd818aadb973f103dd515b6fc60751879# Parent 4949061fcb8cfc937791f88d743c2a51c65061cd Merge from branch zoom diff -r 9fb7133dd818 -r 86429ff00f05 files.pri --- a/files.pri Wed Oct 03 12:59:55 2018 +0100 +++ b/files.pri Wed Oct 10 08:44:27 2018 +0100 @@ -1,9 +1,9 @@ SVGUI_HEADERS += \ layer/Colour3DPlotLayer.h \ - layer/Colour3DPlotRenderer.h \ - layer/ColourDatabase.h \ - layer/ColourMapper.h \ + layer/Colour3DPlotRenderer.h \ + layer/ColourDatabase.h \ + layer/ColourMapper.h \ layer/ColourScale.h \ layer/ColourScaleLayer.h \ layer/FlexiNoteLayer.h \ @@ -36,14 +36,14 @@ layer/TimeValueLayer.h \ layer/VerticalScaleLayer.h \ layer/WaveformLayer.h \ - view/AlignmentView.h \ + view/AlignmentView.h \ view/Overview.h \ view/Pane.h \ view/PaneStack.h \ view/View.h \ view/ViewManager.h \ view/ViewProxy.h \ - widgets/ActivityLog.h \ + widgets/ActivityLog.h \ widgets/AudioDial.h \ widgets/ClickableLabel.h \ widgets/ColourComboBox.h \ @@ -95,10 +95,10 @@ SVGUI_SOURCES += \ layer/Colour3DPlotLayer.cpp \ - layer/Colour3DPlotRenderer.cpp \ - layer/ColourDatabase.cpp \ - layer/ColourMapper.cpp \ - layer/ColourScale.cpp \ + layer/Colour3DPlotRenderer.cpp \ + layer/ColourDatabase.cpp \ + layer/ColourMapper.cpp \ + layer/ColourScale.cpp \ layer/FlexiNoteLayer.cpp \ layer/HorizontalFrequencyScale.cpp \ layer/ImageLayer.cpp \ @@ -124,13 +124,13 @@ layer/TimeRulerLayer.cpp \ layer/TimeValueLayer.cpp \ layer/WaveformLayer.cpp \ - view/AlignmentView.cpp \ + view/AlignmentView.cpp \ view/Overview.cpp \ view/Pane.cpp \ view/PaneStack.cpp \ view/View.cpp \ view/ViewManager.cpp \ - widgets/ActivityLog.cpp \ + widgets/ActivityLog.cpp \ widgets/AudioDial.cpp \ widgets/ColourComboBox.cpp \ widgets/ColourMapComboBox.cpp \ diff -r 9fb7133dd818 -r 86429ff00f05 layer/Colour3DPlotRenderer.cpp --- a/layer/Colour3DPlotRenderer.cpp Wed Oct 03 12:59:55 2018 +0100 +++ b/layer/Colour3DPlotRenderer.cpp Wed Oct 10 08:44:27 2018 +0100 @@ -32,7 +32,10 @@ #include -//#define DEBUG_COLOUR_PLOT_REPAINT 1 +#include +using namespace std::rel_ops; + +#define DEBUG_COLOUR_PLOT_REPAINT 1 using namespace std; @@ -315,7 +318,7 @@ } int binResolution = model->getResolution(); - int zoomLevel = v->getZoomLevel(); + ZoomLevel zoomLevel = v->getZoomLevel(); sv_samplerate_t modelRate = model->getSampleRate(); double rateRatio = v->getViewManager()->getMainModelSampleRate() / modelRate; @@ -332,12 +335,14 @@ // explicitly requested opaque & sufficiently zoomed-in if (model->getHeight() * 3 < v->getPaintHeight() && - relativeBinResolution >= 3 * zoomLevel) { + zoomLevel < ZoomLevel(ZoomLevel::FramesPerPixel, + int(round(relativeBinResolution / 3)))) { return DirectTranslucent; } } - if (relativeBinResolution > zoomLevel) { + if (ZoomLevel(ZoomLevel::FramesPerPixel, + int(round(relativeBinResolution))) > zoomLevel) { return DrawBufferBinResolution; } else { return DrawBufferPixelResolution; @@ -555,12 +560,12 @@ if (m_params.binDisplay == BinDisplay::PeakFrequencies) return; if (m_params.colourScale.getScale() == ColourScaleType::Phase) return; - int zoomLevel = v->getZoomLevel(); + ZoomLevel zoomLevel = v->getZoomLevel(); int binResolution = model->getResolution(); for (int ix = 0; in_range_for(m_sources.peakCaches, ix); ++ix) { int bpp = m_sources.peakCaches[ix]->getColumnsPerPeak(); - int equivZoom = binResolution * bpp; + ZoomLevel equivZoom(ZoomLevel::FramesPerPixel, binResolution * bpp); if (zoomLevel >= equivZoom) { // this peak cache would work, though it might not be best if (bpp > binsPerPeak) { @@ -759,6 +764,11 @@ int drawBufferWidth; int binResolution = model->getResolution(); + // These loops should eventually terminate provided that + // getFrameForX always returns a multiple of the zoom level, + // i.e. there is some x for which getFrameForX(x) == 0 and + // subsequent return values are equally spaced + for (int x = x0; ; --x) { sv_frame_t f = v->getFrameForX(x); if ((f / binResolution) * binResolution == f) { @@ -769,6 +779,7 @@ } } } + for (int x = x0 + repaintWidth; ; ++x) { sv_frame_t f = v->getFrameForX(x); if ((f / binResolution) * binResolution == f) { @@ -779,6 +790,7 @@ } } } + drawBufferWidth = int ((rightBoundaryFrame - leftBoundaryFrame) / binResolution); diff -r 9fb7133dd818 -r 86429ff00f05 layer/LayerGeometryProvider.h --- a/layer/LayerGeometryProvider.h Wed Oct 03 12:59:55 2018 +0100 +++ b/layer/LayerGeometryProvider.h Wed Oct 10 08:44:27 2018 +0100 @@ -12,10 +12,11 @@ COPYING included with this distribution for more information. */ -#ifndef LAYER_GEOMETRY_PROVIDER_H -#define LAYER_GEOMETRY_PROVIDER_H +#ifndef SV_LAYER_GEOMETRY_PROVIDER_H +#define SV_LAYER_GEOMETRY_PROVIDER_H #include "base/BaseTypes.h" +#include "base/ZoomLevel.h" #include #include @@ -147,9 +148,10 @@ bool &log) const = 0; /** - * Return the zoom level, i.e. the number of frames per pixel + * Return the zoom level, i.e. the number of frames per pixel or + * pixels per frame */ - virtual int getZoomLevel() const = 0; + virtual ZoomLevel getZoomLevel() const = 0; /** * To be called from a layer, to obtain the extent of the surface diff -r 9fb7133dd818 -r 86429ff00f05 layer/ScrollableImageCache.h --- a/layer/ScrollableImageCache.h Wed Oct 03 12:59:55 2018 +0100 +++ b/layer/ScrollableImageCache.h Wed Oct 10 08:44:27 2018 +0100 @@ -40,8 +40,7 @@ ScrollableImageCache() : m_validLeft(0), m_validWidth(0), - m_startFrame(0), - m_zoomLevel(0) + m_startFrame(0) {} void invalidate() { @@ -83,7 +82,7 @@ return QRect(m_validLeft, 0, m_validWidth, m_image.height()); } - int getZoomLevel() const { + ZoomLevel getZoomLevel() const { return m_zoomLevel; } @@ -93,7 +92,8 @@ * invalidate the cache here is the only thing the zoom level is * used for.) */ - void setZoomLevel(int zoom) { + void setZoomLevel(ZoomLevel zoom) { + using namespace std::rel_ops; if (m_zoomLevel != zoom) { m_zoomLevel = zoom; invalidate(); @@ -157,7 +157,7 @@ int m_validLeft; int m_validWidth; sv_frame_t m_startFrame; - int m_zoomLevel; + ZoomLevel m_zoomLevel; }; #endif diff -r 9fb7133dd818 -r 86429ff00f05 layer/ScrollableMagRangeCache.h --- a/layer/ScrollableMagRangeCache.h Wed Oct 03 12:59:55 2018 +0100 +++ b/layer/ScrollableMagRangeCache.h Wed Oct 10 08:44:27 2018 +0100 @@ -36,8 +36,7 @@ { public: ScrollableMagRangeCache() : - m_startFrame(0), - m_zoomLevel(0) + m_startFrame(0) {} void invalidate() { @@ -58,7 +57,7 @@ } } - int getZoomLevel() const { + ZoomLevel getZoomLevel() const { return m_zoomLevel; } @@ -68,7 +67,8 @@ * invalidate the cache here is the only thing the zoom level is * used for.) */ - void setZoomLevel(int zoom) { + void setZoomLevel(ZoomLevel zoom) { + using namespace std::rel_ops; if (m_zoomLevel != zoom) { m_zoomLevel = zoom; invalidate(); @@ -133,7 +133,7 @@ private: std::vector m_ranges; sv_frame_t m_startFrame; - int m_zoomLevel; + ZoomLevel m_zoomLevel; }; #endif diff -r 9fb7133dd818 -r 86429ff00f05 layer/TimeRulerLayer.cpp --- a/layer/TimeRulerLayer.cpp Wed Oct 03 12:59:55 2018 +0100 +++ b/layer/TimeRulerLayer.cpp Wed Oct 10 08:44:27 2018 +0100 @@ -19,6 +19,7 @@ #include "data/model/Model.h" #include "base/RealTime.h" +#include "base/Preferences.h" #include "view/View.h" #include "ColourDatabase.h" @@ -59,8 +60,8 @@ } bool q; - int tick = getMajorTickSpacing(v, q); - RealTime rtick = RealTime::fromMilliseconds(tick); + int64_t tickUSec = getMajorTickUSec(v, q); + RealTime rtick = RealTime::fromMicroseconds(tickUSec); sv_samplerate_t rate = m_model->getSampleRate(); RealTime rt = RealTime::frame2RealTime(frame, rate); @@ -141,20 +142,20 @@ return true; } -int -TimeRulerLayer::getMajorTickSpacing(LayerGeometryProvider *v, bool &quarterTicks) const +int64_t +TimeRulerLayer::getMajorTickUSec(LayerGeometryProvider *v, + bool &quarterTicks) const { - // return value is in milliseconds - - if (!m_model || !v) return 1000; + // return value is in microseconds + if (!m_model || !v) return 1000 * 1000; sv_samplerate_t sampleRate = m_model->getSampleRate(); - if (!sampleRate) return 1000; + if (!sampleRate) return 1000 * 1000; sv_frame_t startFrame = v->getStartFrame(); sv_frame_t endFrame = v->getEndFrame(); - int minPixelSpacing = 50; + int minPixelSpacing = ViewManager::scalePixelSize(50); RealTime rtStart = RealTime::frame2RealTime(startFrame, sampleRate); RealTime rtEnd = RealTime::frame2RealTime(endFrame, sampleRate); @@ -163,43 +164,82 @@ if (count < 1) count = 1; RealTime rtGap = (rtEnd - rtStart) / count; - int incms; + int64_t incus; quarterTicks = false; if (rtGap.sec > 0) { - incms = 1000; + incus = 1000 * 1000; int s = rtGap.sec; - if (s > 0) { incms *= 5; s /= 5; } - if (s > 0) { incms *= 2; s /= 2; } - if (s > 0) { incms *= 6; s /= 6; quarterTicks = true; } - if (s > 0) { incms *= 5; s /= 5; quarterTicks = false; } - if (s > 0) { incms *= 2; s /= 2; } - if (s > 0) { incms *= 6; s /= 6; quarterTicks = true; } + if (s > 0) { incus *= 5; s /= 5; } + if (s > 0) { incus *= 2; s /= 2; } + if (s > 0) { incus *= 6; s /= 6; quarterTicks = true; } + if (s > 0) { incus *= 5; s /= 5; quarterTicks = false; } + if (s > 0) { incus *= 2; s /= 2; } + if (s > 0) { incus *= 6; s /= 6; quarterTicks = true; } while (s > 0) { - incms *= 10; + incus *= 10; s /= 10; quarterTicks = false; } + } else if (rtGap.msec() > 0) { + incus = 1000; + int ms = rtGap.msec(); + if (ms > 0) { incus *= 10; ms /= 10; } + if (ms > 0) { incus *= 10; ms /= 10; } + if (ms > 0) { incus *= 5; ms /= 5; } + if (ms > 0) { incus *= 2; ms /= 2; } } else { - incms = 1; - int ms = rtGap.msec(); -// cerr << "rtGap.msec = " << ms << ", rtGap = " << rtGap << ", count = " << count << endl; -// cerr << "startFrame = " << startFrame << ", endFrame = " << endFrame << " rtStart = " << rtStart << ", rtEnd = " << rtEnd << endl; - if (ms > 0) { incms *= 10; ms /= 10; } - if (ms > 0) { incms *= 10; ms /= 10; } - if (ms > 0) { incms *= 5; ms /= 5; } - if (ms > 0) { incms *= 2; ms /= 2; } + incus = 1; + int us = rtGap.usec(); + if (us > 0) { incus *= 10; us /= 10; } + if (us > 0) { incus *= 10; us /= 10; } + if (us > 0) { incus *= 5; us /= 5; } + if (us > 0) { incus *= 2; us /= 2; } } - return incms; + return incus; +} + +int +TimeRulerLayer::getXForUSec(LayerGeometryProvider *v, double us) const +{ + sv_samplerate_t sampleRate = m_model->getSampleRate(); + double dframe = (us * sampleRate) / 1000000.0; + double eps = 1e-7; + sv_frame_t frame = sv_frame_t(floor(dframe + eps)); + int x; + + ZoomLevel zoom = v->getZoomLevel(); + + if (zoom.zone == ZoomLevel::FramesPerPixel) { + + frame /= zoom.level; + frame *= zoom.level; // so frame corresponds to an exact pixel + + x = v->getXForFrame(frame); + + } else { + + double off = dframe - double(frame); + int x0 = v->getXForFrame(frame); + int x1 = v->getXForFrame(frame + 1); + + x = int(x0 + off * (x1 - x0)); + } + +#ifdef DEBUG_TIME_RULER_LAYER + cerr << "Considering frame = " << frame << ", x = " << x << endl; +#endif + + return x; } void TimeRulerLayer::paint(LayerGeometryProvider *v, QPainter &paint, QRect rect) const { #ifdef DEBUG_TIME_RULER_LAYER - SVDEBUG << "TimeRulerLayer::paint (" << rect.x() << "," << rect.y() - << ") [" << rect.width() << "x" << rect.height() << "]" << endl; + SVCERR << "TimeRulerLayer::paint (" << rect.x() << "," << rect.y() + << ") [" << rect.width() << "x" << rect.height() << "]" << endl; #endif if (!m_model || !m_model->isOK()) return; @@ -210,27 +250,36 @@ sv_frame_t startFrame = v->getFrameForX(rect.x() - 50); #ifdef DEBUG_TIME_RULER_LAYER - cerr << "start frame = " << startFrame << endl; + SVCERR << "start frame = " << startFrame << endl; #endif bool quarter = false; - int incms = getMajorTickSpacing(v, quarter); - - int ms = int(lrint(1000.0 * (double(startFrame) / double(sampleRate)))); - ms = (ms / incms) * incms - incms; + int64_t incus = getMajorTickUSec(v, quarter); + int64_t us = int64_t(floor(1000.0 * 1000.0 * (double(startFrame) / + double(sampleRate)))); + us = (us / incus) * incus - incus; #ifdef DEBUG_TIME_RULER_LAYER - cerr << "start ms = " << ms << " at step " << incms << endl; + SVCERR << "start us = " << us << " at step " << incus << endl; #endif + Preferences *prefs = Preferences::getInstance(); + auto origTimeTextMode = prefs->getTimeToTextMode(); + if (incus < 1000) { + // Temporarily switch to usec display mode (if we aren't using + // it already) + prefs->blockSignals(true); + prefs->setTimeToTextMode(Preferences::TimeToTextUs); + } + // Calculate the number of ticks per increment -- approximate // values for x and frame counts here will do, no rounding issue. - // We always use the exact incms in our calculations for where to + // We always use the exact incus in our calculations for where to // draw the actual ticks or lines. int minPixelSpacing = 50; - sv_frame_t incFrame = lrint((incms * sampleRate) / 1000); - int incX = int(incFrame / v->getZoomLevel()); + sv_frame_t incFrame = lrint((double(incus) * sampleRate) / 1000000); + int incX = int(round(v->getZoomLevel().framesToPixels(double(incFrame)))); int ticks = 10; if (incX < minPixelSpacing * 2) { ticks = quarter ? 4 : 5; @@ -242,10 +291,7 @@ // Do not label time zero - we now overlay an opaque area over // time < 0 which would cut it in half - int minlabel = 1; // ms - - // used for a sanity check - sv_frame_t prevframe = 0; + int minlabel = 1; // us while (1) { @@ -254,40 +300,27 @@ // a different pixel when scrolling a small amount and // re-drawing with a different start frame). - double dms = ms; - sv_frame_t frame = lrint((dms * sampleRate) / 1000.0); - frame /= v->getZoomLevel(); - frame *= v->getZoomLevel(); // so frame corresponds to an exact pixel + double dus = double(us); - if (frame == prevframe && prevframe != 0) { - cerr << "ERROR: frame == prevframe (== " << frame - << ") in TimeRulerLayer::paint" << endl; - throw std::logic_error("frame == prevframe in TimeRulerLayer::paint"); - } - prevframe = frame; - - int x = v->getXForFrame(frame); - -#ifdef DEBUG_TIME_RULER_LAYER - cerr << "Considering frame = " << frame << ", x = " << x << endl; -#endif + int x = getXForUSec(v, dus); if (x >= rect.x() + rect.width() + 50) { #ifdef DEBUG_TIME_RULER_LAYER - cerr << "X well out of range, ending here" << endl; + SVCERR << "X well out of range, ending here" << endl; #endif break; } - if (x >= rect.x() - 50 && ms >= minlabel) { + if (x >= rect.x() - 50 && us >= minlabel) { - RealTime rt = RealTime::fromMilliseconds(ms); + RealTime rt = RealTime::fromMicroseconds(us); #ifdef DEBUG_TIME_RULER_LAYER - cerr << "X in range, drawing line here for time " << rt.toText() << endl; + SVCERR << "X in range, drawing line here for time " << rt.toText() << " (usec = " << us << ")" << endl; #endif QString text(QString::fromStdString(rt.toText())); + QFontMetrics metrics = paint.fontMetrics(); int tw = metrics.width(text); @@ -295,7 +328,7 @@ (x < rect.x() - tw/2 || x >= rect.x() + rect.width() + tw/2)) { #ifdef DEBUG_TIME_RULER_LAYER - cerr << "hm, maybe X isn't in range after all (x = " << x << ", tw = " << tw << ", rect.x() = " << rect.x() << ", rect.width() = " << rect.width() << ")" << endl; + SVCERR << "hm, maybe X isn't in range after all (x = " << x << ", tw = " << tw << ", rect.x() = " << rect.x() << ", rect.width() = " << rect.width() << ")" << endl; #endif } @@ -335,22 +368,19 @@ for (int i = 1; i < ticks; ++i) { - dms = ms + (i * double(incms)) / ticks; - frame = lrint((dms * sampleRate) / 1000.0); - frame /= v->getZoomLevel(); - frame *= v->getZoomLevel(); // exact pixel as above + dus = double(us) + (i * double(incus)) / ticks; - x = v->getXForFrame(frame); + x = getXForUSec(v, dus); if (x < rect.x() || x >= rect.x() + rect.width()) { #ifdef DEBUG_TIME_RULER_LAYER -// cerr << "tick " << i << ": X out of range, going on to next tick" << endl; +// SVCERR << "tick " << i << ": X out of range, going on to next tick" << endl; #endif continue; } #ifdef DEBUG_TIME_RULER_LAYER - cerr << "tick " << i << " in range, drawing at " << x << endl; + SVCERR << "tick " << i << " in range, drawing at " << x << endl; #endif int sz = 5; @@ -367,8 +397,11 @@ paint.drawLine(x, v->getPaintHeight() - sz - 1, x, v->getPaintHeight() - 1); } - ms += incms; + us += incus; } + + prefs->setTimeToTextMode(origTimeTextMode); + prefs->blockSignals(false); paint.restore(); } diff -r 9fb7133dd818 -r 86429ff00f05 layer/TimeRulerLayer.h --- a/layer/TimeRulerLayer.h Wed Oct 03 12:59:55 2018 +0100 +++ b/layer/TimeRulerLayer.h Wed Oct 10 08:44:27 2018 +0100 @@ -68,7 +68,8 @@ virtual int getDefaultColourHint(bool dark, bool &impose); - int getMajorTickSpacing(LayerGeometryProvider *, bool &quarterTicks) const; + int64_t getMajorTickUSec(LayerGeometryProvider *, bool &quarterTicks) const; + int getXForUSec(LayerGeometryProvider *, double usec) const; }; #endif diff -r 9fb7133dd818 -r 86429ff00f05 layer/WaveformLayer.cpp --- a/layer/WaveformLayer.cpp Wed Oct 03 12:59:55 2018 +0100 +++ b/layer/WaveformLayer.cpp Wed Oct 10 08:44:27 2018 +0100 @@ -24,6 +24,8 @@ #include "ColourDatabase.h" #include "PaintAssistant.h" +#include "data/model/WaveformOversampler.h" + #include #include #include @@ -32,8 +34,9 @@ #include //#define DEBUG_WAVEFORM_PAINT 1 +//#define DEBUG_WAVEFORM_PAINT_BY_PIXEL 1 - +using std::vector; WaveformLayer::WaveformLayer() : @@ -49,10 +52,8 @@ m_middleLineHeight(0.5), m_aggressive(false), m_cache(0), - m_cacheValid(false), - m_cacheZoomLevel(0) + m_cacheValid(false) { - } WaveformLayer::~WaveformLayer() @@ -411,7 +412,8 @@ -5, -3, -2, -1, -0.5, 0 }; bool -WaveformLayer::getSourceFramesForX(LayerGeometryProvider *v, int x, int modelZoomLevel, +WaveformLayer::getSourceFramesForX(LayerGeometryProvider *v, + int x, int modelZoomLevel, sv_frame_t &f0, sv_frame_t &f1) const { sv_frame_t viewFrame = v->getFrameForX(x); @@ -422,15 +424,17 @@ } f0 = viewFrame; - f0 = f0 / modelZoomLevel; f0 = f0 * modelZoomLevel; - viewFrame = v->getFrameForX(x + 1); - - f1 = viewFrame; - f1 = f1 / modelZoomLevel; - f1 = f1 * modelZoomLevel; + if (v->getZoomLevel().zone == ZoomLevel::PixelsPerFrame) { + f1 = f0 + 1; + } else { + viewFrame = v->getFrameForX(x + 1); + f1 = viewFrame; + f1 = f1 / modelZoomLevel; + f1 = f1 * modelZoomLevel; + } return (f0 < m_model->getEndFrame()); } @@ -482,11 +486,11 @@ return; } - int zoomLevel = v->getZoomLevel(); + ZoomLevel zoomLevel = v->getZoomLevel(); #ifdef DEBUG_WAVEFORM_PAINT Profiler profiler("WaveformLayer::paint", true); - cerr << "WaveformLayer::paint (" << rect.x() << "," << rect.y() + SVCERR << "WaveformLayer::paint (" << rect.x() << "," << rect.y() << ") [" << rect.width() << "x" << rect.height() << "]: zoom " << zoomLevel << endl; #endif @@ -500,15 +504,16 @@ int w = v->getPaintWidth(); int h = v->getPaintHeight(); - bool ready = m_model->isReady(); QPainter *paint; if (m_aggressive) { #ifdef DEBUG_WAVEFORM_PAINT - cerr << "WaveformLayer::paint: aggressive is true" << endl; + SVCERR << "WaveformLayer::paint: aggressive is true" << endl; #endif + using namespace std::rel_ops; + if (m_cacheValid && (zoomLevel != m_cacheZoomLevel)) { m_cacheValid = false; } @@ -516,7 +521,7 @@ if (!m_cache || m_cache->width() != w || m_cache->height() != h) { #ifdef DEBUG_WAVEFORM_PAINT if (m_cache) { - cerr << "WaveformLayer::paint: cache size " << m_cache->width() << "x" << m_cache->height() << " differs from view size " << w << "x" << h << ": regenerating aggressive cache" << endl; + SVCERR << "WaveformLayer::paint: cache size " << m_cache->width() << "x" << m_cache->height() << " differs from view size " << w << "x" << h << ": regenerating aggressive cache" << endl; } #endif delete m_cache; @@ -554,15 +559,19 @@ } int x0 = 0, x1 = w - 1; - int y0 = 0, y1 = h - 1; x0 = rect.left(); x1 = rect.right(); - y0 = rect.top(); - y1 = rect.bottom(); - if (x0 > 0) --x0; - if (x1 < w) ++x1; + if (x0 > 0) { + rect.adjust(-1, 0, 0, 0); + x0 = rect.left(); + } + + if (x1 < w) { + rect.adjust(0, 0, 1, 0); + x1 = rect.right(); + } // Our zoom level may differ from that at which the underlying // model has its blocks. @@ -572,28 +581,179 @@ // the range being drawn is. And that set of underlying frames // must remain the same when we scroll one or more pixels left or // right. - - int modelZoomLevel = m_model->getSummaryBlockSize(zoomLevel); + + int desiredBlockSize = 1; + if (zoomLevel.zone == ZoomLevel::FramesPerPixel) { + desiredBlockSize = zoomLevel.level; + } + int blockSize = m_model->getSummaryBlockSize(desiredBlockSize); sv_frame_t frame0; sv_frame_t frame1; sv_frame_t spare; - getSourceFramesForX(v, x0, modelZoomLevel, frame0, spare); - getSourceFramesForX(v, x1, modelZoomLevel, spare, frame1); + getSourceFramesForX(v, x0, blockSize, frame0, spare); + getSourceFramesForX(v, x1, blockSize, spare, frame1); #ifdef DEBUG_WAVEFORM_PAINT - cerr << "Painting waveform from " << frame0 << " to " << frame1 << " (" << (x1-x0+1) << " pixels at zoom " << zoomLevel << " and model zoom " << modelZoomLevel << ")" << endl; + SVCERR << "Painting waveform from " << frame0 << " to " << frame1 << " (" << (x1-x0+1) << " pixels at zoom " << zoomLevel << " and model zoom " << blockSize << ")" << endl; #endif - RangeSummarisableTimeValueModel::RangeBlock *ranges = - new RangeSummarisableTimeValueModel::RangeBlock; + m_effectiveGains.clear(); + while ((int)m_effectiveGains.size() <= maxChannel) { + m_effectiveGains.push_back(m_gain); + } + if (m_autoNormalize) { + for (int ch = minChannel; ch <= maxChannel; ++ch) { + m_effectiveGains[ch] = getNormalizeGain(v, ch); + } + } - RangeSummarisableTimeValueModel::RangeBlock *otherChannelRanges = 0; - RangeSummarisableTimeValueModel::Range range; + RangeVec ranges; + + if (v->getZoomLevel().zone == ZoomLevel::FramesPerPixel) { + getSummaryRanges(minChannel, maxChannel, + mixingChannels || mergingChannels, + frame0, frame1, + blockSize, ranges); + } else { + getOversampledRanges(minChannel, maxChannel, + mixingChannels || mergingChannels, + frame0, frame1, + v->getZoomLevel().level, ranges); + } + + if (!ranges.empty()) { + for (int ch = minChannel; ch <= maxChannel; ++ch) { + paintChannel(v, paint, rect, ch, ranges, blockSize, + frame0, frame1); + } + } + + if (m_middleLineHeight != 0.5) { + paint->restore(); + } + + if (m_aggressive) { + if (m_model->isReady() && rect == v->getPaintRect()) { + m_cacheValid = true; + m_cacheZoomLevel = zoomLevel; + } + paint->end(); + delete paint; + viewPainter.drawPixmap(rect, *m_cache, rect); + } +} + +void +WaveformLayer::getSummaryRanges(int minChannel, int maxChannel, + bool mixingOrMerging, + sv_frame_t frame0, sv_frame_t frame1, + int blockSize, RangeVec &ranges) + const +{ + for (int ch = minChannel; ch <= maxChannel; ++ch) { + ranges.push_back({}); + m_model->getSummaries(ch, frame0, frame1 - frame0, + ranges[ch - minChannel], blockSize); +#ifdef DEBUG_WAVEFORM_PAINT + SVCERR << "channel " << ch << ": " << ranges[ch - minChannel].size() << " ranges from " << frame0 << " to " << frame1 << " at zoom level " << blockSize << endl; +#endif + } + + if (mixingOrMerging) { + if (minChannel != 0 || maxChannel != 0) { + SVCERR << "Internal error: min & max channels should be 0 when merging or mixing all channels" << endl; + } else if (m_model->getChannelCount() > 1) { + ranges.push_back({}); + m_model->getSummaries + (1, frame0, frame1 - frame0, ranges[1], blockSize); + } + } +} + +void +WaveformLayer::getOversampledRanges(int minChannel, int maxChannel, + bool /* mixingOrMerging */, + sv_frame_t frame0, sv_frame_t frame1, + int oversampleBy, RangeVec &ranges) + const +{ + // These frame values, tail length, etc variables are at the model + // sample rate, not the oversampled rate + + sv_frame_t tail = 16; + sv_frame_t startFrame = m_model->getStartFrame(); + sv_frame_t endFrame = m_model->getEndFrame(); + + sv_frame_t rf0 = frame0 - tail; + if (rf0 < startFrame) { + rf0 = 0; + } + + sv_frame_t rf1 = frame1 + tail; + if (rf1 >= endFrame) { + rf1 = endFrame - 1; + } + if (rf1 <= rf0) { + SVCERR << "WARNING: getOversampledRanges: rf1 (" << rf1 << ") <= rf0 (" + << rf0 << ")" << endl; + return; + } + + for (int ch = minChannel; ch <= maxChannel; ++ch) { + floatvec_t oversampled = WaveformOversampler::getOversampledData + (m_model, ch, frame0, frame1 - frame0, oversampleBy); + RangeSummarisableTimeValueModel::RangeBlock rr; + for (float v: oversampled) { + RangeSummarisableTimeValueModel::Range r; + r.sample(v); + rr.push_back(r); + } + ranges.push_back(rr); + +#ifdef DEBUG_WAVEFORM_PAINT + SVCERR << "getOversampledRanges: " << frame0 << " -> " << frame1 + << " (" << frame1 - frame0 << "-frame range) at ratio " + << oversampleBy << " with tail " << tail + << " -> got " << oversampled.size() + << " oversampled values for channel " << ch + << ", from which returning " << rr.size() << " ranges" << endl; +#endif + } + + //!!! + channel modes + + return; +} + +void +WaveformLayer::paintChannel(LayerGeometryProvider *v, + QPainter *paint, + QRect rect, int ch, + const RangeVec &ranges, + int blockSize, + sv_frame_t frame0, + sv_frame_t frame1) + const +{ + int x0 = rect.left(); + int y0 = rect.top(); + + int x1 = rect.right(); + int y1 = rect.bottom(); + + int h = v->getPaintHeight(); + + int channels = 0, minChannel = 0, maxChannel = 0; + bool mergingChannels = false, mixingChannels = false; + + channels = getChannelArrangement(minChannel, maxChannel, + mergingChannels, mixingChannels); + if (channels == 0) return; QColor baseColour = getBaseQColor(); - std::vector greys = getPartialShades(v); + vector greys = getPartialShades(v); QColor midColour = baseColour; if (midColour == Qt::black) { @@ -604,361 +764,344 @@ midColour = midColour.light(50); } - while ((int)m_effectiveGains.size() <= maxChannel) { - m_effectiveGains.push_back(m_gain); + int prevRangeBottom = -1, prevRangeTop = -1; + QColor prevRangeBottomColour = baseColour, prevRangeTopColour = baseColour; + + double gain = m_effectiveGains[ch]; + + int m = (h / channels) / 2; + int my = m + (((ch - minChannel) * h) / channels); + +#ifdef DEBUG_WAVEFORM_PAINT + SVCERR << "ch = " << ch << ", channels = " << channels << ", m = " << m << ", my = " << my << ", h = " << h << endl; +#endif + + if (my - m > y1 || my + m < y0) return; + + if ((m_scale == dBScale || m_scale == MeterScale) && + m_channelMode != MergeChannels) { + m = (h / channels); + my = m + (((ch - minChannel) * h) / channels); } - for (int ch = minChannel; ch <= maxChannel; ++ch) { + paint->setPen(greys[1]); + paint->drawLine(x0, my, x1, my); - int prevRangeBottom = -1, prevRangeTop = -1; - QColor prevRangeBottomColour = baseColour, prevRangeTopColour = baseColour; + paintChannelScaleGuides(v, paint, rect, ch); + + int rangeix = ch - minChannel; - m_effectiveGains[ch] = m_gain; +#ifdef DEBUG_WAVEFORM_PAINT + SVCERR << "paint channel " << ch << ": frame0 = " << frame0 << ", frame1 = " << frame1 << ", blockSize = " << blockSize << ", have " << ranges.size() << " range blocks of which ours is index " << rangeix << " with " << ranges[rangeix].size() << " ranges in it" << endl; +#else + (void)frame1; // not actually used +#endif - if (m_autoNormalize) { - m_effectiveGains[ch] = getNormalizeGain(v, ch); + for (int x = x0; x <= x1; ++x) { + + sv_frame_t f0, f1; + sv_frame_t i0, i1; + + bool showIndividualSample = false; + + if (v->getZoomLevel().zone == ZoomLevel::FramesPerPixel) { + if (!getSourceFramesForX(v, x, blockSize, f0, f1)) { + continue; + } + f1 = f1 - 1; + i0 = (f0 - frame0) / blockSize; + i1 = (f1 - frame0) / blockSize; + } else { + int oversampleBy = v->getZoomLevel().level; + f0 = f1 = v->getFrameForX(x); + int xf0 = v->getXForFrame(f0); + showIndividualSample = (x == xf0); + i0 = i1 = (f0 - frame0) * oversampleBy + (x - xf0); } - double gain = m_effectiveGains[ch]; + if (f0 < frame0) { + SVCERR << "ERROR: WaveformLayer::paint: pixel " << x << " has f0 = " << f0 << " which is less than range frame0 " << frame0 << " for x0 = " << x0 << endl; + continue; + } - int m = (h / channels) / 2; - int my = m + (((ch - minChannel) * h) / channels); - -#ifdef DEBUG_WAVEFORM_PAINT - cerr << "ch = " << ch << ", channels = " << channels << ", m = " << m << ", my = " << my << ", h = " << h << endl; +#ifdef DEBUG_WAVEFORM_PAINT_BY_PIXEL + SVCERR << "WaveformLayer::paint: pixel " << x << ": i0 " << i0 << " (f " << f0 << "), i1 " << i1 << " (f " << f1 << ")" << endl; #endif - if (my - m > y1 || my + m < y0) continue; - - if ((m_scale == dBScale || m_scale == MeterScale) && - m_channelMode != MergeChannels) { - m = (h / channels); - my = m + (((ch - minChannel) * h) / channels); + if (i1 > i0 + 1) { + SVCERR << "WaveformLayer::paint: ERROR: i1 " << i1 << " > i0 " << i0 << " plus one (zoom = " << v->getZoomLevel() << ", model zoom = " << blockSize << ")" << endl; } - paint->setPen(greys[1]); - paint->drawLine(x0, my, x1, my); + const auto &r = ranges[rangeix]; + RangeSummarisableTimeValueModel::Range range; + + if (in_range_for(r, i0)) { - int n = 10; - int py = -1; + range = r[i0]; + + if (i1 > i0 && in_range_for(r, i1)) { + range.setMax(std::max(range.max(), r[i1].max())); + range.setMin(std::min(range.min(), r[i1].min())); + range.setAbsmean((range.absmean() + r[i1].absmean()) / 2); + } + + } else { +#ifdef DEBUG_WAVEFORM_PAINT + SVCERR << "No (or not enough) ranges for index i0 = " << i0 << " (there are " << r.size() << " range(s))" << endl; +#endif + continue; + } + + int rangeBottom = 0, rangeTop = 0, meanBottom = 0, meanTop = 0; + + if (mergingChannels && ranges.size() > 1) { + + const auto &other = ranges[1]; + + if (in_range_for(other, i0)) { + + range.setMax(fabsf(range.max())); + range.setMin(-fabsf(other[i0].max())); + range.setAbsmean + ((range.absmean() + other[i0].absmean()) / 2); + + if (i1 > i0 && in_range_for(other, i1)) { + // let's not concern ourselves about the mean + range.setMin(std::min(range.min(), + -fabsf(other[i1].max()))); + } + } + + } else if (mixingChannels && ranges.size() > 1) { + + const auto &other = ranges[1]; + + if (in_range_for(other, i0)) { + + range.setMax((range.max() + other[i0].max()) / 2); + range.setMin((range.min() + other[i0].min()) / 2); + range.setAbsmean((range.absmean() + other[i0].absmean()) / 2); + } + } + + int greyLevels = 1; + if (m_greyscale && (m_scale == LinearScale)) greyLevels = 4; + + switch (m_scale) { + + case LinearScale: + rangeBottom = int(double(m * greyLevels) * range.min() * gain); + rangeTop = int(double(m * greyLevels) * range.max() * gain); + meanBottom = int(double(-m) * range.absmean() * gain); + meanTop = int(double(m) * range.absmean() * gain); + break; + + case dBScale: + if (!mergingChannels) { + int db0 = dBscale(range.min() * gain, m); + int db1 = dBscale(range.max() * gain, m); + rangeTop = std::max(db0, db1); + meanTop = std::min(db0, db1); + if (mixingChannels) rangeBottom = meanTop; + else rangeBottom = dBscale(range.absmean() * gain, m); + meanBottom = rangeBottom; + } else { + rangeBottom = -dBscale(range.min() * gain, m * greyLevels); + rangeTop = dBscale(range.max() * gain, m * greyLevels); + meanBottom = -dBscale(range.absmean() * gain, m); + meanTop = dBscale(range.absmean() * gain, m); + } + break; + + case MeterScale: + if (!mergingChannels) { + int r0 = abs(AudioLevel::multiplier_to_preview(range.min() * gain, m)); + int r1 = abs(AudioLevel::multiplier_to_preview(range.max() * gain, m)); + rangeTop = std::max(r0, r1); + meanTop = std::min(r0, r1); + if (mixingChannels) rangeBottom = meanTop; + else rangeBottom = AudioLevel::multiplier_to_preview(range.absmean() * gain, m); + meanBottom = rangeBottom; + } else { + rangeBottom = -AudioLevel::multiplier_to_preview(range.min() * gain, m * greyLevels); + rangeTop = AudioLevel::multiplier_to_preview(range.max() * gain, m * greyLevels); + meanBottom = -AudioLevel::multiplier_to_preview(range.absmean() * gain, m); + meanTop = AudioLevel::multiplier_to_preview(range.absmean() * gain, m); + } + break; + } + + rangeBottom = my * greyLevels - rangeBottom; + rangeTop = my * greyLevels - rangeTop; + meanBottom = my - meanBottom; + meanTop = my - meanTop; + + int topFill = (rangeTop % greyLevels); + if (topFill > 0) topFill = greyLevels - topFill; + + int bottomFill = (rangeBottom % greyLevels); + + rangeTop = rangeTop / greyLevels; + rangeBottom = rangeBottom / greyLevels; + + bool clipped = false; + + if (rangeTop < my - m) { rangeTop = my - m; } + if (rangeTop > my + m) { rangeTop = my + m; } + if (rangeBottom < my - m) { rangeBottom = my - m; } + if (rangeBottom > my + m) { rangeBottom = my + m; } + + if (range.max() <= -1.0 || + range.max() >= 1.0) clipped = true; + + if (meanBottom > rangeBottom) meanBottom = rangeBottom; + if (meanTop < rangeTop) meanTop = rangeTop; + + bool drawMean = m_showMeans; + if (meanTop == rangeTop) { + if (meanTop < meanBottom) ++meanTop; + else drawMean = false; + } + if (meanBottom == rangeBottom && m_scale == LinearScale) { + if (meanBottom > meanTop) --meanBottom; + else drawMean = false; + } + + if (showIndividualSample) { + paint->setPen(baseColour); + paint->drawRect(x-1, rangeTop-1, 2, 2); + } - if (v->hasLightBackground() && - v->getViewManager() && - v->getViewManager()->shouldShowScaleGuides()) { + if (x != x0 && prevRangeBottom != -1) { + if (prevRangeBottom > rangeBottom + 1 && + prevRangeTop > rangeBottom + 1) { +// paint->setPen(midColour); + paint->setPen(baseColour); + paint->drawLine(x-1, prevRangeTop, x, rangeBottom + 1); + paint->setPen(prevRangeTopColour); + paint->drawPoint(x-1, prevRangeTop); + } else if (prevRangeBottom < rangeTop - 1 && + prevRangeTop < rangeTop - 1) { +// paint->setPen(midColour); + paint->setPen(baseColour); + paint->drawLine(x-1, prevRangeBottom, x, rangeTop - 1); + paint->setPen(prevRangeBottomColour); + paint->drawPoint(x-1, prevRangeBottom); + } + } - paint->setPen(QColor(240, 240, 240)); + if (m_model->isReady()) { + if (clipped /*!!! || + range.min() * gain <= -1.0 || + range.max() * gain >= 1.0 */) { + paint->setPen(Qt::red); //!!! getContrastingColour + } else { + paint->setPen(baseColour); + } + } else { + paint->setPen(midColour); + } - for (int i = 1; i < n; ++i) { - - double val = 0.0, nval = 0.0; +#ifdef DEBUG_WAVEFORM_PAINT_BY_PIXEL + SVCERR << "range " << rangeBottom << " -> " << rangeTop << ", means " << meanBottom << " -> " << meanTop << ", raw range " << range.min() << " -> " << range.max() << endl; +#endif - switch (m_scale) { + if (rangeTop == rangeBottom) { + paint->drawPoint(x, rangeTop); + } else { + paint->drawLine(x, rangeBottom, x, rangeTop); + } - case LinearScale: - val = (i * gain) / n; - if (i > 0) nval = -val; - break; + prevRangeTopColour = baseColour; + prevRangeBottomColour = baseColour; - case MeterScale: - val = AudioLevel::dB_to_multiplier(meterdbs[i]) * gain; - break; - - case dBScale: - val = AudioLevel::dB_to_multiplier(-(10*n) + i * 10) * gain; - break; - } - - if (val < -1.0 || val > 1.0) continue; - - int y = getYForValue(v, val, ch); - - if (py >= 0 && abs(y - py) < 10) continue; - else py = y; - - int ny = y; - if (nval != 0.0) { - ny = getYForValue(v, nval, ch); - } - - paint->drawLine(x0, y, x1, y); - if (ny != y) { - paint->drawLine(x0, ny, x1, ny); + if (m_greyscale && (m_scale == LinearScale) && m_model->isReady()) { + if (!clipped) { + if (rangeTop < rangeBottom) { + if (topFill > 0 && + (!drawMean || (rangeTop < meanTop - 1))) { + paint->setPen(greys[topFill - 1]); + paint->drawPoint(x, rangeTop); + prevRangeTopColour = greys[topFill - 1]; + } + if (bottomFill > 0 && + (!drawMean || (rangeBottom > meanBottom + 1))) { + paint->setPen(greys[bottomFill - 1]); + paint->drawPoint(x, rangeBottom); + prevRangeBottomColour = greys[bottomFill - 1]; + } } } } - - m_model->getSummaries(ch, frame0, frame1 - frame0, - *ranges, modelZoomLevel); + + if (drawMean) { + paint->setPen(midColour); + paint->drawLine(x, meanBottom, x, meanTop); + } + + prevRangeBottom = rangeBottom; + prevRangeTop = rangeTop; + } +} -#ifdef DEBUG_WAVEFORM_PAINT - cerr << "channel " << ch << ": " << ranges->size() << " ranges from " << frame0 << " to " << frame1 << " at zoom level " << modelZoomLevel << endl; -#endif +void +WaveformLayer::paintChannelScaleGuides(LayerGeometryProvider *v, + QPainter *paint, + QRect rect, + int ch) const +{ + int x0 = rect.left(); + int x1 = rect.right(); - if (mergingChannels || mixingChannels) { - if (m_model->getChannelCount() > 1) { - if (!otherChannelRanges) { - otherChannelRanges = - new RangeSummarisableTimeValueModel::RangeBlock; - } - m_model->getSummaries - (1, frame0, frame1 - frame0, *otherChannelRanges, - modelZoomLevel); - } else { - if (otherChannelRanges != ranges) delete otherChannelRanges; - otherChannelRanges = ranges; - } - } + int n = 10; + int py = -1; - for (int x = x0; x <= x1; ++x) { + double gain = m_effectiveGains[ch]; + + if (v->hasLightBackground() && + v->getViewManager() && + v->getViewManager()->shouldShowScaleGuides()) { - range = RangeSummarisableTimeValueModel::Range(); + paint->setPen(QColor(240, 240, 240)); - sv_frame_t f0, f1; - if (!getSourceFramesForX(v, x, modelZoomLevel, f0, f1)) continue; - f1 = f1 - 1; - - if (f0 < frame0) { - cerr << "ERROR: WaveformLayer::paint: pixel " << x << " has f0 = " << f0 << " which is less than range frame0 " << frame0 << " for x0 = " << x0 << endl; - continue; - } - - sv_frame_t i0 = (f0 - frame0) / modelZoomLevel; - sv_frame_t i1 = (f1 - frame0) / modelZoomLevel; - -#ifdef DEBUG_WAVEFORM_PAINT - cerr << "WaveformLayer::paint: pixel " << x << ": i0 " << i0 << " (f " << f0 << "), i1 " << i1 << " (f " << f1 << ")" << endl; -#endif - - if (i1 > i0 + 1) { - cerr << "WaveformLayer::paint: ERROR: i1 " << i1 << " > i0 " << i0 << " plus one (zoom = " << zoomLevel << ", model zoom = " << modelZoomLevel << ")" << endl; - } - - if (ranges && i0 < (sv_frame_t)ranges->size()) { - - range = (*ranges)[size_t(i0)]; - - if (i1 > i0 && i1 < (int)ranges->size()) { - range.setMax(std::max(range.max(), - (*ranges)[size_t(i1)].max())); - range.setMin(std::min(range.min(), - (*ranges)[size_t(i1)].min())); - range.setAbsmean((range.absmean() - + (*ranges)[size_t(i1)].absmean()) / 2); - } - - } else { -#ifdef DEBUG_WAVEFORM_PAINT - cerr << "No (or not enough) ranges for i0 = " << i0 << endl; -#endif - continue; - } - - int rangeBottom = 0, rangeTop = 0, meanBottom = 0, meanTop = 0; - - if (mergingChannels) { - - if (otherChannelRanges && i0 < (sv_frame_t)otherChannelRanges->size()) { - - range.setMax(fabsf(range.max())); - range.setMin(-fabsf((*otherChannelRanges)[size_t(i0)].max())); - range.setAbsmean - ((range.absmean() + - (*otherChannelRanges)[size_t(i0)].absmean()) / 2); - - if (i1 > i0 && i1 < (sv_frame_t)otherChannelRanges->size()) { - // let's not concern ourselves about the mean - range.setMin - (std::min - (range.min(), - -fabsf((*otherChannelRanges)[size_t(i1)].max()))); - } - } - - } else if (mixingChannels) { - - if (otherChannelRanges && i0 < (sv_frame_t)otherChannelRanges->size()) { - - range.setMax((range.max() - + (*otherChannelRanges)[size_t(i0)].max()) / 2); - range.setMin((range.min() - + (*otherChannelRanges)[size_t(i0)].min()) / 2); - range.setAbsmean((range.absmean() - + (*otherChannelRanges)[size_t(i0)].absmean()) / 2); - } - } - - int greyLevels = 1; - if (m_greyscale && (m_scale == LinearScale)) greyLevels = 4; + for (int i = 1; i < n; ++i) { + + double val = 0.0, nval = 0.0; switch (m_scale) { case LinearScale: - rangeBottom = int(double(m * greyLevels) * range.min() * gain); - rangeTop = int(double(m * greyLevels) * range.max() * gain); - meanBottom = int(double(-m) * range.absmean() * gain); - meanTop = int(double(m) * range.absmean() * gain); + val = (i * gain) / n; + if (i > 0) nval = -val; + break; + + case MeterScale: + val = AudioLevel::dB_to_multiplier(meterdbs[i]) * gain; break; case dBScale: - if (!mergingChannels) { - int db0 = dBscale(range.min() * gain, m); - int db1 = dBscale(range.max() * gain, m); - rangeTop = std::max(db0, db1); - meanTop = std::min(db0, db1); - if (mixingChannels) rangeBottom = meanTop; - else rangeBottom = dBscale(range.absmean() * gain, m); - meanBottom = rangeBottom; - } else { - rangeBottom = -dBscale(range.min() * gain, m * greyLevels); - rangeTop = dBscale(range.max() * gain, m * greyLevels); - meanBottom = -dBscale(range.absmean() * gain, m); - meanTop = dBscale(range.absmean() * gain, m); - } - break; - - case MeterScale: - if (!mergingChannels) { - int r0 = abs(AudioLevel::multiplier_to_preview(range.min() * gain, m)); - int r1 = abs(AudioLevel::multiplier_to_preview(range.max() * gain, m)); - rangeTop = std::max(r0, r1); - meanTop = std::min(r0, r1); - if (mixingChannels) rangeBottom = meanTop; - else rangeBottom = AudioLevel::multiplier_to_preview(range.absmean() * gain, m); - meanBottom = rangeBottom; - } else { - rangeBottom = -AudioLevel::multiplier_to_preview(range.min() * gain, m * greyLevels); - rangeTop = AudioLevel::multiplier_to_preview(range.max() * gain, m * greyLevels); - meanBottom = -AudioLevel::multiplier_to_preview(range.absmean() * gain, m); - meanTop = AudioLevel::multiplier_to_preview(range.absmean() * gain, m); - } + val = AudioLevel::dB_to_multiplier(-(10*n) + i * 10) * gain; break; } - rangeBottom = my * greyLevels - rangeBottom; - rangeTop = my * greyLevels - rangeTop; - meanBottom = my - meanBottom; - meanTop = my - meanTop; + if (val < -1.0 || val > 1.0) continue; - int topFill = (rangeTop % greyLevels); - if (topFill > 0) topFill = greyLevels - topFill; + int y = getYForValue(v, val, ch); - int bottomFill = (rangeBottom % greyLevels); + if (py >= 0 && abs(y - py) < 10) continue; + else py = y; - rangeTop = rangeTop / greyLevels; - rangeBottom = rangeBottom / greyLevels; - - bool clipped = false; - - if (rangeTop < my - m) { rangeTop = my - m; } - if (rangeTop > my + m) { rangeTop = my + m; } - if (rangeBottom < my - m) { rangeBottom = my - m; } - if (rangeBottom > my + m) { rangeBottom = my + m; } - - if (range.max() <= -1.0 || - range.max() >= 1.0) clipped = true; - - if (meanBottom > rangeBottom) meanBottom = rangeBottom; - if (meanTop < rangeTop) meanTop = rangeTop; - - bool drawMean = m_showMeans; - if (meanTop == rangeTop) { - if (meanTop < meanBottom) ++meanTop; - else drawMean = false; - } - if (meanBottom == rangeBottom && m_scale == LinearScale) { - if (meanBottom > meanTop) --meanBottom; - else drawMean = false; + int ny = y; + if (nval != 0.0) { + ny = getYForValue(v, nval, ch); } - if (x != x0 && prevRangeBottom != -1) { - if (prevRangeBottom > rangeBottom + 1 && - prevRangeTop > rangeBottom + 1) { -// paint->setPen(midColour); - paint->setPen(baseColour); - paint->drawLine(x-1, prevRangeTop, x, rangeBottom + 1); - paint->setPen(prevRangeTopColour); - paint->drawPoint(x-1, prevRangeTop); - } else if (prevRangeBottom < rangeTop - 1 && - prevRangeTop < rangeTop - 1) { -// paint->setPen(midColour); - paint->setPen(baseColour); - paint->drawLine(x-1, prevRangeBottom, x, rangeTop - 1); - paint->setPen(prevRangeBottomColour); - paint->drawPoint(x-1, prevRangeBottom); - } + paint->drawLine(x0, y, x1, y); + if (ny != y) { + paint->drawLine(x0, ny, x1, ny); } - - if (ready) { - if (clipped /*!!! || - range.min() * gain <= -1.0 || - range.max() * gain >= 1.0 */) { - paint->setPen(Qt::red); //!!! getContrastingColour - } else { - paint->setPen(baseColour); - } - } else { - paint->setPen(midColour); - } - -#ifdef DEBUG_WAVEFORM_PAINT - cerr << "range " << rangeBottom << " -> " << rangeTop << ", means " << meanBottom << " -> " << meanTop << ", raw range " << range.min() << " -> " << range.max() << endl; -#endif - - if (rangeTop == rangeBottom) { - paint->drawPoint(x, rangeTop); - } else { - paint->drawLine(x, rangeBottom, x, rangeTop); - } - - prevRangeTopColour = baseColour; - prevRangeBottomColour = baseColour; - - if (m_greyscale && (m_scale == LinearScale) && ready) { - if (!clipped) { - if (rangeTop < rangeBottom) { - if (topFill > 0 && - (!drawMean || (rangeTop < meanTop - 1))) { - paint->setPen(greys[topFill - 1]); - paint->drawPoint(x, rangeTop); - prevRangeTopColour = greys[topFill - 1]; - } - if (bottomFill > 0 && - (!drawMean || (rangeBottom > meanBottom + 1))) { - paint->setPen(greys[bottomFill - 1]); - paint->drawPoint(x, rangeBottom); - prevRangeBottomColour = greys[bottomFill - 1]; - } - } - } - } - - if (drawMean) { - paint->setPen(midColour); - paint->drawLine(x, meanBottom, x, meanTop); - } - - prevRangeBottom = rangeBottom; - prevRangeTop = rangeTop; } } - - if (m_middleLineHeight != 0.5) { - paint->restore(); - } - - if (m_aggressive) { - - if (ready && rect == v->getPaintRect()) { - m_cacheValid = true; - m_cacheZoomLevel = zoomLevel; - } - paint->end(); - delete paint; - viewPainter.drawPixmap(rect, *m_cache, rect); - } - - if (otherChannelRanges != ranges) delete otherChannelRanges; - delete ranges; } QString @@ -968,12 +1111,17 @@ if (!m_model || !m_model->isOK()) return ""; - int zoomLevel = v->getZoomLevel(); + ZoomLevel zoomLevel = v->getZoomLevel(); - int modelZoomLevel = m_model->getSummaryBlockSize(zoomLevel); + int desiredBlockSize = 1; + if (zoomLevel.zone == ZoomLevel::FramesPerPixel) { + desiredBlockSize = zoomLevel.level; + } + + int blockSize = m_model->getSummaryBlockSize(desiredBlockSize); sv_frame_t f0, f1; - if (!getSourceFramesForX(v, x, modelZoomLevel, f0, f1)) return ""; + if (!getSourceFramesForX(v, x, blockSize, f0, f1)) return ""; QString text; @@ -998,7 +1146,6 @@ for (int ch = minChannel; ch <= maxChannel; ++ch) { - int blockSize = v->getZoomLevel(); RangeSummarisableTimeValueModel::RangeBlock ranges; m_model->getSummaries(ch, f0, f1 - f0, ranges, blockSize); @@ -1082,7 +1229,7 @@ break; } -// cerr << "mergingChannels= " << mergingChannels << ", channel = " << channel << ", value = " << value << ", vy = " << vy << endl; +// SVCERR << "mergingChannels= " << mergingChannels << ", channel = " << channel << ", value = " << value << ", vy = " << vy << endl; return my - vy; } diff -r 9fb7133dd818 -r 86429ff00f05 layer/WaveformLayer.h --- a/layer/WaveformLayer.h Wed Oct 03 12:59:55 2018 +0100 +++ b/layer/WaveformLayer.h Wed Oct 10 08:44:27 2018 +0100 @@ -209,10 +209,30 @@ const RangeSummarisableTimeValueModel *m_model; // I do not own this + typedef std::vector RangeVec; + /// Return value is number of channels displayed int getChannelArrangement(int &min, int &max, - bool &merging, bool &mixing) const; + bool &merging, bool &mixing) const; + void paintChannel + (LayerGeometryProvider *, QPainter *paint, QRect rect, int channel, + const RangeVec &ranges, + int blockSize, sv_frame_t frame0, sv_frame_t frame1) const; + + void paintChannelScaleGuides(LayerGeometryProvider *, QPainter *paint, + QRect rect, int channel) const; + + void getSummaryRanges(int minChannel, int maxChannel, + bool mixingOrMerging, + sv_frame_t f0, sv_frame_t f1, + int blockSize, RangeVec &ranges) const; + + void getOversampledRanges(int minChannel, int maxChannel, + bool mixingOrMerging, + sv_frame_t f0, sv_frame_t f1, + int oversampleBy, RangeVec &ranges) const; + int getYForValue(const LayerGeometryProvider *v, double value, int channel) const; double getValueForY(const LayerGeometryProvider *v, int y, int &channel) const; @@ -238,7 +258,7 @@ mutable QPixmap *m_cache; mutable bool m_cacheValid; - mutable int m_cacheZoomLevel; + mutable ZoomLevel m_cacheZoomLevel; }; #endif diff -r 9fb7133dd818 -r 86429ff00f05 view/AlignmentView.cpp --- a/view/AlignmentView.cpp Wed Oct 03 12:59:55 2018 +0100 +++ b/view/AlignmentView.cpp Wed Oct 10 08:44:27 2018 +0100 @@ -57,14 +57,14 @@ } void -AlignmentView::viewAboveZoomLevelChanged(int level, bool) +AlignmentView::viewAboveZoomLevelChanged(ZoomLevel level, bool) { m_zoomLevel = level; update(); } void -AlignmentView::viewBelowZoomLevelChanged(int, bool) +AlignmentView::viewBelowZoomLevelChanged(ZoomLevel, bool) { update(); } @@ -80,9 +80,9 @@ if (m_above) { connect(m_above, - SIGNAL(zoomLevelChanged(int, bool)), + SIGNAL(zoomLevelChanged(ZoomLevel, bool)), this, - SLOT(viewAboveZoomLevelChanged(int, bool))); + SLOT(viewAboveZoomLevelChanged(ZoomLevel, bool))); } } @@ -97,9 +97,9 @@ if (m_below) { connect(m_below, - SIGNAL(zoomLevelChanged(int, bool)), + SIGNAL(zoomLevelChanged(ZoomLevel, bool)), this, - SLOT(viewBelowZoomLevelChanged(int, bool))); + SLOT(viewBelowZoomLevelChanged(ZoomLevel, bool))); } } diff -r 9fb7133dd818 -r 86429ff00f05 view/AlignmentView.h --- a/view/AlignmentView.h Wed Oct 03 12:59:55 2018 +0100 +++ b/view/AlignmentView.h Wed Oct 10 08:44:27 2018 +0100 @@ -32,8 +32,8 @@ public slots: virtual void globalCentreFrameChanged(sv_frame_t); virtual void viewCentreFrameChanged(View *, sv_frame_t); - virtual void viewAboveZoomLevelChanged(int, bool); - virtual void viewBelowZoomLevelChanged(int, bool); + virtual void viewAboveZoomLevelChanged(ZoomLevel, bool); + virtual void viewBelowZoomLevelChanged(ZoomLevel, bool); virtual void viewManagerPlaybackFrameChanged(sv_frame_t); protected: diff -r 9fb7133dd818 -r 86429ff00f05 view/Overview.cpp --- a/view/Overview.cpp Wed Oct 03 12:59:55 2018 +0100 +++ b/view/Overview.cpp Wed Oct 10 08:44:27 2018 +0100 @@ -44,13 +44,14 @@ void Overview::modelChangedWithin(sv_frame_t startFrame, sv_frame_t endFrame) { + using namespace std::rel_ops; + bool zoomChanged = false; sv_frame_t frameCount = getModelsEndFrame() - getModelsStartFrame(); - int zoomLevel = int(frameCount / width()); - if (zoomLevel < 1) zoomLevel = 1; - zoomLevel = getZoomConstraintBlockSize(zoomLevel, - ZoomConstraint::RoundUp); + ZoomLevel zoomLevel { ZoomLevel::FramesPerPixel, int(frameCount / width()) }; + if (zoomLevel.level < 1) zoomLevel.level = 1; + zoomLevel = getZoomConstraintLevel(zoomLevel, ZoomConstraint::RoundUp); if (zoomLevel != m_zoomLevel) { zoomChanged = true; } @@ -123,7 +124,7 @@ } void -Overview::viewZoomLevelChanged(View *v, int, bool) +Overview::viewZoomLevelChanged(View *v, ZoomLevel, bool) { if (v == this) return; if (m_views.find(v) != m_views.end()) { @@ -171,6 +172,8 @@ void Overview::paintEvent(QPaintEvent *e) { + using namespace std::rel_ops; + // Recalculate zoom in case the size of the widget has changed. #ifdef DEBUG_OVERVIEW @@ -179,16 +182,17 @@ sv_frame_t startFrame = getModelsStartFrame(); sv_frame_t frameCount = getModelsEndFrame() - getModelsStartFrame(); - int zoomLevel = int(frameCount / width()); - if (zoomLevel < 1) zoomLevel = 1; - zoomLevel = getZoomConstraintBlockSize(zoomLevel, - ZoomConstraint::RoundUp); + ZoomLevel zoomLevel { ZoomLevel::FramesPerPixel, int(frameCount / width()) }; + if (zoomLevel.level < 1) zoomLevel.level = 1; + zoomLevel = getZoomConstraintLevel(zoomLevel, ZoomConstraint::RoundUp); if (zoomLevel != m_zoomLevel) { m_zoomLevel = zoomLevel; emit zoomLevelChanged(m_zoomLevel, m_followZoom); } - sv_frame_t centreFrame = startFrame + m_zoomLevel * (width() / 2); + sv_frame_t centreFrame = startFrame + + sv_frame_t(round(m_zoomLevel.pixelsToFrames(width()/2))); + if (centreFrame > (startFrame + getModelsEndFrame())/2) { centreFrame = (startFrame + getModelsEndFrame())/2; } @@ -309,7 +313,7 @@ if (!m_clickedInRange) return; int xoff = int(e->x()) - int(m_clickPos.x()); - sv_frame_t frameOff = xoff * m_zoomLevel; + sv_frame_t frameOff = sv_frame_t(round(m_zoomLevel.pixelsToFrames(xoff))); sv_frame_t newCentreFrame = m_dragCentreFrame; if (frameOff > 0) { @@ -325,8 +329,11 @@ if (newCentreFrame > 0) --newCentreFrame; } + sv_frame_t pixel = sv_frame_t(round(m_zoomLevel.pixelsToFrames(1))); + if (std::max(m_centreFrame, newCentreFrame) - - std::min(m_centreFrame, newCentreFrame) > m_zoomLevel) { + std::min(m_centreFrame, newCentreFrame) > + pixel) { sv_frame_t rf = alignToReference(newCentreFrame); #ifdef DEBUG_OVERVIEW cerr << "Overview::mouseMoveEvent: x " << e->x() << " and click x " << m_clickPos.x() << " -> frame " << newCentreFrame << " -> rf " << rf << endl; diff -r 9fb7133dd818 -r 86429ff00f05 view/Overview.h --- a/view/Overview.h Wed Oct 03 12:59:55 2018 +0100 +++ b/view/Overview.h Wed Oct 10 08:44:27 2018 +0100 @@ -13,8 +13,8 @@ COPYING included with this distribution for more information. */ -#ifndef _OVERVIEW_H_ -#define _OVERVIEW_H_ +#ifndef SV_OVERVIEW_H +#define SV_OVERVIEW_H #include "View.h" @@ -46,7 +46,7 @@ virtual void globalCentreFrameChanged(sv_frame_t); virtual void viewCentreFrameChanged(View *, sv_frame_t); - virtual void viewZoomLevelChanged(View *, int, bool); + virtual void viewZoomLevelChanged(View *, ZoomLevel, bool); virtual void viewManagerPlaybackFrameChanged(sv_frame_t); virtual void setBoxColour(QColor); diff -r 9fb7133dd818 -r 86429ff00f05 view/Pane.cpp --- a/view/Pane.cpp Wed Oct 03 12:59:55 2018 +0100 +++ b/view/Pane.cpp Wed Oct 10 08:44:27 2018 +0100 @@ -201,7 +201,7 @@ int count = 0; int current = 0; - int level = 1; + ZoomLevel level; //!!! pull out into function (presumably in View) bool haveConstraint = false; @@ -213,20 +213,26 @@ } } + SVCERR << "haveConstraint = " << haveConstraint << endl; + if (haveConstraint) { while (true) { + //!!! this won't terminate if level is in the PixelsPerFrame zone if (getZoomLevel() == level) current = count; - int newLevel = getZoomConstraintBlockSize(level + 1, - ZoomConstraint::RoundUp); + ZoomLevel newLevel = getZoomConstraintLevel(level.incremented(), + ZoomConstraint::RoundUp); + SVCERR << "newLevel = " << newLevel << endl; if (newLevel == level) break; level = newLevel; if (++count == 50) break; } } else { // if we have no particular constraints, we can really spread out + //!!! this is nonsense in PixelsPerFrame zone while (true) { + using namespace std::rel_ops; if (getZoomLevel() >= level) current = count; - int step = level / 10; + int step = level.level / 10; int pwr = 0; while (step > 0) { ++pwr; @@ -237,13 +243,14 @@ step *= 2; --pwr; } -// cerr << level << endl; - level += step; - if (++count == 100 || level > 262144) break; + cerr << level.level << ", step " << step << endl; + level.level += step; + if (++count == 100 || level.level > 262144) break; } } -// cerr << "Have " << count << " zoom levels" << endl; + //!!! + SVCERR << "Have " << count << " zoom levels" << endl; m_hthumb->setMinimumValue(0); m_hthumb->setMaximumValue(count); @@ -1145,8 +1152,8 @@ QImage * Pane::renderPartToNewImage(sv_frame_t f0, sv_frame_t f1) { - int x0 = int(f0 / getZoomLevel()); - int x1 = int(f1 / getZoomLevel()); + int x0 = int(round(getZoomLevel().framesToPixels(double(f0)))); + int x1 = int(round(getZoomLevel().framesToPixels(double(f1)))); QImage *image = new QImage(x1 - x0 + m_scaleWidth, height(), QImage::Format_RGB32); @@ -1881,9 +1888,9 @@ int x1 = r.x() + r.width(); int y1 = r.y() + r.height(); - int w = x1 - x0; - sv_frame_t newStartFrame = getFrameForX(x0); + sv_frame_t newEndFrame = getFrameForX(x1); + sv_frame_t dist = newEndFrame - newStartFrame; sv_frame_t visibleFrames = getEndFrame() - getStartFrame(); if (newStartFrame <= -visibleFrames) { @@ -1893,14 +1900,9 @@ if (newStartFrame >= getModelsEndFrame()) { newStartFrame = getModelsEndFrame() - 1; } - - double ratio = double(w) / double(width()); -// cerr << "ratio: " << ratio << endl; - int newZoomLevel = (int)nearbyint(m_zoomLevel * ratio); - if (newZoomLevel < 1) newZoomLevel = 1; - -// cerr << "start: " << m_startFrame << ", level " << m_zoomLevel << endl; - setZoomLevel(getZoomConstraintBlockSize(newZoomLevel)); + + ZoomLevel newZoomLevel = ZoomLevel::fromRatio(width(), dist); + setZoomLevel(getZoomConstraintLevel(newZoomLevel)); setStartFrame(newStartFrame); QString unit; @@ -2379,21 +2381,18 @@ } } else { + using namespace std::rel_ops; // Zoom in or out - int newZoomLevel = m_zoomLevel; + ZoomLevel newZoomLevel = m_zoomLevel; if (sign > 0) { - if (newZoomLevel <= 2) { - newZoomLevel = 1; - } else { - newZoomLevel = getZoomConstraintBlockSize - (newZoomLevel - 1, ZoomConstraint::RoundDown); - } - } else { // sign < 0 - newZoomLevel = getZoomConstraintBlockSize - (newZoomLevel + 1, ZoomConstraint::RoundUp); + newZoomLevel = getZoomConstraintLevel(newZoomLevel.decremented(), + ZoomConstraint::RoundDown); + } else { + newZoomLevel = getZoomConstraintLevel(newZoomLevel.incremented(), + ZoomConstraint::RoundUp); } if (newZoomLevel != m_zoomLevel) { @@ -2422,9 +2421,11 @@ // Scroll left or right by a fixed number of pixels if (getStartFrame() < 0 && - getEndFrame() >= getModelsEndFrame()) return; - - int delta = (pixels * m_zoomLevel); + getEndFrame() >= getModelsEndFrame()) { + return; + } + + int delta = int(round(m_zoomLevel.pixelsToFrames(pixels))); if (m_centreFrame < delta) { setCentreFrame(0); @@ -2443,8 +2444,7 @@ //!!! dupe with updateHeadsUpDisplay int count = 0; - int level = 1; - + ZoomLevel level; //!!! pull out into function (presumably in View) bool haveConstraint = false; @@ -2458,17 +2458,19 @@ if (haveConstraint) { while (true) { + //!!! this won't terminate if level is in the PixelsPerFrame zone if (m_hthumb->getMaximumValue() - value == count) break; - int newLevel = getZoomConstraintBlockSize(level + 1, - ZoomConstraint::RoundUp); + ZoomLevel newLevel = getZoomConstraintLevel(level.incremented(), + ZoomConstraint::RoundUp); if (newLevel == level) break; level = newLevel; if (++count == 50) break; } } else { + //!!! this is nonsense in PixelsPerFrame zone while (true) { if (m_hthumb->getMaximumValue() - value == count) break; - int step = level / 10; + int step = level.level / 10; int pwr = 0; while (step > 0) { ++pwr; @@ -2480,8 +2482,8 @@ --pwr; } // cerr << level << endl; - level += step; - if (++count == 100 || level > 262144) break; + level.level += step; + if (++count == 100 || level.level > 262144) break; } } @@ -2747,7 +2749,7 @@ } void -Pane::viewZoomLevelChanged(View *v, int z, bool locked) +Pane::viewZoomLevelChanged(View *v, ZoomLevel z, bool locked) { // cerr << "Pane[" << this << "]::zoomLevelChanged (global now " // << (m_manager ? m_manager->getGlobalZoom() : 0) << ")" << endl; diff -r 9fb7133dd818 -r 86429ff00f05 view/Pane.h --- a/view/Pane.h Wed Oct 03 12:59:55 2018 +0100 +++ b/view/Pane.h Wed Oct 10 08:44:27 2018 +0100 @@ -87,7 +87,7 @@ // view slots virtual void toolModeChanged() override; virtual void zoomWheelsEnabledChanged() override; - virtual void viewZoomLevelChanged(View *v, int z, bool locked) override; + virtual void viewZoomLevelChanged(View *, ZoomLevel, bool locked) override; virtual void modelAlignmentCompletionChanged() override; // local slots, not overrides diff -r 9fb7133dd818 -r 86429ff00f05 view/View.cpp --- a/view/View.cpp Wed Oct 03 12:59:55 2018 +0100 +++ b/view/View.cpp Wed Oct 10 08:44:27 2018 +0100 @@ -54,7 +54,7 @@ QFrame(w), m_id(getNextId()), m_centreFrame(0), - m_zoomLevel(1024), + m_zoomLevel(ZoomLevel::FramesPerPixel, 1024), m_followPan(true), m_followZoom(true), m_followPlay(PlaybackScrollPageWithCentre), @@ -64,7 +64,7 @@ m_cache(0), m_buffer(0), m_cacheCentreFrame(0), - m_cacheZoomLevel(1024), + m_cacheZoomLevel(ZoomLevel::FramesPerPixel, 1024), m_selectionCached(false), m_deleting(false), m_haveSelectedLayer(false), @@ -319,7 +319,8 @@ void View::setStartFrame(sv_frame_t f) { - setCentreFrame(f + m_zoomLevel * (width() / 2)); + setCentreFrame(f + sv_frame_t(round + (m_zoomLevel.pixelsToFrames(width() / 2)))); } bool @@ -327,22 +328,40 @@ { bool changeVisible = false; +#ifdef DEBUG_VIEW + SVCERR << "View::setCentreFrame: from " << m_centreFrame + << " to " << f << endl; +#endif + if (m_centreFrame != f) { - int formerPixel = int(m_centreFrame / m_zoomLevel); - + sv_frame_t formerCentre = m_centreFrame; m_centreFrame = f; - - int newPixel = int(m_centreFrame / m_zoomLevel); - if (newPixel != formerPixel) { + if (m_zoomLevel.zone == ZoomLevel::PixelsPerFrame) { #ifdef DEBUG_VIEW_WIDGET_PAINT - cout << "View(" << this << ")::setCentreFrame: newPixel " << newPixel << ", formerPixel " << formerPixel << endl; + SVCERR << "View(" << this << ")::setCentreFrame: in PixelsPerFrame zone, so change must be visible" << endl; #endif update(); - changeVisible = true; + + } else { + + int formerPixel = int(formerCentre / m_zoomLevel.level); + int newPixel = int(m_centreFrame / m_zoomLevel.level); + + if (newPixel != formerPixel) { + +#ifdef DEBUG_VIEW_WIDGET_PAINT + SVCERR << "View(" << this << ")::setCentreFrame: newPixel " << newPixel << ", formerPixel " << formerPixel << endl; +#endif + // ensure the centre frame is a multiple of the zoom level + m_centreFrame = sv_frame_t(newPixel) * m_zoomLevel.level; + + update(); + changeVisible = true; + } } if (e) { @@ -362,23 +381,65 @@ int View::getXForFrame(sv_frame_t frame) const { - return int((frame - getStartFrame()) / m_zoomLevel); + // In FramesPerPixel mode, the pixel should be the one "covering" + // the given frame, i.e. to the "left" of it - not necessarily the + // nearest boundary. + + sv_frame_t level = m_zoomLevel.level; + sv_frame_t fdiff = frame - getCentreFrame(); + int diff, result; + + if (m_zoomLevel.zone == ZoomLevel::FramesPerPixel) { + diff = int(fdiff / level); + if ((fdiff < 0) && ((fdiff % level) != 0)) { + --diff; // round to the left + } + } else { + diff = int(fdiff * level); + } + + result = int(diff + (width()/2)); + return result; } sv_frame_t View::getFrameForX(int x) const { - sv_frame_t z = m_zoomLevel; // nb not just int, or multiplication may overflow - sv_frame_t frame = m_centreFrame - (width()/2) * z; - - frame = (frame / z) * z; // this is start frame - frame = frame + x * z; - -#ifdef DEBUG_VIEW_WIDGET_PAINT - cerr << "View::getFrameForX(" << x << "): z = " << z << ", m_centreFrame = " << m_centreFrame << ", width() = " << width() << ", frame = " << frame << endl; + // Note, this must always return a value that is on a zoom-level + // boundary - regardless of whether the nominal centre frame is on + // such a boundary or not. + + // In PixelsPerFrame mode, the frame should be the one immediately + // left of the given pixel, not necessarily the nearest. + + int diff = x - (width()/2); + sv_frame_t level = m_zoomLevel.level; + sv_frame_t fdiff, result; + + if (m_zoomLevel.zone == ZoomLevel::FramesPerPixel) { + fdiff = diff * level; + result = ((fdiff + m_centreFrame) / level) * level; + } else { + fdiff = diff / level; + if ((diff < 0) && ((diff % level) != 0)) { + --fdiff; // round to the left + } + result = fdiff + m_centreFrame; + } + +#ifdef DEBUG_VIEW + if (x == 0) { + SVCERR << "getFrameForX(" << x << "): diff = " << diff << ", fdiff = " + << fdiff << ", m_centreFrame = " << m_centreFrame + << ", level = " << m_zoomLevel.level + << ", diff % level = " << (diff % m_zoomLevel.level) + << ", nominal " << fdiff + m_centreFrame + << ", will return " << result + << endl; + } #endif - - return frame; + + return result; } double @@ -447,7 +508,7 @@ } } -int +ZoomLevel View::getZoomLevel() const { #ifdef DEBUG_VIEW_WIDGET_PAINT @@ -476,16 +537,17 @@ } void -View::setZoomLevel(int z) +View::setZoomLevel(ZoomLevel z) { - int dpratio = effectiveDevicePixelRatio(); - if (z < dpratio) return; - if (z < 1) z = 1; - if (m_zoomLevel != int(z)) { - m_zoomLevel = z; - emit zoomLevelChanged(z, m_followZoom); - update(); +//!!! int dpratio = effectiveDevicePixelRatio(); +// if (z < dpratio) return; +// if (z < 1) z = 1; + if (m_zoomLevel == z) { + return; } + m_zoomLevel = z; + emit zoomLevelChanged(z, m_followZoom); + update(); } bool @@ -725,13 +787,13 @@ m_manager->disconnect(this, SLOT(globalCentreFrameChanged(sv_frame_t))); m_manager->disconnect(this, SLOT(viewCentreFrameChanged(View *, sv_frame_t))); m_manager->disconnect(this, SLOT(viewManagerPlaybackFrameChanged(sv_frame_t))); - m_manager->disconnect(this, SLOT(viewZoomLevelChanged(View *, int, bool))); + m_manager->disconnect(this, SLOT(viewZoomLevelChanged(View *, ZoomLevel, bool))); m_manager->disconnect(this, SLOT(toolModeChanged())); m_manager->disconnect(this, SLOT(selectionChanged())); m_manager->disconnect(this, SLOT(overlayModeChanged())); m_manager->disconnect(this, SLOT(zoomWheelsEnabledChanged())); disconnect(m_manager, SLOT(viewCentreFrameChanged(sv_frame_t, bool, PlaybackFollowMode))); - disconnect(m_manager, SLOT(zoomLevelChanged(int, bool))); + disconnect(m_manager, SLOT(zoomLevelChanged(ZoomLevel, bool))); } m_manager = manager; @@ -743,8 +805,8 @@ connect(m_manager, SIGNAL(playbackFrameChanged(sv_frame_t)), this, SLOT(viewManagerPlaybackFrameChanged(sv_frame_t))); - connect(m_manager, SIGNAL(viewZoomLevelChanged(View *, int, bool)), - this, SLOT(viewZoomLevelChanged(View *, int, bool))); + connect(m_manager, SIGNAL(viewZoomLevelChanged(View *, ZoomLevel, bool)), + this, SLOT(viewZoomLevelChanged(View *, ZoomLevel, bool))); connect(m_manager, SIGNAL(toolModeChanged()), this, SLOT(toolModeChanged())); @@ -764,8 +826,8 @@ m_manager, SLOT(viewCentreFrameChanged(sv_frame_t, bool, PlaybackFollowMode))); - connect(this, SIGNAL(zoomLevelChanged(int, bool)), - m_manager, SLOT(viewZoomLevelChanged(int, bool))); + connect(this, SIGNAL(zoomLevelChanged(ZoomLevel, bool)), + m_manager, SLOT(viewZoomLevelChanged(ZoomLevel, bool))); switch (m_followPlay) { @@ -1127,7 +1189,7 @@ } void -View::viewZoomLevelChanged(View *p, int z, bool locked) +View::viewZoomLevelChanged(View *p, ZoomLevel z, bool locked) { #ifdef DEBUG_VIEW_WIDGET_PAINT cerr << "View[" << this << "]: viewZoomLevelChanged(" << p << ", " << z << ", " << locked << ")" << endl; @@ -1400,30 +1462,32 @@ return nonScrollables; } -int -View::getZoomConstraintBlockSize(int blockSize, - ZoomConstraint::RoundingDirection dir) +ZoomLevel +View::getZoomConstraintLevel(ZoomLevel zoomLevel, + ZoomConstraint::RoundingDirection dir) const { - int candidate = blockSize; + using namespace std::rel_ops; + + ZoomLevel candidate = zoomLevel; bool haveCandidate = false; PowerOfSqrtTwoZoomConstraint defaultZoomConstraint; - for (LayerList::const_iterator i = m_layerStack.begin(); i != m_layerStack.end(); ++i) { + for (auto i = m_layerStack.begin(); i != m_layerStack.end(); ++i) { const ZoomConstraint *zoomConstraint = (*i)->getZoomConstraint(); if (!zoomConstraint) zoomConstraint = &defaultZoomConstraint; - int thisBlockSize = - zoomConstraint->getNearestBlockSize(blockSize, dir); + ZoomLevel thisLevel = + zoomConstraint->getNearestZoomLevel(zoomLevel, dir); // Go for the block size that's furthest from the one // passed in. Most of the time, that's what we want. if (!haveCandidate || - (thisBlockSize > blockSize && thisBlockSize > candidate) || - (thisBlockSize < blockSize && thisBlockSize < candidate)) { - candidate = thisBlockSize; + (thisLevel > zoomLevel && thisLevel > candidate) || + (thisLevel < zoomLevel && thisLevel < candidate)) { + candidate = thisLevel; haveCandidate = true; } } @@ -1454,16 +1518,18 @@ void View::zoom(bool in) { - int newZoomLevel = m_zoomLevel; + ZoomLevel newZoomLevel = m_zoomLevel; if (in) { - newZoomLevel = getZoomConstraintBlockSize(newZoomLevel - 1, - ZoomConstraint::RoundDown); + newZoomLevel = getZoomConstraintLevel(m_zoomLevel.decremented(), + ZoomConstraint::RoundDown); } else { - newZoomLevel = getZoomConstraintBlockSize(newZoomLevel + 1, - ZoomConstraint::RoundUp); + newZoomLevel = getZoomConstraintLevel(m_zoomLevel.incremented(), + ZoomConstraint::RoundUp); } + using namespace std::rel_ops; + if (newZoomLevel != m_zoomLevel) { setZoomLevel(newZoomLevel); } @@ -1765,6 +1831,8 @@ << m_cacheZoomLevel << ", zoom " << m_zoomLevel << endl; #endif + using namespace std::rel_ops; + if (!m_cache || m_cacheZoomLevel != m_zoomLevel || scaledCacheSize != m_cache->size()) { @@ -2370,8 +2438,8 @@ bool View::render(QPainter &paint, int xorigin, sv_frame_t f0, sv_frame_t f1) { - int x0 = int(f0 / m_zoomLevel); - int x1 = int(f1 / m_zoomLevel); + int x0 = int(round(m_zoomLevel.framesToPixels(double(f0)))); + int x1 = int(round(m_zoomLevel.framesToPixels(double(f1)))); int w = x1 - x0; @@ -2433,7 +2501,8 @@ return false; } - m_centreFrame = f0 + (x + width()/2) * m_zoomLevel; + m_centreFrame = f0 + sv_frame_t(round(m_zoomLevel.pixelsToFrames + (x + width()/2))); QRect chunk(0, 0, width(), height()); @@ -2484,8 +2553,8 @@ QImage * View::renderPartToNewImage(sv_frame_t f0, sv_frame_t f1) { - int x0 = int(f0 / getZoomLevel()); - int x1 = int(f1 / getZoomLevel()); + int x0 = int(round(getZoomLevel().framesToPixels(double(f0)))); + int x1 = int(round(getZoomLevel().framesToPixels(double(f1)))); QImage *image = new QImage(x1 - x0, height(), QImage::Format_RGB32); @@ -2512,8 +2581,8 @@ QSize View::getRenderedPartImageSize(sv_frame_t f0, sv_frame_t f1) { - int x0 = int(f0 / getZoomLevel()); - int x1 = int(f1 / getZoomLevel()); + int x0 = int(round(getZoomLevel().framesToPixels(double(f0)))); + int x1 = int(round(getZoomLevel().framesToPixels(double(f1)))); return QSize(x1 - x0, height()); } @@ -2530,8 +2599,8 @@ bool View::renderPartToSvgFile(QString filename, sv_frame_t f0, sv_frame_t f1) { - int x0 = int(f0 / getZoomLevel()); - int x1 = int(f1 / getZoomLevel()); + int x0 = int(round(getZoomLevel().framesToPixels(double(f0)))); + int x1 = int(round(getZoomLevel().framesToPixels(double(f1)))); QSvgGenerator generator; generator.setFileName(filename); @@ -2553,15 +2622,27 @@ { stream << indent; + int classicZoomValue, deepZoomValue; + + if (m_zoomLevel.zone == ZoomLevel::FramesPerPixel) { + classicZoomValue = m_zoomLevel.level; + deepZoomValue = 1; + } else { + classicZoomValue = 1; + deepZoomValue = m_zoomLevel.level; + } + stream << QString("\n") + "deepZoom=\"%3\" " + "followPan=\"%4\" " + "followZoom=\"%5\" " + "tracking=\"%6\" " + " %7>\n") .arg(m_centreFrame) - .arg(m_zoomLevel) + .arg(classicZoomValue) + .arg(deepZoomValue) .arg(m_followPan) .arg(m_followZoom) .arg(m_followPlay == PlaybackScrollContinuous ? "scroll" : diff -r 9fb7133dd818 -r 86429ff00f05 view/View.h --- a/view/View.h Wed Oct 03 12:59:55 2018 +0100 +++ b/view/View.h Wed Oct 10 08:44:27 2018 +0100 @@ -148,16 +148,17 @@ bool logarithmic) const; /** - * Return the zoom level, i.e. the number of frames per pixel + * Return the zoom level, i.e. the number of frames per pixel or + * pixels per frame */ - int getZoomLevel() const; + ZoomLevel getZoomLevel() const; /** - * Set the zoom level, i.e. the number of frames per pixel. The - * centre frame will be unchanged; the start and end frames will - * change. + * Set the zoom level, i.e. the number of frames per pixel or + * pixels per frame. The centre frame will be unchanged; the + * start and end frames will change. */ - virtual void setZoomLevel(int z); + virtual void setZoomLevel(ZoomLevel z); /** * Zoom in or out. @@ -400,7 +401,7 @@ bool globalScroll, PlaybackFollowMode followMode); - void zoomLevelChanged(int level, bool locked); + void zoomLevelChanged(ZoomLevel level, bool locked); void contextHelpChanged(const QString &); @@ -418,7 +419,7 @@ virtual void globalCentreFrameChanged(sv_frame_t); virtual void viewCentreFrameChanged(View *, sv_frame_t); virtual void viewManagerPlaybackFrameChanged(sv_frame_t); - virtual void viewZoomLevelChanged(View *, int, bool); + virtual void viewZoomLevelChanged(View *, ZoomLevel, bool); virtual void propertyContainerSelected(View *, PropertyContainer *pc); @@ -456,9 +457,9 @@ bool areLayersScrollable() const; LayerList getScrollableBackLayers(bool testChanged, bool &changed) const; LayerList getNonScrollableFrontLayers(bool testChanged, bool &changed) const; - int getZoomConstraintBlockSize(int blockSize, - ZoomConstraint::RoundingDirection dir = - ZoomConstraint::RoundNearest) const; + ZoomLevel getZoomConstraintLevel(ZoomLevel level, + ZoomConstraint::RoundingDirection dir = + ZoomConstraint::RoundNearest) const; // True if the top layer(s) use colours for meaningful things. If // this is the case, selections will be shown using unfilled boxes @@ -481,7 +482,7 @@ int effectiveDevicePixelRatio() const; sv_frame_t m_centreFrame; - int m_zoomLevel; + ZoomLevel m_zoomLevel; bool m_followPan; bool m_followZoom; PlaybackFollowMode m_followPlay; @@ -493,7 +494,7 @@ QPixmap *m_cache; // I own this QPixmap *m_buffer; // I own this sv_frame_t m_cacheCentreFrame; - int m_cacheZoomLevel; + ZoomLevel m_cacheZoomLevel; bool m_selectionCached; bool m_deleting; diff -r 9fb7133dd818 -r 86429ff00f05 view/ViewManager.cpp --- a/view/ViewManager.cpp Wed Oct 03 12:59:55 2018 +0100 +++ b/view/ViewManager.cpp Wed Oct 10 08:44:27 2018 +0100 @@ -33,7 +33,7 @@ m_playSource(0), m_recordTarget(0), m_globalCentreFrame(0), - m_globalZoom(1024), + m_globalZoom(ZoomLevel::FramesPerPixel, 1024), m_playbackFrame(0), m_playbackModel(0), m_mainModelSampleRate(0), @@ -147,7 +147,7 @@ emit globalCentreFrameChanged(f); } -int +ZoomLevel ViewManager::getGlobalZoom() const { #ifdef DEBUG_VIEW_MANAGER @@ -687,7 +687,7 @@ } void -ViewManager::viewZoomLevelChanged(int z, bool locked) +ViewManager::viewZoomLevelChanged(ZoomLevel z, bool locked) { View *v = dynamic_cast(sender()); @@ -709,7 +709,11 @@ emit viewZoomLevelChanged(v, z, locked); if (!dynamic_cast(v)) { - emit activity(tr("Zoom to %n sample(s) per pixel", "", z)); + if (z.zone == ZoomLevel::FramesPerPixel) { + emit activity(tr("Zoom to %n sample(s) per pixel", "", z.level)); + } else { + emit activity(tr("Zoom to %n pixels per sample", "", z.level)); + } } } diff -r 9fb7133dd818 -r 86429ff00f05 view/ViewManager.h --- a/view/ViewManager.h Wed Oct 03 12:59:55 2018 +0100 +++ b/view/ViewManager.h Wed Oct 10 08:44:27 2018 +0100 @@ -27,6 +27,7 @@ #include "base/Command.h" #include "base/Clipboard.h" #include "base/BaseTypes.h" +#include "base/ZoomLevel.h" class AudioPlaySource; class AudioRecordTarget; @@ -87,7 +88,7 @@ bool isRecording() const; sv_frame_t getGlobalCentreFrame() const; // the set method is a slot - int getGlobalZoom() const; + ZoomLevel getGlobalZoom() const; sv_frame_t getPlaybackFrame() const; // the set method is a slot @@ -255,7 +256,7 @@ void viewCentreFrameChanged(View *v, sv_frame_t frame); /** Emitted when a view zooms. */ - void viewZoomLevelChanged(View *v, int zoom, bool locked); + void viewZoomLevelChanged(View *v, ZoomLevel zoom, bool locked); /** Emitted when the playback frame changes. */ void playbackFrameChanged(sv_frame_t frame); @@ -307,7 +308,7 @@ public slots: void viewCentreFrameChanged(sv_frame_t, bool, PlaybackFollowMode); - void viewZoomLevelChanged(int, bool); + void viewZoomLevelChanged(ZoomLevel, bool); void setGlobalCentreFrame(sv_frame_t); void setPlaybackFrame(sv_frame_t); void playStatusChanged(bool playing); @@ -323,7 +324,7 @@ AudioRecordTarget *m_recordTarget; sv_frame_t m_globalCentreFrame; - int m_globalZoom; + ZoomLevel m_globalZoom; mutable sv_frame_t m_playbackFrame; Model *m_playbackModel; //!!! sv_samplerate_t m_mainModelSampleRate; diff -r 9fb7133dd818 -r 86429ff00f05 view/ViewProxy.h --- a/view/ViewProxy.h Wed Oct 03 12:59:55 2018 +0100 +++ b/view/ViewProxy.h Wed Oct 10 08:44:27 2018 +0100 @@ -75,12 +75,20 @@ bool &log) const { return m_view->getValueExtents(unit, min, max, log); } - virtual int getZoomLevel() const { - int z = m_view->getZoomLevel(); + virtual ZoomLevel getZoomLevel() const { + ZoomLevel z = m_view->getZoomLevel(); + //!!! // cerr << "getZoomLevel: from " << z << " to "; - z = z / m_scaleFactor; + if (z.zone == ZoomLevel::FramesPerPixel) { + z.level /= m_scaleFactor; + if (z.level < 1) { + z.level = 1; + } + } else { + //!!!??? + z.level *= m_scaleFactor; + } // cerr << z << endl; - if (z < 1) z = 1; return z; } virtual QRect getPaintRect() const {