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