changeset 1159:444d133b5ab7 3.0-integration

Merge from branch "tony-2.0-integration"
author Chris Cannam
date Thu, 04 Feb 2016 11:13:39 +0000
parents 2dc27f0f97ad (diff) e94719f941ba (current diff)
children ea636412f9fe
files data/model/FFTModel.cpp data/model/FFTModel.h
diffstat 19 files changed, 355 insertions(+), 326 deletions(-) [+]
line wrap: on
line diff
--- a/base/ResizeableBitset.h	Tue Oct 20 12:54:06 2015 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,107 +0,0 @@
-/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
-
-/*
-    Sonic Visualiser
-    An audio file viewer and annotation editor.
-    Centre for Digital Music, Queen Mary, University of London.
-    This file copyright 2006 Chris Cannam.
-    
-    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 _RESIZEABLE_BITMAP_H_
-#define _RESIZEABLE_BITMAP_H_
-
-#include <vector>
-#include <stdint.h>
-#include <stddef.h>
-#include <stdlib.h>
-
-class ResizeableBitset {
-
-public:
-    ResizeableBitset() : m_bits(0), m_size(0) {
-    }
-    ResizeableBitset(size_t size) : m_bits(new std::vector<uint8_t>), m_size(size) {
-        m_bits->assign((size >> 3) + 1, 0);
-    }
-    ResizeableBitset(const ResizeableBitset &b) {
-        m_bits = new std::vector<uint8_t>(*b.m_bits);
-    }
-    ResizeableBitset &operator=(const ResizeableBitset &b) {
-        if (&b != this) return *this;
-        delete m_bits;
-        m_bits = new std::vector<uint8_t>(*b.m_bits);
-        return *this;
-    }
-    ~ResizeableBitset() {
-        delete m_bits;
-    }
-    
-    void resize(size_t size) { // retaining existing data; not thread safe
-        size_t bytes = (size >> 3) + 1;
-        if (m_bits && bytes == m_bits->size()) return;
-        std::vector<uint8_t> *newbits = new std::vector<uint8_t>(bytes);
-        newbits->assign(bytes, 0);
-        if (m_bits) {
-            for (size_t i = 0; i < bytes && i < m_bits->size(); ++i) {
-                (*newbits)[i] = (*m_bits)[i];
-            }
-            delete m_bits;
-        }
-        m_bits = newbits;
-        m_size = size;
-    }
-    
-    bool get(size_t column) const {
-        return ((*m_bits)[column >> 3]) & (1u << (column & 0x07));
-    }
-    
-    void set(size_t column) {
-        size_t ix = (column >> 3);
-        uint8_t prior = (*m_bits)[ix];
-        uint8_t extra = ((1u << (column & 0x07)) & 0xff);
-        (*m_bits)[ix] = uint8_t(prior | extra);
-    }
-
-    void reset(size_t column) {
-        ((*m_bits)[column >> 3]) &= uint8_t((~(1u << (column & 0x07))) & 0xff);
-    }
-
-    void copy(size_t source, size_t dest) {
-        get(source) ? set(dest) : reset(dest);
-    }
-
-    bool isAllOff() const {
-        for (size_t i = 0; i < m_bits->size(); ++i) {
-            if ((*m_bits)[i]) return false;
-        }
-        return true;
-    }
-
-    bool isAllOn() const {
-        for (size_t i = 0; i + 1 < m_bits->size(); ++i) {
-            if ((*m_bits)[i] != 0xff) return false;
-        }
-        for (size_t i = (m_size / 8) * 8; i < m_size; ++i) {
-            if (!get(i)) return false;
-        }
-        return true;
-    }
-
-    size_t size() const {
-        return m_size;
-    }
-    
-private:
-    std::vector<uint8_t> *m_bits;
-    size_t m_size;
-};
-
-
-#endif
-
--- a/data/fileio/MatrixFile.cpp	Tue Oct 20 12:54:06 2015 +0100
+++ b/data/fileio/MatrixFile.cpp	Thu Feb 04 11:13:39 2016 +0000
@@ -176,8 +176,6 @@
 
     QMutexLocker locker(&m_createMutex);
 
-    delete m_setColumns;
-
     if (m_fileName != "") {
 
         if (--m_refcount[m_fileName] == 0) {
@@ -208,7 +206,7 @@
 
     assert(m_mode == WriteOnly);
 
-    m_setColumns = new ResizeableBitset(m_width);
+    m_setColumns.resize(m_width, false);
     
     off_t off = m_headerSize + (m_width * m_height * m_cellSize) + m_width;
 
@@ -316,7 +314,7 @@
 MatrixFile::haveSetColumnAt(int x) const
 {
     if (m_mode == WriteOnly) {
-        return m_setColumns->get(x);
+        return m_setColumns[x];
     }
 
     if (m_readyToReadColumn >= 0 &&
@@ -398,9 +396,10 @@
         throw FileOperationFailed(m_fileName, "write");
     }
 
-    m_setColumns->set(x);
+    m_setColumns[x] = true;
     if (m_autoClose) {
-        if (m_setColumns->isAllOn()) {
+        if (std::all_of(m_setColumns.begin(), m_setColumns.end(),
+                        [](bool c) { return c; })) {
 #ifdef DEBUG_MATRIX_FILE
             cerr << "MatrixFile[" << m_fd << "]::setColumnAt(" << x << "): All columns set: auto-closing" << endl;
 #endif
--- a/data/fileio/MatrixFile.h	Tue Oct 20 12:54:06 2015 +0100
+++ b/data/fileio/MatrixFile.h	Thu Feb 04 11:13:39 2016 +0000
@@ -13,10 +13,8 @@
     COPYING included with this distribution for more information.
 */
 
-#ifndef _MATRIX_FILE_CACHE_H_
-#define _MATRIX_FILE_CACHE_H_
-
-#include "base/ResizeableBitset.h"
+#ifndef MATRIX_FILE_H
+#define MATRIX_FILE_H
 
 #include "FileReadThread.h"
 
@@ -91,7 +89,7 @@
     int     m_headerSize;
     QString m_fileName;
 
-    ResizeableBitset *m_setColumns; // only in writer
+    std::vector<bool> m_setColumns; // only populated in writer
     bool m_autoClose;
 
     // In reader: if this is >= 0, we can read that column directly
