Chris@137
|
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
|
Chris@137
|
2
|
Chris@137
|
3 /*
|
Chris@137
|
4 Sonic Annotator
|
Chris@137
|
5 A utility for batch feature extraction from audio files.
|
Chris@137
|
6 Mark Levy, Chris Sutton and Chris Cannam, Queen Mary, University of London.
|
Chris@137
|
7 Copyright 2007-2014 QMUL.
|
Chris@137
|
8
|
Chris@137
|
9 This program is free software; you can redistribute it and/or
|
Chris@137
|
10 modify it under the terms of the GNU General Public License as
|
Chris@137
|
11 published by the Free Software Foundation; either version 2 of the
|
Chris@137
|
12 License, or (at your option) any later version. See the file
|
Chris@137
|
13 COPYING included with this distribution for more information.
|
Chris@137
|
14 */
|
Chris@137
|
15
|
Chris@137
|
16 #include "MIDIFeatureWriter.h"
|
Chris@137
|
17
|
Chris@137
|
18 using namespace std;
|
Chris@137
|
19 using Vamp::Plugin;
|
Chris@137
|
20 using Vamp::PluginBase;
|
Chris@137
|
21
|
Chris@137
|
22 #include "base/Exceptions.h"
|
Chris@137
|
23 #include "data/fileio/MIDIFileWriter.h"
|
Chris@137
|
24
|
Chris@137
|
25 MIDIFeatureWriter::MIDIFeatureWriter() :
|
Chris@137
|
26 FileFeatureWriter(SupportOneFilePerTrackTransform |
|
Chris@137
|
27 SupportOneFilePerTrack |
|
Chris@137
|
28 SupportOneFileTotal,
|
Chris@137
|
29 "mid")
|
Chris@137
|
30 {
|
Chris@137
|
31 }
|
Chris@137
|
32
|
Chris@137
|
33 MIDIFeatureWriter::~MIDIFeatureWriter()
|
Chris@137
|
34 {
|
Chris@137
|
35 }
|
Chris@137
|
36
|
Chris@144
|
37 string
|
Chris@144
|
38 MIDIFeatureWriter::getDescription() const
|
Chris@144
|
39 {
|
Chris@144
|
40 return "Write features to MIDI files. All features are written as MIDI notes. If a feature has at least one value, its first value will be used as the note pitch, the second value (if present) for velocity. If a feature has units of Hz, then its pitch will be converted from frequency to an integer value in MIDI range, otherwise it will be written directly. Multiple (up to 16) transforms can be written to a single MIDI file, where they will be given separate MIDI channel numbers.";
|
Chris@144
|
41 }
|
Chris@144
|
42
|
Chris@137
|
43 MIDIFeatureWriter::ParameterList
|
Chris@137
|
44 MIDIFeatureWriter::getSupportedParameters() const
|
Chris@137
|
45 {
|
Chris@137
|
46 ParameterList pl = FileFeatureWriter::getSupportedParameters();
|
Chris@137
|
47 return pl;
|
Chris@137
|
48 }
|
Chris@137
|
49
|
Chris@137
|
50 void
|
Chris@137
|
51 MIDIFeatureWriter::setParameters(map<string, string> ¶ms)
|
Chris@137
|
52 {
|
Chris@137
|
53 FileFeatureWriter::setParameters(params);
|
Chris@137
|
54 }
|
Chris@137
|
55
|
Chris@137
|
56 void
|
Chris@137
|
57 MIDIFeatureWriter::setTrackMetadata(QString, TrackMetadata)
|
Chris@137
|
58 {
|
Chris@137
|
59 cerr << "MIDIFeatureWriter::setTrackMetadata: not supported (yet?)" << endl;
|
Chris@137
|
60 }
|
Chris@137
|
61
|
Chris@137
|
62 void
|
Chris@137
|
63 MIDIFeatureWriter::write(QString trackId,
|
Chris@137
|
64 const Transform &transform,
|
Chris@137
|
65 const Plugin::OutputDescriptor& output,
|
Chris@137
|
66 const Plugin::FeatureList& features,
|
Chris@141
|
67 std::string /* summaryType */)
|
Chris@137
|
68 {
|
Chris@140
|
69 QString transformId = transform.getIdentifier();
|
Chris@140
|
70
|
Chris@140
|
71 QString filename = getOutputFilename(trackId, transformId);
|
Chris@137
|
72 if (filename == "") {
|
Chris@140
|
73 throw FailedToOpenOutputStream(trackId, transformId);
|
Chris@137
|
74 }
|
Chris@137
|
75
|
Chris@141
|
76 int sampleRate = transform.getSampleRate();
|
Chris@141
|
77
|
Chris@140
|
78 if (m_rates.find(filename) == m_rates.end()) {
|
Chris@140
|
79 m_rates[filename] = sampleRate;
|
Chris@140
|
80 }
|
Chris@140
|
81
|
Chris@175
|
82 if (m_fileTransforms[filename].find(transform) ==
|
Chris@140
|
83 m_fileTransforms[filename].end()) {
|
Chris@140
|
84
|
Chris@140
|
85 // This transform is new to the file, give it a channel number
|
Chris@140
|
86
|
Chris@140
|
87 int channel = m_nextChannels[filename];
|
Chris@140
|
88 m_nextChannels[filename] = channel + 1;
|
Chris@140
|
89
|
Chris@175
|
90 m_fileTransforms[filename].insert(transform);
|
Chris@175
|
91 m_channels[transform] = channel;
|
Chris@140
|
92 }
|
Chris@140
|
93
|
Chris@140
|
94 NoteList notes = m_notes[filename];
|
Chris@140
|
95
|
Chris@141
|
96 bool freq = (output.unit == "Hz" ||
|
Chris@141
|
97 output.unit == "hz" ||
|
Chris@141
|
98 output.unit == "HZ");
|
Chris@141
|
99
|
Chris@141
|
100 for (int i = 0; i < (int)features.size(); ++i) {
|
Chris@141
|
101
|
Chris@141
|
102 const Plugin::Feature &feature(features[i]);
|
Chris@141
|
103
|
Chris@141
|
104 Vamp::RealTime timestamp = feature.timestamp;
|
Chris@141
|
105 int frame = Vamp::RealTime::realTime2Frame(timestamp, sampleRate);
|
Chris@141
|
106
|
Chris@141
|
107 int duration = 1;
|
Chris@141
|
108 if (feature.hasDuration) {
|
Chris@141
|
109 duration = Vamp::RealTime::realTime2Frame(feature.duration, sampleRate);
|
Chris@141
|
110 }
|
Chris@141
|
111
|
Chris@141
|
112 int pitch = 60;
|
Chris@141
|
113 if (feature.values.size() > 0) {
|
Chris@141
|
114 float pval = feature.values[0];
|
Chris@141
|
115 if (freq) {
|
Chris@141
|
116 pitch = Pitch::getPitchForFrequency(pval);
|
Chris@141
|
117 } else {
|
Chris@141
|
118 pitch = int(pval + 0.5);
|
Chris@141
|
119 }
|
Chris@141
|
120 }
|
Chris@141
|
121
|
Chris@141
|
122 int velocity = 100;
|
Chris@141
|
123 if (feature.values.size() > 1) {
|
Chris@141
|
124 float vval = feature.values[1];
|
Chris@141
|
125 if (vval < 128) {
|
Chris@141
|
126 velocity = int(vval + 0.5);
|
Chris@141
|
127 }
|
Chris@141
|
128 }
|
Chris@141
|
129
|
Chris@141
|
130 NoteData note(frame, duration, pitch, velocity);
|
Chris@141
|
131
|
Chris@175
|
132 note.channel = m_channels[transform];
|
Chris@141
|
133
|
Chris@141
|
134 notes.push_back(note);
|
Chris@141
|
135 }
|
Chris@140
|
136
|
Chris@140
|
137 m_notes[filename] = notes;
|
Chris@137
|
138 }
|
Chris@137
|
139
|
Chris@137
|
140 void
|
Chris@137
|
141 MIDIFeatureWriter::finish()
|
Chris@137
|
142 {
|
Chris@137
|
143 for (NoteMap::const_iterator i = m_notes.begin(); i != m_notes.end(); ++i) {
|
Chris@137
|
144
|
Chris@137
|
145 QString filename = i->first;
|
Chris@137
|
146 NoteList notes = i->second;
|
Chris@137
|
147 float rate = m_rates[filename];
|
Chris@137
|
148
|
Chris@137
|
149 TrivialNoteExportable exportable(notes);
|
Chris@137
|
150
|
Chris@137
|
151 {
|
Chris@137
|
152 MIDIFileWriter writer(filename, &exportable, rate);
|
Chris@137
|
153 if (!writer.isOK()) {
|
Chris@137
|
154 cerr << "ERROR: Failed to create MIDI writer: "
|
Chris@137
|
155 << writer.getError() << endl;
|
Chris@137
|
156 throw FileOperationFailed(filename, "create MIDI writer");
|
Chris@137
|
157 }
|
Chris@147
|
158
|
Chris@137
|
159 writer.write();
|
Chris@147
|
160
|
Chris@147
|
161 if (!writer.isOK()) {
|
Chris@147
|
162 cerr << "ERROR: Failed to write to MIDI file: "
|
Chris@147
|
163 << writer.getError() << endl;
|
Chris@147
|
164 throw FileOperationFailed(filename, "MIDI write");
|
Chris@147
|
165 }
|
Chris@137
|
166 }
|
Chris@137
|
167 }
|
Chris@145
|
168
|
Chris@152
|
169 m_notes.clear();
|
Chris@152
|
170
|
Chris@145
|
171 FileFeatureWriter::finish();
|
Chris@137
|
172 }
|
Chris@137
|
173
|