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 <iostream>
Chris@148: #include <fstream>
Chris@148: #include <string>
Chris@148: #include <cstdio>
Chris@148: #include <algorithm>
Chris@148: 
Chris@148: #include "MIDIFileReader.h"
Chris@148: 
Chris@560: #include "data/midi/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 <QString>
Chris@1030: #include <QFileInfo>
Chris@148: 
Chris@148: #include <sstream>
Chris@148: 
Chris@843: #include "base/Debug.h"
Chris@843: 
Chris@148: using std::string;
Chris@148: using std::ifstream;
Chris@148: using std::stringstream;
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@690: //#define MIDI_SVDEBUG 1
Chris@148: 
Chris@148: 
Chris@148: MIDIFileReader::MIDIFileReader(QString path,
Chris@392:                                MIDIFileImportPreferenceAcquirer *acquirer,
Chris@1047: 			       sv_samplerate_t mainModelSampleRate) :
Chris@613:     m_smpte(false),
Chris@148:     m_timingDivision(0),
Chris@613:     m_fps(0),
Chris@613:     m_subframes(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@392:     m_mainModelSampleRate(mainModelSampleRate),
Chris@392:     m_acquirer(acquirer)
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@690:     SVDEBUG << "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@1038:         std::streamoff off = m_midiFile->tellg();
Chris@1038: 	m_fileSize = 0;
Chris@1038:         if (off > 0) m_fileSize = off;
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@690: 	    SVDEBUG << "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@690:         SVDEBUG << "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@690:         SVDEBUG << "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@690: 	SVDEBUG << "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@690:         SVDEBUG << "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@613:     if (m_timingDivision >= 32768) {
Chris@613:         m_smpte = true;
Chris@613:         m_fps = 256 - (m_timingDivision >> 8);
Chris@613:         m_subframes = (m_timingDivision & 0xff);
Chris@613:     } else {
Chris@613:         m_smpte = false;
Chris@148:     }
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@1038:     long messageLength;
Chris@1038:     long deltaTime;
Chris@1038:     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<unsigned int> but we need -1 to indicate
Chris@148:     // "not yet used"
Chris@148:     vector<int> 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<int, unsigned long> 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@690: 	    SVDEBUG << "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@690:                     SVDEBUG << "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:             default:
Chris@148: #ifdef MIDI_DEBUG
Chris@690:                 SVDEBUG << "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@965: 	 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@613:                 (*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@843:     cerr << "updateTempoMap for track " << track << " (" << m_midiComposition[track].size() << " events)" << 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@843: 	    cerr << "updateTempoMap: have tempo, it's " << tempo << " at " << (*i)->getTime() << 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@690:     SVDEBUG << "MIDIFileReader::getTimeForMIDITime(" << midiTime << ")"
Chris@687: 	      << endl;
Chris@690:     SVDEBUG << "timing division = " << td << endl;
Chris@843:     cerr << "nearest tempo event (of " << m_tempoMap.size() << ") is at " << tempoMIDITime << " ("
Chris@843: 	      << tempoRealTime << ")" << endl;
Chris@843:     cerr << "quarters since then = " << quarters << endl;
Chris@843:     cerr << "tempo = " << tempo << " quarters per minute" << endl;
Chris@843:     cerr << "seconds since then = " << seconds << endl;
Chris@690:     SVDEBUG << "resulting time = " << (tempoRealTime + RealTime::fromSeconds(seconds)) << 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@392:         if (m_acquirer) {
Chris@392:             m_acquirer->showError
Chris@392:                 (tr("MIDI file \"%1\" has no notes in any track").arg(m_path));
Chris@392:         }
Chris@148: 	return 0;
Chris@148:     }
Chris@148: 
Chris@148:     std::set<unsigned int> 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@392:         QStringList displayNames;
Chris@148: 
Chris@148: 	for (set<unsigned int>::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@392: 
Chris@392:             displayNames << label;
Chris@148: 	}
Chris@148: 
Chris@392:         QString singleTrack;
Chris@148: 
Chris@392:         bool haveSomePercussion = 
Chris@392:             (!m_percussionTracks.empty() &&
Chris@392:              (m_percussionTracks.size() < m_loadableTracks.size()));
Chris@148: 
Chris@392:         MIDIFileImportPreferenceAcquirer::TrackPreference pref;
Chris@392: 
Chris@392:         if (m_acquirer) {
Chris@392:             pref = m_acquirer->getTrackImportPreference(displayNames,
Chris@392:                                                         haveSomePercussion,
Chris@392:                                                         singleTrack);
Chris@392:         } else {
Chris@392:             pref = MIDIFileImportPreferenceAcquirer::MergeAllTracks;
Chris@392:         }
Chris@392: 
Chris@392:         if (pref == MIDIFileImportPreferenceAcquirer::ImportNothing) return 0;
Chris@392: 
Chris@392:         if (pref == MIDIFileImportPreferenceAcquirer::MergeAllTracks ||
Chris@392:             pref == MIDIFileImportPreferenceAcquirer::MergeAllNonPercussionTracks) {
Chris@392:             
Chris@392:             for (set<unsigned int>::iterator i = m_loadableTracks.begin();
Chris@392:                  i != m_loadableTracks.end(); ++i) {
Chris@392:                 
Chris@392: 		if (pref == MIDIFileImportPreferenceAcquirer::MergeAllTracks ||
Chris@148: 		    m_percussionTracks.find(*i) == m_percussionTracks.end()) {
Chris@392:                     
Chris@148: 		    tracksToLoad.insert(*i);
Chris@148: 		}
Chris@148: 	    }
Chris@148: 
Chris@148: 	} else {
Chris@148: 	    
Chris@392: 	    int j = 0;
Chris@148: 
Chris@148: 	    for (set<unsigned int>::iterator i = m_loadableTracks.begin();
Chris@148: 		 i != m_loadableTracks.end(); ++i) {
Chris@148: 		
Chris@392: 		if (singleTrack == displayNames[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@1038:     int n = int(tracksToLoad.size()), count = 0;
Chris@148:     Model *model = 0;
Chris@148: 
Chris@148:     for (std::set<unsigned int>::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<NoteModel *>(model)) {
Chris@148: 	dynamic_cast<NoteModel *>(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<NoteModel *>(existingModel);
Chris@148: 	if (!model) {
Chris@843: 	    cerr << "WARNING: MIDIFileReader::loadTrack: Existing model given, but it isn't a NoteModel -- ignoring it" << 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@1030:         model->setObjectName(QFileInfo(m_path).fileName());
Chris@148:     }
Chris@148: 
Chris@148:     const MIDITrack &track = m_midiComposition.find(trackToLoad)->second;
Chris@148: 
Chris@1038:     int totalEvents = int(track.size());
Chris@929:     int count = 0;
Chris@148: 
Chris@148:     bool sharpKey = true;
Chris@148: 
Chris@148:     for (MIDITrack::const_iterator i = track.begin(); i != track.end(); ++i) {
Chris@148: 
Chris@613:         RealTime rt;
Chris@613:         unsigned long midiTime = (*i)->getTime();
Chris@613: 
Chris@613:         if (m_smpte) {
Chris@613:             rt = RealTime::frame2RealTime(midiTime, m_fps * m_subframes);
Chris@613:         } else {
Chris@613:             rt = getTimeForMIDITime(midiTime);
Chris@613:         }
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@929: 		// 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@613: 		    RealTime endRT;
Chris@613:                     unsigned long endMidiTime = (*i)->getTime() + (*i)->getDuration();
Chris@613:                     if (m_smpte) {
Chris@613:                         endRT = RealTime::frame2RealTime(endMidiTime, m_fps * m_subframes);
Chris@613:                     } else {
Chris@613:                         endRT = getTimeForMIDITime(endMidiTime);
Chris@613:                     }
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@340:                     float level = float((*i)->getVelocity()) / 128.f;
Chris@340: 
Chris@148: 		    Note note(startFrame, (*i)->getPitch(),
Chris@340: 			      endFrame - startFrame, level, noteLabel);
Chris@148: 
Chris@690: //		    SVDEBUG << "Adding note " << startFrame << "," << (endFrame-startFrame) << " : " << int((*i)->getPitch()) << 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: