# HG changeset patch # User Chris Cannam # Date 1413304244 -3600 # Node ID 6ff4da31db8b711dd2b03bfcc41d1e63ae260123 # Parent 04945e74d31497a4404ed985b8bdb859140d0076 Implement .lab file writer diff -r 04945e74d314 -r 6ff4da31db8b .hgsubstate --- a/.hgsubstate Tue Oct 14 11:13:31 2014 +0100 +++ b/.hgsubstate Tue Oct 14 17:30:44 2014 +0100 @@ -1,3 +1,3 @@ d16f0fd6db6104d87882bc43788a3bb1b0f8c528 dataquay 879bdc878826bebec67130326f99397c430419b1 sv-dependency-builds -694301cc71ccb10c8cc5a729c9ffb861891589c2 svcore +ec6e69373997482c65b68a10dac1b8a0b5d13dac svcore diff -r 04945e74d314 -r 6ff4da31db8b runner.pro --- a/runner.pro Tue Oct 14 11:13:31 2014 +0100 +++ b/runner.pro Tue Oct 14 17:30:44 2014 +0100 @@ -84,6 +84,7 @@ runner/FeatureWriterFactory.h \ runner/DefaultFeatureWriter.h \ runner/FeatureExtractionManager.h \ + runner/LabFeatureWriter.h \ runner/MIDIFeatureWriter.h \ runner/MultiplexedReader.h @@ -93,6 +94,7 @@ runner/FeatureExtractionManager.cpp \ runner/AudioDBFeatureWriter.cpp \ runner/FeatureWriterFactory.cpp \ + runner/LabFeatureWriter.cpp \ runner/MIDIFeatureWriter.cpp \ runner/MultiplexedReader.cpp diff -r 04945e74d314 -r 6ff4da31db8b runner/FeatureWriterFactory.cpp --- a/runner/FeatureWriterFactory.cpp Tue Oct 14 11:13:31 2014 +0100 +++ b/runner/FeatureWriterFactory.cpp Tue Oct 14 17:30:44 2014 +0100 @@ -17,10 +17,13 @@ #include "FeatureWriterFactory.h" #include "DefaultFeatureWriter.h" + #include "rdf/RDFFeatureWriter.h" +#include "transform/CSVFeatureWriter.h" + #include "AudioDBFeatureWriter.h" #include "MIDIFeatureWriter.h" -#include "transform/CSVFeatureWriter.h" +#include "LabFeatureWriter.h" set FeatureWriterFactory::getWriterTags() @@ -30,6 +33,7 @@ tags.insert("rdf"); tags.insert("audiodb"); tags.insert("csv"); + tags.insert("lab"); tags.insert("midi"); return tags; } @@ -45,6 +49,8 @@ return new AudioDBFeatureWriter(); } else if (tag == "csv") { return new CSVFeatureWriter(); + } else if (tag == "lab") { + return new LabFeatureWriter(); } else if (tag == "midi") { return new MIDIFeatureWriter(); } diff -r 04945e74d314 -r 6ff4da31db8b runner/LabFeatureWriter.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/runner/LabFeatureWriter.cpp Tue Oct 14 17:30:44 2014 +0100 @@ -0,0 +1,135 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + + 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-2008 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 "LabFeatureWriter.h" + +#include + +#include +#include + +using namespace std; +using namespace Vamp; + +LabFeatureWriter::LabFeatureWriter() : + FileFeatureWriter(SupportOneFilePerTrackTransform | + SupportStdOut, + "lab"), + m_forceEnd(false) +{ +} + +LabFeatureWriter::~LabFeatureWriter() +{ +} + +string +LabFeatureWriter::getDescription() const +{ + return "Write features in .lab, a tab-separated columnar format. The first column is always the feature start time in seconds. If the features have duration, the second column will be the feature end time in seconds. Remaining columns are the feature values (if any) and finally the feature label (if any). There is no identification of the audio file or the transform, so confusion will result if features from different audio or transforms are mixed. For more control over the output, consider using the CSV writer."; +} + +LabFeatureWriter::ParameterList +LabFeatureWriter::getSupportedParameters() const +{ + ParameterList pl = FileFeatureWriter::getSupportedParameters(); + + Parameter p; + + p.name = "fill-ends"; + p.description = "Include end times even for features without duration, by using the gap to the next feature instead."; + p.hasArg = false; + pl.push_back(p); + + return pl; +} + +void +LabFeatureWriter::setParameters(map ¶ms) +{ + FileFeatureWriter::setParameters(params); + + for (map::iterator i = params.begin(); + i != params.end(); ++i) { + if (i->first == "fill-ends") { + m_forceEnd = true; + } + } +} + +void +LabFeatureWriter::write(QString trackId, + const Transform &transform, + const Plugin::OutputDescriptor& , + const Plugin::FeatureList& features, + std::string summaryType) +{ + // Select appropriate output file for our track/transform + // combination + + QTextStream *sptr = getOutputStream(trackId, transform.getIdentifier()); + if (!sptr) { + throw FailedToOpenOutputStream(trackId, transform.getIdentifier()); + } + + QTextStream &stream = *sptr; + + QString sep = "\t"; + + for (unsigned int i = 0; i < features.size(); ++i) { + + QString timestamp = features[i].timestamp.toString().c_str(); + timestamp.replace(QRegExp("^ +"), ""); + stream << timestamp; + + Vamp::RealTime endTime; + bool haveEndTime = true; + + if (features[i].hasDuration) { + endTime = features[i].timestamp + features[i].duration; + } else if (m_forceEnd) { + if (i+1 < features.size()) { + endTime = features[i+1].timestamp; + } else { + //!!! what to do??? can we get the end time of the input file? + endTime = features[i].timestamp; + } + } else { + haveEndTime = false; + } + + if (haveEndTime) { + QString e = endTime.toString().c_str(); + e.replace(QRegExp("^ +"), ""); + stream << sep << e; + } + + for (unsigned int j = 0; j < features[i].values.size(); ++j) { + stream << sep << features[i].values[j]; + } + + if (features[i].label != "") { + stream << sep << "\"" << features[i].label.c_str() << "\""; + } + + stream << "\n"; + } +} + + diff -r 04945e74d314 -r 6ff4da31db8b runner/LabFeatureWriter.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/runner/LabFeatureWriter.h Tue Oct 14 17:30:44 2014 +0100 @@ -0,0 +1,60 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + + 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-2008 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 _LAB_FEATURE_WRITER_H_ +#define _LAB_FEATURE_WRITER_H_ + +#include +#include +#include + +#include + +#include "transform/FileFeatureWriter.h" + +using std::string; +using std::map; + +class QTextStream; +class QFile; + +class LabFeatureWriter : public FileFeatureWriter +{ +public: + LabFeatureWriter(); + virtual ~LabFeatureWriter(); + + virtual string getDescription() const; + + virtual ParameterList getSupportedParameters() const; + virtual void setParameters(map ¶ms); + + virtual void write(QString trackid, + const Transform &transform, + const Vamp::Plugin::OutputDescriptor &output, + const Vamp::Plugin::FeatureList &features, + std::string summaryType = ""); + + virtual QString getWriterTag() const { return "lab"; } + +private: + bool m_forceEnd; +}; + +#endif diff -r 04945e74d314 -r 6ff4da31db8b tests/test-lab-destinations/test-lab-destinations.sh --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-lab-destinations/test-lab-destinations.sh Tue Oct 14 17:30:44 2014 +0100 @@ -0,0 +1,115 @@ +#!/bin/bash + +. ../include.sh + +infile1=$audiopath/3clicks8.wav +infile2=$audiopath/6clicks8.wav + +outfile1=$audiopath/3clicks8_vamp_vamp-example-plugins_percussiononsets_onsets.lab +outfile2=$audiopath/6clicks8_vamp_vamp-example-plugins_percussiononsets_onsets.lab + +infile1dot=$audiopath/3.clicks.8.wav +outfile1dot=$audiopath/3.clicks.8_vamp_vamp-example-plugins_percussiononsets_onsets.lab + +outfile3=$audiopath/3clicks8_vamp_vamp-example-plugins_percussiononsets_onsets.lab +outfile4=$audiopath/3clicks8_vamp_vamp-example-plugins_percussiononsets_detectionfunction.lab + +tmplab=$mypath/tmp_1_$$.lab + +trap "rm -f $tmplab $outfile1 $outfile2 $outfile3 $outfile4 $infile1dot $outfile1dot" 0 + +transformdir=$mypath/transforms + +check_lab() { + test -f $1 || \ + fail "Fails to write output to expected location $1 for $2" + # every line must contain the same number of tabs + formats=`awk -F'\t' '{ print NF; }' $1 | sort | uniq | wc | awk '{ print $1 }'` + if [ "$formats" != "1" ]; then + fail "Output is not consistently formatted tab-separated file for $2" + fi + rm -f $1 +} + + +ctx="onsets transform, one audio file, default LAB writer destination" + +rm -f $outfile1 + +$r -t $transformdir/onsets.n3 -w lab $infile1 2>/dev/null || \ + fail "Fails to run with $ctx" + +check_lab $outfile1 "$ctx" + + +ctx="onsets transform, one audio file with dots in filename, default LAB writer destination" + +rm -f $outfile1 + +cp $infile1 $infile1dot + +$r -t $transformdir/onsets.n3 -w lab $infile1dot 2>/dev/null || \ + fail "Fails to run with $ctx" + +check_lab $outfile1dot "$ctx" + +rm -f $infile1dot $outfile1dot + + +ctx="onsets and df transforms, one audio file, default LAB writer destination" + +rm -f $outfile1 + +$r -t $transformdir/onsets.n3 -t $transformdir/detectionfunction.n3 -w lab $infile1 2>/dev/null || \ + fail "Fails to run with $ctx" + +check_lab $outfile1 "$ctx" + + +ctx="onsets transform, two audio files, default LAB writer destination" + +rm -f $outfile1 +rm -f $outfile2 + +$r -t $transformdir/onsets.n3 -w lab $infile1 $infile2 2>/dev/null || \ + fail "Fails to run with $ctx" + +check_lab $outfile1 "$ctx" +check_lab $outfile2 "$ctx" + + +ctx="onsets transform, two audio files, one-file LAB writer" + +# Writer should refuse to write results from more than one audio file +# together (as the audio file is not identified) + +$r -t $transformdir/onsets.n3 -w lab --lab-one-file $tmplab $infile1 $infile2 2>/dev/null && \ + fail "Fails by completing successfully with $ctx" + + +ctx="onsets transform, two audio files, stdout LAB writer" + +$r -t $transformdir/onsets.n3 -w lab --lab-stdout $infile1 $infile2 2>/dev/null >$tmplab || \ + fail "Fails to run with $ctx" + +check_lab $tmplab "$ctx" + + +ctx="existing output file and no --lab-force" + +touch $outfile1 + +$r -t $transformdir/onsets.n3 -w lab $infile1 2>/dev/null && \ + fail "Fails by completing successfully when output file already exists (should refuse and bail out)" + + +ctx="existing output file and --lab-force" + +touch $outfile1 + +$r -t $transformdir/onsets.n3 -w lab --lab-force $infile1 2>/dev/null || \ + fail "Fails to run with $ctx" + +check_lab $outfile1 "$ctx" + +exit 0 diff -r 04945e74d314 -r 6ff4da31db8b tests/test-lab-destinations/transforms/detectionfunction.n3 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-lab-destinations/transforms/detectionfunction.n3 Tue Oct 14 17:30:44 2014 +0100 @@ -0,0 +1,11 @@ +@prefix rdf: . +@prefix vamp: . +@prefix examples: . +@prefix : <#>. + +:transform0 a vamp:Transform; + vamp:plugin examples:percussiononsets ; + vamp:output examples:percussiononsets_output_detectionfunction . + + + diff -r 04945e74d314 -r 6ff4da31db8b tests/test-lab-destinations/transforms/onsets.n3 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-lab-destinations/transforms/onsets.n3 Tue Oct 14 17:30:44 2014 +0100 @@ -0,0 +1,10 @@ +@prefix rdf: . +@prefix vamp: . +@prefix examples: . +@prefix : <#>. + +:transform0 a vamp:Transform; + vamp:plugin examples:percussiononsets. + + + diff -r 04945e74d314 -r 6ff4da31db8b tests/test.sh --- a/tests/test.sh Tue Oct 14 11:13:31 2014 +0100 +++ b/tests/test.sh Tue Oct 14 17:30:44 2014 +0100 @@ -12,6 +12,7 @@ rdf-writer \ rdf-destinations \ csv-destinations \ + lab-destinations \ midi-destinations \ summaries \ multiple-audio \