Mercurial > hg > sonic-annotator
comparison runner/JAMSFeatureWriter.cpp @ 171:c1834a31029c
Merge from branch "jams"
author | Chris Cannam |
---|---|
date | Wed, 15 Oct 2014 16:09:47 +0100 |
parents | 3536342ac088 |
children | 089f1a13963d |
comparison
equal
deleted
inserted
replaced
163:f4f770b4356b | 171:c1834a31029c |
---|---|
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ | |
2 | |
3 /* | |
4 Sonic Annotator | |
5 A utility for batch feature extraction from audio files. | |
6 Mark Levy, Chris Sutton and Chris Cannam, Queen Mary, University of London. | |
7 Copyright 2007-2014 QMUL. | |
8 | |
9 This program is free software; you can redistribute it and/or | |
10 modify it under the terms of the GNU General Public License as | |
11 published by the Free Software Foundation; either version 2 of the | |
12 License, or (at your option) any later version. See the file | |
13 COPYING included with this distribution for more information. | |
14 */ | |
15 | |
16 #include "JAMSFeatureWriter.h" | |
17 | |
18 using namespace std; | |
19 using Vamp::Plugin; | |
20 using Vamp::PluginBase; | |
21 | |
22 #include "base/Exceptions.h" | |
23 #include "rdf/PluginRDFIndexer.h" | |
24 | |
25 #include <QFileInfo> | |
26 | |
27 #include "version.h" | |
28 | |
29 JAMSFeatureWriter::JAMSFeatureWriter() : | |
30 FileFeatureWriter(SupportOneFilePerTrackTransform | | |
31 SupportOneFilePerTrack | | |
32 SupportOneFileTotal | | |
33 SupportStdOut, | |
34 "json"), | |
35 m_network(false), | |
36 m_networkRetrieved(false), | |
37 m_n(1), | |
38 m_m(1) | |
39 { | |
40 } | |
41 | |
42 JAMSFeatureWriter::~JAMSFeatureWriter() | |
43 { | |
44 } | |
45 | |
46 string | |
47 JAMSFeatureWriter::getDescription() const | |
48 { | |
49 return "Write features to JSON files in JAMS (JSON Annotated Music Specification) format. WARNING: This is a provisional implementation! The output format may change in future releases to comply more effectively with the specification. Please report any problems you find with the current implementation."; | |
50 } | |
51 | |
52 JAMSFeatureWriter::ParameterList | |
53 JAMSFeatureWriter::getSupportedParameters() const | |
54 { | |
55 ParameterList pl = FileFeatureWriter::getSupportedParameters(); | |
56 Parameter p; | |
57 | |
58 p.name = "network"; | |
59 p.description = "Attempt to retrieve RDF descriptions of plugins from network, if not available locally"; | |
60 p.hasArg = false; | |
61 pl.push_back(p); | |
62 | |
63 return pl; | |
64 } | |
65 | |
66 void | |
67 JAMSFeatureWriter::setParameters(map<string, string> ¶ms) | |
68 { | |
69 FileFeatureWriter::setParameters(params); | |
70 | |
71 for (map<string, string>::iterator i = params.begin(); | |
72 i != params.end(); ++i) { | |
73 if (i->first == "network") { | |
74 m_network = true; | |
75 } | |
76 } | |
77 } | |
78 | |
79 void | |
80 JAMSFeatureWriter::setTrackMetadata(QString trackId, TrackMetadata metadata) | |
81 { | |
82 m_trackMetadata[trackId] = metadata; | |
83 } | |
84 | |
85 static double | |
86 realTime2Sec(const Vamp::RealTime &r) | |
87 { | |
88 return r / Vamp::RealTime(1, 0); | |
89 } | |
90 | |
91 void | |
92 JAMSFeatureWriter::write(QString trackId, | |
93 const Transform &transform, | |
94 const Plugin::OutputDescriptor& , | |
95 const Plugin::FeatureList& features, | |
96 std::string /* summaryType */) | |
97 { | |
98 QString transformId = transform.getIdentifier(); | |
99 | |
100 QTextStream *sptr = getOutputStream(trackId, transformId); | |
101 if (!sptr) { | |
102 throw FailedToOpenOutputStream(trackId, transformId); | |
103 } | |
104 | |
105 DataId did(trackId, transform); | |
106 | |
107 if (m_data.find(did) == m_data.end()) { | |
108 identifyTask(transform); | |
109 m_streamTracks[sptr].insert(trackId); | |
110 m_streamTasks[sptr].insert(m_tasks[transformId]); | |
111 m_streamData[sptr].insert(did); | |
112 } | |
113 | |
114 QString d = m_data[did]; | |
115 | |
116 for (int i = 0; i < int(features.size()); ++i) { | |
117 | |
118 if (d != "") { | |
119 d += ",\n"; | |
120 } | |
121 | |
122 d += " { "; | |
123 | |
124 Plugin::Feature f(features[i]); | |
125 | |
126 if (f.hasDuration) { | |
127 d += QString | |
128 ("\"start\": { \"value\": %1 }, " | |
129 "\"end\": { \"value\": %2 }") | |
130 .arg(realTime2Sec(f.timestamp)) | |
131 .arg(realTime2Sec | |
132 (f.timestamp + | |
133 (f.hasDuration ? f.duration : Vamp::RealTime::zeroTime))); | |
134 } else { | |
135 d += QString("\"time\": { \"value\": %1 }") | |
136 .arg(realTime2Sec(f.timestamp)); | |
137 } | |
138 | |
139 if (f.label != "") { | |
140 d += QString(", \"label\": { \"value\": \"%2\" }") | |
141 .arg(f.label.c_str()); | |
142 } | |
143 | |
144 if (f.values.size() > 0) { | |
145 d += QString(", \"value\": [ "); | |
146 for (int j = 0; j < int(f.values.size()); ++j) { | |
147 if (isnan(f.values[j])) { | |
148 d += "\"NaN\" "; | |
149 } else if (isinf(f.values[j])) { | |
150 d += "\"Inf\" "; | |
151 } else { | |
152 d += QString("%1 ").arg(f.values[j]); | |
153 } | |
154 } | |
155 d += "]"; | |
156 } | |
157 | |
158 d += " }"; | |
159 } | |
160 | |
161 m_data[did] = d; | |
162 } | |
163 | |
164 void | |
165 JAMSFeatureWriter::setNofM(int n, int m) | |
166 { | |
167 if (m_singleFileName != "" || m_stdout) { | |
168 m_n = n; | |
169 m_m = m; | |
170 } else { | |
171 m_n = 1; | |
172 m_m = 1; | |
173 } | |
174 } | |
175 | |
176 void | |
177 JAMSFeatureWriter::finish() | |
178 { | |
179 for (FileStreamMap::const_iterator stri = m_streams.begin(); | |
180 stri != m_streams.end(); ++stri) { | |
181 | |
182 QTextStream *sptr = stri->second; | |
183 QTextStream &stream = *sptr; | |
184 | |
185 bool firstInStream = true; | |
186 | |
187 for (TrackIds::const_iterator tri = m_streamTracks[sptr].begin(); | |
188 tri != m_streamTracks[sptr].end(); ++tri) { | |
189 | |
190 TrackId trackId = *tri; | |
191 | |
192 if (firstInStream) { | |
193 if (m_streamTracks[sptr].size() > 1 || (m_m > 1 && m_n == 1)) { | |
194 stream << "[\n"; | |
195 } | |
196 } | |
197 | |
198 if (!firstInStream || (m_m > 1 && m_n > 1)) { | |
199 stream << ",\n"; | |
200 } | |
201 | |
202 stream << "{\n" | |
203 << QString("\"file_metadata\": {\n" | |
204 " \"filename\": \"%1\"") | |
205 .arg(QFileInfo(trackId).fileName()); | |
206 | |
207 if (m_trackMetadata.find(trackId) != m_trackMetadata.end()) { | |
208 if (m_trackMetadata[trackId].maker != "") { | |
209 stream << QString(",\n \"artist\": \"%1\"") | |
210 .arg(m_trackMetadata[trackId].maker); | |
211 } | |
212 if (m_trackMetadata[trackId].title != "") { | |
213 stream << QString(",\n \"title\": \"%1\"") | |
214 .arg(m_trackMetadata[trackId].title); | |
215 } | |
216 } | |
217 | |
218 stream << "\n},\n"; | |
219 | |
220 bool firstInTrack = true; | |
221 | |
222 for (Tasks::const_iterator ti = m_streamTasks[sptr].begin(); | |
223 ti != m_streamTasks[sptr].end(); ++ti) { | |
224 | |
225 Task task = *ti; | |
226 | |
227 if (!firstInTrack) { | |
228 stream << ",\n"; | |
229 } | |
230 | |
231 stream << "\"" << getTaskKey(task) << "\": [\n"; | |
232 | |
233 bool firstInTask = true; | |
234 | |
235 for (DataIds::const_iterator di = m_streamData[sptr].begin(); | |
236 di != m_streamData[sptr].end(); ++di) { | |
237 | |
238 DataId did = *di; | |
239 | |
240 QString trackId = did.first; | |
241 Transform transform = did.second; | |
242 | |
243 if (m_tasks[transform.getIdentifier()] != task) continue; | |
244 | |
245 QString data = m_data[did]; | |
246 | |
247 if (!firstInTask) { | |
248 stream << ",\n"; | |
249 } | |
250 | |
251 stream << QString | |
252 ("{ \n" | |
253 " \"annotation_metadata\": {\n" | |
254 " \"annotation_tools\": \"Sonic Annotator v%2\",\n" | |
255 " \"data_source\": \"Automatic feature extraction\",\n" | |
256 " \"annotator\": {\n" | |
257 "%3" | |
258 " }\n" | |
259 " },\n" | |
260 " \"data\": [\n") | |
261 .arg(RUNNER_VERSION) | |
262 .arg(writeTransformToObjectContents(transform)); | |
263 | |
264 stream << data; | |
265 | |
266 stream << "\n ]\n}"; | |
267 firstInTask = false; | |
268 } | |
269 | |
270 stream << "\n]"; | |
271 firstInTrack = false; | |
272 } | |
273 | |
274 stream << "\n}"; | |
275 firstInStream = false; | |
276 } | |
277 | |
278 if (!firstInStream) { | |
279 if (m_streamTracks[sptr].size() > 1 || (m_m > 1 && m_n == m_m)) { | |
280 stream << "\n]"; | |
281 } | |
282 stream << "\n"; | |
283 } | |
284 } | |
285 | |
286 m_streamTracks.clear(); | |
287 m_streamTasks.clear(); | |
288 m_streamData.clear(); | |
289 m_data.clear(); | |
290 | |
291 FileFeatureWriter::finish(); | |
292 } | |
293 | |
294 void | |
295 JAMSFeatureWriter::loadRDFDescription(const Transform &transform) | |
296 { | |
297 QString pluginId = transform.getPluginIdentifier(); | |
298 if (m_rdfDescriptions.find(pluginId) != m_rdfDescriptions.end()) return; | |
299 | |
300 if (m_network && !m_networkRetrieved) { | |
301 PluginRDFIndexer::getInstance()->indexConfiguredURLs(); | |
302 m_networkRetrieved = true; | |
303 } | |
304 | |
305 m_rdfDescriptions[pluginId] = PluginRDFDescription(pluginId); | |
306 | |
307 if (m_rdfDescriptions[pluginId].haveDescription()) { | |
308 cerr << "NOTE: Have RDF description for plugin ID \"" | |
309 << pluginId << "\"" << endl; | |
310 } else { | |
311 cerr << "NOTE: No RDF description for plugin ID \"" | |
312 << pluginId << "\"" << endl; | |
313 if (!m_network) { | |
314 cerr << " Consider using the --json-network option to retrieve plugin descriptions" << endl; | |
315 cerr << " from the network where possible." << endl; | |
316 } | |
317 } | |
318 } | |
319 | |
320 void | |
321 JAMSFeatureWriter::identifyTask(const Transform &transform) | |
322 { | |
323 QString transformId = transform.getIdentifier(); | |
324 if (m_tasks.find(transformId) != m_tasks.end()) return; | |
325 | |
326 loadRDFDescription(transform); | |
327 | |
328 Task task = UnknownTask; | |
329 | |
330 QString pluginId = transform.getPluginIdentifier(); | |
331 QString outputId = transform.getOutput(); | |
332 | |
333 const PluginRDFDescription &desc = m_rdfDescriptions[pluginId]; | |
334 | |
335 if (desc.haveDescription()) { | |
336 | |
337 PluginRDFDescription::OutputDisposition disp = | |
338 desc.getOutputDisposition(outputId); | |
339 | |
340 QString af = "http://purl.org/ontology/af/"; | |
341 | |
342 if (disp == PluginRDFDescription::OutputSparse) { | |
343 | |
344 QString eventUri = desc.getOutputEventTypeURI(outputId); | |
345 | |
346 //!!! todo: allow user to prod writer for task type | |
347 | |
348 if (eventUri == af + "Note") { | |
349 task = NoteTask; | |
350 } else if (eventUri == af + "Beat") { | |
351 task = BeatTask; | |
352 } else if (eventUri == af + "ChordSegment") { | |
353 task = ChordTask; | |
354 } else if (eventUri == af + "KeyChange") { | |
355 task = KeyTask; | |
356 } else if (eventUri == af + "KeySegment") { | |
357 task = KeyTask; | |
358 } else if (eventUri == af + "Onset") { | |
359 task = OnsetTask; | |
360 } else if (eventUri == af + "NonTonalOnset") { | |
361 task = OnsetTask; | |
362 } else if (eventUri == af + "Segment") { | |
363 task = SegmentTask; | |
364 } else if (eventUri == af + "SpeechSegment") { | |
365 task = SegmentTask; | |
366 } else if (eventUri == af + "StructuralSegment") { | |
367 task = SegmentTask; | |
368 } else { | |
369 cerr << "WARNING: Unsupported event type URI <" | |
370 << eventUri << ">, proceeding with UnknownTask type" | |
371 << endl; | |
372 } | |
373 | |
374 } else { | |
375 | |
376 cerr << "WARNING: Cannot currently write dense or track-level outputs to JSON format (only sparse ones). Will proceed using UnknownTask type, but this probably isn't going to work" << endl; | |
377 } | |
378 } | |
379 | |
380 m_tasks[transformId] = task; | |
381 } | |
382 | |
383 QString | |
384 JAMSFeatureWriter::getTaskKey(Task task) | |
385 { | |
386 switch (task) { | |
387 case UnknownTask: return "unknown"; | |
388 case BeatTask: return "beat"; | |
389 case OnsetTask: return "onset"; | |
390 case ChordTask: return "chord"; | |
391 case SegmentTask: return "segment"; | |
392 case KeyTask: return "key"; | |
393 case NoteTask: return "note"; | |
394 case MelodyTask: return "melody"; | |
395 case PitchTask: return "pitch"; | |
396 } | |
397 return "unknown"; | |
398 } | |
399 | |
400 QString | |
401 JAMSFeatureWriter::writeTransformToObjectContents(const Transform &t) | |
402 { | |
403 QString json; | |
404 QString stpl(" \"%1\": \"%2\",\n"); | |
405 QString ntpl(" \"%1\": %2,\n"); | |
406 | |
407 json += stpl.arg("plugin_id").arg(t.getPluginIdentifier()); | |
408 json += stpl.arg("output_id").arg(t.getOutput()); | |
409 | |
410 if (t.getSummaryType() != Transform::NoSummary) { | |
411 json += stpl.arg("summary_type") | |
412 .arg(Transform::summaryTypeToString(t.getSummaryType())); | |
413 } | |
414 | |
415 if (t.getPluginVersion() != QString()) { | |
416 json += stpl.arg("plugin_version").arg(t.getPluginVersion()); | |
417 } | |
418 | |
419 if (t.getProgram() != QString()) { | |
420 json += stpl.arg("program").arg(t.getProgram()); | |
421 } | |
422 | |
423 if (t.getStepSize() != 0) { | |
424 json += ntpl.arg("step_size").arg(t.getStepSize()); | |
425 } | |
426 | |
427 if (t.getBlockSize() != 0) { | |
428 json += ntpl.arg("block_size").arg(t.getBlockSize()); | |
429 } | |
430 | |
431 if (t.getWindowType() != HanningWindow) { | |
432 json += stpl.arg("window_type") | |
433 .arg(Window<float>::getNameForType(t.getWindowType()).c_str()); | |
434 } | |
435 | |
436 if (t.getStartTime() != RealTime::zeroTime) { | |
437 json += ntpl.arg("start").arg(t.getStartTime().toDouble()); | |
438 } | |
439 | |
440 if (t.getDuration() != RealTime::zeroTime) { | |
441 json += ntpl.arg("duration").arg(t.getDuration().toDouble()); | |
442 } | |
443 | |
444 if (t.getSampleRate() != 0) { | |
445 json += ntpl.arg("sample_rate").arg(t.getSampleRate()); | |
446 } | |
447 | |
448 if (!t.getParameters().empty()) { | |
449 json += QString(" \"parameters\": {\n"); | |
450 Transform::ParameterMap parameters = t.getParameters(); | |
451 for (Transform::ParameterMap::const_iterator i = parameters.begin(); | |
452 i != parameters.end(); ++i) { | |
453 if (i != parameters.begin()) { | |
454 json += ",\n"; | |
455 } | |
456 QString name = i->first; | |
457 float value = i->second; | |
458 json += QString(" \"%1\": %2").arg(name).arg(value); | |
459 } | |
460 json += QString("\n },\n"); | |
461 } | |
462 | |
463 // no trailing comma on final property: | |
464 json += QString(" \"transform_id\": \"%1\"\n").arg(t.getIdentifier()); | |
465 | |
466 return json; | |
467 } | |
468 |