Chris@148: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ Chris@148: Chris@148: /* Chris@148: Sonic Visualiser Chris@148: An audio file viewer and annotation editor. Chris@148: Centre for Digital Music, Queen Mary, University of London. Chris@148: Chris@148: This program is free software; you can redistribute it and/or Chris@148: modify it under the terms of the GNU General Public License as Chris@148: published by the Free Software Foundation; either version 2 of the Chris@148: License, or (at your option) any later version. See the file Chris@148: COPYING included with this distribution for more information. Chris@148: */ Chris@148: Chris@148: Chris@148: /* Chris@148: This is a modified version of a source file from the Chris@148: Rosegarden MIDI and audio sequencer and notation editor. Chris@148: This file copyright 2000-2006 Richard Bown and Chris Cannam. Chris@148: */ Chris@148: Chris@148: Chris@148: #include Chris@148: #include Chris@148: #include Chris@148: #include Chris@148: #include Chris@148: Chris@148: #include "MIDIFileReader.h" Chris@148: Chris@301: #include "MIDIEvent.h" Chris@301: Chris@150: #include "model/Model.h" Chris@148: #include "base/Pitch.h" Chris@148: #include "base/RealTime.h" Chris@148: #include "model/NoteModel.h" Chris@148: Chris@148: #include Chris@148: #include Chris@148: #include Chris@148: Chris@148: #include Chris@148: Chris@148: using std::string; Chris@148: using std::ifstream; Chris@148: using std::stringstream; Chris@148: using std::cerr; Chris@148: using std::endl; Chris@148: using std::ends; Chris@148: using std::ios; Chris@148: using std::vector; Chris@148: using std::map; Chris@148: using std::set; Chris@148: Chris@301: using namespace MIDIConstants; Chris@301: Chris@148: //#define MIDI_DEBUG 1 Chris@148: Chris@148: Chris@148: MIDIFileReader::MIDIFileReader(QString path, Chris@148: size_t mainModelSampleRate) : Chris@148: m_timingDivision(0), Chris@148: m_format(MIDI_FILE_BAD_FORMAT), Chris@148: m_numberOfTracks(0), Chris@148: m_trackByteCount(0), Chris@148: m_decrementCount(false), Chris@148: m_path(path), Chris@148: m_midiFile(0), Chris@148: m_fileSize(0), Chris@148: m_mainModelSampleRate(mainModelSampleRate) Chris@148: { Chris@148: if (parseFile()) { Chris@148: m_error = ""; Chris@148: } Chris@148: } Chris@148: Chris@148: MIDIFileReader::~MIDIFileReader() Chris@148: { Chris@148: for (MIDIComposition::iterator i = m_midiComposition.begin(); Chris@148: i != m_midiComposition.end(); ++i) { Chris@148: Chris@148: for (MIDITrack::iterator j = i->second.begin(); Chris@148: j != i->second.end(); ++j) { Chris@148: delete *j; Chris@148: } Chris@148: Chris@148: i->second.clear(); Chris@148: } Chris@148: Chris@148: m_midiComposition.clear(); Chris@148: } Chris@148: Chris@148: bool Chris@148: MIDIFileReader::isOK() const Chris@148: { Chris@148: return (m_error == ""); Chris@148: } Chris@148: Chris@148: QString Chris@148: MIDIFileReader::getError() const Chris@148: { Chris@148: return m_error; Chris@148: } Chris@148: Chris@148: long Chris@148: MIDIFileReader::midiBytesToLong(const string& bytes) Chris@148: { Chris@148: if (bytes.length() != 4) { Chris@148: throw MIDIException(tr("Wrong length for long data in MIDI stream (%1, should be %2)").arg(bytes.length()).arg(4)); Chris@148: } Chris@148: Chris@148: long longRet = ((long)(((MIDIByte)bytes[0]) << 24)) | Chris@148: ((long)(((MIDIByte)bytes[1]) << 16)) | Chris@148: ((long)(((MIDIByte)bytes[2]) << 8)) | Chris@148: ((long)((MIDIByte)(bytes[3]))); Chris@148: Chris@148: return longRet; Chris@148: } Chris@148: Chris@148: int Chris@148: MIDIFileReader::midiBytesToInt(const string& bytes) Chris@148: { Chris@148: if (bytes.length() != 2) { Chris@148: throw MIDIException(tr("Wrong length for int data in MIDI stream (%1, should be %2)").arg(bytes.length()).arg(2)); Chris@148: } Chris@148: Chris@148: int intRet = ((int)(((MIDIByte)bytes[0]) << 8)) | Chris@148: ((int)(((MIDIByte)bytes[1]))); Chris@148: return(intRet); Chris@148: } Chris@148: Chris@148: Chris@148: // Gets a single byte from the MIDI byte stream. For each track Chris@148: // section we can read only a specified number of bytes held in Chris@148: // m_trackByteCount. Chris@148: // Chris@301: MIDIByte Chris@148: MIDIFileReader::getMIDIByte() Chris@148: { Chris@148: if (!m_midiFile) { Chris@148: throw MIDIException(tr("getMIDIByte called but no MIDI file open")); Chris@148: } Chris@148: Chris@148: if (m_midiFile->eof()) { Chris@148: throw MIDIException(tr("End of MIDI file encountered while reading")); Chris@148: } Chris@148: Chris@148: if (m_decrementCount && m_trackByteCount <= 0) { Chris@148: throw MIDIException(tr("Attempt to get more bytes than expected on Track")); Chris@148: } Chris@148: Chris@148: char byte; Chris@148: if (m_midiFile->read(&byte, 1)) { Chris@148: --m_trackByteCount; Chris@148: return (MIDIByte)byte; Chris@148: } Chris@148: Chris@148: throw MIDIException(tr("Attempt to read past MIDI file end")); Chris@148: } Chris@148: Chris@148: Chris@148: // Gets a specified number of bytes from the MIDI byte stream. For Chris@148: // each track section we can read only a specified number of bytes Chris@148: // held in m_trackByteCount. Chris@148: // Chris@148: string Chris@148: MIDIFileReader::getMIDIBytes(unsigned long numberOfBytes) Chris@148: { Chris@148: if (!m_midiFile) { Chris@148: throw MIDIException(tr("getMIDIBytes called but no MIDI file open")); Chris@148: } Chris@148: Chris@148: if (m_midiFile->eof()) { Chris@148: throw MIDIException(tr("End of MIDI file encountered while reading")); Chris@148: } Chris@148: Chris@148: if (m_decrementCount && (numberOfBytes > (unsigned long)m_trackByteCount)) { Chris@148: throw MIDIException(tr("Attempt to get more bytes than available on Track (%1, only have %2)").arg(numberOfBytes).arg(m_trackByteCount)); Chris@148: } Chris@148: Chris@148: string stringRet; Chris@148: char fileMIDIByte; Chris@148: Chris@148: while (stringRet.length() < numberOfBytes && Chris@148: m_midiFile->read(&fileMIDIByte, 1)) { Chris@148: stringRet += fileMIDIByte; Chris@148: } Chris@148: Chris@148: // if we've reached the end of file without fulfilling the Chris@148: // quota then panic as our parsing has performed incorrectly Chris@148: // Chris@148: if (stringRet.length() < numberOfBytes) { Chris@148: stringRet = ""; Chris@148: throw MIDIException(tr("Attempt to read past MIDI file end")); Chris@148: } Chris@148: Chris@148: // decrement the byte count Chris@148: if (m_decrementCount) Chris@148: m_trackByteCount -= stringRet.length(); Chris@148: Chris@148: return stringRet; Chris@148: } Chris@148: Chris@148: Chris@148: // Get a long number of variable length from the MIDI byte stream. Chris@148: // Chris@148: long Chris@148: MIDIFileReader::getNumberFromMIDIBytes(int firstByte) Chris@148: { Chris@148: if (!m_midiFile) { Chris@148: throw MIDIException(tr("getNumberFromMIDIBytes called but no MIDI file open")); Chris@148: } Chris@148: Chris@148: long longRet = 0; Chris@148: MIDIByte midiByte; Chris@148: Chris@148: if (firstByte >= 0) { Chris@148: midiByte = (MIDIByte)firstByte; Chris@148: } else if (m_midiFile->eof()) { Chris@148: return longRet; Chris@148: } else { Chris@148: midiByte = getMIDIByte(); Chris@148: } Chris@148: Chris@148: longRet = midiByte; Chris@148: if (midiByte & 0x80) { Chris@148: longRet &= 0x7F; Chris@148: do { Chris@148: midiByte = getMIDIByte(); Chris@148: longRet = (longRet << 7) + (midiByte & 0x7F); Chris@148: } while (!m_midiFile->eof() && (midiByte & 0x80)); Chris@148: } Chris@148: Chris@148: return longRet; Chris@148: } Chris@148: Chris@148: Chris@148: // Seek to the next track in the midi file and set the number Chris@148: // of bytes to be read in the counter m_trackByteCount. Chris@148: // Chris@148: bool Chris@148: MIDIFileReader::skipToNextTrack() Chris@148: { Chris@148: if (!m_midiFile) { Chris@148: throw MIDIException(tr("skipToNextTrack called but no MIDI file open")); Chris@148: } Chris@148: Chris@148: string buffer, buffer2; Chris@148: m_trackByteCount = -1; Chris@148: m_decrementCount = false; Chris@148: Chris@148: while (!m_midiFile->eof() && (m_decrementCount == false)) { Chris@148: buffer = getMIDIBytes(4); Chris@148: if (buffer.compare(0, 4, MIDI_TRACK_HEADER) == 0) { Chris@148: m_trackByteCount = midiBytesToLong(getMIDIBytes(4)); Chris@148: m_decrementCount = true; Chris@148: } Chris@148: } Chris@148: Chris@148: if (m_trackByteCount == -1) { // we haven't found a track Chris@148: return false; Chris@148: } else { Chris@148: return true; Chris@148: } Chris@148: } Chris@148: Chris@148: Chris@148: // Read in a MIDI file. The parsing process throws exceptions back up Chris@148: // here if we run into trouble which we can then pass back out to Chris@148: // whoever called us using a nice bool. Chris@148: // Chris@148: bool Chris@148: MIDIFileReader::parseFile() Chris@148: { Chris@148: m_error = ""; Chris@148: Chris@148: #ifdef MIDI_DEBUG Chris@148: cerr << "MIDIFileReader::open() : fileName = " << m_fileName.c_str() << endl; Chris@148: #endif Chris@148: Chris@148: // Open the file Chris@148: m_midiFile = new ifstream(m_path.toLocal8Bit().data(), Chris@148: ios::in | ios::binary); Chris@148: Chris@148: if (!*m_midiFile) { Chris@148: m_error = "File not found or not readable."; Chris@148: m_format = MIDI_FILE_BAD_FORMAT; Chris@148: delete m_midiFile; Chris@301: m_midiFile = 0; Chris@148: return false; Chris@148: } Chris@148: Chris@148: bool retval = false; Chris@148: Chris@148: try { Chris@148: Chris@148: // Set file size so we can count it off Chris@148: // Chris@148: m_midiFile->seekg(0, ios::end); Chris@148: m_fileSize = m_midiFile->tellg(); Chris@148: m_midiFile->seekg(0, ios::beg); Chris@148: Chris@148: // Parse the MIDI header first. The first 14 bytes of the file. Chris@148: if (!parseHeader(getMIDIBytes(14))) { Chris@148: m_format = MIDI_FILE_BAD_FORMAT; Chris@148: m_error = "Not a MIDI file."; Chris@148: goto done; Chris@148: } Chris@148: Chris@148: unsigned int i = 0; Chris@148: Chris@148: for (unsigned int j = 0; j < m_numberOfTracks; ++j) { Chris@148: Chris@148: #ifdef MIDI_DEBUG Chris@148: cerr << "Parsing Track " << j << endl; Chris@148: #endif Chris@148: Chris@148: if (!skipToNextTrack()) { Chris@148: #ifdef MIDI_DEBUG Chris@148: cerr << "Couldn't find Track " << j << endl; Chris@148: #endif Chris@148: m_error = "File corrupted or in non-standard format?"; Chris@148: m_format = MIDI_FILE_BAD_FORMAT; Chris@148: goto done; Chris@148: } Chris@148: Chris@148: #ifdef MIDI_DEBUG Chris@148: cerr << "Track has " << m_trackByteCount << " bytes" << endl; Chris@148: #endif Chris@148: Chris@148: // Run through the events taking them into our internal Chris@148: // representation. Chris@148: if (!parseTrack(i)) { Chris@148: #ifdef MIDI_DEBUG Chris@148: cerr << "Track " << j << " parsing failed" << endl; Chris@148: #endif Chris@148: m_error = "File corrupted or in non-standard format?"; Chris@148: m_format = MIDI_FILE_BAD_FORMAT; Chris@148: goto done; Chris@148: } Chris@148: Chris@148: ++i; // j is the source track number, i the destination Chris@148: } Chris@148: Chris@148: m_numberOfTracks = i; Chris@148: retval = true; Chris@148: Chris@148: } catch (MIDIException e) { Chris@148: Chris@148: cerr << "MIDIFileReader::open() - caught exception - " << e.what() << endl; Chris@148: m_error = e.what(); Chris@148: } Chris@148: Chris@148: done: Chris@148: m_midiFile->close(); Chris@148: delete m_midiFile; Chris@148: Chris@148: for (unsigned int track = 0; track < m_numberOfTracks; ++track) { Chris@148: Chris@148: // Convert the deltaTime to an absolute time since the track Chris@148: // start. The addTime method returns the sum of the current Chris@148: // MIDI Event delta time plus the argument. Chris@148: Chris@148: unsigned long acc = 0; Chris@148: Chris@148: for (MIDITrack::iterator i = m_midiComposition[track].begin(); Chris@148: i != m_midiComposition[track].end(); ++i) { Chris@148: acc = (*i)->addTime(acc); Chris@148: } Chris@148: Chris@148: if (consolidateNoteOffEvents(track)) { // returns true if some notes exist Chris@148: m_loadableTracks.insert(track); Chris@148: } Chris@148: } Chris@148: Chris@148: for (unsigned int track = 0; track < m_numberOfTracks; ++track) { Chris@148: updateTempoMap(track); Chris@148: } Chris@148: Chris@148: calculateTempoTimestamps(); Chris@148: Chris@148: return retval; Chris@148: } Chris@148: Chris@148: // Parse and ensure the MIDI Header is legitimate Chris@148: // Chris@148: bool Chris@148: MIDIFileReader::parseHeader(const string &midiHeader) Chris@148: { Chris@148: if (midiHeader.size() < 14) { Chris@148: #ifdef MIDI_DEBUG Chris@148: cerr << "MIDIFileReader::parseHeader() - file header undersized" << endl; Chris@148: #endif Chris@148: return false; Chris@148: } Chris@148: Chris@148: if (midiHeader.compare(0, 4, MIDI_FILE_HEADER) != 0) { Chris@148: #ifdef MIDI_DEBUG Chris@148: cerr << "MIDIFileReader::parseHeader()" Chris@148: << "- file header not found or malformed" Chris@148: << endl; Chris@148: #endif Chris@148: return false; Chris@148: } Chris@148: Chris@148: if (midiBytesToLong(midiHeader.substr(4,4)) != 6L) { Chris@148: #ifdef MIDI_DEBUG Chris@148: cerr << "MIDIFileReader::parseHeader()" Chris@148: << " - header length incorrect" Chris@148: << endl; Chris@148: #endif Chris@148: return false; Chris@148: } Chris@148: Chris@148: m_format = (MIDIFileFormatType) midiBytesToInt(midiHeader.substr(8,2)); Chris@148: m_numberOfTracks = midiBytesToInt(midiHeader.substr(10,2)); Chris@148: m_timingDivision = midiBytesToInt(midiHeader.substr(12,2)); Chris@148: Chris@148: if (m_format == MIDI_SEQUENTIAL_TRACK_FILE) { Chris@148: #ifdef MIDI_DEBUG Chris@148: cerr << "MIDIFileReader::parseHeader()" Chris@148: << "- can't load sequential track file" Chris@148: << endl; Chris@148: #endif Chris@148: return false; Chris@148: } Chris@148: Chris@148: #ifdef MIDI_DEBUG Chris@148: if (m_timingDivision < 0) { Chris@148: cerr << "MIDIFileReader::parseHeader()" Chris@148: << " - file uses SMPTE timing" Chris@148: << endl; Chris@148: } Chris@148: #endif Chris@148: Chris@148: return true; Chris@148: } Chris@148: Chris@148: // Extract the contents from a MIDI file track and places it into Chris@148: // our local map of MIDI events. Chris@148: // Chris@148: bool Chris@148: MIDIFileReader::parseTrack(unsigned int &lastTrackNum) Chris@148: { Chris@148: MIDIByte midiByte, metaEventCode, data1, data2; Chris@148: MIDIByte eventCode = 0x80; Chris@148: string metaMessage; Chris@148: unsigned int messageLength; Chris@148: unsigned long deltaTime; Chris@148: unsigned long accumulatedTime = 0; Chris@148: Chris@148: // The trackNum passed in to this method is the default track for Chris@148: // all events provided they're all on the same channel. If we find Chris@148: // events on more than one channel, we increment trackNum and record Chris@148: // the mapping from channel to trackNum in this channelTrackMap. Chris@148: // We then return the new trackNum by reference so the calling Chris@148: // method knows we've got more tracks than expected. Chris@148: Chris@148: // This would be a vector but we need -1 to indicate Chris@148: // "not yet used" Chris@148: vector channelTrackMap(16, -1); Chris@148: Chris@148: // This is used to store the last absolute time found on each track, Chris@148: // allowing us to modify delta-times correctly when separating events Chris@148: // out from one to multiple tracks Chris@148: // Chris@148: map trackTimeMap; Chris@148: Chris@148: // Meta-events don't have a channel, so we place them in a fixed Chris@148: // track number instead Chris@148: unsigned int metaTrack = lastTrackNum; Chris@148: Chris@148: // Remember the last non-meta status byte (-1 if we haven't seen one) Chris@148: int runningStatus = -1; Chris@148: Chris@148: bool firstTrack = true; Chris@148: Chris@148: while (!m_midiFile->eof() && (m_trackByteCount > 0)) { Chris@148: Chris@148: if (eventCode < 0x80) { Chris@148: #ifdef MIDI_DEBUG Chris@148: cerr << "WARNING: Invalid event code " << eventCode Chris@148: << " in MIDI file" << endl; Chris@148: #endif Chris@148: throw MIDIException(tr("Invalid event code %1 found").arg(int(eventCode))); Chris@148: } Chris@148: Chris@148: deltaTime = getNumberFromMIDIBytes(); Chris@148: Chris@148: #ifdef MIDI_DEBUG Chris@148: cerr << "read delta time " << deltaTime << endl; Chris@148: #endif Chris@148: Chris@148: // Get a single byte Chris@148: midiByte = getMIDIByte(); Chris@148: Chris@148: if (!(midiByte & MIDI_STATUS_BYTE_MASK)) { Chris@148: Chris@148: if (runningStatus < 0) { Chris@148: throw MIDIException(tr("Running status used for first event in track")); Chris@148: } Chris@148: Chris@148: eventCode = (MIDIByte)runningStatus; Chris@148: data1 = midiByte; Chris@148: Chris@148: #ifdef MIDI_DEBUG Chris@148: cerr << "using running status (byte " << int(midiByte) << " found)" << endl; Chris@148: #endif Chris@148: } else { Chris@148: #ifdef MIDI_DEBUG Chris@148: cerr << "have new event code " << int(midiByte) << endl; Chris@148: #endif Chris@148: eventCode = midiByte; Chris@148: data1 = getMIDIByte(); Chris@148: } Chris@148: Chris@148: if (eventCode == MIDI_FILE_META_EVENT) { Chris@148: Chris@148: metaEventCode = data1; Chris@148: messageLength = getNumberFromMIDIBytes(); Chris@148: Chris@148: //#ifdef MIDI_DEBUG Chris@148: cerr << "Meta event of type " << int(metaEventCode) << " and " << messageLength << " bytes found, putting on track " << metaTrack << endl; Chris@148: //#endif Chris@148: metaMessage = getMIDIBytes(messageLength); Chris@148: Chris@148: long gap = accumulatedTime - trackTimeMap[metaTrack]; Chris@148: accumulatedTime += deltaTime; Chris@148: deltaTime += gap; Chris@148: trackTimeMap[metaTrack] = accumulatedTime; Chris@148: Chris@148: MIDIEvent *e = new MIDIEvent(deltaTime, Chris@148: MIDI_FILE_META_EVENT, Chris@148: metaEventCode, Chris@148: metaMessage); Chris@148: Chris@148: m_midiComposition[metaTrack].push_back(e); Chris@148: Chris@148: if (metaEventCode == MIDI_TRACK_NAME) { Chris@148: m_trackNames[metaTrack] = metaMessage.c_str(); Chris@148: } Chris@148: Chris@148: } else { // non-meta events Chris@148: Chris@148: runningStatus = eventCode; Chris@148: Chris@148: MIDIEvent *midiEvent; Chris@148: Chris@148: int channel = (eventCode & MIDI_CHANNEL_NUM_MASK); Chris@148: if (channelTrackMap[channel] == -1) { Chris@148: if (!firstTrack) ++lastTrackNum; Chris@148: else firstTrack = false; Chris@148: channelTrackMap[channel] = lastTrackNum; Chris@148: } Chris@148: Chris@148: unsigned int trackNum = channelTrackMap[channel]; Chris@148: Chris@148: // accumulatedTime is abs time of last event on any track; Chris@148: // trackTimeMap[trackNum] is that of last event on this track Chris@148: Chris@148: long gap = accumulatedTime - trackTimeMap[trackNum]; Chris@148: accumulatedTime += deltaTime; Chris@148: deltaTime += gap; Chris@148: trackTimeMap[trackNum] = accumulatedTime; Chris@148: Chris@148: switch (eventCode & MIDI_MESSAGE_TYPE_MASK) { Chris@148: Chris@148: case MIDI_NOTE_ON: Chris@148: case MIDI_NOTE_OFF: Chris@148: case MIDI_POLY_AFTERTOUCH: Chris@148: case MIDI_CTRL_CHANGE: Chris@148: data2 = getMIDIByte(); Chris@148: Chris@148: // create and store our event Chris@148: midiEvent = new MIDIEvent(deltaTime, eventCode, data1, data2); Chris@148: Chris@148: /* Chris@148: cerr << "MIDI event for channel " << channel << " (track " Chris@148: << trackNum << ")" << endl; Chris@148: midiEvent->print(); Chris@148: */ Chris@148: Chris@148: Chris@148: m_midiComposition[trackNum].push_back(midiEvent); Chris@148: Chris@148: if (midiEvent->getChannelNumber() == MIDI_PERCUSSION_CHANNEL) { Chris@148: m_percussionTracks.insert(trackNum); Chris@148: } Chris@148: Chris@148: break; Chris@148: Chris@148: case MIDI_PITCH_BEND: Chris@148: data2 = getMIDIByte(); Chris@148: Chris@148: // create and store our event Chris@148: midiEvent = new MIDIEvent(deltaTime, eventCode, data1, data2); Chris@148: m_midiComposition[trackNum].push_back(midiEvent); Chris@148: break; Chris@148: Chris@148: case MIDI_PROG_CHANGE: Chris@148: case MIDI_CHNL_AFTERTOUCH: Chris@148: // create and store our event Chris@148: midiEvent = new MIDIEvent(deltaTime, eventCode, data1); Chris@148: m_midiComposition[trackNum].push_back(midiEvent); Chris@148: break; Chris@148: Chris@148: case MIDI_SYSTEM_EXCLUSIVE: Chris@148: messageLength = getNumberFromMIDIBytes(data1); Chris@148: Chris@148: #ifdef MIDI_DEBUG Chris@148: cerr << "SysEx of " << messageLength << " bytes found" << endl; Chris@148: #endif Chris@148: Chris@148: metaMessage= getMIDIBytes(messageLength); Chris@148: Chris@148: if (MIDIByte(metaMessage[metaMessage.length() - 1]) != Chris@148: MIDI_END_OF_EXCLUSIVE) Chris@148: { Chris@148: #ifdef MIDI_DEBUG Chris@148: cerr << "MIDIFileReader::parseTrack() - " Chris@148: << "malformed or unsupported SysEx type" Chris@148: << endl; Chris@148: #endif Chris@148: continue; Chris@148: } Chris@148: Chris@148: // chop off the EOX Chris@148: // length fixed by Pedro Lopez-Cabanillas (20030523) Chris@148: // Chris@148: metaMessage = metaMessage.substr(0, metaMessage.length()-1); Chris@148: Chris@148: midiEvent = new MIDIEvent(deltaTime, Chris@148: MIDI_SYSTEM_EXCLUSIVE, Chris@148: metaMessage); Chris@148: m_midiComposition[trackNum].push_back(midiEvent); Chris@148: break; Chris@148: Chris@148: case MIDI_END_OF_EXCLUSIVE: Chris@148: #ifdef MIDI_DEBUG Chris@148: cerr << "MIDIFileReader::parseTrack() - " Chris@148: << "Found a stray MIDI_END_OF_EXCLUSIVE" << endl; Chris@148: #endif Chris@148: break; Chris@148: Chris@148: default: Chris@148: #ifdef MIDI_DEBUG Chris@148: cerr << "MIDIFileReader::parseTrack()" Chris@148: << " - Unsupported MIDI Event Code: " Chris@148: << (int)eventCode << endl; Chris@148: #endif Chris@148: break; Chris@148: } Chris@148: } Chris@148: } Chris@148: Chris@148: if (lastTrackNum > metaTrack) { Chris@148: for (unsigned int track = metaTrack + 1; track <= lastTrackNum; ++track) { Chris@148: m_trackNames[track] = QString("%1 <%2>") Chris@148: .arg(m_trackNames[metaTrack]).arg(track - metaTrack + 1); Chris@148: } Chris@148: } Chris@148: Chris@148: return true; Chris@148: } Chris@148: Chris@148: // Delete dead NOTE OFF and NOTE ON/Zero Velocity Events after Chris@148: // reading them and modifying their relevant NOTE ONs. Return true Chris@148: // if there are some notes in this track. Chris@148: // Chris@148: bool Chris@148: MIDIFileReader::consolidateNoteOffEvents(unsigned int track) Chris@148: { Chris@148: bool notesOnTrack = false; Chris@148: bool noteOffFound; Chris@148: Chris@148: for (MIDITrack::iterator i = m_midiComposition[track].begin(); Chris@148: i != m_midiComposition[track].end(); i++) { Chris@148: Chris@148: if ((*i)->getMessageType() == MIDI_NOTE_ON && (*i)->getVelocity() > 0) { Chris@148: Chris@148: notesOnTrack = true; Chris@148: noteOffFound = false; Chris@148: Chris@148: for (MIDITrack::iterator j = i; Chris@148: j != m_midiComposition[track].end(); j++) { Chris@148: Chris@148: if (((*j)->getChannelNumber() == (*i)->getChannelNumber()) && Chris@148: ((*j)->getPitch() == (*i)->getPitch()) && Chris@148: ((*j)->getMessageType() == MIDI_NOTE_OFF || Chris@148: ((*j)->getMessageType() == MIDI_NOTE_ON && Chris@148: (*j)->getVelocity() == 0x00))) { Chris@148: Chris@148: (*i)->setDuration((*j)->getTime() - (*i)->getTime()); Chris@148: Chris@148: delete *j; Chris@148: m_midiComposition[track].erase(j); Chris@148: Chris@148: noteOffFound = true; Chris@148: break; Chris@148: } Chris@148: } Chris@148: Chris@148: // If no matching NOTE OFF has been found then set Chris@148: // Event duration to length of track Chris@148: // Chris@148: if (!noteOffFound) { Chris@148: MIDITrack::iterator j = m_midiComposition[track].end(); Chris@148: --j; Chris@148: (*i)->setDuration((*j)->getTime() - (*i)->getTime()); Chris@148: } Chris@148: } Chris@148: } Chris@148: Chris@148: return notesOnTrack; Chris@148: } Chris@148: Chris@148: // Add any tempo events found in the given track to the global tempo map. Chris@148: // Chris@148: void Chris@148: MIDIFileReader::updateTempoMap(unsigned int track) Chris@148: { Chris@148: std::cerr << "updateTempoMap for track " << track << " (" << m_midiComposition[track].size() << " events)" << std::endl; Chris@148: Chris@148: for (MIDITrack::iterator i = m_midiComposition[track].begin(); Chris@148: i != m_midiComposition[track].end(); ++i) { Chris@148: Chris@148: if ((*i)->isMeta() && Chris@148: (*i)->getMetaEventCode() == MIDI_SET_TEMPO) { Chris@148: Chris@148: MIDIByte m0 = (*i)->getMetaMessage()[0]; Chris@148: MIDIByte m1 = (*i)->getMetaMessage()[1]; Chris@148: MIDIByte m2 = (*i)->getMetaMessage()[2]; Chris@148: Chris@148: long tempo = (((m0 << 8) + m1) << 8) + m2; Chris@148: Chris@148: std::cerr << "updateTempoMap: have tempo, it's " << tempo << " at " << (*i)->getTime() << std::endl; Chris@148: Chris@148: if (tempo != 0) { Chris@148: double qpm = 60000000.0 / double(tempo); Chris@148: m_tempoMap[(*i)->getTime()] = Chris@148: TempoChange(RealTime::zeroTime, qpm); Chris@148: } Chris@148: } Chris@148: } Chris@148: } Chris@148: Chris@148: void Chris@148: MIDIFileReader::calculateTempoTimestamps() Chris@148: { Chris@148: unsigned long lastMIDITime = 0; Chris@148: RealTime lastRealTime = RealTime::zeroTime; Chris@148: double tempo = 120.0; Chris@148: int td = m_timingDivision; Chris@148: if (td == 0) td = 96; Chris@148: Chris@148: for (TempoMap::iterator i = m_tempoMap.begin(); i != m_tempoMap.end(); ++i) { Chris@148: Chris@148: unsigned long mtime = i->first; Chris@148: unsigned long melapsed = mtime - lastMIDITime; Chris@148: double quarters = double(melapsed) / double(td); Chris@148: double seconds = (60.0 * quarters) / tempo; Chris@148: Chris@148: RealTime t = lastRealTime + RealTime::fromSeconds(seconds); Chris@148: Chris@148: i->second.first = t; Chris@148: Chris@148: lastRealTime = t; Chris@148: lastMIDITime = mtime; Chris@148: tempo = i->second.second; Chris@148: } Chris@148: } Chris@148: Chris@148: RealTime Chris@148: MIDIFileReader::getTimeForMIDITime(unsigned long midiTime) const Chris@148: { Chris@148: unsigned long tempoMIDITime = 0; Chris@148: RealTime tempoRealTime = RealTime::zeroTime; Chris@148: double tempo = 120.0; Chris@148: Chris@148: TempoMap::const_iterator i = m_tempoMap.lower_bound(midiTime); Chris@148: if (i != m_tempoMap.begin()) { Chris@148: --i; Chris@148: tempoMIDITime = i->first; Chris@148: tempoRealTime = i->second.first; Chris@148: tempo = i->second.second; Chris@148: } Chris@148: Chris@148: int td = m_timingDivision; Chris@148: if (td == 0) td = 96; Chris@148: Chris@148: unsigned long melapsed = midiTime - tempoMIDITime; Chris@148: double quarters = double(melapsed) / double(td); Chris@148: double seconds = (60.0 * quarters) / tempo; Chris@148: Chris@148: /* Chris@148: std::cerr << "MIDIFileReader::getTimeForMIDITime(" << midiTime << ")" Chris@148: << std::endl; Chris@148: std::cerr << "timing division = " << td << std::endl; Chris@148: std::cerr << "nearest tempo event (of " << m_tempoMap.size() << ") is at " << tempoMIDITime << " (" Chris@148: << tempoRealTime << ")" << std::endl; Chris@148: std::cerr << "quarters since then = " << quarters << std::endl; Chris@148: std::cerr << "tempo = " << tempo << " quarters per minute" << std::endl; Chris@148: std::cerr << "seconds since then = " << seconds << std::endl; Chris@148: std::cerr << "resulting time = " << (tempoRealTime + RealTime::fromSeconds(seconds)) << std::endl; Chris@148: */ Chris@148: Chris@148: return tempoRealTime + RealTime::fromSeconds(seconds); Chris@148: } Chris@148: Chris@148: Model * Chris@148: MIDIFileReader::load() const Chris@148: { Chris@148: if (!isOK()) return 0; Chris@148: Chris@148: if (m_loadableTracks.empty()) { Chris@148: QMessageBox::critical(0, tr("No notes in MIDI file"), Chris@148: tr("MIDI file \"%1\" has no notes in any track") Chris@148: .arg(m_path)); Chris@148: return 0; Chris@148: } Chris@148: Chris@148: std::set tracksToLoad; Chris@148: Chris@148: if (m_loadableTracks.size() == 1) { Chris@148: Chris@148: tracksToLoad.insert(*m_loadableTracks.begin()); Chris@148: Chris@148: } else { Chris@148: Chris@148: QStringList available; Chris@148: QString allTracks = tr("Merge all tracks"); Chris@148: QString allNonPercussion = tr("Merge all non-percussion tracks"); Chris@148: Chris@148: int nonTrackItems = 1; Chris@148: Chris@148: available << allTracks; Chris@148: Chris@148: if (!m_percussionTracks.empty() && Chris@148: (m_percussionTracks.size() < m_loadableTracks.size())) { Chris@148: available << allNonPercussion; Chris@148: ++nonTrackItems; Chris@148: } Chris@148: Chris@148: for (set::iterator i = m_loadableTracks.begin(); Chris@148: i != m_loadableTracks.end(); ++i) { Chris@148: Chris@148: unsigned int trackNo = *i; Chris@148: QString label; Chris@148: Chris@148: QString perc; Chris@148: if (m_percussionTracks.find(trackNo) != m_percussionTracks.end()) { Chris@148: perc = tr(" - uses GM percussion channel"); Chris@148: } Chris@148: Chris@148: if (m_trackNames.find(trackNo) != m_trackNames.end()) { Chris@148: label = tr("Track %1 (%2)%3") Chris@148: .arg(trackNo).arg(m_trackNames.find(trackNo)->second) Chris@148: .arg(perc); Chris@148: } else { Chris@148: label = tr("Track %1 (untitled)%3").arg(trackNo).arg(perc); Chris@148: } Chris@148: available << label; Chris@148: } Chris@148: Chris@148: bool ok = false; Chris@148: QString selected = QInputDialog::getItem Chris@148: (0, tr("Select track or tracks to import"), Chris@308: tr("Select track to import