--- a/data/model/Dense3DModelPeakCache.cpp	Tue Oct 20 12:54:06 2015 +0100
+++ b/data/model/Dense3DModelPeakCache.cpp	Thu Feb 04 11:13:39 2016 +0000
@@ -22,8 +22,6 @@
     m_source(source),
     m_resolution(columnsPerPeak)
 {
-    m_coverage.resize(1); // otherwise it is simply invalid
-
     m_cache = new EditableDenseThreeDimensionalModel
         (source->getSampleRate(),
          getResolution(),
@@ -43,20 +41,6 @@
     delete m_cache;
 }
 
-bool
-Dense3DModelPeakCache::isColumnAvailable(int column) const
-{
-    if (!m_source) return false;
-    if (haveColumn(column)) return true;
-    for (int i = m_resolution; i > 0; ) {
-        --i;
-        if (!m_source->isColumnAvailable(column * m_resolution + i)) {
-            return false;
-        }
-    }
-    return true;
-}
-
 Dense3DModelPeakCache::Column
 Dense3DModelPeakCache::getColumn(int column) const
 {
@@ -81,9 +65,9 @@
     if (m_coverage.size() > 0) {
         // The last peak may have come from an incomplete read, which
         // may since have been filled, so reset it
-        m_coverage.reset(m_coverage.size()-1);
+        m_coverage[m_coverage.size()-1] = false;
     }
-    m_coverage.resize(getWidth()); // retaining data
+    m_coverage.resize(getWidth(), false); // retaining data
 }
 
 void
@@ -95,7 +79,7 @@
 bool
 Dense3DModelPeakCache::haveColumn(int column) const
 {
-    return column < (int)m_coverage.size() && m_coverage.get(column);
+    return in_range_for(m_coverage, column) && m_coverage[column];
 }
 
 void
@@ -103,26 +87,29 @@
 {
     Profiler profiler("Dense3DModelPeakCache::fillColumn");
 
-    if (column >= (int)m_coverage.size()) {
+    if (!in_range_for(m_coverage, column)) {
         // see note in sourceModelChanged
-        if (m_coverage.size() > 0) m_coverage.reset(m_coverage.size()-1);
-        m_coverage.resize(column + 1);
+        if (m_coverage.size() > 0) m_coverage[m_coverage.size()-1] = false;
+        m_coverage.resize(column + 1, false);
     }
 
     Column peak;
-    for (int i = 0; i < int(m_resolution); ++i) {
+    int n = 0;
+    for (int i = 0; i < m_resolution; ++i) {
         Column here = m_source->getColumn(column * m_resolution + i);
         if (i == 0) {
             peak = here;
+            n = int(peak.size());
         } else {
-            for (int j = 0; j < (int)peak.size() && j < (int)here.size(); ++j) {
-                if (here[j] > peak[j]) peak[j] = here[j];
+            int m = std::min(n, int(here.size()));
+            for (int j = 0; j < m; ++j) {
+                peak[j] = std::max(here[j], peak[j]);
             }
         }
     }
 
     m_cache->setColumn(column, peak);
-    m_coverage.set(column);
+    m_coverage[column] = true;
 }
 
 
--- a/data/model/Dense3DModelPeakCache.h	Tue Oct 20 12:54:06 2015 +0100
+++ b/data/model/Dense3DModelPeakCache.h	Thu Feb 04 11:13:39 2016 +0000
@@ -18,7 +18,6 @@
 
 #include "DenseThreeDimensionalModel.h"
 #include "EditableDenseThreeDimensionalModel.h"
-#include "base/ResizeableBitset.h"
 
 class Dense3DModelPeakCache : public DenseThreeDimensionalModel
 {
@@ -65,8 +64,6 @@
         return m_source->getMaximumLevel();
     }
 
-    virtual bool isColumnAvailable(int column) const;
-
     virtual Column getColumn(int column) const;
 
     virtual float getValueAt(int column, int n) const;
@@ -92,7 +89,8 @@
 private:
     DenseThreeDimensionalModel *m_source;
     mutable EditableDenseThreeDimensionalModel *m_cache;
-    mutable ResizeableBitset m_coverage;
+    mutable std::vector<bool> m_coverage; // must be bool, for space efficiency
+                                          // (vector of bool uses 1-bit elements)
     int m_resolution;
 
     bool haveColumn(int column) const;
--- a/data/model/DenseThreeDimensionalModel.h	Tue Oct 20 12:54:06 2015 +0100
+++ b/data/model/DenseThreeDimensionalModel.h	Thu Feb 04 11:13:39 2016 +0000
@@ -55,16 +55,7 @@
      */
     virtual float getMaximumLevel() const = 0;
 
-    /**
-     * Return true if there are data available for the given column.
-     * This should return true only if getColumn(column) would not
-     * have to do any substantial work to calculate its return values.
-     * If this function returns false, it may still be possible to
-     * retrieve the column, but its values may have to be calculated.
-     */
-    virtual bool isColumnAvailable(int column) const = 0;
-
-    typedef QVector<float> Column;
+    typedef std::vector<float> Column;
 
     /**
      * Get data from the given column of bin values.
--- a/data/model/EditableDenseThreeDimensionalModel.cpp	Tue Oct 20 12:54:06 2015 +0100
+++ b/data/model/EditableDenseThreeDimensionalModel.cpp	Thu Feb 04 11:13:39 2016 +0000
@@ -96,7 +96,7 @@
 int
 EditableDenseThreeDimensionalModel::getWidth() const
 {
-    return m_data.size();
+    return int(m_data.size());
 }
 
 int
@@ -139,15 +139,15 @@
 EditableDenseThreeDimensionalModel::getColumn(int index) const
 {
     QReadLocker locker(&m_lock);
-    if (index < 0 || index >= m_data.size()) return Column();
-    return expandAndRetrieve(index);
+    if (in_range_for(m_data, index)) return expandAndRetrieve(index);
+    else return Column();
 }
 
 float
 EditableDenseThreeDimensionalModel::getValueAt(int index, int n) const
 {
     Column c = getColumn(index);
-    if (int(n) < c.size()) return c.at(n);
+    if (in_range_for(c, n)) return c.at(n);
     return m_minimum;
 }
 
@@ -157,7 +157,7 @@
 EditableDenseThreeDimensionalModel::truncateAndStore(int index,
                                                      const Column &values)
 {
-    assert(int(index) < m_data.size());
+    assert(in_range_for(m_data, index));
 
     //cout << "truncateAndStore(" << index << ", " << values.size() << ")" << endl;
 
@@ -169,7 +169,7 @@
     m_trunc[index] = 0;
     if (index == 0 ||
         m_compression == NoCompression ||
-        values.size() != int(m_yBinCount)) {
+        int(values.size()) != m_yBinCount) {
 //        given += values.size();
 //        stored += values.size();
         m_data[index] = values;
@@ -206,7 +206,7 @@
     Column p = expandAndRetrieve(index - tdist);
     int h = m_yBinCount;
 
-    if (p.size() == h && tdist <= maxdist) {
+    if (int(p.size()) == h && tdist <= maxdist) {
 
         int bcount = 0, tcount = 0;
         if (!known || !top) {
@@ -282,7 +282,7 @@
     int tdist = trunc;
     if (trunc < 0) { top = false; tdist = -trunc; }
     Column p = expandAndRetrieve(index - tdist);
-    int psize = p.size(), csize = c.size();
+    int psize = int(p.size()), csize = int(c.size());
     if (psize != m_yBinCount) {
         cerr << "WARNING: EditableDenseThreeDimensionalModel::expandAndRetrieve: Trying to expand from incorrectly sized column" << endl;
     }
@@ -291,10 +291,6 @@
             c.push_back(p.at(i));
         }
     } else {
-        // push_front is very slow on QVector -- but not enough to
-        // make it desirable to choose a different container, since
-        // QVector has all the other advantages for us.  easier to
-        // write the whole array out to a new vector
         Column cc(psize);
         for (int i = 0; i < psize - csize; ++i) {
             cc[i] = p.at(i);
@@ -313,7 +309,7 @@
 {
     QWriteLocker locker(&m_lock);
 
-    while (int(index) >= m_data.size()) {
+    while (index >= int(m_data.size())) {
 	m_data.push_back(Column());
         m_trunc.push_back(0);
     }
@@ -322,7 +318,7 @@
 
 //    if (values.size() > m_yBinCount) m_yBinCount = values.size();
 
-    for (int i = 0; i < values.size(); ++i) {
+    for (int i = 0; in_range_for(values, i); ++i) {
         float value = values[i];
         if (ISNAN(value) || ISINF(value)) {
             continue;
@@ -432,13 +428,13 @@
     
     for (int i = 0; i < 10; ++i) {
         int index = i * 10;
-        if (index < m_data.size()) {
+        if (in_range_for(m_data, index)) {
             const Column &c = m_data.at(index);
-            while (c.size() > int(sample.size())) {
+            while (c.size() > sample.size()) {
                 sample.push_back(0.0);
                 n.push_back(0);
             }
-            for (int j = 0; j < c.size(); ++j) {
+            for (int j = 0; in_range_for(c, j); ++j) {
                 sample[j] += c.at(j);
                 ++n[j];
             }
@@ -486,9 +482,9 @@
 {
     QReadLocker locker(&m_lock);
     QString s;
-    for (int i = 0; i < m_data.size(); ++i) {
+    for (int i = 0; in_range_for(m_data, i); ++i) {
         QStringList list;
-	for (int j = 0; j < m_data.at(i).size(); ++j) {
+	for (int j = 0; in_range_for(m_data.at(i), j); ++j) {
             list << QString("%1").arg(m_data.at(i).at(j));
         }
         s += list.join(delimiter) + "\n";
@@ -501,11 +497,11 @@
 {
     QReadLocker locker(&m_lock);
     QString s;
-    for (int i = 0; i < m_data.size(); ++i) {
+    for (int i = 0; in_range_for(m_data, i); ++i) {
         sv_frame_t fr = m_startFrame + i * m_resolution;
         if (fr >= f0 && fr < f1) {
             QStringList list;
-            for (int j = 0; j < m_data.at(i).size(); ++j) {
+            for (int j = 0; in_range_for(m_data.at(i), j); ++j) {
                 list << QString("%1").arg(m_data.at(i).at(j));
             }
             s += list.join(delimiter) + "\n";
--- a/data/model/EditableDenseThreeDimensionalModel.h	Tue Oct 20 12:54:06 2015 +0100
+++ b/data/model/EditableDenseThreeDimensionalModel.h	Thu Feb 04 11:13:39 2016 +0000
@@ -105,11 +105,6 @@
     virtual void setMaximumLevel(float sz);
 
     /**
-     * Return true if there are data available for the given column.
-     */
-    virtual bool isColumnAvailable(int x) const { return x < getWidth(); }
-
-    /**
      * Get the set of bin values at the given column.
      */
     virtual Column getColumn(int x) const;
@@ -194,7 +189,7 @@
                        QString extraAttributes = "") const;
 
 protected:
-    typedef QVector<Column> ValueMatrix;
+    typedef std::vector<Column> ValueMatrix;
     ValueMatrix m_data;
 
     // m_trunc is used for simple compression.  If at least the top N
--- a/data/model/FFTModel.cpp	Tue Oct 20 12:54:06 2015 +0100
+++ b/data/model/FFTModel.cpp	Thu Feb 04 11:13:39 2016 +0000
@@ -98,9 +98,9 @@
 {
     auto cplx = getFFTColumn(x);
     Column col;
-    col.reserve(int(cplx.size()));
+    col.reserve(cplx.size());
     for (auto c: cplx) col.push_back(abs(c));
-    return col;
+    return move(col);
 }
 
 float
@@ -116,7 +116,8 @@
 {
     Column col(getColumn(x));
     float max = 0.f;
-    for (int i = 0; i < col.size(); ++i) {
+    int n = int(col.size());
+    for (int i = 0; i < n; ++i) {
         if (col[i] > max) max = col[i];
     }
     return max;
@@ -138,13 +139,6 @@
 }
 
 bool
-FFTModel::isColumnAvailable(int) const
-{
-    //!!!
-    return true;
-}
-
-bool
 FFTModel::getMagnitudesAt(int x, float *values, int minbin, int count) const
 {
     if (count == 0) count = getHeight();
@@ -313,7 +307,7 @@
     }
     m_cached.push_back(sc);
 
-    return col;
+    return move(col);
 }
 
 bool
@@ -388,10 +382,11 @@
     }
 
     Column values = getColumn(x);
+    int nv = int(values.size());
 
     float mean = 0.f;
-    for (int i = 0; i < values.size(); ++i) mean += values[i];
-    if (values.size() > 0) mean = mean / float(values.size());
+    for (int i = 0; i < nv; ++i) mean += values[i];
+    if (nv > 0) mean = mean / float(values.size());
     
     // For peak picking we use a moving median window, picking the
     // highest value within each continuous region of values that
@@ -412,8 +407,8 @@
     else binmin = 0;
 
     int binmax;
-    if (ymax + halfWin < values.size()) binmax = ymax + halfWin;
-    else binmax = values.size()-1;
+    if (ymax + halfWin < nv) binmax = ymax + halfWin;
+    else binmax = nv - 1;
 
     int prevcentre = 0;
 
@@ -434,8 +429,8 @@
         int actualSize = int(window.size());
 
         if (type == MajorPitchAdaptivePeaks) {
-            if (ymax + halfWin < values.size()) binmax = ymax + halfWin;
-            else binmax = values.size()-1;
+            if (ymax + halfWin < nv) binmax = ymax + halfWin;
+            else binmax = nv - 1;
         }
 
         deque<float> sorted(window);
@@ -455,7 +450,7 @@
                 inrange.push_back(centrebin);
             }
 
-            if (centre <= median || centrebin+1 == values.size()) {
+            if (centre <= median || centrebin+1 == nv) {
                 if (!inrange.empty()) {
                     int peakbin = 0;
                     float peakval = 0.f;
--- a/data/model/FFTModel.h	Tue Oct 20 12:54:06 2015 +0100
+++ b/data/model/FFTModel.h	Thu Feb 04 11:13:39 2016 +0000
@@ -100,7 +100,6 @@
     float getMaximumMagnitudeAt(int x) const;
     float getPhaseAt(int x, int y) const;
     void getValuesAt(int x, int y, float &real, float &imaginary) const;
-    bool isColumnAvailable(int x) const;
     bool getMagnitudesAt(int x, float *values, int minbin = 0, int count = 0) const;
     float getNormalizedMagnitudesAt(int x, float *values, int minbin = 0, int count = 0) const; // returns maximum of unnormalized magnitudes
     bool getPhasesAt(int x, float *values, int minbin = 0, int count = 0) const;
--- a/data/model/ReadOnlyWaveFileModel.cpp	Tue Oct 20 12:54:06 2015 +0100
+++ b/data/model/ReadOnlyWaveFileModel.cpp	Thu Feb 04 11:13:39 2016 +0000
@@ -193,7 +193,7 @@
     // playback or input to transforms.
 
 #ifdef DEBUG_WAVE_FILE_MODEL
-    cout << "ReadOnlyWaveFileModel::getData[" << this << "]: " << channel << ", " << start << ", " << count << ", " << buffer << endl;
+    cout << "ReadOnlyWaveFileModel::getData[" << this << "]: " << channel << ", " << start << ", " << count << endl;
 #endif
 
     int channels = getChannelCount();
@@ -252,7 +252,7 @@
     // playback or input to transforms.
 
 #ifdef DEBUG_WAVE_FILE_MODEL
-    cout << "ReadOnlyWaveFileModel::getData[" << this << "]: " << fromchannel << "," << tochannel << ", " << start << ", " << count << ", " << buffer << endl;
+    cout << "ReadOnlyWaveFileModel::getData[" << this << "]: " << fromchannel << "," << tochannel << ", " << start << ", " << count << endl;
 #endif
 
     int channels = getChannelCount();
@@ -322,7 +322,7 @@
 
 void
 ReadOnlyWaveFileModel::getSummaries(int channel, sv_frame_t start, sv_frame_t count,
-                            RangeBlock &ranges, int &blockSize) const
+                                    RangeBlock &ranges, int &blockSize) const
 {
     ranges.clear();
     if (!isOK()) return;
@@ -403,14 +403,12 @@
         blockSize = roundedBlockSize;
 
 	sv_frame_t cacheBlock, div;
-        
-	if (cacheType == 0) {
-	    cacheBlock = (1 << m_zoomConstraint.getMinCachePower());
-            div = (1 << power) / cacheBlock;
-	} else {
-	    cacheBlock = sv_frame_t((1 << m_zoomConstraint.getMinCachePower()) * sqrt(2.) + 0.01);
-            div = sv_frame_t(((1 << power) * sqrt(2.) + 0.01) / double(cacheBlock));
+
+        cacheBlock = (sv_frame_t(1) << m_zoomConstraint.getMinCachePower());
+	if (cacheType == 1) {
+	    cacheBlock = sv_frame_t(double(cacheBlock) * sqrt(2.) + 0.01);
 	}
+        div = blockSize / cacheBlock;
 
 	sv_frame_t startIndex = start / cacheBlock;
 	sv_frame_t endIndex = (start + count) / cacheBlock;
@@ -425,7 +423,7 @@
 	for (i = 0; i <= endIndex - startIndex; ) {
         
 	    sv_frame_t index = (i + startIndex) * channels + channel;
-	    if (index >= (sv_frame_t)cache.size()) break;
+	    if (!in_range_for(cache, index)) break;
             
             const Range &range = cache[index];
             if (range.max() > max || got == 0) max = range.max();
@@ -448,7 +446,7 @@
     }
 
 #ifdef DEBUG_WAVE_FILE_MODEL
-    SVDEBUG << "returning " << ranges.size() << " ranges" << endl;
+    cerr << "returning " << ranges.size() << " ranges" << endl;
 #endif
     return;
 }
@@ -573,7 +571,7 @@
                                         sqrt(2.) + 0.01));
     
     sv_frame_t frame = 0;
-    const sv_frame_t readBlockSize = 16384;
+    const sv_frame_t readBlockSize = 32768;
     vector<float> block;
 
     if (!m_model.isOK()) return;
@@ -583,7 +581,9 @@
 
     if (updating) {
         while (channels == 0 && !m_model.m_exiting) {
-//            SVDEBUG << "ReadOnlyWaveFileModel::fill: Waiting for channels..." << endl;
+#ifdef DEBUG_WAVE_FILE_MODEL
+            cerr << "ReadOnlyWaveFileModel::fill: Waiting for channels..." << endl;
+#endif
             sleep(1);
             channels = m_model.getChannelCount();
         }
@@ -604,38 +604,38 @@
         updating = m_model.m_reader->isUpdating();
         m_frameCount = m_model.getFrameCount();
 
-//        SVDEBUG << "ReadOnlyWaveFileModel::fill: frame = " << frame << ", count = " << m_frameCount << endl;
+        m_model.m_mutex.lock();
 
         while (frame < m_frameCount) {
 
-//            SVDEBUG << "ReadOnlyWaveFileModel::fill inner loop: frame = " << frame << ", count = " << m_frameCount << ", blocksize " << readBlockSize << endl;
+            m_model.m_mutex.unlock();
+
+#ifdef DEBUG_WAVE_FILE_MODEL
+            cerr << "ReadOnlyWaveFileModel::fill inner loop: frame = " << frame << ", count = " << m_frameCount << ", blocksize " << readBlockSize << endl;
+#endif
 
             if (updating && (frame + readBlockSize > m_frameCount)) break;
 
             block = m_model.m_reader->getInterleavedFrames(frame, readBlockSize);
 
-//            cerr << "block is " << block.size() << endl;
+            sv_frame_t gotBlockSize = block.size() / channels;
 
-            for (sv_frame_t i = 0; i < readBlockSize; ++i) {
+            m_model.m_mutex.lock();
+
+            for (sv_frame_t i = 0; i < gotBlockSize; ++i) {
 		
-                if (channels * i + channels > (int)block.size()) break;
-
                 for (int ch = 0; ch < channels; ++ch) {
 
                     sv_frame_t index = channels * i + ch;
                     float sample = block[index];
                     
-                    for (int cacheType = 0; cacheType < 2; ++cacheType) { // cache type
-                        
+                    for (int cacheType = 0; cacheType < 2; ++cacheType) {
                         sv_frame_t rangeIndex = ch * 2 + cacheType;
                         range[rangeIndex].sample(sample);
                         means[rangeIndex] += fabsf(sample);
                     }
                 }
 
-                //!!! this looks like a ludicrous way to do synchronisation
-                QMutexLocker locker(&m_model.m_mutex);
-
                 for (int cacheType = 0; cacheType < 2; ++cacheType) {
 
                     if (++count[cacheType] == cacheBlockSize[cacheType]) {
@@ -655,18 +655,16 @@
                 
                 ++frame;
             }
-            
+
             if (m_model.m_exiting) break;
-            
             m_fillExtent = frame;
         }
 
-//        cerr << "ReadOnlyWaveFileModel: inner loop ended" << endl;
-
+        m_model.m_mutex.unlock();
+            
         first = false;
         if (m_model.m_exiting) break;
         if (updating) {
-//            cerr << "sleeping..." << endl;
             sleep(1);
         }
     }
--- a/plugin/FeatureExtractionPluginFactory.cpp	Tue Oct 20 12:54:06 2015 +0100
+++ b/plugin/FeatureExtractionPluginFactory.cpp	Thu Feb 04 11:13:39 2016 +0000
@@ -30,6 +30,8 @@
 
 #include "base/Profiler.h"
 
+using namespace std;
+
 //#define DEBUG_PLUGIN_SCAN_AND_INSTANTIATE 1
 
 class PluginDeletionNotifyAdapter : public Vamp::HostExt::PluginWrapper {
@@ -77,25 +79,25 @@
     return instance(type);
 }
 
-std::vector<QString>
+vector<QString>
 FeatureExtractionPluginFactory::getPluginPath()
 {
     if (!m_pluginPath.empty()) return m_pluginPath;
 
-    std::vector<std::string> p = Vamp::PluginHostAdapter::getPluginPath();
+    vector<string> p = Vamp::PluginHostAdapter::getPluginPath();
     for (size_t i = 0; i < p.size(); ++i) m_pluginPath.push_back(p[i].c_str());
     return m_pluginPath;
 }
 
-std::vector<QString>
+vector<QString>
 FeatureExtractionPluginFactory::getAllPluginIdentifiers()
 {
     FeatureExtractionPluginFactory *factory;
-    std::vector<QString> rv;
+    vector<QString> rv;
     
     factory = instance("vamp");
     if (factory) {
-	std::vector<QString> tmp = factory->getPluginIdentifiers();
+	vector<QString> tmp = factory->getPluginIdentifiers();
 	for (size_t i = 0; i < tmp.size(); ++i) {
 //            cerr << "identifier: " << tmp[i] << endl;
 	    rv.push_back(tmp[i]);
@@ -108,103 +110,165 @@
     return rv;
 }
 
-std::vector<QString>
+vector<QString>
+FeatureExtractionPluginFactory::getPluginCandidateFiles()
+{
+    vector<QString> path = getPluginPath();
+    vector<QString> candidates;
+
+    for (QString dirname : path) {
+
+#ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
+        SVDEBUG << "FeatureExtractionPluginFactory::getPluginIdentifiers: scanning directory " << dirname << endl;
+#endif
+
+	QDir pluginDir(dirname, PLUGIN_GLOB,
+                       QDir::Name | QDir::IgnoreCase,
+                       QDir::Files | QDir::Readable);
+
+	for (unsigned int j = 0; j < pluginDir.count(); ++j) {
+            QString soname = pluginDir.filePath(pluginDir[j]);
+            candidates.push_back(soname);
+        }
+    }
+
+    return candidates;
+}
+
+vector<QString>
+FeatureExtractionPluginFactory::winnowPluginCandidates(vector<QString> candidates,
+                                                       QString &warningMessage)
+{
+    vector<QString> good, bad;
+    vector<PluginLoadStatus> badStatuses;
+    
+    for (QString c: candidates) {
+
+        PluginLoadStatus status =
+            TestPluginLoadability(c, "vampGetPluginDescriptor");
+
+        if (status == PluginLoadOK) {
+            good.push_back(c);
+        } else if (status == UnknownPluginLoadStatus) {
+            cerr << "WARNING: Unknown load status for plugin candidate \""
+                 << c << "\", continuing" << endl;
+            good.push_back(c);
+        } else {
+            bad.push_back(c);
+            badStatuses.push_back(status);
+        }
+    }
+    
+    if (!bad.empty()) {
+        warningMessage =
+            QObject::tr("<b>Failed to load plugins</b>"
+                        "<p>Failed to load one or more plugin libraries:</p>\n");
+        warningMessage += "<ul>";
+        for (int i = 0; in_range_for(bad, i); ++i) {
+            QString m;
+            if (badStatuses[i] == PluginLoadFailedToLoadLibrary) {
+                m = QObject::tr("Failed to load library");
+            } else if (badStatuses[i] == PluginLoadFailedToFindDescriptor) {
+                m = QObject::tr("Failed to query plugins from library after loading");
+            } else if (badStatuses[i] == PluginLoadFailedElsewhere) {
+                m = QObject::tr("Unknown failure");
+            } else {
+                m = QObject::tr("Success: internal error?");
+            }
+            warningMessage += QString("<li>%1 (%2)</li>\n")
+                .arg(bad[i])
+                .arg(m);
+        }
+        warningMessage += "</ul>";
+    }
+    return good;
+}
+
+vector<QString>
 FeatureExtractionPluginFactory::getPluginIdentifiers()
 {
     Profiler profiler("FeatureExtractionPluginFactory::getPluginIdentifiers");
 
-    std::vector<QString> rv;
-    std::vector<QString> path = getPluginPath();
+    vector<QString> rv;
+    vector<QString> candidates = winnowPluginCandidates(getPluginCandidateFiles(),
+                                                        m_pluginScanError);
     
-    for (std::vector<QString>::iterator i = path.begin(); i != path.end(); ++i) {
+    for (QString soname : candidates) {
 
 #ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
-        SVDEBUG << "FeatureExtractionPluginFactory::getPluginIdentifiers: scanning directory " << i-<< endl;
+        SVDEBUG << "FeatureExtractionPluginFactory::getPluginIdentifiers: trying potential library " << soname << endl;
 #endif
 
-	QDir pluginDir(*i, PLUGIN_GLOB,
-                       QDir::Name | QDir::IgnoreCase,
-                       QDir::Files | QDir::Readable);
-
-	for (unsigned int j = 0; j < pluginDir.count(); ++j) {
-
-            QString soname = pluginDir.filePath(pluginDir[j]);
+        void *libraryHandle = DLOPEN(soname, RTLD_LAZY | RTLD_LOCAL);
+            
+        if (!libraryHandle) {
+            cerr << "WARNING: FeatureExtractionPluginFactory::getPluginIdentifiers: Failed to load library " << soname << ": " << DLERROR() << endl;
+            continue;
+        }
 
 #ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
-            SVDEBUG << "FeatureExtractionPluginFactory::getPluginIdentifiers: trying potential library " << soname << endl;
+        SVDEBUG << "FeatureExtractionPluginFactory::getPluginIdentifiers: It's a library all right, checking for descriptor" << endl;
 #endif
 
-            void *libraryHandle = DLOPEN(soname, RTLD_LAZY | RTLD_LOCAL);
-            
-            if (!libraryHandle) {
-                cerr << "WARNING: FeatureExtractionPluginFactory::getPluginIdentifiers: Failed to load library " << soname << ": " << DLERROR() << endl;
-                continue;
+        VampGetPluginDescriptorFunction fn = (VampGetPluginDescriptorFunction)
+            DLSYM(libraryHandle, "vampGetPluginDescriptor");
+
+        if (!fn) {
+            cerr << "WARNING: FeatureExtractionPluginFactory::getPluginIdentifiers: No descriptor function in " << soname << endl;
+            if (DLCLOSE(libraryHandle) != 0) {
+                cerr << "WARNING: FeatureExtractionPluginFactory::getPluginIdentifiers: Failed to unload library " << soname << endl;
+            }
+            continue;
+        }
+
+#ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
+        SVDEBUG << "FeatureExtractionPluginFactory::getPluginIdentifiers: Vamp descriptor found" << endl;
+#endif
+
+        const VampPluginDescriptor *descriptor = 0;
+        int index = 0;
+
+        map<string, int> known;
+        bool ok = true;
+
+        while ((descriptor = fn(VAMP_API_VERSION, index))) {
+
+            if (known.find(descriptor->identifier) != known.end()) {
+                cerr << "WARNING: FeatureExtractionPluginFactory::getPluginIdentifiers: Plugin library "
+                     << soname
+                     << " returns the same plugin identifier \""
+                     << descriptor->identifier << "\" at indices "
+                     << known[descriptor->identifier] << " and "
+                     << index << endl;
+                cerr << "FeatureExtractionPluginFactory::getPluginIdentifiers: Avoiding this library (obsolete API?)" << endl;
+                ok = false;
+                break;
+            } else {
+                known[descriptor->identifier] = index;
             }
 
-#ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
-            SVDEBUG << "FeatureExtractionPluginFactory::getPluginIdentifiers: It's a library all right, checking for descriptor" << endl;
-#endif
+            ++index;
+        }
 
-            VampGetPluginDescriptorFunction fn = (VampGetPluginDescriptorFunction)
-                DLSYM(libraryHandle, "vampGetPluginDescriptor");
+        if (ok) {
 
-            if (!fn) {
-                cerr << "WARNING: FeatureExtractionPluginFactory::getPluginIdentifiers: No descriptor function in " << soname << endl;
-                if (DLCLOSE(libraryHandle) != 0) {
-                    cerr << "WARNING: FeatureExtractionPluginFactory::getPluginIdentifiers: Failed to unload library " << soname << endl;
-                }
-                continue;
-            }
-
-#ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
-            SVDEBUG << "FeatureExtractionPluginFactory::getPluginIdentifiers: Vamp descriptor found" << endl;
-#endif
-
-            const VampPluginDescriptor *descriptor = 0;
-            int index = 0;
-
-            std::map<std::string, int> known;
-            bool ok = true;
+            index = 0;
 
             while ((descriptor = fn(VAMP_API_VERSION, index))) {
 
-                if (known.find(descriptor->identifier) != known.end()) {
-                    cerr << "WARNING: FeatureExtractionPluginFactory::getPluginIdentifiers: Plugin library "
-                              << soname
-                              << " returns the same plugin identifier \""
-                              << descriptor->identifier << "\" at indices "
-                              << known[descriptor->identifier] << " and "
-                              << index << endl;
-                    SVDEBUG << "FeatureExtractionPluginFactory::getPluginIdentifiers: Avoiding this library (obsolete API?)" << endl;
-                    ok = false;
-                    break;
-                } else {
-                    known[descriptor->identifier] = index;
-                }
-
+                QString id = PluginIdentifier::createIdentifier
+                    ("vamp", soname, descriptor->identifier);
+                rv.push_back(id);
+#ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
+                SVDEBUG << "FeatureExtractionPluginFactory::getPluginIdentifiers: Found plugin id " << id << " at index " << index << endl;
+#endif
                 ++index;
             }
-
-            if (ok) {
-
-                index = 0;
-
-                while ((descriptor = fn(VAMP_API_VERSION, index))) {
-
-                    QString id = PluginIdentifier::createIdentifier
-                        ("vamp", soname, descriptor->identifier);
-                    rv.push_back(id);
-#ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
-                    SVDEBUG << "FeatureExtractionPluginFactory::getPluginIdentifiers: Found plugin id " << id << " at index " << index << endl;
-#endif
-                    ++index;
-                }
-            }
+        }
             
-            if (DLCLOSE(libraryHandle) != 0) {
-                cerr << "WARNING: FeatureExtractionPluginFactory::getPluginIdentifiers: Failed to unload library " << soname << endl;
-            }
-	}
+        if (DLCLOSE(libraryHandle) != 0) {
+            cerr << "WARNING: FeatureExtractionPluginFactory::getPluginIdentifiers: Failed to unload library " << soname << endl;
+        }
     }
 
     generateTaxonomy();
@@ -279,8 +343,8 @@
             if (file != "") return file;
         }
 
-        std::vector<QString> path = getPluginPath();
-        for (std::vector<QString>::iterator i = path.begin();
+        vector<QString> path = getPluginPath();
+        for (vector<QString>::iterator i = path.begin();
              i != path.end(); ++i) {
             if (*i != "") {
                 file = findPluginFile(soname, *i);
@@ -312,7 +376,9 @@
     QString type, soname, label;
     PluginIdentifier::parseIdentifier(identifier, type, soname, label);
     if (type != "vamp") {
-	SVDEBUG << "FeatureExtractionPluginFactory::instantiatePlugin: Wrong factory for plugin type " << type << endl;
+#ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
+        cerr << "FeatureExtractionPluginFactory::instantiatePlugin: Wrong factory for plugin type " << type << endl;
+#endif
 	return 0;
     }
 
@@ -324,7 +390,7 @@
     } else if (found != soname) {
 
 #ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
-        SVDEBUG << "FeatureExtractionPluginFactory::instantiatePlugin: Given library name was " << soname << ", found at " << found << endl;
+        cerr << "FeatureExtractionPluginFactory::instantiatePlugin: Given library name was " << soname << ", found at " << found << endl;
         cerr << soname << " -> " << found << endl;
 #endif
 
@@ -343,7 +409,7 @@
         DLSYM(libraryHandle, "vampGetPluginDescriptor");
     
     if (!fn) {
-        SVDEBUG << "FeatureExtractionPluginFactory::instantiatePlugin: No descriptor function in " << soname << endl;
+        cerr << "FeatureExtractionPluginFactory::instantiatePlugin: No descriptor function in " << soname << endl;
         goto done;
     }
 
@@ -375,7 +441,9 @@
         }
     }
 
-//    SVDEBUG << "FeatureExtractionPluginFactory::instantiatePlugin: Instantiated plugin " << label << " from library " << soname << ": descriptor " << descriptor << ", rv "<< rv << ", label " << rv->getName() << ", outputs " << rv->getOutputDescriptors().size() << endl;
+#ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
+    cerr << "FeatureExtractionPluginFactory::instantiatePlugin: Instantiated plugin " << label << " from library " << soname << ": descriptor " << descriptor << ", rv "<< rv << ", label " << rv->getName() << ", outputs " << rv->getOutputDescriptors().size() << endl;
+#endif
     
     return rv;
 }
@@ -385,7 +453,9 @@
 {
     void *handle = m_handleMap[plugin];
     if (handle) {
-//        SVDEBUG << "unloading library " << handle << " for plugin " << plugin << endl;
+#ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
+        cerr << "unloading library " << handle << " for plugin " << plugin << endl;
+#endif
         DLCLOSE(handle);
     }
     m_handleMap.erase(plugin);
@@ -400,8 +470,8 @@
 void
 FeatureExtractionPluginFactory::generateTaxonomy()
 {
-    std::vector<QString> pluginPath = getPluginPath();
-    std::vector<QString> path;
+    vector<QString> pluginPath = getPluginPath();
+    vector<QString> path;
 
     for (size_t i = 0; i < pluginPath.size(); ++i) {
 	if (pluginPath[i].contains("/lib/")) {
--- a/plugin/FeatureExtractionPluginFactory.h	Tue Oct 20 12:54:06 2015 +0100
+++ b/plugin/FeatureExtractionPluginFactory.h	Thu Feb 04 11:13:39 2016 +0000
@@ -38,6 +38,14 @@
 
     virtual std::vector<QString> getPluginIdentifiers();
 
+    /**
+     * Return any error message arising from the initial plugin
+     * scan. The return value will either be an empty string (nothing
+     * to report) or an HTML string suitable for dropping into a
+     * dialog and showing the user.
+     */
+    virtual QString getPluginPopulationWarning() { return m_pluginScanError; }
+    
     virtual QString findPluginFile(QString soname, QString inDir = "");
 
     // We don't set blockSize or channels on this -- they're
@@ -57,8 +65,14 @@
     friend class PluginDeletionNotifyAdapter;
     void pluginDeleted(Vamp::Plugin *);
     std::map<Vamp::Plugin *, void *> m_handleMap;
+    
+    std::vector<QString> getPluginCandidateFiles();
+    std::vector<QString> winnowPluginCandidates(std::vector<QString> candidates,
+                                                QString &warningMessage);
+    
+    void generateTaxonomy();
 
-    void generateTaxonomy();
+    QString m_pluginScanError;
 };
 
 #endif
--- a/svcore.pro	Tue Oct 20 12:54:06 2015 +0100
+++ b/svcore.pro	Thu Feb 04 11:13:39 2016 +0000
@@ -70,7 +70,6 @@
            base/RealTime.h \
            base/RecentFiles.h \
            base/Resampler.h \
-           base/ResizeableBitset.h \
            base/ResourceFinder.h \
            base/RingBuffer.h \
            base/Scavenger.h \
--- a/system/System.cpp	Tue Oct 20 12:54:06 2015 +0100
+++ b/system/System.cpp	Thu Feb 04 11:13:39 2016 +0000
@@ -325,3 +325,66 @@
 double princarg(double a) { return mod(a + M_PI, -2 * M_PI) + M_PI; }
 float princargf(float a) { return float(princarg(a)); }
 
+#ifndef _WIN32
+
+#include <unistd.h>
+#include <sys/wait.h>
+
+PluginLoadStatus
+TestPluginLoadability(QString soname, QString descriptorFn)
+{
+    //!!! This is POSIX only, no equivalent on Windows, where we'll
+    //!!! have to do something completely different
+    
+    pid_t pid = fork();
+
+    if (pid < 0) {
+        return UnknownPluginLoadStatus; // fork failed
+    }
+
+    if (pid == 0) { // the child process
+
+        void *handle = DLOPEN(soname, RTLD_NOW | RTLD_LOCAL);
+        if (!handle) {
+            cerr << "isPluginLibraryLoadable: Failed to open plugin library \""
+                 << soname << "\": " << dlerror() << "\n";
+            cerr << "exiting with status 1" << endl;
+            exit(1);
+        }
+
+        void *fn = DLSYM(handle, descriptorFn.toLocal8Bit().data());
+        if (!fn) {
+            cerr << "isPluginLibraryLoadable: Failed to find plugin descriptor function \"" << descriptorFn << "\" in library \"" << soname << "\": " << dlerror() << "\n";
+            exit(2);
+        }
+
+//        cerr << "isPluginLibraryLoadable: Successfully loaded library \"" << soname << "\" and retrieved descriptor function" << endl;
+        
+        exit(0);
+
+    } else { // the parent process
+
+        int status = 0;
+
+        do {
+            waitpid(pid, &status, 0);
+        } while (WIFSTOPPED(status));
+
+        if (WIFEXITED(status)) {
+            switch (WEXITSTATUS(status)) {
+            case 0: return PluginLoadOK; // success
+            case 1: return PluginLoadFailedToLoadLibrary;
+            case 2: return PluginLoadFailedToFindDescriptor;
+            default: return PluginLoadFailedElsewhere;
+            }
+        }
+
+        if (WIFSIGNALED(status)) { 
+            return PluginLoadFailedElsewhere;
+        }
+
+        return UnknownPluginLoadStatus;
+    }
+}
+
+#endif
--- a/system/System.h	Tue Oct 20 12:54:06 2015 +0100
+++ b/system/System.h	Thu Feb 04 11:13:39 2016 +0000
@@ -154,6 +154,21 @@
 extern void StoreStartupLocale();
 extern void RestoreStartupLocale();
 
+enum PluginLoadStatus {
+    UnknownPluginLoadStatus,
+    PluginLoadOK,
+    PluginLoadFailedToLoadLibrary,
+    PluginLoadFailedToFindDescriptor,
+    PluginLoadFailedElsewhere
+};
+
+// Check whether a plugin library is loadable without crashing (may
+// need to spawn an external process to do it). Descriptor fn is the
+// name of a LADSPA/DSSI/Vamp-style descriptor function to try
+// calling; may be an empty string if the plugin doesn't follow that
+// convention.
+PluginLoadStatus TestPluginLoadability(QString soname, QString descriptorFn);
+
 #include <cmath>
 
 #ifndef M_PI
--- a/transform/FeatureExtractionModelTransformer.cpp	Tue Oct 20 12:54:06 2015 +0100
+++ b/transform/FeatureExtractionModelTransformer.cpp	Thu Feb 04 11:13:39 2016 +0000
@@ -1007,8 +1007,7 @@
 	
     } else if (isOutput<EditableDenseThreeDimensionalModel>(n)) {
 	
-	DenseThreeDimensionalModel::Column values =
-            DenseThreeDimensionalModel::Column::fromStdVector(feature.values);
+	DenseThreeDimensionalModel::Column values = feature.values;
 	
 	EditableDenseThreeDimensionalModel *model =
             getConformingOutput<EditableDenseThreeDimensionalModel>(n);
--- a/transform/TransformFactory.cpp	Tue Oct 20 12:54:06 2015 +0100
+++ b/transform/TransformFactory.cpp	Thu Feb 04 11:13:39 2016 +0000
@@ -399,6 +399,18 @@
     m_transformsPopulated = true;
 }
 
+QString
+TransformFactory::getPluginPopulationWarning()
+{
+    FeatureExtractionPluginFactory *vfactory =
+        FeatureExtractionPluginFactory::instance("vamp");
+    QString warningMessage;
+    if (vfactory) {
+        warningMessage = vfactory->getPluginPopulationWarning();
+    }
+    return warningMessage;
+}
+
 void
 TransformFactory::populateFeatureExtractionPlugins(TransformDescriptionMap &transforms)
 {
--- a/transform/TransformFactory.h	Tue Oct 20 12:54:06 2015 +0100
+++ b/transform/TransformFactory.h	Thu Feb 04 11:13:39 2016 +0000
@@ -196,6 +196,14 @@
     void setParametersFromPluginConfigurationXml(Transform &transform,
                                                  QString xml);
 
+    /**
+     * Return any error message arising from the initial plugin
+     * scan. The return value will either be an empty string (nothing
+     * to report) or an HTML string suitable for dropping into a
+     * dialog and showing the user.
+     */
+    QString getPluginPopulationWarning();
+    
 protected:
     typedef std::map<TransformId, TransformDescription> TransformDescriptionMap;