changeset 248:c8e5fcddf8be

Merge
author Chris Cannam
date Fri, 18 Mar 2016 15:15:55 +0000
parents 5eadb3b687bb (current diff) 4307b34f86c0 (diff)
children 85ab36c3b7d8
files runner.pro
diffstat 45 files changed, 981 insertions(+), 259 deletions(-) [+]
line wrap: on
line diff
--- a/.hgsubstate	Fri Mar 18 15:15:37 2016 +0000
+++ b/.hgsubstate	Fri Mar 18 15:15:55 2016 +0000
@@ -1,4 +1,4 @@
 d16f0fd6db6104d87882bc43788a3bb1b0f8c528 dataquay
-55ece8862b6d3a54aad271a53f9c1615e5d3bcf8 sv-dependency-builds
-c8e291700c0eaa10c064dbf732ba0e72efa1f8a7 svcore
-632d90c185ecc8655f7a85ba58dc568351449dfd vamp-plugin-sdk
+1e4f338ae482429a7ab9bdd0825242042354152f sv-dependency-builds
+0ad516dc5d8d1eac370c7f43eeb7775919e0f2e9 svcore
+55de53d7c777008997721bb43051a67c3b3772d2 vamp-plugin-sdk
--- a/.hgtags	Fri Mar 18 15:15:37 2016 +0000
+++ b/.hgtags	Fri Mar 18 15:15:55 2016 +0000
@@ -14,3 +14,4 @@
 5fe1f2efd40779acbdccc4c91a53693bd61f6d9b sonic-annotator-1.0
 766268a32378f8e9e4fdfd3c78c3d43d482976ca sonic-annotator-1.1
 f35bbb3e4d41caf3ca1be5576ed8ef6ef7c23e34 sonic-annotator-1.2
+710f4885f901c7b6617cfa4f1d9b3ff6a431affb sonic-annotator-1.3
--- a/CHANGELOG	Fri Mar 18 15:15:37 2016 +0000
+++ b/CHANGELOG	Fri Mar 18 15:15:55 2016 +0000
@@ -1,3 +1,25 @@
+
+Changes in Sonic Annotator 1.4 since the previous release 1.3:
+
+Front-end changes:
+
+ - Better error reporting, especially for invalid transform files
+   and transform-not-found
+
+ - Avoid crashing out when a single plugin (that is not being used)
+   can't be loaded because of e.g. an undefined symbol
+
+Bug fixes:
+
+ - Fix (with test) horrible crash with --multiplex option
+
+ - Fix erroneous quantization to 16 bits for coded file types of
+   greater bit depth
+
+ - Fix multiple outputs when requesting both summary and non-summary
+   for the same output
+
+
 Changes in Sonic Annotator 1.3 since the previous release 1.2:
 
 Back-end (feature writer) changes:
--- a/acinclude.m4	Fri Mar 18 15:15:37 2016 +0000
+++ b/acinclude.m4	Fri Mar 18 15:15:55 2016 +0000
@@ -69,6 +69,9 @@
    	AC_CHECK_PROG(QMAKE, qmake-qt5, $QTDIR/bin/qmake-qt5,,$QTDIR/bin/)
 fi
 if test x$QMAKE = x ; then
+   	AC_CHECK_PROG(QMAKE, qt5-qmake, $QTDIR/bin/qt5-qmake,,$QTDIR/bin/)
+fi
+if test x$QMAKE = x ; then
    	AC_CHECK_PROG(QMAKE, qmake, $QTDIR/bin/qmake,,$QTDIR/bin/)
 fi
 if test x$QMAKE = x ; then
@@ -78,6 +81,9 @@
    	AC_CHECK_PROG(QMAKE, qmake-qt5, qmake-qt5,,$PATH)
 fi
 if test x$QMAKE = x ; then
+   	AC_CHECK_PROG(QMAKE, qt5-qmake, qt5-qmake,,$PATH)
+fi
+if test x$QMAKE = x ; then
    	AC_CHECK_PROG(QMAKE, qmake, qmake,,$PATH)
 fi
 if test x$QMAKE = x ; then
@@ -112,3 +118,146 @@
 
 ])
 
+# From autoconf archive:
+
+# ============================================================================
+#  http://www.gnu.org/software/autoconf-archive/ax_cxx_compile_stdcxx_11.html
+# ============================================================================
+#
+# SYNOPSIS
+#
+#   AX_CXX_COMPILE_STDCXX_11([ext|noext],[mandatory|optional])
+#
+# DESCRIPTION
+#
+#   Check for baseline language coverage in the compiler for the C++11
+#   standard; if necessary, add switches to CXXFLAGS to enable support.
+#
+#   The first argument, if specified, indicates whether you insist on an
+#   extended mode (e.g. -std=gnu++11) or a strict conformance mode (e.g.
+#   -std=c++11).  If neither is specified, you get whatever works, with
+#   preference for an extended mode.
+#
+#   The second argument, if specified 'mandatory' or if left unspecified,
+#   indicates that baseline C++11 support is required and that the macro
+#   should error out if no mode with that support is found.  If specified
+#   'optional', then configuration proceeds regardless, after defining
+#   HAVE_CXX11 if and only if a supporting mode is found.
+#
+# LICENSE
+#
+#   Copyright (c) 2008 Benjamin Kosnik <bkoz@redhat.com>
+#   Copyright (c) 2012 Zack Weinberg <zackw@panix.com>
+#   Copyright (c) 2013 Roy Stogner <roystgnr@ices.utexas.edu>
+#   Copyright (c) 2014 Alexey Sokolov <sokolov@google.com>
+#
+#   Copying and distribution of this file, with or without modification, are
+#   permitted in any medium without royalty provided the copyright notice
+#   and this notice are preserved. This file is offered as-is, without any
+#   warranty.
+
+m4_define([_AX_CXX_COMPILE_STDCXX_11_testbody], [[
+  template <typename T>
+    struct check
+    {
+      static_assert(sizeof(int) <= sizeof(T), "not big enough");
+    };
+
+    struct Base {
+    virtual void f() {}
+    };
+    struct Child : public Base {
+    virtual void f() override {}
+    };
+
+    typedef check<check<bool>> right_angle_brackets;
+
+    int a;
+    decltype(a) b;
+
+    typedef check<int> check_type;
+    check_type c;
+    check_type&& cr = static_cast<check_type&&>(c);
+
+    auto d = a;
+    auto l = [](){};
+]])
+
+AC_DEFUN([AX_CXX_COMPILE_STDCXX_11], [dnl
+  m4_if([$1], [], [],
+        [$1], [ext], [],
+        [$1], [noext], [],
+        [m4_fatal([invalid argument `$1' to AX_CXX_COMPILE_STDCXX_11])])dnl
+  m4_if([$2], [], [ax_cxx_compile_cxx11_required=true],
+        [$2], [mandatory], [ax_cxx_compile_cxx11_required=true],
+        [$2], [optional], [ax_cxx_compile_cxx11_required=false],
+        [m4_fatal([invalid second argument `$2' to AX_CXX_COMPILE_STDCXX_11])])
+  AC_LANG_PUSH([C++])dnl
+  ac_success=no
+  AC_CACHE_CHECK(whether $CXX supports C++11 features by default,
+  ax_cv_cxx_compile_cxx11,
+  [AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_11_testbody])],
+    [ax_cv_cxx_compile_cxx11=yes],
+    [ax_cv_cxx_compile_cxx11=no])])
+  if test x$ax_cv_cxx_compile_cxx11 = xyes; then
+    ac_success=yes
+  fi
+
+  m4_if([$1], [noext], [], [dnl
+  if test x$ac_success = xno; then
+    for switch in -std=gnu++11 -std=gnu++0x; do
+      cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx11_$switch])
+      AC_CACHE_CHECK(whether $CXX supports C++11 features with $switch,
+                     $cachevar,
+        [ac_save_CXXFLAGS="$CXXFLAGS"
+         CXXFLAGS="$CXXFLAGS $switch"
+         AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_11_testbody])],
+          [eval $cachevar=yes],
+          [eval $cachevar=no])
+         CXXFLAGS="$ac_save_CXXFLAGS"])
+      if eval test x\$$cachevar = xyes; then
+        CXXFLAGS="$CXXFLAGS $switch"
+        ac_success=yes
+        break
+      fi
+    done
+  fi])
+
+  m4_if([$1], [ext], [], [dnl
+  if test x$ac_success = xno; then
+    for switch in -std=c++11 -std=c++0x; do
+      cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx11_$switch])
+      AC_CACHE_CHECK(whether $CXX supports C++11 features with $switch,
+                     $cachevar,
+        [ac_save_CXXFLAGS="$CXXFLAGS"
+         CXXFLAGS="$CXXFLAGS $switch"
+         AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_11_testbody])],
+          [eval $cachevar=yes],
+          [eval $cachevar=no])
+         CXXFLAGS="$ac_save_CXXFLAGS"])
+      if eval test x\$$cachevar = xyes; then
+        CXXFLAGS="$CXXFLAGS $switch"
+        ac_success=yes
+        break
+      fi
+    done
+  fi])
+  AC_LANG_POP([C++])
+  if test x$ax_cxx_compile_cxx11_required = xtrue; then
+    if test x$ac_success = xno; then
+      AC_MSG_ERROR([*** A compiler with support for C++11 language features is required.])
+    fi
+  else
+    if test x$ac_success = xno; then
+      HAVE_CXX11=0
+      AC_MSG_NOTICE([No compiler with C++11 support was found])
+    else
+      HAVE_CXX11=1
+      AC_DEFINE(HAVE_CXX11,1,
+                [define if the compiler supports basic C++11 syntax])
+    fi
+
+    AC_SUBST(HAVE_CXX11)
+  fi
+])
+
--- a/configure	Fri Mar 18 15:15:37 2016 +0000
+++ b/configure	Fri Mar 18 15:15:55 2016 +0000
@@ -1,8 +1,6 @@
 #! /bin/sh
 # Guess values for system-dependent variables and create Makefiles.
-# Generated by GNU Autoconf 2.69 for Sonic Annotator 1.2.
-#
-# Report bugs to <cannam@all-day-breakfast.com>.
+# Generated by GNU Autoconf 2.69.
 #
 #
 # Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc.
@@ -266,8 +264,7 @@
     $as_echo "$0: In particular, zsh $ZSH_VERSION has bugs and should"
     $as_echo "$0: be upgraded to zsh 4.3.4 or later."
   else