You can only import this file as a single annotation layer, but the file contains more than one track, or notes on more than one channel.

Please select the track or merged tracks you wish to import:"), Chris@148: available, 0, false, &ok); Chris@148: Chris@148: if (!ok || selected.isEmpty()) return 0; Chris@148: Chris@148: if (selected == allTracks || selected == allNonPercussion) { Chris@148: Chris@148: for (set::iterator i = m_loadableTracks.begin(); Chris@148: i != m_loadableTracks.end(); ++i) { Chris@148: Chris@148: if (selected == allTracks || Chris@148: m_percussionTracks.find(*i) == m_percussionTracks.end()) { Chris@148: Chris@148: tracksToLoad.insert(*i); Chris@148: } Chris@148: } Chris@148: Chris@148: } else { Chris@148: Chris@148: int j = nonTrackItems; Chris@148: Chris@148: for (set::iterator i = m_loadableTracks.begin(); Chris@148: i != m_loadableTracks.end(); ++i) { Chris@148: Chris@148: if (selected == available[j]) { Chris@148: tracksToLoad.insert(*i); Chris@148: break; Chris@148: } Chris@148: Chris@148: ++j; Chris@148: } Chris@148: } Chris@148: } Chris@148: Chris@148: if (tracksToLoad.empty()) return 0; Chris@148: Chris@148: size_t n = tracksToLoad.size(), count = 0; Chris@148: Model *model = 0; Chris@148: Chris@148: for (std::set::iterator i = tracksToLoad.begin(); Chris@148: i != tracksToLoad.end(); ++i) { Chris@148: Chris@148: int minProgress = (100 * count) / n; Chris@148: int progressAmount = 100 / n; Chris@148: Chris@148: model = loadTrack(*i, model, minProgress, progressAmount); Chris@148: Chris@148: ++count; Chris@148: } Chris@148: Chris@148: if (dynamic_cast(model)) { Chris@148: dynamic_cast(model)->setCompletion(100); Chris@148: } Chris@148: Chris@148: return model; Chris@148: } Chris@148: Chris@148: Model * Chris@148: MIDIFileReader::loadTrack(unsigned int trackToLoad, Chris@148: Model *existingModel, Chris@148: int minProgress, Chris@148: int progressAmount) const Chris@148: { Chris@148: if (m_midiComposition.find(trackToLoad) == m_midiComposition.end()) { Chris@148: return 0; Chris@148: } Chris@148: Chris@148: NoteModel *model = 0; Chris@148: Chris@148: if (existingModel) { Chris@148: model = dynamic_cast(existingModel); Chris@148: if (!model) { Chris@148: std::cerr << "WARNING: MIDIFileReader::loadTrack: Existing model given, but it isn't a NoteModel -- ignoring it" << std::endl; Chris@148: } Chris@148: } Chris@148: Chris@148: if (!model) { Chris@148: model = new NoteModel(m_mainModelSampleRate, 1, 0.0, 0.0, false); Chris@148: model->setValueQuantization(1.0); Chris@148: } Chris@148: Chris@148: const MIDITrack &track = m_midiComposition.find(trackToLoad)->second; Chris@148: Chris@148: size_t totalEvents = track.size(); Chris@148: size_t count = 0; Chris@148: Chris@148: bool minorKey = false; Chris@148: bool sharpKey = true; Chris@148: Chris@148: for (MIDITrack::const_iterator i = track.begin(); i != track.end(); ++i) { Chris@148: Chris@148: RealTime rt = getTimeForMIDITime((*i)->getTime()); Chris@148: Chris@148: // We ignore most of these event types for now, though in Chris@148: // theory some of the text ones could usefully be incorporated Chris@148: Chris@148: if ((*i)->isMeta()) { Chris@148: Chris@148: switch((*i)->getMetaEventCode()) { Chris@148: Chris@148: case MIDI_KEY_SIGNATURE: Chris@148: minorKey = (int((*i)->getMetaMessage()[1]) != 0); Chris@148: sharpKey = (int((*i)->getMetaMessage()[0]) >= 0); Chris@148: break; Chris@148: Chris@148: case MIDI_TEXT_EVENT: Chris@148: case MIDI_LYRIC: Chris@148: case MIDI_TEXT_MARKER: Chris@148: case MIDI_COPYRIGHT_NOTICE: Chris@148: case MIDI_TRACK_NAME: Chris@148: // The text events that we could potentially use Chris@148: break; Chris@148: Chris@148: case MIDI_SET_TEMPO: Chris@148: // Already dealt with in a separate pass previously Chris@148: break; Chris@148: Chris@148: case MIDI_TIME_SIGNATURE: Chris@148: // Not yet! Chris@148: break; Chris@148: Chris@148: case MIDI_SEQUENCE_NUMBER: Chris@148: case MIDI_CHANNEL_PREFIX_OR_PORT: Chris@148: case MIDI_INSTRUMENT_NAME: Chris@148: case MIDI_CUE_POINT: Chris@148: case MIDI_CHANNEL_PREFIX: Chris@148: case MIDI_SEQUENCER_SPECIFIC: Chris@148: case MIDI_SMPTE_OFFSET: Chris@148: default: Chris@148: break; Chris@148: } Chris@148: Chris@148: } else { Chris@148: Chris@148: switch ((*i)->getMessageType()) { Chris@148: Chris@148: case MIDI_NOTE_ON: Chris@148: Chris@148: if ((*i)->getVelocity() == 0) break; // effective note-off Chris@148: else { Chris@148: RealTime endRT = getTimeForMIDITime((*i)->getTime() + Chris@148: (*i)->getDuration()); Chris@148: Chris@148: long startFrame = RealTime::realTime2Frame Chris@148: (rt, model->getSampleRate()); Chris@148: Chris@148: long endFrame = RealTime::realTime2Frame Chris@148: (endRT, model->getSampleRate()); Chris@148: Chris@148: QString pitchLabel = Pitch::getPitchLabel((*i)->getPitch(), Chris@148: 0, Chris@148: !sharpKey); Chris@148: Chris@148: QString noteLabel = tr("%1 - vel %2") Chris@148: .arg(pitchLabel).arg(int((*i)->getVelocity())); Chris@148: Chris@384: float level = float((*i)->getVelocity()) / 128.f; Chris@384: Chris@148: Note note(startFrame, (*i)->getPitch(), Chris@384: endFrame - startFrame, level, noteLabel); Chris@148: Chris@148: // std::cerr << "Adding note " << startFrame << "," << (endFrame-startFrame) << " : " << int((*i)->getPitch()) << std::endl; Chris@148: Chris@148: model->addPoint(note); Chris@148: break; Chris@148: } Chris@148: Chris@148: case MIDI_PITCH_BEND: Chris@148: // I guess we could make some use of this... Chris@148: break; Chris@148: Chris@148: case MIDI_NOTE_OFF: Chris@148: case MIDI_PROG_CHANGE: Chris@148: case MIDI_CTRL_CHANGE: Chris@148: case MIDI_SYSTEM_EXCLUSIVE: Chris@148: case MIDI_POLY_AFTERTOUCH: Chris@148: case MIDI_CHNL_AFTERTOUCH: Chris@148: break; Chris@148: Chris@148: default: Chris@148: break; Chris@148: } Chris@148: } Chris@148: Chris@148: model->setCompletion(minProgress + Chris@148: (count * progressAmount) / totalEvents); Chris@148: ++count; Chris@148: } Chris@148: Chris@148: return model; Chris@148: } Chris@148: Chris@148: