annotate data/fileio/MIDIFileWriter.cpp @ 1700:c1208b211d8c single-point

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