annotate data/fileio/MIDIFileReader.cpp @ 263:71dfc6ab3b54

* Threaded mp3/ogg file reading. Not activated yet, as it doesn't work in context (SV needs to know the duration of its main model at the outset)
author Chris Cannam
date Thu, 24 May 2007 16:20:22 +0000
parents 4b2ea82fd0ed
children 73537d900d4b
rev   line source
Chris@148 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@148 2
Chris@148 3 /*
Chris@148 4 Sonic Visualiser
Chris@148 5 An audio file viewer and annotation editor.
Chris@148 6 Centre for Digital Music, Queen Mary, University of London.
Chris@148 7
Chris@148 8 This program is free software; you can redistribute it and/or
Chris@148 9 modify it under the terms of the GNU General Public License as
Chris@148 10 published by the Free Software Foundation; either version 2 of the
Chris@148 11 License, or (at your option) any later version. See the file
Chris@148 12 COPYING included with this distribution for more information.
Chris@148 13 */
Chris@148 14
Chris@148 15
Chris@148 16 /*
Chris@148 17 This is a modified version of a source file from the
Chris@148 18 Rosegarden MIDI and audio sequencer and notation editor.
Chris@148 19 This file copyright 2000-2006 Richard Bown and Chris Cannam.
Chris@148 20 */
Chris@148 21
Chris@148 22
Chris@148 23 #include <iostream>
Chris@148 24 #include <fstream>
Chris@148 25 #include <string>
Chris@148 26 #include <cstdio>
Chris@148 27 #include <algorithm>
Chris@148 28
Chris@148 29 #include "MIDIFileReader.h"
Chris@148 30
Chris@150 31 #include "model/Model.h"
Chris@148 32 #include "base/Pitch.h"
Chris@148 33 #include "base/RealTime.h"
Chris@148 34 #include "model/NoteModel.h"
Chris@148 35
Chris@148 36 #include <QString>
Chris@148 37 #include <QMessageBox>
Chris@148 38 #include <QInputDialog>
Chris@148 39
Chris@148 40 #include <sstream>
Chris@148 41
Chris@148 42 using std::string;
Chris@148 43 using std::ifstream;
Chris@148 44 using std::stringstream;
Chris@148 45 using std::cerr;
Chris@148 46 using std::endl;
Chris@148 47 using std::ends;
Chris@148 48 using std::ios;
Chris@148 49 using std::vector;
Chris@148 50 using std::map;
Chris@148 51 using std::set;
Chris@148 52
Chris@148 53 //#define MIDI_DEBUG 1
Chris@148 54
Chris@148 55 static const char *const MIDI_FILE_HEADER = "MThd";
Chris@148 56 static const char *const MIDI_TRACK_HEADER = "MTrk";
Chris@148 57
Chris@148 58 static const MIDIFileReader::MIDIByte MIDI_STATUS_BYTE_MASK = 0x80;
Chris@148 59 static const MIDIFileReader::MIDIByte MIDI_MESSAGE_TYPE_MASK = 0xF0;
Chris@148 60 static const MIDIFileReader::MIDIByte MIDI_CHANNEL_NUM_MASK = 0x0F;
Chris@148 61 static const MIDIFileReader::MIDIByte MIDI_NOTE_OFF = 0x80;
Chris@148 62 static const MIDIFileReader::MIDIByte MIDI_NOTE_ON = 0x90;
Chris@148 63 static const MIDIFileReader::MIDIByte MIDI_POLY_AFTERTOUCH = 0xA0;
Chris@148 64 static const MIDIFileReader::MIDIByte MIDI_CTRL_CHANGE = 0xB0;
Chris@148 65 static const MIDIFileReader::MIDIByte MIDI_PROG_CHANGE = 0xC0;
Chris@148 66 static const MIDIFileReader::MIDIByte MIDI_CHNL_AFTERTOUCH = 0xD0;
Chris@148 67 static const MIDIFileReader::MIDIByte MIDI_PITCH_BEND = 0xE0;
Chris@148 68 static const MIDIFileReader::MIDIByte MIDI_SELECT_CHNL_MODE = 0xB0;
Chris@148 69 static const MIDIFileReader::MIDIByte MIDI_SYSTEM_EXCLUSIVE = 0xF0;
Chris@148 70 static const MIDIFileReader::MIDIByte MIDI_TC_QUARTER_FRAME = 0xF1;
Chris@148 71 static const MIDIFileReader::MIDIByte MIDI_SONG_POSITION_PTR = 0xF2;
Chris@148 72 static const MIDIFileReader::MIDIByte MIDI_SONG_SELECT = 0xF3;
Chris@148 73 static const MIDIFileReader::MIDIByte MIDI_TUNE_REQUEST = 0xF6;
Chris@148 74 static const MIDIFileReader::MIDIByte MIDI_END_OF_EXCLUSIVE = 0xF7;
Chris@148 75 static const MIDIFileReader::MIDIByte MIDI_TIMING_CLOCK = 0xF8;
Chris@148 76 static const MIDIFileReader::MIDIByte MIDI_START = 0xFA;
Chris@148 77 static const MIDIFileReader::MIDIByte MIDI_CONTINUE = 0xFB;
Chris@148 78 static const MIDIFileReader::MIDIByte MIDI_STOP = 0xFC;
Chris@148 79 static const MIDIFileReader::MIDIByte MIDI_ACTIVE_SENSING = 0xFE;
Chris@148 80 static const MIDIFileReader::MIDIByte MIDI_SYSTEM_RESET = 0xFF;
Chris@148 81 static const MIDIFileReader::MIDIByte MIDI_SYSEX_NONCOMMERCIAL = 0x7D;
Chris@148 82 static const MIDIFileReader::MIDIByte MIDI_SYSEX_NON_RT = 0x7E;
Chris@148 83 static const MIDIFileReader::MIDIByte MIDI_SYSEX_RT = 0x7F;
Chris@148 84 static const MIDIFileReader::MIDIByte MIDI_SYSEX_RT_COMMAND = 0x06;
Chris@148 85 static const MIDIFileReader::MIDIByte MIDI_SYSEX_RT_RESPONSE = 0x07;
Chris@148 86 static const MIDIFileReader::MIDIByte MIDI_MMC_STOP = 0x01;
Chris@148 87 static const MIDIFileReader::MIDIByte MIDI_MMC_PLAY = 0x02;
Chris@148 88 static const MIDIFileReader::MIDIByte MIDI_MMC_DEFERRED_PLAY = 0x03;
Chris@148 89 static const MIDIFileReader::MIDIByte MIDI_MMC_FAST_FORWARD = 0x04;
Chris@148 90 static const MIDIFileReader::MIDIByte MIDI_MMC_REWIND = 0x05;
Chris@148 91 static const MIDIFileReader::MIDIByte MIDI_MMC_RECORD_STROBE = 0x06;
Chris@148 92 static const MIDIFileReader::MIDIByte MIDI_MMC_RECORD_EXIT = 0x07;
Chris@148 93 static const MIDIFileReader::MIDIByte MIDI_MMC_RECORD_PAUSE = 0x08;
Chris@148 94 static const MIDIFileReader::MIDIByte MIDI_MMC_PAUSE = 0x08;
Chris@148 95 static const MIDIFileReader::MIDIByte MIDI_MMC_EJECT = 0x0A;
Chris@148 96 static const MIDIFileReader::MIDIByte MIDI_MMC_LOCATE = 0x44;
Chris@148 97 static const MIDIFileReader::MIDIByte MIDI_FILE_META_EVENT = 0xFF;
Chris@148 98 static const MIDIFileReader::MIDIByte MIDI_SEQUENCE_NUMBER = 0x00;
Chris@148 99 static const MIDIFileReader::MIDIByte MIDI_TEXT_EVENT = 0x01;
Chris@148 100 static const MIDIFileReader::MIDIByte MIDI_COPYRIGHT_NOTICE = 0x02;
Chris@148 101 static const MIDIFileReader::MIDIByte MIDI_TRACK_NAME = 0x03;
Chris@148 102 static const MIDIFileReader::MIDIByte MIDI_INSTRUMENT_NAME = 0x04;
Chris@148 103 static const MIDIFileReader::MIDIByte MIDI_LYRIC = 0x05;
Chris@148 104 static const MIDIFileReader::MIDIByte MIDI_TEXT_MARKER = 0x06;
Chris@148 105 static const MIDIFileReader::MIDIByte MIDI_CUE_POINT = 0x07;
Chris@148 106 static const MIDIFileReader::MIDIByte MIDI_CHANNEL_PREFIX = 0x20;
Chris@148 107 static const MIDIFileReader::MIDIByte MIDI_CHANNEL_PREFIX_OR_PORT = 0x21;
Chris@148 108 static const MIDIFileReader::MIDIByte MIDI_END_OF_TRACK = 0x2F;
Chris@148 109 static const MIDIFileReader::MIDIByte MIDI_SET_TEMPO = 0x51;
Chris@148 110 static const MIDIFileReader::MIDIByte MIDI_SMPTE_OFFSET = 0x54;
Chris@148 111 static const MIDIFileReader::MIDIByte MIDI_TIME_SIGNATURE = 0x58;
Chris@148 112 static const MIDIFileReader::MIDIByte MIDI_KEY_SIGNATURE = 0x59;
Chris@148 113 static const MIDIFileReader::MIDIByte MIDI_SEQUENCER_SPECIFIC = 0x7F;
Chris@148 114 static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_BANK_MSB = 0x00;
Chris@148 115 static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_VOLUME = 0x07;
Chris@148 116 static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_BANK_LSB = 0x20;
Chris@148 117 static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_MODULATION = 0x01;
Chris@148 118 static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_PAN = 0x0A;
Chris@148 119 static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_SUSTAIN = 0x40;
Chris@148 120 static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_RESONANCE = 0x47;
Chris@148 121 static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_RELEASE = 0x48;
Chris@148 122 static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_ATTACK = 0x49;
Chris@148 123 static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_FILTER = 0x4A;
Chris@148 124 static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_REVERB = 0x5B;
Chris@148 125 static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_CHORUS = 0x5D;
Chris@148 126 static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_NRPN_1 = 0x62;
Chris@148 127 static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_NRPN_2 = 0x63;
Chris@148 128 static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_RPN_1 = 0x64;
Chris@148 129 static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_RPN_2 = 0x65;
Chris@148 130 static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_SOUNDS_OFF = 0x78;
Chris@148 131 static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_RESET = 0x79;
Chris@148 132 static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_LOCAL = 0x7A;
Chris@148 133 static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_ALL_NOTES_OFF = 0x7B;
Chris@148 134 static const MIDIFileReader::MIDIByte MIDI_PERCUSSION_CHANNEL = 9;
Chris@148 135
Chris@148 136 class MIDIEvent
Chris@148 137 {
Chris@148 138 public:
Chris@148 139 typedef MIDIFileReader::MIDIByte MIDIByte;
Chris@148 140
Chris@148 141 MIDIEvent(unsigned long deltaTime,
Chris@148 142 MIDIByte eventCode,
Chris@148 143 MIDIByte data1 = 0,
Chris@148 144 MIDIByte data2 = 0) :
Chris@148 145 m_deltaTime(deltaTime),
Chris@148 146 m_duration(0),
Chris@148 147 m_eventCode(eventCode),
Chris@148 148 m_data1(data1),
Chris@148 149 m_data2(data2),
Chris@148 150 m_metaEventCode(0)
Chris@148 151 { }
Chris@148 152
Chris@148 153 MIDIEvent(unsigned long deltaTime,
Chris@148 154 MIDIByte eventCode,
Chris@148 155 MIDIByte metaEventCode,
Chris@148 156 const string &metaMessage) :
Chris@148 157 m_deltaTime(deltaTime),
Chris@148 158 m_duration(0),
Chris@148 159 m_eventCode(eventCode),
Chris@148 160 m_data1(0),
Chris@148 161 m_data2(0),
Chris@148 162 m_metaEventCode(metaEventCode),
Chris@148 163 m_metaMessage(metaMessage)
Chris@148 164 { }
Chris@148 165
Chris@148 166 MIDIEvent(unsigned long deltaTime,
Chris@148 167 MIDIByte eventCode,
Chris@148 168 const string &sysEx) :
Chris@148 169 m_deltaTime(deltaTime),
Chris@148 170 m_duration(0),
Chris@148 171 m_eventCode(eventCode),
Chris@148 172 m_data1(0),
Chris@148 173 m_data2(0),
Chris@148 174 m_metaEventCode(0),
Chris@148 175 m_metaMessage(sysEx)
Chris@148 176 { }
Chris@148 177
Chris@148 178 ~MIDIEvent() { }
Chris@148 179
Chris@148 180 void setTime(const unsigned long &time) { m_deltaTime = time; }
Chris@148 181 void setDuration(const unsigned long& duration) { m_duration = duration;}
Chris@148 182 unsigned long addTime(const unsigned long &time) {
Chris@148 183 m_deltaTime += time;
Chris@148 184 return m_deltaTime;
Chris@148 185 }
Chris@148 186
Chris@148 187 MIDIByte getMessageType() const
Chris@148 188 { return (m_eventCode & MIDI_MESSAGE_TYPE_MASK); }
Chris@148 189
Chris@148 190 MIDIByte getChannelNumber() const
Chris@148 191 { return (m_eventCode & MIDI_CHANNEL_NUM_MASK); }
Chris@148 192
Chris@148 193 unsigned long getTime() const { return m_deltaTime; }
Chris@148 194 unsigned long getDuration() const { return m_duration; }
Chris@148 195
Chris@148 196 MIDIByte getPitch() const { return m_data1; }
Chris@148 197 MIDIByte getVelocity() const { return m_data2; }
Chris@148 198 MIDIByte getData1() const { return m_data1; }
Chris@148 199 MIDIByte getData2() const { return m_data2; }
Chris@148 200 MIDIByte getEventCode() const { return m_eventCode; }
Chris@148 201
Chris@148 202 bool isMeta() const { return (m_eventCode == MIDI_FILE_META_EVENT); }
Chris@148 203
Chris@148 204 MIDIByte getMetaEventCode() const { return m_metaEventCode; }
Chris@148 205 string getMetaMessage() const { return m_metaMessage; }
Chris@148 206 void setMetaMessage(const string &meta) { m_metaMessage = meta; }
Chris@148 207
Chris@148 208 friend bool operator<(const MIDIEvent &a, const MIDIEvent &b);
Chris@148 209
Chris@148 210 private:
Chris@148 211 MIDIEvent& operator=(const MIDIEvent);
Chris@148 212
Chris@148 213 unsigned long m_deltaTime;
Chris@148 214 unsigned long m_duration;
Chris@148 215 MIDIByte m_eventCode;
Chris@148 216 MIDIByte m_data1; // or Note
Chris@148 217 MIDIByte m_data2; // or Velocity
Chris@148 218 MIDIByte m_metaEventCode;
Chris@148 219 string m_metaMessage;
Chris@148 220 };
Chris@148 221
Chris@148 222 // Comparator for sorting
Chris@148 223 //
Chris@148 224 struct MIDIEventCmp
Chris@148 225 {
Chris@148 226 bool operator()(const MIDIEvent &mE1, const MIDIEvent &mE2) const
Chris@148 227 { return mE1.getTime() < mE2.getTime(); }
Chris@148 228
Chris@148 229 bool operator()(const MIDIEvent *mE1, const MIDIEvent *mE2) const
Chris@148 230 { return mE1->getTime() < mE2->getTime(); }
Chris@148 231 };
Chris@148 232
Chris@148 233 class MIDIException : virtual public std::exception
Chris@148 234 {
Chris@148 235 public:
Chris@148 236 MIDIException(QString message) throw() : m_message(message) {
Chris@148 237 cerr << "WARNING: MIDI exception: "
Chris@148 238 << message.toLocal8Bit().data() << endl;
Chris@148 239 }
Chris@148 240 virtual ~MIDIException() throw() { }
Chris@148 241
Chris@148 242 virtual const char *what() const throw() {
Chris@148 243 return m_message.toLocal8Bit().data();
Chris@148 244 }
Chris@148 245
Chris@148 246 protected:
Chris@148 247 QString m_message;
Chris@148 248 };
Chris@148 249
Chris@148 250
Chris@148 251 MIDIFileReader::MIDIFileReader(QString path,
Chris@148 252 size_t mainModelSampleRate) :
Chris@148 253 m_timingDivision(0),
Chris@148 254 m_format(MIDI_FILE_BAD_FORMAT),
Chris@148 255 m_numberOfTracks(0),
Chris@148 256 m_trackByteCount(0),
Chris@148 257 m_decrementCount(false),
Chris@148 258 m_path(path),
Chris@148 259 m_midiFile(0),
Chris@148 260 m_fileSize(0),
Chris@148 261 m_mainModelSampleRate(mainModelSampleRate)
Chris@148 262 {
Chris@148 263 if (parseFile()) {
Chris@148 264 m_error = "";
Chris@148 265 }
Chris@148 266 }
Chris@148 267
Chris@148 268 MIDIFileReader::~MIDIFileReader()
Chris@148 269 {
Chris@148 270 for (MIDIComposition::iterator i = m_midiComposition.begin();
Chris@148 271 i != m_midiComposition.end(); ++i) {
Chris@148 272
Chris@148 273 for (MIDITrack::iterator j = i->second.begin();
Chris@148 274 j != i->second.end(); ++j) {
Chris@148 275 delete *j;
Chris@148 276 }
Chris@148 277
Chris@148 278 i->second.clear();
Chris@148 279 }
Chris@148 280
Chris@148 281 m_midiComposition.clear();
Chris@148 282 }
Chris@148 283
Chris@148 284 bool
Chris@148 285 MIDIFileReader::isOK() const
Chris@148 286 {
Chris@148 287 return (m_error == "");
Chris@148 288 }
Chris@148 289
Chris@148 290 QString
Chris@148 291 MIDIFileReader::getError() const
Chris@148 292 {
Chris@148 293 return m_error;
Chris@148 294 }
Chris@148 295
Chris@148 296 long
Chris@148 297 MIDIFileReader::midiBytesToLong(const string& bytes)
Chris@148 298 {
Chris@148 299 if (bytes.length() != 4) {
Chris@148 300 throw MIDIException(tr("Wrong length for long data in MIDI stream (%1, should be %2)").arg(bytes.length()).arg(4));
Chris@148 301 }
Chris@148 302
Chris@148 303 long longRet = ((long)(((MIDIByte)bytes[0]) << 24)) |
Chris@148 304 ((long)(((MIDIByte)bytes[1]) << 16)) |
Chris@148 305 ((long)(((MIDIByte)bytes[2]) << 8)) |
Chris@148 306 ((long)((MIDIByte)(bytes[3])));
Chris@148 307
Chris@148 308 return longRet;
Chris@148 309 }
Chris@148 310
Chris@148 311 int
Chris@148 312 MIDIFileReader::midiBytesToInt(const string& bytes)
Chris@148 313 {
Chris@148 314 if (bytes.length() != 2) {
Chris@148 315 throw MIDIException(tr("Wrong length for int data in MIDI stream (%1, should be %2)").arg(bytes.length()).arg(2));
Chris@148 316 }
Chris@148 317
Chris@148 318 int intRet = ((int)(((MIDIByte)bytes[0]) << 8)) |
Chris@148 319 ((int)(((MIDIByte)bytes[1])));
Chris@148 320 return(intRet);
Chris@148 321 }
Chris@148 322
Chris@148 323
Chris@148 324 // Gets a single byte from the MIDI byte stream. For each track
Chris@148 325 // section we can read only a specified number of bytes held in
Chris@148 326 // m_trackByteCount.
Chris@148 327 //
Chris@148 328 MIDIFileReader::MIDIByte
Chris@148 329 MIDIFileReader::getMIDIByte()
Chris@148 330 {
Chris@148 331 if (!m_midiFile) {
Chris@148 332 throw MIDIException(tr("getMIDIByte called but no MIDI file open"));
Chris@148 333 }
Chris@148 334
Chris@148 335 if (m_midiFile->eof()) {
Chris@148 336 throw MIDIException(tr("End of MIDI file encountered while reading"));
Chris@148 337 }
Chris@148 338
Chris@148 339 if (m_decrementCount && m_trackByteCount <= 0) {
Chris@148 340 throw MIDIException(tr("Attempt to get more bytes than expected on Track"));
Chris@148 341 }
Chris@148 342
Chris@148 343 char byte;
Chris@148 344 if (m_midiFile->read(&byte, 1)) {
Chris@148 345 --m_trackByteCount;
Chris@148 346 return (MIDIByte)byte;
Chris@148 347 }
Chris@148 348
Chris@148 349 throw MIDIException(tr("Attempt to read past MIDI file end"));
Chris@148 350 }
Chris@148 351
Chris@148 352
Chris@148 353 // Gets a specified number of bytes from the MIDI byte stream. For
Chris@148 354 // each track section we can read only a specified number of bytes
Chris@148 355 // held in m_trackByteCount.
Chris@148 356 //
Chris@148 357 string
Chris@148 358 MIDIFileReader::getMIDIBytes(unsigned long numberOfBytes)
Chris@148 359 {
Chris@148 360 if (!m_midiFile) {
Chris@148 361 throw MIDIException(tr("getMIDIBytes called but no MIDI file open"));
Chris@148 362 }
Chris@148 363
Chris@148 364 if (m_midiFile->eof()) {
Chris@148 365 throw MIDIException(tr("End of MIDI file encountered while reading"));
Chris@148 366 }
Chris@148 367
Chris@148 368 if (m_decrementCount && (numberOfBytes > (unsigned long)m_trackByteCount)) {
Chris@148 369 throw MIDIException(tr("Attempt to get more bytes than available on Track (%1, only have %2)").arg(numberOfBytes).arg(m_trackByteCount));
Chris@148 370 }
Chris@148 371
Chris@148 372 string stringRet;
Chris@148 373 char fileMIDIByte;
Chris@148 374
Chris@148 375 while (stringRet.length() < numberOfBytes &&
Chris@148 376 m_midiFile->read(&fileMIDIByte, 1)) {
Chris@148 377 stringRet += fileMIDIByte;
Chris@148 378 }
Chris@148 379
Chris@148 380 // if we've reached the end of file without fulfilling the
Chris@148 381 // quota then panic as our parsing has performed incorrectly
Chris@148 382 //
Chris@148 383 if (stringRet.length() < numberOfBytes) {
Chris@148 384 stringRet = "";
Chris@148 385 throw MIDIException(tr("Attempt to read past MIDI file end"));
Chris@148 386 }
Chris@148 387
Chris@148 388 // decrement the byte count
Chris@148 389 if (m_decrementCount)
Chris@148 390 m_trackByteCount -= stringRet.length();
Chris@148 391
Chris@148 392 return stringRet;
Chris@148 393 }
Chris@148 394
Chris@148 395
Chris@148 396 // Get a long number of variable length from the MIDI byte stream.
Chris@148 397 //
Chris@148 398 long
Chris@148 399 MIDIFileReader::getNumberFromMIDIBytes(int firstByte)
Chris@148 400 {
Chris@148 401 if (!m_midiFile) {
Chris@148 402 throw MIDIException(tr("getNumberFromMIDIBytes called but no MIDI file open"));
Chris@148 403 }
Chris@148 404
Chris@148 405 long longRet = 0;
Chris@148 406 MIDIByte midiByte;
Chris@148 407
Chris@148 408 if (firstByte >= 0) {
Chris@148 409 midiByte = (MIDIByte)firstByte;
Chris@148 410 } else if (m_midiFile->eof()) {
Chris@148 411 return longRet;
Chris@148 412 } else {
Chris@148 413 midiByte = getMIDIByte();
Chris@148 414 }
Chris@148 415
Chris@148 416 longRet = midiByte;
Chris@148 417 if (midiByte & 0x80) {
Chris@148 418 longRet &= 0x7F;
Chris@148 419 do {
Chris@148 420 midiByte = getMIDIByte();
Chris@148 421 longRet = (longRet << 7) + (midiByte & 0x7F);
Chris@148 422 } while (!m_midiFile->eof() && (midiByte & 0x80));
Chris@148 423 }
Chris@148 424
Chris@148 425 return longRet;
Chris@148 426 }
Chris@148 427
Chris@148 428
Chris@148 429 // Seek to the next track in the midi file and set the number
Chris@148 430 // of bytes to be read in the counter m_trackByteCount.
Chris@148 431 //
Chris@148 432 bool
Chris@148 433 MIDIFileReader::skipToNextTrack()
Chris@148 434 {
Chris@148 435 if (!m_midiFile) {
Chris@148 436 throw MIDIException(tr("skipToNextTrack called but no MIDI file open"));
Chris@148 437 }
Chris@148 438
Chris@148 439 string buffer, buffer2;
Chris@148 440 m_trackByteCount = -1;
Chris@148 441 m_decrementCount = false;
Chris@148 442
Chris@148 443 while (!m_midiFile->eof() && (m_decrementCount == false)) {
Chris@148 444 buffer = getMIDIBytes(4);
Chris@148 445 if (buffer.compare(0, 4, MIDI_TRACK_HEADER) == 0) {
Chris@148 446 m_trackByteCount = midiBytesToLong(getMIDIBytes(4));
Chris@148 447 m_decrementCount = true;
Chris@148 448 }
Chris@148 449 }
Chris@148 450
Chris@148 451 if (m_trackByteCount == -1) { // we haven't found a track
Chris@148 452 return false;
Chris@148 453 } else {
Chris@148 454 return true;
Chris@148 455 }
Chris@148 456 }
Chris@148 457
Chris@148 458
Chris@148 459 // Read in a MIDI file. The parsing process throws exceptions back up
Chris@148 460 // here if we run into trouble which we can then pass back out to
Chris@148 461 // whoever called us using a nice bool.
Chris@148 462 //
Chris@148 463 bool
Chris@148 464 MIDIFileReader::parseFile()
Chris@148 465 {
Chris@148 466 m_error = "";
Chris@148 467
Chris@148 468 #ifdef MIDI_DEBUG
Chris@148 469 cerr << "MIDIFileReader::open() : fileName = " << m_fileName.c_str() << endl;
Chris@148 470 #endif
Chris@148 471
Chris@148 472 // Open the file
Chris@148 473 m_midiFile = new ifstream(m_path.toLocal8Bit().data(),
Chris@148 474 ios::in | ios::binary);
Chris@148 475
Chris@148 476 if (!*m_midiFile) {
Chris@148 477 m_error = "File not found or not readable.";
Chris@148 478 m_format = MIDI_FILE_BAD_FORMAT;
Chris@148 479 delete m_midiFile;
Chris@148 480 return false;
Chris@148 481 }
Chris@148 482
Chris@148 483 bool retval = false;
Chris@148 484
Chris@148 485 try {
Chris@148 486
Chris@148 487 // Set file size so we can count it off
Chris@148 488 //
Chris@148 489 m_midiFile->seekg(0, ios::end);
Chris@148 490 m_fileSize = m_midiFile->tellg();
Chris@148 491 m_midiFile->seekg(0, ios::beg);
Chris@148 492
Chris@148 493 // Parse the MIDI header first. The first 14 bytes of the file.
Chris@148 494 if (!parseHeader(getMIDIBytes(14))) {
Chris@148 495 m_format = MIDI_FILE_BAD_FORMAT;
Chris@148 496 m_error = "Not a MIDI file.";
Chris@148 497 goto done;
Chris@148 498 }
Chris@148 499
Chris@148 500 unsigned int i = 0;
Chris@148 501
Chris@148 502 for (unsigned int j = 0; j < m_numberOfTracks; ++j) {
Chris@148 503
Chris@148 504 #ifdef MIDI_DEBUG
Chris@148 505 cerr << "Parsing Track " << j << endl;
Chris@148 506 #endif
Chris@148 507
Chris@148 508 if (!skipToNextTrack()) {
Chris@148 509 #ifdef MIDI_DEBUG
Chris@148 510 cerr << "Couldn't find Track " << j << endl;
Chris@148 511 #endif
Chris@148 512 m_error = "File corrupted or in non-standard format?";
Chris@148 513 m_format = MIDI_FILE_BAD_FORMAT;
Chris@148 514 goto done;
Chris@148 515 }
Chris@148 516
Chris@148 517 #ifdef MIDI_DEBUG
Chris@148 518 cerr << "Track has " << m_trackByteCount << " bytes" << endl;
Chris@148 519 #endif
Chris@148 520
Chris@148 521 // Run through the events taking them into our internal
Chris@148 522 // representation.
Chris@148 523 if (!parseTrack(i)) {
Chris@148 524 #ifdef MIDI_DEBUG
Chris@148 525 cerr << "Track " << j << " parsing failed" << endl;
Chris@148 526 #endif
Chris@148 527 m_error = "File corrupted or in non-standard format?";
Chris@148 528 m_format = MIDI_FILE_BAD_FORMAT;
Chris@148 529 goto done;
Chris@148 530 }
Chris@148 531
Chris@148 532 ++i; // j is the source track number, i the destination
Chris@148 533 }
Chris@148 534
Chris@148 535 m_numberOfTracks = i;
Chris@148 536 retval = true;
Chris@148 537
Chris@148 538 } catch (MIDIException e) {
Chris@148 539
Chris@148 540 cerr << "MIDIFileReader::open() - caught exception - " << e.what() << endl;
Chris@148 541 m_error = e.what();
Chris@148 542 }
Chris@148 543
Chris@148 544 done:
Chris@148 545 m_midiFile->close();
Chris@148 546 delete m_midiFile;
Chris@148 547
Chris@148 548 for (unsigned int track = 0; track < m_numberOfTracks; ++track) {
Chris@148 549
Chris@148 550 // Convert the deltaTime to an absolute time since the track
Chris@148 551 // start. The addTime method returns the sum of the current
Chris@148 552 // MIDI Event delta time plus the argument.
Chris@148 553
Chris@148 554 unsigned long acc = 0;
Chris@148 555
Chris@148 556 for (MIDITrack::iterator i = m_midiComposition[track].begin();
Chris@148 557 i != m_midiComposition[track].end(); ++i) {
Chris@148 558 acc = (*i)->addTime(acc);
Chris@148 559 }
Chris@148 560
Chris@148 561 if (consolidateNoteOffEvents(track)) { // returns true if some notes exist
Chris@148 562 m_loadableTracks.insert(track);
Chris@148 563 }
Chris@148 564 }
Chris@148 565
Chris@148 566 for (unsigned int track = 0; track < m_numberOfTracks; ++track) {
Chris@148 567 updateTempoMap(track);
Chris@148 568 }
Chris@148 569
Chris@148 570 calculateTempoTimestamps();
Chris@148 571
Chris@148 572 return retval;
Chris@148 573 }
Chris@148 574
Chris@148 575 // Parse and ensure the MIDI Header is legitimate
Chris@148 576 //
Chris@148 577 bool
Chris@148 578 MIDIFileReader::parseHeader(const string &midiHeader)
Chris@148 579 {
Chris@148 580 if (midiHeader.size() < 14) {
Chris@148 581 #ifdef MIDI_DEBUG
Chris@148 582 cerr << "MIDIFileReader::parseHeader() - file header undersized" << endl;
Chris@148 583 #endif
Chris@148 584 return false;
Chris@148 585 }
Chris@148 586
Chris@148 587 if (midiHeader.compare(0, 4, MIDI_FILE_HEADER) != 0) {
Chris@148 588 #ifdef MIDI_DEBUG
Chris@148 589 cerr << "MIDIFileReader::parseHeader()"
Chris@148 590 << "- file header not found or malformed"
Chris@148 591 << endl;
Chris@148 592 #endif
Chris@148 593 return false;
Chris@148 594 }
Chris@148 595
Chris@148 596 if (midiBytesToLong(midiHeader.substr(4,4)) != 6L) {
Chris@148 597 #ifdef MIDI_DEBUG
Chris@148 598 cerr << "MIDIFileReader::parseHeader()"
Chris@148 599 << " - header length incorrect"
Chris@148 600 << endl;
Chris@148 601 #endif
Chris@148 602 return false;
Chris@148 603 }
Chris@148 604
Chris@148 605 m_format = (MIDIFileFormatType) midiBytesToInt(midiHeader.substr(8,2));
Chris@148 606 m_numberOfTracks = midiBytesToInt(midiHeader.substr(10,2));
Chris@148 607 m_timingDivision = midiBytesToInt(midiHeader.substr(12,2));
Chris@148 608
Chris@148 609 if (m_format == MIDI_SEQUENTIAL_TRACK_FILE) {
Chris@148 610 #ifdef MIDI_DEBUG
Chris@148 611 cerr << "MIDIFileReader::parseHeader()"
Chris@148 612 << "- can't load sequential track file"
Chris@148 613 << endl;
Chris@148 614 #endif
Chris@148 615 return false;
Chris@148 616 }
Chris@148 617
Chris@148 618 #ifdef MIDI_DEBUG
Chris@148 619 if (m_timingDivision < 0) {
Chris@148 620 cerr << "MIDIFileReader::parseHeader()"
Chris@148 621 << " - file uses SMPTE timing"
Chris@148 622 << endl;
Chris@148 623 }
Chris@148 624 #endif
Chris@148 625
Chris@148 626 return true;
Chris@148 627 }
Chris@148 628
Chris@148 629 // Extract the contents from a MIDI file track and places it into
Chris@148 630 // our local map of MIDI events.
Chris@148 631 //
Chris@148 632 bool
Chris@148 633 MIDIFileReader::parseTrack(unsigned int &lastTrackNum)
Chris@148 634 {
Chris@148 635 MIDIByte midiByte, metaEventCode, data1, data2;
Chris@148 636 MIDIByte eventCode = 0x80;
Chris@148 637 string metaMessage;
Chris@148 638 unsigned int messageLength;
Chris@148 639 unsigned long deltaTime;
Chris@148 640 unsigned long accumulatedTime = 0;
Chris@148 641
Chris@148 642 // The trackNum passed in to this method is the default track for
Chris@148 643 // all events provided they're all on the same channel. If we find
Chris@148 644 // events on more than one channel, we increment trackNum and record
Chris@148 645 // the mapping from channel to trackNum in this channelTrackMap.
Chris@148 646 // We then return the new trackNum by reference so the calling
Chris@148 647 // method knows we've got more tracks than expected.
Chris@148 648
Chris@148 649 // This would be a vector<unsigned int> but we need -1 to indicate
Chris@148 650 // "not yet used"
Chris@148 651 vector<int> channelTrackMap(16, -1);
Chris@148 652
Chris@148 653 // This is used to store the last absolute time found on each track,
Chris@148 654 // allowing us to modify delta-times correctly when separating events
Chris@148 655 // out from one to multiple tracks
Chris@148 656 //
Chris@148 657 map<int, unsigned long> trackTimeMap;
Chris@148 658
Chris@148 659 // Meta-events don't have a channel, so we place them in a fixed
Chris@148 660 // track number instead
Chris@148 661 unsigned int metaTrack = lastTrackNum;
Chris@148 662
Chris@148 663 // Remember the last non-meta status byte (-1 if we haven't seen one)
Chris@148 664 int runningStatus = -1;
Chris@148 665
Chris@148 666 bool firstTrack = true;
Chris@148 667
Chris@148 668 while (!m_midiFile->eof() && (m_trackByteCount > 0)) {
Chris@148 669
Chris@148 670 if (eventCode < 0x80) {
Chris@148 671 #ifdef MIDI_DEBUG
Chris@148 672 cerr << "WARNING: Invalid event code " << eventCode
Chris@148 673 << " in MIDI file" << endl;
Chris@148 674 #endif
Chris@148 675 throw MIDIException(tr("Invalid event code %1 found").arg(int(eventCode)));
Chris@148 676 }
Chris@148 677
Chris@148 678 deltaTime = getNumberFromMIDIBytes();
Chris@148 679
Chris@148 680 #ifdef MIDI_DEBUG
Chris@148 681 cerr << "read delta time " << deltaTime << endl;
Chris@148 682 #endif
Chris@148 683
Chris@148 684 // Get a single byte
Chris@148 685 midiByte = getMIDIByte();
Chris@148 686
Chris@148 687 if (!(midiByte & MIDI_STATUS_BYTE_MASK)) {
Chris@148 688
Chris@148 689 if (runningStatus < 0) {
Chris@148 690 throw MIDIException(tr("Running status used for first event in track"));
Chris@148 691 }
Chris@148 692
Chris@148 693 eventCode = (MIDIByte)runningStatus;
Chris@148 694 data1 = midiByte;
Chris@148 695
Chris@148 696 #ifdef MIDI_DEBUG
Chris@148 697 cerr << "using running status (byte " << int(midiByte) << " found)" << endl;
Chris@148 698 #endif
Chris@148 699 } else {
Chris@148 700 #ifdef MIDI_DEBUG
Chris@148 701 cerr << "have new event code " << int(midiByte) << endl;
Chris@148 702 #endif
Chris@148 703 eventCode = midiByte;
Chris@148 704 data1 = getMIDIByte();
Chris@148 705 }
Chris@148 706
Chris@148 707 if (eventCode == MIDI_FILE_META_EVENT) {
Chris@148 708
Chris@148 709 metaEventCode = data1;
Chris@148 710 messageLength = getNumberFromMIDIBytes();
Chris@148 711
Chris@148 712 //#ifdef MIDI_DEBUG
Chris@148 713 cerr << "Meta event of type " << int(metaEventCode) << " and " << messageLength << " bytes found, putting on track " << metaTrack << endl;
Chris@148 714 //#endif
Chris@148 715 metaMessage = getMIDIBytes(messageLength);
Chris@148 716
Chris@148 717 long gap = accumulatedTime - trackTimeMap[metaTrack];
Chris@148 718 accumulatedTime += deltaTime;
Chris@148 719 deltaTime += gap;
Chris@148 720 trackTimeMap[metaTrack] = accumulatedTime;
Chris@148 721
Chris@148 722 MIDIEvent *e = new MIDIEvent(deltaTime,
Chris@148 723 MIDI_FILE_META_EVENT,
Chris@148 724 metaEventCode,
Chris@148 725 metaMessage);
Chris@148 726
Chris@148 727 m_midiComposition[metaTrack].push_back(e);
Chris@148 728
Chris@148 729 if (metaEventCode == MIDI_TRACK_NAME) {
Chris@148 730 m_trackNames[metaTrack] = metaMessage.c_str();
Chris@148 731 }
Chris@148 732
Chris@148 733 } else { // non-meta events
Chris@148 734
Chris@148 735 runningStatus = eventCode;
Chris@148 736
Chris@148 737 MIDIEvent *midiEvent;
Chris@148 738
Chris@148 739 int channel = (eventCode & MIDI_CHANNEL_NUM_MASK);
Chris@148 740 if (channelTrackMap[channel] == -1) {
Chris@148 741 if (!firstTrack) ++lastTrackNum;
Chris@148 742 else firstTrack = false;
Chris@148 743 channelTrackMap[channel] = lastTrackNum;
Chris@148 744 }
Chris@148 745
Chris@148 746 unsigned int trackNum = channelTrackMap[channel];
Chris@148 747
Chris@148 748 // accumulatedTime is abs time of last event on any track;
Chris@148 749 // trackTimeMap[trackNum] is that of last event on this track
Chris@148 750
Chris@148 751 long gap = accumulatedTime - trackTimeMap[trackNum];
Chris@148 752 accumulatedTime += deltaTime;
Chris@148 753 deltaTime += gap;
Chris@148 754 trackTimeMap[trackNum] = accumulatedTime;
Chris@148 755
Chris@148 756 switch (eventCode & MIDI_MESSAGE_TYPE_MASK) {
Chris@148 757
Chris@148 758 case MIDI_NOTE_ON:
Chris@148 759 case MIDI_NOTE_OFF:
Chris@148 760 case MIDI_POLY_AFTERTOUCH:
Chris@148 761 case MIDI_CTRL_CHANGE:
Chris@148 762 data2 = getMIDIByte();
Chris@148 763
Chris@148 764 // create and store our event
Chris@148 765 midiEvent = new MIDIEvent(deltaTime, eventCode, data1, data2);
Chris@148 766
Chris@148 767 /*
Chris@148 768 cerr << "MIDI event for channel " << channel << " (track "
Chris@148 769 << trackNum << ")" << endl;
Chris@148 770 midiEvent->print();
Chris@148 771 */
Chris@148 772
Chris@148 773
Chris@148 774 m_midiComposition[trackNum].push_back(midiEvent);
Chris@148 775
Chris@148 776 if (midiEvent->getChannelNumber() == MIDI_PERCUSSION_CHANNEL) {
Chris@148 777 m_percussionTracks.insert(trackNum);
Chris@148 778 }
Chris@148 779
Chris@148 780 break;
Chris@148 781
Chris@148 782 case MIDI_PITCH_BEND:
Chris@148 783 data2 = getMIDIByte();
Chris@148 784
Chris@148 785 // create and store our event
Chris@148 786 midiEvent = new MIDIEvent(deltaTime, eventCode, data1, data2);
Chris@148 787 m_midiComposition[trackNum].push_back(midiEvent);
Chris@148 788 break;
Chris@148 789
Chris@148 790 case MIDI_PROG_CHANGE:
Chris@148 791 case MIDI_CHNL_AFTERTOUCH:
Chris@148 792 // create and store our event
Chris@148 793 midiEvent = new MIDIEvent(deltaTime, eventCode, data1);
Chris@148 794 m_midiComposition[trackNum].push_back(midiEvent);
Chris@148 795 break;
Chris@148 796
Chris@148 797 case MIDI_SYSTEM_EXCLUSIVE:
Chris@148 798 messageLength = getNumberFromMIDIBytes(data1);
Chris@148 799
Chris@148 800 #ifdef MIDI_DEBUG
Chris@148 801 cerr << "SysEx of " << messageLength << " bytes found" << endl;
Chris@148 802 #endif
Chris@148 803
Chris@148 804 metaMessage= getMIDIBytes(messageLength);
Chris@148 805
Chris@148 806 if (MIDIByte(metaMessage[metaMessage.length() - 1]) !=
Chris@148 807 MIDI_END_OF_EXCLUSIVE)
Chris@148 808 {
Chris@148 809 #ifdef MIDI_DEBUG
Chris@148 810 cerr << "MIDIFileReader::parseTrack() - "
Chris@148 811 << "malformed or unsupported SysEx type"
Chris@148 812 << endl;
Chris@148 813 #endif
Chris@148 814 continue;
Chris@148 815 }
Chris@148 816
Chris@148 817 // chop off the EOX
Chris@148 818 // length fixed by Pedro Lopez-Cabanillas (20030523)
Chris@148 819 //
Chris@148 820 metaMessage = metaMessage.substr(0, metaMessage.length()-1);
Chris@148 821
Chris@148 822 midiEvent = new MIDIEvent(deltaTime,
Chris@148 823 MIDI_SYSTEM_EXCLUSIVE,
Chris@148 824 metaMessage);
Chris@148 825 m_midiComposition[trackNum].push_back(midiEvent);
Chris@148 826 break;
Chris@148 827
Chris@148 828 case MIDI_END_OF_EXCLUSIVE:
Chris@148 829 #ifdef MIDI_DEBUG
Chris@148 830 cerr << "MIDIFileReader::parseTrack() - "
Chris@148 831 << "Found a stray MIDI_END_OF_EXCLUSIVE" << endl;
Chris@148 832 #endif
Chris@148 833 break;
Chris@148 834
Chris@148 835 default:
Chris@148 836 #ifdef MIDI_DEBUG
Chris@148 837 cerr << "MIDIFileReader::parseTrack()"
Chris@148 838 << " - Unsupported MIDI Event Code: "
Chris@148 839 << (int)eventCode << endl;
Chris@148 840 #endif
Chris@148 841 break;
Chris@148 842 }
Chris@148 843 }
Chris@148 844 }
Chris@148 845
Chris@148 846 if (lastTrackNum > metaTrack) {
Chris@148 847 for (unsigned int track = metaTrack + 1; track <= lastTrackNum; ++track) {
Chris@148 848 m_trackNames[track] = QString("%1 <%2>")
Chris@148 849 .arg(m_trackNames[metaTrack]).arg(track - metaTrack + 1);
Chris@148 850 }
Chris@148 851 }
Chris@148 852
Chris@148 853 return true;
Chris@148 854 }
Chris@148 855
Chris@148 856 // Delete dead NOTE OFF and NOTE ON/Zero Velocity Events after
Chris@148 857 // reading them and modifying their relevant NOTE ONs. Return true
Chris@148 858 // if there are some notes in this track.
Chris@148 859 //
Chris@148 860 bool
Chris@148 861 MIDIFileReader::consolidateNoteOffEvents(unsigned int track)
Chris@148 862 {
Chris@148 863 bool notesOnTrack = false;
Chris@148 864 bool noteOffFound;
Chris@148 865
Chris@148 866 for (MIDITrack::iterator i = m_midiComposition[track].begin();
Chris@148 867 i != m_midiComposition[track].end(); i++) {
Chris@148 868
Chris@148 869 if ((*i)->getMessageType() == MIDI_NOTE_ON && (*i)->getVelocity() > 0) {
Chris@148 870
Chris@148 871 notesOnTrack = true;
Chris@148 872 noteOffFound = false;
Chris@148 873
Chris@148 874 for (MIDITrack::iterator j = i;
Chris@148 875 j != m_midiComposition[track].end(); j++) {
Chris@148 876
Chris@148 877 if (((*j)->getChannelNumber() == (*i)->getChannelNumber()) &&
Chris@148 878 ((*j)->getPitch() == (*i)->getPitch()) &&
Chris@148 879 ((*j)->getMessageType() == MIDI_NOTE_OFF ||
Chris@148 880 ((*j)->getMessageType() == MIDI_NOTE_ON &&
Chris@148 881 (*j)->getVelocity() == 0x00))) {
Chris@148 882
Chris@148 883 (*i)->setDuration((*j)->getTime() - (*i)->getTime());
Chris@148 884
Chris@148 885 delete *j;
Chris@148 886 m_midiComposition[track].erase(j);
Chris@148 887
Chris@148 888 noteOffFound = true;
Chris@148 889 break;
Chris@148 890 }
Chris@148 891 }
Chris@148 892
Chris@148 893 // If no matching NOTE OFF has been found then set
Chris@148 894 // Event duration to length of track
Chris@148 895 //
Chris@148 896 if (!noteOffFound) {
Chris@148 897 MIDITrack::iterator j = m_midiComposition[track].end();
Chris@148 898 --j;
Chris@148 899 (*i)->setDuration((*j)->getTime() - (*i)->getTime());
Chris@148 900 }
Chris@148 901 }
Chris@148 902 }
Chris@148 903
Chris@148 904 return notesOnTrack;
Chris@148 905 }
Chris@148 906
Chris@148 907 // Add any tempo events found in the given track to the global tempo map.
Chris@148 908 //
Chris@148 909 void
Chris@148 910 MIDIFileReader::updateTempoMap(unsigned int track)
Chris@148 911 {
Chris@148 912 std::cerr << "updateTempoMap for track " << track << " (" << m_midiComposition[track].size() << " events)" << std::endl;
Chris@148 913
Chris@148 914 for (MIDITrack::iterator i = m_midiComposition[track].begin();
Chris@148 915 i != m_midiComposition[track].end(); ++i) {
Chris@148 916
Chris@148 917 if ((*i)->isMeta() &&
Chris@148 918 (*i)->getMetaEventCode() == MIDI_SET_TEMPO) {
Chris@148 919
Chris@148 920 MIDIByte m0 = (*i)->getMetaMessage()[0];
Chris@148 921 MIDIByte m1 = (*i)->getMetaMessage()[1];
Chris@148 922 MIDIByte m2 = (*i)->getMetaMessage()[2];
Chris@148 923
Chris@148 924 long tempo = (((m0 << 8) + m1) << 8) + m2;
Chris@148 925
Chris@148 926 std::cerr << "updateTempoMap: have tempo, it's " << tempo << " at " << (*i)->getTime() << std::endl;
Chris@148 927
Chris@148 928 if (tempo != 0) {
Chris@148 929 double qpm = 60000000.0 / double(tempo);
Chris@148 930 m_tempoMap[(*i)->getTime()] =
Chris@148 931 TempoChange(RealTime::zeroTime, qpm);
Chris@148 932 }
Chris@148 933 }
Chris@148 934 }
Chris@148 935 }
Chris@148 936
Chris@148 937 void
Chris@148 938 MIDIFileReader::calculateTempoTimestamps()
Chris@148 939 {
Chris@148 940 unsigned long lastMIDITime = 0;
Chris@148 941 RealTime lastRealTime = RealTime::zeroTime;
Chris@148 942 double tempo = 120.0;
Chris@148 943 int td = m_timingDivision;
Chris@148 944 if (td == 0) td = 96;
Chris@148 945
Chris@148 946 for (TempoMap::iterator i = m_tempoMap.begin(); i != m_tempoMap.end(); ++i) {
Chris@148 947
Chris@148 948 unsigned long mtime = i->first;
Chris@148 949 unsigned long melapsed = mtime - lastMIDITime;
Chris@148 950 double quarters = double(melapsed) / double(td);
Chris@148 951 double seconds = (60.0 * quarters) / tempo;
Chris@148 952
Chris@148 953 RealTime t = lastRealTime + RealTime::fromSeconds(seconds);
Chris@148 954
Chris@148 955 i->second.first = t;
Chris@148 956
Chris@148 957 lastRealTime = t;
Chris@148 958 lastMIDITime = mtime;
Chris@148 959 tempo = i->second.second;
Chris@148 960 }
Chris@148 961 }
Chris@148 962
Chris@148 963 RealTime
Chris@148 964 MIDIFileReader::getTimeForMIDITime(unsigned long midiTime) const
Chris@148 965 {
Chris@148 966 unsigned long tempoMIDITime = 0;
Chris@148 967 RealTime tempoRealTime = RealTime::zeroTime;
Chris@148 968 double tempo = 120.0;
Chris@148 969
Chris@148 970 TempoMap::const_iterator i = m_tempoMap.lower_bound(midiTime);
Chris@148 971 if (i != m_tempoMap.begin()) {
Chris@148 972 --i;
Chris@148 973 tempoMIDITime = i->first;
Chris@148 974 tempoRealTime = i->second.first;
Chris@148 975 tempo = i->second.second;
Chris@148 976 }
Chris@148 977
Chris@148 978 int td = m_timingDivision;
Chris@148 979 if (td == 0) td = 96;
Chris@148 980
Chris@148 981 unsigned long melapsed = midiTime - tempoMIDITime;
Chris@148 982 double quarters = double(melapsed) / double(td);
Chris@148 983 double seconds = (60.0 * quarters) / tempo;
Chris@148 984
Chris@148 985 /*
Chris@148 986 std::cerr << "MIDIFileReader::getTimeForMIDITime(" << midiTime << ")"
Chris@148 987 << std::endl;
Chris@148 988 std::cerr << "timing division = " << td << std::endl;
Chris@148 989 std::cerr << "nearest tempo event (of " << m_tempoMap.size() << ") is at " << tempoMIDITime << " ("
Chris@148 990 << tempoRealTime << ")" << std::endl;
Chris@148 991 std::cerr << "quarters since then = " << quarters << std::endl;
Chris@148 992 std::cerr << "tempo = " << tempo << " quarters per minute" << std::endl;
Chris@148 993 std::cerr << "seconds since then = " << seconds << std::endl;
Chris@148 994 std::cerr << "resulting time = " << (tempoRealTime + RealTime::fromSeconds(seconds)) << std::endl;
Chris@148 995 */
Chris@148 996
Chris@148 997 return tempoRealTime + RealTime::fromSeconds(seconds);
Chris@148 998 }
Chris@148 999
Chris@148 1000 Model *
Chris@148 1001 MIDIFileReader::load() const
Chris@148 1002 {
Chris@148 1003 if (!isOK()) return 0;
Chris@148 1004
Chris@148 1005 if (m_loadableTracks.empty()) {
Chris@148 1006 QMessageBox::critical(0, tr("No notes in MIDI file"),
Chris@148 1007 tr("MIDI file \"%1\" has no notes in any track")
Chris@148 1008 .arg(m_path));
Chris@148 1009 return 0;
Chris@148 1010 }
Chris@148 1011
Chris@148 1012 std::set<unsigned int> tracksToLoad;
Chris@148 1013
Chris@148 1014 if (m_loadableTracks.size() == 1) {
Chris@148 1015
Chris@148 1016 tracksToLoad.insert(*m_loadableTracks.begin());
Chris@148 1017
Chris@148 1018 } else {
Chris@148 1019
Chris@148 1020 QStringList available;
Chris@148 1021 QString allTracks = tr("Merge all tracks");
Chris@148 1022 QString allNonPercussion = tr("Merge all non-percussion tracks");
Chris@148 1023
Chris@148 1024 int nonTrackItems = 1;
Chris@148 1025
Chris@148 1026 available << allTracks;
Chris@148 1027
Chris@148 1028 if (!m_percussionTracks.empty() &&
Chris@148 1029 (m_percussionTracks.size() < m_loadableTracks.size())) {
Chris@148 1030 available << allNonPercussion;
Chris@148 1031 ++nonTrackItems;
Chris@148 1032 }
Chris@148 1033
Chris@148 1034 for (set<unsigned int>::iterator i = m_loadableTracks.begin();
Chris@148 1035 i != m_loadableTracks.end(); ++i) {
Chris@148 1036
Chris@148 1037 unsigned int trackNo = *i;
Chris@148 1038 QString label;
Chris@148 1039
Chris@148 1040 QString perc;
Chris@148 1041 if (m_percussionTracks.find(trackNo) != m_percussionTracks.end()) {
Chris@148 1042 perc = tr(" - uses GM percussion channel");
Chris@148 1043 }
Chris@148 1044
Chris@148 1045 if (m_trackNames.find(trackNo) != m_trackNames.end()) {
Chris@148 1046 label = tr("Track %1 (%2)%3")
Chris@148 1047 .arg(trackNo).arg(m_trackNames.find(trackNo)->second)
Chris@148 1048 .arg(perc);
Chris@148 1049 } else {
Chris@148 1050 label = tr("Track %1 (untitled)%3").arg(trackNo).arg(perc);
Chris@148 1051 }
Chris@148 1052 available << label;
Chris@148 1053 }
Chris@148 1054
Chris@148 1055 bool ok = false;
Chris@148 1056 QString selected = QInputDialog::getItem
Chris@148 1057 (0, tr("Select track or tracks to import"),
Chris@148 1058 tr("You can only import this file as a single annotation layer,\nbut the file contains more than one track,\nor notes on more than one channel.\n\nPlease select the track or merged tracks you wish to import:"),
Chris@148 1059 available, 0, false, &ok);
Chris@148 1060
Chris@148 1061 if (!ok || selected.isEmpty()) return 0;
Chris@148 1062
Chris@148 1063 if (selected == allTracks || selected == allNonPercussion) {
Chris@148 1064
Chris@148 1065 for (set<unsigned int>::iterator i = m_loadableTracks.begin();
Chris@148 1066 i != m_loadableTracks.end(); ++i) {
Chris@148 1067
Chris@148 1068 if (selected == allTracks ||
Chris@148 1069 m_percussionTracks.find(*i) == m_percussionTracks.end()) {
Chris@148 1070
Chris@148 1071 tracksToLoad.insert(*i);
Chris@148 1072 }
Chris@148 1073 }
Chris@148 1074
Chris@148 1075 } else {
Chris@148 1076
Chris@148 1077 int j = nonTrackItems;
Chris@148 1078
Chris@148 1079 for (set<unsigned int>::iterator i = m_loadableTracks.begin();
Chris@148 1080 i != m_loadableTracks.end(); ++i) {
Chris@148 1081
Chris@148 1082 if (selected == available[j]) {
Chris@148 1083 tracksToLoad.insert(*i);
Chris@148 1084 break;
Chris@148 1085 }
Chris@148 1086
Chris@148 1087 ++j;
Chris@148 1088 }
Chris@148 1089 }
Chris@148 1090 }
Chris@148 1091
Chris@148 1092 if (tracksToLoad.empty()) return 0;
Chris@148 1093
Chris@148 1094 size_t n = tracksToLoad.size(), count = 0;
Chris@148 1095 Model *model = 0;
Chris@148 1096
Chris@148 1097 for (std::set<unsigned int>::iterator i = tracksToLoad.begin();
Chris@148 1098 i != tracksToLoad.end(); ++i) {
Chris@148 1099
Chris@148 1100 int minProgress = (100 * count) / n;
Chris@148 1101 int progressAmount = 100 / n;
Chris@148 1102
Chris@148 1103 model = loadTrack(*i, model, minProgress, progressAmount);
Chris@148 1104
Chris@148 1105 ++count;
Chris@148 1106 }
Chris@148 1107
Chris@148 1108 if (dynamic_cast<NoteModel *>(model)) {
Chris@148 1109 dynamic_cast<NoteModel *>(model)->setCompletion(100);
Chris@148 1110 }
Chris@148 1111
Chris@148 1112 return model;
Chris@148 1113 }
Chris@148 1114
Chris@148 1115 Model *
Chris@148 1116 MIDIFileReader::loadTrack(unsigned int trackToLoad,
Chris@148 1117 Model *existingModel,
Chris@148 1118 int minProgress,
Chris@148 1119 int progressAmount) const
Chris@148 1120 {
Chris@148 1121 if (m_midiComposition.find(trackToLoad) == m_midiComposition.end()) {
Chris@148 1122 return 0;
Chris@148 1123 }
Chris@148 1124
Chris@148 1125 NoteModel *model = 0;
Chris@148 1126
Chris@148 1127 if (existingModel) {
Chris@148 1128 model = dynamic_cast<NoteModel *>(existingModel);
Chris@148 1129 if (!model) {
Chris@148 1130 std::cerr << "WARNING: MIDIFileReader::loadTrack: Existing model given, but it isn't a NoteModel -- ignoring it" << std::endl;
Chris@148 1131 }
Chris@148 1132 }
Chris@148 1133
Chris@148 1134 if (!model) {
Chris@148 1135 model = new NoteModel(m_mainModelSampleRate, 1, 0.0, 0.0, false);
Chris@148 1136 model->setValueQuantization(1.0);
Chris@148 1137 }
Chris@148 1138
Chris@148 1139 const MIDITrack &track = m_midiComposition.find(trackToLoad)->second;
Chris@148 1140
Chris@148 1141 size_t totalEvents = track.size();
Chris@148 1142 size_t count = 0;
Chris@148 1143
Chris@148 1144 bool minorKey = false;
Chris@148 1145 bool sharpKey = true;
Chris@148 1146
Chris@148 1147 for (MIDITrack::const_iterator i = track.begin(); i != track.end(); ++i) {
Chris@148 1148
Chris@148 1149 RealTime rt = getTimeForMIDITime((*i)->getTime());
Chris@148 1150
Chris@148 1151 // We ignore most of these event types for now, though in
Chris@148 1152 // theory some of the text ones could usefully be incorporated
Chris@148 1153
Chris@148 1154 if ((*i)->isMeta()) {
Chris@148 1155
Chris@148 1156 switch((*i)->getMetaEventCode()) {
Chris@148 1157
Chris@148 1158 case MIDI_KEY_SIGNATURE:
Chris@148 1159 minorKey = (int((*i)->getMetaMessage()[1]) != 0);
Chris@148 1160 sharpKey = (int((*i)->getMetaMessage()[0]) >= 0);
Chris@148 1161 break;
Chris@148 1162
Chris@148 1163 case MIDI_TEXT_EVENT:
Chris@148 1164 case MIDI_LYRIC:
Chris@148 1165 case MIDI_TEXT_MARKER:
Chris@148 1166 case MIDI_COPYRIGHT_NOTICE:
Chris@148 1167 case MIDI_TRACK_NAME:
Chris@148 1168 // The text events that we could potentially use
Chris@148 1169 break;
Chris@148 1170
Chris@148 1171 case MIDI_SET_TEMPO:
Chris@148 1172 // Already dealt with in a separate pass previously
Chris@148 1173 break;
Chris@148 1174
Chris@148 1175 case MIDI_TIME_SIGNATURE:
Chris@148 1176 // Not yet!
Chris@148 1177 break;
Chris@148 1178
Chris@148 1179 case MIDI_SEQUENCE_NUMBER:
Chris@148 1180 case MIDI_CHANNEL_PREFIX_OR_PORT:
Chris@148 1181 case MIDI_INSTRUMENT_NAME:
Chris@148 1182 case MIDI_CUE_POINT:
Chris@148 1183 case MIDI_CHANNEL_PREFIX:
Chris@148 1184 case MIDI_SEQUENCER_SPECIFIC:
Chris@148 1185 case MIDI_SMPTE_OFFSET:
Chris@148 1186 default:
Chris@148 1187 break;
Chris@148 1188 }
Chris@148 1189
Chris@148 1190 } else {
Chris@148 1191
Chris@148 1192 switch ((*i)->getMessageType()) {
Chris@148 1193
Chris@148 1194 case MIDI_NOTE_ON:
Chris@148 1195
Chris@148 1196 if ((*i)->getVelocity() == 0) break; // effective note-off
Chris@148 1197 else {
Chris@148 1198 RealTime endRT = getTimeForMIDITime((*i)->getTime() +
Chris@148 1199 (*i)->getDuration());
Chris@148 1200
Chris@148 1201 long startFrame = RealTime::realTime2Frame
Chris@148 1202 (rt, model->getSampleRate());
Chris@148 1203
Chris@148 1204 long endFrame = RealTime::realTime2Frame
Chris@148 1205 (endRT, model->getSampleRate());
Chris@148 1206
Chris@148 1207 QString pitchLabel = Pitch::getPitchLabel((*i)->getPitch(),
Chris@148 1208 0,
Chris@148 1209 !sharpKey);
Chris@148 1210
Chris@148 1211 QString noteLabel = tr("%1 - vel %2")
Chris@148 1212 .arg(pitchLabel).arg(int((*i)->getVelocity()));
Chris@148 1213
Chris@148 1214 Note note(startFrame, (*i)->getPitch(),
Chris@148 1215 endFrame - startFrame, noteLabel);
Chris@148 1216
Chris@148 1217 // std::cerr << "Adding note " << startFrame << "," << (endFrame-startFrame) << " : " << int((*i)->getPitch()) << std::endl;
Chris@148 1218
Chris@148 1219 model->addPoint(note);
Chris@148 1220 break;
Chris@148 1221 }
Chris@148 1222
Chris@148 1223 case MIDI_PITCH_BEND:
Chris@148 1224 // I guess we could make some use of this...
Chris@148 1225 break;
Chris@148 1226
Chris@148 1227 case MIDI_NOTE_OFF:
Chris@148 1228 case MIDI_PROG_CHANGE:
Chris@148 1229 case MIDI_CTRL_CHANGE:
Chris@148 1230 case MIDI_SYSTEM_EXCLUSIVE:
Chris@148 1231 case MIDI_POLY_AFTERTOUCH:
Chris@148 1232 case MIDI_CHNL_AFTERTOUCH:
Chris@148 1233 break;
Chris@148 1234
Chris@148 1235 default:
Chris@148 1236 break;
Chris@148 1237 }
Chris@148 1238 }
Chris@148 1239
Chris@148 1240 model->setCompletion(minProgress +
Chris@148 1241 (count * progressAmount) / totalEvents);
Chris@148 1242 ++count;
Chris@148 1243 }
Chris@148 1244
Chris@148 1245 return model;
Chris@148 1246 }
Chris@148 1247
Chris@148 1248