annotate data/fileio/MIDIFileWriter.cpp @ 507:0944d13689b2

* Implement proper RDF feature writing for track level features, using the feature attribute URI given in the plugin description RDF (if there is one)
author Chris Cannam
date Fri, 05 Dec 2008 14:19:04 +0000
parents 516819f2b97b
children 2e50d95cf621
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@301 25 #include "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