Mercurial > hg > svcore
view data/fileio/MIDIFileReader.cpp @ 201:d892237cf790
* Remove some obsolete files
author | Chris Cannam |
---|---|
date | Wed, 15 Nov 2006 20:00:48 +0000 |
parents | 4b2ea82fd0ed |
children | 73537d900d4b |
line wrap: on
line source
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ /* Sonic Visualiser An audio file viewer and annotation editor. Centre for Digital Music, Queen Mary, University of London. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. See the file COPYING included with this distribution for more information. */ /* This is a modified version of a source file from the Rosegarden MIDI and audio sequencer and notation editor. This file copyright 2000-2006 Richard Bown and Chris Cannam. */ #include <iostream> #include <fstream> #include <string> #include <cstdio> #include <algorithm> #include "MIDIFileReader.h" #include "model/Model.h" #include "base/Pitch.h" #include "base/RealTime.h" #include "model/NoteModel.h" #include <QString> #include <QMessageBox> #include <QInputDialog> #include <sstream> using std::string; using std::ifstream; using std::stringstream; using std::cerr; using std::endl; using std::ends; using std::ios; using std::vector; using std::map; using std::set; //#define MIDI_DEBUG 1 static const char *const MIDI_FILE_HEADER = "MThd"; static const char *const MIDI_TRACK_HEADER = "MTrk"; static const MIDIFileReader::MIDIByte MIDI_STATUS_BYTE_MASK = 0x80; static const MIDIFileReader::MIDIByte MIDI_MESSAGE_TYPE_MASK = 0xF0; static const MIDIFileReader::MIDIByte MIDI_CHANNEL_NUM_MASK = 0x0F; static const MIDIFileReader::MIDIByte MIDI_NOTE_OFF = 0x80; static const MIDIFileReader::MIDIByte MIDI_NOTE_ON = 0x90; static const MIDIFileReader::MIDIByte MIDI_POLY_AFTERTOUCH = 0xA0; static const MIDIFileReader::MIDIByte MIDI_CTRL_CHANGE = 0xB0; static const MIDIFileReader::MIDIByte MIDI_PROG_CHANGE = 0xC0; static const MIDIFileReader::MIDIByte MIDI_CHNL_AFTERTOUCH = 0xD0; static const MIDIFileReader::MIDIByte MIDI_PITCH_BEND = 0xE0; static const MIDIFileReader::MIDIByte MIDI_SELECT_CHNL_MODE = 0xB0; static const MIDIFileReader::MIDIByte MIDI_SYSTEM_EXCLUSIVE = 0xF0; static const MIDIFileReader::MIDIByte MIDI_TC_QUARTER_FRAME = 0xF1; static const MIDIFileReader::MIDIByte MIDI_SONG_POSITION_PTR = 0xF2; static const MIDIFileReader::MIDIByte MIDI_SONG_SELECT = 0xF3; static const MIDIFileReader::MIDIByte MIDI_TUNE_REQUEST = 0xF6; static const MIDIFileReader::MIDIByte MIDI_END_OF_EXCLUSIVE = 0xF7; static const MIDIFileReader::MIDIByte MIDI_TIMING_CLOCK = 0xF8; static const MIDIFileReader::MIDIByte MIDI_START = 0xFA; static const MIDIFileReader::MIDIByte MIDI_CONTINUE = 0xFB; static const MIDIFileReader::MIDIByte MIDI_STOP = 0xFC; static const MIDIFileReader::MIDIByte MIDI_ACTIVE_SENSING = 0xFE; static const MIDIFileReader::MIDIByte MIDI_SYSTEM_RESET = 0xFF; static const MIDIFileReader::MIDIByte MIDI_SYSEX_NONCOMMERCIAL = 0x7D; static const MIDIFileReader::MIDIByte MIDI_SYSEX_NON_RT = 0x7E; static const MIDIFileReader::MIDIByte MIDI_SYSEX_RT = 0x7F; static const MIDIFileReader::MIDIByte MIDI_SYSEX_RT_COMMAND = 0x06; static const MIDIFileReader::MIDIByte MIDI_SYSEX_RT_RESPONSE = 0x07; static const MIDIFileReader::MIDIByte MIDI_MMC_STOP = 0x01; static const MIDIFileReader::MIDIByte MIDI_MMC_PLAY = 0x02; static const MIDIFileReader::MIDIByte MIDI_MMC_DEFERRED_PLAY = 0x03; static const MIDIFileReader::MIDIByte MIDI_MMC_FAST_FORWARD = 0x04; static const MIDIFileReader::MIDIByte MIDI_MMC_REWIND = 0x05; static const MIDIFileReader::MIDIByte MIDI_MMC_RECORD_STROBE = 0x06; static const MIDIFileReader::MIDIByte MIDI_MMC_RECORD_EXIT = 0x07; static const MIDIFileReader::MIDIByte MIDI_MMC_RECORD_PAUSE = 0x08; static const MIDIFileReader::MIDIByte MIDI_MMC_PAUSE = 0x08; static const MIDIFileReader::MIDIByte MIDI_MMC_EJECT = 0x0A; static const MIDIFileReader::MIDIByte MIDI_MMC_LOCATE = 0x44; static const MIDIFileReader::MIDIByte MIDI_FILE_META_EVENT = 0xFF; static const MIDIFileReader::MIDIByte MIDI_SEQUENCE_NUMBER = 0x00; static const MIDIFileReader::MIDIByte MIDI_TEXT_EVENT = 0x01; static const MIDIFileReader::MIDIByte MIDI_COPYRIGHT_NOTICE = 0x02; static const MIDIFileReader::MIDIByte MIDI_TRACK_NAME = 0x03; static const MIDIFileReader::MIDIByte MIDI_INSTRUMENT_NAME = 0x04; static const MIDIFileReader::MIDIByte MIDI_LYRIC = 0x05; static const MIDIFileReader::MIDIByte MIDI_TEXT_MARKER = 0x06; static const MIDIFileReader::MIDIByte MIDI_CUE_POINT = 0x07; static const MIDIFileReader::MIDIByte MIDI_CHANNEL_PREFIX = 0x20; static const MIDIFileReader::MIDIByte MIDI_CHANNEL_PREFIX_OR_PORT = 0x21; static const MIDIFileReader::MIDIByte MIDI_END_OF_TRACK = 0x2F; static const MIDIFileReader::MIDIByte MIDI_SET_TEMPO = 0x51; static const MIDIFileReader::MIDIByte MIDI_SMPTE_OFFSET = 0x54; static const MIDIFileReader::MIDIByte MIDI_TIME_SIGNATURE = 0x58; static const MIDIFileReader::MIDIByte MIDI_KEY_SIGNATURE = 0x59; static const MIDIFileReader::MIDIByte MIDI_SEQUENCER_SPECIFIC = 0x7F; static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_BANK_MSB = 0x00; static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_VOLUME = 0x07; static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_BANK_LSB = 0x20; static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_MODULATION = 0x01; static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_PAN = 0x0A; static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_SUSTAIN = 0x40; static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_RESONANCE = 0x47; static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_RELEASE = 0x48; static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_ATTACK = 0x49; static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_FILTER = 0x4A; static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_REVERB = 0x5B; static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_CHORUS = 0x5D; static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_NRPN_1 = 0x62; static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_NRPN_2 = 0x63; static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_RPN_1 = 0x64; static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_RPN_2 = 0x65; static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_SOUNDS_OFF = 0x78; static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_RESET = 0x79; static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_LOCAL = 0x7A; static const MIDIFileReader::MIDIByte MIDI_CONTROLLER_ALL_NOTES_OFF = 0x7B; static const MIDIFileReader::MIDIByte MIDI_PERCUSSION_CHANNEL = 9; class MIDIEvent { public: typedef MIDIFileReader::MIDIByte MIDIByte; MIDIEvent(unsigned long deltaTime, MIDIByte eventCode, MIDIByte data1 = 0, MIDIByte data2 = 0) : m_deltaTime(deltaTime), m_duration(0), m_eventCode(eventCode), m_data1(data1), m_data2(data2), m_metaEventCode(0) { } MIDIEvent(unsigned long deltaTime, MIDIByte eventCode, MIDIByte metaEventCode, const string &metaMessage) : m_deltaTime(deltaTime), m_duration(0), m_eventCode(eventCode), m_data1(0), m_data2(0), m_metaEventCode(metaEventCode), m_metaMessage(metaMessage) { } MIDIEvent(unsigned long deltaTime, MIDIByte eventCode, const string &sysEx) : m_deltaTime(deltaTime), m_duration(0), m_eventCode(eventCode), m_data1(0), m_data2(0), m_metaEventCode(0), m_metaMessage(sysEx) { } ~MIDIEvent() { } void setTime(const unsigned long &time) { m_deltaTime = time; } void setDuration(const unsigned long& duration) { m_duration = duration;} unsigned long addTime(const unsigned long &time) { m_deltaTime += time; return m_deltaTime; } MIDIByte getMessageType() const { return (m_eventCode & MIDI_MESSAGE_TYPE_MASK); } MIDIByte getChannelNumber() const { return (m_eventCode & MIDI_CHANNEL_NUM_MASK); } unsigned long getTime() const { return m_deltaTime; } unsigned long getDuration() const { return m_duration; } MIDIByte getPitch() const { return m_data1; } MIDIByte getVelocity() const { return m_data2; } MIDIByte getData1() const { return m_data1; } MIDIByte getData2() const { return m_data2; } MIDIByte getEventCode() const { return m_eventCode; } bool isMeta() const { return (m_eventCode == MIDI_FILE_META_EVENT); } MIDIByte getMetaEventCode() const { return m_metaEventCode; } string getMetaMessage() const { return m_metaMessage; } void setMetaMessage(const string &meta) { m_metaMessage = meta; } friend bool operator<(const MIDIEvent &a, const MIDIEvent &b); private: MIDIEvent& operator=(const MIDIEvent); unsigned long m_deltaTime; unsigned long m_duration; MIDIByte m_eventCode; MIDIByte m_data1; // or Note MIDIByte m_data2; // or Velocity MIDIByte m_metaEventCode; string m_metaMessage; }; // Comparator for sorting // struct MIDIEventCmp { bool operator()(const MIDIEvent &mE1, const MIDIEvent &mE2) const { return mE1.getTime() < mE2.getTime(); } bool operator()(const MIDIEvent *mE1, const MIDIEvent *mE2) const { return mE1->getTime() < mE2->getTime(); } }; class MIDIException : virtual public std::exception { public: MIDIException(QString message) throw() : m_message(message) { cerr << "WARNING: MIDI exception: " << message.toLocal8Bit().data() << endl; } virtual ~MIDIException() throw() { } virtual const char *what() const throw() { return m_message.toLocal8Bit().data(); } protected: QString m_message; }; MIDIFileReader::MIDIFileReader(QString path, size_t mainModelSampleRate) : m_timingDivision(0), m_format(MIDI_FILE_BAD_FORMAT), m_numberOfTracks(0), m_trackByteCount(0), m_decrementCount(false), m_path(path), m_midiFile(0), m_fileSize(0), m_mainModelSampleRate(mainModelSampleRate) { if (parseFile()) { m_error = ""; } } MIDIFileReader::~MIDIFileReader() { for (MIDIComposition::iterator i = m_midiComposition.begin(); i != m_midiComposition.end(); ++i) { for (MIDITrack::iterator j = i->second.begin(); j != i->second.end(); ++j) { delete *j; } i->second.clear(); } m_midiComposition.clear(); } bool MIDIFileReader::isOK() const { return (m_error == ""); } QString MIDIFileReader::getError() const { return m_error; } long MIDIFileReader::midiBytesToLong(const string& bytes) { if (bytes.length() != 4) { throw MIDIException(tr("Wrong length for long data in MIDI stream (%1, should be %2)").arg(bytes.length()).arg(4)); } long longRet = ((long)(((MIDIByte)bytes[0]) << 24)) | ((long)(((MIDIByte)bytes[1]) << 16)) | ((long)(((MIDIByte)bytes[2]) << 8)) | ((long)((MIDIByte)(bytes[3]))); return longRet; } int MIDIFileReader::midiBytesToInt(const string& bytes) { if (bytes.length() != 2) { throw MIDIException(tr("Wrong length for int data in MIDI stream (%1, should be %2)").arg(bytes.length()).arg(2)); } int intRet = ((int)(((MIDIByte)bytes[0]) << 8)) | ((int)(((MIDIByte)bytes[1]))); return(intRet); } // Gets a single byte from the MIDI byte stream. For each track // section we can read only a specified number of bytes held in // m_trackByteCount. // MIDIFileReader::MIDIByte MIDIFileReader::getMIDIByte() { if (!m_midiFile) { throw MIDIException(tr("getMIDIByte called but no MIDI file open")); } if (m_midiFile->eof()) { throw MIDIException(tr("End of MIDI file encountered while reading")); } if (m_decrementCount && m_trackByteCount <= 0) { throw MIDIException(tr("Attempt to get more bytes than expected on Track")); } char byte; if (m_midiFile->read(&byte, 1)) { --m_trackByteCount; return (MIDIByte)byte; } throw MIDIException(tr("Attempt to read past MIDI file end")); } // Gets a specified number of bytes from the MIDI byte stream. For // each track section we can read only a specified number of bytes // held in m_trackByteCount. // string MIDIFileReader::getMIDIBytes(unsigned long numberOfBytes) { if (!m_midiFile) { throw MIDIException(tr("getMIDIBytes called but no MIDI file open")); } if (m_midiFile->eof()) { throw MIDIException(tr("End of MIDI file encountered while reading")); } if (m_decrementCount && (numberOfBytes > (unsigned long)m_trackByteCount)) { throw MIDIException(tr("Attempt to get more bytes than available on Track (%1, only have %2)").arg(numberOfBytes).arg(m_trackByteCount)); } string stringRet; char fileMIDIByte; while (stringRet.length() < numberOfBytes && m_midiFile->read(&fileMIDIByte, 1)) { stringRet += fileMIDIByte; } // if we've reached the end of file without fulfilling the // quota then panic as our parsing has performed incorrectly // if (stringRet.length() < numberOfBytes) { stringRet = ""; throw MIDIException(tr("Attempt to read past MIDI file end")); } // decrement the byte count if (m_decrementCount) m_trackByteCount -= stringRet.length(); return stringRet; } // Get a long number of variable length from the MIDI byte stream. // long MIDIFileReader::getNumberFromMIDIBytes(int firstByte) { if (!m_midiFile) { throw MIDIException(tr("getNumberFromMIDIBytes called but no MIDI file open")); } long longRet = 0; MIDIByte midiByte; if (firstByte >= 0) { midiByte = (MIDIByte)firstByte; } else if (m_midiFile->eof()) { return longRet; } else { midiByte = getMIDIByte(); } longRet = midiByte; if (midiByte & 0x80) { longRet &= 0x7F; do { midiByte = getMIDIByte(); longRet = (longRet << 7) + (midiByte & 0x7F); } while (!m_midiFile->eof() && (midiByte & 0x80)); } return longRet; } // Seek to the next track in the midi file and set the number // of bytes to be read in the counter m_trackByteCount. // bool MIDIFileReader::skipToNextTrack() { if (!m_midiFile) { throw MIDIException(tr("skipToNextTrack called but no MIDI file open")); } string buffer, buffer2; m_trackByteCount = -1; m_decrementCount = false; while (!m_midiFile->eof() && (m_decrementCount == false)) { buffer = getMIDIBytes(4); if (buffer.compare(0, 4, MIDI_TRACK_HEADER) == 0) { m_trackByteCount = midiBytesToLong(getMIDIBytes(4)); m_decrementCount = true; } } if (m_trackByteCount == -1) { // we haven't found a track return false; } else { return true; } } // Read in a MIDI file. The parsing process throws exceptions back up // here if we run into trouble which we can then pass back out to // whoever called us using a nice bool. // bool MIDIFileReader::parseFile() { m_error = ""; #ifdef MIDI_DEBUG cerr << "MIDIFileReader::open() : fileName = " << m_fileName.c_str() << endl; #endif // Open the file m_midiFile = new ifstream(m_path.toLocal8Bit().data(), ios::in | ios::binary); if (!*m_midiFile) { m_error = "File not found or not readable."; m_format = MIDI_FILE_BAD_FORMAT; delete m_midiFile; return false; } bool retval = false; try { // Set file size so we can count it off // m_midiFile->seekg(0, ios::end); m_fileSize = m_midiFile->tellg(); m_midiFile->seekg(0, ios::beg); // Parse the MIDI header first. The first 14 bytes of the file. if (!parseHeader(getMIDIBytes(14))) { m_format = MIDI_FILE_BAD_FORMAT; m_error = "Not a MIDI file."; goto done; } unsigned int i = 0; for (unsigned int j = 0; j < m_numberOfTracks; ++j) { #ifdef MIDI_DEBUG cerr << "Parsing Track " << j << endl; #endif if (!skipToNextTrack()) { #ifdef MIDI_DEBUG cerr << "Couldn't find Track " << j << endl; #endif m_error = "File corrupted or in non-standard format?"; m_format = MIDI_FILE_BAD_FORMAT; goto done; } #ifdef MIDI_DEBUG cerr << "Track has " << m_trackByteCount << " bytes" << endl; #endif // Run through the events taking them into our internal // representation. if (!parseTrack(i)) { #ifdef MIDI_DEBUG cerr << "Track " << j << " parsing failed" << endl; #endif m_error = "File corrupted or in non-standard format?"; m_format = MIDI_FILE_BAD_FORMAT; goto done; } ++i; // j is the source track number, i the destination } m_numberOfTracks = i; retval = true; } catch (MIDIException e) { cerr << "MIDIFileReader::open() - caught exception - " << e.what() << endl; m_error = e.what(); } done: m_midiFile->close(); delete m_midiFile; for (unsigned int track = 0; track < m_numberOfTracks; ++track) { // Convert the deltaTime to an absolute time since the track // start. The addTime method returns the sum of the current // MIDI Event delta time plus the argument. unsigned long acc = 0; for (MIDITrack::iterator i = m_midiComposition[track].begin(); i != m_midiComposition[track].end(); ++i) { acc = (*i)->addTime(acc); } if (consolidateNoteOffEvents(track)) { // returns true if some notes exist m_loadableTracks.insert(track); } } for (unsigned int track = 0; track < m_numberOfTracks; ++track) { updateTempoMap(track); } calculateTempoTimestamps(); return retval; } // Parse and ensure the MIDI Header is legitimate // bool MIDIFileReader::parseHeader(const string &midiHeader) { if (midiHeader.size() < 14) { #ifdef MIDI_DEBUG cerr << "MIDIFileReader::parseHeader() - file header undersized" << endl; #endif return false; } if (midiHeader.compare(0, 4, MIDI_FILE_HEADER) != 0) { #ifdef MIDI_DEBUG cerr << "MIDIFileReader::parseHeader()" << "- file header not found or malformed" << endl; #endif return false; } if (midiBytesToLong(midiHeader.substr(4,4)) != 6L) { #ifdef MIDI_DEBUG cerr << "MIDIFileReader::parseHeader()" << " - header length incorrect" << endl; #endif return false; } m_format = (MIDIFileFormatType) midiBytesToInt(midiHeader.substr(8,2)); m_numberOfTracks = midiBytesToInt(midiHeader.substr(10,2)); m_timingDivision = midiBytesToInt(midiHeader.substr(12,2)); if (m_format == MIDI_SEQUENTIAL_TRACK_FILE) { #ifdef MIDI_DEBUG cerr << "MIDIFileReader::parseHeader()" << "- can't load sequential track file" << endl; #endif return false; } #ifdef MIDI_DEBUG if (m_timingDivision < 0) { cerr << "MIDIFileReader::parseHeader()" << " - file uses SMPTE timing" << endl; } #endif return true; } // Extract the contents from a MIDI file track and places it into // our local map of MIDI events. // bool MIDIFileReader::parseTrack(unsigned int &lastTrackNum) { MIDIByte midiByte, metaEventCode, data1, data2; MIDIByte eventCode = 0x80; string metaMessage; unsigned int messageLength; unsigned long deltaTime; unsigned long accumulatedTime = 0; // The trackNum passed in to this method is the default track for // all events provided they're all on the same channel. If we find // events on more than one channel, we increment trackNum and record // the mapping from channel to trackNum in this channelTrackMap. // We then return the new trackNum by reference so the calling // method knows we've got more tracks than expected. // This would be a vector<unsigned int> but we need -1 to indicate // "not yet used" vector<int> channelTrackMap(16, -1); // This is used to store the last absolute time found on each track, // allowing us to modify delta-times correctly when separating events // out from one to multiple tracks // map<int, unsigned long> trackTimeMap; // Meta-events don't have a channel, so we place them in a fixed // track number instead unsigned int metaTrack = lastTrackNum; // Remember the last non-meta status byte (-1 if we haven't seen one) int runningStatus = -1; bool firstTrack = true; while (!m_midiFile->eof() && (m_trackByteCount > 0)) { if (eventCode < 0x80) { #ifdef MIDI_DEBUG cerr << "WARNING: Invalid event code " << eventCode << " in MIDI file" << endl; #endif throw MIDIException(tr("Invalid event code %1 found").arg(int(eventCode))); } deltaTime = getNumberFromMIDIBytes(); #ifdef MIDI_DEBUG cerr << "read delta time " << deltaTime << endl; #endif // Get a single byte midiByte = getMIDIByte(); if (!(midiByte & MIDI_STATUS_BYTE_MASK)) { if (runningStatus < 0) { throw MIDIException(tr("Running status used for first event in track")); } eventCode = (MIDIByte)runningStatus; data1 = midiByte; #ifdef MIDI_DEBUG cerr << "using running status (byte " << int(midiByte) << " found)" << endl; #endif } else { #ifdef MIDI_DEBUG cerr << "have new event code " << int(midiByte) << endl; #endif eventCode = midiByte; data1 = getMIDIByte(); } if (eventCode == MIDI_FILE_META_EVENT) { metaEventCode = data1; messageLength = getNumberFromMIDIBytes(); //#ifdef MIDI_DEBUG cerr << "Meta event of type " << int(metaEventCode) << " and " << messageLength << " bytes found, putting on track " << metaTrack << endl; //#endif metaMessage = getMIDIBytes(messageLength); long gap = accumulatedTime - trackTimeMap[metaTrack]; accumulatedTime += deltaTime; deltaTime += gap; trackTimeMap[metaTrack] = accumulatedTime; MIDIEvent *e = new MIDIEvent(deltaTime, MIDI_FILE_META_EVENT, metaEventCode, metaMessage); m_midiComposition[metaTrack].push_back(e); if (metaEventCode == MIDI_TRACK_NAME) { m_trackNames[metaTrack] = metaMessage.c_str(); } } else { // non-meta events runningStatus = eventCode; MIDIEvent *midiEvent; int channel = (eventCode & MIDI_CHANNEL_NUM_MASK); if (channelTrackMap[channel] == -1) { if (!firstTrack) ++lastTrackNum; else firstTrack = false; channelTrackMap[channel] = lastTrackNum; } unsigned int trackNum = channelTrackMap[channel]; // accumulatedTime is abs time of last event on any track; // trackTimeMap[trackNum] is that of last event on this track long gap = accumulatedTime - trackTimeMap[trackNum]; accumulatedTime += deltaTime; deltaTime += gap; trackTimeMap[trackNum] = accumulatedTime; switch (eventCode & MIDI_MESSAGE_TYPE_MASK) { case MIDI_NOTE_ON: case MIDI_NOTE_OFF: case MIDI_POLY_AFTERTOUCH: case MIDI_CTRL_CHANGE: data2 = getMIDIByte(); // create and store our event midiEvent = new MIDIEvent(deltaTime, eventCode, data1, data2); /* cerr << "MIDI event for channel " << channel << " (track " << trackNum << ")" << endl; midiEvent->print(); */ m_midiComposition[trackNum].push_back(midiEvent); if (midiEvent->getChannelNumber() == MIDI_PERCUSSION_CHANNEL) { m_percussionTracks.insert(trackNum); } break; case MIDI_PITCH_BEND: data2 = getMIDIByte(); // create and store our event midiEvent = new MIDIEvent(deltaTime, eventCode, data1, data2); m_midiComposition[trackNum].push_back(midiEvent); break; case MIDI_PROG_CHANGE: case MIDI_CHNL_AFTERTOUCH: // create and store our event midiEvent = new MIDIEvent(deltaTime, eventCode, data1); m_midiComposition[trackNum].push_back(midiEvent); break; case MIDI_SYSTEM_EXCLUSIVE: messageLength = getNumberFromMIDIBytes(data1); #ifdef MIDI_DEBUG cerr << "SysEx of " << messageLength << " bytes found" << endl; #endif metaMessage= getMIDIBytes(messageLength); if (MIDIByte(metaMessage[metaMessage.length() - 1]) != MIDI_END_OF_EXCLUSIVE) { #ifdef MIDI_DEBUG cerr << "MIDIFileReader::parseTrack() - " << "malformed or unsupported SysEx type" << endl; #endif continue; } // chop off the EOX // length fixed by Pedro Lopez-Cabanillas (20030523) // metaMessage = metaMessage.substr(0, metaMessage.length()-1); midiEvent = new MIDIEvent(deltaTime, MIDI_SYSTEM_EXCLUSIVE, metaMessage); m_midiComposition[trackNum].push_back(midiEvent); break; case MIDI_END_OF_EXCLUSIVE: #ifdef MIDI_DEBUG cerr << "MIDIFileReader::parseTrack() - " << "Found a stray MIDI_END_OF_EXCLUSIVE" << endl; #endif break; default: #ifdef MIDI_DEBUG cerr << "MIDIFileReader::parseTrack()" << " - Unsupported MIDI Event Code: " << (int)eventCode << endl; #endif break; } } } if (lastTrackNum > metaTrack) { for (unsigned int track = metaTrack + 1; track <= lastTrackNum; ++track) { m_trackNames[track] = QString("%1 <%2>") .arg(m_trackNames[metaTrack]).arg(track - metaTrack + 1); } } return true; } // Delete dead NOTE OFF and NOTE ON/Zero Velocity Events after // reading them and modifying their relevant NOTE ONs. Return true // if there are some notes in this track. // bool MIDIFileReader::consolidateNoteOffEvents(unsigned int track) { bool notesOnTrack = false; bool noteOffFound; for (MIDITrack::iterator i = m_midiComposition[track].begin(); i != m_midiComposition[track].end(); i++) { if ((*i)->getMessageType() == MIDI_NOTE_ON && (*i)->getVelocity() > 0) { notesOnTrack = true; noteOffFound = false; for (MIDITrack::iterator j = i; j != m_midiComposition[track].end(); j++) { if (((*j)->getChannelNumber() == (*i)->getChannelNumber()) && ((*j)->getPitch() == (*i)->getPitch()) && ((*j)->getMessageType() == MIDI_NOTE_OFF || ((*j)->getMessageType() == MIDI_NOTE_ON && (*j)->getVelocity() == 0x00))) { (*i)->setDuration((*j)->getTime() - (*i)->getTime()); delete *j; m_midiComposition[track].erase(j); noteOffFound = true; break; } } // If no matching NOTE OFF has been found then set // Event duration to length of track // if (!noteOffFound) { MIDITrack::iterator j = m_midiComposition[track].end(); --j; (*i)->setDuration((*j)->getTime() - (*i)->getTime()); } } } return notesOnTrack; } // Add any tempo events found in the given track to the global tempo map. // void MIDIFileReader::updateTempoMap(unsigned int track) { std::cerr << "updateTempoMap for track " << track << " (" << m_midiComposition[track].size() << " events)" << std::endl; for (MIDITrack::iterator i = m_midiComposition[track].begin(); i != m_midiComposition[track].end(); ++i) { if ((*i)->isMeta() && (*i)->getMetaEventCode() == MIDI_SET_TEMPO) { MIDIByte m0 = (*i)->getMetaMessage()[0]; MIDIByte m1 = (*i)->getMetaMessage()[1]; MIDIByte m2 = (*i)->getMetaMessage()[2]; long tempo = (((m0 << 8) + m1) << 8) + m2; std::cerr << "updateTempoMap: have tempo, it's " << tempo << " at " << (*i)->getTime() << std::endl; if (tempo != 0) { double qpm = 60000000.0 / double(tempo); m_tempoMap[(*i)->getTime()] = TempoChange(RealTime::zeroTime, qpm); } } } } void MIDIFileReader::calculateTempoTimestamps() { unsigned long lastMIDITime = 0; RealTime lastRealTime = RealTime::zeroTime; double tempo = 120.0; int td = m_timingDivision; if (td == 0) td = 96; for (TempoMap::iterator i = m_tempoMap.begin(); i != m_tempoMap.end(); ++i) { unsigned long mtime = i->first; unsigned long melapsed = mtime - lastMIDITime; double quarters = double(melapsed) / double(td); double seconds = (60.0 * quarters) / tempo; RealTime t = lastRealTime + RealTime::fromSeconds(seconds); i->second.first = t; lastRealTime = t; lastMIDITime = mtime; tempo = i->second.second; } } RealTime MIDIFileReader::getTimeForMIDITime(unsigned long midiTime) const { unsigned long tempoMIDITime = 0; RealTime tempoRealTime = RealTime::zeroTime; double tempo = 120.0; TempoMap::const_iterator i = m_tempoMap.lower_bound(midiTime); if (i != m_tempoMap.begin()) { --i; tempoMIDITime = i->first; tempoRealTime = i->second.first; tempo = i->second.second; } int td = m_timingDivision; if (td == 0) td = 96; unsigned long melapsed = midiTime - tempoMIDITime; double quarters = double(melapsed) / double(td); double seconds = (60.0 * quarters) / tempo; /* std::cerr << "MIDIFileReader::getTimeForMIDITime(" << midiTime << ")" << std::endl; std::cerr << "timing division = " << td << std::endl; std::cerr << "nearest tempo event (of " << m_tempoMap.size() << ") is at " << tempoMIDITime << " (" << tempoRealTime << ")" << std::endl; std::cerr << "quarters since then = " << quarters << std::endl; std::cerr << "tempo = " << tempo << " quarters per minute" << std::endl; std::cerr << "seconds since then = " << seconds << std::endl; std::cerr << "resulting time = " << (tempoRealTime + RealTime::fromSeconds(seconds)) << std::endl; */ return tempoRealTime + RealTime::fromSeconds(seconds); } Model * MIDIFileReader::load() const { if (!isOK()) return 0; if (m_loadableTracks.empty()) { QMessageBox::critical(0, tr("No notes in MIDI file"), tr("MIDI file \"%1\" has no notes in any track") .arg(m_path)); return 0; } std::set<unsigned int> tracksToLoad; if (m_loadableTracks.size() == 1) { tracksToLoad.insert(*m_loadableTracks.begin()); } else { QStringList available; QString allTracks = tr("Merge all tracks"); QString allNonPercussion = tr("Merge all non-percussion tracks"); int nonTrackItems = 1; available << allTracks; if (!m_percussionTracks.empty() && (m_percussionTracks.size() < m_loadableTracks.size())) { available << allNonPercussion; ++nonTrackItems; } for (set<unsigned int>::iterator i = m_loadableTracks.begin(); i != m_loadableTracks.end(); ++i) { unsigned int trackNo = *i; QString label; QString perc; if (m_percussionTracks.find(trackNo) != m_percussionTracks.end()) { perc = tr(" - uses GM percussion channel"); } if (m_trackNames.find(trackNo) != m_trackNames.end()) { label = tr("Track %1 (%2)%3") .arg(trackNo).arg(m_trackNames.find(trackNo)->second) .arg(perc); } else { label = tr("Track %1 (untitled)%3").arg(trackNo).arg(perc); } available << label; } bool ok = false; QString selected = QInputDialog::getItem (0, tr("Select track or tracks to import"), tr("You can only import this file as a single annotation layer,\nbut the file contains more than one track,\nor notes on more than one channel.\n\nPlease select the track or merged tracks you wish to import:"), available, 0, false, &ok); if (!ok || selected.isEmpty()) return 0; if (selected == allTracks || selected == allNonPercussion) { for (set<unsigned int>::iterator i = m_loadableTracks.begin(); i != m_loadableTracks.end(); ++i) { if (selected == allTracks || m_percussionTracks.find(*i) == m_percussionTracks.end()) { tracksToLoad.insert(*i); } } } else { int j = nonTrackItems; for (set<unsigned int>::iterator i = m_loadableTracks.begin(); i != m_loadableTracks.end(); ++i) { if (selected == available[j]) { tracksToLoad.insert(*i); break; } ++j; } } } if (tracksToLoad.empty()) return 0; size_t n = tracksToLoad.size(), count = 0; Model *model = 0; for (std::set<unsigned int>::iterator i = tracksToLoad.begin(); i != tracksToLoad.end(); ++i) { int minProgress = (100 * count) / n; int progressAmount = 100 / n; model = loadTrack(*i, model, minProgress, progressAmount); ++count; } if (dynamic_cast<NoteModel *>(model)) { dynamic_cast<NoteModel *>(model)->setCompletion(100); } return model; } Model * MIDIFileReader::loadTrack(unsigned int trackToLoad, Model *existingModel, int minProgress, int progressAmount) const { if (m_midiComposition.find(trackToLoad) == m_midiComposition.end()) { return 0; } NoteModel *model = 0; if (existingModel) { model = dynamic_cast<NoteModel *>(existingModel); if (!model) { std::cerr << "WARNING: MIDIFileReader::loadTrack: Existing model given, but it isn't a NoteModel -- ignoring it" << std::endl; } } if (!model) { model = new NoteModel(m_mainModelSampleRate, 1, 0.0, 0.0, false); model->setValueQuantization(1.0); } const MIDITrack &track = m_midiComposition.find(trackToLoad)->second; size_t totalEvents = track.size(); size_t count = 0; bool minorKey = false; bool sharpKey = true; for (MIDITrack::const_iterator i = track.begin(); i != track.end(); ++i) { RealTime rt = getTimeForMIDITime((*i)->getTime()); // We ignore most of these event types for now, though in // theory some of the text ones could usefully be incorporated if ((*i)->isMeta()) { switch((*i)->getMetaEventCode()) { case MIDI_KEY_SIGNATURE: minorKey = (int((*i)->getMetaMessage()[1]) != 0); sharpKey = (int((*i)->getMetaMessage()[0]) >= 0); break; case MIDI_TEXT_EVENT: case MIDI_LYRIC: case MIDI_TEXT_MARKER: case MIDI_COPYRIGHT_NOTICE: case MIDI_TRACK_NAME: // The text events that we could potentially use break; case MIDI_SET_TEMPO: // Already dealt with in a separate pass previously break; case MIDI_TIME_SIGNATURE: // Not yet! break; case MIDI_SEQUENCE_NUMBER: case MIDI_CHANNEL_PREFIX_OR_PORT: case MIDI_INSTRUMENT_NAME: case MIDI_CUE_POINT: case MIDI_CHANNEL_PREFIX: case MIDI_SEQUENCER_SPECIFIC: case MIDI_SMPTE_OFFSET: default: break; } } else { switch ((*i)->getMessageType()) { case MIDI_NOTE_ON: if ((*i)->getVelocity() == 0) break; // effective note-off else { RealTime endRT = getTimeForMIDITime((*i)->getTime() + (*i)->getDuration()); long startFrame = RealTime::realTime2Frame (rt, model->getSampleRate()); long endFrame = RealTime::realTime2Frame (endRT, model->getSampleRate()); QString pitchLabel = Pitch::getPitchLabel((*i)->getPitch(), 0, !sharpKey); QString noteLabel = tr("%1 - vel %2") .arg(pitchLabel).arg(int((*i)->getVelocity())); Note note(startFrame, (*i)->getPitch(), endFrame - startFrame, noteLabel); // std::cerr << "Adding note " << startFrame << "," << (endFrame-startFrame) << " : " << int((*i)->getPitch()) << std::endl; model->addPoint(note); break; } case MIDI_PITCH_BEND: // I guess we could make some use of this... break; case MIDI_NOTE_OFF: case MIDI_PROG_CHANGE: case MIDI_CTRL_CHANGE: case MIDI_SYSTEM_EXCLUSIVE: case MIDI_POLY_AFTERTOUCH: case MIDI_CHNL_AFTERTOUCH: break; default: break; } } model->setCompletion(minProgress + (count * progressAmount) / totalEvents); ++count; } return model; }