Chris@498
|
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
|
Chris@498
|
2
|
Chris@498
|
3 /*
|
Chris@498
|
4 Sonic Visualiser
|
Chris@498
|
5 An audio file viewer and annotation editor.
|
Chris@498
|
6
|
Chris@498
|
7 Sonic Annotator
|
Chris@498
|
8 A utility for batch feature extraction from audio files.
|
Chris@498
|
9
|
Chris@498
|
10 Mark Levy, Chris Sutton and Chris Cannam, Queen Mary, University of London.
|
Chris@498
|
11 Copyright 2007-2008 QMUL.
|
Chris@498
|
12
|
Chris@498
|
13 This program is free software; you can redistribute it and/or
|
Chris@498
|
14 modify it under the terms of the GNU General Public License as
|
Chris@498
|
15 published by the Free Software Foundation; either version 2 of the
|
Chris@498
|
16 License, or (at your option) any later version. See the file
|
Chris@498
|
17 COPYING included with this distribution for more information.
|
Chris@498
|
18 */
|
Chris@498
|
19
|
Chris@498
|
20 #include "CSVFeatureWriter.h"
|
Chris@498
|
21
|
Chris@498
|
22 #include <iostream>
|
Chris@498
|
23
|
Chris@498
|
24 #include <QRegExp>
|
Chris@498
|
25 #include <QTextStream>
|
Chris@498
|
26
|
Chris@498
|
27 using namespace std;
|
Chris@498
|
28 using namespace Vamp;
|
Chris@498
|
29
|
Chris@498
|
30 CSVFeatureWriter::CSVFeatureWriter() :
|
Chris@498
|
31 FileFeatureWriter(SupportOneFilePerTrackTransform |
|
Chris@997
|
32 SupportOneFileTotal |
|
Chris@997
|
33 SupportStdOut,
|
Chris@498
|
34 "csv"),
|
Chris@669
|
35 m_separator(","),
|
Chris@1000
|
36 m_sampleTiming(false),
|
Chris@1001
|
37 m_endTimes(false),
|
Chris@1001
|
38 m_forceEnd(false)
|
Chris@498
|
39 {
|
Chris@498
|
40 }
|
Chris@498
|
41
|
Chris@498
|
42 CSVFeatureWriter::~CSVFeatureWriter()
|
Chris@498
|
43 {
|
Chris@498
|
44 }
|
Chris@498
|
45
|
Chris@998
|
46 string
|
Chris@998
|
47 CSVFeatureWriter::getDescription() const
|
Chris@998
|
48 {
|
Chris@998
|
49 return "Write features in comma-separated (CSV) format. If transforms are being written to a single file or to stdout, the first column in the output will contain the input audio filename, or an empty string if the feature hails from the same audio file as its predecessor. If transforms are being written to multiple files, the audio filename column will be omitted. Subsequent columns will contain the feature timestamp, then any or all of duration, values, and label.";
|
Chris@998
|
50 }
|
Chris@998
|
51
|
Chris@498
|
52 CSVFeatureWriter::ParameterList
|
Chris@498
|
53 CSVFeatureWriter::getSupportedParameters() const
|
Chris@498
|
54 {
|
Chris@498
|
55 ParameterList pl = FileFeatureWriter::getSupportedParameters();
|
Chris@498
|
56 Parameter p;
|
Chris@498
|
57
|
Chris@498
|
58 p.name = "separator";
|
Chris@498
|
59 p.description = "Column separator for output. Default is \",\" (comma).";
|
Chris@498
|
60 p.hasArg = true;
|
Chris@498
|
61 pl.push_back(p);
|
Chris@669
|
62
|
Chris@669
|
63 p.name = "sample-timing";
|
Chris@669
|
64 p.description = "Show timings as sample frame counts instead of in seconds.";
|
Chris@669
|
65 p.hasArg = false;
|
Chris@669
|
66 pl.push_back(p);
|
Chris@1000
|
67
|
Chris@1000
|
68 p.name = "end-times";
|
Chris@1000
|
69 p.description = "Show start and end time instead of start and duration, for features with duration.";
|
Chris@1000
|
70 p.hasArg = false;
|
Chris@1000
|
71 pl.push_back(p);
|
Chris@498
|
72
|
Chris@1001
|
73 p.name = "fill-ends";
|
Chris@1001
|
74 p.description = "Include durations (or end times) even for features without duration, by using the gap to the next feature instead.";
|
Chris@1001
|
75 p.hasArg = false;
|
Chris@1001
|
76 pl.push_back(p);
|
Chris@1001
|
77
|
Chris@498
|
78 return pl;
|
Chris@498
|
79 }
|
Chris@498
|
80
|
Chris@498
|
81 void
|
Chris@498
|
82 CSVFeatureWriter::setParameters(map<string, string> ¶ms)
|
Chris@498
|
83 {
|
Chris@498
|
84 FileFeatureWriter::setParameters(params);
|
Chris@498
|
85
|
Chris@690
|
86 SVDEBUG << "CSVFeatureWriter::setParameters" << endl;
|
Chris@498
|
87 for (map<string, string>::iterator i = params.begin();
|
Chris@498
|
88 i != params.end(); ++i) {
|
Chris@498
|
89 cerr << i->first << " -> " << i->second << endl;
|
Chris@498
|
90 if (i->first == "separator") {
|
Chris@498
|
91 m_separator = i->second.c_str();
|
Chris@669
|
92 } else if (i->first == "sample-timing") {
|
Chris@669
|
93 m_sampleTiming = true;
|
Chris@1000
|
94 } else if (i->first == "end-times") {
|
Chris@1000
|
95 m_endTimes = true;
|
Chris@1001
|
96 } else if (i->first == "fill-ends") {
|
Chris@1001
|
97 m_forceEnd = true;
|
Chris@498
|
98 }
|
Chris@498
|
99 }
|
Chris@498
|
100 }
|
Chris@498
|
101
|
Chris@498
|
102 void
|
Chris@498
|
103 CSVFeatureWriter::write(QString trackId,
|
Chris@498
|
104 const Transform &transform,
|
Chris@930
|
105 const Plugin::OutputDescriptor& ,
|
Chris@498
|
106 const Plugin::FeatureList& features,
|
Chris@498
|
107 std::string summaryType)
|
Chris@498
|
108 {
|
Chris@1001
|
109 TransformId transformId = transform.getIdentifier();
|
Chris@1001
|
110
|
Chris@498
|
111 // Select appropriate output file for our track/transform
|
Chris@498
|
112 // combination
|
Chris@498
|
113
|
Chris@1001
|
114 QTextStream *sptr = getOutputStream(trackId, transformId);
|
Chris@604
|
115 if (!sptr) {
|
Chris@1001
|
116 throw FailedToOpenOutputStream(trackId, transformId);
|
Chris@604
|
117 }
|
Chris@498
|
118
|
Chris@498
|
119 QTextStream &stream = *sptr;
|
Chris@498
|
120
|
Chris@1001
|
121 int n = features.size();
|
Chris@498
|
122
|
Chris@1001
|
123 if (n == 0) return;
|
Chris@1001
|
124
|
Chris@1001
|
125 TrackTransformPair tt(trackId, transformId);
|
Chris@1001
|
126
|
Chris@1001
|
127 if (m_rates.find(transformId) == m_rates.end()) {
|
Chris@1001
|
128 m_rates[transformId] = transform.getSampleRate();
|
Chris@1001
|
129 }
|
Chris@1001
|
130
|
Chris@1001
|
131 if (m_pending.find(tt) != m_pending.end()) {
|
Chris@1001
|
132 writeFeature(tt,
|
Chris@1001
|
133 stream,
|
Chris@1001
|
134 m_pending[tt],
|
Chris@1001
|
135 &features[0],
|
Chris@1001
|
136 m_pendingSummaryTypes[tt]);
|
Chris@1001
|
137 m_pending.erase(tt);
|
Chris@1001
|
138 m_pendingSummaryTypes.erase(tt);
|
Chris@1001
|
139 }
|
Chris@1001
|
140
|
Chris@1001
|
141 if (m_forceEnd) {
|
Chris@1001
|
142 // can't write final feature until we know its end time
|
Chris@1001
|
143 --n;
|
Chris@1001
|
144 m_pending[tt] = features[n];
|
Chris@1001
|
145 m_pendingSummaryTypes[tt] = summaryType;
|
Chris@1001
|
146 }
|
Chris@1001
|
147
|
Chris@1001
|
148 for (int i = 0; i < n; ++i) {
|
Chris@1001
|
149 writeFeature(tt,
|
Chris@1001
|
150 stream,
|
Chris@1001
|
151 features[i],
|
Chris@1001
|
152 m_forceEnd ? &features[i+1] : 0,
|
Chris@1001
|
153 summaryType);
|
Chris@1001
|
154 }
|
Chris@1001
|
155 }
|
Chris@1001
|
156
|
Chris@1001
|
157 void
|
Chris@1001
|
158 CSVFeatureWriter::finish()
|
Chris@1001
|
159 {
|
Chris@1001
|
160 for (PendingFeatures::const_iterator i = m_pending.begin();
|
Chris@1001
|
161 i != m_pending.end(); ++i) {
|
Chris@1001
|
162 TrackTransformPair tt = i->first;
|
Chris@1001
|
163 Plugin::Feature f = i->second;
|
Chris@1001
|
164 QTextStream *sptr = getOutputStream(tt.first, tt.second);
|
Chris@1001
|
165 if (!sptr) {
|
Chris@1001
|
166 throw FailedToOpenOutputStream(tt.first, tt.second);
|
Chris@1001
|
167 }
|
Chris@1001
|
168 QTextStream &stream = *sptr;
|
Chris@1001
|
169 // final feature has its own time as end time (we can't
|
Chris@1001
|
170 // reliably determine the end of audio file, and because of
|
Chris@1001
|
171 // the nature of block processing, the feature could even
|
Chris@1001
|
172 // start beyond that anyway)
|
Chris@1001
|
173 writeFeature(tt, stream, f, &f, m_pendingSummaryTypes[tt]);
|
Chris@1001
|
174 }
|
Chris@1001
|
175
|
Chris@1001
|
176 m_pending.clear();
|
Chris@1001
|
177 }
|
Chris@1001
|
178
|
Chris@1001
|
179 void
|
Chris@1001
|
180 CSVFeatureWriter::writeFeature(TrackTransformPair tt,
|
Chris@1001
|
181 QTextStream &stream,
|
Chris@1001
|
182 const Plugin::Feature &f,
|
Chris@1001
|
183 const Plugin::Feature *optionalNextFeature,
|
Chris@1001
|
184 std::string summaryType)
|
Chris@1001
|
185 {
|
Chris@1001
|
186 QString trackId = tt.first;
|
Chris@1001
|
187 TransformId transformId = tt.second;
|
Chris@1001
|
188
|
Chris@1001
|
189 if (m_stdout || m_singleFileName != "") {
|
Chris@1001
|
190 if (trackId != m_prevPrintedTrackId) {
|
Chris@1001
|
191 stream << "\"" << trackId << "\"" << m_separator;
|
Chris@1001
|
192 m_prevPrintedTrackId = trackId;
|
Chris@1001
|
193 } else {
|
Chris@1001
|
194 stream << m_separator;
|
Chris@1001
|
195 }
|
Chris@1001
|
196 }
|
Chris@1001
|
197
|
Chris@1001
|
198 Vamp::RealTime duration;
|
Chris@1001
|
199 bool haveDuration = true;
|
Chris@1001
|
200
|
Chris@1001
|
201 if (f.hasDuration) {
|
Chris@1001
|
202 duration = f.duration;
|
Chris@1001
|
203 } else if (optionalNextFeature) {
|
Chris@1001
|
204 duration = optionalNextFeature->timestamp - f.timestamp;
|
Chris@1001
|
205 } else {
|
Chris@1001
|
206 haveDuration = false;
|
Chris@1001
|
207 }
|
Chris@1001
|
208
|
Chris@1001
|
209 if (m_sampleTiming) {
|
Chris@1001
|
210
|
Chris@1001
|
211 float rate = m_rates[transformId];
|
Chris@1001
|
212
|
Chris@1001
|
213 stream << Vamp::RealTime::realTime2Frame(f.timestamp, rate);
|
Chris@1001
|
214
|
Chris@1001
|
215 if (haveDuration) {
|
Chris@1001
|
216 stream << m_separator;
|
Chris@1001
|
217 if (m_endTimes) {
|
Chris@1001
|
218 stream << Vamp::RealTime::realTime2Frame
|
Chris@1001
|
219 (f.timestamp + duration, rate);
|
Chris@514
|
220 } else {
|
Chris@1001
|
221 stream << Vamp::RealTime::realTime2Frame(duration, rate);
|
Chris@514
|
222 }
|
Chris@514
|
223 }
|
Chris@514
|
224
|
Chris@1001
|
225 } else {
|
Chris@498
|
226
|
Chris@1001
|
227 QString timestamp = f.timestamp.toString().c_str();
|
Chris@1001
|
228 timestamp.replace(QRegExp("^ +"), "");
|
Chris@1001
|
229 stream << timestamp;
|
Chris@669
|
230
|
Chris@1001
|
231 if (haveDuration) {
|
Chris@1001
|
232 if (m_endTimes) {
|
Chris@1001
|
233 QString endtime =
|
Chris@1001
|
234 (f.timestamp + duration).toString().c_str();
|
Chris@1001
|
235 endtime.replace(QRegExp("^ +"), "");
|
Chris@1001
|
236 stream << m_separator << endtime;
|
Chris@1001
|
237 } else {
|
Chris@1001
|
238 QString d = duration.toString().c_str();
|
Chris@1001
|
239 d.replace(QRegExp("^ +"), "");
|
Chris@1001
|
240 stream << m_separator << d;
|
Chris@669
|
241 }
|
Chris@1001
|
242 }
|
Chris@1001
|
243 }
|
Chris@669
|
244
|
Chris@1001
|
245 if (summaryType != "") {
|
Chris@1001
|
246 stream << m_separator << summaryType.c_str();
|
Chris@498
|
247 }
|
Chris@1001
|
248
|
Chris@1001
|
249 for (unsigned int j = 0; j < f.values.size(); ++j) {
|
Chris@1001
|
250 stream << m_separator << f.values[j];
|
Chris@1001
|
251 }
|
Chris@1001
|
252
|
Chris@1001
|
253 if (f.label != "") {
|
Chris@1001
|
254 stream << m_separator << "\"" << f.label.c_str() << "\"";
|
Chris@1001
|
255 }
|
Chris@1001
|
256
|
Chris@1001
|
257 stream << "\n";
|
Chris@498
|
258 }
|
Chris@498
|
259
|
Chris@498
|
260
|