changeset 1172:59ae7e04f7e9

Merge
author Chris Cannam
date Fri, 04 Mar 2016 12:29:35 +0000
parents fa1bec83441e (current diff) 134ce7667256 (diff)
children abb78e824820 0ad516dc5d8d
files
diffstat 15 files changed, 368 insertions(+), 126 deletions(-) [+]
line wrap: on
line diff
--- a/data/fileio/AudioFileReaderFactory.cpp	Fri Mar 04 09:50:09 2016 +0000
+++ b/data/fileio/AudioFileReaderFactory.cpp	Fri Mar 04 12:29:35 2016 +0000
@@ -26,6 +26,8 @@
 #include <QFileInfo>
 #include <iostream>
 
+//#define DEBUG_AUDIO_FILE_READER_FACTORY 1
+
 QString
 AudioFileReaderFactory::getKnownExtensions()
 {
@@ -84,7 +86,9 @@
 {
     QString err;
 
-    SVDEBUG << "AudioFileReaderFactory::createReader(\"" << source.getLocation() << "\"): Requested rate: " << targetRate << endl;
+#ifdef DEBUG_AUDIO_FILE_READER_FACTORY
+    cerr << "AudioFileReaderFactory::createReader(\"" << source.getLocation() << "\"): Requested rate: " << targetRate << endl;
+#endif
 
     if (!source.isOK()) {
         cerr << "AudioFileReaderFactory::createReader(\"" << source.getLocation() << "\": Failed to retrieve source (transmission error?): " << source.getErrorString() << endl;
@@ -92,7 +96,7 @@
     }
 
     if (!source.isAvailable()) {
-        SVDEBUG << "AudioFileReaderFactory::createReader(\"" << source.getLocation() << "\": Source not found" << endl;
+        cerr << "AudioFileReaderFactory::createReader(\"" << source.getLocation() << "\": Source not found" << endl;
         return 0;
     }
 
@@ -112,8 +116,10 @@
              normalised ||
              (targetRate != 0 && fileRate != targetRate))) {
 
-            SVDEBUG << "AudioFileReaderFactory::createReader: WAV file rate: " << reader->getSampleRate() << ", normalised " << normalised << ", seekable " << reader->isQuicklySeekable() << ", creating decoding reader" << endl;
-
+#ifdef DEBUG_AUDIO_FILE_READER_FACTORY
+            cerr << "AudioFileReaderFactory::createReader: WAV file rate: " << reader->getSampleRate() << ", normalised " << normalised << ", seekable " << reader->isQuicklySeekable() << ", creating decoding reader" << endl;
+#endif
+            
             delete reader;
             reader = new DecodingWavFileReader
                 (source,
@@ -231,7 +237,9 @@
              normalised ||
              (targetRate != 0 && fileRate != targetRate))) {
 
-            SVDEBUG << "AudioFileReaderFactory::createReader: WAV file rate: " << reader->getSampleRate() << ", normalised " << normalised << ", seekable " << reader->isQuicklySeekable() << ", creating decoding reader" << endl;
+#ifdef DEBUG_AUDIO_FILE_READER_FACTORY
+            cerr << "AudioFileReaderFactory::createReader: WAV file rate: " << reader->getSampleRate() << ", normalised " << normalised << ", seekable " << reader->isQuicklySeekable() << ", creating decoding reader" << endl;
+#endif
 
             delete reader;
             reader = new DecodingWavFileReader
@@ -327,7 +335,9 @@
 
     if (reader) {
         if (reader->isOK()) {
-            SVDEBUG << "AudioFileReaderFactory: Reader is OK" << endl;
+#ifdef DEBUG_AUDIO_FILE_READER_FACTORY
+            cerr << "AudioFileReaderFactory: Reader is OK" << endl;
+#endif
             return reader;
         }
         cerr << "AudioFileReaderFactory: Preferred reader for "
--- a/data/fileio/CodedAudioFileReader.cpp	Fri Mar 04 09:50:09 2016 +0000
+++ b/data/fileio/CodedAudioFileReader.cpp	Fri Mar 04 12:29:35 2016 +0000
@@ -137,11 +137,27 @@
             }
             fileInfo.samplerate = fileRate;
             fileInfo.channels = m_channelCount;
-            
-            // No point in writing 24-bit or float; generally this
-            // class is used for decoding files that have come from a
-            // 16 bit source or that decode to only 16 bits anyway.
-            fileInfo.format = SF_FORMAT_WAV | SF_FORMAT_PCM_16;
+
+            // Previously we were writing SF_FORMAT_PCM_16 and in a
+            // comment I wrote: "No point in writing 24-bit or float;
+            // generally this class is used for decoding files that
+            // have come from a 16 bit source or that decode to only
+            // 16 bits anyway." That was naive -- we want to preserve
+            // the original values to the same float precision that we
+            // use internally. Saving PCM_16 obviously doesn't
+            // preserve values for sources at bit depths greater than
+            // 16, but it also doesn't always do so for sources at bit
+            // depths less than 16.
+            //
+            // (This came to light with a bug in libsndfile 1.0.26,
+            // which always reports every file as non-seekable, so
+            // that coded readers were being used even for WAV
+            // files. This changed the values that came from PCM_8 WAV
+            // sources, breaking Sonic Annotator's output comparison
+            // tests.)
+            //
+            // So: now we write floats.
+            fileInfo.format = SF_FORMAT_WAV | SF_FORMAT_FLOAT;
     
             m_cacheFileWritePtr = sf_open(m_cacheFileName.toLocal8Bit(),
                                           SFM_WRITE, &fileInfo);
--- a/data/fileio/MP3FileReader.cpp	Fri Mar 04 09:50:09 2016 +0000
+++ b/data/fileio/MP3FileReader.cpp	Fri Mar 04 12:29:35 2016 +0000
@@ -32,6 +32,7 @@
 #ifdef HAVE_ID3TAG
 #include <id3tag.h>
 #endif
+
 //#define DEBUG_ID3TAG 1
 
 #include <QFileInfo>
@@ -178,7 +179,7 @@
     id3_tag *tag = id3_file_tag(file);
     if (!tag) {
 #ifdef DEBUG_ID3TAG
-        SVDEBUG << "MP3FileReader::loadTags: No ID3 tag found" << endl;
+        cerr << "MP3FileReader::loadTags: No ID3 tag found" << endl;
 #endif
         id3_file_close(file);
         return;
@@ -201,7 +202,7 @@
 
 #else
 #ifdef DEBUG_ID3TAG
-    SVDEBUG << "MP3FileReader::loadTags: ID3 tag support not compiled in"
+    cerr << "MP3FileReader::loadTags: ID3 tag support not compiled in"
               << endl;
 #endif
 #endif
@@ -216,20 +217,20 @@
     id3_frame *frame = id3_tag_findframe(tag, name, 0);
     if (!frame) {
 #ifdef DEBUG_ID3TAG
-        SVDEBUG << "MP3FileReader::loadTags: No \"" << name << "\" in ID3 tag" << endl;
+        cerr << "MP3FileReader::loadTags: No \"" << name << "\" in ID3 tag" << endl;
 #endif
         return "";
     }
         
     if (frame->nfields < 2) {
-        SVDEBUG << "MP3FileReader::loadTags: WARNING: Not enough fields (" << frame->nfields << ") for \"" << name << "\" in ID3 tag" << endl;
+        cerr << "MP3FileReader::loadTags: WARNING: Not enough fields (" << frame->nfields << ") for \"" << name << "\" in ID3 tag" << endl;
         return "";
     }
 
     unsigned int nstrings = id3_field_getnstrings(&frame->fields[1]);
     if (nstrings == 0) {
 #ifdef DEBUG_ID3TAG
-        SVDEBUG << "MP3FileReader::loadTags: No strings for \"" << name << "\" in ID3 tag" << endl;
+        cerr << "MP3FileReader::loadTags: No strings for \"" << name << "\" in ID3 tag" << endl;
 #endif
         return "";
     }
@@ -237,7 +238,7 @@
     id3_ucs4_t const *ustr = id3_field_getstrings(&frame->fields[1], 0);
     if (!ustr) {
 #ifdef DEBUG_ID3TAG
-        SVDEBUG << "MP3FileReader::loadTags: Invalid or absent data for \"" << name << "\" in ID3 tag" << endl;
+        cerr << "MP3FileReader::loadTags: Invalid or absent data for \"" << name << "\" in ID3 tag" << endl;
 #endif
         return "";
     }
@@ -252,7 +253,7 @@
     free(u8str);
 
 #ifdef DEBUG_ID3TAG
-	SVDEBUG << "MP3FileReader::loadTags: tag \"" << name << "\" -> \""
+	cerr << "MP3FileReader::loadTags: tag \"" << name << "\" -> \""
 	<< rv << "\"" << endl;
 #endif
 
--- a/data/fileio/WavFileReader.cpp	Fri Mar 04 09:50:09 2016 +0000
+++ b/data/fileio/WavFileReader.cpp	Fri Mar 04 12:29:35 2016 +0000
@@ -60,20 +60,26 @@
 
         m_seekable = (m_fileInfo.seekable != 0);
 
-        // Our m_seekable reports whether a file is rapidly seekable,
-        // so things like Ogg don't qualify. We cautiously report
-        // every file type of "at least" the historical period of Ogg
-        // or FLAC as non-seekable.
         int type = m_fileInfo.format & SF_FORMAT_TYPEMASK;
-//        cerr << "WavFileReader: format type is " << type << " (flac, ogg are " << SF_FORMAT_FLAC << ", " << SF_FORMAT_OGG << ")" << endl;
+        int subtype = m_fileInfo.format & SF_FORMAT_SUBMASK;
+
         if (type >= SF_FORMAT_FLAC || type >= SF_FORMAT_OGG) {
-//            cerr << "WavFileReader: Recording as non-seekable" << endl;
+            // Our m_seekable reports whether a file is rapidly
+            // seekable, so things like Ogg don't qualify. We
+            // cautiously report every file type of "at least" the
+            // historical period of Ogg or FLAC as non-seekable.
             m_seekable = false;
+        } else if (type == SF_FORMAT_WAV && subtype <= SF_FORMAT_DOUBLE) {
+            // libsndfile 1.0.26 has a bug (subsequently fixed in the
+            // repo) that causes all files to be reported as
+            // non-seekable. We know that certain common file types
+            // are definitely seekable so, again cautiously, identify
+            // and mark those (basically only non-adaptive WAVs).
+            m_seekable = true;
         }
     }
 
-//    cerr << "WavFileReader: Frame count " << m_frameCount << ", channel count " << m_channelCount << ", sample rate " << m_sampleRate << ", seekable " << m_seekable << endl;
-
+//    cerr << "WavFileReader: Filename " << m_path << ", frame count " << m_frameCount << ", channel count " << m_channelCount << ", sample rate " << m_sampleRate << ", format " << m_fileInfo.format << ", seekable " << m_fileInfo.seekable << " adjusted to " << m_seekable << endl;
 }
 
 WavFileReader::~WavFileReader()
--- a/plugin/FeatureExtractionPluginFactory.cpp	Fri Mar 04 09:50:09 2016 +0000
+++ b/plugin/FeatureExtractionPluginFactory.cpp	Fri Mar 04 12:29:35 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
-        cerr << "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
-            cerr << "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
-            cerr << "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
-            cerr << "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;
-                    cerr << "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
-                    cerr << "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") {
-	cerr << "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;
     }
 
@@ -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	Fri Mar 04 09:50:09 2016 +0000
+++ b/plugin/FeatureExtractionPluginFactory.h	Fri Mar 04 12:29:35 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/rdf/RDFTransformFactory.cpp	Fri Mar 04 09:50:09 2016 +0000
+++ b/rdf/RDFTransformFactory.cpp	Fri Mar 04 12:29:35 2016 +0000
@@ -129,7 +129,12 @@
         }
         m_store->import(qurl, BasicStore::ImportIgnoreDuplicates);
         m_isRDF = true;
-    } catch (...) { }
+    } catch (const std::exception &e) {
+        // The file is not RDF -- we report this by returning false
+        // from isRDF (because we have not reached the m_isRDF = true
+        // line above), but we also set the error string
+        m_errorString = e.what();
+    }
 }
 
 RDFTransformFactoryImpl::~RDFTransformFactoryImpl()
