annotate data/fileio/MIDIFileReader.cpp @ 1881:b504df98c3be

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