changeset 115:95de6db296a1

Merge from multiplex branch
author Chris Cannam
date Fri, 03 Oct 2014 15:07:19 +0100
parents 74f7ad72fee6 (current diff) 34a0dad473c3 (diff)
children 1c0799754670
files
diffstat 7 files changed, 274 insertions(+), 52 deletions(-) [+]
line wrap: on
line diff
--- a/.hgsubstate	Thu Oct 02 15:21:16 2014 +0100
+++ b/.hgsubstate	Fri Oct 03 15:07:19 2014 +0100
@@ -1,2 +1,2 @@
 d16f0fd6db6104d87882bc43788a3bb1b0f8c528 dataquay
-58c4d69b4dd8ff05908097ea2d5c520067b12aa4 svcore
+c9d456b1fcde4ddc6e0347c4eb105a8b6e4ca527 svcore
--- a/runner.pro	Thu Oct 02 15:21:16 2014 +0100
+++ b/runner.pro	Fri Oct 03 15:07:19 2014 +0100
@@ -67,14 +67,16 @@
 	runner/AudioDBFeatureWriter.h \
         runner/FeatureWriterFactory.h  \
         runner/DefaultFeatureWriter.h \
-        runner/FeatureExtractionManager.h
+        runner/FeatureExtractionManager.h \
+        runner/MultiplexedReader.h
 
 SOURCES += \
 	runner/main.cpp \
 	runner/DefaultFeatureWriter.cpp \
 	runner/FeatureExtractionManager.cpp \
         runner/AudioDBFeatureWriter.cpp \
-        runner/FeatureWriterFactory.cpp
+        runner/FeatureWriterFactory.cpp \
+        runner/MultiplexedReader.cpp
 
 !win32 {
     QMAKE_POST_LINK=/bin/bash tests/test.sh
--- a/runner/FeatureExtractionManager.cpp	Thu Oct 02 15:21:16 2014 +0100
+++ b/runner/FeatureExtractionManager.cpp	Fri Oct 03 15:07:19 2014 +0100
@@ -14,6 +14,7 @@
 */
 
 #include "FeatureExtractionManager.h"
+#include "MultiplexedReader.h"
 
 #include <vamp-hostsdk/PluginChannelAdapter.h>
 #include <vamp-hostsdk/PluginBufferingAdapter.h>
@@ -431,7 +432,7 @@
     return addFeatureExtractor(transform, writers);
 }
 
