Mercurial > hg > midifile
changeset 1:3e65e0344413 midifile
* A simple program (for analytical and test purposes) for converting a
MIDI file to text, using MIDI parser originally from RG.
This is something I've had hanging around for some time, but recently
decided I really ought to put somewhere public (in case I lose it, apart
from anything else). RG developers, let me know if you would rather not
have this in the RG SVN.
| author | cannam |
|---|---|
| date | Thu, 28 Jan 2010 14:07:12 +0000 |
| parents | a8f7ccc96a9f |
| children | 1a4000271db6 |
| files | MIDIComposition.h MIDIEvent.h MIDIFileReader.cpp MIDIFileReader.h main.cpp midifile.pro |
| diffstat | 6 files changed, 1165 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MIDIComposition.h Thu Jan 28 14:07:12 2010 +0000 @@ -0,0 +1,19 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + 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. +*/ + +#ifndef _MIDI_COMPOSITION_H_ +#define _MIDI_COMPOSITION_H_ + +#include "MIDIEvent.h" +#include <QLinkedList> +#include <QMap> + +typedef QLinkedList<MIDIEvent> MIDITrack; +typedef QMap<unsigned int, MIDITrack> MIDIComposition; + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MIDIEvent.h Thu Jan 28 14:07:12 2010 +0000 @@ -0,0 +1,220 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + 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. +*/ + +#ifndef _MIDI_EVENT_H_ +#define _MIDI_EVENT_H_ + +#include <QString> +#include <string> +#include <iostream> + +typedef unsigned char MIDIByte; + +namespace MIDIConstants +{ + static const char *const MIDI_FILE_HEADER = "MThd"; + static const char *const MIDI_TRACK_HEADER = "MTrk"; + + static const MIDIByte MIDI_STATUS_BYTE_MASK = 0x80; + static const MIDIByte MIDI_MESSAGE_TYPE_MASK = 0xF0; + static const MIDIByte MIDI_CHANNEL_NUM_MASK = 0x0F; + + static const MIDIByte MIDI_NOTE_OFF = 0x80; + static const MIDIByte MIDI_NOTE_ON = 0x90; + static const MIDIByte MIDI_POLY_AFTERTOUCH = 0xA0; + static const MIDIByte MIDI_CTRL_CHANGE = 0xB0; + static const MIDIByte MIDI_PROG_CHANGE = 0xC0; + static const MIDIByte MIDI_CHNL_AFTERTOUCH = 0xD0; + static const MIDIByte MIDI_PITCH_BEND = 0xE0; + static const MIDIByte MIDI_SELECT_CHNL_MODE = 0xB0; + static const MIDIByte MIDI_SYSTEM_EXCLUSIVE = 0xF0; + static const MIDIByte MIDI_TC_QUARTER_FRAME = 0xF1; + static const MIDIByte MIDI_SONG_POSITION_PTR = 0xF2; + static const MIDIByte MIDI_SONG_SELECT = 0xF3; + static const MIDIByte MIDI_TUNE_REQUEST = 0xF6; + static const MIDIByte MIDI_END_OF_EXCLUSIVE = 0xF7; + static const MIDIByte MIDI_TIMING_CLOCK = 0xF8; + static const MIDIByte MIDI_START = 0xFA; + static const MIDIByte MIDI_CONTINUE = 0xFB; + static const MIDIByte MIDI_STOP = 0xFC; + static const MIDIByte MIDI_ACTIVE_SENSING = 0xFE; + static const MIDIByte MIDI_SYSTEM_RESET = 0xFF; + static const MIDIByte MIDI_SYSEX_NONCOMMERCIAL = 0x7D; + static const MIDIByte MIDI_SYSEX_NON_RT = 0x7E; + static const MIDIByte MIDI_SYSEX_RT = 0x7F; + static const MIDIByte MIDI_SYSEX_RT_COMMAND = 0x06; + static const MIDIByte MIDI_SYSEX_RT_RESPONSE = 0x07; + static const MIDIByte MIDI_MMC_STOP = 0x01; + static const MIDIByte MIDI_MMC_PLAY = 0x02; + static const MIDIByte MIDI_MMC_DEFERRED_PLAY = 0x03; + static const MIDIByte MIDI_MMC_FAST_FORWARD = 0x04; + static const MIDIByte MIDI_MMC_REWIND = 0x05; + static const MIDIByte MIDI_MMC_RECORD_STROBE = 0x06; + static const MIDIByte MIDI_MMC_RECORD_EXIT = 0x07; + static const MIDIByte MIDI_MMC_RECORD_PAUSE = 0x08; + static const MIDIByte MIDI_MMC_PAUSE = 0x08; + static const MIDIByte MIDI_MMC_EJECT = 0x0A; + static const MIDIByte MIDI_MMC_LOCATE = 0x44; + static const MIDIByte MIDI_FILE_META_EVENT = 0xFF; + static const MIDIByte MIDI_SEQUENCE_NUMBER = 0x00; + static const MIDIByte MIDI_TEXT_EVENT = 0x01; + static const MIDIByte MIDI_COPYRIGHT_NOTICE = 0x02; + static const MIDIByte MIDI_TRACK_NAME = 0x03; + static const MIDIByte MIDI_INSTRUMENT_NAME = 0x04; + static const MIDIByte MIDI_LYRIC = 0x05; + static const MIDIByte MIDI_TEXT_MARKER = 0x06; + static const MIDIByte MIDI_CUE_POINT = 0x07; + static const MIDIByte MIDI_CHANNEL_PREFIX = 0x20; + static const MIDIByte MIDI_CHANNEL_PREFIX_OR_PORT = 0x21; + static const MIDIByte MIDI_END_OF_TRACK = 0x2F; + static const MIDIByte MIDI_SET_TEMPO = 0x51; + static const MIDIByte MIDI_SMPTE_OFFSET = 0x54; + static const MIDIByte MIDI_TIME_SIGNATURE = 0x58; + static const MIDIByte MIDI_KEY_SIGNATURE = 0x59; + static const MIDIByte MIDI_SEQUENCER_SPECIFIC = 0x7F; + static const MIDIByte MIDI_CONTROLLER_BANK_MSB = 0x00; + static const MIDIByte MIDI_CONTROLLER_VOLUME = 0x07; + static const MIDIByte MIDI_CONTROLLER_BANK_LSB = 0x20; + static const MIDIByte MIDI_CONTROLLER_MODULATION = 0x01; + static const MIDIByte MIDI_CONTROLLER_PAN = 0x0A; + static const MIDIByte MIDI_CONTROLLER_SUSTAIN = 0x40; + static const MIDIByte MIDI_CONTROLLER_RESONANCE = 0x47; + static const MIDIByte MIDI_CONTROLLER_RELEASE = 0x48; + static const MIDIByte MIDI_CONTROLLER_ATTACK = 0x49; + static const MIDIByte MIDI_CONTROLLER_FILTER = 0x4A; + static const MIDIByte MIDI_CONTROLLER_REVERB = 0x5B; + static const MIDIByte MIDI_CONTROLLER_CHORUS = 0x5D; + static const MIDIByte MIDI_CONTROLLER_NRPN_1 = 0x62; + static const MIDIByte MIDI_CONTROLLER_NRPN_2 = 0x63; + static const MIDIByte MIDI_CONTROLLER_RPN_1 = 0x64; + static const MIDIByte MIDI_CONTROLLER_RPN_2 = 0x65; + static const MIDIByte MIDI_CONTROLLER_SOUNDS_OFF = 0x78; + static const MIDIByte MIDI_CONTROLLER_RESET = 0x79; + static const MIDIByte MIDI_CONTROLLER_LOCAL = 0x7A; + static const MIDIByte MIDI_CONTROLLER_ALL_NOTES_OFF = 0x7B; + static const MIDIByte MIDI_PERCUSSION_CHANNEL = 9; + + typedef enum { + MIDI_SINGLE_TRACK_FILE = 0x00, + MIDI_SIMULTANEOUS_TRACK_FILE = 0x01, + MIDI_SEQUENTIAL_TRACK_FILE = 0x02, + MIDI_FILE_BAD_FORMAT = 0xFF + } MIDIFileFormatType; +} + +class MIDIEvent +{ +public: + 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 std::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 std::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; + } + + int getMessageType() const + { return (m_eventCode & MIDIConstants::MIDI_MESSAGE_TYPE_MASK); } + + int getChannelNumber() const + { return (m_eventCode & MIDIConstants::MIDI_CHANNEL_NUM_MASK); } + + unsigned long getTime() const { return m_deltaTime; } + unsigned long getDuration() const { return m_duration; } + + int getPitch() const { return m_data1; } + int getVelocity() const { return m_data2; } + int getData1() const { return m_data1; } + int getData2() const { return m_data2; } + int getEventCode() const { return m_eventCode; } + + bool isMeta() const { return (m_eventCode == MIDIConstants::MIDI_FILE_META_EVENT); } + + int getMetaEventCode() const { return m_metaEventCode; } + std::string getMetaMessage() const { return m_metaMessage; } + void setMetaMessage(const std::string &meta) { m_metaMessage = meta; } + + friend bool operator<(const MIDIEvent &a, const MIDIEvent &b); + +private: + unsigned long m_deltaTime; + unsigned long m_duration; + MIDIByte m_eventCode; + MIDIByte m_data1; // or Note + MIDIByte m_data2; // or Velocity + MIDIByte m_metaEventCode; + std::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) { + std::cerr << "WARNING: MIDI exception: " + << message.toLocal8Bit().data() << std::endl; + } + virtual ~MIDIException() throw() { } + + virtual const char *what() const throw() { + return m_message.toLocal8Bit().data(); + } + +protected: + QString m_message; +}; + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MIDIFileReader.cpp Thu Jan 28 14:07:12 2010 +0000 @@ -0,0 +1,637 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + 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 "MIDIFileReader.h" +#include "MIDIEvent.h" + +#include <QString> +#include <QVector> + +#include <sstream> + +using std::string; +using std::ifstream; +using std::stringstream; +using std::cerr; +using std::endl; +using std::ends; +using std::ios; + +using namespace MIDIConstants; + +//#define DEBUG_MIDI_FILE_READER 1 + + +MIDIFileReader::MIDIFileReader(QString path) : + 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) +{ + if (parseFile()) { + m_error = ""; + } +} + +MIDIFileReader::~MIDIFileReader() +{ +} + +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(QObject::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(QObject::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. +// +MIDIByte +MIDIFileReader::getMIDIByte() +{ + if (!m_midiFile) { + throw MIDIException(QObject::tr("getMIDIByte called but no MIDI file open")); + } + + if (m_midiFile->eof()) { + throw MIDIException(QObject::tr("End of MIDI file encountered while reading")); + } + + if (m_decrementCount && m_trackByteCount <= 0) { + throw MIDIException(QObject::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(QObject::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(QObject::tr("getMIDIBytes called but no MIDI file open")); + } + + if (m_midiFile->eof()) { + throw MIDIException(QObject::tr("End of MIDI file encountered while reading")); + } + + if (m_decrementCount && (numberOfBytes > (unsigned long)m_trackByteCount)) { + throw MIDIException(QObject::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(QObject::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(QObject::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(QObject::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 DEBUG_MIDI_FILE_READER + cerr << "MIDIFileReader::open() : fileName = " << m_path.toStdString() << 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; + m_midiFile = 0; + 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; + } + + for (unsigned int j = 0; j < m_numberOfTracks; ++j) { + +#ifdef DEBUG_MIDI_FILE_READER + cerr << "Parsing Track " << j << endl; +#endif + + if (!skipToNextTrack()) { +#ifdef DEBUG_MIDI_FILE_READER + 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 DEBUG_MIDI_FILE_READER + cerr << "Track has " << m_trackByteCount << " bytes" << endl; +#endif + + // Run through the events taking them into our internal + // representation. + if (!parseTrack(j)) { +#ifdef DEBUG_MIDI_FILE_READER + cerr << "Track " << j << " parsing failed" << endl; +#endif + m_error = "File corrupted or in non-standard format?"; + m_format = MIDI_FILE_BAD_FORMAT; + goto done; + } + } + + 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) { +#ifdef DEBUG_MIDI_FILE_READER + cerr << "converting delta time " << i->getTime(); +#endif + acc = i->addTime(acc); +#ifdef DEBUG_MIDI_FILE_READER + cerr << " to " << i->getTime() << endl; +#endif + } + + consolidateNoteOffEvents(track); + } + + return retval; +} + +// Parse and ensure the MIDI Header is legitimate +// +bool +MIDIFileReader::parseHeader(const string &midiHeader) +{ + if (midiHeader.size() < 14) { +#ifdef DEBUG_MIDI_FILE_READER + cerr << "MIDIFileReader::parseHeader() - file header undersized" << endl; +#endif + return false; + } + + if (midiHeader.compare(0, 4, MIDI_FILE_HEADER) != 0) { +#ifdef DEBUG_MIDI_FILE_READER + cerr << "MIDIFileReader::parseHeader()" + << "- file header not found or malformed" + << endl; +#endif + return false; + } + + if (midiBytesToLong(midiHeader.substr(4,4)) != 6L) { +#ifdef DEBUG_MIDI_FILE_READER + 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)); + +#ifdef DEBUG_MIDI_FILE_READER + 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 trackNum) +{ + MIDIByte midiByte, metaEventCode, data1, data2; + MIDIByte eventCode = 0x80; + string metaMessage; + unsigned int messageLength; + unsigned long deltaTime; + unsigned long accumulatedTime = 0; + + // Remember the last non-meta status byte (-1 if we haven't seen one) + int runningStatus = -1; + + while (!m_midiFile->eof() && (m_trackByteCount > 0)) { + + if (eventCode < 0x80) { +#ifdef DEBUG_MIDI_FILE_READER + cerr << "WARNING: Invalid event code " << eventCode + << " in MIDI file" << endl; +#endif + throw MIDIException(QObject::tr("Invalid event code %1 found").arg(int(eventCode))); + } + + deltaTime = getNumberFromMIDIBytes(); + +#ifdef DEBUG_MIDI_FILE_READER + cerr << "read delta time " << deltaTime << endl; +#endif + + // Get a single byte + midiByte = getMIDIByte(); + + if (!(midiByte & MIDI_STATUS_BYTE_MASK)) { + + if (runningStatus < 0) { + throw MIDIException(QObject::tr("Running status used for first event in track")); + } + + eventCode = (MIDIByte)runningStatus; + data1 = midiByte; + +#ifdef DEBUG_MIDI_FILE_READER + cerr << "using running status (byte " << int(midiByte) << " found)" << endl; +#endif + } else { +#ifdef DEBUG_MIDI_FILE_READER + cerr << "have new event code " << int(midiByte) << endl; +#endif + eventCode = midiByte; + data1 = getMIDIByte(); + } + + if (eventCode == MIDI_FILE_META_EVENT) { + + metaEventCode = data1; + messageLength = getNumberFromMIDIBytes(); + +#ifdef DEBUG_MIDI_FILE_READER + cerr << "Meta event of type " << int(metaEventCode) << " and " << messageLength << " bytes found, putting on track " << metaTrack << endl; +#endif + metaMessage = getMIDIBytes(messageLength); + + accumulatedTime += deltaTime; + + MIDIEvent e(deltaTime, + MIDI_FILE_META_EVENT, + metaEventCode, + metaMessage); + + m_midiComposition[trackNum].push_back(e); + + if (metaEventCode == MIDI_TRACK_NAME) { + m_trackNames[trackNum] = metaMessage.c_str(); + } + + } else { // non-meta events + + runningStatus = eventCode; + + int channel = (eventCode & MIDI_CHANNEL_NUM_MASK); + + accumulatedTime += deltaTime; + + 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 midiEvent(deltaTime, eventCode, data1, data2); + +#ifdef DEBUG_MIDI_FILE_READER + cerr << "MIDI event for channel " << channel << " (track " + << trackNum << ") with delta time " << deltaTime << endl; +#endif + + m_midiComposition[trackNum].push_back(midiEvent); + } + break; + + case MIDI_PITCH_BEND: + data2 = getMIDIByte(); + + { + // create and store our event + MIDIEvent 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 midiEvent(deltaTime, eventCode, data1); + m_midiComposition[trackNum].push_back(midiEvent); + } + break; + + case MIDI_SYSTEM_EXCLUSIVE: + messageLength = getNumberFromMIDIBytes(data1); + +#ifdef DEBUG_MIDI_FILE_READER + cerr << "SysEx of " << messageLength << " bytes found" << endl; +#endif + + metaMessage= getMIDIBytes(messageLength); + + if (MIDIByte(metaMessage[metaMessage.length() - 1]) != + MIDI_END_OF_EXCLUSIVE) + { +#ifdef DEBUG_MIDI_FILE_READER + 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 midiEvent(deltaTime, + MIDI_SYSTEM_EXCLUSIVE, + metaMessage); + m_midiComposition[trackNum].push_back(midiEvent); + } + break; + + case MIDI_END_OF_EXCLUSIVE: +#ifdef DEBUG_MIDI_FILE_READER + cerr << "MIDIFileReader::parseTrack() - " + << "Found a stray MIDI_END_OF_EXCLUSIVE" << endl; +#endif + break; + + default: +#ifdef DEBUG_MIDI_FILE_READER + cerr << "MIDIFileReader::parseTrack()" + << " - Unsupported MIDI Event Code: " + << (int)eventCode << endl; +#endif + break; + } + } + } + + 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; + + MIDITrack &t = m_midiComposition[track]; + + for (MIDITrack::iterator i = t.begin(); i != t.end(); ++i) { + + if (i->getMessageType() == MIDI_NOTE_ON && i->getVelocity() > 0) { + +#ifdef DEBUG_MIDI_FILE_READER + cerr << "Looking for note-offs for note at " << i->getTime() << " (pitch " << (int)i->getPitch() << ")" << endl; +#endif + + notesOnTrack = true; + noteOffFound = false; + + for (MIDITrack::iterator j = i; j != t.end(); ++j) { + + if ((j->getChannelNumber() == i->getChannelNumber()) && + (j->getPitch() == i->getPitch()) && + (j->getMessageType() == MIDI_NOTE_OFF || + (j->getMessageType() == MIDI_NOTE_ON && + j->getVelocity() == 0x00))) { + +#ifdef DEBUG_MIDI_FILE_READER + cerr << "Found note-off at " << j->getTime() << " for note at " << i->getTime() << endl; +#endif + + i->setDuration(j->getTime() - i->getTime()); + +#ifdef DEBUG_MIDI_FILE_READER + cerr << "Duration is now " << i->getDuration() << endl; +#endif + + t.erase(j); + + noteOffFound = true; + break; + } + } + + // If no matching NOTE OFF has been found then set + // Event duration to length of track + // + if (!noteOffFound) { +#ifdef DEBUG_MIDI_FILE_READER + cerr << "Failed to find note-off for note at " << i->getTime() << endl; +#endif + MIDITrack::iterator j = t.end(); + --j; + i->setDuration(j->getTime() - i->getTime()); + } + } + } + + return notesOnTrack; +} + +MIDIComposition +MIDIFileReader::load() const +{ + return m_midiComposition; +} + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MIDIFileReader.h Thu Jan 28 14:07:12 2010 +0000 @@ -0,0 +1,73 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + 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. +*/ + +#ifndef _MIDI_FILE_READER_H_ +#define _MIDI_FILE_READER_H_ + +#include <QObject> +#include <QList> +#include <QMap> +#include <QSet> + +#include <iostream> + +#include "MIDIComposition.h" + +typedef unsigned char MIDIByte; + +class MIDIFileReader +{ +public: + MIDIFileReader(QString path); + virtual ~MIDIFileReader(); + + virtual bool isOK() const; + virtual QString getError() const; + + virtual MIDIComposition load() const; + + MIDIConstants::MIDIFileFormatType getFormat() const { return m_format; } + int getTimingDivision() const { return m_timingDivision; } + +protected: + + bool parseFile(); + bool parseHeader(const std::string &midiHeader); + bool parseTrack(unsigned int trackNum); + bool consolidateNoteOffEvents(unsigned int track); + + // Internal convenience functions + // + int midiBytesToInt(const std::string &bytes); + long midiBytesToLong(const std::string &bytes); + + long getNumberFromMIDIBytes(int firstByte = -1); + + MIDIByte getMIDIByte(); + std::string getMIDIBytes(unsigned long bytes); + + bool skipToNextTrack(); + + int m_timingDivision; // pulses per quarter note + MIDIConstants::MIDIFileFormatType m_format; + unsigned int m_numberOfTracks; + + long m_trackByteCount; + bool m_decrementCount; + + QMap<int, QString> m_trackNames; + MIDIComposition m_midiComposition; + + QString m_path; + std::ifstream *m_midiFile; + size_t m_fileSize; + QString m_error; +}; + + +#endif // _MIDI_FILE_READER_H_
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/main.cpp Thu Jan 28 14:07:12 2010 +0000 @@ -0,0 +1,207 @@ + +#include "MIDIFileReader.h" + +#include <iostream> + +using namespace std; +using namespace MIDIConstants; + +int main(int argc, char **argv) +{ + if (argc != 2) { + cerr << "Usage: midifile <file.mid>" << endl; + return 1; + } + + QString filename = argv[1]; + + MIDIFileReader fr(filename); + if (!fr.isOK()) { + std::cerr << "Error: " << fr.getError().toStdString() << std::endl; + return 1; + } + + MIDIComposition c = fr.load(); + + switch (fr.getFormat()) { + case MIDI_SINGLE_TRACK_FILE: cout << "Format: MIDI Single Track File" << endl; break; + case MIDI_SIMULTANEOUS_TRACK_FILE: cout << "Format: MIDI Simultaneous Track File" << endl; break; + case MIDI_SEQUENTIAL_TRACK_FILE: cout << "Format: MIDI Sequential Track File" << endl; break; + default: cout << "Format: Unknown MIDI file format?" << endl; break; + } + + cout << "Tracks: " << c.size() << endl; + + int td = fr.getTimingDivision(); + if (td < 32768) { + cout << "Timing division: " << fr.getTimingDivision() << " ppq" << endl; + } else { + int frames = 256 - (td >> 8); + int subframes = td & 0xff; + cout << "SMPTE timing: " << frames << " fps, " << subframes << " subframes" << endl; + } + + for (MIDIComposition::const_iterator i = c.begin(); i != c.end(); ++i) { + + cout << "Start of track: " << i.key()+1 << endl; + + for (MIDITrack::const_iterator j = i->begin(); j != i->end(); ++j) { + + unsigned int t = j->getTime(); + int ch = j->getChannelNumber(); + + if (j->isMeta()) { + int code = j->getMetaEventCode(); + string name; + bool printable = true; + switch (code) { + + case MIDI_END_OF_TRACK: + cout << t << ": End of track" << endl; + break; + + case MIDI_TEXT_EVENT: name = "Text"; break; + case MIDI_COPYRIGHT_NOTICE: name = "Copyright"; break; + case MIDI_TRACK_NAME: name = "Track name"; break; + case MIDI_INSTRUMENT_NAME: name = "Instrument name"; break; + case MIDI_LYRIC: name = "Lyric"; break; + case MIDI_TEXT_MARKER: name = "Text marker"; break; + case MIDI_SEQUENCE_NUMBER: name = "Sequence number"; printable = false; break; + case MIDI_CHANNEL_PREFIX_OR_PORT: name = "Channel prefix or port"; printable = false; break; + case MIDI_CUE_POINT: name = "Cue point"; break; + case MIDI_CHANNEL_PREFIX: name = "Channel prefix"; printable = false; break; + case MIDI_SEQUENCER_SPECIFIC: name = "Sequencer specific"; printable = false; break; + case MIDI_SMPTE_OFFSET: name = "SMPTE offset"; printable = false; break; + + case MIDI_SET_TEMPO: + { + int m0 = j->getMetaMessage()[0]; + int m1 = j->getMetaMessage()[1]; + int m2 = j->getMetaMessage()[2]; + long tempo = (((m0 << 8) + m1) << 8) + m2; + + cout << t << ": Tempo: " << 60000000.0 / double(tempo) << endl; + } + break; + + case MIDI_TIME_SIGNATURE: + { + int numerator = j->getMetaMessage()[0]; + int denominator = 1 << (int)j->getMetaMessage()[1]; + + cout << t << ": Time signature: " << numerator << "/" << denominator << endl; + } + + case MIDI_KEY_SIGNATURE: + { + int accidentals = j->getMetaMessage()[0]; + int isMinor = j->getMetaMessage()[1]; + bool isSharp = accidentals < 0 ? false : true; + accidentals = accidentals < 0 ? -accidentals : accidentals; + cout << t << ": Key signature: " << accidentals << " " + << (isSharp ? + (accidentals > 1 ? "sharps" : "sharp") : + (accidentals > 1 ? "flats" : "flat")) + << (isMinor ? ", minor" : ", major") << endl; + } + + } + + + if (name != "") { + if (printable) { + cout << t << ": File meta event: code " << code + << ": " << name << ": \"" << j->getMetaMessage() + << "\"" << endl; + } else { + cout << t << ": File meta event: code " << code + << ": " << name << ": "; + for (int k = 0; k < j->getMetaMessage().length(); ++k) { + cout << (int)j->getMetaMessage()[k] << " "; + } + } + } + continue; + } + + switch (j->getMessageType()) { + + case MIDI_NOTE_ON: + cout << t << ": Note: channel " << ch + << " duration " << j->getDuration() + << " pitch " << j->getPitch() + << " velocity " << j->getVelocity() << endl; + break; + + case MIDI_POLY_AFTERTOUCH: + cout << t << ": Polyphonic aftertouch: channel " << ch + << " pitch " << j->getPitch() + << " pressure " << j->getData2() << endl; + break; + + case MIDI_CTRL_CHANGE: + { + int controller = j->getData1(); + string name; + switch (controller) { + case MIDI_CONTROLLER_BANK_MSB: name = "Bank select MSB"; break; + case MIDI_CONTROLLER_VOLUME: name = "Volume"; break; + case MIDI_CONTROLLER_BANK_LSB: name = "Bank select LSB"; break; + case MIDI_CONTROLLER_MODULATION: name = "Modulation wheel"; break; + case MIDI_CONTROLLER_PAN: name = "Pan"; break; + case MIDI_CONTROLLER_SUSTAIN: name = "Sustain"; break; + case MIDI_CONTROLLER_RESONANCE: name = "Resonance"; break; + case MIDI_CONTROLLER_RELEASE: name = "Release"; break; + case MIDI_CONTROLLER_ATTACK: name = "Attack"; break; + case MIDI_CONTROLLER_FILTER: name = "Filter"; break; + case MIDI_CONTROLLER_REVERB: name = "Reverb"; break; + case MIDI_CONTROLLER_CHORUS: name = "Chorus"; break; + case MIDI_CONTROLLER_NRPN_1: name = "NRPN 1"; break; + case MIDI_CONTROLLER_NRPN_2: name = "NRPN 2"; break; + case MIDI_CONTROLLER_RPN_1: name = "RPN 1"; break; + case MIDI_CONTROLLER_RPN_2: name = "RPN 2"; break; + case MIDI_CONTROLLER_SOUNDS_OFF: name = "All sounds off"; break; + case MIDI_CONTROLLER_RESET: name = "Reset"; break; + case MIDI_CONTROLLER_LOCAL: name = "Local"; break; + case MIDI_CONTROLLER_ALL_NOTES_OFF: name = "All notes off"; break; + } + cout << t << ": Controller change: channel " << ch + << " controller " << j->getData1(); + if (name != "") cout << " (" << name << ")"; + cout << " value " << j->getData2() << endl; + } + break; + + case MIDI_PROG_CHANGE: + cout << t << ": Program change: channel " << ch + << " program " << j->getData1() << endl; + break; + + case MIDI_CHNL_AFTERTOUCH: + cout << t << ": Channel aftertouch: channel " << ch + << " pressure " << j->getData1() << endl; + break; + + case MIDI_PITCH_BEND: + cout << t << ": Pitch bend: channel " << ch + << " value " << (int)j->getData2() * 128 + (int)j->getData1() << endl; + break; + + case MIDI_SYSTEM_EXCLUSIVE: + cout << t << ": System exclusive: code " + << (int)j->getMessageType() << " message length " << + j->getMetaMessage().length() << endl; + break; + + + } + + + } + + + } + +} + +
