changeset 1564:5f9c9d8c3de6 zoom

Merge from default branch
author Chris Cannam
date Tue, 06 Nov 2018 08:58:57 +0000
parents 2fec0d9bd7ac (current diff) 175ef02c7864 (diff)
children 2a06e4bc1c41
files
diffstat 17 files changed, 546 insertions(+), 160 deletions(-) [+]
line wrap: on
line diff
--- a/base/Debug.cpp	Fri Oct 05 10:25:25 2018 +0100
+++ b/base/Debug.cpp	Tue Nov 06 08:58:57 2018 +0000
@@ -20,6 +20,7 @@
 #include <QDir>
 #include <QUrl>
 #include <QCoreApplication>
+#include <QDateTime>
 
 #include <stdexcept>
 
@@ -82,8 +83,9 @@
                              << "Failed to open debug log file "
                              << fileName << " for writing";
     } else {
-//        cerr << m_prefix << ": Log file is " << fileName << endl;
         m_ok = true;
+        (*this) << "Debug log started at "
+                << QDateTime::currentDateTime().toString() << endl;
     }
 }
 
--- a/base/RingBuffer.h	Fri Oct 05 10:25:25 2018 +0100
+++ b/base/RingBuffer.h	Tue Nov 06 08:58:57 2018 +0000
@@ -18,13 +18,15 @@
    This file copyright 2000-2006 Chris Cannam.
 */
 
-#ifndef _RINGBUFFER_H_
-#define _RINGBUFFER_H_
+#ifndef SV_RINGBUFFER_H
+#define SV_RINGBUFFER_H
 
 #include <sys/types.h>
 
 #include "system/System.h"
 
+#include <bqvec/Barrier.h>
+
 #include <cstring> // memcpy, memset &c
 
 //#define DEBUG_RINGBUFFER 1
@@ -339,7 +341,7 @@
         memcpy(destination + here, m_buffer, (n - here) * sizeof(T));
     }
 
-    MBARRIER();
+    BQ_MBARRIER();
     m_readers[R] = (m_readers[R] + n) % m_size;
 
 #ifdef DEBUG_RINGBUFFER
@@ -382,7 +384,7 @@
         }
     }
 
-    MBARRIER();
+    BQ_MBARRIER();
     m_readers[R] = (m_readers[R] + n) % m_size;
     return n;
 }
@@ -405,7 +407,7 @@
         return t;
     }
     T value = m_buffer[m_readers[R]];
-    MBARRIER();
+    BQ_MBARRIER();
     if (++m_readers[R] == m_size) m_readers[R] = 0;
     return value;
 }
@@ -512,7 +514,7 @@
         memcpy(m_buffer, source + here, (n - here) * sizeof(T));
     }
 
-    MBARRIER();
+    BQ_MBARRIER();
     m_writer = (m_writer + n) % m_size;
 
 #ifdef DEBUG_RINGBUFFER
@@ -548,7 +550,7 @@
         memset(m_buffer, 0, (n - here) * sizeof(T));
     }
     
-    MBARRIER();
+    BQ_MBARRIER();
     m_writer = (m_writer + n) % m_size;
     return n;
 }
--- a/base/ZoomConstraint.h	Fri Oct 05 10:25:25 2018 +0100
+++ b/base/ZoomConstraint.h	Tue Nov 06 08:58:57 2018 +0000
@@ -54,6 +54,10 @@
                                           RoundingDirection = RoundNearest)
         const
     {
+        // canonicalise
+        if (requestedZoomLevel.level == 1) {
+            requestedZoomLevel.zone = ZoomLevel::FramesPerPixel;
+        }
         if (getMaxZoomLevel() < requestedZoomLevel) return getMaxZoomLevel();
 	else return requestedZoomLevel;
     }
--- a/data/model/AggregateWaveModel.cpp	Fri Oct 05 10:25:25 2018 +0100
+++ b/data/model/AggregateWaveModel.cpp	Tue Nov 06 08:58:57 2018 +0000
@@ -21,6 +21,8 @@
 
 using namespace std;
 
+//#define DEBUG_AGGREGATE_WAVE_FILE_MODEL 1
+
 PowerOfSqrtTwoZoomConstraint
 AggregateWaveModel::m_zoomConstraint;
 
@@ -80,11 +82,19 @@
     for (ChannelSpecList::const_iterator i = m_components.begin();
          i != m_components.end(); ++i) {
         int completionHere = 100;
-        if (!i->model->isReady(&completionHere)) ready = false;
+        if (!i->model->isReady(&completionHere)) {
+            ready = false;
+        }
         if (completion && completionHere < *completion) {
             *completion = completionHere;
         }
     }
+
+#ifdef DEBUG_AGGREGATE_WAVE_FILE_MODEL
+    SVDEBUG << "AggregateWaveModel(" << objectName()
+            << ")::isReady: returning " << ready << endl;
+#endif
+    
     return ready;
 }
 
--- a/data/model/AlignmentModel.cpp	Fri Oct 05 10:25:25 2018 +0100
+++ b/data/model/AlignmentModel.cpp	Tue Nov 06 08:58:57 2018 +0000
@@ -101,10 +101,16 @@
 {
     if (!m_pathBegun && m_rawPath) {
         if (completion) *completion = 0;
+#ifdef DEBUG_ALIGNMENT_MODEL
+        SVDEBUG << "AlignmentModel::isReady: path not begun" << endl;
+#endif
         return false;
     }
     if (m_pathComplete) {
         if (completion) *completion = 100;
+#ifdef DEBUG_ALIGNMENT_MODEL
+        SVDEBUG << "AlignmentModel::isReady: path complete" << endl;
+#endif
         return true;
     }
     if (!m_rawPath) {
@@ -112,6 +118,9 @@
         // m_pathComplete true above) or else no alignment has been
         // set at all yet (this case)
         if (completion) *completion = 0;
+#ifdef DEBUG_ALIGNMENT_MODEL
+        SVDEBUG << "AlignmentModel::isReady: no raw path" << endl;
+#endif
         return false;
     }
     return m_rawPath->isReady(completion);
@@ -381,6 +390,7 @@
     if (m_path) m_path->aboutToDelete();
     delete m_path;
     m_path = path;
+    m_pathComplete = true;
 #ifdef DEBUG_ALIGNMENT_MODEL
     cerr << "AlignmentModel::setPath: path = " << m_path << endl;
 #endif
--- a/data/model/Model.cpp	Fri Oct 05 10:25:25 2018 +0100
+++ b/data/model/Model.cpp	Tue Nov 06 08:58:57 2018 +0000
@@ -161,14 +161,15 @@
 int
 Model::getAlignmentCompletion() const
 {
-//    SVDEBUG << "Model::getAlignmentCompletion" << endl;
+//    SVDEBUG << "Model::getAlignmentCompletion: m_alignment = "
+//            << m_alignment << endl;
     if (!m_alignment) {
         if (m_sourceModel) return m_sourceModel->getAlignmentCompletion();
         else return 100;
     }
     int completion = 0;
     (void)m_alignment->isReady(&completion);
-//    cerr << " -> " << completion << endl;
+//    SVDEBUG << " -> " << completion << endl;
     return completion;
 }
 
--- a/data/model/PowerOfSqrtTwoZoomConstraint.cpp	Fri Oct 05 10:25:25 2018 +0100
+++ b/data/model/PowerOfSqrtTwoZoomConstraint.cpp	Tue Nov 06 08:58:57 2018 +0000
@@ -103,6 +103,7 @@
 
         if (base == blockSize) {
             result = base;
+//            SVCERR << "Equal, accepting" << endl;
             break;
         }
 
@@ -110,8 +111,12 @@
             if (dir == RoundNearest) {
                 if (base - blockSize < blockSize - prevBase) {
                     dir = RoundUp;
+//                    SVCERR << "Closer to " << base << " than " << prevBase
+//                           << ", rounding up" << endl;
                 } else {
                     dir = RoundDown;
+//                    SVCERR << "Closer to " << prevBase << " than " << base
+//                           << ", rounding down" << endl;
                 }
             }
             if (dir == RoundUp) {
@@ -134,5 +139,7 @@
         result = getMaxZoomLevel().level;
     }
 
+//    SVCERR << "Returning result " << result << endl;
+
     return result;
 }   
--- a/data/model/ReadOnlyWaveFileModel.cpp	Fri Oct 05 10:25:25 2018 +0100
+++ b/data/model/ReadOnlyWaveFileModel.cpp	Tue Nov 06 08:58:57 2018 +0000
@@ -48,10 +48,14 @@
     m_fillThread(0),
     m_updateTimer(0),
     m_lastFillExtent(0),
+    m_prevCompletion(0),
     m_exiting(false),
     m_lastDirectReadStart(0),
     m_lastDirectReadCount(0)
 {
+    SVDEBUG << "ReadOnlyWaveFileModel::ReadOnlyWaveFileModel: path "
+            << m_path << ", target rate " << targetRate << endl;
+    
     m_source.waitForData();
 
     if (m_source.isOK()) {
@@ -77,7 +81,7 @@
                       << m_reader->getSampleRate() << endl;
         }
     }
-    
+
     if (m_reader) setObjectName(m_reader->getTitle());
     if (objectName() == "") setObjectName(QFileInfo(m_path).fileName());
     if (isOK()) fillCache();
@@ -92,8 +96,12 @@
     m_fillThread(0),
     m_updateTimer(0),
     m_lastFillExtent(0),