@@ -146,7 +151,7 @@
 bool
 RDFTransformFactoryImpl::isOK()
 {
-    return (m_errorString == "");
+    return m_isRDF && (m_errorString == "");
 }
 
 QString
@@ -160,6 +165,8 @@
 {
     std::vector<Transform> transforms;
 
+    if (!m_isRDF) return transforms;
+    
     std::map<QString, Transform> uriTransformMap;
 
     Nodes tnodes = m_store->match
--- a/rdf/RDFTransformFactory.h	Fri Mar 04 09:50:09 2016 +0000
+++ b/rdf/RDFTransformFactory.h	Fri Mar 04 12:29:35 2016 +0000
@@ -36,8 +36,28 @@
     RDFTransformFactory(QString url);
     virtual ~RDFTransformFactory();
 
-    bool isRDF(); // true if the file was parseable and had transforms in it
-    bool isOK();  // true if the transforms could be completely constructed
+    /** isRDF() may be queried at any point after construction. It
+        returns true if the file was parseable as RDF.
+    */
+    bool isRDF();
+
+    /** isOK() may be queried at any point after getTransforms() has
+        been called. It is true if the file was parseable as RDF and
+        any transforms in it could be completely constructed.
+
+        Note that even if isOK() returns true, it is still possible
+        that the file did not define any transforms; in this case,
+        getTransforms() would have returned an empty list.
+
+        If isOK() is called before getTransforms() has been invoked to
+        query the file, it will return true iff isRDF() is true.
+    */
+    bool isOK();
+
+    /** Return any error string resulting from loading or querying the
+        file. This will be non-empty if isRDF() or isOK() returns
+        false.
+     */
     QString getErrorString() const;
 
     std::vector<Transform> getTransforms(ProgressReporter *reporter);
--- a/system/System.cpp	Fri Mar 04 09:50:09 2016 +0000
+++ b/system/System.cpp	Fri Mar 04 12:29:35 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	Fri Mar 04 09:50:09 2016 +0000
+++ b/system/System.h	Fri Mar 04 12:29:35 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/CSVFeatureWriter.cpp	Fri Mar 04 09:50:09 2016 +0000
+++ b/transform/CSVFeatureWriter.cpp	Fri Mar 04 12:29:35 2016 +0000
@@ -99,10 +99,10 @@
     SVDEBUG << "CSVFeatureWriter::setParameters" << endl;
     for (map<string, string>::iterator i = params.begin();
          i != params.end(); ++i) {
-        cerr << i->first << " -> " << i->second << endl;
+        SVDEBUG << i->first << " -> " << i->second << endl;
         if (i->first == "separator") {
             m_separator = i->second.c_str();
-            cerr << "m_separator = " << m_separator << endl;
+            SVDEBUG << "m_separator = " << m_separator << endl;
             if (m_separator == "\\t") {
                 m_separator = QChar::Tabulation;
             }
--- a/transform/Transform.cpp	Fri Mar 04 09:50:09 2016 +0000
+++ b/transform/Transform.cpp	Fri Mar 04 12:29:35 2016 +0000
@@ -54,12 +54,8 @@
     int errorColumn;
 
     if (!doc.setContent(xml, false, &error, &errorLine, &errorColumn)) {
-        cerr << "Transform::Transform: Error in parsing XML: "
-                  << error << " at line " << errorLine
-                  << ", column " << errorColumn << endl;
-        cerr << "Input follows:" << endl;
-        cerr << xml << endl;
-        cerr << "Input ends." << endl;
+        m_errorString = QString("%1 at line %2, column %3")
+            .arg(error).arg(errorLine).arg(errorColumn);
         return;
     }
     
--- a/transform/Transform.h	Fri Mar 04 09:50:09 2016 +0000
+++ b/transform/Transform.h	Fri Mar 04 12:29:35 2016 +0000
@@ -46,7 +46,8 @@
 
     /**
      * Construct a Transform by parsing the given XML data string.
-     * This is the inverse of toXml.
+     * This is the inverse of toXml. If this fails, getErrorString()
+     * will return a non-empty string.
      */
     Transform(QString xml);
 
@@ -156,6 +157,8 @@
      */
     void setFromXmlAttributes(const QXmlAttributes &);
 
+    QString getErrorString() const { return m_errorString; }
+    
     static SummaryType stringToSummaryType(QString);
     static QString summaryTypeToString(SummaryType);
 
@@ -195,6 +198,7 @@
     RealTime m_startTime;
     RealTime m_duration;
     sv_samplerate_t m_sampleRate;
+    QString m_errorString;
 };
 
 typedef std::vector<Transform> Transforms;
--- a/transform/TransformFactory.cpp	Fri Mar 04 09:50:09 2016 +0000
+++ b/transform/TransformFactory.cpp	Fri Mar 04 12:29:35 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	Fri Mar 04 09:50:09 2016 +0000
+++ b/transform/TransformFactory.h	Fri Mar 04 12:29:35 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;