# HG changeset patch # User Chris Cannam # Date 1413191797 -3600 # Node ID caf05503bf4228cf90e321221c3ef875007818f1 # Parent 565bc0c2295d9582a03db65e3e62960863900898# Parent c82736e79739f43ddeade2eead4f4857a5038db5 Merge from branch "with-dependencies" diff -r c82736e79739 -r caf05503bf42 .hgignore --- a/.hgignore Thu May 09 13:55:11 2013 +0100 +++ b/.hgignore Mon Oct 13 10:16:37 2014 +0100 @@ -10,7 +10,7 @@ *.so *.so.* *.a -*.wav *~ *.orig *.rej +cov-int diff -r c82736e79739 -r caf05503bf42 .hgsubstate --- a/.hgsubstate Thu May 09 13:55:11 2013 +0100 +++ b/.hgsubstate Mon Oct 13 10:16:37 2014 +0100 @@ -1,3 +1,3 @@ -1618ab0f5e89bb6a0c6242bea545fe76a5fcd755 dataquay -619f715526df43e23b2b9b50715e825941572352 sv-dependency-builds -9207c142b461b2ed347a16a1b7da49751257f8f6 svcore +d16f0fd6db6104d87882bc43788a3bb1b0f8c528 dataquay +879bdc878826bebec67130326f99397c430419b1 sv-dependency-builds +952005e252668fecdee23fed6227c52ba28cf0a3 svcore diff -r c82736e79739 -r caf05503bf42 .hgtags --- a/.hgtags Thu May 09 13:55:11 2013 +0100 +++ b/.hgtags Mon Oct 13 10:16:37 2014 +0100 @@ -9,3 +9,6 @@ e4652b60e469c8d09487c03c7a875f109696efaf sonic-annotator-0.7 e4652b60e469c8d09487c03c7a875f109696efaf sonic-annotator-0.7 ace08e6519a9e48210231c789889b25e82079ecf sonic-annotator-0.7 +52e5e2c03792209d1cd7561d34ecd0f3c33eb676 sonic-annotator-1.0 +52e5e2c03792209d1cd7561d34ecd0f3c33eb676 sonic-annotator-1.0 +5fe1f2efd40779acbdccc4c91a53693bd61f6d9b sonic-annotator-1.0 diff -r c82736e79739 -r caf05503bf42 CHANGELOG --- a/CHANGELOG Thu May 09 13:55:11 2013 +0100 +++ b/CHANGELOG Mon Oct 13 10:16:37 2014 +0100 @@ -1,3 +1,23 @@ + +Changes in Sonic Annotator 1.1 since the previous release 1.0: + +Front-end changes: + + - Add support for the start time and duration properties of a + transform, applying a plugin to only a range of the input audio + - Add --normalise to request each audio file be normalised to 1.0 max + - Add --multiplex option to compose multiple audio files into a + single multi-channel stream with one input file per channel + - Add --minversion option to permit scripts to check that the + version of Sonic Annotator is as they expect + +Bug fixes: + + - Fix the former habit of forging ahead even if not all transform + files could be found or parsed (this may have been intentional + behaviour but it is confusing more than it is useful) + - Fix failure to support --summary-only flag when reading transforms + with summaries from a transform file Changes in Sonic Annotator 1.0 since the previous release 0.7: diff -r c82736e79739 -r caf05503bf42 CITATION --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/CITATION Mon Oct 13 10:16:37 2014 +0100 @@ -0,0 +1,9 @@ +@article{SonicAnnotator, + author = {Chris Cannam and Michael O. Jewell and Christophe Rhodes and Mark Sandler and Mark d'Inverno}, + title = {Linked Data And You: Bringing music research software into the Semantic Web}, + journal = {Journal of New Music Research}, + number = {4}, + pages = {313-325}, + volume = {39}, + year = {2010} +} diff -r c82736e79739 -r caf05503bf42 README --- a/README Thu May 09 13:55:11 2013 +0100 +++ b/README Mon Oct 13 10:16:37 2014 +0100 @@ -25,7 +25,7 @@ The main program is by Mark Levy, Chris Cannam, and Chris Sutton. Sonic Annotator incorporates library code from the Sonic Visualiser application by Chris Cannam. Code copyright 2005-2007 Chris Cannam, -copyright 2006-2013 Queen Mary, University of London, except where +copyright 2006-2014 Queen Mary, University of London, except where indicated in the individual source files. This work was funded by the Engineering and Physical Sciences Research diff -r c82736e79739 -r caf05503bf42 configure --- a/configure Thu May 09 13:55:11 2013 +0100 +++ b/configure Mon Oct 13 10:16:37 2014 +0100 @@ -644,6 +644,8 @@ oggz_CFLAGS lrdf_LIBS lrdf_CFLAGS +liblo_LIBS +liblo_CFLAGS serd_LIBS serd_CFLAGS sord_LIBS @@ -759,6 +761,8 @@ sord_LIBS serd_CFLAGS serd_LIBS +liblo_CFLAGS +liblo_LIBS lrdf_CFLAGS lrdf_LIBS oggz_CFLAGS @@ -1422,6 +1426,9 @@ sord_LIBS linker flags for sord, overriding pkg-config serd_CFLAGS C compiler flags for serd, overriding pkg-config serd_LIBS linker flags for serd, overriding pkg-config + liblo_CFLAGS + C compiler flags for liblo, overriding pkg-config + liblo_LIBS linker flags for liblo, overriding pkg-config lrdf_CFLAGS C compiler flags for lrdf, overriding pkg-config lrdf_LIBS linker flags for lrdf, overriding pkg-config oggz_CFLAGS C compiler flags for oggz, overriding pkg-config @@ -5588,6 +5595,161 @@ +SV_MODULE_MODULE=liblo +SV_MODULE_VERSION_TEST="" +SV_MODULE_HEADER=lo/lo.h +SV_MODULE_LIB=lo +SV_MODULE_FUNC=lo_address_new +SV_MODULE_HAVE=HAVE_$(echo liblo | tr 'a-z' 'A-Z') +SV_MODULE_FAILED=1 +if test -n "$liblo_LIBS" ; then + { $as_echo "$as_me:${as_lineno-$LINENO}: User set ${SV_MODULE_MODULE}_LIBS explicitly, skipping test for $SV_MODULE_MODULE" >&5 +$as_echo "$as_me: User set ${SV_MODULE_MODULE}_LIBS explicitly, skipping test for $SV_MODULE_MODULE" >&6;} + CXXFLAGS="$CXXFLAGS $liblo_CFLAGS" + LIBS="$LIBS $liblo_LIBS" + SV_MODULE_FAILED="" +fi +if test -z "$SV_MODULE_VERSION_TEST" ; then + SV_MODULE_VERSION_TEST=$SV_MODULE_MODULE +fi +if test -n "$SV_MODULE_FAILED" && test -n "$PKG_CONFIG"; then + +pkg_failed=no +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for liblo" >&5 +$as_echo_n "checking for liblo... " >&6; } + +if test -n "$liblo_CFLAGS"; then + pkg_cv_liblo_CFLAGS="$liblo_CFLAGS" + elif test -n "$PKG_CONFIG"; then + if test -n "$PKG_CONFIG" && \ + { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"\$SV_MODULE_VERSION_TEST\""; } >&5 + ($PKG_CONFIG --exists --print-errors "$SV_MODULE_VERSION_TEST") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then + pkg_cv_liblo_CFLAGS=`$PKG_CONFIG --cflags "$SV_MODULE_VERSION_TEST" 2>/dev/null` + test "x$?" != "x0" && pkg_failed=yes +else + pkg_failed=yes +fi + else + pkg_failed=untried +fi +if test -n "$liblo_LIBS"; then + pkg_cv_liblo_LIBS="$liblo_LIBS" + elif test -n "$PKG_CONFIG"; then + if test -n "$PKG_CONFIG" && \ + { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"\$SV_MODULE_VERSION_TEST\""; } >&5 + ($PKG_CONFIG --exists --print-errors "$SV_MODULE_VERSION_TEST") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then + pkg_cv_liblo_LIBS=`$PKG_CONFIG --libs "$SV_MODULE_VERSION_TEST" 2>/dev/null` + test "x$?" != "x0" && pkg_failed=yes +else + pkg_failed=yes +fi + else + pkg_failed=untried +fi + + + +if test $pkg_failed = yes; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + +if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then + _pkg_short_errors_supported=yes +else + _pkg_short_errors_supported=no +fi + if test $_pkg_short_errors_supported = yes; then + liblo_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "$SV_MODULE_VERSION_TEST" 2>&1` + else + liblo_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "$SV_MODULE_VERSION_TEST" 2>&1` + fi + # Put the nasty error message in config.log where it belongs + echo "$liblo_PKG_ERRORS" >&5 + + { $as_echo "$as_me:${as_lineno-$LINENO}: Failed to find optional module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&5 +$as_echo "$as_me: Failed to find optional module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&6;} +elif test $pkg_failed = untried; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + { $as_echo "$as_me:${as_lineno-$LINENO}: Failed to find optional module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&5 +$as_echo "$as_me: Failed to find optional module $SV_MODULE_MODULE using pkg-config, trying again by old-fashioned means" >&6;} +else + liblo_CFLAGS=$pkg_cv_liblo_CFLAGS + liblo_LIBS=$pkg_cv_liblo_LIBS + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + HAVES="$HAVES $SV_MODULE_HAVE";CXXFLAGS="$CXXFLAGS $liblo_CFLAGS";LIBS="$LIBS $liblo_LIBS";SV_MODULE_FAILED="" +fi +fi +if test -n "$SV_MODULE_FAILED"; then + as_ac_Header=`$as_echo "ac_cv_header_$SV_MODULE_HEADER" | $as_tr_sh` +ac_fn_cxx_check_header_mongrel "$LINENO" "$SV_MODULE_HEADER" "$as_ac_Header" "$ac_includes_default" +if eval test \"x\$"$as_ac_Header"\" = x"yes"; then : + HAVES="$HAVES $SV_MODULE_HAVE";SV_MODULE_FAILED="" +else + { $as_echo "$as_me:${as_lineno-$LINENO}: Failed to find header $SV_MODULE_HEADER for optional module $SV_MODULE_MODULE" >&5 +$as_echo "$as_me: Failed to find header $SV_MODULE_HEADER for optional module $SV_MODULE_MODULE" >&6;} +fi + + + if test -z "$SV_MODULE_FAILED"; then + if test -n "$SV_MODULE_LIB"; then + as_ac_Lib=`$as_echo "ac_cv_lib_$SV_MODULE_LIB''_$SV_MODULE_FUNC" | $as_tr_sh` +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $SV_MODULE_FUNC in -l$SV_MODULE_LIB" >&5 +$as_echo_n "checking for $SV_MODULE_FUNC in -l$SV_MODULE_LIB... " >&6; } +if eval \${$as_ac_Lib+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-l$SV_MODULE_LIB $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char $SV_MODULE_FUNC (); +int +main () +{ +return $SV_MODULE_FUNC (); + ; + return 0; +} +_ACEOF +if ac_fn_cxx_try_link "$LINENO"; then : + eval "$as_ac_Lib=yes" +else + eval "$as_ac_Lib=no" +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +eval ac_res=\$$as_ac_Lib + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } +if eval test \"x\$"$as_ac_Lib"\" = x"yes"; then : + LIBS="$LIBS -l$SV_MODULE_LIB" +else + { $as_echo "$as_me:${as_lineno-$LINENO}: Failed to find library $SV_MODULE_LIB for optional module $SV_MODULE_MODULE" >&5 +$as_echo "$as_me: Failed to find library $SV_MODULE_LIB for optional module $SV_MODULE_MODULE" >&6;} +fi + + fi + fi +fi + + SV_MODULE_MODULE=lrdf SV_MODULE_VERSION_TEST="lrdf >= 0.2" SV_MODULE_HEADER=lrdf.h diff -r c82736e79739 -r caf05503bf42 configure.ac --- a/configure.ac Thu May 09 13:55:11 2013 +0100 +++ b/configure.ac Mon Oct 13 10:16:37 2014 +0100 @@ -84,6 +84,7 @@ SV_MODULE_REQUIRED([sord],[sord-0 >= 0.5],[sord/sord.h],[sord-0],[sord_world_new]) SV_MODULE_REQUIRED([serd],[serd-0 >= 0.5],[serd/serd.h],[serd-0],[serd_reader_read_file]) +SV_MODULE_OPTIONAL([liblo],[],[lo/lo.h],[lo],[lo_address_new]) SV_MODULE_OPTIONAL([lrdf],[lrdf >= 0.2],[lrdf.h],[lrdf],[lrdf_init]) SV_MODULE_OPTIONAL([oggz],[oggz >= 1.0.0],[oggz/oggz.h],[oggz],[oggz_run]) SV_MODULE_OPTIONAL([fishsound],[fishsound >= 1.0.0],[fishsound/fishsound.h],[fishsound],[fish_sound_new]) diff -r c82736e79739 -r caf05503bf42 platform-dataquay.pri --- a/platform-dataquay.pri Thu May 09 13:55:11 2013 +0100 +++ b/platform-dataquay.pri Mon Oct 13 10:16:37 2014 +0100 @@ -10,10 +10,17 @@ EXTRALIBS -= -lrdf DEFINES += USE_SORD -QMAKE_CXXFLAGS += -I/usr/local/include/sord-0 -I/usr/local/include/serd-0 -I/usr/include/sord-0 -I/usr/include/serd-0 -EXTRALIBS += -lsord-0 -lserd-0 +# Libraries and paths should be added by config.pri -win32-g++: { +win32-g++ { INCLUDEPATH += ../sv-dependency-builds/win32-mingw/include LIBS += -L../../sv-dependency-builds/win32-mingw/lib } +win32-msvc* { + INCLUDEPATH += ../sv-dependency-builds/win32-msvc/include + LIBS += -L../../sv-dependency-builds/win32-msvc/lib +} +mac* { + INCLUDEPATH += ../sv-dependency-builds/osx/include + LIBS += -L../sv-dependency-builds/osx/lib +} diff -r c82736e79739 -r caf05503bf42 runner.pro --- a/runner.pro Thu May 09 13:55:11 2013 +0100 +++ b/runner.pro Mon Oct 13 10:16:37 2014 +0100 @@ -8,14 +8,30 @@ INCLUDEPATH += sv-dependency-builds/win32-msvc/include LIBS += -Lsv-dependency-builds/win32-msvc/lib } +mac* { + INCLUDEPATH += sv-dependency-builds/osx/include + LIBS += -L../vamp-plugin-sdk -Lsv-dependency-builds/osx/lib +} exists(config.pri) { include(config.pri) } -win* { - !exists(config.pri) { - DEFINES += HAVE_BZ2 HAVE_FFTW3 HAVE_FFTW3F HAVE_SNDFILE HAVE_SAMPLERATE HAVE_VAMP HAVE_VAMPHOSTSDK HAVE_DATAQUAY HAVE_MAD HAVE_ID3TAG - LIBS += -lbz2 -lvamp-hostsdk -lfftw3 -lfftw3f -lsndfile -lFLAC -logg -lvorbis -lvorbisenc -lvorbisfile -logg -lmad -lid3tag -lsamplerate -lz -lsord-0 -lserd-0 -lwinmm -lws2_32 + +!exists(config.pri) { + + CONFIG += release + DEFINES += NDEBUG BUILD_RELEASE NO_TIMING + + DEFINES += HAVE_BZ2 HAVE_FFTW3 HAVE_FFTW3F HAVE_SNDFILE HAVE_SAMPLERATE HAVE_VAMP HAVE_VAMPHOSTSDK HAVE_DATAQUAY HAVE_MAD HAVE_ID3TAG + + LIBS += -lbz2 -lvamp-hostsdk -lfftw3 -lfftw3f -lsndfile -lFLAC -logg -lvorbis -lvorbisenc -lvorbisfile -logg -lmad -lid3tag -lsamplerate -lz -lsord-0 -lserd-0 + + win* { + LIBS += -lwinmm -lws2_32 + } + macx* { + DEFINES += HAVE_COREAUDIO + LIBS += -framework CoreAudio -framework CoreMidi -framework AudioUnit -framework AudioToolbox -framework CoreFoundation -framework CoreServices -framework Accelerate } } @@ -67,14 +83,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 diff -r c82736e79739 -r caf05503bf42 runner/FeatureExtractionManager.cpp --- a/runner/FeatureExtractionManager.cpp Thu May 09 13:55:11 2013 +0100 +++ b/runner/FeatureExtractionManager.cpp Mon Oct 13 10:16:37 2014 +0100 @@ -14,6 +14,7 @@ */ #include "FeatureExtractionManager.h" +#include "MultiplexedReader.h" #include #include @@ -40,7 +41,6 @@ #include "data/fileio/FileSource.h" #include "data/fileio/AudioFileReader.h" #include "data/fileio/AudioFileReaderFactory.h" -#include "data/fileio/PlaylistFileReader.h" #include "base/TempDirectory.h" #include "base/ProgressPrinter.h" #include "transform/TransformFactory.h" @@ -59,7 +59,8 @@ m_blockSize(16384), m_defaultSampleRate(0), m_sampleRate(0), - m_channels(0) + m_channels(0), + m_normalise(false) { } @@ -84,6 +85,11 @@ m_defaultSampleRate = sampleRate; } +void FeatureExtractionManager::setNormalise(bool normalise) +{ + m_normalise = normalise; +} + static PluginSummarisingAdapter::SummaryType getSummaryType(string name) { @@ -99,9 +105,9 @@ return PluginSummarisingAdapter::UnknownSummaryType; } -bool FeatureExtractionManager::setSummaryTypes(const set &names, - bool summariesOnly, - const PluginSummarisingAdapter::SegmentBoundaries &boundaries) +bool +FeatureExtractionManager::setSummaryTypes(const set &names, + const PluginSummarisingAdapter::SegmentBoundaries &boundaries) { for (SummaryNameSet::const_iterator i = names.begin(); i != names.end(); ++i) { @@ -111,11 +117,16 @@ } } m_summaries = names; - m_summariesOnly = summariesOnly; m_boundaries = boundaries; return true; } +void +FeatureExtractionManager::setSummariesOnly(bool summariesOnly) +{ + m_summariesOnly = summariesOnly; +} + static PluginInputDomainAdapter::WindowType convertWindowType(WindowType t) { @@ -329,9 +340,31 @@ } } + if (transform.getPluginVersion() != "") { + if (QString("%1").arg(plugin->getPluginVersion()) + != transform.getPluginVersion()) { + cerr << "ERROR: Transform specifies version " + << transform.getPluginVersion() + << " of plugin \"" << plugin->getIdentifier() + << "\", but installed plugin is version " + << plugin->getPluginVersion() + << endl; + return false; + } + } + if (transform.getOutput() == "") { transform.setOutput (plugin->getOutputDescriptors()[0].identifier.c_str()); + } else { + if (m_pluginOutputs[plugin].find + (transform.getOutput().toLocal8Bit().data()) == + m_pluginOutputs[plugin].end()) { + cerr << "ERROR: Transform requests nonexistent plugin output \"" + << transform.getOutput() + << "\"" << endl; + return false; + } } m_transformPluginMap[transform] = plugin; @@ -345,6 +378,10 @@ plugin = m_transformPluginMap[transform]; } + if (m_plugins.find(plugin) == m_plugins.end()) { + m_orderedPlugins.push_back(plugin); + } + m_plugins[plugin][transform] = writers; return true; @@ -373,25 +410,37 @@ bool FeatureExtractionManager::addFeatureExtractorFromFile (QString transformXmlFile, const vector &writers) { - RDFTransformFactory factory - (QUrl::fromLocalFile(QFileInfo(transformXmlFile).absoluteFilePath()) - .toString()); - ProgressPrinter printer("Parsing transforms RDF file"); - std::vector transforms = factory.getTransforms(&printer); - if (!factory.isOK()) { - cerr << "WARNING: FeatureExtractionManager::addFeatureExtractorFromFile: Failed to parse transforms file: " << factory.getErrorString().toStdString() << endl; - if (factory.isRDF()) { - return false; // no point trying it as XML - } + bool tryRdf = true; + + if (transformXmlFile.endsWith(".xml") || transformXmlFile.endsWith(".XML")) { + // We don't support RDF-XML (and nor does the underlying + // parser library) so skip the RDF parse if the filename + // suggests XML, to avoid puking out a load of errors from + // feeding XML to a Turtle parser + tryRdf = false; } - if (!transforms.empty()) { - bool success = true; - for (int i = 0; i < (int)transforms.size(); ++i) { - if (!addFeatureExtractor(transforms[i], writers)) { - success = false; + + if (tryRdf) { + RDFTransformFactory factory + (QUrl::fromLocalFile(QFileInfo(transformXmlFile).absoluteFilePath()) + .toString()); + ProgressPrinter printer("Parsing transforms RDF file"); + std::vector transforms = factory.getTransforms(&printer); + if (!factory.isOK()) { + cerr << "WARNING: FeatureExtractionManager::addFeatureExtractorFromFile: Failed to parse transforms file: " << factory.getErrorString().toStdString() << endl; + if (factory.isRDF()) { + return false; // no point trying it as XML } } - return success; + if (!transforms.empty()) { + bool success = true; + for (int i = 0; i < (int)transforms.size(); ++i) { + if (!addFeatureExtractor(transforms[i], writers)) { + success = false; + } + } + return success; + } } QFile file(transformXmlFile); @@ -411,31 +460,8 @@ return addFeatureExtractor(transform, writers); } -void FeatureExtractionManager::addSource(QString audioSource) +void FeatureExtractionManager::addSource(QString audioSource, bool willMultiplex) { - if (QFileInfo(audioSource).suffix().toLower() == "m3u") { - ProgressPrinter retrievalProgress("Opening playlist file..."); - FileSource source(audioSource, &retrievalProgress); - if (!source.isAvailable()) { - cerr << "ERROR: File or URL \"" << audioSource.toStdString() - << "\" could not be located" << endl; - throw FileNotFound(audioSource); - } - source.waitForData(); - PlaylistFileReader reader(source); - if (reader.isOK()) { - vector files = reader.load(); - for (int i = 0; i < (int)files.size(); ++i) { - addSource(files[i]); - } - return; - } else { - cerr << "ERROR: Playlist \"" << audioSource.toStdString() - << "\" could not be opened" << endl; - throw FileNotFound(audioSource); - } - } - std::cerr << "Have audio source: \"" << audioSource.toStdString() << "\"" << std::endl; // We don't actually do anything with it here, unless it's the @@ -449,7 +475,11 @@ FileSource source(audioSource, &retrievalProgress); if (!source.isAvailable()) { cerr << "ERROR: File or URL \"" << audioSource.toStdString() - << "\" could not be located" << endl; + << "\" could not be located"; + if (source.getErrorString() != "") { + cerr << ": " << source.getErrorString(); + } + cerr << endl; throw FileNotFound(audioSource); } @@ -459,7 +489,9 @@ // (then close, and open again later with actual desired rate &c) AudioFileReader *reader = - AudioFileReaderFactory::createReader(source, 0, &retrievalProgress); + AudioFileReaderFactory::createReader(source, 0, + m_normalise, + &retrievalProgress); if (!reader) { throw FailedToOpenFile(audioSource); @@ -469,10 +501,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) { @@ -484,39 +518,18 @@ 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; - if (QFileInfo(audioSource).suffix().toLower() == "m3u") { - FileSource source(audioSource); - PlaylistFileReader reader(source); - if (reader.isOK()) { - vector files = reader.load(); - for (int i = 0; i < (int)files.size(); ++i) { - try { - extractFeatures(files[i], force); - } catch (const std::exception &e) { - if (!force) throw; - cerr << "ERROR: Feature extraction failed for playlist entry \"" - << files[i].toStdString() - << "\": " << e.what() << endl; - // print a note only if we have more files to process - if (++i != files.size()) { - cerr << "NOTE: \"--force\" option was provided, continuing (more errors may occur)" << endl; - } - } - } - return; - } else { - cerr << "ERROR: Playlist \"" << audioSource.toStdString() - << "\" could not be opened" << endl; - throw FileNotFound(audioSource); - } - } - testOutputFiles(audioSource); if (m_sampleRate == 0) { @@ -528,13 +541,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 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; @@ -542,23 +587,31 @@ } if (!reader) { ProgressPrinter retrievalProgress("Retrieving audio data..."); - FileSource source(audioSource, &retrievalProgress); - source.waitForData(); - reader = AudioFileReaderFactory::createReader - (source, m_sampleRate, &retrievalProgress); + FileSource fs(source, &retrievalProgress); + fs.waitForData(); + reader = AudioFileReaderFactory::createReader(fs, m_sampleRate, + m_normalise, + &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; } @@ -591,12 +644,15 @@ // cerr << "file has " << frameCount << " frames" << endl; - for (PluginMap::iterator pi = m_plugins.begin(); - pi != m_plugins.end(); ++pi) { + int earliestStartFrame = 0; + int latestEndFrame = frameCount; + bool haveExtents = false; - Plugin *plugin = pi->first; + foreach (Plugin *plugin, m_orderedPlugins) { -// std::cerr << "Calling reset on " << plugin << std::endl; + PluginMap::iterator pi = m_plugins.find(plugin); + + std::cerr << "Calling reset on " << plugin << std::endl; plugin->reset(); for (TransformWriterMap::iterator ti = pi->second.begin(); @@ -604,18 +660,34 @@ const Transform &transform = ti->first; - //!!! we may want to set the start and duration times for extraction - // in the transform record (defaults of zero indicate extraction - // from the whole file) -// transform.setStartTime(RealTime::zeroTime); -// transform.setDuration -// (RealTime::frame2RealTime(reader->getFrameCount(), m_sampleRate)); + int startFrame = RealTime::realTime2Frame + (transform.getStartTime(), m_sampleRate); + int duration = RealTime::realTime2Frame + (transform.getDuration(), m_sampleRate); + if (duration == 0) { + duration = frameCount - startFrame; + } + + if (!haveExtents || startFrame < earliestStartFrame) { + earliestStartFrame = startFrame; + } + if (!haveExtents || startFrame + duration > latestEndFrame) { + latestEndFrame = startFrame + duration; + } +/* + cerr << "startFrame for transform " << startFrame << endl; + cerr << "duration for transform " << duration << endl; + cerr << "earliestStartFrame becomes " << earliestStartFrame << endl; + cerr << "latestEndFrame becomes " << latestEndFrame << endl; +*/ + haveExtents = true; string outputId = transform.getOutput().toStdString(); if (m_pluginOutputs[plugin].find(outputId) == m_pluginOutputs[plugin].end()) { - //!!! throw? - cerr << "WARNING: Nonexistent plugin output \"" << outputId << "\" requested for transform \"" + // We shouldn't actually reach this point: + // addFeatureExtractor tests whether the output exists + cerr << "ERROR: Nonexistent plugin output \"" << outputId << "\" requested for transform \"" << transform.getIdentifier().toStdString() << "\", ignoring this transform" << endl; /* @@ -637,36 +709,12 @@ } } - long startFrame = 0; - long endFrame = frameCount; + int startFrame = earliestStartFrame; + int endFrame = latestEndFrame; + + foreach (Plugin *plugin, m_orderedPlugins) { -/*!!! No -- there is no single transform to pull this stuff from -- - * the transforms may have various start and end times, need to be far - * cleverer about this if we're going to support them - - RealTime trStartRT = transform.getStartTime(); - RealTime trDurationRT = transform.getDuration(); - - long trStart = RealTime::realTime2Frame(trStartRT, m_sampleRate); - long trDuration = RealTime::realTime2Frame(trDurationRT, m_sampleRate); - - if (trStart == 0 || trStart < startFrame) { - trStart = startFrame; - } - - if (trDuration == 0) { - trDuration = endFrame - trStart; - } - if (trStart + trDuration > endFrame) { - trDuration = endFrame - trStart; - } - - startFrame = trStart; - endFrame = trStart + trDuration; -*/ - - for (PluginMap::iterator pi = m_plugins.begin(); - pi != m_plugins.end(); ++pi) { + PluginMap::iterator pi = m_plugins.find(plugin); for (TransformWriterMap::const_iterator ti = pi->second.begin(); ti != pi->second.end(); ++ti) { @@ -687,10 +735,10 @@ ProgressPrinter extractionProgress("Extracting and writing features..."); int progress = 0; - for (long i = startFrame; i < endFrame; i += m_blockSize) { + for (int i = startFrame; i < endFrame; i += m_blockSize) { //!!! inefficient, although much of the inefficiency may be - // susceptible to optimisation + // susceptible to compiler optimisation SampleBlock frames; reader->getInterleavedFrames(i, m_blockSize, frames); @@ -738,10 +786,28 @@ Vamp::RealTime timestamp = Vamp::RealTime::frame2RealTime (i, m_sampleRate); - for (PluginMap::iterator pi = m_plugins.begin(); - pi != m_plugins.end(); ++pi) { + foreach (Plugin *plugin, m_orderedPlugins) { - Plugin *plugin = pi->first; + PluginMap::iterator pi = m_plugins.find(plugin); + + // Skip any plugin none of whose transforms have come + // around yet. (Though actually, all transforms for a + // given plugin must have the same start time -- they can + // only differ in output and summary type.) + bool inRange = false; + for (TransformWriterMap::const_iterator ti = pi->second.begin(); + ti != pi->second.end(); ++ti) { + int startFrame = RealTime::realTime2Frame + (ti->first.getStartTime(), m_sampleRate); + if (i >= startFrame || i + m_blockSize > startFrame) { + inRange = true; + break; + } + } + if (!inRange) { + continue; + } + Plugin::FeatureSet featureSet = plugin->process(data, timestamp); if (!m_summariesOnly) { @@ -758,41 +824,9 @@ lifemgr.destroy(); // deletes reader, data - // In order to ensure our results are written to the output in a - // fixed order (and not one that depends on the pointer value of - // each plugin on the heap in any given run of the program) we - // take the plugins' entries from the plugin map and sort them - // into a new, temporary map that is indexed by the first - // transform for each plugin. We then iterate over than instead of - // over m_plugins in order to get the right ordering. + foreach (Plugin *plugin, m_orderedPlugins) { - // This is not the most elegant way to do this -- it would be more - // elegant to impose an ordering directly on the plugins that are - // used as keys to m_plugins. But the plugin type comes from the - // Vamp SDK, so this change is more localised. - - // Thanks to Matthias for this. - - typedef map OrderedPluginMap; - OrderedPluginMap orderedPlugins; - - for (PluginMap::iterator pi = m_plugins.begin(); - pi != m_plugins.end(); ++pi) { - Transform firstForPlugin = (pi->second).begin()->first; - orderedPlugins.insert(OrderedPluginMap::value_type(firstForPlugin, *pi)); - } - - for (OrderedPluginMap::iterator superPi = orderedPlugins.begin(); - superPi != orderedPlugins.end(); ++superPi) { - - // The value we extract from this map is just the same as the - // value_type we get from iterating over our PluginMap - // directly -- but we happen to get them in the right order - // now because the map iterator is ordered by the Transform - // key type ordering - PluginMap::value_type pi = superPi->second; - - Plugin *plugin = pi.first; + PluginMap::iterator pi = m_plugins.find(plugin); Plugin::FeatureSet featureSet = plugin->getRemainingFeatures(); if (!m_summariesOnly) { @@ -935,8 +969,9 @@ void FeatureExtractionManager::finish() { - for (PluginMap::iterator pi = m_plugins.begin(); - pi != m_plugins.end(); ++pi) { + foreach (Plugin *plugin, m_orderedPlugins) { + + PluginMap::iterator pi = m_plugins.find(plugin); for (TransformWriterMap::iterator ti = pi->second.begin(); ti != pi->second.end(); ++ti) { diff -r c82736e79739 -r caf05503bf42 runner/FeatureExtractionManager.h --- a/runner/FeatureExtractionManager.h Thu May 09 13:55:11 2013 +0100 +++ b/runner/FeatureExtractionManager.h Mon Oct 13 10:16:37 2014 +0100 @@ -43,11 +43,13 @@ void setChannels(int channels); void setDefaultSampleRate(int sampleRate); + void setNormalise(bool normalise); bool setSummaryTypes(const set &summaryTypes, - bool summariesOnly, const Vamp::HostExt::PluginSummarisingAdapter::SegmentBoundaries &boundaries); + void setSummariesOnly(bool summariesOnly); + bool addFeatureExtractor(Transform transform, const vector &writers); @@ -60,12 +62,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 @@ -77,7 +84,17 @@ typedef map > TransformWriterMap; typedef map PluginMap; PluginMap m_plugins; - + + // When we run plugins, we want to run them in a known order so as + // to get the same results on each run of Sonic Annotator with the + // same transforms. But if we just iterate through our PluginMap, + // we get them in an arbitrary order based on pointer + // address. This vector provides an underlying order for us. Note + // that the TransformWriterMap is consistently ordered (because + // the key is a Transform which has a proper ordering) so using + // this gives us a consistent order across the whole PluginMap + vector m_orderedPlugins; + // And a map back from transforms to their plugins. Note that // this is keyed by transform, not transform ID -- two differently // configured transforms with the same ID must use different @@ -101,6 +118,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, @@ -116,6 +137,7 @@ int m_defaultSampleRate; int m_sampleRate; int m_channels; + bool m_normalise; QMap m_readyReaders; diff -r c82736e79739 -r caf05503bf42 runner/MultiplexedReader.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/runner/MultiplexedReader.cpp Mon Oct 13 10:16:37 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 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; +} + + + diff -r c82736e79739 -r caf05503bf42 runner/MultiplexedReader.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/runner/MultiplexedReader.h Mon Oct 13 10:16:37 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 +#include + +class MultiplexedReader : public AudioFileReader +{ + Q_OBJECT + +public: + // I take ownership of readers + MultiplexedReader(QList 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 m_readers; +}; + +#endif diff -r c82736e79739 -r caf05503bf42 runner/main.cpp --- a/runner/main.cpp Thu May 09 13:55:11 2013 +0100 +++ b/runner/main.cpp Mon Oct 13 10:16:37 2014 +0100 @@ -35,6 +35,7 @@ #include "base/Exceptions.h" #include "base/TempDirectory.h" +#include "base/ProgressPrinter.h" #include "data/fileio/AudioFileReaderFactory.h" #include "data/fileio/PlaylistFileReader.h" @@ -136,32 +137,83 @@ return ws; } -void usage(QString myname) +static bool +isVersionNewerThan(QString a, QString b) // from VersionTester in svapp { - set writers = FeatureWriterFactory::getWriterTags(); - + QRegExp re("[._-]"); + QStringList alist = a.split(re, QString::SkipEmptyParts); + QStringList blist = b.split(re, QString::SkipEmptyParts); + int ae = alist.size(); + int be = blist.size(); + int e = std::max(ae, be); + for (int i = 0; i < e; ++i) { + int an = 0, bn = 0; + if (i < ae) { + an = alist[i].toInt(); + if (an == 0 && alist[i] != "0") { + an = -1; // non-numeric field -> "-pre1" etc + } + } + if (i < be) { + bn = blist[i].toInt(); + if (bn == 0 && blist[i] != "0") { + bn = -1; + } + } + if (an < bn) return false; + if (an > bn) return true; + } + return false; +} + +static int +checkMinVersion(QString myname, QString v) +{ + if (v == RUNNER_VERSION) { + return 0; + } else if (isVersionNewerThan(RUNNER_VERSION, v)) { + return 0; + } else { + cerr << myname << ": version " + << RUNNER_VERSION << " is less than requested min version " + << v << ", failing" << endl; + return 1; + } +} + +void printUsage(QString myname) +{ cerr << endl; cerr << "Sonic Annotator v" << RUNNER_VERSION << endl; cerr << "A utility for batch feature extraction from audio files." << endl; cerr << "Mark Levy, Chris Sutton and Chris Cannam, Queen Mary, University of London." << endl; - cerr << "Copyright 2007-2013 Queen Mary, University of London." << endl; + cerr << "Copyright 2007-2014 Queen Mary, University of London." << endl; cerr << endl; cerr << "This program is free software. You may redistribute copies of it under the" << endl; cerr << "terms of the GNU General Public License ." << endl; cerr << "This program is supplied with NO WARRANTY, to the extent permitted by law." << endl; cerr << endl; - cerr << " Usage: " << myname.toStdString() - << " [-mr] -t trans.xml [...] -w [...]