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@196
|
25 //#define DEBUG_MIDI_FEATURE_WRITER 1
|
Chris@196
|
26
|
Chris@137
|
27 MIDIFeatureWriter::MIDIFeatureWriter() :
|
Chris@137
|
28 FileFeatureWriter(SupportOneFilePerTrackTransform |
|
Chris@137
|
29 SupportOneFilePerTrack |
|
Chris@137
|
30 SupportOneFileTotal,
|
Chris@137
|
31 "mid")
|
Chris@137
|
32 {
|
Chris@137
|
33 }
|
Chris@137
|
34
|
Chris@137
|
35 MIDIFeatureWriter::~MIDIFeatureWriter()
|
Chris@137
|
36 {
|
Chris@137
|
37 }
|
Chris@137
|
38
|
Chris@144
|
39 string
|
Chris@144
|
40 MIDIFeatureWriter::getDescription() const
|
Chris@144
|
41 {
|
Chris@197
|
42 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 simply be rounded to the nearest integer and written directly. (Be aware that MIDI cannot represent overlapping notes of equal pitch, so features with durations may be misrepresented if they do not have distinct enough values.) Multiple (up to 16) transforms can be written to a single MIDI file, where they will be given separate MIDI channel numbers.";
|
Chris@144
|
43 }
|
Chris@144
|
44
|
Chris@137
|
45 MIDIFeatureWriter::ParameterList
|
Chris@137
|
46 MIDIFeatureWriter::getSupportedParameters() const
|
Chris@137
|
47 {
|
Chris@137
|
48 ParameterList pl = FileFeatureWriter::getSupportedParameters();
|
Chris@137
|
49 return pl;
|
Chris@137
|
50 }
|
Chris@137
|
51
|
Chris@137
|
52 void
|
Chris@137
|
53 MIDIFeatureWriter::setParameters(map<string, string> ¶ms)
|
Chris@137
|
54 {
|
Chris@137
|
55 FileFeatureWriter::setParameters(params);
|
Chris@137
|
56 }
|
Chris@137
|
57
|
Chris@137
|
58 void
|
Chris@137
|
59 MIDIFeatureWriter::setTrackMetadata(QString, TrackMetadata)
|
Chris@137
|
60 {
|
Chris@137
|
61 cerr << "MIDIFeatureWriter::setTrackMetadata: not supported (yet?)" << endl;
|
Chris@137
|
62 }
|
Chris@137
|
63
|
Chris@137
|
64 void
|
Chris@137
|
65 MIDIFeatureWriter::write(QString trackId,
|
Chris@137
|
66 const Transform &transform,
|
Chris@137
|
67 const Plugin::OutputDescriptor& output,
|
Chris@137
|
68 const Plugin::FeatureList& features,
|
Chris@141
|
69 std::string /* summaryType */)
|
Chris@137
|
70 {
|
Chris@140
|
71 QString transformId = transform.getIdentifier();
|
Chris@140
|
72
|
Chris@140
|
73 QString filename = getOutputFilename(trackId, transformId);
|
Chris@137
|
74 if (filename == "") {
|
Chris@140
|
75 throw FailedToOpenOutputStream(trackId, transformId);
|
Chris@137
|
76 }
|
Chris@137
|
77
|
Chris@141
|
78 int sampleRate = transform.getSampleRate();
|
Chris@141
|
79
|
Chris@140
|
80 if (m_rates.find(filename) == m_rates.end()) {
|
Chris@140
|
81 m_rates[filename] = sampleRate;
|
Chris@140
|
82 }
|
Chris@140
|
83
|
Chris@175
|
84 if (m_fileTransforms[filename].find(transform) ==
|
Chris@140
|
85 m_fileTransforms[filename].end()) {
|
Chris@140
|
86
|
Chris@140
|
87 // This transform is new to the file, give it a channel number
|
Chris@140
|
88
|
Chris@140
|
89 int channel = m_nextChannels[filename];
|
Chris@140
|
90 m_nextChannels[filename] = channel + 1;
|
Chris@140
|
91
|
Chris@175
|
92 m_fileTransforms[filename].insert(transform);
|
Chris@175
|
93 m_channels[transform] = channel;
|
Chris@140
|
94 }
|
Chris@140
|
95
|
Chris@140
|
96 NoteList notes = m_notes[filename];
|
Chris@140
|
97
|
Chris@141
|
98 bool freq = (output.unit == "Hz" ||
|
Chris@141
|
99 output.unit == "hz" ||
|
Chris@141
|
100 output.unit == "HZ");
|
Chris@141
|
101
|
Chris@141
|
102 for (int i = 0; i < (int)features.size(); ++i) {
|
Chris@141
|
103
|
Chris@141
|
104 const Plugin::Feature &feature(features[i]);
|
Chris@141
|
105
|
Chris@141
|
106 Vamp::RealTime timestamp = feature.timestamp;
|
Chris@141
|
107 int frame = Vamp::RealTime::realTime2Frame(timestamp, sampleRate);
|
Chris@141
|
108
|
Chris@141
|
109 int duration = 1;
|
Chris@141
|
110 if (feature.hasDuration) {
|
Chris@141
|
111 duration = Vamp::RealTime::realTime2Frame(feature.duration, sampleRate);
|
Chris@141
|
112 }
|
Chris@196
|
113
|
Chris@196
|
114 #ifdef DEBUG_MIDI_FEATURE_WRITER
|
Chris@196
|
115 cerr << "feature timestamp = " << feature.timestamp << ", sampleRate = " << sampleRate << ", frame = " << frame << endl;
|
Chris@196
|
116 cerr << "feature duration = " << feature.duration << ", sampleRate = " << sampleRate << ", duration = " << duration << endl;
|
Chris@196
|
117 #endif
|
Chris@141
|
118
|
Chris@141
|
119 int pitch = 60;
|
Chris@141
|
120 if (feature.values.size() > 0) {
|
Chris@141
|
121 float pval = feature.values[0];
|
Chris@141
|
122 if (freq) {
|
Chris@141
|
123 pitch = Pitch::getPitchForFrequency(pval);
|
Chris@141
|
124 } else {
|
Chris@141
|
125 pitch = int(pval + 0.5);
|
Chris@141
|
126 }
|
Chris@141
|
127 }
|
Chris@141
|
128
|
Chris@141
|
129 int velocity = 100;
|
Chris@141
|
130 if (feature.values.size() > 1) {
|
Chris@141
|
131 float vval = feature.values[1];
|
Chris@141
|
132 if (vval < 128) {
|
Chris@141
|
133 velocity = int(vval + 0.5);
|
Chris@141
|
134 }
|
Chris@141
|
135 }
|
Chris@141
|
136
|
Chris@141
|
137 NoteData note(frame, duration, pitch, velocity);
|
Chris@141
|
138
|
Chris@175
|
139 note.channel = m_channels[transform];
|
Chris@141
|
140
|
Chris@141
|
141 notes.push_back(note);
|
Chris@141
|
142 }
|
Chris@140
|
143
|
Chris@140
|
144 m_notes[filename] = notes;
|
Chris@137
|
145 }
|
Chris@137
|
146
|
Chris@137
|
147 void
|
Chris@137
|
148 MIDIFeatureWriter::finish()
|
Chris@137
|
149 {
|
Chris@137
|
150 for (NoteMap::const_iterator i = m_notes.begin(); i != m_notes.end(); ++i) {
|
Chris@137
|
151
|
Chris@137
|
152 QString filename = i->first;
|
Chris@137
|
153 NoteList notes = i->second;
|
Chris@137
|
154 float rate = m_rates[filename];
|
Chris@137
|
155
|
Chris@137
|
156 TrivialNoteExportable exportable(notes);
|
Chris@137
|
157
|
Chris@137
|
158 {
|
Chris@137
|
159 MIDIFileWriter writer(filename, &exportable, rate);
|
Chris@137
|
160 if (!writer.isOK()) {
|
Chris@137
|
161 cerr << "ERROR: Failed to create MIDI writer: "
|
Chris@137
|
162 << writer.getError() << endl;
|
Chris@137
|
163 throw FileOperationFailed(filename, "create MIDI writer");
|
Chris@137
|
164 }
|
Chris@147
|
165
|
Chris@137
|
166 writer.write();
|
Chris@147
|
167
|
Chris@147
|
168 if (!writer.isOK()) {
|
Chris@147
|
169 cerr << "ERROR: Failed to write to MIDI file: "
|
Chris@147
|
170 << writer.getError() << endl;
|
Chris@147
|
171 throw FileOperationFailed(filename, "MIDI write");
|
Chris@147
|
172 }
|
Chris@137
|
173 }
|
Chris@137
|
174 }
|
Chris@145
|
175
|
Chris@152
|
176 m_notes.clear();
|
Chris@152
|
177
|
Chris@145
|
178 FileFeatureWriter::finish();
|
Chris@137
|
179 }
|
Chris@137
|
180
|