# HG changeset patch # User Chris Cannam # Date 1538731525 -3600 # Node ID 2fec0d9bd7ac1b4b6f0890b2dbe6ac090678632c # Parent 32400727bcbd2f9b4de784541b5a4aecd9d538ab# Parent 51d6551d524410a742f95d4e3cafc512bcb6e9ac Merge from default branch diff -r 51d6551d5244 -r 2fec0d9bd7ac base/RealTime.h --- a/base/RealTime.h Wed Oct 03 15:45:57 2018 +0100 +++ b/base/RealTime.h Fri Oct 05 10:25:25 2018 +0100 @@ -57,7 +57,8 @@ sec(r.sec), nsec(r.nsec) { } static RealTime fromSeconds(double sec); - static RealTime fromMilliseconds(int msec); + static RealTime fromMilliseconds(int64_t msec); + static RealTime fromMicroseconds(int64_t usec); static RealTime fromTimeval(const struct timeval &); static RealTime fromXsdDuration(std::string xsdd); @@ -171,7 +172,8 @@ * Unlike toText, this function does not depend on the application * preferences. */ - std::string toFrameText(int fps, bool hms) const; + std::string toFrameText(int fps, bool hms, + std::string frameDelimiter = ":") const; /** * Return a user-readable string to the nearest second, in H:M:S diff -r 51d6551d5244 -r 2fec0d9bd7ac base/RealTimeSV.cpp --- a/base/RealTimeSV.cpp Wed Oct 03 15:45:57 2018 +0100 +++ b/base/RealTimeSV.cpp Fri Oct 05 10:25:25 2018 +0100 @@ -61,9 +61,29 @@ } RealTime -RealTime::fromMilliseconds(int msec) +RealTime::fromMilliseconds(int64_t msec) { - return RealTime(msec / 1000, (msec % 1000) * 1000000); + int64_t sec = msec / 1000; + if (sec > INT_MAX || sec < INT_MIN) { + cerr << "WARNING: millisecond value out of range for RealTime, " + << "returning zero instead: " << msec << endl; + return RealTime::zeroTime; + } + + return RealTime(int(sec), int(msec % 1000) * 1000000); +} + +RealTime +RealTime::fromMicroseconds(int64_t usec) +{ + int64_t sec = usec / 1000000; + if (sec > INT_MAX || sec < INT_MIN) { + cerr << "WARNING: microsecond value out of range for RealTime, " + << "returning zero instead: " << usec << endl; + return RealTime::zeroTime; + } + + return RealTime(int(sec), int(usec % 1000000) * 1000); } RealTime @@ -258,20 +278,27 @@ Preferences *p = Preferences::getInstance(); bool hms = true; + std::string frameDelimiter = ":"; if (p) { hms = p->getShowHMS(); int fps = 0; switch (p->getTimeToTextMode()) { - case Preferences::TimeToTextMs: break; - case Preferences::TimeToTextUs: fps = 1000000; break; + case Preferences::TimeToTextMs: + break; + case Preferences::TimeToTextUs: + fps = 1000000; + frameDelimiter = "."; + break; case Preferences::TimeToText24Frame: fps = 24; break; case Preferences::TimeToText25Frame: fps = 25; break; case Preferences::TimeToText30Frame: fps = 30; break; case Preferences::TimeToText50Frame: fps = 50; break; case Preferences::TimeToText60Frame: fps = 60; break; } - if (fps != 0) return toFrameText(fps, hms); + if (fps != 0) { + return toFrameText(fps, hms, frameDelimiter); + } } return toMSText(fixedDp, hms); @@ -338,9 +365,11 @@ } std::string -RealTime::toFrameText(int fps, bool hms) const +RealTime::toFrameText(int fps, bool hms, std::string frameDelimiter) const { - if (*this < RealTime::zeroTime) return "-" + (-*this).toFrameText(fps, hms); + if (*this < RealTime::zeroTime) { + return "-" + (-*this).toFrameText(fps, hms); + } std::stringstream out; @@ -357,7 +386,7 @@ div *= 10; } - out << ":"; + out << frameDelimiter; // cerr << "div = " << div << ", f = "<< f << endl; diff -r 51d6551d5244 -r 2fec0d9bd7ac base/ZoomConstraint.h --- a/base/ZoomConstraint.h Wed Oct 03 15:45:57 2018 +0100 +++ b/base/ZoomConstraint.h Fri Oct 05 10:25:25 2018 +0100 @@ -18,6 +18,8 @@ #include +#include "ZoomLevel.h" + /** * ZoomConstraint is a simple interface that describes a limitation on * the available zoom sizes for a view, for example based on cache @@ -39,30 +41,42 @@ }; /** - * Given the "ideal" block size (frames per pixel) for a given - * zoom level, return the nearest viable block size for this - * constraint. + * Given an "ideal" zoom level (frames per pixel or pixels per + * frame) for a given zoom level, return the nearest viable block + * size for this constraint. * * For example, if a block size of 1523 frames per pixel is * requested but the underlying model only supports value * summaries at powers-of-two block sizes, return 1024 or 2048 * depending on the rounding direction supplied. */ - virtual int getNearestBlockSize(int requestedBlockSize, - RoundingDirection = RoundNearest) + virtual ZoomLevel getNearestZoomLevel(ZoomLevel requestedZoomLevel, + RoundingDirection = RoundNearest) const { - if (requestedBlockSize > getMaxZoomLevel()) return getMaxZoomLevel(); - else return requestedBlockSize; + if (getMaxZoomLevel() < requestedZoomLevel) return getMaxZoomLevel(); + else return requestedZoomLevel; } /** + * Return the minimum zoom level within range for this constraint. + * Individual views will probably want to limit this, for example + * in order to ensure that at least one or two samples fit in the + * current window size, or in order to save on interpolation cost. + */ + virtual ZoomLevel getMinZoomLevel() const { + return { ZoomLevel::PixelsPerFrame, 512 }; + } + + /** * Return the maximum zoom level within range for this constraint. * This is quite large -- individual views will probably want to * limit how far a user might reasonably zoom out based on other * factors such as the duration of the file. */ - virtual int getMaxZoomLevel() const { return 4194304; } // 2^22, arbitrarily + virtual ZoomLevel getMaxZoomLevel() const { + return { ZoomLevel::FramesPerPixel, 4194304 }; // 2^22, arbitrarily + } }; #endif diff -r 51d6551d5244 -r 2fec0d9bd7ac base/ZoomLevel.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/ZoomLevel.cpp Fri Oct 05 10:25:25 2018 +0100 @@ -0,0 +1,25 @@ +/* -*- 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 "ZoomLevel.h" + +std::ostream &operator<<(std::ostream &s, const ZoomLevel &z) { + if (z.zone == ZoomLevel::PixelsPerFrame) { + s << "1/" << z.level; + } else { + s << z.level; + } + return s; +} + diff -r 51d6551d5244 -r 2fec0d9bd7ac base/ZoomLevel.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/base/ZoomLevel.h Fri Oct 05 10:25:25 2018 +0100 @@ -0,0 +1,124 @@ +/* -*- 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 SV_ZOOM_LEVEL_H +#define SV_ZOOM_LEVEL_H + +#include "BaseTypes.h" + +#include +#include + +/** Display zoom level. Can be an integer number of samples per pixel, + * or an integer number of pixels per sample. + */ +struct ZoomLevel { + + enum Zone { + FramesPerPixel, // zoomed out (as in classic SV) + PixelsPerFrame // zoomed in beyond 1-1 (interpolating the waveform) + }; + + Zone zone; + int level; + + ZoomLevel() : zone(FramesPerPixel), level(1) { } + ZoomLevel(Zone z, int lev) : zone(z), level(lev) { } + + bool operator<(const ZoomLevel &other) const { + if (zone == FramesPerPixel) { + if (other.zone == zone) { + return level < other.level; + } else { + return false; + } + } else { + if (other.zone == zone) { + return level > other.level; + } else { + return false; + } + } + } + + bool operator==(const ZoomLevel &other) const { + return (zone == other.zone && level == other.level); + } + + ZoomLevel incremented() const { + if (zone == FramesPerPixel) { + return { zone, level + 1 }; + } else if (level == 1) { + return { FramesPerPixel, 2 }; + } else if (level == 2) { + return { FramesPerPixel, 1 }; + } else { + return { zone, level - 1 }; + } + } + + ZoomLevel decremented() const { + if (zone == PixelsPerFrame) { + return { zone, level + 1 }; + } else if (level == 1) { + return { PixelsPerFrame, 2 }; + } else { + return { zone, level - 1 }; + } + } + + /** Inexact conversion. The result is a whole number if we are + * zoomed in enough (in PixelsPerFrame zone), a fraction + * otherwise. + */ + double framesToPixels(double frames) const { + if (zone == PixelsPerFrame) { + return frames * level; + } else { + return frames / level; + } + } + + /** Inexact conversion. The result is a whole number if we are + * zoomed out enough (in FramesPerPixel zone), a fraction + * otherwise. + */ + double pixelsToFrames(double pixels) const { + if (zone == PixelsPerFrame) { + return pixels / level; + } else { + return pixels * level; + } + } + + /** Return a ZoomLevel that approximates the given ratio of pixels + * to frames. + */ + static ZoomLevel fromRatio(int pixels, sv_frame_t frames) { + if (pixels < frames) { + return { FramesPerPixel, int(round(double(frames)/pixels)) }; + } else { + int r = int(round(pixels/double(frames))); + if (r > 1) { + return { PixelsPerFrame, r }; + } else { + return { FramesPerPixel, 1 }; + } + } + } +}; + +std::ostream &operator<<(std::ostream &s, const ZoomLevel &z); + +#endif diff -r 51d6551d5244 -r 2fec0d9bd7ac base/test/TestOurRealTime.h --- a/base/test/TestOurRealTime.h Wed Oct 03 15:45:57 2018 +0100 +++ b/base/test/TestOurRealTime.h Fri Oct 05 10:25:25 2018 +0100 @@ -138,6 +138,20 @@ QCOMPARE(RealTime::fromMilliseconds(-1000), RealTime(-1, 0)); QCOMPARE(RealTime::fromMilliseconds(-1500), RealTime(-1, -ONE_BILLION/2)); } + + void fromMicroseconds() + { + QCOMPARE(RealTime::fromMicroseconds(0), RealTime(0, 0)); + QCOMPARE(RealTime::fromMicroseconds(500000), RealTime(0, ONE_BILLION/2)); + QCOMPARE(RealTime::fromMicroseconds(1000000), RealTime(1, 0)); + QCOMPARE(RealTime::fromMicroseconds(1500000), RealTime(1, ONE_BILLION/2)); + + QCOMPARE(RealTime::fromMicroseconds(-0), RealTime(0, 0)); + QCOMPARE(RealTime::fromMicroseconds(-500000), RealTime(0, -ONE_BILLION/2)); + QCOMPARE(RealTime::fromMicroseconds(-1000000), RealTime(-1, 0)); + QCOMPARE(RealTime::fromMicroseconds(-1500000), RealTime(-1, -ONE_BILLION/2)); + QCOMPARE(RealTime::fromMicroseconds(13500000), RealTime(13, ONE_BILLION/2)); + } void fromTimeval() { diff -r 51d6551d5244 -r 2fec0d9bd7ac data/model/PowerOfSqrtTwoZoomConstraint.cpp --- a/data/model/PowerOfSqrtTwoZoomConstraint.cpp Wed Oct 03 15:45:57 2018 +0100 +++ b/data/model/PowerOfSqrtTwoZoomConstraint.cpp Fri Oct 05 10:25:25 2018 +0100 @@ -21,13 +21,30 @@ #include "base/Debug.h" -int -PowerOfSqrtTwoZoomConstraint::getNearestBlockSize(int blockSize, +ZoomLevel +PowerOfSqrtTwoZoomConstraint::getNearestZoomLevel(ZoomLevel requested, RoundingDirection dir) const { int type, power; - int rv = getNearestBlockSize(blockSize, type, power, dir); - return rv; + int blockSize; + + if (requested.zone == ZoomLevel::FramesPerPixel) { + blockSize = getNearestBlockSize(requested.level, type, power, dir); + return { requested.zone, blockSize }; + } else { + RoundingDirection opposite = dir; + if (dir == RoundUp) opposite = RoundDown; + else if (dir == RoundDown) opposite = RoundUp; + blockSize = getNearestBlockSize(requested.level, type, power, opposite); + if (blockSize > getMinZoomLevel().level) { + blockSize = getMinZoomLevel().level; + } + if (blockSize == 1) { + return { ZoomLevel::FramesPerPixel, 1 }; + } else { + return { requested.zone, blockSize }; + } + } } int @@ -113,6 +130,9 @@ prevBase = base; } - if (result > getMaxZoomLevel()) result = getMaxZoomLevel(); + if (result > getMaxZoomLevel().level) { + result = getMaxZoomLevel().level; + } + return result; } diff -r 51d6551d5244 -r 2fec0d9bd7ac data/model/PowerOfSqrtTwoZoomConstraint.h --- a/data/model/PowerOfSqrtTwoZoomConstraint.h Wed Oct 03 15:45:57 2018 +0100 +++ b/data/model/PowerOfSqrtTwoZoomConstraint.h Fri Oct 05 10:25:25 2018 +0100 @@ -21,17 +21,17 @@ class PowerOfSqrtTwoZoomConstraint : virtual public ZoomConstraint { public: - virtual int getNearestBlockSize(int requestedBlockSize, - RoundingDirection dir = RoundNearest) - const; - + virtual ZoomLevel getNearestZoomLevel(ZoomLevel requested, + RoundingDirection dir = RoundNearest) + const override; + + virtual int getMinCachePower() const { return 6; } + virtual int getNearestBlockSize(int requestedBlockSize, int &type, int &power, RoundingDirection dir = RoundNearest) const; - - virtual int getMinCachePower() const { return 6; } }; #endif diff -r 51d6551d5244 -r 2fec0d9bd7ac data/model/PowerOfTwoZoomConstraint.cpp --- a/data/model/PowerOfTwoZoomConstraint.cpp Wed Oct 03 15:45:57 2018 +0100 +++ b/data/model/PowerOfTwoZoomConstraint.cpp Fri Oct 05 10:25:25 2018 +0100 @@ -15,11 +15,39 @@ #include "PowerOfTwoZoomConstraint.h" +ZoomLevel +PowerOfTwoZoomConstraint::getNearestZoomLevel(ZoomLevel requested, + RoundingDirection dir) const +{ + int blockSize; + + if (requested.zone == ZoomLevel::FramesPerPixel) { + blockSize = getNearestBlockSize(requested.level, dir); + if (blockSize > getMaxZoomLevel().level) { + blockSize = getMaxZoomLevel().level; + } + return { requested.zone, blockSize }; + } else { + RoundingDirection opposite = dir; + if (dir == RoundUp) opposite = RoundDown; + else if (dir == RoundDown) opposite = RoundUp; + blockSize = getNearestBlockSize(requested.level, opposite); + if (blockSize > getMinZoomLevel().level) { + blockSize = getMinZoomLevel().level; + } + if (blockSize == 1) { + return { ZoomLevel::FramesPerPixel, 1 }; + } else { + return { requested.zone, blockSize }; + } + } +} + int PowerOfTwoZoomConstraint::getNearestBlockSize(int req, RoundingDirection dir) const { - int max = getMaxZoomLevel(); + int max = getMaxZoomLevel().level; if (req > max) { return max; diff -r 51d6551d5244 -r 2fec0d9bd7ac data/model/PowerOfTwoZoomConstraint.h --- a/data/model/PowerOfTwoZoomConstraint.h Wed Oct 03 15:45:57 2018 +0100 +++ b/data/model/PowerOfTwoZoomConstraint.h Fri Oct 05 10:25:25 2018 +0100 @@ -21,7 +21,12 @@ class PowerOfTwoZoomConstraint : virtual public ZoomConstraint { public: - virtual int getNearestBlockSize(int requestedBlockSize, + virtual ZoomLevel getNearestZoomLevel(ZoomLevel requested, + RoundingDirection dir = RoundNearest) + const override; + +protected: + virtual int getNearestBlockSize(int requested, RoundingDirection dir = RoundNearest) const; }; diff -r 51d6551d5244 -r 2fec0d9bd7ac data/model/ReadOnlyWaveFileModel.cpp --- a/data/model/ReadOnlyWaveFileModel.cpp Wed Oct 03 15:45:57 2018 +0100 +++ b/data/model/ReadOnlyWaveFileModel.cpp Fri Oct 05 10:25:25 2018 +0100 @@ -341,6 +341,7 @@ int power = m_zoomConstraint.getMinCachePower(); int roundedBlockSize = m_zoomConstraint.getNearestBlockSize (desired, cacheType, power, ZoomConstraint::RoundDown); + if (cacheType != 0 && cacheType != 1) { // We will be reading directly from file, so can satisfy any // blocksize requirement diff -r 51d6551d5244 -r 2fec0d9bd7ac data/model/WaveFileModel.h --- a/data/model/WaveFileModel.h Wed Oct 03 15:45:57 2018 +0100 +++ b/data/model/WaveFileModel.h Fri Oct 05 10:25:25 2018 +0100 @@ -13,8 +13,8 @@ COPYING included with this distribution for more information. */ -#ifndef WAVE_FILE_MODEL_H -#define WAVE_FILE_MODEL_H +#ifndef SV_WAVE_FILE_MODEL_H +#define SV_WAVE_FILE_MODEL_H #include "RangeSummarisableTimeValueModel.h" diff -r 51d6551d5244 -r 2fec0d9bd7ac data/model/WaveformOversampler.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/model/WaveformOversampler.cpp Fri Oct 05 10:25:25 2018 +0100 @@ -0,0 +1,295 @@ +/* -*- 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 "WaveformOversampler.h" + +#include "base/Profiler.h" + +#include "data/model/DenseTimeValueModel.h" + +floatvec_t +WaveformOversampler::getOversampledData(const DenseTimeValueModel *source, + int channel, + sv_frame_t sourceStartFrame, + sv_frame_t sourceFrameCount, + int oversampleBy) +{ + Profiler profiler("WaveformOversampler::getOversampledData"); + + // Oversampled at a fixed ratio of m_filterRatio + floatvec_t fixedRatio = getFixedRatioData(source, channel, + sourceStartFrame, + sourceFrameCount); + sv_frame_t fixedCount = fixedRatio.size(); + sv_frame_t targetCount = (fixedCount / m_filterRatio) * oversampleBy; + + // And apply linear interpolation to the desired factor + + floatvec_t result(targetCount, 0.f); + + for (int i = 0; i < targetCount; ++i) { + double pos = (double(i) / oversampleBy) * m_filterRatio; + double diff = pos - floor(pos); + int ix = int(floor(pos)); + double interpolated = (1.0 - diff) * fixedRatio[ix]; + if (in_range_for(fixedRatio, ix + 1)) { + interpolated += diff * fixedRatio[ix + 1]; + } + result[i] = float(interpolated); + } + + return result; +} + +floatvec_t +WaveformOversampler::getFixedRatioData(const DenseTimeValueModel *source, + int channel, + sv_frame_t sourceStartFrame, + sv_frame_t sourceFrameCount) +{ + Profiler profiler("WaveformOversampler::getFixedRatioData"); + + sv_frame_t sourceLength = source->getEndFrame(); + + if (sourceStartFrame + sourceFrameCount > sourceLength) { + sourceFrameCount = sourceLength - sourceStartFrame; + if (sourceFrameCount <= 0) return {}; + } + + sv_frame_t targetFrameCount = sourceFrameCount * m_filterRatio; + + sv_frame_t filterLength = m_filter.size(); // NB this is known to be odd + sv_frame_t filterTailOut = (filterLength - 1) / 2; + sv_frame_t filterTailIn = filterTailOut / m_filterRatio; + + floatvec_t oversampled(targetFrameCount, 0.f); + + sv_frame_t i0 = sourceStartFrame - filterTailIn; + if (i0 < 0) { + i0 = 0; + } + sv_frame_t i1 = sourceStartFrame + sourceFrameCount + filterTailIn; + if (i1 > sourceLength) { + i1 = sourceLength; + } + + floatvec_t sourceData = source->getData(channel, i0, i1 - i0); + + for (sv_frame_t i = i0; i < i1; ++i) { + float v = sourceData[i - i0]; + sv_frame_t outOffset = + (i - sourceStartFrame) * m_filterRatio - filterTailOut; + for (sv_frame_t j = 0; j < filterLength; ++j) { + sv_frame_t outIndex = outOffset + j; + if (outIndex < 0 || outIndex >= targetFrameCount) { + continue; + } + oversampled[outIndex] += v * m_filter[j]; + } + } + + return oversampled; +} + +int +WaveformOversampler::m_filterRatio = 8; + +/// Precalculated windowed sinc FIR filter for oversampling ratio of 8 +floatvec_t +WaveformOversampler::m_filter { + 2.0171043153063023E-4, 2.887198196326776E-4, + 3.410439309101285E-4, 3.4267123819805857E-4, + 2.843462511901066E-4, 1.6636986363946504E-4, + -4.5940658605786285E-18, -1.9299665002484582E-4, + -3.8279951732549946E-4, -5.357990649609105E-4, + -6.201170748425957E-4, -6.11531555444137E-4, + -4.987822892899791E-4, -2.872272251922189E-4, + -7.822991648518709E-19, 3.2382854144162815E-4, + 6.341027017666046E-4, 8.769331519465396E-4, + 0.001003535186382615, 9.791732608026272E-4, + 7.906678783612421E-4, 4.5101220009813206E-4, + -3.039648514978356E-18, -4.996532051508215E-4, + -9.704877518513666E-4, -0.0013318083550190923, + -0.0015128911871247466, -0.0014658111457301016, + -0.0011756800671747431, -6.663214707558645E-4, + 1.0713650598357415E-17, 7.292959363289514E-4, + 0.0014084768982220279, 0.0019222969998680237, + 0.0021721723797956524, 0.0020938999751673173, + 0.0016712330766289326, 9.427050283841188E-4, + -5.656965938821965E-18, -0.0010225643654040554, + -0.001966437013513757, -0.002672722670880038, + -0.00300806671037164, -0.00288843624179131, + -0.0022967244980623574, -0.0012908081494665458, + -5.1499690577098E-18, 0.0013904094522721, + 0.0026648961861419334, 0.0036103002009868065, + 0.004050469159316014, 0.0038774554290217484, + 0.0030739396559265075, 0.001722603817632299, + -9.130030250503607E-18, -0.0018451873718735516, + -0.0035270571169279162, -0.004765847116110058, + -0.0053332982334767624, -0.005092831604550132, + -0.00402770012894082, -0.0022517645624319594, + 3.2752446397299053E-17, 0.0024010765839506923, + 0.004579613038976446, 0.006174912111845945, + 0.00689578873526276, 0.006571541332393174, + 0.005186887306036285, 0.002894248521447605, + -1.336645565990815E-17, -0.0030747336558684963, + -0.0058540294958507235, -0.007879546416595632, + -0.008784519668338507, -0.008357645279493864, + -0.006586046547485615, -0.003669217725935383, + -1.9348975378752276E-17, 0.0038863208094135626, + 0.007388553022623823, 0.009931080628244226, + 0.011056594746806033, 0.010505398026651453, + 0.008267906564613223, 0.00460048159140493, + -1.816145184081109E-17, -0.004861124757802925, + -0.009231379891669668, -0.012394511669674028, + -0.01378467517229709, -0.013084177592430083, + -0.010287380585427207, -0.00571879407588959, + 7.520535431851951E-17, 0.006032144534828161, + 0.011445734103106982, 0.015355551390625357, + 0.017065088242935025, 0.016186450815185452, + 0.012718051439340603, 0.0070655888687995785, + -2.3209664144996714E-17, -0.007444328311482942, + -0.01411821163125819, -0.018932253281654043, + -0.02103125585328301, -0.019941019333653123, + -0.015663002335303704, -0.008699245445932525, + 2.5712475624993567E-17, 0.009161748270723635, + 0.0173729814451255, 0.023294901939595228, + 0.02587678878709242, 0.02453592568963366, + 0.01927365323131565, 0.010706050935569809, + -2.8133472199037193E-17, -0.011280308241551094, + -0.02139710071477064, -0.02870170641615764, + -0.031897218249350504, -0.030260140480986304, + -0.023784294156618507, -0.013220449772289724, + 3.042099156841831E-17, 0.013951594368737923, + 0.0264884258371512, 0.03556693609945249, + 0.03957036852169639, 0.0375845888664677, + 0.029579845398822833, 0.016465167405787955, + -3.2524514488155654E-17, -0.017431115375410273, + -0.03315356952091943, -0.04460179422099746, + -0.0497244634100025, -0.04733366619358394, + -0.037341081614037944, -0.020838316594689998, + 3.439626695384943E-17, 0.022185914535618936, + 0.04232958202159685, 0.05713801867687856, + 0.06393033000280622, 0.06109191933191721, + 0.04839482380906132, 0.027127167584840003, + -3.5992766927138734E-17, -0.029168755716052385, + -0.055960213335110184, -0.07598693477350407, + -0.08556575102599769, -0.08233350786406181, + -0.06571046000158454, -0.03713224848702707, + 3.727625511036616E-17, 0.04066438975848791, + 0.07882920057770397, 0.10826166115536123, + 0.12343378955977465, 0.12040455825217859, + 0.09755344650130694, 0.056053367635801106, + -3.8215953158245473E-17, -0.0638435745677513, + -0.12667849902789644, -0.17861887575594584, + -0.20985333136704623, -0.21188193950868073, + -0.17867464086818077, -0.10760048593620072, + 3.8789099095340224E-17, 0.13868670259490817, + 0.29927055936918734, 0.46961864377510765, + 0.6358321371992203, 0.7836674214332147, + 0.9000377382311825, 0.9744199685311685, + 1.0000000000000004, 0.9744199685311685, + 0.9000377382311825, 0.7836674214332147, + 0.6358321371992203, 0.46961864377510765, + 0.29927055936918734, 0.13868670259490817, + 3.8789099095340224E-17, -0.10760048593620072, + -0.17867464086818077, -0.21188193950868073, + -0.20985333136704623, -0.17861887575594584, + -0.12667849902789644, -0.0638435745677513, + -3.8215953158245473E-17, 0.056053367635801106, + 0.09755344650130694, 0.12040455825217859, + 0.12343378955977465, 0.10826166115536123, + 0.07882920057770397, 0.04066438975848791, + 3.727625511036616E-17, -0.03713224848702707, + -0.06571046000158454, -0.08233350786406181, + -0.08556575102599769, -0.07598693477350407, + -0.055960213335110184, -0.029168755716052385, + -3.5992766927138734E-17, 0.027127167584840003, + 0.04839482380906132, 0.06109191933191721, + 0.06393033000280622, 0.05713801867687856, + 0.04232958202159685, 0.022185914535618936, + 3.439626695384943E-17, -0.020838316594689998, + -0.037341081614037944, -0.04733366619358394, + -0.0497244634100025, -0.04460179422099746, + -0.03315356952091943, -0.017431115375410273, + -3.2524514488155654E-17, 0.016465167405787955, + 0.029579845398822833, 0.0375845888664677, + 0.03957036852169639, 0.03556693609945249, + 0.0264884258371512, 0.013951594368737923, + 3.042099156841831E-17, -0.013220449772289724, + -0.023784294156618507, -0.030260140480986304, + -0.031897218249350504, -0.02870170641615764, + -0.02139710071477064, -0.011280308241551094, + -2.8133472199037193E-17, 0.010706050935569809, + 0.01927365323131565, 0.02453592568963366, + 0.02587678878709242, 0.023294901939595228, + 0.0173729814451255, 0.009161748270723635, + 2.5712475624993567E-17, -0.008699245445932525, + -0.015663002335303704, -0.019941019333653123, + -0.02103125585328301, -0.018932253281654043, + -0.01411821163125819, -0.007444328311482942, + -2.3209664144996714E-17, 0.0070655888687995785, + 0.012718051439340603, 0.016186450815185452, + 0.017065088242935025, 0.015355551390625357, + 0.011445734103106982, 0.006032144534828161, + 7.520535431851951E-17, -0.00571879407588959, + -0.010287380585427207, -0.013084177592430083, + -0.01378467517229709, -0.012394511669674028, + -0.009231379891669668, -0.004861124757802925, + -1.816145184081109E-17, 0.00460048159140493, + 0.008267906564613223, 0.010505398026651453, + 0.011056594746806033, 0.009931080628244226, + 0.007388553022623823, 0.0038863208094135626, + -1.9348975378752276E-17, -0.003669217725935383, + -0.006586046547485615, -0.008357645279493864, + -0.008784519668338507, -0.007879546416595632, + -0.0058540294958507235, -0.0030747336558684963, + -1.336645565990815E-17, 0.002894248521447605, + 0.005186887306036285, 0.006571541332393174, + 0.00689578873526276, 0.006174912111845945, + 0.004579613038976446, 0.0024010765839506923, + 3.2752446397299053E-17, -0.0022517645624319594, + -0.00402770012894082, -0.005092831604550132, + -0.0053332982334767624, -0.004765847116110058, + -0.0035270571169279162, -0.0018451873718735516, + -9.130030250503607E-18, 0.001722603817632299, + 0.0030739396559265075, 0.0038774554290217484, + 0.004050469159316014, 0.0036103002009868065, + 0.0026648961861419334, 0.0013904094522721, + -5.1499690577098E-18, -0.0012908081494665458, + -0.0022967244980623574, -0.00288843624179131, + -0.00300806671037164, -0.002672722670880038, + -0.001966437013513757, -0.0010225643654040554, + -5.656965938821965E-18, 9.427050283841188E-4, + 0.0016712330766289326, 0.0020938999751673173, + 0.0021721723797956524, 0.0019222969998680237, + 0.0014084768982220279, 7.292959363289514E-4, + 1.0713650598357415E-17, -6.663214707558645E-4, + -0.0011756800671747431, -0.0014658111457301016, + -0.0015128911871247466, -0.0013318083550190923, + -9.704877518513666E-4, -4.996532051508215E-4, + -3.039648514978356E-18, 4.5101220009813206E-4, + 7.906678783612421E-4, 9.791732608026272E-4, + 0.001003535186382615, 8.769331519465396E-4, + 6.341027017666046E-4, 3.2382854144162815E-4, + -7.822991648518709E-19, -2.872272251922189E-4, + -4.987822892899791E-4, -6.11531555444137E-4, + -6.201170748425957E-4, -5.357990649609105E-4, + -3.8279951732549946E-4, -1.9299665002484582E-4, + -4.5940658605786285E-18, 1.6636986363946504E-4, + 2.843462511901066E-4, 3.4267123819805857E-4, + 3.410439309101285E-4, 2.887198196326776E-4, + 2.0171043153063023E-4 +}; + diff -r 51d6551d5244 -r 2fec0d9bd7ac data/model/WaveformOversampler.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/model/WaveformOversampler.h Fri Oct 05 10:25:25 2018 +0100 @@ -0,0 +1,57 @@ +/* -*- 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 SV_WAVEFORM_OVERSAMPLER_H +#define SV_WAVEFORM_OVERSAMPLER_H + +#include "base/BaseTypes.h" + +class DenseTimeValueModel; + +/** Oversample the sample data from a DenseTimeValueModel by an + * integer factor, on the assumption that the model represents + * audio. Oversampling is carried out using a windowed sinc filter + * for a fixed 8x ratio with further linear interpolation to handle + * other ratios. The aim is not to provide the "best-sounding" + * interpolation, but to provide accurate and predictable projections + * of the theoretical waveform shape for display rendering without + * leaving decisions about interpolation up to a resampler library. + */ +class WaveformOversampler +{ +public: + /** Return an oversampled version of the audio data from the given + * source sample range. Will query sufficient source audio before + * and after the requested range (where available) to ensure an + * accurate-looking result after filtering. The returned vector + * will have sourceFrameCount * oversampleBy samples, except when + * truncated because the end of the model was reached. + */ + static floatvec_t getOversampledData(const DenseTimeValueModel *source, + int channel, + sv_frame_t sourceStartFrame, + sv_frame_t sourceFrameCount, + int oversampleBy); + +private: + static floatvec_t getFixedRatioData(const DenseTimeValueModel *source, + int channel, + sv_frame_t sourceStartFrame, + sv_frame_t sourceFrameCount); + + static int m_filterRatio; + static floatvec_t m_filter; +}; + +#endif diff -r 51d6551d5244 -r 2fec0d9bd7ac data/model/test/TestWaveformOversampler.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/model/test/TestWaveformOversampler.h Fri Oct 05 10:25:25 2018 +0100 @@ -0,0 +1,251 @@ +/* -*- 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 TEST_WAVEFORM_OVERSAMPLER_H +#define TEST_WAVEFORM_OVERSAMPLER_H + +#include "../WaveformOversampler.h" +#include "../WritableWaveFileModel.h" + +#include "../../../base/BaseTypes.h" + +#include +#include + +class TestWaveformOversampler : public QObject +{ + Q_OBJECT + +public: + TestWaveformOversampler() { + m_source = floatvec_t(5000, 0.f); + m_source[0] = 1.f; + m_source[2500] = 0.5f; + m_source[2501] = -0.5f; + m_source[4999] = -1.f; + for (int i = 3000; i < 3900; ++i) { + m_source[i] = float(sin(double(i - 3000) * M_PI / 50.0)); + } + m_sourceModel = new WritableWaveFileModel(8000, 1); + const float *d = m_source.data(); + QVERIFY(m_sourceModel->addSamples(&d, m_source.size())); + m_sourceModel->writeComplete(); + } + + ~TestWaveformOversampler() { + delete m_sourceModel; + } + +private: + floatvec_t m_source; + WritableWaveFileModel *m_sourceModel; + + void compareStrided(floatvec_t obtained, floatvec_t expected, int stride) { + QCOMPARE(obtained.size(), expected.size() * stride); + float threshold = 1e-10f; + for (int i = 0; in_range_for(expected, i); ++i) { + if (fabsf(obtained[i * stride] - expected[i]) > threshold) { + std::cerr << "At position " << i * stride << ": " + << obtained[i * stride] << " != " << expected[i] + << std::endl; + QCOMPARE(obtained, expected); + } + } + } + + void compareVecs(floatvec_t obtained, floatvec_t expected) { + compareStrided(obtained, expected, 1); + } + + floatvec_t get(sv_frame_t sourceStartFrame, + sv_frame_t sourceFrameCount, + int oversampleBy) { + return WaveformOversampler::getOversampledData + (m_sourceModel, 0, + sourceStartFrame, sourceFrameCount, oversampleBy); + } + + void testVerbatim(sv_frame_t sourceStartFrame, + sv_frame_t sourceFrameCount, + int oversampleBy, + floatvec_t expected) { + floatvec_t output = + get(sourceStartFrame, sourceFrameCount, oversampleBy); + compareVecs(output, expected); + } + + void testStrided(sv_frame_t sourceStartFrame, + sv_frame_t sourceFrameCount, + int oversampleBy, + floatvec_t expected) { + // check only the values that are expected to be precisely the + // original samples + floatvec_t output = + get(sourceStartFrame, sourceFrameCount, oversampleBy); + compareStrided(output, expected, oversampleBy); + } + + floatvec_t sourceSubset(sv_frame_t start, sv_frame_t length) { + return floatvec_t(m_source.begin() + start, + m_source.begin() + start + length); + } + +private slots: + void testWholeVerbatim() { + testVerbatim(0, 5000, 1, m_source); + } + + void testSubsetsVerbatim() { + testVerbatim(0, 500, 1, sourceSubset(0, 500)); + testVerbatim(4500, 500, 1, sourceSubset(4500, 500)); + testVerbatim(2000, 1000, 1, sourceSubset(2000, 1000)); + } + + void testOverlapsVerbatim() { + // overlapping the start -> result should be zero-padded to + // preserve start frame + floatvec_t expected = sourceSubset(0, 400); + expected.insert(expected.begin(), 100, 0.f); + testVerbatim(-100, 500, 1, expected); + + // overlapping the end -> result should be truncated to + // preserve source length + expected = sourceSubset(4600, 400); + testVerbatim(4600, 500, 1, expected); + } + + void testWhole2x() { + testStrided(0, 5000, 2, m_source); + + // check for windowed sinc values between the original samples + floatvec_t output = get(0, 5000, 2); + QVERIFY(output[1] - 0.6358 < 0.0001); + QVERIFY(output[3] + 0.2099 < 0.0001); + } + + void testWhole3x() { + testStrided(0, 5000, 3, m_source); + + // check for windowed sinc values between the original samples + floatvec_t output = get(0, 5000, 3); + QVERIFY(output[1] > 0.7); + QVERIFY(output[2] > 0.4); + QVERIFY(output[4] < -0.1); + QVERIFY(output[5] < -0.1); + } + + void testWhole4x() { + testStrided(0, 5000, 4, m_source); + + // check for windowed sinc values between the original samples + floatvec_t output = get(0, 5000, 4); + QVERIFY(output[1] - 0.9000 < 0.0001); + QVERIFY(output[2] - 0.6358 < 0.0001); + QVERIFY(output[3] - 0.2993 < 0.0001); + QVERIFY(output[5] + 0.1787 < 0.0001); + QVERIFY(output[6] + 0.2099 < 0.0001); + QVERIFY(output[7] + 0.1267 < 0.0001); + + // alternate values at 2n should equal all values at n + output = get(0, 5000, 4); + floatvec_t half = get(0, 5000, 2); + compareStrided(output, half, 2); + } + + void testWhole8x() { + testStrided(0, 5000, 8, m_source); + + // alternate values at 2n should equal all values at n + floatvec_t output = get(0, 5000, 8); + floatvec_t half = get(0, 5000, 4); + compareStrided(output, half, 2); + } + + void testWhole10x() { + testStrided(0, 5000, 10, m_source); + + // alternate values at 2n should equal all values at n + floatvec_t output = get(0, 5000, 10); + floatvec_t half = get(0, 5000, 5); + compareStrided(output, half, 2); + } + + void testWhole16x() { + testStrided(0, 5000, 16, m_source); + + // alternate values at 2n should equal all values at n + floatvec_t output = get(0, 5000, 16); + floatvec_t half = get(0, 5000, 8); + compareStrided(output, half, 2); + } + + void testSubsets4x() { + testStrided(0, 500, 4, sourceSubset(0, 500)); + testStrided(4500, 500, 4, sourceSubset(4500, 500)); + testStrided(2000, 1000, 4, sourceSubset(2000, 1000)); + + // check for windowed sinc values between the original + // samples, even when the original sample that was the source + // of this sinc kernel is not within the requested range + floatvec_t output = get(1, 10, 4); + QVERIFY(output[0] < 0.0001); + QVERIFY(output[1] + 0.1787 < 0.0001); + QVERIFY(output[2] + 0.2099 < 0.0001); + QVERIFY(output[3] + 0.1267 < 0.0001); + + // and again at the end + output = get(4989, 10, 4); + QVERIFY(output[39] + 0.9000 < 0.0001); + QVERIFY(output[38] + 0.6358 < 0.0001); + QVERIFY(output[37] + 0.2993 < 0.0001); + QVERIFY(output[35] - 0.1787 < 0.0001); + QVERIFY(output[34] - 0.2099 < 0.0001); + QVERIFY(output[33] - 0.1267 < 0.0001); + } + + void testOverlaps4x() { + // overlapping the start -> result should be zero-padded to + // preserve start frame + floatvec_t expected = sourceSubset(0, 400); + expected.insert(expected.begin(), 100, 0.f); + testStrided(-100, 500, 4, expected); + + // overlapping the end -> result should be truncated to + // preserve source length + expected = sourceSubset(4600, 400); + testStrided(4600, 500, 4, expected); + } + + void testSubsets15x() { + testStrided(0, 500, 15, sourceSubset(0, 500)); + testStrided(4500, 500, 15, sourceSubset(4500, 500)); + testStrided(2000, 1000, 15, sourceSubset(2000, 1000)); + } + + void testOverlaps15x() { + // overlapping the start -> result should be zero-padded to + // preserve start frame + floatvec_t expected = sourceSubset(0, 400); + expected.insert(expected.begin(), 100, 0.f); + testStrided(-100, 500, 15, expected); + + // overlapping the end -> result should be truncated to + // preserve source length + expected = sourceSubset(4600, 400); + testStrided(4600, 500, 15, expected); + } +}; + + +#endif diff -r 51d6551d5244 -r 2fec0d9bd7ac data/model/test/TestZoomConstraints.h --- a/data/model/test/TestZoomConstraints.h Wed Oct 03 15:45:57 2018 +0100 +++ b/data/model/test/TestZoomConstraints.h Fri Oct 05 10:25:25 2018 +0100 @@ -1,15 +1,15 @@ /* -*- 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. + 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. + 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 TEST_ZOOM_CONSTRAINTS_H @@ -30,149 +30,171 @@ { Q_OBJECT + void checkFpp(const ZoomConstraint &c, + ZoomConstraint::RoundingDirection dir, + int n, + int expected) { + QCOMPARE(c.getNearestZoomLevel(ZoomLevel(ZoomLevel::FramesPerPixel, n), + dir), + ZoomLevel(ZoomLevel::FramesPerPixel, expected)); + } + private slots: void unconstrainedNearest() { ZoomConstraint c; - QCOMPARE(c.getNearestBlockSize(1), 1); - QCOMPARE(c.getNearestBlockSize(2), 2); - QCOMPARE(c.getNearestBlockSize(3), 3); - QCOMPARE(c.getNearestBlockSize(4), 4); - QCOMPARE(c.getNearestBlockSize(20), 20); - QCOMPARE(c.getNearestBlockSize(23), 23); - int max = c.getMaxZoomLevel(); - QCOMPARE(c.getNearestBlockSize(max), max); - QCOMPARE(c.getNearestBlockSize(max+1), max); + checkFpp(c, ZoomConstraint::RoundNearest, 1, 1); + checkFpp(c, ZoomConstraint::RoundNearest, 2, 2); + checkFpp(c, ZoomConstraint::RoundNearest, 3, 3); + checkFpp(c, ZoomConstraint::RoundNearest, 4, 4); + checkFpp(c, ZoomConstraint::RoundNearest, 20, 20); + checkFpp(c, ZoomConstraint::RoundNearest, 32, 32); + auto max = c.getMaxZoomLevel(); + QCOMPARE(c.getNearestZoomLevel(max), max); + QCOMPARE(c.getNearestZoomLevel(max.incremented()), max); } void unconstrainedUp() { ZoomConstraint c; - QCOMPARE(c.getNearestBlockSize(1, ZoomConstraint::RoundUp), 1); - QCOMPARE(c.getNearestBlockSize(2, ZoomConstraint::RoundUp), 2); - QCOMPARE(c.getNearestBlockSize(3, ZoomConstraint::RoundUp), 3); - QCOMPARE(c.getNearestBlockSize(4, ZoomConstraint::RoundUp), 4); - QCOMPARE(c.getNearestBlockSize(20, ZoomConstraint::RoundUp), 20); - QCOMPARE(c.getNearestBlockSize(32, ZoomConstraint::RoundUp), 32); - int max = c.getMaxZoomLevel(); - QCOMPARE(c.getNearestBlockSize(max, ZoomConstraint::RoundUp), max); - QCOMPARE(c.getNearestBlockSize(max+1, ZoomConstraint::RoundUp), max); + checkFpp(c, ZoomConstraint::RoundUp, 1, 1); + checkFpp(c, ZoomConstraint::RoundUp, 2, 2); + checkFpp(c, ZoomConstraint::RoundUp, 3, 3); + checkFpp(c, ZoomConstraint::RoundUp, 4, 4); + checkFpp(c, ZoomConstraint::RoundUp, 20, 20); + checkFpp(c, ZoomConstraint::RoundUp, 32, 32); + auto max = c.getMaxZoomLevel(); + QCOMPARE(c.getNearestZoomLevel(max, + ZoomConstraint::RoundUp), max); + QCOMPARE(c.getNearestZoomLevel(max.incremented(), + ZoomConstraint::RoundUp), max); } void unconstrainedDown() { ZoomConstraint c; - QCOMPARE(c.getNearestBlockSize(1, ZoomConstraint::RoundDown), 1); - QCOMPARE(c.getNearestBlockSize(2, ZoomConstraint::RoundDown), 2); - QCOMPARE(c.getNearestBlockSize(3, ZoomConstraint::RoundDown), 3); - QCOMPARE(c.getNearestBlockSize(4, ZoomConstraint::RoundDown), 4); - QCOMPARE(c.getNearestBlockSize(20, ZoomConstraint::RoundDown), 20); - QCOMPARE(c.getNearestBlockSize(32, ZoomConstraint::RoundDown), 32); - int max = c.getMaxZoomLevel(); - QCOMPARE(c.getNearestBlockSize(max, ZoomConstraint::RoundDown), max); - QCOMPARE(c.getNearestBlockSize(max+1, ZoomConstraint::RoundDown), max); + checkFpp(c, ZoomConstraint::RoundDown, 1, 1); + checkFpp(c, ZoomConstraint::RoundDown, 2, 2); + checkFpp(c, ZoomConstraint::RoundDown, 3, 3); + checkFpp(c, ZoomConstraint::RoundDown, 4, 4); + checkFpp(c, ZoomConstraint::RoundDown, 20, 20); + checkFpp(c, ZoomConstraint::RoundDown, 32, 32); + auto max = c.getMaxZoomLevel(); + QCOMPARE(c.getNearestZoomLevel(max, + ZoomConstraint::RoundDown), max); + QCOMPARE(c.getNearestZoomLevel(max.incremented(), + ZoomConstraint::RoundDown), max); } void powerOfTwoNearest() { PowerOfTwoZoomConstraint c; - QCOMPARE(c.getNearestBlockSize(1), 1); - QCOMPARE(c.getNearestBlockSize(2), 2); - QCOMPARE(c.getNearestBlockSize(3), 2); - QCOMPARE(c.getNearestBlockSize(4), 4); - QCOMPARE(c.getNearestBlockSize(20), 16); - QCOMPARE(c.getNearestBlockSize(23), 16); - QCOMPARE(c.getNearestBlockSize(24), 16); - QCOMPARE(c.getNearestBlockSize(25), 32); - int max = c.getMaxZoomLevel(); - QCOMPARE(c.getNearestBlockSize(max), max); - QCOMPARE(c.getNearestBlockSize(max+1), max); + checkFpp(c, ZoomConstraint::RoundNearest, 1, 1); + checkFpp(c, ZoomConstraint::RoundNearest, 2, 2); + checkFpp(c, ZoomConstraint::RoundNearest, 3, 2); + checkFpp(c, ZoomConstraint::RoundNearest, 4, 4); + checkFpp(c, ZoomConstraint::RoundNearest, 20, 16); + checkFpp(c, ZoomConstraint::RoundNearest, 23, 16); + checkFpp(c, ZoomConstraint::RoundNearest, 24, 16); + checkFpp(c, ZoomConstraint::RoundNearest, 25, 32); + auto max = c.getMaxZoomLevel(); + QCOMPARE(c.getNearestZoomLevel(max), max); + QCOMPARE(c.getNearestZoomLevel(max.incremented()), max); } void powerOfTwoUp() { PowerOfTwoZoomConstraint c; - QCOMPARE(c.getNearestBlockSize(1, ZoomConstraint::RoundUp), 1); - QCOMPARE(c.getNearestBlockSize(2, ZoomConstraint::RoundUp), 2); - QCOMPARE(c.getNearestBlockSize(3, ZoomConstraint::RoundUp), 4); - QCOMPARE(c.getNearestBlockSize(4, ZoomConstraint::RoundUp), 4); - QCOMPARE(c.getNearestBlockSize(20, ZoomConstraint::RoundUp), 32); - QCOMPARE(c.getNearestBlockSize(32, ZoomConstraint::RoundUp), 32); - QCOMPARE(c.getNearestBlockSize(33, ZoomConstraint::RoundUp), 64); - int max = c.getMaxZoomLevel(); - QCOMPARE(c.getNearestBlockSize(max, ZoomConstraint::RoundUp), max); - QCOMPARE(c.getNearestBlockSize(max+1, ZoomConstraint::RoundUp), max); + checkFpp(c, ZoomConstraint::RoundUp, 1, 1); + checkFpp(c, ZoomConstraint::RoundUp, 2, 2); + checkFpp(c, ZoomConstraint::RoundUp, 3, 4); + checkFpp(c, ZoomConstraint::RoundUp, 4, 4); + checkFpp(c, ZoomConstraint::RoundUp, 20, 32); + checkFpp(c, ZoomConstraint::RoundUp, 32, 32); + checkFpp(c, ZoomConstraint::RoundUp, 33, 64); + auto max = c.getMaxZoomLevel(); + QCOMPARE(c.getNearestZoomLevel(max, + ZoomConstraint::RoundUp), max); + QCOMPARE(c.getNearestZoomLevel(max.incremented(), + ZoomConstraint::RoundUp), max); } void powerOfTwoDown() { PowerOfTwoZoomConstraint c; - QCOMPARE(c.getNearestBlockSize(1, ZoomConstraint::RoundDown), 1); - QCOMPARE(c.getNearestBlockSize(2, ZoomConstraint::RoundDown), 2); - QCOMPARE(c.getNearestBlockSize(3, ZoomConstraint::RoundDown), 2); - QCOMPARE(c.getNearestBlockSize(4, ZoomConstraint::RoundDown), 4); - QCOMPARE(c.getNearestBlockSize(20, ZoomConstraint::RoundDown), 16); - QCOMPARE(c.getNearestBlockSize(32, ZoomConstraint::RoundDown), 32); - QCOMPARE(c.getNearestBlockSize(33, ZoomConstraint::RoundDown), 32); - int max = c.getMaxZoomLevel(); - QCOMPARE(c.getNearestBlockSize(max, ZoomConstraint::RoundDown), max); - QCOMPARE(c.getNearestBlockSize(max+1, ZoomConstraint::RoundDown), max); + checkFpp(c, ZoomConstraint::RoundDown, 1, 1); + checkFpp(c, ZoomConstraint::RoundDown, 2, 2); + checkFpp(c, ZoomConstraint::RoundDown, 3, 2); + checkFpp(c, ZoomConstraint::RoundDown, 4, 4); + checkFpp(c, ZoomConstraint::RoundDown, 20, 16); + checkFpp(c, ZoomConstraint::RoundDown, 32, 32); + checkFpp(c, ZoomConstraint::RoundDown, 33, 32); + auto max = c.getMaxZoomLevel(); + QCOMPARE(c.getNearestZoomLevel(max, + ZoomConstraint::RoundDown), max); + QCOMPARE(c.getNearestZoomLevel(max.incremented(), + ZoomConstraint::RoundDown), max); } void powerOfSqrtTwoNearest() { PowerOfSqrtTwoZoomConstraint c; - QCOMPARE(c.getNearestBlockSize(1), 1); - QCOMPARE(c.getNearestBlockSize(2), 2); - QCOMPARE(c.getNearestBlockSize(3), 2); - QCOMPARE(c.getNearestBlockSize(4), 4); - QCOMPARE(c.getNearestBlockSize(18), 16); - QCOMPARE(c.getNearestBlockSize(19), 16); - QCOMPARE(c.getNearestBlockSize(20), 22); - QCOMPARE(c.getNearestBlockSize(23), 22); - QCOMPARE(c.getNearestBlockSize(28), 32); + checkFpp(c, ZoomConstraint::RoundNearest, 1, 1); + checkFpp(c, ZoomConstraint::RoundNearest, 2, 2); + checkFpp(c, ZoomConstraint::RoundNearest, 3, 2); + checkFpp(c, ZoomConstraint::RoundNearest, 4, 4); + checkFpp(c, ZoomConstraint::RoundNearest, 18, 16); + checkFpp(c, ZoomConstraint::RoundNearest, 19, 16); + checkFpp(c, ZoomConstraint::RoundNearest, 20, 22); + checkFpp(c, ZoomConstraint::RoundNearest, 23, 22); + checkFpp(c, ZoomConstraint::RoundNearest, 28, 32); // PowerOfSqrtTwoZoomConstraint makes an effort to ensure // bigger numbers get rounded to a multiple of something // simple (64 or 90 depending on whether they are power-of-two // or power-of-sqrt-two types) - QCOMPARE(c.getNearestBlockSize(800), 720); - QCOMPARE(c.getNearestBlockSize(1023), 1024); - QCOMPARE(c.getNearestBlockSize(1024), 1024); - QCOMPARE(c.getNearestBlockSize(1025), 1024); - int max = c.getMaxZoomLevel(); - QCOMPARE(c.getNearestBlockSize(max), max); - QCOMPARE(c.getNearestBlockSize(max+1), max); + checkFpp(c, ZoomConstraint::RoundNearest, 800, 720); + checkFpp(c, ZoomConstraint::RoundNearest, 1023, 1024); + checkFpp(c, ZoomConstraint::RoundNearest, 1024, 1024); + checkFpp(c, ZoomConstraint::RoundNearest, 1024, 1024); + checkFpp(c, ZoomConstraint::RoundNearest, 1025, 1024); + auto max = c.getMaxZoomLevel(); + QCOMPARE(c.getNearestZoomLevel(max), max); + QCOMPARE(c.getNearestZoomLevel(max.incremented()), max); } void powerOfSqrtTwoUp() { PowerOfSqrtTwoZoomConstraint c; - QCOMPARE(c.getNearestBlockSize(1, ZoomConstraint::RoundUp), 1); - QCOMPARE(c.getNearestBlockSize(2, ZoomConstraint::RoundUp), 2); - QCOMPARE(c.getNearestBlockSize(3, ZoomConstraint::RoundUp), 4); - QCOMPARE(c.getNearestBlockSize(4, ZoomConstraint::RoundUp), 4); - QCOMPARE(c.getNearestBlockSize(18, ZoomConstraint::RoundUp), 22); - QCOMPARE(c.getNearestBlockSize(22, ZoomConstraint::RoundUp), 22); - QCOMPARE(c.getNearestBlockSize(23, ZoomConstraint::RoundUp), 32); - QCOMPARE(c.getNearestBlockSize(800, ZoomConstraint::RoundUp), 1024); - QCOMPARE(c.getNearestBlockSize(1023, ZoomConstraint::RoundUp), 1024); - QCOMPARE(c.getNearestBlockSize(1024, ZoomConstraint::RoundUp), 1024); + checkFpp(c, ZoomConstraint::RoundUp, 1, 1); + checkFpp(c, ZoomConstraint::RoundUp, 2, 2); + checkFpp(c, ZoomConstraint::RoundUp, 3, 4); + checkFpp(c, ZoomConstraint::RoundUp, 4, 4); + checkFpp(c, ZoomConstraint::RoundUp, 18, 22); + checkFpp(c, ZoomConstraint::RoundUp, 22, 22); + checkFpp(c, ZoomConstraint::RoundUp, 23, 32); + checkFpp(c, ZoomConstraint::RoundUp, 800, 1024); + checkFpp(c, ZoomConstraint::RoundUp, 1023, 1024); + checkFpp(c, ZoomConstraint::RoundUp, 1024, 1024); // see comment above - QCOMPARE(c.getNearestBlockSize(1025, ZoomConstraint::RoundUp), 1440); - int max = c.getMaxZoomLevel(); - QCOMPARE(c.getNearestBlockSize(max, ZoomConstraint::RoundUp), max); - QCOMPARE(c.getNearestBlockSize(max+1, ZoomConstraint::RoundUp), max); + checkFpp(c, ZoomConstraint::RoundUp, 1025, 1440); + auto max = c.getMaxZoomLevel(); + QCOMPARE(c.getNearestZoomLevel(max, + ZoomConstraint::RoundUp), max); + QCOMPARE(c.getNearestZoomLevel(max.incremented(), + ZoomConstraint::RoundUp), max); } void powerOfSqrtTwoDown() { PowerOfSqrtTwoZoomConstraint c; - QCOMPARE(c.getNearestBlockSize(1, ZoomConstraint::RoundDown), 1); - QCOMPARE(c.getNearestBlockSize(2, ZoomConstraint::RoundDown), 2); - QCOMPARE(c.getNearestBlockSize(3, ZoomConstraint::RoundDown), 2); - QCOMPARE(c.getNearestBlockSize(4, ZoomConstraint::RoundDown), 4); - QCOMPARE(c.getNearestBlockSize(18, ZoomConstraint::RoundDown), 16); - QCOMPARE(c.getNearestBlockSize(22, ZoomConstraint::RoundDown), 22); - QCOMPARE(c.getNearestBlockSize(23, ZoomConstraint::RoundDown), 22); + checkFpp(c, ZoomConstraint::RoundDown, 1, 1); + checkFpp(c, ZoomConstraint::RoundDown, 2, 2); + checkFpp(c, ZoomConstraint::RoundDown, 3, 2); + checkFpp(c, ZoomConstraint::RoundDown, 4, 4); + checkFpp(c, ZoomConstraint::RoundDown, 18, 16); + checkFpp(c, ZoomConstraint::RoundDown, 22, 22); + checkFpp(c, ZoomConstraint::RoundDown, 23, 22); // see comment above - QCOMPARE(c.getNearestBlockSize(800, ZoomConstraint::RoundDown), 720); - QCOMPARE(c.getNearestBlockSize(1023, ZoomConstraint::RoundDown), 720); - QCOMPARE(c.getNearestBlockSize(1024, ZoomConstraint::RoundDown), 1024); - QCOMPARE(c.getNearestBlockSize(1025, ZoomConstraint::RoundDown), 1024); - int max = c.getMaxZoomLevel(); - QCOMPARE(c.getNearestBlockSize(max, ZoomConstraint::RoundDown), max); - QCOMPARE(c.getNearestBlockSize(max+1, ZoomConstraint::RoundDown), max); + checkFpp(c, ZoomConstraint::RoundDown, 800, 720); + checkFpp(c, ZoomConstraint::RoundDown, 1023, 720); + checkFpp(c, ZoomConstraint::RoundDown, 1024, 1024); + checkFpp(c, ZoomConstraint::RoundDown, 1025, 1024); + auto max = c.getMaxZoomLevel(); + QCOMPARE(c.getNearestZoomLevel(max, + ZoomConstraint::RoundDown), max); + QCOMPARE(c.getNearestZoomLevel(max.incremented(), + ZoomConstraint::RoundDown), max); } }; diff -r 51d6551d5244 -r 2fec0d9bd7ac data/model/test/files.pri --- a/data/model/test/files.pri Wed Oct 03 15:45:57 2018 +0100 +++ b/data/model/test/files.pri Fri Oct 05 10:25:25 2018 +0100 @@ -2,6 +2,7 @@ Compares.h \ MockWaveModel.h \ TestFFTModel.h \ + TestWaveformOversampler.h \ TestZoomConstraints.h TEST_SOURCES += \ diff -r 51d6551d5244 -r 2fec0d9bd7ac data/model/test/svcore-data-model-test.cpp --- a/data/model/test/svcore-data-model-test.cpp Wed Oct 03 15:45:57 2018 +0100 +++ b/data/model/test/svcore-data-model-test.cpp Fri Oct 05 10:25:25 2018 +0100 @@ -13,6 +13,7 @@ #include "TestFFTModel.h" #include "TestZoomConstraints.h" +#include "TestWaveformOversampler.h" #include @@ -40,6 +41,12 @@ else ++bad; } + { + TestWaveformOversampler t; + if (QTest::qExec(&t, argc, argv) == 0) ++good; + else ++bad; + } + if (bad > 0) { SVCERR << "\n********* " << bad << " test suite(s) failed!\n" << endl; return 1; diff -r 51d6551d5244 -r 2fec0d9bd7ac files.pri --- a/files.pri Wed Oct 03 15:45:57 2018 +0100 +++ b/files.pri Fri Oct 05 10:25:25 2018 +0100 @@ -43,6 +43,7 @@ base/Window.h \ base/XmlExportable.h \ base/ZoomConstraint.h \ + base/ZoomLevel.h \ data/fileio/AudioFileReader.h \ data/fileio/AudioFileReaderFactory.h \ data/fileio/AudioFileSizeEstimator.h \ @@ -95,6 +96,7 @@ data/model/SparseValueModel.h \ data/model/TabularModel.h \ data/model/TextModel.h \ + data/model/WaveformOversampler.h \ data/model/WaveFileModel.h \ data/model/ReadOnlyWaveFileModel.h \ data/model/WritableWaveFileModel.h \ @@ -175,6 +177,7 @@ base/UnitDatabase.cpp \ base/ViewManagerBase.cpp \ base/XmlExportable.cpp \ + base/ZoomLevel.cpp \ data/fileio/AudioFileReader.cpp \ data/fileio/AudioFileReaderFactory.cpp \ data/fileio/AudioFileSizeEstimator.cpp \ @@ -209,6 +212,7 @@ data/model/PowerOfSqrtTwoZoomConstraint.cpp \ data/model/PowerOfTwoZoomConstraint.cpp \ data/model/RangeSummarisableTimeValueModel.cpp \ + data/model/WaveformOversampler.cpp \ data/model/WaveFileModel.cpp \ data/model/ReadOnlyWaveFileModel.cpp \ data/model/WritableWaveFileModel.cpp \