annotate data/fileio/MIDIFileWriter.cpp @ 1607:a77fc6da288b

Try to avoid interference with QtTest's own arg handling
author Chris Cannam
date Mon, 18 Feb 2019 11:37:41 +0000
parents 70e172e6cc59
children 24dc8cb42755
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@1582 49 m_midiFile(nullptr)
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@1429 59 i != m_midiComposition.end(); ++i) {
Chris@1429 60
Chris@1429 61 for (MIDITrack::iterator j = i->second.begin();
Chris@1429 62 j != i->second.end(); ++j) {
Chris@1429 63 delete *j;
Chris@1429 64 }
Chris@301 65
Chris@1429 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@1582 291 m_midiFile = nullptr;
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@1582 307 m_midiFile = nullptr;
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