-void FeatureExtractionManager::addSource(QString audioSource)
+void FeatureExtractionManager::addSource(QString audioSource, bool willMultiplex)
 {
     std::cerr << "Have audio source: \"" << audioSource.toStdString() << "\"" << std::endl;
 
@@ -467,10 +468,12 @@
 
         cerr << "File or URL \"" << audioSource.toStdString() << "\" opened successfully" << endl;
 
-        if (m_channels == 0) {
-            m_channels = reader->getChannelCount();
-            cerr << "Taking default channel count of "
-                 << reader->getChannelCount() << " from file" << endl;
+        if (!willMultiplex) {
+            if (m_channels == 0) {
+                m_channels = reader->getChannelCount();
+                cerr << "Taking default channel count of "
+                     << reader->getChannelCount() << " from file" << endl;
+            }
         }
 
         if (m_defaultSampleRate == 0) {
@@ -482,9 +485,15 @@
 
         m_readyReaders[audioSource] = reader;
     }
+
+    if (willMultiplex) {
+        ++m_channels; // channel count is simply number of sources
+        cerr << "Multiplexing, incremented target channel count to " 
+             << m_channels << endl;
+    }
 }
 
-void FeatureExtractionManager::extractFeatures(QString audioSource, bool force)
+void FeatureExtractionManager::extractFeatures(QString audioSource)
 {
     if (m_plugins.empty()) return;
 
@@ -499,13 +508,45 @@
             (audioSource, "internal error: have sources and plugins, but no channel count");
     }
 
+    AudioFileReader *reader = prepareReader(audioSource);
+    extractFeaturesFor(reader, audioSource); // Note this also deletes reader
+}
+
+void FeatureExtractionManager::extractFeaturesMultiplexed(QStringList sources)
+{
+    if (m_plugins.empty() || sources.empty()) return;
+
+    QString nominalSource = sources[0];
+
+    testOutputFiles(nominalSource);
+
+    if (m_sampleRate == 0) {
+        throw FileOperationFailed
+            (nominalSource, "internal error: have sources and plugins, but no sample rate");
+    }
+    if (m_channels == 0) {
+        throw FileOperationFailed
+            (nominalSource, "internal error: have sources and plugins, but no channel count");
+    }
+
+    QList<AudioFileReader *> readers;
+    foreach (QString source, sources) {
+        AudioFileReader *reader = prepareReader(source);
+        readers.push_back(reader);
+    }
+
+    AudioFileReader *reader = new MultiplexedReader(readers);
+    extractFeaturesFor(reader, nominalSource); // Note this also deletes reader
+}
+
+AudioFileReader *
+FeatureExtractionManager::prepareReader(QString source)
+{
     AudioFileReader *reader = 0;
-
-    if (m_readyReaders.contains(audioSource)) {
-        reader = m_readyReaders[audioSource];
-        m_readyReaders.remove(audioSource);
-        if (reader->getChannelCount() != m_channels ||
-            reader->getSampleRate() != m_sampleRate) {
+    if (m_readyReaders.contains(source)) {
+        reader = m_readyReaders[source];
+        m_readyReaders.remove(source);
+        if (reader->getSampleRate() != m_sampleRate) {
             // can't use this; open it again
             delete reader;
             reader = 0;
@@ -513,23 +554,30 @@
     }
     if (!reader) {
         ProgressPrinter retrievalProgress("Retrieving audio data...");
-        FileSource source(audioSource, &retrievalProgress);
-        source.waitForData();
+        FileSource fs(source, &retrievalProgress);
+        fs.waitForData();
         reader = AudioFileReaderFactory::createReader
-            (source, m_sampleRate, false, &retrievalProgress);
+            (fs, m_sampleRate, false, &retrievalProgress);
         retrievalProgress.done();
     }
+    if (!reader) {
+        throw FailedToOpenFile(source);
+    }
+    return reader;
+}
 
-    if (!reader) {
-        throw FailedToOpenFile(audioSource);
-    }
+void
+FeatureExtractionManager::extractFeaturesFor(AudioFileReader *reader,
+                                             QString audioSource)
+{
+    // Note: This also deletes reader
 
     cerr << "Audio file \"" << audioSource.toStdString() << "\": "
          << reader->getChannelCount() << "ch at " 
          << reader->getNativeRate() << "Hz" << endl;
     if (reader->getChannelCount() != m_channels ||
         reader->getNativeRate() != m_sampleRate) {
-        cerr << "NOTE: File will be mixed or resampled for processing: "
+        cerr << "NOTE: File will be mixed or resampled for processing, to: "
              << m_channels << "ch at " 
              << m_sampleRate << "Hz" << endl;
     }
--- a/runner/FeatureExtractionManager.h	Thu Oct 02 15:21:16 2014 +0100
+++ b/runner/FeatureExtractionManager.h	Fri Oct 03 15:07:19 2014 +0100
@@ -61,12 +61,17 @@
     // Make a note of an audio or playlist file which will be passed
     // to extractFeatures later.  Amongst other things, this may
     // initialise the default sample rate and channel count
-    void addSource(QString audioSource);
+    void addSource(QString audioSource, bool willMultiplex);
 
     // Extract features from the given audio or playlist file.  If the
     // file is a playlist and force is true, continue extracting even
     // if a file in the playlist fails.
-    void extractFeatures(QString audioSource, bool force);
+    void extractFeatures(QString audioSource);
+
+    // Extract features from the given audio files, multiplexing into
+    // a single "file" whose individual channels are mixdowns of the
+    // supplied sources.
+    void extractFeaturesMultiplexed(QStringList sources);
 
 private:
     // A plugin may have many outputs, so we can have more than one
@@ -112,6 +117,10 @@
     bool m_summariesOnly;
     Vamp::HostExt::PluginSummarisingAdapter::SegmentBoundaries m_boundaries;
 
+    AudioFileReader *prepareReader(QString audioSource);
+
+    void extractFeaturesFor(AudioFileReader *reader, QString audioSource);
+
     void writeSummaries(QString audioSource, Vamp::Plugin *);
 
     void writeFeatures(QString audioSource,
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/runner/MultiplexedReader.cpp	Fri Oct 03 15:07:19 2014 +0100
@@ -0,0 +1,106 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Annotator
+    A utility for batch feature extraction from audio files.
+    Mark Levy, Chris Sutton and Chris Cannam, Queen Mary, University of London.
+    Copyright 2007-2014 QMUL.
+
+    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 "MultiplexedReader.h"
+
+MultiplexedReader::MultiplexedReader(QList<AudioFileReader *> readers) : 
+    m_readers(readers)
+{
+    m_channelCount = readers.size();
+    m_sampleRate = readers[0]->getSampleRate();
+    
+    m_frameCount = 0;
+    m_quicklySeekable = true;
+    
+    foreach (AudioFileReader *r, m_readers) {
+	if (!r->isOK()) {
+	    m_channelCount = 0;
+	    m_error = r->getError();
+	} else {
+	    if (r->getFrameCount() > m_frameCount) {
+		m_frameCount = r->getFrameCount();
+	    }
+	    if (!r->isQuicklySeekable()) {
+		m_quicklySeekable = false;
+	    }
+	}
+    }
+}
+
+MultiplexedReader::~MultiplexedReader()
+{
+    foreach (AudioFileReader *r, m_readers) {
+	delete r;
+    }
+}
+
+void
+MultiplexedReader::getInterleavedFrames(int start, int frameCount,
+					SampleBlock &block) const
+{
+    int out_chans = m_readers.size();
+
+    // Allocate and zero
+    block = SampleBlock(frameCount * out_chans, 0.f);
+
+    for (int out_chan = 0; out_chan < out_chans; ++out_chan) {
+
+	AudioFileReader *reader = m_readers[out_chan];
+	SampleBlock readerBlock;
+	reader->getInterleavedFrames(start, frameCount, readerBlock);
+
+	int in_chans = reader->getChannelCount();
+
+	for (int frame = 0; frame < frameCount; ++frame) {
+
+            int out_index = frame * out_chans + out_chan;
+
+	    for (int in_chan = 0; in_chan < in_chans; ++in_chan) {
+                int in_index = frame * in_chans + in_chan;
+                if (in_index >= (int)readerBlock.size()) break;
+		block[out_index] += readerBlock[in_index];
+	    }
+
+            if (in_chans > 1) {
+                block[out_index] /= float(in_chans);
+            }
+	}
+    }
+}
+
+int
+MultiplexedReader::getDecodeCompletion() const
+{
+    int completion = 100;
+    foreach (AudioFileReader *r, m_readers) {
+	int c = r->getDecodeCompletion();
+	if (c < 100) {
+	    completion = c;
+	}
+    }
+    return completion;
+}
+
+bool
+MultiplexedReader::isUpdating() const
+{
+    foreach (AudioFileReader *r, m_readers) {
+	if (r->isUpdating()) return true;
+    }
+    return false;
+}
+
+
+    
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/runner/MultiplexedReader.h	Fri Oct 03 15:07:19 2014 +0100
@@ -0,0 +1,49 @@
+/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
+
+/*
+    Sonic Annotator
+    A utility for batch feature extraction from audio files.
+    Mark Levy, Chris Sutton and Chris Cannam, Queen Mary, University of London.
+    Copyright 2007-2014 QMUL.
+
+    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 _MULTIPLEXED_READER_H_
+#define _MULTIPLEXED_READER_H_
+
+#include "data/fileio/AudioFileReader.h"
+
+#include <QString>
+#include <QList>
+
+class MultiplexedReader : public AudioFileReader
+{
+    Q_OBJECT
+
+public:
+    // I take ownership of readers
+    MultiplexedReader(QList<AudioFileReader *> readers);
+    virtual ~MultiplexedReader();
+
+    virtual QString getError() const { return m_error; }
+    virtual bool isQuicklySeekable() const { return m_quicklySeekable; }
+
+    virtual void getInterleavedFrames(int start, int count,
+				      SampleBlock &frames) const;
+
+    virtual int getDecodeCompletion() const;
+
+    virtual bool isUpdating() const;
+
+protected:
+    QString m_error;
+    bool m_quicklySeekable;
+    QList<AudioFileReader *> m_readers;
+};
+
+#endif
--- a/runner/main.cpp	Thu Oct 02 15:21:16 2014 +0100
+++ b/runner/main.cpp	Fri Oct 03 15:07:19 2014 +0100
@@ -234,15 +234,12 @@
     cerr << "                      Summarise in segments, with segment boundaries" << endl;
     cerr << "                      at A, B, ... seconds." << endl;
     cerr << endl;
-
-/*!!! This feature not implemented yet (sniff)
     cerr << "  -m, --multiplex     If multiple input audio files are given, use mono" << endl;
     cerr << "                      mixdowns of all files as the input channels for a single" << endl;
     cerr << "                      invocation of each transform, instead of running the" << endl;
-    cerr << "                      transform against all files separately." << endl;
+    cerr << "                      transform against all files separately. The first file" << endl;
+    cerr << "                      will be used for output reference name and sample rate." << endl;
     cerr << endl;
-*/
-
     cerr << "  -r, --recursive     If any of the <audio> arguments is found to be a local" << endl;
     cerr << "                      directory, search the tree starting at that directory" << endl;
     cerr << "                      for all supported audio files and take all of those as" << endl;
@@ -395,7 +392,7 @@
     set<string> requestedDefaultTransforms;
     set<string> requestedSummaryTypes;
     bool force = false;
-//!!!    bool multiplex = false;
+    bool multiplex = false;
     bool recursive = false;
     bool list = false;
     bool summaryOnly = false;
@@ -518,13 +515,9 @@
                     }
                 }
             }
-/*!!!
         } else if (arg == "-m" || arg == "--multiplex") {
             multiplex = true;
-            cerr << myname.toStdString()
-                 << ": WARNING: Multiplex argument not yet implemented" << endl; //!!!
             continue;
-*/
         } else if (arg == "-r" || arg == "--recursive") {
             recursive = true;
             continue;
@@ -734,7 +727,7 @@
     for (QStringList::const_iterator i = sources.begin();
          i != sources.end(); ++i) {
         try {
-            manager.addSource(*i);
+            manager.addSource(*i, multiplex);
         } catch (const std::exception &e) {
             badSources.insert(*i);
             cerr << "ERROR: Failed to process file \"" << i->toStdString()
@@ -779,26 +772,41 @@
     }
 
     if (good) {
-        for (QStringList::const_iterator i = sources.begin();
-             i != sources.end(); ++i) {
-            if (badSources.contains(*i)) continue;
-            std::cerr << "Extracting features for: \"" << i->toStdString() << "\"" << std::endl;
+        QStringList goodSources;
+        foreach (QString source, sources) {
+            if (!badSources.contains(source)) {
+                goodSources.push_back(source);
+            }
+        }
+        if (multiplex) {
             try {
-                manager.extractFeatures(*i, force);
+                manager.extractFeaturesMultiplexed(goodSources);
             } catch (const std::exception &e) {
-                cerr << "ERROR: Feature extraction failed for \"" << i->toStdString()
-                     << "\": " << e.what() << endl;
-                if (force) {
-                    // print a note only if we have more files to process
-                    QStringList::const_iterator j = i;
-                    if (++j != sources.end()) {
-                        cerr << "NOTE: \"--force\" option was provided, continuing (more errors may occur)" << endl;
+                cerr << "ERROR: Feature extraction failed: "
+                     << e.what() << endl;
+            }
+        } else {
+            for (QStringList::const_iterator i = goodSources.begin();
+                 i != goodSources.end(); ++i) {
+                std::cerr << "Extracting features for: \"" << i->toStdString()
+                          << "\"" << std::endl;
+                try {
+                    manager.extractFeatures(*i);
+                } catch (const std::exception &e) {
+                    cerr << "ERROR: Feature extraction failed for \""
+                         << i->toStdString() << "\": " << e.what() << endl;
+                    if (force) {
+                        // print a note only if we have more files to process
+                        QStringList::const_iterator j = i;
+                        if (++j != sources.end()) {
+                            cerr << "NOTE: \"--force\" option was provided, continuing (more errors may occur)" << endl;
+                        }
+                    } else {
+                        cerr << "NOTE: If you want to continue with processing any further files after an" << endl
+                             << "error like this, use the --force option" << endl;
+                        good = false;
+                        break;
                     }
-                } else {
-                    cerr << "NOTE: If you want to continue with processing any further files after an" << endl
-                         << "error like this, use the --force option" << endl;
-                    good = false;
-                    break;
                 }
             }
         }