annotate data/fileio/MIDIFileWriter.cpp @ 869:8e22795fe30b tonioni

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