annotate data/fileio/MIDIFileWriter.cpp @ 1008:d9e0e59a1581

When using an aggregate model to pass data to a transform, zero-pad the shorter input to the duration of the longer rather than truncating the longer. (This is better behaviour for e.g. MATCH, and in any case the code was previously truncating incorrectly and ending up with garbage data at the end.)
author Chris Cannam
date Fri, 14 Nov 2014 13:51:33 +0000
parents 0d3d1ec7dfde
children cc27f35aa75c
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
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@929 347 int frame = i->start;
Chris@929 348 int duration = i->duration;
Chris@852 349 int pitch = i->midiPitch;
Chris@852 350 int velocity = i->velocity;
Chris@996 351 int channel = i->channel;
Chris@301 352
Chris@301 353 if (pitch < 0) pitch = 0;
Chris@301 354 if (pitch > 127) pitch = 127;
Chris@301 355
Chris@996 356 if (channel < 0) channel = 0;
Chris@996 357 if (channel > 15) channel = 0;
Chris@996 358
Chris@301 359 // Convert frame to MIDI time
Chris@301 360
Chris@852 361 double seconds = double(frame) / double(m_sampleRate);
Chris@301 362 double quarters = (seconds * m_tempo) / 60.0;
Chris@852 363 unsigned long midiTime = int(quarters * m_timingDivision + 0.5);
Chris@301 364
Chris@301 365 // Get the sounding time for the matching NOTE_OFF
Chris@852 366 seconds = double(frame + duration) / double(m_sampleRate);
Chris@301 367 quarters = (seconds * m_tempo) / 60.0;
Chris@852 368 unsigned long endTime = int(quarters * m_timingDivision + 0.5);
Chris@301 369
Chris@301 370 // At this point all the notes we insert have absolute times
Chris@301 371 // in the delta time fields. We resolve these into delta
Chris@301 372 // times further down (can't do it until all the note offs are
Chris@301 373 // in place).
Chris@301 374
Chris@301 375 event = new MIDIEvent(midiTime,
Chris@996 376 MIDI_NOTE_ON | channel,
Chris@301 377 pitch,
Chris@301 378 velocity);
Chris@301 379 m_midiComposition[track].push_back(event);
Chris@301 380
Chris@301 381 event = new MIDIEvent(endTime,
Chris@996 382 MIDI_NOTE_OFF | channel,
Chris@301 383 pitch,
Chris@301 384 127); // loudest silence you can muster
Chris@301 385
Chris@301 386 m_midiComposition[track].push_back(event);
Chris@301 387 }
Chris@301 388
Chris@301 389 // Now gnash through the MIDI events and turn the absolute times
Chris@301 390 // into delta times.
Chris@301 391 //
Chris@301 392 for (unsigned int i = 0; i < m_numberOfTracks; i++) {
Chris@301 393
Chris@301 394 unsigned long lastMidiTime = 0;
Chris@301 395
Chris@301 396 // First sort the track with the MIDIEvent comparator. Use
Chris@301 397 // stable_sort so that events with equal times are maintained
Chris@301 398 // in their current order.
Chris@301 399 //
Chris@301 400 std::stable_sort(m_midiComposition[i].begin(),
Chris@301 401 m_midiComposition[i].end(),
Chris@301 402 MIDIEventCmp());
Chris@301 403
Chris@301 404 for (MIDITrack::iterator it = m_midiComposition[i].begin();
Chris@301 405 it != m_midiComposition[i].end(); it++) {
Chris@301 406 unsigned long deltaTime = (*it)->getTime() - lastMidiTime;
Chris@301 407 lastMidiTime = (*it)->getTime();
Chris@301 408 (*it)->setTime(deltaTime);
Chris@301 409 }
Chris@301 410
Chris@301 411 // Insert end of track event (delta time = 0)
Chris@301 412 //
Chris@301 413 event = new MIDIEvent(0, MIDI_FILE_META_EVENT,
Chris@301 414 MIDI_END_OF_TRACK, "");
Chris@301 415
Chris@301 416 m_midiComposition[i].push_back(event);
Chris@301 417 }
Chris@301 418
Chris@301 419 return true;
Chris@301 420 }
Chris@301 421