annotate data/fileio/MIDIFileWriter.cpp @ 875:3e6ed8a8577b tonioni

Use a sparse time-value model only for outputs with fixed bin count of 1, not for those with unknown bin count. (Precursor to using more than one model for outputs with unknown bin count)
author Chris Cannam
date Tue, 28 Jan 2014 18:52:22 +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