annotate data/fileio/MIDIFileReader.cpp @ 1677:f97d64b8674f single-point

Make XmlExportables store their export IDs and always obtain a new one, avoiding reuse when an object is allocated at the same heap location as a previous one. This makes the ID system stable enough to be used in the export tests.
author Chris Cannam
date Thu, 28 Mar 2019 11:55:02 +0000
parents 513192aa9b03
children 8a06e16948d7
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@1429 937 model = new NoteModel(m_mainModelSampleRate, 1, 0.0, 0.0, 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