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