annotate data/fileio/MIDIFileWriter.cpp @ 1248:58dd6a6fe414 piper

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