annotate data/fileio/MIDIFileReader.cpp @ 1346:75ad55315db4 3.0-integration

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