annotate data/fileio/MIDIFileWriter.cpp @ 974:65494d0d9ded

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