-    $as_echo "$0: Please tell bug-autoconf@gnu.org and
-$0: cannam@all-day-breakfast.com about your system,
+    $as_echo "$0: Please tell bug-autoconf@gnu.org about your system,
 $0: including any error possibly output before this
 $0: message. Then install a modern shell, or manually run
 $0: the script under such a shell if you do have one."
@@ -578,13 +575,14 @@
 MAKEFLAGS=
 
 # Identity of this package.
-PACKAGE_NAME='Sonic Annotator'
-PACKAGE_TARNAME='sonic-annotator'
-PACKAGE_VERSION='1.2'
-PACKAGE_STRING='Sonic Annotator 1.2'
-PACKAGE_BUGREPORT='cannam@all-day-breakfast.com'
-PACKAGE_URL=''
-
+PACKAGE_NAME=
+PACKAGE_TARNAME=
+PACKAGE_VERSION=
+PACKAGE_STRING=
+PACKAGE_BUGREPORT=
+PACKAGE_URL=
+
+ac_unique_file="Sonic Annotator"
 ac_unique_file="runner/main.cpp"
 # Factoring default headers for most tests.
 ac_includes_default="\
@@ -667,6 +665,7 @@
 EGREP
 GREP
 CXXCPP
+HAVE_CXX11
 MKDIR_P
 INSTALL_DATA
 INSTALL_SCRIPT
@@ -805,7 +804,7 @@
 localstatedir='${prefix}/var'
 includedir='${prefix}/include'
 oldincludedir='/usr/include'
-docdir='${datarootdir}/doc/${PACKAGE_TARNAME}'
+docdir='${datarootdir}/doc/${PACKAGE}'
 infodir='${datarootdir}/info'
 htmldir='${docdir}'
 dvidir='${docdir}'
@@ -1305,7 +1304,7 @@
   # Omit some internal or obsolete options to make the list less imposing.
   # This message is too long to be a string in the A/UX 3.1 sh.
   cat <<_ACEOF
-\`configure' configures Sonic Annotator 1.2 to adapt to many kinds of systems.
+\`configure' configures this package to adapt to many kinds of systems.
 
 Usage: $0 [OPTION]... [VAR=VALUE]...
 
@@ -1353,7 +1352,7 @@
   --infodir=DIR           info documentation [DATAROOTDIR/info]
   --localedir=DIR         locale-dependent data [DATAROOTDIR/locale]
   --mandir=DIR            man documentation [DATAROOTDIR/man]
-  --docdir=DIR            documentation root [DATAROOTDIR/doc/sonic-annotator]
+  --docdir=DIR            documentation root [DATAROOTDIR/doc/PACKAGE]
   --htmldir=DIR           html documentation [DOCDIR]
   --dvidir=DIR            dvi documentation [DOCDIR]
   --pdfdir=DIR            pdf documentation [DOCDIR]
@@ -1365,9 +1364,7 @@
 fi
 
 if test -n "$ac_init_help"; then
-  case $ac_init_help in
-     short | recursive ) echo "Configuration of Sonic Annotator 1.2:";;
-   esac
+
   cat <<\_ACEOF
 
 Optional Features:
@@ -1432,7 +1429,7 @@
 Use these variables to override the choices made by `configure' or to help
 it to find libraries and programs with nonstandard names/locations.
 
-Report bugs to <cannam@all-day-breakfast.com>.
+Report bugs to the package provider.
 _ACEOF
 ac_status=$?
 fi
@@ -1495,7 +1492,7 @@
 test -n "$ac_init_help" && exit $ac_status
 if $ac_init_version; then
   cat <<\_ACEOF
-Sonic Annotator configure 1.2
+configure
 generated by GNU Autoconf 2.69
 
 Copyright (C) 2012 Free Software Foundation, Inc.
@@ -1734,10 +1731,6 @@
 $as_echo "$as_me: WARNING: $2:     section \"Present But Cannot Be Compiled\"" >&2;}
     { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: proceeding with the compiler's result" >&5
 $as_echo "$as_me: WARNING: $2: proceeding with the compiler's result" >&2;}
-( $as_echo "## ------------------------------------------- ##
-## Report this to cannam@all-day-breakfast.com ##
-## ------------------------------------------- ##"
-     ) | sed "s/^/$as_me: WARNING:     /" >&2
     ;;
 esac
   { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5
@@ -1835,7 +1828,7 @@
 This file contains any messages produced by compilers while
 running configure, to aid debugging if configure makes a mistake.
 
-It was created by Sonic Annotator $as_me 1.2, which was
+It was created by $as_me, which was
 generated by GNU Autoconf 2.69.  Invocation command line was
 
   $ $0 $@
@@ -3419,6 +3412,146 @@
 $as_echo "$MKDIR_P" >&6; }
 
 
