annotate data/fileio/MIDIFileWriter.cpp @ 1833:21c792334c2e sensible-delimited-data-strings

Rewrite all the DelimitedDataString stuff so as to return vectors of individual cell strings rather than having the classes add the delimiters themselves. Rename accordingly to names based on StringExport. Take advantage of this in the CSV writer code so as to properly quote cells that contain delimiter characters.
author Chris Cannam
date Fri, 03 Apr 2020 17:11:05 +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