annotate data/fileio/MIDIFileReader.cpp @ 244:85bf384db35f

* Update to use new vamp-hostsdk instead of vamp-sdk * Make spectrogram adapt its paint block size depending on how long it actually takes to draw * Some thread debugging infrastructure
author Chris Cannam
date Fri, 02 Mar 2007 13:01:41 +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