+# We are daringly making use of C++11 now
+
+    ax_cxx_compile_cxx11_required=true
+  ac_ext=cpp
+ac_cpp='$CXXCPP $CPPFLAGS'
+ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_cxx_compiler_gnu
+  ac_success=no
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CXX supports C++11 features by default" >&5
+$as_echo_n "checking whether $CXX supports C++11 features by default... " >&6; }
+if ${ax_cv_cxx_compile_cxx11+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+  template <typename T>
+    struct check
+    {
+      static_assert(sizeof(int) <= sizeof(T), "not big enough");
+    };
+
+    struct Base {
+    virtual void f() {}
+    };
+    struct Child : public Base {
+    virtual void f() override {}
+    };
+
+    typedef check<check<bool>> right_angle_brackets;
+
+    int a;
+    decltype(a) b;
+
+    typedef check<int> check_type;
+    check_type c;
+    check_type&& cr = static_cast<check_type&&>(c);
+
+    auto d = a;
+    auto l = [](){};
+
+_ACEOF
+if ac_fn_cxx_try_compile "$LINENO"; then :
+  ax_cv_cxx_compile_cxx11=yes
+else
+  ax_cv_cxx_compile_cxx11=no
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ax_cv_cxx_compile_cxx11" >&5
+$as_echo "$ax_cv_cxx_compile_cxx11" >&6; }
+  if test x$ax_cv_cxx_compile_cxx11 = xyes; then
+    ac_success=yes
+  fi
+
+
+
+    if test x$ac_success = xno; then
+    for switch in -std=c++11 -std=c++0x; do
+      cachevar=`$as_echo "ax_cv_cxx_compile_cxx11_$switch" | $as_tr_sh`
+      { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CXX supports C++11 features with $switch" >&5
+$as_echo_n "checking whether $CXX supports C++11 features with $switch... " >&6; }
+if eval \${$cachevar+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  ac_save_CXXFLAGS="$CXXFLAGS"
+         CXXFLAGS="$CXXFLAGS $switch"
+         cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+  template <typename T>
+    struct check
+    {
+      static_assert(sizeof(int) <= sizeof(T), "not big enough");
+    };
+
+    struct Base {
+    virtual void f() {}
+    };
+    struct Child : public Base {
+    virtual void f() override {}
+    };
+
+    typedef check<check<bool>> right_angle_brackets;
+
+    int a;
+    decltype(a) b;
+
+    typedef check<int> check_type;
+    check_type c;
+    check_type&& cr = static_cast<check_type&&>(c);
+
+    auto d = a;
+    auto l = [](){};
+
+_ACEOF
+if ac_fn_cxx_try_compile "$LINENO"; then :
+  eval $cachevar=yes
+else
+  eval $cachevar=no
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+         CXXFLAGS="$ac_save_CXXFLAGS"
+fi
+eval ac_res=\$$cachevar
+	       { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
+$as_echo "$ac_res" >&6; }
+      if eval test x\$$cachevar = xyes; then
+        CXXFLAGS="$CXXFLAGS $switch"
+        ac_success=yes
+        break
+      fi
+    done
+  fi
+  ac_ext=cpp
+ac_cpp='$CXXCPP $CPPFLAGS'
+ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_cxx_compiler_gnu
+
+  if test x$ax_cxx_compile_cxx11_required = xtrue; then
+    if test x$ac_success = xno; then
+      as_fn_error $? "*** A compiler with support for C++11 language features is required." "$LINENO" 5
+    fi
+  else
+    if test x$ac_success = xno; then
+      HAVE_CXX11=0
+      { $as_echo "$as_me:${as_lineno-$LINENO}: No compiler with C++11 support was found" >&5
+$as_echo "$as_me: No compiler with C++11 support was found" >&6;}
+    else
+      HAVE_CXX11=1
+
+$as_echo "#define HAVE_CXX11 1" >>confdefs.h
+
+    fi
+
+
+  fi
+
 
 ac_ext=cpp
 ac_cpp='$CXXCPP $CPPFLAGS'
@@ -3966,6 +4099,45 @@
 
 fi
 if test x$QMAKE = x ; then
+   	# Extract the first word of "qt5-qmake", so it can be a program name with args.
+set dummy qt5-qmake; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_QMAKE+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$QMAKE"; then
+  ac_cv_prog_QMAKE="$QMAKE" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $QTDIR/bin/
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_prog_QMAKE="$QTDIR/bin/qt5-qmake"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+QMAKE=$ac_cv_prog_QMAKE
+if test -n "$QMAKE"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $QMAKE" >&5
+$as_echo "$QMAKE" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+fi
+if test x$QMAKE = x ; then
    	# Extract the first word of "qmake", so it can be a program name with args.
 set dummy qmake; ac_word=$2
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
@@ -4083,6 +4255,45 @@
 
 fi
 if test x$QMAKE = x ; then
+   	# Extract the first word of "qt5-qmake", so it can be a program name with args.
+set dummy qt5-qmake; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_QMAKE+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$QMAKE"; then
+  ac_cv_prog_QMAKE="$QMAKE" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_prog_QMAKE="qt5-qmake"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+QMAKE=$ac_cv_prog_QMAKE
+if test -n "$QMAKE"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $QMAKE" >&5
+$as_echo "$QMAKE" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+fi
+if test x$QMAKE = x ; then
    	# Extract the first word of "qmake", so it can be a program name with args.
 set dummy qmake; ac_word=$2
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
@@ -6231,7 +6442,7 @@
 
 subdirs="$subdirs svcore"
 
-ac_config_files="$ac_config_files config.pri version.h"
+ac_config_files="$ac_config_files config.pri"
 
 
 cat >confcache <<\_ACEOF
@@ -6776,7 +6987,7 @@
 # report actual input values of CONFIG_FILES etc. instead of their
 # values after options handling.
 ac_log="
-This file was extended by Sonic Annotator $as_me 1.2, which was
+This file was extended by $as_me, which was
 generated by GNU Autoconf 2.69.  Invocation command line was
 
   CONFIG_FILES    = $CONFIG_FILES
@@ -6823,13 +7034,13 @@
 Configuration files:
 $config_files
 
-Report bugs to <cannam@all-day-breakfast.com>."
+Report bugs to the package provider."
 
 _ACEOF
 cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`"
 ac_cs_version="\\
-Sonic Annotator config.status 1.2
+config.status
 configured by $0, generated by GNU Autoconf 2.69,
   with options \\"\$ac_cs_config\\"
 
@@ -6942,7 +7153,6 @@
 do
   case $ac_config_target in
     "config.pri") CONFIG_FILES="$CONFIG_FILES config.pri" ;;
-    "version.h") CONFIG_FILES="$CONFIG_FILES version.h" ;;
 
   *) as_fn_error $? "invalid argument: \`$ac_config_target'" "$LINENO" 5;;
   esac
--- a/configure.ac	Fri Mar 18 15:15:37 2016 +0000
+++ b/configure.ac	Fri Mar 18 15:15:55 2016 +0000
@@ -1,5 +1,5 @@
 
-AC_INIT([Sonic Annotator], [1.2], cannam@all-day-breakfast.com)
+AC_INIT([Sonic Annotator], [], cannam@all-day-breakfast.com)
 
 AC_CONFIG_SRCDIR(runner/main.cpp)
 
@@ -25,6 +25,9 @@
 AC_PROG_INSTALL
 AC_PROG_MKDIR_P
 
+# We are daringly making use of C++11 now
+AX_CXX_COMPILE_STDCXX_11(noext)
+
 AC_HEADER_STDC
 
 # These are the flags Autoconf guesses for us; we use them later if
@@ -108,7 +111,7 @@
 AC_SUBST(QMAKE_CONFIG)
 
 AC_CONFIG_SUBDIRS([svcore])
-AC_CONFIG_FILES([config.pri version.h])
+AC_CONFIG_FILES([config.pri])
 
 AC_OUTPUT
 
--- a/runner.pro	Fri Mar 18 15:15:37 2016 +0000
+++ b/runner.pro	Fri Mar 18 15:15:55 2016 +0000
@@ -1,5 +1,7 @@
 TEMPLATE = app
 
+INCLUDEPATH += vamp-plugin-sdk
+
 win32-g++ {
     INCLUDEPATH += sv-dependency-builds/win32-mingw/include
     LIBS += -Lsv-dependency-builds/win32-mingw/lib
@@ -22,7 +24,7 @@
     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
+    DEFINES += HAVE_BZ2 HAVE_FFTW3 HAVE_FFTW3F HAVE_SNDFILE HAVE_SAMPLERATE HAVE_DATAQUAY HAVE_MAD HAVE_ID3TAG
 
     LIBS += -lbz2 -lfftw3 -lfftw3f -lsndfile -lFLAC -logg -lvorbis -lvorbisenc -lvorbisfile -logg -lmad -lid3tag -lsamplerate -lz -lsord-0 -lserd-0
 
@@ -49,11 +51,7 @@
 # look for win32 features
 win32-x-g++:QMAKE_LFLAGS += -Wl,-subsystem,console
 
-# If you have compiled your Vamp plugin SDK with FFTW (using its
-# HAVE_FFTW3 flag), you can define the same flag here to ensure the
-# program saves and restores FFTW wisdom in its configuration properly
-#
-DEFINES += HAVE_FFTW3
+DEFINES += HAVE_FFTW3 HAVE_VAMP HAVE_VAMPHOSTSDK
 
 TARGET = sonic-annotator
 
@@ -81,7 +79,7 @@
 
 LIBS = $$MY_LIBS $$LIBS
 
-#PRE_TARGETDEPS += svcore/libsvcore.a
+PRE_TARGETDEPS += svcore/libsvcore.a
 
 HEADERS += \
         vamp-plugin-sdk/vamp-hostsdk/PluginBase.h \
@@ -113,6 +111,7 @@
         vamp-plugin-sdk/src/vamp-hostsdk/PluginSummarisingAdapter.cpp \
         vamp-plugin-sdk/src/vamp-hostsdk/PluginWrapper.cpp \
         vamp-plugin-sdk/src/vamp-hostsdk/RealTime.cpp \
+        vamp-plugin-sdk/src/vamp-hostsdk/Files.cpp \
 	runner/main.cpp \
 	runner/DefaultFeatureWriter.cpp \
 	runner/FeatureExtractionManager.cpp \
--- a/runner/FeatureExtractionManager.cpp	Fri Mar 18 15:15:37 2016 +0000
+++ b/runner/FeatureExtractionManager.cpp	Fri Mar 18 15:15:55 2016 +0000
@@ -323,6 +323,8 @@
                  << " (adapter step and block size " << m_blockSize << ")"
                  << endl;
 
+//            cerr << "NOTE: That transform is: " << transform.toXmlString() << endl;
+            
             if (pida) {
                 cerr << "NOTE: PluginInputDomainAdapter timestamp adjustment is "
 
@@ -382,8 +384,11 @@
 
         m_transformPluginMap[transform] = plugin;
 
+//        cerr << "NOTE: Assigned plugin " << plugin << " for transform: " << transform.toXmlString() << endl;
+
         if (!(originalTransform == transform)) {
             m_transformPluginMap[originalTransform] = plugin;
+//            cerr << "NOTE: Also assigned plugin " << plugin << " for original transform: " << originalTransform.toXmlString() << endl;
         }
 
     } else {
@@ -427,11 +432,36 @@
 }
 
 bool FeatureExtractionManager::addFeatureExtractorFromFile
-(QString transformXmlFile, const vector<FeatureWriter*> &writers)
+(QString transformFile, const vector<FeatureWriter*> &writers)
 {
+    // We support two formats for transform description files, XML (in
+    // a format specific to Sonic Annotator) and RDF/Turtle. The RDF
+    // format can describe multiple transforms in a single file, the
+    // XML only one.
+    
+    // Possible errors we should report:
+    //
+    // 1. File does not exist or cannot be opened
+    // 2. File is ostensibly XML, but is not parseable
+    // 3. File is ostensibly Turtle, but is not parseable
+    // 4. File is XML, but contains no valid transform (e.g. is unrelated XML)
+    // 5. File is Turtle, but contains no valid transform(s)
+    // 6. File is Turtle and contains both valid and invalid transform(s)
+
+    {
+        // We don't actually need to open this here yet, we just hoist
+        // it to the top for error reporting purposes
+        QFile file(transformFile);
+        if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
+            // Error case 1. File does not exist or cannot be opened
+            cerr << "ERROR: Failed to open transform file \"" << transformFile
+                 << "\" for reading" << endl;
+            return false;
+        }
+    }
+    
     bool tryRdf = true;
-
-    if (transformXmlFile.endsWith(".xml") || transformXmlFile.endsWith(".XML")) {
+    if (transformFile.endsWith(".xml") || transformFile.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
@@ -439,44 +469,90 @@
         tryRdf = false;
     }
 
+    bool tryXml = true;
+    if (transformFile.endsWith(".ttl") || transformFile.endsWith(".TTL") ||
+        transformFile.endsWith(".ntriples") || transformFile.endsWith(".NTRIPLES") ||
+        transformFile.endsWith(".n3") || transformFile.endsWith(".N3")) {
+        tryXml = false;
+    }
+
+    QString rdfError, xmlError;
+    
     if (tryRdf) {
+
         RDFTransformFactory factory
-            (QUrl::fromLocalFile(QFileInfo(transformXmlFile).absoluteFilePath())
+            (QUrl::fromLocalFile(QFileInfo(transformFile).absoluteFilePath())
              .toString());
         ProgressPrinter printer("Parsing transforms RDF file");
         std::vector<Transform> transforms = factory.getTransforms(&printer);
-        if (!factory.isOK()) {
-            cerr << "WARNING: FeatureExtractionManager::addFeatureExtractorFromFile: Failed to parse transforms file: " << factory.getErrorString().toStdString() << endl;
+
+        if (factory.isOK()) {
+            if (transforms.empty()) {
+                cerr << "ERROR: Transform file \"" << transformFile
+                     << "\" is valid RDF but defines no transforms" << endl;
+                return false;
+            } else {
+                bool success = true;
+                for (int i = 0; i < (int)transforms.size(); ++i) {
+                    if (!addFeatureExtractor(transforms[i], writers)) {
+                        success = false;
+                    }
+                }
+                return success;
+            }
+        } else { // !factory.isOK()
             if (factory.isRDF()) {
-                return false; // no point trying it as XML
+                cerr << "ERROR: Invalid transform RDF file \"" << transformFile
+                     << "\": " << factory.getErrorString() << endl;
+                return false;
             }
-        }
-        if (!transforms.empty()) {
-            bool success = true;
-            for (int i = 0; i < (int)transforms.size(); ++i) {
-                if (!addFeatureExtractor(transforms[i], writers)) {
-                    success = false;
-                }
-            }
-            return success;
+
+            // the not-RDF case: fall through without reporting an
+            // error, so we try the file as XML, and if that fails, we
+            // print a general unparseable-file error
+            rdfError = factory.getErrorString();
         }
     }
 
-    QFile file(transformXmlFile);
-    if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
-        cerr << "ERROR: Failed to open transform XML file \""
-             << transformXmlFile.toStdString() << "\" for reading" << endl;
-        return false;
+    if (tryXml) {
+        
+        QFile file(transformFile);
+        if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
+            cerr << "ERROR: Failed to open transform file \""
+                 << transformFile.toStdString() << "\" for reading" << endl;
+            return false;
+        }
+        
+        QTextStream *qts = new QTextStream(&file);
+        QString qs = qts->readAll();
+        delete qts;
+        file.close();
+    
+        Transform transform(qs);
+        xmlError = transform.getErrorString();
+
+        if (xmlError == "") {
+
+            if (transform.getIdentifier() == "") {
+                cerr << "ERROR: Transform file \"" << transformFile
+                     << "\" is valid XML but defines no transform" << endl;
+                return false;
+            }
+
+            return addFeatureExtractor(transform, writers);
+        }
     }
 
-    QTextStream *qts = new QTextStream(&file);
-    QString qs = qts->readAll();
-    delete qts;
-    file.close();
+    cerr << "ERROR: Transform file \"" << transformFile
+         << "\" could not be parsed" << endl;
+    if (rdfError != "") {
+        cerr << "ERROR: RDF parser reported: " << rdfError << endl;
+    }
+    if (xmlError != "") {
+        cerr << "ERROR: XML parser reported: " << xmlError << endl;
+    }
 
-    Transform transform(qs);
-
-    return addFeatureExtractor(transform, writers);
+    return false;
 }
 
 void FeatureExtractionManager::addSource(QString audioSource, bool willMultiplex)
@@ -489,7 +565,7 @@
 
     if (m_channels == 0 || m_defaultSampleRate == 0) {
 
-        ProgressPrinter retrievalProgress("Determining default rate and channel count from first input file...");
+        ProgressPrinter retrievalProgress("Retrieving first input file to determine default rate and channel count...");
 
         FileSource source(audioSource, &retrievalProgress);
         if (!source.isAvailable()) {
@@ -524,14 +600,14 @@
             if (m_channels == 0) {
                 m_channels = reader->getChannelCount();
                 cerr << "Taking default channel count of "
-                     << reader->getChannelCount() << " from file" << endl;
+                     << reader->getChannelCount() << " from audio file" << endl;
             }
         }
 
         if (m_defaultSampleRate == 0) {
             m_defaultSampleRate = reader->getNativeRate();
             cerr << "Taking default sample rate of "
-                 << reader->getNativeRate() << "Hz from file" << endl;
+                 << reader->getNativeRate() << "Hz from audio file" << endl;
             cerr << "(Note: Default may be overridden by transforms)" << endl;
         }
 
@@ -616,6 +692,12 @@
     if (!reader) {
         throw FailedToOpenFile(source);
     }
+    if (reader->getChannelCount() != m_channels ||
+        reader->getNativeRate() != m_sampleRate) {
+        cerr << "NOTE: File will be mixed or resampled for processing, to: "
+             << m_channels << "ch at " 
+             << m_sampleRate << "Hz" << endl;
+    }
     return reader;
 }
 
@@ -628,12 +710,6 @@
     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, to: "
-             << m_channels << "ch at " 
-             << m_sampleRate << "Hz" << endl;
-    }
 
     // allocate audio buffers
     float **data = new float *[m_channels];
@@ -671,7 +747,7 @@
 
         PluginMap::iterator pi = m_plugins.find(plugin);
 
-        std::cerr << "Calling reset on " << plugin << std::endl;
+//        std::cerr << "Calling reset on " << plugin << std::endl;
         plugin->reset();
 
         for (TransformWriterMap::iterator ti = pi->second.begin();
@@ -852,6 +928,7 @@
         }
 
         if (!m_summaries.empty()) {
+            // Summaries requested on the command line, for all transforms
             PluginSummarisingAdapter *adapter =
                 dynamic_cast<PluginSummarisingAdapter *>(plugin);
             if (!adapter) {
@@ -868,12 +945,13 @@
                     featureSet = adapter->getSummaryForAllOutputs
                         (getSummaryType(*sni),
                          PluginSummarisingAdapter::ContinuousTimeAverage);
-                    writeFeatures(audioSource, plugin, featureSet,//!!! *sni);
+                    writeFeatures(audioSource, plugin, featureSet,
                                   Transform::stringToSummaryType(sni->c_str()));
                 }
             }
         }
 
+        // Summaries specified in transform definitions themselves
         writeSummaries(audioSource, plugin);
     }
 
@@ -895,11 +973,15 @@
         
         const Transform &transform = ti->first;
 
+//        cerr << "FeatureExtractionManager::writeSummaries: plugin is " << plugin
+//             << ", found transform: " << transform.toXmlString() << endl;
+        
         Transform::SummaryType summaryType = transform.getSummaryType();
         PluginSummarisingAdapter::SummaryType pType =
             (PluginSummarisingAdapter::SummaryType)summaryType;
 
         if (transform.getSummaryType() == Transform::NoSummary) {
+//            cerr << "(no summary, continuing)" << endl;
             continue;
         }
 
@@ -913,7 +995,7 @@
         Plugin::FeatureSet featureSet = adapter->getSummaryForAllOutputs
             (pType, PluginSummarisingAdapter::ContinuousTimeAverage);
 
-//        cout << "summary type " << int(pType) << " for transform:" << endl << transform.toXmlString().toStdString()<< endl << "... feature set with " << featureSet.size() << " elts" << endl;
+//        cerr << "summary type " << int(pType) << " for transform:" << endl << transform.toXmlString().toStdString()<< endl << "... feature set with " << featureSet.size() << " elts" << endl;
 
         writeFeatures(audioSource, plugin, featureSet, summaryType);
     }
@@ -927,21 +1009,25 @@
     // caller should have ensured plugin is in m_plugins
     PluginMap::iterator pi = m_plugins.find(plugin);
 
+    // Write features from the feature set passed in, according to the
+    // transforms listed for the given plugin with the given summary type
+    
     for (TransformWriterMap::const_iterator ti = pi->second.begin();
          ti != pi->second.end(); ++ti) {
         
         const Transform &transform = ti->first;
         const vector<FeatureWriter *> &writers = ti->second;
-        
-        if (transform.getSummaryType() != Transform::NoSummary &&
-            m_summaries.empty() &&
-            summaryType == Transform::NoSummary) {
-            continue;
-        }
 
-        if (transform.getSummaryType() != Transform::NoSummary &&
-            summaryType != Transform::NoSummary &&
-            transform.getSummaryType() != summaryType) {
+//        cerr << "writeFeatures: plugin " << plugin << " has transform: " << transform.toXmlString() << endl;
+
+        if (transform.getSummaryType() == Transform::NoSummary &&
+            !m_summaries.empty()) {
+//            cerr << "transform has no summary, but summaries requested on command line, so going for it anyway" << endl;
+        } else if (transform.getSummaryType() != summaryType) {
+            // Either we're not writing a summary and the transform
+            // has one, or we're writing a summary but the transform
+            // has none or a different one; either way, skip it
+//            cerr << "summary type differs from passed-in one " << summaryType << endl;
             continue;
         }
 
@@ -959,6 +1045,8 @@
         Plugin::FeatureSet::const_iterator fsi = features.find(outputIndex);
         if (fsi == features.end()) continue;
 
+//        cerr << "this transform has " << writers.size() << " writer(s)" << endl;
+        
         for (int j = 0; j < (int)writers.size(); ++j) {
             writers[j]->write
                 (audioSource, transform, desc, fsi->second,
--- a/runner/FeatureExtractionManager.h	Fri Mar 18 15:15:37 2016 +0000
+++ b/runner/FeatureExtractionManager.h	Fri Mar 18 15:15:55 2016 +0000
@@ -114,8 +114,8 @@
     OutputIndexMap m_pluginOutputIndices;
 
     typedef set<std::string> SummaryNameSet;
-    SummaryNameSet m_summaries;
-    bool m_summariesOnly;
+    SummaryNameSet m_summaries; // requested on command line for all transforms
+    bool m_summariesOnly; // command line flag
     Vamp::HostExt::PluginSummarisingAdapter::SegmentBoundaries m_boundaries;
 
     AudioFileReader *prepareReader(QString audioSource);
--- a/runner/MultiplexedReader.cpp	Fri Mar 18 15:15:37 2016 +0000
+++ b/runner/MultiplexedReader.cpp	Fri Mar 18 15:15:55 2016 +0000
@@ -60,7 +60,7 @@
     for (int out_chan = 0; out_chan < out_chans; ++out_chan) {
 
 	AudioFileReader *reader = m_readers[out_chan];
-	SampleBlock readerBlock = getInterleavedFrames(start, frameCount);
+	SampleBlock readerBlock = reader->getInterleavedFrames(start, frameCount);
 
 	int in_chans = reader->getChannelCount();
 
--- a/runner/main.cpp	Fri Mar 18 15:15:37 2016 +0000
+++ b/runner/main.cpp	Fri Mar 18 15:15:55 2016 +0000
@@ -137,6 +137,10 @@
     return ws;
 }
 
+static QString wrapCol(QString s) {
+    return wrap(s, 56, 22);
+}
+
 static bool
 isVersionNewerThan(QString a, QString b) // from VersionTester in svapp
 {
@@ -186,8 +190,8 @@
     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-2015 Queen Mary, University of London." << endl;
+    cerr << "Mark Levy, Chris Sutton, and Chris Cannam, Queen Mary, University of London." << endl;
+    cerr << "Copyright 2007-2016 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 <http://www.gnu.org/licenses/gpl.html>." << endl;
@@ -199,7 +203,7 @@
     cerr << "  " << myname
          << " [-mrnf] -T translist.txt [..] -w <writer> [..] <audio> [..]" << endl;
     cerr << "  " << myname
-         << " [-mrnf] -d <plugin> [..] -w <writer> [..] <audio> [...]" << endl;
+         << " [-mrnf] -d <id> [..] -w <writer> [..] <audio> [...]" << endl;
     cerr << "  " << myname
          << " -s <transform>" << endl;
     cerr << "  " << myname
@@ -207,7 +211,7 @@
     cerr << endl;
     cerr << "Where <audio> is an audio file or URL to use as input: either a local file" << endl;
     cerr << "path, local \"file://\" URL, or remote \"http://\" or \"ftp://\" URL;" << endl;
-    cerr << "and <plugin> is a plugin output identified as vamp:libname:plugin:output." << endl;
+    cerr << "and <id> is a transform id of the form vamp:libname:plugin:output." << endl;
     cerr << endl;
 }
 
@@ -218,7 +222,7 @@
     if (p.hasArg) { cerr << "<X> "; spaceage -= 4; }
     for (int k = 0; k < spaceage; ++k) cerr << " ";
     QString s(p.description.c_str());
-    s = wrap(s, 56, 22);
+    s = wrapCol(s);
     cerr << s << endl;
 }
 
@@ -261,92 +265,112 @@
         if (++i != writers.end()) writerText += ", ";
         else writerText += ".";
     }
-    writerText = wrap(writerText, 56, 22);
+    writerText = wrapCol(writerText);
 
     if (writer == "" || writers.find(writer) == writers.end()) {
 
         cerr << "Transformation options:" << endl;
         cerr << endl;
-        cerr << "  -t, --transform <T> Apply transform described in transform file <T> to" << endl;
-        cerr << "                      all input audio files. You may supply this option" << endl;
-        cerr << "                      multiple times. You must supply this option or -T at" << endl;
-        cerr << "                      least once for any work to be done. Transform format" << endl;
-        cerr << "                      may be SV transform XML or Vamp transform RDF/Turtle." << endl;
-        cerr << "                      See accompanying documentation for transform examples." << endl;
-        cerr << endl;
-        cerr << "  -T, --transforms <T> Apply all transforms described in transform files" << endl;
-        cerr << "                      whose names are listed in text file <T>. You may supply" << endl;
-        cerr << "                      this option multiple times." << endl;
-        cerr << endl;
-        cerr << "  -d, --default <I>   Apply the default transform for transform id <I>. This" << endl;
-        cerr << "                      is equivalent to generating a skeleton transform for this" << endl;
-        cerr << "                      id (using the -s option, below) and then applying that," << endl;
-        cerr << "                      unmodified, with the -t option in the normal way. Note" << endl;
-        cerr << "                      that results may vary, as the implementation's default" << endl;
-        cerr << "                      processing parameters are not guaranteed. Do not use" << endl;
-        cerr << "                      this in production systems. You may supply this option" << endl;
-        cerr << "                      multiple times, and mix it with -t and -T." << endl;
-        cerr << endl;
-        cerr << "  -w, --writer <W>    Write output using writer type <W>." << endl;
-        cerr << "                      " << writerText << endl;
-        cerr << "                      You may supply this option multiple times. You must" << endl;
-        cerr << "                      supply this option at least once for any work to be done." << endl;
-        cerr << endl;
-        cerr << "  -S, --summary <S>   In addition to the result features, write summary feature" << endl;
-        cerr << "                      of summary type <S>." << endl;
-        cerr << "                      Supported summary types are min, max, mean, median, mode," << endl;
-        cerr << "                      sum, variance, sd, count." << endl;
-        cerr << "                      You may supply this option multiple times." << endl;
-        cerr << endl;
-        cerr << "      --summary-only  Write only summary features; do not write the regular" << endl;
-        cerr << "                      result features." << endl;
-        cerr << endl;
-        cerr << "      --segments <A>,<B>[,...]" << endl;
-        cerr << "                      Summarise in segments, with segment boundaries" << endl;
-        cerr << "                      at A, B, ... seconds." << endl;
-        cerr << endl;
-        cerr << "      --segments-from <F>" << endl;
-        cerr << "                      Summarise in segments, with segment boundaries" << endl;
-        cerr << "                      at times read from the text file <F>. (one time per" << endl;
-        cerr << "                      line, in seconds)." << endl;
-        cerr << endl;
-        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. 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;
-        cerr << "                      input instead." << endl;
-        cerr << endl;
-        cerr << "  -n, --normalise     Normalise input audio files to signal absolute max = 1.f." << endl;
-        cerr << endl;
-        cerr << "  -f, --force         Continue with subsequent files following an error." << endl;
-        cerr << endl;
-        cerr << "Housekeeping options:" << endl;
-        cerr << endl;
+        cerr << "  -t, --transform <T> "
+             << wrapCol("Apply transform described in transform file <T> to"
+                        " all input audio files. You may supply this option" 
+                        " multiple times. You must supply this option, -T, or -d" 
+                        " at least once for any work to be done. Transform format" 
+                        " may be SV transform XML or Vamp transform RDF/Turtle."
+                        " A skeleton transform file for"
+                        " a given transform id can be generated using the"
+                        " -s option (see below). See accompanying"
+                        " documentation for transform examples.")
+             << endl << endl;
+        cerr << "  -T, --transforms <T> "
+             << wrapCol("Apply all transforms described in transform files"
+                        " whose names are listed in text file <T>. You may supply"
+                        " this option multiple times.")
+             << endl << endl;
+        cerr << "  -d, --default <I>   "
+             << wrapCol("Apply the default transform for transform id <I>. This"
+                        " is equivalent to generating a skeleton transform for the"
+                        " id (using the -s option, below) and then applying that,"
+                        " unmodified, with the -t option in the normal way. Note"
+                        " that results may vary, as default"
+                        " processing parameters may change between releases of "
+                        + myname + " as well as of individual plugins. Do not use"
+                        " this in production systems. You may supply this option"
+                        " multiple times, and mix it with -t and -T.")
+             << endl << endl;
+        cerr << "  -w, --writer <W>    Write output using writer type <W>.\n"
+             << "                      " << writerText << endl
+             << "                      "
+             << wrapCol("You may supply this option multiple times. You must"
+                        " supply this option at least once for any work to be done.")
+             << endl << endl;
+        cerr << "  -S, --summary <S>   "
+             << wrapCol("In addition to the result features, write summary feature"
+                        " of summary type <S>.") << endl
+             << "                      "
+             << wrapCol("Supported summary types are min, max, mean, median, mode,"
+                        " sum, variance, sd, count.") << endl
+             << "                      You may supply this option multiple times."
+             << endl << endl;
+        cerr << "      --summary-only  "
+             << wrapCol("Write only summary features; do not write the regular"
+                        " result features.")
+             << endl << endl;
+        cerr << "      --segments <A>,<B>[,...]\n                      "
+             << wrapCol("Summarise in segments, with segment boundaries"
+                        " at A, B, ... seconds.")
+             << endl << endl;
+        cerr << "      --segments-from <F>\n                      "
+             << wrapCol("Summarise in segments, with segment boundaries"
+                        " at times read from the text file <F>. (one time per"
+                        " line, in seconds).")
+             << endl << endl;
+        cerr << "  -m, --multiplex     "
+             << wrapCol("If multiple input audio files are given, use mono"
+                        " mixdowns of the files as the input channels for a single"
+                        " invocation of each transform, instead of running the"
+                        " transform against all files separately. The first file"
+                        " will be used for output reference name and sample rate.")
+             << endl << endl;
+        cerr << "  -r, --recursive     "
+             << wrapCol("If any of the <audio> arguments is found to be a local"
+                        " directory, search the tree starting at that directory"
+                        " for all supported audio files and take all of those as"
+                        " input in place of it.")
+             << endl << endl;
+        cerr << "  -n, --normalise     "
+             << wrapCol("Normalise each input audio file to signal abs max = 1.f.")
+             << endl << endl;
+        cerr << "  -f, --force         "
+             << wrapCol("Continue with subsequent files following an error.")
+             << endl << endl;
+        cerr << "Housekeeping options:"
+             << endl << endl;
         cerr << "  -l, --list          List available transform ids to standard output." << endl;
         cerr << "      --list-writers  List supported writer types to standard output." << endl;
         cerr << "      --list-formats  List supported input audio formats to standard output." << endl;
         cerr << endl;
-        cerr << "  -s, --skeleton <I>  Generate a skeleton transform file for transform id <I>" << endl;
-        cerr << "                      and write it to standard output." << endl;
-        cerr << endl;
+        cerr << "  -s, --skeleton <I>  "
+             << wrapCol("Generate a skeleton RDF transform file for transform id"
+                        " <I>, with default parameters for that transform, and write it"
+                        " to standard output.")
+             << endl << endl;
         cerr << "  -v, --version       Show the version number and exit." << endl;
         cerr << endl;
-        cerr << "      --minversion <V> Exit with successful return code if the version of" << endl;
-        cerr << "                      " << myname << " is at least <V>, failure otherwise." << endl;
-        cerr << "                      For scripts that depend on certain option support." << endl;
-        cerr << endl;
+        cerr << "      --minversion <V> "
+             << wrapCol("Exit with successful return code if the version of "
+                        + myname + " is at least <V>, failure otherwise."
+                        " For scripts that depend on certain option support.")
+             << endl << endl;
         cerr << "  -h, --help          Show help." << endl;
         cerr << "  -h, --help <W>      Show help for writer type W." << endl;
         cerr << "                      " << writerText << endl;
 
-        cerr << endl;
-        cerr << "If no -w (or --writer) options are supplied, either the -l -s -v or -h option" << endl;
-        cerr << "(or long equivalent) must be given instead." << endl;
+        cerr << endl
+             << wrap("If no -w (or --writer) options are supplied, one of the"
+                     " housekeeping options (-l -s -v -h or long equivalent) must"
+                     " be given instead.", 78, 0)
+             << endl;
 
     } else {
 
@@ -857,8 +881,7 @@
     if (!requestedSummaryTypes.empty()) {
         if (!manager.setSummaryTypes(requestedSummaryTypes,
                                      boundaries)) {
-            cerr << myname
-                 << ": failed to set requested summary types" << endl;
+            cerr << myname << ": failed to set requested summary types" << endl;
             exit(1);
         }
     }
--- a/tests/include.sh	Fri Mar 18 15:15:37 2016 +0000
+++ b/tests/include.sh	Fri Mar 18 15:15:55 2016 +0000
@@ -13,8 +13,8 @@
     ;;
 esac
 
-version=1.3
-nextversion=1.4
+version=1.4
+nextversion=1.5
 
 testdir=$mypath/..
 r=$testdir/../sonic-annotator
@@ -60,11 +60,13 @@
 }
 
 jsoncompare() {
+    # The Sonic Annotator version number appears in the JAMS output --
+    # filter that out, and also reformat to ignore whitespace differences
     a="$1"
     b="$2"
-    cat "$a" | json_reformat > "${a}__"
-    cat "$b" | json_reformat > "${b}__"
-    cmp -s "$a" "$b"
+    cat "$a" | sed 's/Sonic Annotator v[0-9.]*/Sonic Annotator vXXX/' | json_reformat > "${a}__"
+    cat "$b" | sed 's/Sonic Annotator v[0-9.]*/Sonic Annotator vXXX/' | json_reformat > "${b}__"
+    cmp -s "${a}__" "${b}__"
     rv=$?
     rm "${a}__" "${b}__"
     return $rv
@@ -81,7 +83,7 @@
 	echo "--"
 	cat "$3"
 	echo "--"
-	echo "Diff:"
+	echo "Diff (output on left, expected on right):"
 	echo "--"
 	sdiff -w78 "$2" "$3"
 	echo "--"
--- a/tests/test-multiple-audio/expected/all-files.csv	Fri Mar 18 15:15:37 2016 +0000
+++ b/tests/test-multiple-audio/expected/all-files.csv	Fri Mar 18 15:15:55 2016 +0000
@@ -1,36 +1,36 @@
 "./../audio/20sec-silence.wav",0.000000000,20.015600906,mean,61.1636,"(mean value, continuous-time average)"
 ,0.000000000,20.015600906,median,0,"(median value, continuous-time average)"
 ,0.000000000,20.015600906,mode,0,"(modal value, continuous-time average)"
-,0.000000000,20.015600906,mean,893.139,"(mean value, continuous-time average)"
-"./../audio/3clicks.mp3",0.000000000,5.061950112,mean,169.206,"(mean value, continuous-time average)"
-,0.000000000,5.061950112,median,170,"(median value, continuous-time average)"
-,0.000000000,5.061950112,mode,162,"(modal value, continuous-time average)"
-,0.000000000,5.061950112,mean,699.101,"(mean value, continuous-time average)"
-"./../audio/3clicks.ogg",0.000000000,4.992290248,mean,169.505,"(mean value, continuous-time average)"
-,0.000000000,4.992290248,median,170,"(median value, continuous-time average)"
-,0.000000000,4.992290248,mode,174,"(modal value, continuous-time average)"
-,0.000000000,4.992290248,mean,724.777,"(mean value, continuous-time average)"
+,0.000000000,20.015600906,mean,1406.56,"(mean value, continuous-time average)"
+"./../audio/3clicks.mp3",0.000000000,5.061950112,mean,174.507,"(mean value, continuous-time average)"
+,0.000000000,5.061950112,median,173,"(median value, continuous-time average)"
+,0.000000000,5.061950112,mode,173,"(modal value, continuous-time average)"
+,0.000000000,5.061950112,mean,1510.95,"(mean value, continuous-time average)"
+"./../audio/3clicks.ogg",0.000000000,4.992290248,mean,169.481,"(mean value, continuous-time average)"
+,0.000000000,4.992290248,median,156,"(median value, continuous-time average)"
+,0.000000000,4.992290248,mode,132,"(modal value, continuous-time average)"
+,0.000000000,4.992290248,mean,1480.59,"(mean value, continuous-time average)"
 "./../audio/3clicks8.wav",0.000000000,4.992290248,mean,169.391,"(mean value, continuous-time average)"
 ,0.000000000,4.992290248,median,169,"(median value, continuous-time average)"
 ,0.000000000,4.992290248,mode,164,"(modal value, continuous-time average)"
-,0.000000000,4.992290248,mean,703.191,"(mean value, continuous-time average)"
+,0.000000000,4.992290248,mean,1403.77,"(mean value, continuous-time average)"
 "./../audio/3clicks8quiet.wav",0.000000000,4.992290248,mean,169.058,"(mean value, continuous-time average)"
 ,0.000000000,4.992290248,median,169,"(median value, continuous-time average)"
 ,0.000000000,4.992290248,mode,165,"(modal value, continuous-time average)"
-,0.000000000,4.992290248,mean,673.623,"(mean value, continuous-time average)"
-"./../audio/6clicks.ogg",0.000000000,9.961360543,mean,167.58,"(mean value, continuous-time average)"
-,0.000000000,9.961360543,median,167,"(median value, continuous-time average)"
-,0.000000000,9.961360543,mode,170,"(modal value, continuous-time average)"
-,0.000000000,9.961360543,mean,714.928,"(mean value, continuous-time average)"
+,0.000000000,4.992290248,mean,1342.8,"(mean value, continuous-time average)"
+"./../audio/6clicks.ogg",0.000000000,9.961360543,mean,168.686,"(mean value, continuous-time average)"
+,0.000000000,9.961360543,median,158,"(median value, continuous-time average)"
+,0.000000000,9.961360543,mode,138,"(modal value, continuous-time average)"
+,0.000000000,9.961360543,mean,1423.64,"(mean value, continuous-time average)"
 "./../audio/6clicks8.wav",0.000000000,9.961360543,mean,170.174,"(mean value, continuous-time average)"
 ,0.000000000,9.961360543,median,169,"(median value, continuous-time average)"
 ,0.000000000,9.961360543,mode,164,"(modal value, continuous-time average)"
-,0.000000000,9.961360543,mean,698.921,"(mean value, continuous-time average)"
-"./../audio/id3v2-iso-8859-1.mp3",0.000000000,5.015510203,mean,170.655,"(mean value, continuous-time average)"
-,0.000000000,5.015510203,median,171,"(median value, continuous-time average)"
-,0.000000000,5.015510203,mode,171,"(modal value, continuous-time average)"
-,0.000000000,5.015510203,mean,690.028,"(mean value, continuous-time average)"
-"./../audio/id3v2-ucs-2.mp3",0.000000000,5.061950112,mean,169.206,"(mean value, continuous-time average)"
-,0.000000000,5.061950112,median,170,"(median value, continuous-time average)"
-,0.000000000,5.061950112,mode,162,"(modal value, continuous-time average)"
-,0.000000000,5.061950112,mean,699.101,"(mean value, continuous-time average)"
+,0.000000000,9.961360543,mean,1411.85,"(mean value, continuous-time average)"
+"./../audio/id3v2-iso-8859-1.mp3",0.000000000,5.015510203,mean,176.083,"(mean value, continuous-time average)"
+,0.000000000,5.015510203,median,172,"(median value, continuous-time average)"
+,0.000000000,5.015510203,mode,164,"(modal value, continuous-time average)"
+,0.000000000,5.015510203,mean,1492.7,"(mean value, continuous-time average)"
+"./../audio/id3v2-ucs-2.mp3",0.000000000,5.061950112,mean,174.507,"(mean value, continuous-time average)"
+,0.000000000,5.061950112,median,173,"(median value, continuous-time average)"
+,0.000000000,5.061950112,mode,173,"(modal value, continuous-time average)"
+,0.000000000,5.061950112,mean,1510.95,"(mean value, continuous-time average)"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-multiple-audio/expected/multiplexed.csv	Fri Mar 18 15:15:55 2016 +0000
@@ -0,0 +1,4 @@
+"3clicks.mp3",0.000000000,9.961360543,mean,168.908,"(mean value, continuous-time average)"
+,0.000000000,9.961360543,median,162,"(median value, continuous-time average)"
+,0.000000000,9.961360543,mode,144,"(modal value, continuous-time average)"
+,0.000000000,9.961360543,mean,1418.17,"(mean value, continuous-time average)"
--- a/tests/test-multiple-audio/expected/playlist.csv	Fri Mar 18 15:15:37 2016 +0000
+++ b/tests/test-multiple-audio/expected/playlist.csv	Fri Mar 18 15:15:55 2016 +0000
@@ -1,8 +1,8 @@
-"3clicks.mp3",0.000000000,5.061950112,mean,169.206,"(mean value, continuous-time average)"
-,0.000000000,5.061950112,median,170,"(median value, continuous-time average)"
-,0.000000000,5.061950112,mode,162,"(modal value, continuous-time average)"
-,0.000000000,5.061950112,mean,699.101,"(mean value, continuous-time average)"
-"6clicks.ogg",0.000000000,9.961360543,mean,167.58,"(mean value, continuous-time average)"
-,0.000000000,9.961360543,median,167,"(median value, continuous-time average)"
-,0.000000000,9.961360543,mode,170,"(modal value, continuous-time average)"
-,0.000000000,9.961360543,mean,714.928,"(mean value, continuous-time average)"
+"3clicks.mp3",0.000000000,5.061950112,mean,174.507,"(mean value, continuous-time average)"
+,0.000000000,5.061950112,median,173,"(median value, continuous-time average)"
+,0.000000000,5.061950112,mode,173,"(modal value, continuous-time average)"
+,0.000000000,5.061950112,mean,1510.95,"(mean value, continuous-time average)"
+"6clicks.ogg",0.000000000,9.961360543,mean,168.686,"(mean value, continuous-time average)"
+,0.000000000,9.961360543,median,158,"(median value, continuous-time average)"
+,0.000000000,9.961360543,mode,138,"(modal value, continuous-time average)"
+,0.000000000,9.961360543,mean,1423.64,"(mean value, continuous-time average)"
--- a/tests/test-multiple-audio/test-multiple-audio.sh	Fri Mar 18 15:15:37 2016 +0000
+++ b/tests/test-multiple-audio/test-multiple-audio.sh	Fri Mar 18 15:15:55 2016 +0000
@@ -2,14 +2,21 @@
 
 . ../include.sh
 
-tmpfile=$mypath/tmp_1_$$
+tmpfile1=$mypath/tmp_1_$$
+tmpfile2=$mypath/tmp_2_$$
 
-trap "rm -f $tmpfile" 0
+trap "rm -f $tmpfile1 $tmpfile2" 0
 
 transform=$mypath/transforms/detectionfunction.n3 
 
 urlbase=http://vamp-plugins.org/sonic-annotator/testfiles
 
+have_network=yes
+if ! ping -c 1 8.8.8.8 2>/dev/null 1>&2 ; then
+    echo "(network appears unavailable, skipping networking tests)"
+    have_network=no
+fi
+
 
 # 1. Recursive local directory
 
@@ -17,12 +24,12 @@
 # would have to regenerate it if we added more test audio files. Note
 # that the -r flag is not supposed to pick up playlist files, only
 # audio files
-$r -t $transform -w csv --csv-stdout -r --summary-only $audiopath > $tmpfile 2>/dev/null || \
+$r -t $transform -w csv --csv-stdout -r --summary-only $audiopath > $tmpfile1 2>/dev/null || \
     fail "Fails to run transform $transform with recursive dir option"
 
 expected=$mypath/expected/all-files
-csvcompare $tmpfile $expected.csv || \
-    faildiff "Output mismatch for transform $transform with summaries and recursive dir option" $tmpfile $expected.csv
+csvcompare $tmpfile1 $expected.csv || \
+    faildiff "Output mismatch for transform $transform with summaries and recursive dir option" $tmpfile1 $expected.csv
 
 
 # 2. Local playlist file referring to local audio files
@@ -30,72 +37,101 @@
 # Here we strip any leading path from the audio file in the output,
 # because the playlist reader will have resolved files to absolute
 # paths and those will differ between systems
-$r -t $transform -w csv --csv-stdout $audiopath/playlist.m3u --summary-only 2>/dev/null | sed 's,^"[^"]*/,",' > $tmpfile || \
+$r -t $transform -w csv --csv-stdout $audiopath/playlist.m3u --summary-only 2>/dev/null > "$tmpfile2" || \
     fail "Fails to run transform $transform with playlist input"
 
+cat "$tmpfile2" | sed 's,^"[^"]*/,",' > "$tmpfile1"
+
 expected=$mypath/expected/playlist
-csvcompare $tmpfile $expected.csv || \
-    faildiff "Output mismatch for transform $transform with summaries and playlist input" $tmpfile $expected.csv
+csvcompare $tmpfile1 $expected.csv || \
+    faildiff "Output mismatch for transform $transform with summaries and playlist input" $tmpfile1 $expected.csv
 
 
 # 3. Multiple files supplied directly on command line
 
 # Strip paths again, just so we can use the same output comparison
 # file as above
-$r -t $transform -w csv --csv-stdout $audiopath/3clicks.mp3 $audiopath/6clicks.ogg --summary-only 2>/dev/null | sed 's,^"[^"]*/,",' > $tmpfile || \
+$r -t $transform -w csv --csv-stdout $audiopath/3clicks.mp3 $audiopath/6clicks.ogg --summary-only 2>/dev/null > $tmpfile2 || \
     fail "Fails to run transform $transform with 2-file input"
 
+cat "$tmpfile2" | sed 's,^"[^"]*/,",' > "$tmpfile1"
+
 expected=$mypath/expected/playlist
-csvcompare $tmpfile $expected.csv || \
-    faildiff "Output mismatch for transform $transform with summaries and 2-file input" $tmpfile $expected.csv
+csvcompare $tmpfile1 $expected.csv || \
+    faildiff "Output mismatch for transform $transform with summaries and 2-file input" $tmpfile1 $expected.csv
 
 
 # 4. Multiple files supplied directly on command line, with file: URL
 
-$r -t $transform -w csv --csv-stdout $audiopath/3clicks.mp3 file://`pwd`/$audiopath/6clicks.ogg --summary-only 2>/dev/null | sed 's,^"[^"]*/,",' > $tmpfile || \
+$r -t $transform -w csv --csv-stdout $audiopath/3clicks.mp3 file://`pwd`/$audiopath/6clicks.ogg --summary-only 2>/dev/null > $tmpfile2 || \
     fail "Fails to run transform $transform with 2-file input"
 
-expected=$mypath/expected/playlist
-csvcompare $tmpfile $expected.csv || \
-    faildiff "Output mismatch for transform $transform with summaries and 2-file input using file:// URL" $tmpfile $expected.csv
-
-
-# 5. Remote playlist file referring to remote audio files
-
-$r -t $transform -w csv --csv-stdout $urlbase/playlist.m3u --summary-only 2>/dev/null | sed 's,^"[^"]*/,",' > $tmpfile || \
-    fail "Fails to run transform $transform with remote playlist input"
+cat "$tmpfile2" | sed 's,^"[^"]*/,",' > "$tmpfile1"
 
 expected=$mypath/expected/playlist
-csvcompare $tmpfile $expected.csv || \
-    faildiff "Output mismatch for transform $transform with summaries and remote playlist input" $tmpfile $expected.csv
+csvcompare $tmpfile1 $expected.csv || \
+    faildiff "Output mismatch for transform $transform with summaries and 2-file input using file:// URL" $tmpfile1 $expected.csv
 
 
-# 6. Local playlist file referring to mixture of remote and local audio files
+if [ "$have_network" = "yes" ]; then
 
-$r -t $transform -w csv --csv-stdout $audiopath/remote-playlist.m3u --summary-only 2>/dev/null | sed 's,^"[^"]*/,",' > $tmpfile || \
-    fail "Fails to run transform $transform with playlist of remote files"
+    # 5. Remote playlist file referring to remote audio files
 
-expected=$mypath/expected/playlist
-csvcompare $tmpfile $expected.csv || \
-    faildiff "Output mismatch for transform $transform with summaries and remote playlist input" $tmpfile $expected.csv
+    $r -t $transform -w csv --csv-stdout $urlbase/playlist.m3u --summary-only 2>/dev/null > $tmpfile2 || \
+	fail "Fails to run transform $transform with remote playlist input"
 
+    cat "$tmpfile2" | sed 's,^"[^"]*/,",' > "$tmpfile1"
 
-# 7. Multiple remote files supplied directly on command line
+    expected=$mypath/expected/playlist
+    csvcompare $tmpfile1 $expected.csv || \
+	faildiff "Output mismatch for transform $transform with summaries and remote playlist input" $tmpfile1 $expected.csv
 
-$r -t $transform -w csv --csv-stdout $urlbase/3clicks.mp3 $urlbase/6clicks.ogg --summary-only 2>/dev/null | sed 's,^"[^"]*/,",' > $tmpfile || \
-    fail "Fails to run transform $transform with 2-file remote input"
 
-expected=$mypath/expected/playlist
-csvcompare $tmpfile $expected.csv || \
-    faildiff "Output mismatch for transform $transform with summaries and 2-file input" $tmpfile $expected.csv
+    # 6. Local playlist file referring to mixture of remote and local audio files
 
+    $r -t $transform -w csv --csv-stdout $audiopath/remote-playlist.m3u --summary-only 2>/dev/null > $tmpfile2 || \
+	fail "Fails to run transform $transform with playlist of remote files"
 
-# 8. Mixture of remote and local files supplied on command line
+    cat "$tmpfile2" | sed 's,^"[^"]*/,",' > "$tmpfile1"
 
-$r -t $transform -w csv --csv-stdout $audiopath/3clicks.mp3 $urlbase/6clicks.ogg --summary-only 2>/dev/null | sed 's,^"[^"]*/,",' > $tmpfile || \
-    fail "Fails to run transform $transform with 2-file remote input"
+    expected=$mypath/expected/playlist
+    csvcompare $tmpfile1 $expected.csv || \
+	faildiff "Output mismatch for transform $transform with summaries and remote playlist input" $tmpfile1 $expected.csv
 
-expected=$mypath/expected/playlist
-csvcompare $tmpfile $expected.csv || \
-    faildiff "Output mismatch for transform $transform with summaries and mixed local/remote 2-file input" $tmpfile $expected.csv
 
+    # 7. Multiple remote files supplied directly on command line
+
+    $r -t $transform -w csv --csv-stdout $urlbase/3clicks.mp3 $urlbase/6clicks.ogg --summary-only 2>/dev/null > $tmpfile2 || \
+	fail "Fails to run transform $transform with 2-file remote input"
+
+    cat "$tmpfile2" | sed 's,^"[^"]*/,",' > "$tmpfile1"
+
+    expected=$mypath/expected/playlist
+    csvcompare $tmpfile1 $expected.csv || \
+	faildiff "Output mismatch for transform $transform with summaries and 2-file input" $tmpfile1 $expected.csv
+
+
+    # 8. Mixture of remote and local files supplied on command line
+
+    $r -t $transform -w csv --csv-stdout $audiopath/3clicks.mp3 $urlbase/6clicks.ogg --summary-only 2>/dev/null > $tmpfile2 || \
+	fail "Fails to run transform $transform with 2-file remote input"
+
+    cat "$tmpfile2" | sed 's,^"[^"]*/,",' > "$tmpfile1"
+
+    expected=$mypath/expected/playlist
+    csvcompare $tmpfile1 $expected.csv || \
+	faildiff "Output mismatch for transform $transform with summaries and mixed local/remote 2-file input" $tmpfile1 $expected.csv
+
+fi
+
+# 9. As 3, but multiplexing rather than extracting separately from each file
+
+$r -t $transform --multiplex -w csv --csv-stdout $audiopath/3clicks.mp3 $audiopath/6clicks.ogg --summary-only 2>/dev/null > $tmpfile2 || \
+    fail "Fails to run transform $transform with 2-file input"
+
+cat "$tmpfile2" | sed 's,^"[^"]*/,",' > "$tmpfile1"
+
+expected=$mypath/expected/multiplexed
+csvcompare $tmpfile1 $expected.csv || \
+    faildiff "Output mismatch for transform $transform with summaries and 2-file multiplexed input" $tmpfile1 $expected.csv
+
--- a/tests/test-multiple-audio/transforms/detectionfunction.n3	Fri Mar 18 15:15:37 2016 +0000
+++ b/tests/test-multiple-audio/transforms/detectionfunction.n3	Fri Mar 18 15:15:55 2016 +0000
@@ -13,6 +13,7 @@
 	vamp:output examples:percussiononsets_output_detectionfunction ;
 	vamp:summary_type "median" .
 
+# This is not a summary and so should not appear with --summary-only
 :transform2 a vamp:Transform;
 	vamp:plugin examples:percussiononsets ;
 	vamp:output examples:percussiononsets_output_onsets .
@@ -22,11 +23,19 @@
 	vamp:output examples:percussiononsets_output_detectionfunction ;
 	vamp:summary_type "mode" .
 
+# This has different step and block sizes from the default
+# (:transform0), and so should be listed separately with different
+# values in the output
 :transform4 a vamp:Transform;
 	vamp:plugin examples:percussiononsets ;
 	vamp:output examples:percussiononsets_output_detectionfunction ;
-	vamp:step_size 2048 ;
-	vamp:block_size 4096 ;
+	vamp:step_size 4096 ;
+	vamp:block_size 8192 ;
 	vamp:summary_type "mean" .
 
+# This is not a summary and so should not appear with --summary-only
+:transform5 a vamp:Transform;
+	vamp:plugin examples:percussiononsets ;
+	vamp:output examples:percussiononsets_output_detectionfunction .
+
  
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-parse-errors/expected/empty.ttl.txt	Fri Mar 18 15:15:55 2016 +0000
@@ -0,0 +1,1 @@
+RDF parser reported: Failed to import model from URL
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-parse-errors/expected/empty.xml.txt	Fri Mar 18 15:15:55 2016 +0000
@@ -0,0 +1,1 @@
+XML parser reported: unexpected end of file at line 1, column 1
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-parse-errors/expected/garbage.dat.txt	Fri Mar 18 15:15:55 2016 +0000
@@ -0,0 +1,2 @@
+ERROR: RDF parser reported:
+ERROR: XML parser reported:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-parse-errors/expected/invalid-turtle.ttl.txt	Fri Mar 18 15:15:55 2016 +0000
@@ -0,0 +1,1 @@
+RDF parser reported: Failed to import model from URL
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-parse-errors/expected/invalid-xml.xml.txt	Fri Mar 18 15:15:55 2016 +0000
@@ -0,0 +1,1 @@
+XML parser reported: tag mismatch
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-parse-errors/expected/valid-turtle-no-transform.ttl.txt	Fri Mar 18 15:15:55 2016 +0000
@@ -0,0 +1,1 @@
+valid RDF but defines no transforms
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-parse-errors/expected/valid-xml-no-transform.xml.txt	Fri Mar 18 15:15:55 2016 +0000
@@ -0,0 +1,1 @@
+valid XML but defines no transform
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-parse-errors/inputs/garbage.dat	Fri Mar 18 15:15:55 2016 +0000
@@ -0,0 +1,1 @@
+gZ~zN9Ԭb$7:2s𤇧0H2	zZ4#<An#|lTs:o_BKꥵ+-%WY[N-tLf7Ҷ
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-parse-errors/inputs/invalid-turtle.ttl	Fri Mar 18 15:15:55 2016 +0000
@@ -0,0 +1,2 @@
+# Trailing ;
+<http://example/s> <http://example/p> <http://example/o> ;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-parse-errors/inputs/invalid-xml.xml	Fri Mar 18 15:15:55 2016 +0000
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<rss version="2.0">
+  <channel>
+    <title>A Title</title>
+    <link>http://example.com</link>
+    <description/>
+    <!-- NB missing end-tag for channel -->
+</rss>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-parse-errors/inputs/valid-turtle-no-transform.ttl	Fri Mar 18 15:15:55 2016 +0000
@@ -0,0 +1,1 @@
+<http://example/s> <http://example/p> <http://example/o> .
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-parse-errors/inputs/valid-xml-no-transform.xml	Fri Mar 18 15:15:55 2016 +0000
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<rss version="2.0">
+  <channel>
+    <title>A Title</title>
+    <link>http://example.com</link>
+    <description/>
+  </channel>
+</rss>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-parse-errors/test-parse-errors.sh	Fri Mar 18 15:15:55 2016 +0000
@@ -0,0 +1,29 @@
+#!/bin/bash
+
+. ../include.sh
+
+infile=$audiopath/3clicks8.wav
+tmpfile=$mypath/tmp_$$
+trap "rm -f $tmpfile" 0
+
+for transform in "$mypath"/inputs/* ; do
+
+    base=$(basename "$transform")
+    expected="$mypath"/expected/"$base".txt
+
+    if [ ! -f "$expected" ]; then
+	fail "Internal error: Expected file $expected not found for transform $transform"
+    fi
+
+    if $r -t "$transform" -w csv --csv-one-file /dev/null "$infile" 2>"$tmpfile" ; then
+	fail "Erroneously succeeds in running bogus transform $transform"
+    fi
+
+    cat "$expected" | while read line; do
+	if ! fgrep -q "$line" "$tmpfile" ; then
+	    fail "Expected output text \"$line\" not found in diagnostic output for transform $base"
+	fi
+    done
+    
+done
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-summaries/expected/testplug-curve-vsr-count.csv	Fri Mar 18 15:15:55 2016 +0000
@@ -0,0 +1,1 @@
+0.000000000,9.750000000,count,10,"(count)"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-summaries/expected/testplug-curve-vsr-max.csv	Fri Mar 18 15:15:55 2016 +0000
@@ -0,0 +1,1 @@
+0.000000000,9.750000000,max,0.9,"(maximum value)"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-summaries/expected/testplug-curve-vsr-mean.csv	Fri Mar 18 15:15:55 2016 +0000
@@ -0,0 +1,20 @@
+# Sonic Annotator's summary method integrates the values across
+# time. If an output lacks duration for its features, each feature is
+# considered to extend until the timestamp of the following feature,
+# and the final feature is considered to extend until the end of the
+# input or the last-ending output, whichever is later.
+#
+# The curve-vsr output is therefore considered to have 9 values (0.0
+# to 0.8) that span 0.75 seconds each, followed by one value (0.9)
+# that spans the 3 seconds from 6.75 to 9.75 where the final output of
+# the plugin ends (that's the notes-regions output).
+#
+# The sum of 0.0..0.8 is 3.6, so the mean is (3.6 * 0.75 + 0.9 * 3.0)
+# / 9.75 = 0.553846 approx.
+# 
+# Equivalently we can consider the last feature the same as four
+# features of 0.75 seconds each (i.e. the same durations as the first
+# 9) each with the same value, 0.9. The mean is then the sum of
+# 0.0..0.9 plus three more 0.9s, i.e. 7.2, divided by 13.
+#
+0.000000000,9.750000000,mean,0.553846,"(mean value, continuous-time average)"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-summaries/expected/testplug-curve-vsr-median.csv	Fri Mar 18 15:15:55 2016 +0000
@@ -0,0 +1,16 @@
+# Sonic Annotator's summary method integrates the values across
+# time. If an output lacks duration for its features, each feature is
+# considered to extend until the timestamp of the following feature,
+# and the final feature is considered to extend until the end of the
+# input or the last-ending output, whichever is later.
+#
+# The curve-vsr output is therefore considered to have 9 values (0.0
+# to 0.8) that span 0.75 seconds each, followed by one value (0.9)
+# that spans the 3 seconds from 6.75 to 9.75 where the final output of
+# the plugin ends (that's the notes-regions output).
+#
+# Since the values from this output are already sorted and are
+# distinct, the integrated median is just whatever is in effect at
+# time 9.75 / 2.0 = 4.875. This value is 0.6.
+#
+0.000000000,9.750000000,median,0.6,"(median value, continuous-time average)"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-summaries/expected/testplug-curve-vsr-min.csv	Fri Mar 18 15:15:55 2016 +0000
@@ -0,0 +1,1 @@
+0.000000000,9.750000000,min,0,"(minimum value)"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-summaries/expected/testplug-curve-vsr-mode.csv	Fri Mar 18 15:15:55 2016 +0000
@@ -0,0 +1,15 @@
+# Sonic Annotator's summary method integrates the values across
+# time. If an output lacks duration for its features, each feature is
+# considered to extend until the timestamp of the following feature,
+# and the final feature is considered to extend until the end of the
+# input or the last-ending output, whichever is later.
+#
+# The curve-vsr output is therefore considered to have 9 values (0.0
+# to 0.8) that span 0.75 seconds each, followed by one value (0.9)
+# that spans the 3 seconds from 6.75 to 9.75 where the final output of
+# the plugin ends (that's the notes-regions output).
+#
+# The modal value is the one spanning the longest, which of course is
+# that final one, 0.9.
+#
+0.000000000,9.750000000,mode,0.9,"(modal value, continuous-time average)"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-summaries/expected/testplug-curve-vsr-sd.csv	Fri Mar 18 15:15:55 2016 +0000
@@ -0,0 +1,4 @@
+# Standard deviation is sqrt(variance), see the variance file for
+# notes on that value.
+#
+0.000000000,9.750000000,sd,0.315291,"(standard deviation, continuous-time average)"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-summaries/expected/testplug-curve-vsr-sum.csv	Fri Mar 18 15:15:55 2016 +0000
@@ -0,0 +1,1 @@
+0.000000000,9.750000000,sum,4.5,"(sum)"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-summaries/expected/testplug-curve-vsr-variance.csv	Fri Mar 18 15:15:55 2016 +0000
@@ -0,0 +1,20 @@
+# Sonic Annotator's summary method integrates the values across
+# time. If an output lacks duration for its features, each feature is
+# considered to extend until the timestamp of the following feature,
+# and the final feature is considered to extend until the end of the
+# input or the last-ending output, whichever is later.
+#
+# The curve-vsr output is therefore considered to have 9 values (0.0
+# to 0.8) that span 0.75 seconds each, followed by one value (0.9)
+# that spans the 3 seconds from 6.75 to 9.75 where the final output of
+# the plugin ends (that's the notes-regions output).
+#
+# We can consider the last feature the same as four features of 0.75
+# seconds each (i.e. the same durations as the first 9) each with the
+# same value, 0.9.
+#
+# The mean m is then 7.2 / 13 = 0.553846 approx, and the variance is
+# the sum for each of the 13 (partly fictitious) values v of (v-m)^2,
+# divided by 13. This works out as 0.0994083 ish.
+#
+0.000000000,9.750000000,variance,0.0994083,"(variance, continuous-time average)"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-summaries/expected/testplug-grid-fsr-max.csv	Fri Mar 18 15:15:55 2016 +0000
@@ -0,0 +1,1 @@
+0.000000000,9.750000000,max,0.55,0.6,0.65,0.7,0.75,0.8,0.85,0.9,0.95,1,"(maximum value)"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-summaries/expected/testplug-grid-fsr-min.csv	Fri Mar 18 15:15:55 2016 +0000
@@ -0,0 +1,1 @@
+0.000000000,9.750000000,min,0.1,0.15,0.2,0.25,0.3,0.35,0.4,0.45,0.5,0.55,"(minimum value)"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-summaries/test-summaries-testplugin.sh	Fri Mar 18 15:15:55 2016 +0000
@@ -0,0 +1,36 @@
+#!/bin/bash
+
+. ../include.sh
+
+infile=$audiopath/3clicks8.wav
+tmpfile=$mypath/tmp_1_$$
+tmpfile2=$mypath/tmp_2_$$
+
+trap "rm -f $tmpfile $tmpfile2" 0
+
+for output in curve-vsr grid-fsr; do
+
+    for summary in min max mean median mode sum variance sd count; do
+
+	# grid-fsr is bulkier, and we're only really concerned that
+	# we're getting a sane result per bin, so just do min and max
+	# there
+	if [ "$output" = "grid-fsr" ]; then
+	    case "$summary" in
+		mean|median|mode|sum|variance|sd|count) continue;;
+	    esac
+	fi
+	
+	id="$testplug:$output"
+	expected="$mypath/expected/testplug-$output-$summary.csv"
+
+	cat "$expected" | grep -v '^#' > "$tmpfile2"
+	
+	$r -d $id -w csv --csv-stdout -S $summary --summary-only --csv-omit-filename $infile > $tmpfile 2>/dev/null || \
+	    fail "Fails to run transform id $id with summary type $summary"
+
+	csvcompare "$tmpfile" "$tmpfile2" ||
+	    faildiff "Output mismatch for output $output with summary type $summary" "$tmpfile" "$tmpfile2"
+
+    done
+done
--- a/tests/test-summaries/test-summaries.sh	Fri Mar 18 15:15:37 2016 +0000
+++ b/tests/test-summaries/test-summaries.sh	Fri Mar 18 15:15:55 2016 +0000
@@ -83,5 +83,6 @@
 compare $tmpcanonical $expcanonical || \
     faildiff "Output mismatch against expected $sexpected.n3 for canonicalised version of transform $stransform" $tmpcanonical $expcanonical
 
-exit 0
+bash "$mypath/test-summaries-testplugin.sh"
 
+
--- a/tests/test.sh	Fri Mar 18 15:15:37 2016 +0000
+++ b/tests/test.sh	Fri Mar 18 15:15:55 2016 +0000
@@ -5,22 +5,23 @@
 for x in \
     supportprogs \
     helpfulflags \
+    parse-errors \
     transforms-basic \
     audioformat \
     vamp-test-plugin \
     as-advertised \
-    rdf-writer \
-    rdf-destinations \
+    summaries \
+    multiple-audio \
     csv-writer \
     csv-destinations \
     lab-writer \
     lab-destinations \
+    rdf-writer \
+    rdf-destinations \
     midi-writer \
     midi-destinations \
     jams-writer \
     jams-destinations \
-    summaries \
-    multiple-audio \
     ; do
 
     echo -n "$x: "
--- a/version.h	Fri Mar 18 15:15:37 2016 +0000
+++ b/version.h	Fri Mar 18 15:15:55 2016 +0000
@@ -1,1 +1,1 @@
-#define RUNNER_VERSION "1.3"
+#define RUNNER_VERSION "1.4"