+    m_prevCompletion(0),
     m_exiting(false)
 {
+    SVDEBUG << "ReadOnlyWaveFileModel::ReadOnlyWaveFileModel: path "
+            << m_path << ", with reader" << endl;
+    
     m_reader = reader;
     if (m_reader) setObjectName(m_reader->getTitle());
     if (objectName() == "") setObjectName(QFileInfo(m_path).fileName());
@@ -121,9 +129,14 @@
 bool
 ReadOnlyWaveFileModel::isReady(int *completion) const
 {
-    bool ready = (isOK() && (m_fillThread == 0));
-    double c = double(m_lastFillExtent) / double(getEndFrame() - getStartFrame());
-    static int prevCompletion = 0;
+    bool ready = true;
+    if (!isOK()) ready = false;
+    if (m_fillThread) ready = false;
+    if (m_reader && m_reader->isUpdating()) ready = false;
+
+    double c = double(m_lastFillExtent) /
+        double(getEndFrame() - getStartFrame());
+
     if (completion) {
         *completion = int(c * 100.0 + 0.01);
         if (m_reader) {
@@ -133,15 +146,20 @@
         }
         if (*completion != 0 &&
             *completion != 100 &&
-            prevCompletion != 0 &&
-            prevCompletion > *completion) {
+            m_prevCompletion != 0 &&
+            m_prevCompletion > *completion) {
             // just to avoid completion going backwards
-            *completion = prevCompletion;
+            *completion = m_prevCompletion;
         }
-        prevCompletion = *completion;
+        m_prevCompletion = *completion;
     }
+    
 #ifdef DEBUG_WAVE_FILE_MODEL
-    SVDEBUG << "ReadOnlyWaveFileModel::isReady(): ready = " << ready << ", completion = " << (completion ? *completion : -1) << endl;
+    if (completion) {
+        SVDEBUG << "ReadOnlyWaveFileModel(" << objectName() << ")::isReady(): ready = " << ready << ", m_fillThread = " << m_fillThread << ", m_lastFillExtent = " << m_lastFillExtent << ", end frame = " << getEndFrame() << ", start frame = " << getStartFrame() << ", c = " << c << ", completion = " << *completion << endl;
+    } else {
+        SVDEBUG << "ReadOnlyWaveFileModel(" << objectName() << ")::isReady(): ready = " << ready << ", m_fillThread = " << m_fillThread << ", m_lastFillExtent = " << m_lastFillExtent << ", end frame = " << getEndFrame() << ", start frame = " << getStartFrame() << ", c = " << c << ", completion not requested" << endl;
+    }
 #endif
     return ready;
 }
@@ -550,7 +568,7 @@
     m_fillThread->start();
 
 #ifdef DEBUG_WAVE_FILE_MODEL
-    SVDEBUG << "ReadOnlyWaveFileModel::fillCache: started fill thread" << endl;
+    SVDEBUG << "ReadOnlyWaveFileModel(" << objectName() << ")::fillCache: started fill thread" << endl;
 #endif
 }   
 
@@ -560,7 +578,7 @@
     if (m_fillThread) {
         sv_frame_t fillExtent = m_fillThread->getFillExtent();
 #ifdef DEBUG_WAVE_FILE_MODEL
-        SVDEBUG << "ReadOnlyWaveFileModel::fillTimerTimedOut: extent = " << fillExtent << endl;
+        SVDEBUG << "ReadOnlyWaveFileModel(" << objectName() << ")::fillTimerTimedOut: extent = " << fillExtent << endl;
 #endif
         if (fillExtent > m_lastFillExtent) {
             emit modelChangedWithin(m_lastFillExtent, fillExtent);
@@ -568,7 +586,7 @@
         }
     } else {
 #ifdef DEBUG_WAVE_FILE_MODEL
-        SVDEBUG << "ReadOnlyWaveFileModel::fillTimerTimedOut: no thread" << endl;
+        SVDEBUG << "ReadOnlyWaveFileModel(" << objectName() << ")::fillTimerTimedOut: no thread" << endl;
 #endif
         emit modelChanged();
     }
@@ -582,15 +600,17 @@
     m_fillThread = 0;
     delete m_updateTimer;
     m_updateTimer = 0;
+    auto prevFillExtent = m_lastFillExtent;
+    m_lastFillExtent = getEndFrame();
     m_mutex.unlock();
-    if (getEndFrame() > m_lastFillExtent) {
-        emit modelChangedWithin(m_lastFillExtent, getEndFrame());
+#ifdef DEBUG_WAVE_FILE_MODEL
+    SVDEBUG << "ReadOnlyWaveFileModel(" << objectName() << ")::cacheFilled, about to emit things" << endl;
+#endif
+    if (getEndFrame() > prevFillExtent) {
+        emit modelChangedWithin(prevFillExtent, getEndFrame());
     }
     emit modelChanged();
     emit ready();
-#ifdef DEBUG_WAVE_FILE_MODEL
-    SVDEBUG << "ReadOnlyWaveFileModel::cacheFilled" << endl;
-#endif
 }
 
 void
