comparison data/fileio/MIDIFileWriter.cpp @ 301:73537d900d4b

* Add MIDI file export (closes FR#1643721)
author Chris Cannam
date Thu, 04 Oct 2007 11:52:38 +0000
parents
children 516819f2b97b
comparison
equal deleted inserted replaced
300:5877d68815c7 301:73537d900d4b
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
2
3 /*
4 Sonic Visualiser
5 An audio file viewer and annotation editor.
6 Centre for Digital Music, Queen Mary, University of London.
7
8 This program is free software; you can redistribute it and/or
9 modify it under the terms of the GNU General Public License as
10 published by the Free Software Foundation; either version 2 of the
11 License, or (at your option) any later version. See the file
12 COPYING included with this distribution for more information.
13 */
14
15
16 /*
17 This is a modified version of a source file from the
18 Rosegarden MIDI and audio sequencer and notation editor.
19 This file copyright 2000-2007 Richard Bown and Chris Cannam
20 and copyright 2007 QMUL.
21 */
22
23 #include "MIDIFileWriter.h"
24
25 #include "MIDIEvent.h"
26
27 #include "model/NoteModel.h"
28
29 #include "base/Pitch.h"
30
31 #include <algorithm>
32 #include <fstream>
33
34 using std::ofstream;
35 using std::string;
36 using std::ios;
37
38 using namespace MIDIConstants;
39
40 MIDIFileWriter::MIDIFileWriter(QString path, NoteModel *model, float tempo) :
41 m_path(path),
42 m_model(model),
43 m_modelUsesHz(false),
44 m_tempo(tempo)
45 {
46 if (model->getScaleUnits().toLower() == "hz") m_modelUsesHz = true;
47
48 if (!convert()) {
49 m_error = "Conversion from model to internal MIDI format failed";
50 }
51 }
52
53 MIDIFileWriter::~MIDIFileWriter()
54 {
55 for (MIDIComposition::iterator i = m_midiComposition.begin();
56 i != m_midiComposition.end(); ++i) {
57
58 for (MIDITrack::iterator j = i->second.begin();
59 j != i->second.end(); ++j) {
60 delete *j;
61 }
62
63 i->second.clear();
64 }
65
66 m_midiComposition.clear();
67 }
68
69 bool
70 MIDIFileWriter::isOK() const
71 {
72 return m_error == "";
73 }
74
75 QString
76 MIDIFileWriter::getError() const
77 {
78 return m_error;
79 }
80
81 void
82 MIDIFileWriter::write()
83 {
84 writeComposition();
85 }
86
87 string
88 MIDIFileWriter::intToMIDIBytes(int number) const
89 {
90 MIDIByte upper;
91 MIDIByte lower;
92
93 upper = (number & 0xFF00) >> 8;
94 lower = (number & 0x00FF);
95
96 string rv;
97 rv += upper;
98 rv += lower;
99 return rv;
100 }
101
102 string
103 MIDIFileWriter::longToMIDIBytes(unsigned long number) const
104 {
105 MIDIByte upper1;
106 MIDIByte lower1;
107 MIDIByte upper2;
108 MIDIByte lower2;
109
110 upper1 = (number & 0xff000000) >> 24;
111 lower1 = (number & 0x00ff0000) >> 16;
112 upper2 = (number & 0x0000ff00) >> 8;
113 lower2 = (number & 0x000000ff);
114
115 string rv;
116 rv += upper1;
117 rv += lower1;
118 rv += upper2;
119 rv += lower2;
120 return rv;
121 }
122
123 // Turn a delta time into a MIDI time - overlapping into
124 // a maximum of four bytes using the MSB as the carry on
125 // flag.
126 //
127 string
128 MIDIFileWriter::longToVarBuffer(unsigned long number) const
129 {
130 string rv;
131
132 long inNumber = number;
133 long outNumber;
134
135 // get the lowest 7 bits of the number
136 outNumber = number & 0x7f;
137
138 // Shift and test and move the numbers
139 // on if we need them - setting the MSB
140 // as we go.
141 //
142 while ((inNumber >>= 7 ) > 0) {
143 outNumber <<= 8;
144 outNumber |= 0x80;
145 outNumber += (inNumber & 0x7f);
146 }
147
148 // Now move the converted number out onto the buffer
149 //
150 while (true) {
151 rv += (MIDIByte)(outNumber & 0xff);
152 if (outNumber & 0x80)
153 outNumber >>= 8;
154 else
155 break;
156 }
157
158 return rv;
159 }
160
161 bool
162 MIDIFileWriter::writeHeader()
163 {
164 *m_midiFile << MIDI_FILE_HEADER;
165
166 // Number of bytes in header
167 *m_midiFile << (MIDIByte) 0x00;
168 *m_midiFile << (MIDIByte) 0x00;
169 *m_midiFile << (MIDIByte) 0x00;
170 *m_midiFile << (MIDIByte) 0x06;
171
172 // File format
173 *m_midiFile << (MIDIByte) 0x00;
174 *m_midiFile << (MIDIByte) m_format;
175
176 *m_midiFile << intToMIDIBytes(m_numberOfTracks);
177
178 *m_midiFile << intToMIDIBytes(m_timingDivision);
179
180 return true;
181 }
182
183 bool
184 MIDIFileWriter::writeTrack(int trackNumber)
185 {
186 bool retOK = true;
187 MIDIByte eventCode = 0;
188 MIDITrack::iterator midiEvent;
189
190 // First we write into the trackBuffer, then write it out to the
191 // file with its accompanying length.
192 //
193 string trackBuffer;
194
195 for (midiEvent = m_midiComposition[trackNumber].begin();
196 midiEvent != m_midiComposition[trackNumber].end();
197 midiEvent++) {
198
199 // Write the time to the buffer in MIDI format
200 trackBuffer += longToVarBuffer((*midiEvent)->getTime());
201
202 if ((*midiEvent)->isMeta()) {
203 trackBuffer += MIDI_FILE_META_EVENT;
204 trackBuffer += (*midiEvent)->getMetaEventCode();
205
206 // Variable length number field
207 trackBuffer += longToVarBuffer((*midiEvent)->
208 getMetaMessage().length());
209
210 trackBuffer += (*midiEvent)->getMetaMessage();
211 } else {
212 // Send the normal event code (with encoded channel information)
213 if (((*midiEvent)->getEventCode() != eventCode) ||
214 ((*midiEvent)->getEventCode() == MIDI_SYSTEM_EXCLUSIVE)) {
215 trackBuffer += (*midiEvent)->getEventCode();
216 eventCode = (*midiEvent)->getEventCode();
217 }
218
219 // Send the relevant data
220 //
221 switch ((*midiEvent)->getMessageType()) {
222 case MIDI_NOTE_ON:
223 case MIDI_NOTE_OFF:
224 case MIDI_POLY_AFTERTOUCH:
225 trackBuffer += (*midiEvent)->getData1();
226 trackBuffer += (*midiEvent)->getData2();
227 break;
228
229 case MIDI_CTRL_CHANGE:
230 trackBuffer += (*midiEvent)->getData1();
231 trackBuffer += (*midiEvent)->getData2();
232 break;
233
234 case MIDI_PROG_CHANGE:
235 trackBuffer += (*midiEvent)->getData1();
236 break;
237
238 case MIDI_CHNL_AFTERTOUCH:
239 trackBuffer += (*midiEvent)->getData1();
240 break;
241
242 case MIDI_PITCH_BEND:
243 trackBuffer += (*midiEvent)->getData1();
244 trackBuffer += (*midiEvent)->getData2();
245 break;
246
247 case MIDI_SYSTEM_EXCLUSIVE:
248 // write out message length
249 trackBuffer +=
250 longToVarBuffer((*midiEvent)->getMetaMessage().length());
251
252 // now the message
253 trackBuffer += (*midiEvent)->getMetaMessage();
254 break;
255
256 default:
257 break;
258 }
259 }
260 }
261
262 // Now we write the track - First the standard header..
263 //
264 *m_midiFile << MIDI_TRACK_HEADER;
265
266 // ..now the length of the buffer..
267 //
268 *m_midiFile << longToMIDIBytes((long)trackBuffer.length());
269
270 // ..then the buffer itself..
271 //
272 *m_midiFile << trackBuffer;
273
274 return retOK;
275 }
276
277 bool
278 MIDIFileWriter::writeComposition()
279 {
280 bool retOK = true;
281
282 m_midiFile =
283 new ofstream(m_path.toLocal8Bit().data(), ios::out | ios::binary);
284
285 if (!(*m_midiFile)) {
286 m_error = "Can't open file for writing.";
287 delete m_midiFile;
288 m_midiFile = 0;
289 return false;
290 }
291
292 if (!writeHeader()) {
293 retOK = false;
294 }
295
296 for (unsigned int i = 0; i < m_numberOfTracks; i++) {
297 if (!writeTrack(i)) {
298 retOK = false;
299 }
300 }
301
302 m_midiFile->close();
303 delete m_midiFile;
304 m_midiFile = 0;
305
306 if (!retOK) {
307 m_error = "MIDI file write failed";
308 }
309
310 return retOK;
311 }
312
313 bool
314 MIDIFileWriter::convert()
315 {
316 m_timingDivision = 480;
317 m_format = MIDI_SINGLE_TRACK_FILE;
318 m_numberOfTracks = 1;
319
320 int track = 0;
321 int midiChannel = 0;
322
323 MIDIEvent *event;
324
325 event = new MIDIEvent(0, MIDI_FILE_META_EVENT, MIDI_CUE_POINT,
326 "Exported from Sonic Visualiser");
327 m_midiComposition[track].push_back(event);
328
329 event = new MIDIEvent(0, MIDI_FILE_META_EVENT, MIDI_CUE_POINT,
330 "http://www.sonicvisualiser.org/");
331 m_midiComposition[track].push_back(event);
332
333 long tempoValue = long(60000000.0 / m_tempo + 0.01);
334 string tempoString;
335 tempoString += (MIDIByte)(tempoValue >> 16 & 0xFF);
336 tempoString += (MIDIByte)(tempoValue >> 8 & 0xFF);
337 tempoString += (MIDIByte)(tempoValue & 0xFF);
338
339 event = new MIDIEvent(0, MIDI_FILE_META_EVENT, MIDI_SET_TEMPO,
340 tempoString);
341 m_midiComposition[track].push_back(event);
342
343 // Omit time signature
344
345 const NoteModel::PointList &notes =
346 static_cast<SparseModel<Note> *>(m_model)->getPoints();
347
348 for (NoteModel::PointList::const_iterator i = notes.begin();
349 i != notes.end(); ++i) {
350
351 long frame = i->frame;
352 float value = i->value;
353 size_t duration = i->duration;
354
355 int pitch;
356
357 if (m_modelUsesHz) {
358 pitch = Pitch::getPitchForFrequency(value);
359 } else {
360 pitch = lrintf(value);
361 }
362
363 if (pitch < 0) pitch = 0;
364 if (pitch > 127) pitch = 127;
365
366 // Convert frame to MIDI time
367
368 double seconds = double(frame) / double(m_model->getSampleRate());
369 double quarters = (seconds * m_tempo) / 60.0;
370 unsigned long midiTime = lrint(quarters * m_timingDivision);
371
372 // We don't support velocity in note models yet
373 int velocity = 100;
374
375 // Get the sounding time for the matching NOTE_OFF
376 seconds = double(frame + duration) / double(m_model->getSampleRate());
377 quarters = (seconds * m_tempo) / 60.0;
378 unsigned long endTime = lrint(quarters * m_timingDivision);
379
380 // At this point all the notes we insert have absolute times
381 // in the delta time fields. We resolve these into delta
382 // times further down (can't do it until all the note offs are
383 // in place).
384
385 event = new MIDIEvent(midiTime,
386 MIDI_NOTE_ON | midiChannel,
387 pitch,
388 velocity);
389 m_midiComposition[track].push_back(event);
390
391 event = new MIDIEvent(endTime,
392 MIDI_NOTE_OFF | midiChannel,
393 pitch,
394 127); // loudest silence you can muster
395
396 m_midiComposition[track].push_back(event);
397 }
398
399 // Now gnash through the MIDI events and turn the absolute times
400 // into delta times.
401 //
402 for (unsigned int i = 0; i < m_numberOfTracks; i++) {
403
404 unsigned long lastMidiTime = 0;
405
406 // First sort the track with the MIDIEvent comparator. Use
407 // stable_sort so that events with equal times are maintained
408 // in their current order.
409 //
410 std::stable_sort(m_midiComposition[i].begin(),
411 m_midiComposition[i].end(),
412 MIDIEventCmp());
413
414 for (MIDITrack::iterator it = m_midiComposition[i].begin();
415 it != m_midiComposition[i].end(); it++) {
416 unsigned long deltaTime = (*it)->getTime() - lastMidiTime;
417 lastMidiTime = (*it)->getTime();
418 (*it)->setTime(deltaTime);
419 }
420
421 // Insert end of track event (delta time = 0)
422 //
423 event = new MIDIEvent(0, MIDI_FILE_META_EVENT,
424 MIDI_END_OF_TRACK, "");
425
426 m_midiComposition[i].push_back(event);
427 }
428
429 return true;
430 }
431