annotate data/fileio/MIDIFileWriter.cpp @ 678:948271d124ac

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