@@ -613,7 +633,7 @@
     if (updating) {
         while (channels == 0 && !m_model.m_exiting) {
 #ifdef DEBUG_WAVE_FILE_MODEL
-            cerr << "ReadOnlyWaveFileModel::fill: Waiting for channels..." << endl;
+            cerr << "ReadOnlyWaveFileModel(" << objectName() << ")::fill: Waiting for channels..." << endl;
 #endif
             sleep(1);
             channels = m_model.getChannelCount();
@@ -642,7 +662,7 @@
             m_model.m_mutex.unlock();
 
 #ifdef DEBUG_WAVE_FILE_MODEL
-            cerr << "ReadOnlyWaveFileModel::fill inner loop: frame = " << frame << ", count = " << m_frameCount << ", blocksize " << readBlockSize << endl;
+            SVDEBUG << "ReadOnlyWaveFileModel(" << m_model.objectName() << ")::fill inner loop: frame = " << frame << ", count = " << m_frameCount << ", blocksize " << readBlockSize << endl;
 #endif
 
             if (updating && (frame + readBlockSize > m_frameCount)) {
@@ -735,7 +755,7 @@
 
 #ifdef DEBUG_WAVE_FILE_MODEL        
     for (int cacheType = 0; cacheType < 2; ++cacheType) {
-        cerr << "Cache type " << cacheType << " now contains " << m_model.m_cache[cacheType].size() << " ranges" << endl;
+        SVDEBUG << "ReadOnlyWaveFileModel(" << m_model.objectName() << "): Cache type " << cacheType << " now contains " << m_model.m_cache[cacheType].size() << " ranges" << endl;
     }
 #endif
 }
--- a/data/model/ReadOnlyWaveFileModel.h	Fri Oct 05 10:25:25 2018 +0100
+++ b/data/model/ReadOnlyWaveFileModel.h	Tue Nov 06 08:58:57 2018 +0000
@@ -131,6 +131,7 @@
     RangeCacheFillThread *m_fillThread;
     QTimer *m_updateTimer;
     sv_frame_t m_lastFillExtent;
+    mutable int m_prevCompletion;
     bool m_exiting;
     static PowerOfSqrtTwoZoomConstraint m_zoomConstraint;
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/model/RelativelyFineZoomConstraint.cpp	Tue Nov 06 08:58:57 2018 +0000
@@ -0,0 +1,101 @@
+/* -*- 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 "RelativelyFineZoomConstraint.h"
+
+#include <vector>
+#include <algorithm>
+#include <iostream>
+
+using namespace std;
+
+ZoomLevel
+RelativelyFineZoomConstraint::getNearestZoomLevel(ZoomLevel requested,
+                                                  RoundingDirection dir) const
+{
+    static vector<int> levels;
+
+    int maxLevel = getMaxZoomLevel().level;
+
+    if (levels.empty()) {
+        int level = 1;
+        while (level <= maxLevel) {
+//            cerr << level << " ";
+            levels.push_back(level);
+            int step = level / 10;
+            int pwr = 0;
+            while (step > 0) {
+                ++pwr;
+                step /= 2;
+            }
+            step = (1 << pwr);
+            level += step;
+        }
+//        cerr << endl;
+    }
+
+    RoundingDirection effective = dir;
+    if (requested.zone == ZoomLevel::PixelsPerFrame) {
+        if (dir == RoundUp) effective = RoundDown;
+        else if (dir == RoundDown) effective = RoundUp;
+    }
+
+    // iterator pointing to first level that is >= requested
+    auto i = lower_bound(levels.begin(), levels.end(), requested.level);
+
+    ZoomLevel newLevel(requested);
+
+    if (i == levels.end()) {
+        newLevel.level = maxLevel;
+
+    } else if (*i == requested.level) {
+        newLevel.level = requested.level;
+
+    } else if (effective == RoundUp) {
+        newLevel.level = *i;
+
+    } else if (effective == RoundDown) {
+        if (i != levels.begin()) {
+            --i;
+        }
+        newLevel.level = *i;
+
+    } else { // RoundNearest
+        if (i != levels.begin()) {
+            auto j = i;
+            --j;
+            if (requested.level - *j < *i - requested.level) {
+                newLevel.level = *j;
+            } else {
+                newLevel.level = *i;
+            }
+        }
+    }
+
+    // canonicalise
+    if (newLevel.level == 1) {
+        newLevel.zone = ZoomLevel::FramesPerPixel;
+    }
+
+    using namespace std::rel_ops;
+    if (newLevel > getMaxZoomLevel()) {
+        newLevel = getMaxZoomLevel();
+    } else if (newLevel < getMinZoomLevel()) {
+        newLevel = getMinZoomLevel();
+    }
+    
+    return newLevel;
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/data/model/RelativelyFineZoomConstraint.h	Tue Nov 06 08:58:57 2018 +0000
@@ -0,0 +1,29 @@
+/* -*- 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_RELATIVELY_FINE_ZOOM_CONSTRAINT_H
+#define SV_RELATIVELY_FINE_ZOOM_CONSTRAINT_H
+
+#include "base/ZoomConstraint.h"
+
+class RelativelyFineZoomConstraint : virtual public ZoomConstraint
+{
+public:
+    virtual ZoomLevel getNearestZoomLevel(ZoomLevel requested,
+                                          RoundingDirection dir = RoundNearest)
+	const override;
+};
+
+#endif
+
--- a/data/model/test/TestZoomConstraints.h	Fri Oct 05 10:25:25 2018 +0100
+++ b/data/model/test/TestZoomConstraints.h	Tue Nov 06 08:58:57 2018 +0000
@@ -17,6 +17,7 @@
 
 #include "../PowerOfTwoZoomConstraint.h"
 #include "../PowerOfSqrtTwoZoomConstraint.h"
+#include "../RelativelyFineZoomConstraint.h"
 
 #include <QObject>
 #include <QtTest>
@@ -30,24 +31,90 @@
 {
     Q_OBJECT
 
+    string roundingName(ZoomConstraint::RoundingDirection dir) {
+        switch (dir) {
+        case ZoomConstraint::RoundDown: return "RoundDown";
+        case ZoomConstraint::RoundUp: return "RoundUp";
+        case ZoomConstraint::RoundNearest: return "RoundNearest";
+        }
+        return "<?>";
+    }
+    
+    void compare(ZoomLevel zin,
+                 ZoomConstraint::RoundingDirection dir,
+                 ZoomLevel zobt,
+                 ZoomLevel zexp) {
+        if (zexp.level == 1) {
+            // A zoom level of "1 pixel per frame" is not considered
+            // canonical - it should be "1 frame per pixel"
+            zexp.zone = ZoomLevel::FramesPerPixel;
+        }
+        if (zobt == zexp) {
+            return;
+        } else {
+            std::cerr << "For input " << zin << " and rounding direction "
+                      << roundingName(dir)
+                      << ", expected output " << zexp << " but obtained "
+                      << zobt << std::endl;
+            QCOMPARE(zobt, zexp);
+        }
+    }
+
     void checkFpp(const ZoomConstraint &c,
                   ZoomConstraint::RoundingDirection dir,
                   int n,
                   int expected) {
-        QCOMPARE(c.getNearestZoomLevel(ZoomLevel(ZoomLevel::FramesPerPixel, n),
-                                       dir),
-                 ZoomLevel(ZoomLevel::FramesPerPixel, expected));
+        ZoomLevel zin(ZoomLevel::FramesPerPixel, n);
+        ZoomLevel zexp(ZoomLevel::FramesPerPixel, expected);
+        ZoomLevel zobt(c.getNearestZoomLevel(zin, dir));
+        compare(zin, dir, zobt, zexp);
     }
-    
+
+    void checkPpf(const ZoomConstraint &c,
+                  ZoomConstraint::RoundingDirection dir,
+                  int n,
+                  int expected) {
+        ZoomLevel zin(ZoomLevel::PixelsPerFrame, n);
+        ZoomLevel zexp(ZoomLevel::PixelsPerFrame, expected);
+        ZoomLevel zobt(c.getNearestZoomLevel(zin, dir));
+        compare(zin, dir, zobt, zexp);
+    }
+
+    void checkBoth(const ZoomConstraint &c,
+                   ZoomConstraint::RoundingDirection dir,
+                   int n,
+                   int expected) {
+        checkFpp(c, dir, n, expected);
+        checkPpf(c, dir, n, expected);
+    }
+
+    void checkMaxMin(const ZoomConstraint &c,
+                     ZoomConstraint::RoundingDirection dir) {
+        auto max = c.getMaxZoomLevel();
+        compare(max, dir,
+                c.getNearestZoomLevel(max, dir), max);
+        compare(max.incremented(), dir,
+                c.getNearestZoomLevel(max.incremented(), dir), max);
+        auto min = c.getMinZoomLevel();
+        compare(min, dir,
+                c.getNearestZoomLevel(min, dir), min);
+        compare(min.decremented(), dir,
+                c.getNearestZoomLevel(min.decremented(), dir), min);
+    }
+
+    const static ZoomConstraint::RoundingDirection up = ZoomConstraint::RoundUp;
+    const static ZoomConstraint::RoundingDirection down = ZoomConstraint::RoundDown;
+    const static ZoomConstraint::RoundingDirection nearest = ZoomConstraint::RoundNearest;
+                                                                         
 private slots:
     void unconstrainedNearest() {
         ZoomConstraint c;
-        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);
+        checkBoth(c, nearest, 1, 1);
+        checkBoth(c, nearest, 2, 2);
+        checkBoth(c, nearest, 3, 3);
+        checkBoth(c, nearest, 4, 4);
+        checkBoth(c, nearest, 20, 20);
+        checkBoth(c, nearest, 32, 32);
         auto max = c.getMaxZoomLevel();
         QCOMPARE(c.getNearestZoomLevel(max), max);
         QCOMPARE(c.getNearestZoomLevel(max.incremented()), max);
@@ -55,44 +122,40 @@
     
     void unconstrainedUp() {
         ZoomConstraint c;
-        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);
+        checkBoth(c, up, 1, 1);
+        checkBoth(c, up, 2, 2);
+        checkBoth(c, up, 3, 3);
+        checkBoth(c, up, 4, 4);
+        checkBoth(c, up, 20, 20);
+        checkBoth(c, up, 32, 32);
         auto max = c.getMaxZoomLevel();
-        QCOMPARE(c.getNearestZoomLevel(max,
-                                       ZoomConstraint::RoundUp), max);
-        QCOMPARE(c.getNearestZoomLevel(max.incremented(),
-                                       ZoomConstraint::RoundUp), max);
+        QCOMPARE(c.getNearestZoomLevel(max, up), max);
+        QCOMPARE(c.getNearestZoomLevel(max.incremented(), up), max);
     }
     
     void unconstrainedDown() {
         ZoomConstraint c;
-        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);
+        checkBoth(c, down, 1, 1);
+        checkBoth(c, down, 2, 2);
+        checkBoth(c, down, 3, 3);
+        checkBoth(c, down, 4, 4);
+        checkBoth(c, down, 20, 20);
+        checkBoth(c, down, 32, 32);
         auto max = c.getMaxZoomLevel();
-        QCOMPARE(c.getNearestZoomLevel(max,
-                                       ZoomConstraint::RoundDown), max);
-        QCOMPARE(c.getNearestZoomLevel(max.incremented(),
-                                       ZoomConstraint::RoundDown), max);
+        QCOMPARE(c.getNearestZoomLevel(max, down), max);
+        QCOMPARE(c.getNearestZoomLevel(max.incremented(), down), max);
     }
 
     void powerOfTwoNearest() {
         PowerOfTwoZoomConstraint c;
-        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);
+        checkBoth(c, nearest, 1, 1);
+        checkBoth(c, nearest, 2, 2);
+        checkBoth(c, nearest, 3, 2);
+        checkBoth(c, nearest, 4, 4);
+        checkBoth(c, nearest, 20, 16);
+        checkBoth(c, nearest, 23, 16);
+        checkBoth(c, nearest, 24, 16);
+        checkBoth(c, nearest, 25, 32);
         auto max = c.getMaxZoomLevel();
         QCOMPARE(c.getNearestZoomLevel(max), max);
         QCOMPARE(c.getNearestZoomLevel(max.incremented()), max);
@@ -100,101 +163,157 @@
     
     void powerOfTwoUp() {
         PowerOfTwoZoomConstraint c;
-        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);
+        checkBoth(c, up, 1, 1);
+        checkBoth(c, up, 2, 2);
+        checkFpp(c, up, 3, 4);
+        checkPpf(c, up, 3, 2);
+        checkBoth(c, up, 4, 4);
+        checkFpp(c, up, 20, 32);
+        checkPpf(c, up, 20, 16);
+        checkBoth(c, up, 32, 32);
+        checkFpp(c, up, 33, 64);
+        checkPpf(c, up, 33, 32);
+        checkMaxMin(c, up);
     }
     
     void powerOfTwoDown() {
         PowerOfTwoZoomConstraint c;
-        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);
+        checkBoth(c, down, 1, 1);
+        checkBoth(c, down, 2, 2);
+        checkFpp(c, down, 3, 2);
+        checkPpf(c, down, 3, 4);
+        checkBoth(c, down, 4, 4);
+        checkFpp(c, down, 20, 16);
+        checkPpf(c, down, 20, 32);
+        checkBoth(c, down, 32, 32);
+        checkFpp(c, down, 33, 32);
+        checkPpf(c, down, 33, 64);
+        checkMaxMin(c, down);
     }
 
     void powerOfSqrtTwoNearest() {
         PowerOfSqrtTwoZoomConstraint c;
-        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);
+        checkBoth(c, nearest, 1, 1);
+        checkBoth(c, nearest, 2, 2);
+        checkBoth(c, nearest, 3, 2);
+        checkBoth(c, nearest, 4, 4);
+        checkBoth(c, nearest, 18, 16);
+        checkBoth(c, nearest, 19, 16);
+        checkBoth(c, nearest, 20, 22);
+        checkBoth(c, nearest, 23, 22);
+        checkBoth(c, nearest, 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)
-        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);
+        checkBoth(c, nearest, 350, 360);
+        // The most extreme level available in ppf mode
+        // (getMinZoomLevel()) is currently 512, so these bigger
+        // numbers will only happen in fpp mode
+        checkFpp(c, nearest, 800, 720);
+        checkFpp(c, nearest, 1023, 1024);
+        checkFpp(c, nearest, 1024, 1024);
+        checkFpp(c, nearest, 1024, 1024);
+        checkFpp(c, nearest, 1025, 1024);
+        checkPpf(c, nearest, 800, 512);
+        checkPpf(c, nearest, 1025, 512);
+        checkMaxMin(c, nearest);
     }
     
     void powerOfSqrtTwoUp() {
         PowerOfSqrtTwoZoomConstraint c;
-        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
-        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);
+        checkBoth(c, up, 1, 1);
+        checkBoth(c, up, 2, 2);
+        checkFpp(c, up, 3, 4);
+        checkPpf(c, up, 3, 2);
+        checkBoth(c, up, 4, 4);
+        checkFpp(c, up, 18, 22);
+        checkPpf(c, up, 18, 16);
+        checkBoth(c, up, 22, 22);
+        checkFpp(c, up, 23, 32);
+        checkPpf(c, up, 23, 22);
+        // see comments above
+        checkFpp(c, up, 800, 1024);
+        checkFpp(c, up, 1023, 1024);
+        checkFpp(c, up, 1024, 1024);
+        checkFpp(c, up, 1025, 1440);
+        checkPpf(c, up, 300, 256);
+        checkPpf(c, up, 800, 512);
+        checkPpf(c, up, 1600, 512);
+        checkMaxMin(c, up);
     }
     
     void powerOfSqrtTwoDown() {
         PowerOfSqrtTwoZoomConstraint c;
-        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
-        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);
+        checkBoth(c, down, 1, 1);
+        checkBoth(c, down, 2, 2);
+        checkFpp(c, down, 3, 2);
+        checkPpf(c, down, 3, 4);
+        checkBoth(c, down, 4, 4);
+        checkFpp(c, down, 18, 16);
+        checkPpf(c, down, 18, 22);
+        checkBoth(c, down, 22, 22);
+        checkFpp(c, down, 23, 22);
+        checkPpf(c, down, 23, 32);
+        // see comments above
+        checkFpp(c, down, 800, 720);
+        checkFpp(c, down, 1023, 720);
+        checkFpp(c, down, 1024, 1024);
+        checkFpp(c, down, 1025, 1024);
+        checkPpf(c, down, 300, 360);
+        checkPpf(c, down, 800, 512);
+        checkPpf(c, down, 1600, 512);
+        checkMaxMin(c, down);
+    }
+
+    void relativelyFineNearest() {
+        RelativelyFineZoomConstraint c;
+        checkBoth(c, nearest, 1, 1);
+        checkBoth(c, nearest, 2, 2);
+        checkBoth(c, nearest, 3, 3);
+        checkBoth(c, nearest, 4, 4);
+        checkBoth(c, nearest, 20, 20);
+        checkBoth(c, nearest, 33, 32);
+        checkBoth(c, nearest, 59, 56);
+        checkBoth(c, nearest, 69, 72);
+        checkBoth(c, nearest, 121, 128);
+        checkMaxMin(c, nearest);
+    }
+    
+    void relativelyFineUp() {
+        RelativelyFineZoomConstraint c;
+        checkBoth(c, up, 1, 1);
+        checkBoth(c, up, 2, 2);
+        checkBoth(c, up, 3, 3);
+        checkBoth(c, up, 4, 4);
+        checkBoth(c, up, 20, 20);
+        checkFpp(c, up, 33, 36);
+        checkPpf(c, up, 33, 32);
+        checkFpp(c, up, 59, 64);
+        checkPpf(c, up, 59, 56);
+        checkFpp(c, up, 69, 72);
+        checkPpf(c, up, 69, 64);
+        checkFpp(c, up, 121, 128);
+        checkPpf(c, up, 121, 112);
+        checkMaxMin(c, up);
+    }
+    
+    void relativelyFineDown() {
+        RelativelyFineZoomConstraint c;
+        checkBoth(c, down, 1, 1);
+        checkBoth(c, down, 2, 2);
+        checkBoth(c, down, 3, 3);
+        checkBoth(c, down, 4, 4);
+        checkBoth(c, down, 20, 20);
+        checkFpp(c, down, 33, 32);
+        checkPpf(c, down, 33, 36);
+        checkFpp(c, down, 59, 56);
+        checkPpf(c, down, 59, 64);
+        checkFpp(c, down, 69, 64);
+        checkPpf(c, down, 69, 72);
+        checkFpp(c, down, 121, 112);
+        checkPpf(c, down, 121, 128);
+        checkMaxMin(c, down);
     }
 };
 
--- a/files.pri	Fri Oct 05 10:25:25 2018 +0100
+++ b/files.pri	Tue Nov 06 08:58:57 2018 +0000
@@ -90,6 +90,7 @@
            data/model/PowerOfTwoZoomConstraint.h \
            data/model/RangeSummarisableTimeValueModel.h \
            data/model/RegionModel.h \
+           data/model/RelativelyFineZoomConstraint.h \
            data/model/SparseModel.h \
            data/model/SparseOneDimensionalModel.h \
            data/model/SparseTimeValueModel.h \
@@ -212,6 +213,7 @@
            data/model/PowerOfSqrtTwoZoomConstraint.cpp \
            data/model/PowerOfTwoZoomConstraint.cpp \
            data/model/RangeSummarisableTimeValueModel.cpp \
+           data/model/RelativelyFineZoomConstraint.cpp \
            data/model/WaveformOversampler.cpp \
            data/model/WaveFileModel.cpp \
            data/model/ReadOnlyWaveFileModel.cpp \
--- a/plugin/PiperVampPluginFactory.cpp	Fri Oct 05 10:25:25 2018 +0100
+++ b/plugin/PiperVampPluginFactory.cpp	Tue Nov 06 08:58:57 2018 +0000
@@ -58,18 +58,29 @@
     m_logger(new Logger)
 {
     QString serverName = "piper-vamp-simple-server";
+    float minimumVersion = 2.0;
 
     HelperExecPath hep(HelperExecPath::AllInstalled);
-    m_servers = hep.getHelperExecutables(serverName);
 
-    for (auto n: m_servers) {
+    auto servers = hep.getHelperExecutables(serverName);
+
+    for (auto n: servers) {
         SVDEBUG << "NOTE: PiperVampPluginFactory: Found server: "
                 << n.executable << endl;
+        if (serverMeetsMinimumVersion(n, minimumVersion)) {
+            m_servers.push_back(n);
+        } else {
+            SVCERR << "WARNING: PiperVampPluginFactory: Server at "
+                   << n.executable
+                   << " does not meet minimum version requirement (version >= "
+                   << minimumVersion << ")" << endl;
+        }
     }
     
     if (m_servers.empty()) {
         SVDEBUG << "NOTE: No Piper Vamp servers found in installation;"
-                << " found none of the following:" << endl;
+                << " the following paths are either absent or fail "
+                << "minimum-version check:" << endl;
         for (auto d: hep.getHelperCandidatePaths(serverName)) {
             SVDEBUG << "NOTE: " << d << endl;
         }
@@ -81,6 +92,55 @@
     delete m_logger;
 }
 
+bool
+PiperVampPluginFactory::serverMeetsMinimumVersion(const HelperExecPath::HelperExec &server,
+                                                  float minimumVersion)
+{
+    QProcess process;
+    QString executable = server.executable;
+    process.setReadChannel(QProcess::StandardOutput);
+    process.setProcessChannelMode(QProcess::ForwardedErrorChannel);
+    process.start(executable, { "--version" });
+
+    if (!process.waitForStarted()) {
+        QProcess::ProcessError err = process.error();
+        if (err == QProcess::FailedToStart) {
+            SVCERR << "WARNING: Unable to start server " << executable
+                   << " for version check" << endl;
+        } else if (err == QProcess::Crashed) {
+            SVCERR << "WARNING: Server " << executable
+                   << " crashed on version check" << endl;
+        } else {
+            SVCERR << "WARNING: Server " << executable
+                   << " failed on version check with error code "
+                   << err << endl;
+        }
+        return false;
+    }
+    process.waitForFinished();
+
+    QByteArray output = process.readAllStandardOutput();
+    while (output.endsWith('\n') || output.endsWith('\r')) {
+        output.chop(1);
+    }
+
+    QString outputString(output);
+    bool ok = false;
+    float version = outputString.toFloat(&ok);
+    if (!ok) {
+        SVCERR << "WARNING: Failed to convert server version response \""
+               << outputString << "\" into one- or two-part version number"
+               << endl;
+    }
+
+    SVDEBUG << "Server " << executable << " reports version number "
+            << version << endl;
+
+    float eps = 1e-6;
+    return (version >= minimumVersion ||
+            fabsf(version - minimumVersion) < eps); // arf
+}
+
 vector<QString>
 PiperVampPluginFactory::getPluginIdentifiers(QString &errorMessage)
 {
--- a/plugin/PiperVampPluginFactory.h	Fri Oct 05 10:25:25 2018 +0100
+++ b/plugin/PiperVampPluginFactory.h	Tue Nov 06 08:58:57 2018 +0000
@@ -59,6 +59,8 @@
     std::map<QString, piper_vamp::PluginStaticData> m_pluginData; // identifier -> data
     std::map<QString, QString> m_taxonomy; // identifier -> category string
 
+    bool serverMeetsMinimumVersion(const HelperExecPath::HelperExec &server,
+                                   float minimumVersion);
     void populate(QString &errorMessage);
     void populateFrom(const HelperExecPath::HelperExec &, QString &errorMessage);
 
--- a/plugin/PluginScan.cpp	Fri Oct 05 10:25:25 2018 +0100
+++ b/plugin/PluginScan.cpp	Tue Nov 06 08:58:57 2018 +0000
@@ -190,7 +190,7 @@
     
     os << "<ul>";
     for (auto f: failures) {
-        os << "<li>" + f.library;
+        os << "<li><code>" + f.library + "</code>";
 
         SVDEBUG << "PluginScan::formatFailureReport: tag is \"" << tag
                 << "\", failure code is " << int(f.code) << ", message is \""
@@ -225,6 +225,11 @@
                 ("Library cannot be loaded: %1").arg(userMessage);
             break;
 
+        case PluginCheckCode::FAIL_FORBIDDEN:
+            userMessage = QObject::tr
+                ("Permission to load library was refused");
+            break;
+
         case PluginCheckCode::FAIL_DESCRIPTOR_MISSING:
             userMessage = QObject::tr
                 ("Not a valid plugin library (no descriptor found)");
@@ -298,8 +303,7 @@
         return report;
     }
 
-    return QObject::tr("<b>Failed to load plugins</b>"
-                       "<p>Failed to load one or more plugin libraries:</p>")
+    return QObject::tr("<p>Failed to load one or more plugin libraries:</p>")
         + report
         + QObject::tr("<p>These plugins may be incompatible with the system, "
                       "and will be ignored during this run of %1.</p>")
--- a/transform/FeatureExtractionModelTransformer.cpp	Fri Oct 05 10:25:25 2018 +0100
+++ b/transform/FeatureExtractionModelTransformer.cpp	Tue Nov 06 08:58:57 2018 +0000
@@ -40,6 +40,8 @@
 
 #include <QSettings>
 
+//#define DEBUG_FEATURE_EXTRACTION_TRANSFORMER_RUN 1
+
 FeatureExtractionModelTransformer::FeatureExtractionModelTransformer(Input in,
                                                                      const Transform &transform) :
     ModelTransformer(in, transform),
@@ -656,9 +658,11 @@
     Transform primaryTransform = m_transforms[0];
 
     while (!input->isReady() && !m_abandoned) {
-        cerr << "FeatureExtractionModelTransformer::run: Waiting for input model to be ready..." << endl;
+        SVDEBUG << "FeatureExtractionModelTransformer::run: Waiting for input model to be ready..." << endl;
         usleep(500000);
     }
+    SVDEBUG << "FeatureExtractionModelTransformer::run: Waited, ready = "
+            << input->isReady() << ", m_abandoned = " << m_abandoned << endl;
     if (m_abandoned) return;
 
     sv_samplerate_t sampleRate = input->getSampleRate();
@@ -678,6 +682,7 @@
 
     bool frequencyDomain = (m_plugin->getInputDomain() ==
                             Vamp::Plugin::FrequencyDomain);
+
     std::vector<FFTModel *> fftModels;
 
     if (frequencyDomain) {
@@ -754,10 +759,12 @@
                     contextStart + contextDuration) break;
             }
 
-//        SVDEBUG << "FeatureExtractionModelTransformer::run: blockFrame "
-//                  << blockFrame << ", endFrame " << endFrame << ", blockSize "
-//                  << blockSize << endl;
-
+#ifdef DEBUG_FEATURE_EXTRACTION_TRANSFORMER_RUN
+            SVDEBUG << "FeatureExtractionModelTransformer::run: blockFrame "
+                    << blockFrame << ", endFrame " << endFrame << ", blockSize "
+                    << blockSize << endl;
+#endif
+        
             int completion = int
                 ((((blockFrame - contextStart) / stepSize) * 99) /
                  (contextDuration / stepSize + 1));
@@ -777,7 +784,8 @@
                             buffers[ch][i*2] = 0.f;
                             buffers[ch][i*2+1] = 0.f;
                         }
-                    }                    
+                    }
+                        
                     error = fftModels[ch]->getError();
                     if (error != "") {
                         SVCERR << "FeatureExtractionModelTransformer::run: Abandoning, error is " << error << endl;
@@ -793,8 +801,10 @@
             if (m_abandoned) break;
 
             Vamp::Plugin::FeatureSet features = m_plugin->process
-                (buffers, RealTime::frame2RealTime(blockFrame, sampleRate).toVampRealTime());
-
+                (buffers,
+                 RealTime::frame2RealTime(blockFrame, sampleRate)
+                 .toVampRealTime());
+            
             if (m_abandoned) break;
 
             for (int j = 0; j < (int)m_outputNos.size(); ++j) {
@@ -1122,8 +1132,10 @@
 void
 FeatureExtractionModelTransformer::setCompletion(int n, int completion)
 {
-//    SVDEBUG << "FeatureExtractionModelTransformer::setCompletion("
-//              << completion << ")" << endl;
+#ifdef DEBUG_FEATURE_EXTRACTION_TRANSFORMER_RUN
+    SVDEBUG << "FeatureExtractionModelTransformer::setCompletion("
+              << completion << ")" << endl;
+#endif
 
     if (isOutput<SparseOneDimensionalModel>(n)) {