annotate data/fileio/MIDIFileReader.cpp @ 661:a4faa1840384

* If a FileSource URL won't convert at all in strict mode, try again in tolerant mode (necessary for e.g. filenames with square brackets in them)
author Chris Cannam
date Tue, 19 Oct 2010 21:47:55 +0100
parents eb1b517f5eeb
children 06f13a3b9e9e
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@560 31 #include "data/midi/MIDIEvent.h"
Chris@301 32
Chris@150 33 #include "model/Model.h"
Chris@148 34 #include "base/Pitch.h"
Chris@148 35 #include "base/RealTime.h"
Chris@148 36 #include "model/NoteModel.h"
Chris@148 37
Chris@148 38 #include <QString>
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@301 53 using namespace MIDIConstants;
Chris@301 54
Chris@148 55 //#define MIDI_DEBUG 1
Chris@148 56
Chris@148 57
Chris@148 58 MIDIFileReader::MIDIFileReader(QString path,
Chris@392 59 MIDIFileImportPreferenceAcquirer *acquirer,
Chris@148 60 size_t mainModelSampleRate) :
Chris@613 61 m_smpte(false),
Chris@148 62 m_timingDivision(0),
Chris@613 63 m_fps(0),
Chris@613 64 m_subframes(0),
Chris@148 65 m_format(MIDI_FILE_BAD_FORMAT),
Chris@148 66 m_numberOfTracks(0),
Chris@148 67 m_trackByteCount(0),
Chris@148 68 m_decrementCount(false),
Chris@148 69 m_path(path),
Chris@148 70 m_midiFile(0),
Chris@148 71 m_fileSize(0),
Chris@392 72 m_mainModelSampleRate(mainModelSampleRate),
Chris@392 73 m_acquirer(acquirer)
Chris@148 74 {
Chris@148 75 if (parseFile()) {
Chris@148 76 m_error = "";
Chris@148 77 }
Chris@148 78 }
Chris@148 79
Chris@148 80 MIDIFileReader::~MIDIFileReader()
Chris@148 81 {
Chris@148 82 for (MIDIComposition::iterator i = m_midiComposition.begin();
Chris@148 83 i != m_midiComposition.end(); ++i) {
Chris@148 84
Chris@148 85 for (MIDITrack::iterator j = i->second.begin();
Chris@148 86 j != i->second.end(); ++j) {
Chris@148 87 delete *j;
Chris@148 88 }
Chris@148 89
Chris@148 90 i->second.clear();
Chris@148 91 }
Chris@148 92
Chris@148 93 m_midiComposition.clear();
Chris@148 94 }
Chris@148 95
Chris@148 96 bool
Chris@148 97 MIDIFileReader::isOK() const
Chris@148 98 {
Chris@148 99 return (m_error == "");
Chris@148 100 }
Chris@148 101
Chris@148 102 QString
Chris@148 103 MIDIFileReader::getError() const
Chris@148 104 {
Chris@148 105 return m_error;
Chris@148 106 }
Chris@148 107
Chris@148 108 long
Chris@148 109 MIDIFileReader::midiBytesToLong(const string& bytes)
Chris@148 110 {
Chris@148 111 if (bytes.length() != 4) {
Chris@148 112 throw MIDIException(tr("Wrong length for long data in MIDI stream (%1, should be %2)").arg(bytes.length()).arg(4));
Chris@148 113 }
Chris@148 114
Chris@148 115 long longRet = ((long)(((MIDIByte)bytes[0]) << 24)) |
Chris@148 116 ((long)(((MIDIByte)bytes[1]) << 16)) |
Chris@148 117 ((long)(((MIDIByte)bytes[2]) << 8)) |
Chris@148 118 ((long)((MIDIByte)(bytes[3])));
Chris@148 119
Chris@148 120 return longRet;
Chris@148 121 }
Chris@148 122
Chris@148 123 int
Chris@148 124 MIDIFileReader::midiBytesToInt(const string& bytes)
Chris@148 125 {
Chris@148 126 if (bytes.length() != 2) {
Chris@148 127 throw MIDIException(tr("Wrong length for int data in MIDI stream (%1, should be %2)").arg(bytes.length()).arg(2));
Chris@148 128 }
Chris@148 129
Chris@148 130 int intRet = ((int)(((MIDIByte)bytes[0]) << 8)) |
Chris@148 131 ((int)(((MIDIByte)bytes[1])));
Chris@148 132 return(intRet);
Chris@148 133 }
Chris@148 134
Chris@148 135
Chris@148 136 // Gets a single byte from the MIDI byte stream. For each track
Chris@148 137 // section we can read only a specified number of bytes held in
Chris@148 138 // m_trackByteCount.
Chris@148 139 //
Chris@301 140 MIDIByte
Chris@148 141 MIDIFileReader::getMIDIByte()
Chris@148 142 {
Chris@148 143 if (!m_midiFile) {
Chris@148 144 throw MIDIException(tr("getMIDIByte called but no MIDI file open"));
Chris@148 145 }
Chris@148 146
Chris@148 147 if (m_midiFile->eof()) {
Chris@148 148 throw MIDIException(tr("End of MIDI file encountered while reading"));
Chris@148 149 }
Chris@148 150
Chris@148 151 if (m_decrementCount && m_trackByteCount <= 0) {
Chris@148 152 throw MIDIException(tr("Attempt to get more bytes than expected on Track"));
Chris@148 153 }
Chris@148 154
Chris@148 155 char byte;
Chris@148 156 if (m_midiFile->read(&byte, 1)) {
Chris@148 157 --m_trackByteCount;
Chris@148 158 return (MIDIByte)byte;
Chris@148 159 }
Chris@148 160
Chris@148 161 throw MIDIException(tr("Attempt to read past MIDI file end"));
Chris@148 162 }
Chris@148 163
Chris@148 164
Chris@148 165 // Gets a specified number of bytes from the MIDI byte stream. For
Chris@148 166 // each track section we can read only a specified number of bytes
Chris@148 167 // held in m_trackByteCount.
Chris@148 168 //
Chris@148 169 string
Chris@148 170 MIDIFileReader::getMIDIBytes(unsigned long numberOfBytes)
Chris@148 171 {
Chris@148 172 if (!m_midiFile) {
Chris@148 173 throw MIDIException(tr("getMIDIBytes called but no MIDI file open"));
Chris@148 174 }
Chris@148 175
Chris@148 176 if (m_midiFile->eof()) {
Chris@148 177 throw MIDIException(tr("End of MIDI file encountered while reading"));
Chris@148 178 }
Chris@148 179
Chris@148 180 if (m_decrementCount && (numberOfBytes > (unsigned long)m_trackByteCount)) {
Chris@148 181 throw MIDIException(tr("Attempt to get more bytes than available on Track (%1, only have %2)").arg(numberOfBytes).arg(m_trackByteCount));
Chris@148 182 }
Chris@148 183
Chris@148 184 string stringRet;
Chris@148 185 char fileMIDIByte;
Chris@148 186
Chris@148 187 while (stringRet.length() < numberOfBytes &&
Chris@148 188 m_midiFile->read(&fileMIDIByte, 1)) {
Chris@148 189 stringRet += fileMIDIByte;
Chris@148 190 }
Chris@148 191
Chris@148 192 // if we've reached the end of file without fulfilling the
Chris@148 193 // quota then panic as our parsing has performed incorrectly
Chris@148 194 //
Chris@148 195 if (stringRet.length() < numberOfBytes) {
Chris@148 196 stringRet = "";
Chris@148 197 throw MIDIException(tr("Attempt to read past MIDI file end"));
Chris@148 198 }
Chris@148 199
Chris@148 200 // decrement the byte count
Chris@148 201 if (m_decrementCount)
Chris@148 202 m_trackByteCount -= stringRet.length();
Chris@148 203
Chris@148 204 return stringRet;
Chris@148 205 }
Chris@148 206
Chris@148 207
Chris@148 208 // Get a long number of variable length from the MIDI byte stream.
Chris@148 209 //
Chris@148 210 long
Chris@148 211 MIDIFileReader::getNumberFromMIDIBytes(int firstByte)
Chris@148 212 {
Chris@148 213 if (!m_midiFile) {
Chris@148 214 throw MIDIException(tr("getNumberFromMIDIBytes called but no MIDI file open"));
Chris@148 215 }
Chris@148 216
Chris@148 217 long longRet = 0;
Chris@148 218 MIDIByte midiByte;
Chris@148 219
Chris@148 220 if (firstByte >= 0) {
Chris@148 221 midiByte = (MIDIByte)firstByte;
Chris@148 222 } else if (m_midiFile->eof()) {
Chris@148 223 return longRet;
Chris@148 224 } else {
Chris@148 225 midiByte = getMIDIByte();
Chris@148 226 }
Chris@148 227
Chris@148 228 longRet = midiByte;
Chris@148 229 if (midiByte & 0x80) {
Chris@148 230 longRet &= 0x7F;
Chris@148 231 do {
Chris@148 232 midiByte = getMIDIByte();
Chris@148 233 longRet = (longRet << 7) + (midiByte & 0x7F);
Chris@148 234 } while (!m_midiFile->eof() && (midiByte & 0x80));
Chris@148 235 }
Chris@148 236
Chris@148 237 return longRet;
Chris@148 238 }
Chris@148 239
Chris@148 240
Chris@148 241 // Seek to the next track in the midi file and set the number
Chris@148 242 // of bytes to be read in the counter m_trackByteCount.
Chris@148 243 //
Chris@148 244 bool
Chris@148 245 MIDIFileReader::skipToNextTrack()
Chris@148 246 {
Chris@148 247 if (!m_midiFile) {
Chris@148 248 throw MIDIException(tr("skipToNextTrack called but no MIDI file open"));
Chris@148 249 }
Chris@148 250
Chris@148 251 string buffer, buffer2;
Chris@148 252 m_trackByteCount = -1;
Chris@148 253 m_decrementCount = false;
Chris@148 254
Chris@148 255 while (!m_midiFile->eof() && (m_decrementCount == false)) {
Chris@148 256 buffer = getMIDIBytes(4);
Chris@148 257 if (buffer.compare(0, 4, MIDI_TRACK_HEADER) == 0) {
Chris@148 258 m_trackByteCount = midiBytesToLong(getMIDIBytes(4));
Chris@148 259 m_decrementCount = true;
Chris@148 260 }
Chris@148 261 }
Chris@148 262
Chris@148 263 if (m_trackByteCount == -1) { // we haven't found a track
Chris@148 264 return false;
Chris@148 265 } else {
Chris@148 266 return true;
Chris@148 267 }
Chris@148 268 }
Chris@148 269
Chris@148 270
Chris@148 271 // Read in a MIDI file. The parsing process throws exceptions back up
Chris@148 272 // here if we run into trouble which we can then pass back out to
Chris@148 273 // whoever called us using a nice bool.
Chris@148 274 //
Chris@148 275 bool
Chris@148 276 MIDIFileReader::parseFile()
Chris@148 277 {
Chris@148 278 m_error = "";
Chris@148 279
Chris@148 280 #ifdef MIDI_DEBUG
Chris@148 281 cerr << "MIDIFileReader::open() : fileName = " << m_fileName.c_str() << endl;
Chris@148 282 #endif
Chris@148 283
Chris@148 284 // Open the file
Chris@148 285 m_midiFile = new ifstream(m_path.toLocal8Bit().data(),
Chris@148 286 ios::in | ios::binary);
Chris@148 287
Chris@148 288 if (!*m_midiFile) {
Chris@148 289 m_error = "File not found or not readable.";
Chris@148 290 m_format = MIDI_FILE_BAD_FORMAT;
Chris@148 291 delete m_midiFile;
Chris@301 292 m_midiFile = 0;
Chris@148 293 return false;
Chris@148 294 }
Chris@148 295
Chris@148 296 bool retval = false;
Chris@148 297
Chris@148 298 try {
Chris@148 299
Chris@148 300 // Set file size so we can count it off
Chris@148 301 //
Chris@148 302 m_midiFile->seekg(0, ios::end);
Chris@148 303 m_fileSize = m_midiFile->tellg();
Chris@148 304 m_midiFile->seekg(0, ios::beg);
Chris@148 305
Chris@148 306 // Parse the MIDI header first. The first 14 bytes of the file.
Chris@148 307 if (!parseHeader(getMIDIBytes(14))) {
Chris@148 308 m_format = MIDI_FILE_BAD_FORMAT;
Chris@148 309 m_error = "Not a MIDI file.";
Chris@148 310 goto done;
Chris@148 311 }
Chris@148 312
Chris@148 313 unsigned int i = 0;
Chris@148 314
Chris@148 315 for (unsigned int j = 0; j < m_numberOfTracks; ++j) {
Chris@148 316
Chris@148 317 #ifdef MIDI_DEBUG
Chris@148 318 cerr << "Parsing Track " << j << endl;
Chris@148 319 #endif
Chris@148 320
Chris@148 321 if (!skipToNextTrack()) {
Chris@148 322 #ifdef MIDI_DEBUG
Chris@148 323 cerr << "Couldn't find Track " << j << endl;
Chris@148 324 #endif
Chris@148 325 m_error = "File corrupted or in non-standard format?";
Chris@148 326 m_format = MIDI_FILE_BAD_FORMAT;
Chris@148 327 goto done;
Chris@148 328 }
Chris@148 329
Chris@148 330 #ifdef MIDI_DEBUG
Chris@148 331 cerr << "Track has " << m_trackByteCount << " bytes" << endl;
Chris@148 332 #endif
Chris@148 333
Chris@148 334 // Run through the events taking them into our internal
Chris@148 335 // representation.
Chris@148 336 if (!parseTrack(i)) {
Chris@148 337 #ifdef MIDI_DEBUG
Chris@148 338 cerr << "Track " << j << " parsing failed" << endl;
Chris@148 339 #endif
Chris@148 340 m_error = "File corrupted or in non-standard format?";
Chris@148 341 m_format = MIDI_FILE_BAD_FORMAT;
Chris@148 342 goto done;
Chris@148 343 }
Chris@148 344
Chris@148 345 ++i; // j is the source track number, i the destination
Chris@148 346 }
Chris@148 347
Chris@148 348 m_numberOfTracks = i;
Chris@148 349 retval = true;
Chris@148 350
Chris@148 351 } catch (MIDIException e) {
Chris@148 352
Chris@148 353 cerr << "MIDIFileReader::open() - caught exception - " << e.what() << endl;
Chris@148 354 m_error = e.what();
Chris@148 355 }
Chris@148 356
Chris@148 357 done:
Chris@148 358 m_midiFile->close();
Chris@148 359 delete m_midiFile;
Chris@148 360
Chris@148 361 for (unsigned int track = 0; track < m_numberOfTracks; ++track) {
Chris@148 362
Chris@148 363 // Convert the deltaTime to an absolute time since the track
Chris@148 364 // start. The addTime method returns the sum of the current
Chris@148 365 // MIDI Event delta time plus the argument.
Chris@148 366
Chris@148 367 unsigned long acc = 0;
Chris@148 368
Chris@148 369 for (MIDITrack::iterator i = m_midiComposition[track].begin();
Chris@148 370 i != m_midiComposition[track].end(); ++i) {
Chris@148 371 acc = (*i)->addTime(acc);
Chris@148 372 }
Chris@148 373
Chris@148 374 if (consolidateNoteOffEvents(track)) { // returns true if some notes exist
Chris@148 375 m_loadableTracks.insert(track);
Chris@148 376 }
Chris@148 377 }
Chris@148 378
Chris@148 379 for (unsigned int track = 0; track < m_numberOfTracks; ++track) {
Chris@148 380 updateTempoMap(track);
Chris@148 381 }
Chris@148 382
Chris@148 383 calculateTempoTimestamps();
Chris@148 384
Chris@148 385 return retval;
Chris@148 386 }
Chris@148 387
Chris@148 388 // Parse and ensure the MIDI Header is legitimate
Chris@148 389 //
Chris@148 390 bool
Chris@148 391 MIDIFileReader::parseHeader(const string &midiHeader)
Chris@148 392 {
Chris@148 393 if (midiHeader.size() < 14) {
Chris@148 394 #ifdef MIDI_DEBUG
Chris@148 395 cerr << "MIDIFileReader::parseHeader() - file header undersized" << endl;
Chris@148 396 #endif
Chris@148 397 return false;
Chris@148 398 }
Chris@148 399
Chris@148 400 if (midiHeader.compare(0, 4, MIDI_FILE_HEADER) != 0) {
Chris@148 401 #ifdef MIDI_DEBUG
Chris@148 402 cerr << "MIDIFileReader::parseHeader()"
Chris@148 403 << "- file header not found or malformed"
Chris@148 404 << endl;
Chris@148 405 #endif
Chris@148 406 return false;
Chris@148 407 }
Chris@148 408
Chris@148 409 if (midiBytesToLong(midiHeader.substr(4,4)) != 6L) {
Chris@148 410 #ifdef MIDI_DEBUG
Chris@148 411 cerr << "MIDIFileReader::parseHeader()"
Chris@148 412 << " - header length incorrect"
Chris@148 413 << endl;
Chris@148 414 #endif
Chris@148 415 return false;
Chris@148 416 }
Chris@148 417
Chris@148 418 m_format = (MIDIFileFormatType) midiBytesToInt(midiHeader.substr(8,2));
Chris@148 419 m_numberOfTracks = midiBytesToInt(midiHeader.substr(10,2));
Chris@148 420 m_timingDivision = midiBytesToInt(midiHeader.substr(12,2));
Chris@148 421
Chris@613 422 if (m_timingDivision >= 32768) {
Chris@613 423 m_smpte = true;
Chris@613 424 m_fps = 256 - (m_timingDivision >> 8);
Chris@613 425 m_subframes = (m_timingDivision & 0xff);
Chris@613 426 } else {
Chris@613 427 m_smpte = false;
Chris@148 428 }
Chris@148 429
Chris@148 430 return true;
Chris@148 431 }
Chris@148 432
Chris@148 433 // Extract the contents from a MIDI file track and places it into
Chris@148 434 // our local map of MIDI events.
Chris@148 435 //
Chris@148 436 bool
Chris@148 437 MIDIFileReader::parseTrack(unsigned int &lastTrackNum)
Chris@148 438 {
Chris@148 439 MIDIByte midiByte, metaEventCode, data1, data2;
Chris@148 440 MIDIByte eventCode = 0x80;
Chris@148 441 string metaMessage;
Chris@148 442 unsigned int messageLength;
Chris@148 443 unsigned long deltaTime;
Chris@148 444 unsigned long accumulatedTime = 0;
Chris@148 445
Chris@148 446 // The trackNum passed in to this method is the default track for
Chris@148 447 // all events provided they're all on the same channel. If we find
Chris@148 448 // events on more than one channel, we increment trackNum and record
Chris@148 449 // the mapping from channel to trackNum in this channelTrackMap.
Chris@148 450 // We then return the new trackNum by reference so the calling
Chris@148 451 // method knows we've got more tracks than expected.
Chris@148 452
Chris@148 453 // This would be a vector<unsigned int> but we need -1 to indicate
Chris@148 454 // "not yet used"
Chris@148 455 vector<int> channelTrackMap(16, -1);
Chris@148 456
Chris@148 457 // This is used to store the last absolute time found on each track,
Chris@148 458 // allowing us to modify delta-times correctly when separating events
Chris@148 459 // out from one to multiple tracks
Chris@148 460 //
Chris@148 461 map<int, unsigned long> trackTimeMap;
Chris@148 462
Chris@148 463 // Meta-events don't have a channel, so we place them in a fixed
Chris@148 464 // track number instead
Chris@148 465 unsigned int metaTrack = lastTrackNum;
Chris@148 466
Chris@148 467 // Remember the last non-meta status byte (-1 if we haven't seen one)
Chris@148 468 int runningStatus = -1;
Chris@148 469
Chris@148 470 bool firstTrack = true;
Chris@148 471
Chris@148 472 while (!m_midiFile->eof() && (m_trackByteCount > 0)) {
Chris@148 473
Chris@148 474 if (eventCode < 0x80) {
Chris@148 475 #ifdef MIDI_DEBUG
Chris@148 476 cerr << "WARNING: Invalid event code " << eventCode
Chris@148 477 << " in MIDI file" << endl;
Chris@148 478 #endif
Chris@148 479 throw MIDIException(tr("Invalid event code %1 found").arg(int(eventCode)));
Chris@148 480 }
Chris@148 481
Chris@148 482 deltaTime = getNumberFromMIDIBytes();
Chris@148 483
Chris@148 484 #ifdef MIDI_DEBUG
Chris@148 485 cerr << "read delta time " << deltaTime << endl;
Chris@148 486 #endif
Chris@148 487
Chris@148 488 // Get a single byte
Chris@148 489 midiByte = getMIDIByte();
Chris@148 490
Chris@148 491 if (!(midiByte & MIDI_STATUS_BYTE_MASK)) {
Chris@148 492
Chris@148 493 if (runningStatus < 0) {
Chris@148 494 throw MIDIException(tr("Running status used for first event in track"));
Chris@148 495 }
Chris@148 496
Chris@148 497 eventCode = (MIDIByte)runningStatus;
Chris@148 498 data1 = midiByte;
Chris@148 499
Chris@148 500 #ifdef MIDI_DEBUG
Chris@148 501 cerr << "using running status (byte " << int(midiByte) << " found)" << endl;
Chris@148 502 #endif
Chris@148 503 } else {
Chris@148 504 #ifdef MIDI_DEBUG
Chris@148 505 cerr << "have new event code " << int(midiByte) << endl;
Chris@148 506 #endif
Chris@148 507 eventCode = midiByte;
Chris@148 508 data1 = getMIDIByte();
Chris@148 509 }
Chris@148 510
Chris@148 511 if (eventCode == MIDI_FILE_META_EVENT) {
Chris@148 512
Chris@148 513 metaEventCode = data1;
Chris@148 514 messageLength = getNumberFromMIDIBytes();
Chris@148 515
Chris@148 516 //#ifdef MIDI_DEBUG
Chris@148 517 cerr << "Meta event of type " << int(metaEventCode) << " and " << messageLength << " bytes found, putting on track " << metaTrack << endl;
Chris@148 518 //#endif
Chris@148 519 metaMessage = getMIDIBytes(messageLength);
Chris@148 520
Chris@148 521 long gap = accumulatedTime - trackTimeMap[metaTrack];
Chris@148 522 accumulatedTime += deltaTime;
Chris@148 523 deltaTime += gap;
Chris@148 524 trackTimeMap[metaTrack] = accumulatedTime;
Chris@148 525
Chris@148 526 MIDIEvent *e = new MIDIEvent(deltaTime,
Chris@148 527 MIDI_FILE_META_EVENT,
Chris@148 528 metaEventCode,
Chris@148 529 metaMessage);
Chris@148 530
Chris@148 531 m_midiComposition[metaTrack].push_back(e);
Chris@148 532
Chris@148 533 if (metaEventCode == MIDI_TRACK_NAME) {
Chris@148 534 m_trackNames[metaTrack] = metaMessage.c_str();
Chris@148 535 }
Chris@148 536
Chris@148 537 } else { // non-meta events
Chris@148 538
Chris@148 539 runningStatus = eventCode;
Chris@148 540
Chris@148 541 MIDIEvent *midiEvent;
Chris@148 542
Chris@148 543 int channel = (eventCode & MIDI_CHANNEL_NUM_MASK);
Chris@148 544 if (channelTrackMap[channel] == -1) {
Chris@148 545 if (!firstTrack) ++lastTrackNum;
Chris@148 546 else firstTrack = false;
Chris@148 547 channelTrackMap[channel] = lastTrackNum;
Chris@148 548 }
Chris@148 549
Chris@148 550 unsigned int trackNum = channelTrackMap[channel];
Chris@148 551
Chris@148 552 // accumulatedTime is abs time of last event on any track;
Chris@148 553 // trackTimeMap[trackNum] is that of last event on this track
Chris@148 554
Chris@148 555 long gap = accumulatedTime - trackTimeMap[trackNum];
Chris@148 556 accumulatedTime += deltaTime;
Chris@148 557 deltaTime += gap;
Chris@148 558 trackTimeMap[trackNum] = accumulatedTime;
Chris@148 559
Chris@148 560 switch (eventCode & MIDI_MESSAGE_TYPE_MASK) {
Chris@148 561
Chris@148 562 case MIDI_NOTE_ON:
Chris@148 563 case MIDI_NOTE_OFF:
Chris@148 564 case MIDI_POLY_AFTERTOUCH:
Chris@148 565 case MIDI_CTRL_CHANGE:
Chris@148 566 data2 = getMIDIByte();
Chris@148 567
Chris@148 568 // create and store our event
Chris@148 569 midiEvent = new MIDIEvent(deltaTime, eventCode, data1, data2);
Chris@148 570
Chris@148 571 /*
Chris@148 572 cerr << "MIDI event for channel " << channel << " (track "
Chris@148 573 << trackNum << ")" << endl;
Chris@148 574 midiEvent->print();
Chris@148 575 */
Chris@148 576
Chris@148 577
Chris@148 578 m_midiComposition[trackNum].push_back(midiEvent);
Chris@148 579
Chris@148 580 if (midiEvent->getChannelNumber() == MIDI_PERCUSSION_CHANNEL) {
Chris@148 581 m_percussionTracks.insert(trackNum);
Chris@148 582 }
Chris@148 583
Chris@148 584 break;
Chris@148 585
Chris@148 586 case MIDI_PITCH_BEND:
Chris@148 587 data2 = getMIDIByte();
Chris@148 588
Chris@148 589 // create and store our event
Chris@148 590 midiEvent = new MIDIEvent(deltaTime, eventCode, data1, data2);
Chris@148 591 m_midiComposition[trackNum].push_back(midiEvent);
Chris@148 592 break;
Chris@148 593
Chris@148 594 case MIDI_PROG_CHANGE:
Chris@148 595 case MIDI_CHNL_AFTERTOUCH:
Chris@148 596 // create and store our event
Chris@148 597 midiEvent = new MIDIEvent(deltaTime, eventCode, data1);
Chris@148 598 m_midiComposition[trackNum].push_back(midiEvent);
Chris@148 599 break;
Chris@148 600
Chris@148 601 case MIDI_SYSTEM_EXCLUSIVE:
Chris@148 602 messageLength = getNumberFromMIDIBytes(data1);
Chris@148 603
Chris@148 604 #ifdef MIDI_DEBUG
Chris@148 605 cerr << "SysEx of " << messageLength << " bytes found" << endl;
Chris@148 606 #endif
Chris@148 607
Chris@148 608 metaMessage= getMIDIBytes(messageLength);
Chris@148 609
Chris@148 610 if (MIDIByte(metaMessage[metaMessage.length() - 1]) !=
Chris@148 611 MIDI_END_OF_EXCLUSIVE)
Chris@148 612 {
Chris@148 613 #ifdef MIDI_DEBUG
Chris@148 614 cerr << "MIDIFileReader::parseTrack() - "
Chris@148 615 << "malformed or unsupported SysEx type"
Chris@148 616 << endl;
Chris@148 617 #endif
Chris@148 618 continue;
Chris@148 619 }
Chris@148 620
Chris@148 621 // chop off the EOX
Chris@148 622 // length fixed by Pedro Lopez-Cabanillas (20030523)
Chris@148 623 //
Chris@148 624 metaMessage = metaMessage.substr(0, metaMessage.length()-1);
Chris@148 625
Chris@148 626 midiEvent = new MIDIEvent(deltaTime,
Chris@148 627 MIDI_SYSTEM_EXCLUSIVE,
Chris@148 628 metaMessage);
Chris@148 629 m_midiComposition[trackNum].push_back(midiEvent);
Chris@148 630 break;
Chris@148 631
Chris@148 632 case MIDI_END_OF_EXCLUSIVE:
Chris@148 633 #ifdef MIDI_DEBUG
Chris@148 634 cerr << "MIDIFileReader::parseTrack() - "
Chris@148 635 << "Found a stray MIDI_END_OF_EXCLUSIVE" << endl;
Chris@148 636 #endif
Chris@148 637 break;
Chris@148 638
Chris@148 639 default:
Chris@148 640 #ifdef MIDI_DEBUG
Chris@148 641 cerr << "MIDIFileReader::parseTrack()"
Chris@148 642 << " - Unsupported MIDI Event Code: "
Chris@148 643 << (int)eventCode << endl;
Chris@148 644 #endif
Chris@148 645 break;
Chris@148 646 }
Chris@148 647 }
Chris@148 648 }
Chris@148 649
Chris@148 650 if (lastTrackNum > metaTrack) {
Chris@148 651 for (unsigned int track = metaTrack + 1; track <= lastTrackNum; ++track) {
Chris@148 652 m_trackNames[track] = QString("%1 <%2>")
Chris@148 653 .arg(m_trackNames[metaTrack]).arg(track - metaTrack + 1);
Chris@148 654 }
Chris@148 655 }
Chris@148 656
Chris@148 657 return true;
Chris@148 658 }
Chris@148 659
Chris@148 660 // Delete dead NOTE OFF and NOTE ON/Zero Velocity Events after
Chris@148 661 // reading them and modifying their relevant NOTE ONs. Return true
Chris@148 662 // if there are some notes in this track.
Chris@148 663 //
Chris@148 664 bool
Chris@148 665 MIDIFileReader::consolidateNoteOffEvents(unsigned int track)
Chris@148 666 {
Chris@148 667 bool notesOnTrack = false;
Chris@148 668 bool noteOffFound;
Chris@148 669
Chris@148 670 for (MIDITrack::iterator i = m_midiComposition[track].begin();
Chris@148 671 i != m_midiComposition[track].end(); i++) {
Chris@148 672
Chris@148 673 if ((*i)->getMessageType() == MIDI_NOTE_ON && (*i)->getVelocity() > 0) {
Chris@148 674
Chris@148 675 notesOnTrack = true;
Chris@148 676 noteOffFound = false;
Chris@148 677
Chris@148 678 for (MIDITrack::iterator j = i;
Chris@148 679 j != m_midiComposition[track].end(); j++) {
Chris@148 680
Chris@148 681 if (((*j)->getChannelNumber() == (*i)->getChannelNumber()) &&
Chris@148 682 ((*j)->getPitch() == (*i)->getPitch()) &&
Chris@148 683 ((*j)->getMessageType() == MIDI_NOTE_OFF ||
Chris@148 684 ((*j)->getMessageType() == MIDI_NOTE_ON &&
Chris@148 685 (*j)->getVelocity() == 0x00))) {
Chris@148 686
Chris@148 687 (*i)->setDuration((*j)->getTime() - (*i)->getTime());
Chris@148 688
Chris@148 689 delete *j;
Chris@148 690 m_midiComposition[track].erase(j);
Chris@148 691
Chris@148 692 noteOffFound = true;
Chris@148 693 break;
Chris@148 694 }
Chris@148 695 }
Chris@148 696
Chris@148 697 // If no matching NOTE OFF has been found then set
Chris@148 698 // Event duration to length of track
Chris@148 699 //
Chris@148 700 if (!noteOffFound) {
Chris@148 701 MIDITrack::iterator j = m_midiComposition[track].end();
Chris@148 702 --j;
Chris@613 703 (*i)->setDuration((*j)->getTime() - (*i)->getTime());
Chris@148 704 }
Chris@148 705 }
Chris@148 706 }
Chris@148 707
Chris@148 708 return notesOnTrack;
Chris@148 709 }
Chris@148 710
Chris@148 711 // Add any tempo events found in the given track to the global tempo map.
Chris@148 712 //
Chris@148 713 void
Chris@148 714 MIDIFileReader::updateTempoMap(unsigned int track)
Chris@148 715 {
Chris@148 716 std::cerr << "updateTempoMap for track " << track << " (" << m_midiComposition[track].size() << " events)" << std::endl;
Chris@148 717
Chris@148 718 for (MIDITrack::iterator i = m_midiComposition[track].begin();
Chris@148 719 i != m_midiComposition[track].end(); ++i) {
Chris@148 720
Chris@148 721 if ((*i)->isMeta() &&
Chris@148 722 (*i)->getMetaEventCode() == MIDI_SET_TEMPO) {
Chris@148 723
Chris@148 724 MIDIByte m0 = (*i)->getMetaMessage()[0];
Chris@148 725 MIDIByte m1 = (*i)->getMetaMessage()[1];
Chris@148 726 MIDIByte m2 = (*i)->getMetaMessage()[2];
Chris@148 727
Chris@148 728 long tempo = (((m0 << 8) + m1) << 8) + m2;
Chris@148 729
Chris@148 730 std::cerr << "updateTempoMap: have tempo, it's " << tempo << " at " << (*i)->getTime() << std::endl;
Chris@148 731
Chris@148 732 if (tempo != 0) {
Chris@148 733 double qpm = 60000000.0 / double(tempo);
Chris@148 734 m_tempoMap[(*i)->getTime()] =
Chris@148 735 TempoChange(RealTime::zeroTime, qpm);
Chris@148 736 }
Chris@148 737 }
Chris@148 738 }
Chris@148 739 }
Chris@148 740
Chris@148 741 void
Chris@148 742 MIDIFileReader::calculateTempoTimestamps()
Chris@148 743 {
Chris@148 744 unsigned long lastMIDITime = 0;
Chris@148 745 RealTime lastRealTime = RealTime::zeroTime;
Chris@148 746 double tempo = 120.0;
Chris@148 747 int td = m_timingDivision;
Chris@148 748 if (td == 0) td = 96;
Chris@148 749
Chris@148 750 for (TempoMap::iterator i = m_tempoMap.begin(); i != m_tempoMap.end(); ++i) {
Chris@148 751
Chris@148 752 unsigned long mtime = i->first;
Chris@148 753 unsigned long melapsed = mtime - lastMIDITime;
Chris@148 754 double quarters = double(melapsed) / double(td);
Chris@148 755 double seconds = (60.0 * quarters) / tempo;
Chris@148 756
Chris@148 757 RealTime t = lastRealTime + RealTime::fromSeconds(seconds);
Chris@148 758
Chris@148 759 i->second.first = t;
Chris@148 760
Chris@148 761 lastRealTime = t;
Chris@148 762 lastMIDITime = mtime;
Chris@148 763 tempo = i->second.second;
Chris@148 764 }
Chris@148 765 }
Chris@148 766
Chris@148 767 RealTime
Chris@148 768 MIDIFileReader::getTimeForMIDITime(unsigned long midiTime) const
Chris@148 769 {
Chris@148 770 unsigned long tempoMIDITime = 0;
Chris@148 771 RealTime tempoRealTime = RealTime::zeroTime;
Chris@148 772 double tempo = 120.0;
Chris@148 773
Chris@148 774 TempoMap::const_iterator i = m_tempoMap.lower_bound(midiTime);
Chris@148 775 if (i != m_tempoMap.begin()) {
Chris@148 776 --i;
Chris@148 777 tempoMIDITime = i->first;
Chris@148 778 tempoRealTime = i->second.first;
Chris@148 779 tempo = i->second.second;
Chris@148 780 }
Chris@148 781
Chris@148 782 int td = m_timingDivision;
Chris@148 783 if (td == 0) td = 96;
Chris@148 784
Chris@148 785 unsigned long melapsed = midiTime - tempoMIDITime;
Chris@148 786 double quarters = double(melapsed) / double(td);
Chris@148 787 double seconds = (60.0 * quarters) / tempo;
Chris@148 788
Chris@148 789 /*
Chris@148 790 std::cerr << "MIDIFileReader::getTimeForMIDITime(" << midiTime << ")"
Chris@148 791 << std::endl;
Chris@148 792 std::cerr << "timing division = " << td << std::endl;
Chris@148 793 std::cerr << "nearest tempo event (of " << m_tempoMap.size() << ") is at " << tempoMIDITime << " ("
Chris@148 794 << tempoRealTime << ")" << std::endl;
Chris@148 795 std::cerr << "quarters since then = " << quarters << std::endl;
Chris@148 796 std::cerr << "tempo = " << tempo << " quarters per minute" << std::endl;
Chris@148 797 std::cerr << "seconds since then = " << seconds << std::endl;
Chris@148 798 std::cerr << "resulting time = " << (tempoRealTime + RealTime::fromSeconds(seconds)) << std::endl;
Chris@148 799 */
Chris@148 800
Chris@148 801 return tempoRealTime + RealTime::fromSeconds(seconds);
Chris@148 802 }
Chris@148 803
Chris@148 804 Model *
Chris@148 805 MIDIFileReader::load() const
Chris@148 806 {
Chris@148 807 if (!isOK()) return 0;
Chris@148 808
Chris@148 809 if (m_loadableTracks.empty()) {
Chris@392 810 if (m_acquirer) {
Chris@392 811 m_acquirer->showError
Chris@392 812 (tr("MIDI file \"%1\" has no notes in any track").arg(m_path));
Chris@392 813 }
Chris@148 814 return 0;
Chris@148 815 }
Chris@148 816
Chris@148 817 std::set<unsigned int> tracksToLoad;
Chris@148 818
Chris@148 819 if (m_loadableTracks.size() == 1) {
Chris@148 820
Chris@148 821 tracksToLoad.insert(*m_loadableTracks.begin());
Chris@148 822
Chris@148 823 } else {
Chris@148 824
Chris@392 825 QStringList displayNames;
Chris@148 826
Chris@148 827 for (set<unsigned int>::iterator i = m_loadableTracks.begin();
Chris@148 828 i != m_loadableTracks.end(); ++i) {
Chris@148 829
Chris@148 830 unsigned int trackNo = *i;
Chris@148 831 QString label;
Chris@148 832
Chris@148 833 QString perc;
Chris@148 834 if (m_percussionTracks.find(trackNo) != m_percussionTracks.end()) {
Chris@148 835 perc = tr(" - uses GM percussion channel");
Chris@148 836 }
Chris@148 837
Chris@148 838 if (m_trackNames.find(trackNo) != m_trackNames.end()) {
Chris@148 839 label = tr("Track %1 (%2)%3")
Chris@148 840 .arg(trackNo).arg(m_trackNames.find(trackNo)->second)
Chris@148 841 .arg(perc);
Chris@148 842 } else {
Chris@148 843 label = tr("Track %1 (untitled)%3").arg(trackNo).arg(perc);
Chris@148 844 }
Chris@392 845
Chris@392 846 displayNames << label;
Chris@148 847 }
Chris@148 848
Chris@392 849 QString singleTrack;
Chris@148 850
Chris@392 851 bool haveSomePercussion =
Chris@392 852 (!m_percussionTracks.empty() &&
Chris@392 853 (m_percussionTracks.size() < m_loadableTracks.size()));
Chris@148 854
Chris@392 855 MIDIFileImportPreferenceAcquirer::TrackPreference pref;
Chris@392 856
Chris@392 857 if (m_acquirer) {
Chris@392 858 pref = m_acquirer->getTrackImportPreference(displayNames,
Chris@392 859 haveSomePercussion,
Chris@392 860 singleTrack);
Chris@392 861 } else {
Chris@392 862 pref = MIDIFileImportPreferenceAcquirer::MergeAllTracks;
Chris@392 863 }
Chris@392 864
Chris@392 865 if (pref == MIDIFileImportPreferenceAcquirer::ImportNothing) return 0;
Chris@392 866
Chris@392 867 if (pref == MIDIFileImportPreferenceAcquirer::MergeAllTracks ||
Chris@392 868 pref == MIDIFileImportPreferenceAcquirer::MergeAllNonPercussionTracks) {
Chris@392 869
Chris@392 870 for (set<unsigned int>::iterator i = m_loadableTracks.begin();
Chris@392 871 i != m_loadableTracks.end(); ++i) {
Chris@392 872
Chris@392 873 if (pref == MIDIFileImportPreferenceAcquirer::MergeAllTracks ||
Chris@148 874 m_percussionTracks.find(*i) == m_percussionTracks.end()) {
Chris@392 875
Chris@148 876 tracksToLoad.insert(*i);
Chris@148 877 }
Chris@148 878 }
Chris@148 879
Chris@148 880 } else {
Chris@148 881
Chris@392 882 int j = 0;
Chris@148 883
Chris@148 884 for (set<unsigned int>::iterator i = m_loadableTracks.begin();
Chris@148 885 i != m_loadableTracks.end(); ++i) {
Chris@148 886
Chris@392 887 if (singleTrack == displayNames[j]) {
Chris@148 888 tracksToLoad.insert(*i);
Chris@148 889 break;
Chris@148 890 }
Chris@148 891
Chris@148 892 ++j;
Chris@148 893 }
Chris@148 894 }
Chris@148 895 }
Chris@148 896
Chris@148 897 if (tracksToLoad.empty()) return 0;
Chris@148 898
Chris@148 899 size_t n = tracksToLoad.size(), count = 0;
Chris@148 900 Model *model = 0;
Chris@148 901
Chris@148 902 for (std::set<unsigned int>::iterator i = tracksToLoad.begin();
Chris@148 903 i != tracksToLoad.end(); ++i) {
Chris@148 904
Chris@148 905 int minProgress = (100 * count) / n;
Chris@148 906 int progressAmount = 100 / n;
Chris@148 907
Chris@148 908 model = loadTrack(*i, model, minProgress, progressAmount);
Chris@148 909
Chris@148 910 ++count;
Chris@148 911 }
Chris@148 912
Chris@148 913 if (dynamic_cast<NoteModel *>(model)) {
Chris@148 914 dynamic_cast<NoteModel *>(model)->setCompletion(100);
Chris@148 915 }
Chris@148 916
Chris@148 917 return model;
Chris@148 918 }
Chris@148 919
Chris@148 920 Model *
Chris@148 921 MIDIFileReader::loadTrack(unsigned int trackToLoad,
Chris@148 922 Model *existingModel,
Chris@148 923 int minProgress,
Chris@148 924 int progressAmount) const
Chris@148 925 {
Chris@148 926 if (m_midiComposition.find(trackToLoad) == m_midiComposition.end()) {
Chris@148 927 return 0;
Chris@148 928 }
Chris@148 929
Chris@148 930 NoteModel *model = 0;
Chris@148 931
Chris@148 932 if (existingModel) {
Chris@148 933 model = dynamic_cast<NoteModel *>(existingModel);
Chris@148 934 if (!model) {
Chris@148 935 std::cerr << "WARNING: MIDIFileReader::loadTrack: Existing model given, but it isn't a NoteModel -- ignoring it" << std::endl;
Chris@148 936 }
Chris@148 937 }
Chris@148 938
Chris@148 939 if (!model) {
Chris@148 940 model = new NoteModel(m_mainModelSampleRate, 1, 0.0, 0.0, false);
Chris@148 941 model->setValueQuantization(1.0);
Chris@148 942 }
Chris@148 943
Chris@148 944 const MIDITrack &track = m_midiComposition.find(trackToLoad)->second;
Chris@148 945
Chris@148 946 size_t totalEvents = track.size();
Chris@148 947 size_t count = 0;
Chris@148 948
Chris@148 949 bool minorKey = false;
Chris@148 950 bool sharpKey = true;
Chris@148 951
Chris@148 952 for (MIDITrack::const_iterator i = track.begin(); i != track.end(); ++i) {
Chris@148 953
Chris@613 954 RealTime rt;
Chris@613 955 unsigned long midiTime = (*i)->getTime();
Chris@613 956
Chris@613 957 if (m_smpte) {
Chris@613 958 rt = RealTime::frame2RealTime(midiTime, m_fps * m_subframes);
Chris@613 959 } else {
Chris@613 960 rt = getTimeForMIDITime(midiTime);
Chris@613 961 }
Chris@148 962
Chris@148 963 // We ignore most of these event types for now, though in
Chris@148 964 // theory some of the text ones could usefully be incorporated
Chris@148 965
Chris@148 966 if ((*i)->isMeta()) {
Chris@148 967
Chris@148 968 switch((*i)->getMetaEventCode()) {
Chris@148 969
Chris@148 970 case MIDI_KEY_SIGNATURE:
Chris@148 971 minorKey = (int((*i)->getMetaMessage()[1]) != 0);
Chris@148 972 sharpKey = (int((*i)->getMetaMessage()[0]) >= 0);
Chris@148 973 break;
Chris@148 974
Chris@148 975 case MIDI_TEXT_EVENT:
Chris@148 976 case MIDI_LYRIC:
Chris@148 977 case MIDI_TEXT_MARKER:
Chris@148 978 case MIDI_COPYRIGHT_NOTICE:
Chris@148 979 case MIDI_TRACK_NAME:
Chris@148 980 // The text events that we could potentially use
Chris@148 981 break;
Chris@148 982
Chris@148 983 case MIDI_SET_TEMPO:
Chris@148 984 // Already dealt with in a separate pass previously
Chris@148 985 break;
Chris@148 986
Chris@148 987 case MIDI_TIME_SIGNATURE:
Chris@148 988 // Not yet!
Chris@148 989 break;
Chris@148 990
Chris@148 991 case MIDI_SEQUENCE_NUMBER:
Chris@148 992 case MIDI_CHANNEL_PREFIX_OR_PORT:
Chris@148 993 case MIDI_INSTRUMENT_NAME:
Chris@148 994 case MIDI_CUE_POINT:
Chris@148 995 case MIDI_CHANNEL_PREFIX:
Chris@148 996 case MIDI_SEQUENCER_SPECIFIC:
Chris@148 997 case MIDI_SMPTE_OFFSET:
Chris@148 998 default:
Chris@148 999 break;
Chris@148 1000 }
Chris@148 1001
Chris@148 1002 } else {
Chris@148 1003
Chris@148 1004 switch ((*i)->getMessageType()) {
Chris@148 1005
Chris@148 1006 case MIDI_NOTE_ON:
Chris@148 1007
Chris@148 1008 if ((*i)->getVelocity() == 0) break; // effective note-off
Chris@148 1009 else {
Chris@613 1010 RealTime endRT;
Chris@613 1011 unsigned long endMidiTime = (*i)->getTime() + (*i)->getDuration();
Chris@613 1012 if (m_smpte) {
Chris@613 1013 endRT = RealTime::frame2RealTime(endMidiTime, m_fps * m_subframes);
Chris@613 1014 } else {
Chris@613 1015 endRT = getTimeForMIDITime(endMidiTime);
Chris@613 1016 }
Chris@148 1017
Chris@148 1018 long startFrame = RealTime::realTime2Frame
Chris@148 1019 (rt, model->getSampleRate());
Chris@148 1020
Chris@148 1021 long endFrame = RealTime::realTime2Frame
Chris@148 1022 (endRT, model->getSampleRate());
Chris@148 1023
Chris@148 1024 QString pitchLabel = Pitch::getPitchLabel((*i)->getPitch(),
Chris@148 1025 0,
Chris@148 1026 !sharpKey);
Chris@148 1027
Chris@148 1028 QString noteLabel = tr("%1 - vel %2")
Chris@148 1029 .arg(pitchLabel).arg(int((*i)->getVelocity()));
Chris@148 1030
Chris@340 1031 float level = float((*i)->getVelocity()) / 128.f;
Chris@340 1032
Chris@148 1033 Note note(startFrame, (*i)->getPitch(),
Chris@340 1034 endFrame - startFrame, level, noteLabel);
Chris@148 1035
Chris@148 1036 // std::cerr << "Adding note " << startFrame << "," << (endFrame-startFrame) << " : " << int((*i)->getPitch()) << std::endl;
Chris@148 1037
Chris@148 1038 model->addPoint(note);
Chris@148 1039 break;
Chris@148 1040 }
Chris@148 1041
Chris@148 1042 case MIDI_PITCH_BEND:
Chris@148 1043 // I guess we could make some use of this...
Chris@148 1044 break;
Chris@148 1045
Chris@148 1046 case MIDI_NOTE_OFF:
Chris@148 1047 case MIDI_PROG_CHANGE:
Chris@148 1048 case MIDI_CTRL_CHANGE:
Chris@148 1049 case MIDI_SYSTEM_EXCLUSIVE:
Chris@148 1050 case MIDI_POLY_AFTERTOUCH:
Chris@148 1051 case MIDI_CHNL_AFTERTOUCH:
Chris@148 1052 break;
Chris@148 1053
Chris@148 1054 default:
Chris@148 1055 break;
Chris@148 1056 }
Chris@148 1057 }
Chris@148 1058
Chris@148 1059 model->setCompletion(minProgress +
Chris@148 1060 (count * progressAmount) / totalEvents);
Chris@148 1061 ++count;
Chris@148 1062 }
Chris@148 1063
Chris@148 1064 return model;
Chris@148 1065 }
Chris@148 1066
Chris@148 1067