Revision 1:3e65e0344413

View differences:

MIDIComposition.h
1
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
2

  
3
/*
4
   This is a modified version of a source file from the 
5
   Rosegarden MIDI and audio sequencer and notation editor.
6
   This file copyright 2000-2006 Richard Bown and Chris Cannam.
7
*/
8

  
9
#ifndef _MIDI_COMPOSITION_H_
10
#define _MIDI_COMPOSITION_H_
11

  
12
#include "MIDIEvent.h"
13
#include <QLinkedList>
14
#include <QMap>
15

  
16
typedef QLinkedList<MIDIEvent> MIDITrack;
17
typedef QMap<unsigned int, MIDITrack> MIDIComposition;
18

  
19
#endif
MIDIEvent.h
1
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
2

  
3
/*
4
   This is a modified version of a source file from the 
5
   Rosegarden MIDI and audio sequencer and notation editor.
6
   This file copyright 2000-2006 Richard Bown and Chris Cannam.
7
*/
8

  
9
#ifndef _MIDI_EVENT_H_
10
#define _MIDI_EVENT_H_
11

  
12
#include <QString>
13
#include <string>
14
#include <iostream>
15

  
16
typedef unsigned char MIDIByte;
17

  
18
namespace MIDIConstants
19
{
20
    static const char *const MIDI_FILE_HEADER         = "MThd";
21
    static const char *const MIDI_TRACK_HEADER        = "MTrk";
22

  
23
    static const MIDIByte MIDI_STATUS_BYTE_MASK       = 0x80;
24
    static const MIDIByte MIDI_MESSAGE_TYPE_MASK      = 0xF0;
25
    static const MIDIByte MIDI_CHANNEL_NUM_MASK       = 0x0F;
26

  
27
    static const MIDIByte MIDI_NOTE_OFF               = 0x80;
28
    static const MIDIByte MIDI_NOTE_ON                = 0x90;
29
    static const MIDIByte MIDI_POLY_AFTERTOUCH        = 0xA0;
30
    static const MIDIByte MIDI_CTRL_CHANGE            = 0xB0;
31
    static const MIDIByte MIDI_PROG_CHANGE            = 0xC0;
32
    static const MIDIByte MIDI_CHNL_AFTERTOUCH        = 0xD0;
33
    static const MIDIByte MIDI_PITCH_BEND             = 0xE0;
34
    static const MIDIByte MIDI_SELECT_CHNL_MODE       = 0xB0;
35
    static const MIDIByte MIDI_SYSTEM_EXCLUSIVE       = 0xF0;
36
    static const MIDIByte MIDI_TC_QUARTER_FRAME       = 0xF1;
37
    static const MIDIByte MIDI_SONG_POSITION_PTR      = 0xF2;
38
    static const MIDIByte MIDI_SONG_SELECT            = 0xF3;
39
    static const MIDIByte MIDI_TUNE_REQUEST           = 0xF6;
40
    static const MIDIByte MIDI_END_OF_EXCLUSIVE       = 0xF7;
41
    static const MIDIByte MIDI_TIMING_CLOCK           = 0xF8;
42
    static const MIDIByte MIDI_START                  = 0xFA;
43
    static const MIDIByte MIDI_CONTINUE               = 0xFB;
44
    static const MIDIByte MIDI_STOP                   = 0xFC;
45
    static const MIDIByte MIDI_ACTIVE_SENSING         = 0xFE;
46
    static const MIDIByte MIDI_SYSTEM_RESET           = 0xFF;
47
    static const MIDIByte MIDI_SYSEX_NONCOMMERCIAL    = 0x7D;
48
    static const MIDIByte MIDI_SYSEX_NON_RT           = 0x7E;
49
    static const MIDIByte MIDI_SYSEX_RT               = 0x7F;
50
    static const MIDIByte MIDI_SYSEX_RT_COMMAND       = 0x06;
51
    static const MIDIByte MIDI_SYSEX_RT_RESPONSE      = 0x07;
52
    static const MIDIByte MIDI_MMC_STOP               = 0x01;
53
    static const MIDIByte MIDI_MMC_PLAY               = 0x02;
54
    static const MIDIByte MIDI_MMC_DEFERRED_PLAY      = 0x03;
55
    static const MIDIByte MIDI_MMC_FAST_FORWARD       = 0x04;
56
    static const MIDIByte MIDI_MMC_REWIND             = 0x05;
57
    static const MIDIByte MIDI_MMC_RECORD_STROBE      = 0x06;
58
    static const MIDIByte MIDI_MMC_RECORD_EXIT        = 0x07;
59
    static const MIDIByte MIDI_MMC_RECORD_PAUSE       = 0x08;
60
    static const MIDIByte MIDI_MMC_PAUSE              = 0x08;
61
    static const MIDIByte MIDI_MMC_EJECT              = 0x0A;
62
    static const MIDIByte MIDI_MMC_LOCATE             = 0x44;
63
    static const MIDIByte MIDI_FILE_META_EVENT        = 0xFF;
64
    static const MIDIByte MIDI_SEQUENCE_NUMBER        = 0x00;
65
    static const MIDIByte MIDI_TEXT_EVENT             = 0x01;
66
    static const MIDIByte MIDI_COPYRIGHT_NOTICE       = 0x02;
67
    static const MIDIByte MIDI_TRACK_NAME             = 0x03;
68
    static const MIDIByte MIDI_INSTRUMENT_NAME        = 0x04;
69
    static const MIDIByte MIDI_LYRIC                  = 0x05;
70
    static const MIDIByte MIDI_TEXT_MARKER            = 0x06;
71
    static const MIDIByte MIDI_CUE_POINT              = 0x07;
72
    static const MIDIByte MIDI_CHANNEL_PREFIX         = 0x20;
73
    static const MIDIByte MIDI_CHANNEL_PREFIX_OR_PORT = 0x21;
74
    static const MIDIByte MIDI_END_OF_TRACK           = 0x2F;
75
    static const MIDIByte MIDI_SET_TEMPO              = 0x51;
76
    static const MIDIByte MIDI_SMPTE_OFFSET           = 0x54;
77
    static const MIDIByte MIDI_TIME_SIGNATURE         = 0x58;
78
    static const MIDIByte MIDI_KEY_SIGNATURE          = 0x59;
79
    static const MIDIByte MIDI_SEQUENCER_SPECIFIC     = 0x7F;
80
    static const MIDIByte MIDI_CONTROLLER_BANK_MSB      = 0x00;
81
    static const MIDIByte MIDI_CONTROLLER_VOLUME        = 0x07;
82
    static const MIDIByte MIDI_CONTROLLER_BANK_LSB      = 0x20;
83
    static const MIDIByte MIDI_CONTROLLER_MODULATION    = 0x01;
84
    static const MIDIByte MIDI_CONTROLLER_PAN           = 0x0A;
85
    static const MIDIByte MIDI_CONTROLLER_SUSTAIN       = 0x40;
86
    static const MIDIByte MIDI_CONTROLLER_RESONANCE     = 0x47;
87
    static const MIDIByte MIDI_CONTROLLER_RELEASE       = 0x48;
88
    static const MIDIByte MIDI_CONTROLLER_ATTACK        = 0x49;
89
    static const MIDIByte MIDI_CONTROLLER_FILTER        = 0x4A;
90
    static const MIDIByte MIDI_CONTROLLER_REVERB        = 0x5B;
91
    static const MIDIByte MIDI_CONTROLLER_CHORUS        = 0x5D;
92
    static const MIDIByte MIDI_CONTROLLER_NRPN_1        = 0x62;
93
    static const MIDIByte MIDI_CONTROLLER_NRPN_2        = 0x63;
94
    static const MIDIByte MIDI_CONTROLLER_RPN_1         = 0x64;
95
    static const MIDIByte MIDI_CONTROLLER_RPN_2         = 0x65;
96
    static const MIDIByte MIDI_CONTROLLER_SOUNDS_OFF    = 0x78;
97
    static const MIDIByte MIDI_CONTROLLER_RESET         = 0x79;
98
    static const MIDIByte MIDI_CONTROLLER_LOCAL         = 0x7A;
99
    static const MIDIByte MIDI_CONTROLLER_ALL_NOTES_OFF = 0x7B;
100
    static const MIDIByte MIDI_PERCUSSION_CHANNEL       = 9;
101

  
102
    typedef enum {
103
	MIDI_SINGLE_TRACK_FILE          = 0x00,
104
	MIDI_SIMULTANEOUS_TRACK_FILE    = 0x01,
105
	MIDI_SEQUENTIAL_TRACK_FILE      = 0x02,
106
	MIDI_FILE_BAD_FORMAT            = 0xFF
107
    } MIDIFileFormatType;
108
}
109

  
110
class MIDIEvent
111
{
112
public:
113
    MIDIEvent(unsigned long deltaTime,
114
              MIDIByte eventCode,
115
              MIDIByte data1 = 0,
116
              MIDIByte data2 = 0) :
117
	m_deltaTime(deltaTime),
118
	m_duration(0),
119
	m_eventCode(eventCode),
120
	m_data1(data1),
121
	m_data2(data2),
122
	m_metaEventCode(0)
123
    { }
124

  
125
    MIDIEvent(unsigned long deltaTime,
126
              MIDIByte eventCode,
127
              MIDIByte metaEventCode,
128
              const std::string &metaMessage) :
129
	m_deltaTime(deltaTime),
130
	m_duration(0),
131
	m_eventCode(eventCode),
132
	m_data1(0),
133
	m_data2(0),
134
	m_metaEventCode(metaEventCode),
135
	m_metaMessage(metaMessage)
136
    { }
137

  
138
    MIDIEvent(unsigned long deltaTime,
139
              MIDIByte eventCode,
140
              const std::string &sysEx) :
141
	m_deltaTime(deltaTime),
142
	m_duration(0),
143
	m_eventCode(eventCode),
144
	m_data1(0),
145
	m_data2(0),
146
	m_metaEventCode(0),
147
	m_metaMessage(sysEx)
148
    { }
149

  
150
    ~MIDIEvent() { }
151

  
152
    void setTime(const unsigned long &time) { m_deltaTime = time; }
153
    void setDuration(const unsigned long& duration) { m_duration = duration;}
154
    unsigned long addTime(const unsigned long &time) {
155
	m_deltaTime += time;
156
	return m_deltaTime;
157
    }
158

  
159
    int getMessageType() const
160
        { return (m_eventCode & MIDIConstants::MIDI_MESSAGE_TYPE_MASK); }
161

  
162
    int getChannelNumber() const
163
        { return (m_eventCode & MIDIConstants::MIDI_CHANNEL_NUM_MASK); }
164

  
165
    unsigned long getTime() const { return m_deltaTime; }
166
    unsigned long getDuration() const { return m_duration; }
167

  
168
    int getPitch() const { return m_data1; }
169
    int getVelocity() const { return m_data2; }
170
    int getData1() const { return m_data1; }
171
    int getData2() const { return m_data2; }
172
    int getEventCode() const { return m_eventCode; }
173

  
174
    bool isMeta() const { return (m_eventCode == MIDIConstants::MIDI_FILE_META_EVENT); }
175

  
176
    int getMetaEventCode() const { return m_metaEventCode; }
177
    std::string getMetaMessage() const { return m_metaMessage; }
178
    void setMetaMessage(const std::string &meta) { m_metaMessage = meta; }
179

  
180
    friend bool operator<(const MIDIEvent &a, const MIDIEvent &b);
181

  
182
private:
183
    unsigned long  m_deltaTime;
184
    unsigned long  m_duration;
185
    MIDIByte       m_eventCode;
186
    MIDIByte       m_data1;         // or Note
187
    MIDIByte       m_data2;         // or Velocity
188
    MIDIByte       m_metaEventCode;
189
    std::string    m_metaMessage;
190
};
191

  
192
// Comparator for sorting
193
//
194
struct MIDIEventCmp
195
{
196
    bool operator()(const MIDIEvent &mE1, const MIDIEvent &mE2) const
197
    { return mE1.getTime() < mE2.getTime(); }
198

  
199
    bool operator()(const MIDIEvent *mE1, const MIDIEvent *mE2) const
200
    { return mE1->getTime() < mE2->getTime(); }
201
};
202

  
203
class MIDIException : virtual public std::exception
204
{
205
public:
206
    MIDIException(QString message) throw() : m_message(message) {
207
        std::cerr << "WARNING: MIDI exception: "
208
		  << message.toLocal8Bit().data() << std::endl;
209
    }
210
    virtual ~MIDIException() throw() { }
211

  
212
    virtual const char *what() const throw() {
213
	return m_message.toLocal8Bit().data();
214
    }
215

  
216
protected:
217
    QString m_message;
218
};
219

  
220
#endif
MIDIFileReader.cpp
1
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
2

  
3
/*
4
   This is a modified version of a source file from the 
5
   Rosegarden MIDI and audio sequencer and notation editor.
6
   This file copyright 2000-2006 Richard Bown and Chris Cannam.
7
*/
8

  
9

  
10
#include <iostream>
11
#include <fstream>
12
#include <string>
13
#include <cstdio>
14

  
15
#include "MIDIFileReader.h"
16
#include "MIDIEvent.h"
17

  
18
#include <QString>
19
#include <QVector>
20

  
21
#include <sstream>
22

  
23
using std::string;
24
using std::ifstream;
25
using std::stringstream;
26
using std::cerr;
27
using std::endl;
28
using std::ends;
29
using std::ios;
30

  
31
using namespace MIDIConstants;
32

  
33
//#define DEBUG_MIDI_FILE_READER 1
34

  
35

  
36
MIDIFileReader::MIDIFileReader(QString path) :
37
    m_timingDivision(0),
38
    m_format(MIDI_FILE_BAD_FORMAT),
39
    m_numberOfTracks(0),
40
    m_trackByteCount(0),
41
    m_decrementCount(false),
42
    m_path(path),
43
    m_midiFile(0),
44
    m_fileSize(0)
45
{
46
    if (parseFile()) {
47
	m_error = "";
48
    }
49
}
50

  
51
MIDIFileReader::~MIDIFileReader()
52
{
53
}
54

  
55
bool
56
MIDIFileReader::isOK() const
57
{
58
    return (m_error == "");
59
}
60

  
61
QString
62
MIDIFileReader::getError() const
63
{
64
    return m_error;
65
}
66

  
67
long
68
MIDIFileReader::midiBytesToLong(const string& bytes)
69
{
70
    if (bytes.length() != 4) {
71
	throw MIDIException(QObject::tr("Wrong length for long data in MIDI stream (%1, should be %2)").arg(bytes.length()).arg(4));
72
    }
73

  
74
    long longRet = ((long)(((MIDIByte)bytes[0]) << 24)) |
75
                   ((long)(((MIDIByte)bytes[1]) << 16)) |
76
                   ((long)(((MIDIByte)bytes[2]) << 8)) |
77
                   ((long)((MIDIByte)(bytes[3])));
78

  
79
    return longRet;
80
}
81

  
82
int
83
MIDIFileReader::midiBytesToInt(const string& bytes)
84
{
85
    if (bytes.length() != 2) {
86
	throw MIDIException(QObject::tr("Wrong length for int data in MIDI stream (%1, should be %2)").arg(bytes.length()).arg(2));
87
    }
88

  
89
    int intRet = ((int)(((MIDIByte)bytes[0]) << 8)) |
90
                 ((int)(((MIDIByte)bytes[1])));
91
    return(intRet);
92
}
93

  
94

  
95
// Gets a single byte from the MIDI byte stream.  For each track
96
// section we can read only a specified number of bytes held in
97
// m_trackByteCount.
98
//
99
MIDIByte
100
MIDIFileReader::getMIDIByte()
101
{
102
    if (!m_midiFile) {
103
	throw MIDIException(QObject::tr("getMIDIByte called but no MIDI file open"));
104
    }
105

  
106
    if (m_midiFile->eof()) {
107
        throw MIDIException(QObject::tr("End of MIDI file encountered while reading"));
108
    }
109

  
110
    if (m_decrementCount && m_trackByteCount <= 0) {
111
        throw MIDIException(QObject::tr("Attempt to get more bytes than expected on Track"));
112
    }
113

  
114
    char byte;
115
    if (m_midiFile->read(&byte, 1)) {
116
	--m_trackByteCount;
117
	return (MIDIByte)byte;
118
    }
119

  
120
    throw MIDIException(QObject::tr("Attempt to read past MIDI file end"));
121
}
122

  
123

  
124
// Gets a specified number of bytes from the MIDI byte stream.  For
125
// each track section we can read only a specified number of bytes
126
// held in m_trackByteCount.
127
//
128
string
129
MIDIFileReader::getMIDIBytes(unsigned long numberOfBytes)
130
{
131
    if (!m_midiFile) {
132
	throw MIDIException(QObject::tr("getMIDIBytes called but no MIDI file open"));
133
    }
134

  
135
    if (m_midiFile->eof()) {
136
        throw MIDIException(QObject::tr("End of MIDI file encountered while reading"));
137
    }
138

  
139
    if (m_decrementCount && (numberOfBytes > (unsigned long)m_trackByteCount)) {
140
        throw MIDIException(QObject::tr("Attempt to get more bytes than available on Track (%1, only have %2)").arg(numberOfBytes).arg(m_trackByteCount));
141
    }
142

  
143
    string stringRet;
144
    char fileMIDIByte;
145

  
146
    while (stringRet.length() < numberOfBytes &&
147
           m_midiFile->read(&fileMIDIByte, 1)) {
148
        stringRet += fileMIDIByte;
149
    }
150

  
151
    // if we've reached the end of file without fulfilling the
152
    // quota then panic as our parsing has performed incorrectly
153
    //
154
    if (stringRet.length() < numberOfBytes) {
155
        stringRet = "";
156
        throw MIDIException(QObject::tr("Attempt to read past MIDI file end"));
157
    }
158

  
159
    // decrement the byte count
160
    if (m_decrementCount)
161
        m_trackByteCount -= stringRet.length();
162

  
163
    return stringRet;
164
}
165

  
166

  
167
// Get a long number of variable length from the MIDI byte stream.
168
//
169
long
170
MIDIFileReader::getNumberFromMIDIBytes(int firstByte)
171
{
172
    if (!m_midiFile) {
173
	throw MIDIException(QObject::tr("getNumberFromMIDIBytes called but no MIDI file open"));
174
    }
175

  
176
    long longRet = 0;
177
    MIDIByte midiByte;
178

  
179
    if (firstByte >= 0) {
180
	midiByte = (MIDIByte)firstByte;
181
    } else if (m_midiFile->eof()) {
182
	return longRet;
183
    } else {
184
	midiByte = getMIDIByte();
185
    }
186

  
187
    longRet = midiByte;
188
    if (midiByte & 0x80) {
189
	longRet &= 0x7F;
190
	do {
191
	    midiByte = getMIDIByte();
192
	    longRet = (longRet << 7) + (midiByte & 0x7F);
193
	} while (!m_midiFile->eof() && (midiByte & 0x80));
194
    }
195

  
196
    return longRet;
197
}
198

  
199

  
200
// Seek to the next track in the midi file and set the number
201
// of bytes to be read in the counter m_trackByteCount.
202
//
203
bool
204
MIDIFileReader::skipToNextTrack()
205
{
206
    if (!m_midiFile) {
207
	throw MIDIException(QObject::tr("skipToNextTrack called but no MIDI file open"));
208
    }
209

  
210
    string buffer, buffer2;
211
    m_trackByteCount = -1;
212
    m_decrementCount = false;
213

  
214
    while (!m_midiFile->eof() && (m_decrementCount == false)) {
215
        buffer = getMIDIBytes(4); 
216
	if (buffer.compare(0, 4, MIDI_TRACK_HEADER) == 0) {
217
	    m_trackByteCount = midiBytesToLong(getMIDIBytes(4));
218
	    m_decrementCount = true;
219
	}
220
    }
221

  
222
    if (m_trackByteCount == -1) { // we haven't found a track
223
        return false;
224
    } else {
225
        return true;
226
    }
227
}
228

  
229

  
230
// Read in a MIDI file.  The parsing process throws exceptions back up
231
// here if we run into trouble which we can then pass back out to
232
// whoever called us using a nice bool.
233
//
234
bool
235
MIDIFileReader::parseFile()
236
{
237
    m_error = "";
238

  
239
#ifdef DEBUG_MIDI_FILE_READER
240
    cerr << "MIDIFileReader::open() : fileName = " << m_path.toStdString() << endl;
241
#endif
242

  
243
    // Open the file
244
    m_midiFile = new ifstream(m_path.toLocal8Bit().data(),
245
			      ios::in | ios::binary);
246

  
247
    if (!*m_midiFile) {
248
	m_error = "File not found or not readable.";
249
	m_format = MIDI_FILE_BAD_FORMAT;
250
	delete m_midiFile;
251
        m_midiFile = 0;
252
	return false;
253
    }
254

  
255
    bool retval = false;
256

  
257
    try {
258

  
259
	// Set file size so we can count it off
260
	//
261
	m_midiFile->seekg(0, ios::end);
262
	m_fileSize = m_midiFile->tellg();
263
	m_midiFile->seekg(0, ios::beg);
264

  
265
	// Parse the MIDI header first.  The first 14 bytes of the file.
266
	if (!parseHeader(getMIDIBytes(14))) {
267
	    m_format = MIDI_FILE_BAD_FORMAT;
268
	    m_error = "Not a MIDI file.";
269
	    goto done;
270
	}
271

  
272
	for (unsigned int j = 0; j < m_numberOfTracks; ++j) {
273

  
274
#ifdef DEBUG_MIDI_FILE_READER
275
	    cerr << "Parsing Track " << j << endl;
276
#endif
277

  
278
	    if (!skipToNextTrack()) {
279
#ifdef DEBUG_MIDI_FILE_READER
280
		cerr << "Couldn't find Track " << j << endl;
281
#endif
282
		m_error = "File corrupted or in non-standard format?";
283
		m_format = MIDI_FILE_BAD_FORMAT;
284
		goto done;
285
	    }
286

  
287
#ifdef DEBUG_MIDI_FILE_READER
288
	    cerr << "Track has " << m_trackByteCount << " bytes" << endl;
289
#endif
290

  
291
	    // Run through the events taking them into our internal
292
	    // representation.
293
	    if (!parseTrack(j)) {
294
#ifdef DEBUG_MIDI_FILE_READER
295
		cerr << "Track " << j << " parsing failed" << endl;
296
#endif
297
		m_error = "File corrupted or in non-standard format?";
298
		m_format = MIDI_FILE_BAD_FORMAT;
299
		goto done;
300
	    }
301
	}
302
	
303
	retval = true;
304

  
305
    } catch (MIDIException e) {
306

  
307
        cerr << "MIDIFileReader::open() - caught exception - " << e.what() << endl;
308
	m_error = e.what();
309
    }
310
    
311
done:
312
    m_midiFile->close();
313
    delete m_midiFile;
314

  
315
    for (unsigned int track = 0; track < m_numberOfTracks; ++track) {
316

  
317
        // Convert the deltaTime to an absolute time since the track
318
        // start.  The addTime method returns the sum of the current
319
        // MIDI Event delta time plus the argument.
320

  
321
	unsigned long acc = 0;
322

  
323
        for (MIDITrack::iterator i = m_midiComposition[track].begin();
324
             i != m_midiComposition[track].end(); ++i) {
325
#ifdef DEBUG_MIDI_FILE_READER
326
            cerr << "converting delta time " << i->getTime();
327
#endif
328
            acc = i->addTime(acc);
329
#ifdef DEBUG_MIDI_FILE_READER
330
            cerr << " to " << i->getTime() << endl;
331
#endif
332
        }
333

  
334
        consolidateNoteOffEvents(track);
335
    }
336

  
337
    return retval;
338
}
339

  
340
// Parse and ensure the MIDI Header is legitimate
341
//
342
bool
343
MIDIFileReader::parseHeader(const string &midiHeader)
344
{
345
    if (midiHeader.size() < 14) {
346
#ifdef DEBUG_MIDI_FILE_READER
347
        cerr << "MIDIFileReader::parseHeader() - file header undersized" << endl;
348
#endif
349
        return false;
350
    }
351

  
352
    if (midiHeader.compare(0, 4, MIDI_FILE_HEADER) != 0) {
353
#ifdef DEBUG_MIDI_FILE_READER
354
	cerr << "MIDIFileReader::parseHeader()"
355
	     << "- file header not found or malformed"
356
	     << endl;
357
#endif
358
	return false;
359
    }
360

  
361
    if (midiBytesToLong(midiHeader.substr(4,4)) != 6L) {
362
#ifdef DEBUG_MIDI_FILE_READER
363
        cerr << "MIDIFileReader::parseHeader()"
364
	     << " - header length incorrect"
365
	     << endl;
366
#endif
367
        return false;
368
    }
369

  
370
    m_format = (MIDIFileFormatType) midiBytesToInt(midiHeader.substr(8,2));
371
    m_numberOfTracks = midiBytesToInt(midiHeader.substr(10,2));
372
    m_timingDivision = midiBytesToInt(midiHeader.substr(12,2));
373

  
374
#ifdef DEBUG_MIDI_FILE_READER
375
    if (m_timingDivision < 0) {
376
        cerr << "MIDIFileReader::parseHeader()"
377
                  << " - file uses SMPTE timing"
378
                  << endl;
379
    }
380
#endif
381

  
382
    return true; 
383
}
384

  
385
// Extract the contents from a MIDI file track and places it into
386
// our local map of MIDI events.
387
//
388
bool
389
MIDIFileReader::parseTrack(unsigned int trackNum)
390
{
391
    MIDIByte midiByte, metaEventCode, data1, data2;
392
    MIDIByte eventCode = 0x80;
393
    string metaMessage;
394
    unsigned int messageLength;
395
    unsigned long deltaTime;
396
    unsigned long accumulatedTime = 0;
397

  
398
    // Remember the last non-meta status byte (-1 if we haven't seen one)
399
    int runningStatus = -1;
400

  
401
    while (!m_midiFile->eof() && (m_trackByteCount > 0)) {
402

  
403
	if (eventCode < 0x80) {
404
#ifdef DEBUG_MIDI_FILE_READER
405
	    cerr << "WARNING: Invalid event code " << eventCode
406
		 << " in MIDI file" << endl;
407
#endif
408
	    throw MIDIException(QObject::tr("Invalid event code %1 found").arg(int(eventCode)));
409
	}
410

  
411
        deltaTime = getNumberFromMIDIBytes();
412

  
413
#ifdef DEBUG_MIDI_FILE_READER
414
	cerr << "read delta time " << deltaTime << endl;
415
#endif
416

  
417
        // Get a single byte
418
        midiByte = getMIDIByte();
419

  
420
        if (!(midiByte & MIDI_STATUS_BYTE_MASK)) {
421

  
422
	    if (runningStatus < 0) {
423
		throw MIDIException(QObject::tr("Running status used for first event in track"));
424
	    }
425

  
426
	    eventCode = (MIDIByte)runningStatus;
427
	    data1 = midiByte;
428

  
429
#ifdef DEBUG_MIDI_FILE_READER
430
	    cerr << "using running status (byte " << int(midiByte) << " found)" << endl;
431
#endif
432
        } else {
433
#ifdef DEBUG_MIDI_FILE_READER
434
	    cerr << "have new event code " << int(midiByte) << endl;
435
#endif
436
            eventCode = midiByte;
437
	    data1 = getMIDIByte();
438
	}
439

  
440
        if (eventCode == MIDI_FILE_META_EVENT) {
441

  
442
	    metaEventCode = data1;
443
            messageLength = getNumberFromMIDIBytes();
444

  
445
#ifdef DEBUG_MIDI_FILE_READER
446
		cerr << "Meta event of type " << int(metaEventCode) << " and " << messageLength << " bytes found, putting on track " << metaTrack << endl;
447
#endif
448
            metaMessage = getMIDIBytes(messageLength);
449

  
450
	    accumulatedTime += deltaTime;
451

  
452
            MIDIEvent e(deltaTime,
453
                        MIDI_FILE_META_EVENT,
454
                        metaEventCode,
455
                        metaMessage);
456

  
457
	    m_midiComposition[trackNum].push_back(e);
458

  
459
	    if (metaEventCode == MIDI_TRACK_NAME) {
460
		m_trackNames[trackNum] = metaMessage.c_str();
461
	    }
462

  
463
        } else { // non-meta events
464

  
465
	    runningStatus = eventCode;
466

  
467
	    int channel = (eventCode & MIDI_CHANNEL_NUM_MASK);
468
	    
469
	    accumulatedTime += deltaTime;
470

  
471
            switch (eventCode & MIDI_MESSAGE_TYPE_MASK) {
472

  
473
            case MIDI_NOTE_ON:
474
            case MIDI_NOTE_OFF:
475
            case MIDI_POLY_AFTERTOUCH:
476
            case MIDI_CTRL_CHANGE:
477
                data2 = getMIDIByte();
478

  
479
                {
480
                // create and store our event
481
                MIDIEvent midiEvent(deltaTime, eventCode, data1, data2);
482

  
483
#ifdef DEBUG_MIDI_FILE_READER
484
		cerr << "MIDI event for channel " << channel << " (track "
485
                     << trackNum << ") with delta time " << deltaTime << endl;
486
#endif
487

  
488
                m_midiComposition[trackNum].push_back(midiEvent);
489
                }
490
                break;
491

  
492
            case MIDI_PITCH_BEND:
493
                data2 = getMIDIByte();
494

  
495
                {
496
                // create and store our event
497
                MIDIEvent midiEvent(deltaTime, eventCode, data1, data2);
498
                m_midiComposition[trackNum].push_back(midiEvent);
499
                }
500
                break;
501

  
502
            case MIDI_PROG_CHANGE:
503
            case MIDI_CHNL_AFTERTOUCH:
504
                
505
                {
506
                // create and store our event
507
                MIDIEvent midiEvent(deltaTime, eventCode, data1);
508
                m_midiComposition[trackNum].push_back(midiEvent);
509
                }
510
                break;
511

  
512
            case MIDI_SYSTEM_EXCLUSIVE:
513
                messageLength = getNumberFromMIDIBytes(data1);
514

  
515
#ifdef DEBUG_MIDI_FILE_READER
516
		cerr << "SysEx of " << messageLength << " bytes found" << endl;
517
#endif
518

  
519
                metaMessage= getMIDIBytes(messageLength);
520

  
521
                if (MIDIByte(metaMessage[metaMessage.length() - 1]) !=
522
                        MIDI_END_OF_EXCLUSIVE)
523
                {
524
#ifdef DEBUG_MIDI_FILE_READER
525
                    cerr << "MIDIFileReader::parseTrack() - "
526
                              << "malformed or unsupported SysEx type"
527
                              << endl;
528
#endif
529
                    continue;
530
                }
531

  
532
                // chop off the EOX 
533
                // length fixed by Pedro Lopez-Cabanillas (20030523)
534
                //
535
                metaMessage = metaMessage.substr(0, metaMessage.length()-1);
536

  
537
                {
538
                MIDIEvent midiEvent(deltaTime,
539
                                    MIDI_SYSTEM_EXCLUSIVE,
540
                                    metaMessage);
541
                m_midiComposition[trackNum].push_back(midiEvent);
542
                }
543
                break;
544

  
545
            case MIDI_END_OF_EXCLUSIVE:
546
#ifdef DEBUG_MIDI_FILE_READER
547
                cerr << "MIDIFileReader::parseTrack() - "
548
                          << "Found a stray MIDI_END_OF_EXCLUSIVE" << endl;
549
#endif
550
                break;
551

  
552
            default:
553
#ifdef DEBUG_MIDI_FILE_READER
554
                cerr << "MIDIFileReader::parseTrack()" 
555
                          << " - Unsupported MIDI Event Code:  "
556
                          << (int)eventCode << endl;
557
#endif
558
                break;
559
            } 
560
        }
561
    }
562

  
563
    return true;
564
}
565

  
566
// Delete dead NOTE OFF and NOTE ON/Zero Velocity Events after
567
// reading them and modifying their relevant NOTE ONs.  Return true
568
// if there are some notes in this track.
569
//
570
bool
571
MIDIFileReader::consolidateNoteOffEvents(unsigned int track)
572
{
573
    bool notesOnTrack = false;
574
    bool noteOffFound;
575

  
576
    MIDITrack &t = m_midiComposition[track];
577

  
578
    for (MIDITrack::iterator i = t.begin(); i != t.end(); ++i) {
579

  
580
        if (i->getMessageType() == MIDI_NOTE_ON && i->getVelocity() > 0) {
581

  
582
#ifdef DEBUG_MIDI_FILE_READER
583
            cerr << "Looking for note-offs for note at " << i->getTime() << " (pitch " << (int)i->getPitch() << ")" <<  endl;
584
#endif
585

  
586
	    notesOnTrack = true;
587
            noteOffFound = false;
588

  
589
            for (MIDITrack::iterator j = i; j != t.end(); ++j) {
590

  
591
                if ((j->getChannelNumber() == i->getChannelNumber()) &&
592
		    (j->getPitch() == i->getPitch()) &&
593
                    (j->getMessageType() == MIDI_NOTE_OFF ||
594
                    (j->getMessageType() == MIDI_NOTE_ON &&
595
                     j->getVelocity() == 0x00))) {
596

  
597
#ifdef DEBUG_MIDI_FILE_READER
598
                    cerr << "Found note-off at " << j->getTime() << " for note at " << i->getTime() << endl;
599
#endif
600

  
601
                    i->setDuration(j->getTime() - i->getTime());
602

  
603
#ifdef DEBUG_MIDI_FILE_READER
604
                    cerr << "Duration is now " << i->getDuration() << endl;
605
#endif
606

  
607
                    t.erase(j);
608

  
609
                    noteOffFound = true;
610
                    break;
611
                }
612
            }
613

  
614
            // If no matching NOTE OFF has been found then set
615
            // Event duration to length of track
616
            //
617
            if (!noteOffFound) {
618
#ifdef DEBUG_MIDI_FILE_READER
619
                cerr << "Failed to find note-off for note at " << i->getTime() << endl;
620
#endif
621
		MIDITrack::iterator j = t.end();
622
		--j;
623
                i->setDuration(j->getTime() - i->getTime());
624
	    }
625
        }
626
    }
627

  
628
    return notesOnTrack;
629
}
630

  
631
MIDIComposition
632
MIDIFileReader::load() const
633
{
634
    return m_midiComposition;
635
}
636

  
637

  
MIDIFileReader.h
1
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
2

  
3
/*
4
   This is a modified version of a source file from the 
5
   Rosegarden MIDI and audio sequencer and notation editor.
6
   This file copyright 2000-2006 Richard Bown and Chris Cannam.
7
*/
8

  
9
#ifndef _MIDI_FILE_READER_H_
10
#define _MIDI_FILE_READER_H_
11

  
12
#include <QObject>
13
#include <QList>
14
#include <QMap>
15
#include <QSet>
16

  
17
#include <iostream>
18

  
19
#include "MIDIComposition.h"
20

  
21
typedef unsigned char MIDIByte;
22

  
23
class MIDIFileReader
24
{
25
public:
26
    MIDIFileReader(QString path);
27
    virtual ~MIDIFileReader();
28

  
29
    virtual bool isOK() const;
30
    virtual QString getError() const;
31

  
32
    virtual MIDIComposition load() const;
33

  
34
    MIDIConstants::MIDIFileFormatType getFormat() const { return m_format; }
35
    int getTimingDivision() const { return m_timingDivision; }
36

  
37
protected:
38

  
39
    bool parseFile();
40
    bool parseHeader(const std::string &midiHeader);
41
    bool parseTrack(unsigned int trackNum);
42
    bool consolidateNoteOffEvents(unsigned int track);
43

  
44
    // Internal convenience functions
45
    //
46
    int  midiBytesToInt(const std::string &bytes);
47
    long midiBytesToLong(const std::string &bytes);
48

  
49
    long getNumberFromMIDIBytes(int firstByte = -1);
50

  
51
    MIDIByte getMIDIByte();
52
    std::string getMIDIBytes(unsigned long bytes);
53

  
54
    bool skipToNextTrack();
55

  
56
    int                    m_timingDivision;   // pulses per quarter note
57
    MIDIConstants::MIDIFileFormatType m_format;
58
    unsigned int           m_numberOfTracks;
59

  
60
    long                   m_trackByteCount;
61
    bool                   m_decrementCount;
62

  
63
    QMap<int, QString>     m_trackNames;
64
    MIDIComposition        m_midiComposition;
65

  
66
    QString                m_path;
67
    std::ifstream         *m_midiFile;
68
    size_t                 m_fileSize;
69
    QString                m_error;
70
};
71

  
72

  
73
#endif // _MIDI_FILE_READER_H_
main.cpp
1

  
2
#include "MIDIFileReader.h"
3

  
4
#include <iostream>
5

  
6
using namespace std;
7
using namespace MIDIConstants;
8

  
9
int main(int argc, char **argv)
10
{
11
    if (argc != 2) {
12
	cerr << "Usage: midifile <file.mid>" << endl;
13
	return 1;
14
    }
15

  
16
    QString filename = argv[1];
17

  
18
    MIDIFileReader fr(filename);
19
    if (!fr.isOK()) {
20
	std::cerr << "Error: " << fr.getError().toStdString() << std::endl;
21
	return 1;
22
    }
23

  
24
    MIDIComposition c = fr.load();
25

  
26
    switch (fr.getFormat()) {
27
    case MIDI_SINGLE_TRACK_FILE: cout << "Format: MIDI Single Track File" << endl; break;
28
    case MIDI_SIMULTANEOUS_TRACK_FILE: cout << "Format: MIDI Simultaneous Track File" << endl; break;
29
    case MIDI_SEQUENTIAL_TRACK_FILE: cout << "Format: MIDI Sequential Track File" << endl; break;
30
    default: cout << "Format: Unknown MIDI file format?" << endl; break;
31
    }
32

  
33
    cout << "Tracks: " << c.size() << endl;
34

  
35
    int td = fr.getTimingDivision();
36
    if (td < 32768) {
37
	cout << "Timing division: " << fr.getTimingDivision() << " ppq" << endl;
38
    } else {
39
	int frames = 256 - (td >> 8);
40
	int subframes = td & 0xff;
41
	cout << "SMPTE timing: " << frames << " fps, " << subframes << " subframes" << endl;
42
    }
43

  
44
    for (MIDIComposition::const_iterator i = c.begin(); i != c.end(); ++i) {
45
	
46
	cout << "Start of track: " << i.key()+1 << endl;
47

  
48
	for (MIDITrack::const_iterator j = i->begin(); j != i->end(); ++j) {
49

  
50
	    unsigned int t = j->getTime();
51
	    int ch = j->getChannelNumber();
52

  
53
	    if (j->isMeta()) {
54
		int code = j->getMetaEventCode();
55
		string name;
56
		bool printable = true;
57
		switch (code) {
58

  
59
		case MIDI_END_OF_TRACK:
60
		    cout << t << ": End of track" << endl;
61
		    break;
62

  
63
		case MIDI_TEXT_EVENT: name = "Text"; break;
64
		case MIDI_COPYRIGHT_NOTICE: name = "Copyright"; break;
65
		case MIDI_TRACK_NAME: name = "Track name"; break;
66
		case MIDI_INSTRUMENT_NAME: name = "Instrument name"; break;
67
		case MIDI_LYRIC: name = "Lyric"; break;
68
		case MIDI_TEXT_MARKER: name = "Text marker"; break;
69
		case MIDI_SEQUENCE_NUMBER: name = "Sequence number"; printable = false; break;
70
		case MIDI_CHANNEL_PREFIX_OR_PORT: name = "Channel prefix or port"; printable = false; break;
71
		case MIDI_CUE_POINT: name = "Cue point"; break;
72
		case MIDI_CHANNEL_PREFIX: name = "Channel prefix"; printable = false; break;
73
		case MIDI_SEQUENCER_SPECIFIC: name = "Sequencer specific"; printable = false; break;
74
		case MIDI_SMPTE_OFFSET: name = "SMPTE offset"; printable = false; break;
75

  
76
		case MIDI_SET_TEMPO:
77
		{
78
		    int m0 = j->getMetaMessage()[0];
79
		    int m1 = j->getMetaMessage()[1];
80
		    int m2 = j->getMetaMessage()[2];
81
		    long tempo = (((m0 << 8) + m1) << 8) + m2;
82

  
83
		    cout << t << ": Tempo: " << 60000000.0 / double(tempo) << endl;
84
		}
85
		    break;
86

  
87
		case MIDI_TIME_SIGNATURE:
88
		{
89
                    int numerator = j->getMetaMessage()[0];
90
                    int denominator = 1 << (int)j->getMetaMessage()[1];
91
		    
92
		    cout << t << ": Time signature: " << numerator << "/" << denominator << endl;
93
		}
94

  
95
		case MIDI_KEY_SIGNATURE:
96
		{
97
                    int accidentals = j->getMetaMessage()[0];
98
                    int isMinor = j->getMetaMessage()[1];
99
                    bool isSharp = accidentals < 0 ? false : true;
100
                    accidentals = accidentals < 0 ? -accidentals : accidentals;
101
		    cout << t << ": Key signature: " << accidentals << " "
102
			 << (isSharp ?
103
			     (accidentals > 1 ? "sharps" : "sharp") :
104
			     (accidentals > 1 ? "flats" : "flat"))
105
			 << (isMinor ? ", minor" : ", major") << endl;
106
		}
107

  
108
		}
109
		
110

  
111
		if (name != "") {
112
		    if (printable) {
113
			cout << t << ": File meta event: code " << code
114
			     << ": " << name << ": \"" << j->getMetaMessage()
115
			     << "\"" << endl;
116
		    } else {
117
			cout << t << ": File meta event: code " << code
118
			     << ": " << name << ": ";
119
			for (int k = 0; k < j->getMetaMessage().length(); ++k) {
120
			    cout << (int)j->getMetaMessage()[k] << " ";
121
			}
122
		    }
123
		}
124
		continue;
125
	    }
126

  
127
	    switch (j->getMessageType()) {
128
		
129
	    case MIDI_NOTE_ON:
130
		cout << t << ": Note: channel " << ch
131
		     << " duration " << j->getDuration()
132
		     << " pitch " << j->getPitch()
133
		     << " velocity " << j->getVelocity() << endl;
134
		break;
135

  
136
	    case MIDI_POLY_AFTERTOUCH:
137
		cout << t << ": Polyphonic aftertouch: channel " << ch
138
		     << " pitch " << j->getPitch()
139
		     << " pressure " << j->getData2() << endl;
140
		break;
141
		
142
	    case MIDI_CTRL_CHANGE:
143
	    {
144
		int controller = j->getData1();
145
		string name;
146
		switch (controller) {
147
		case MIDI_CONTROLLER_BANK_MSB: name = "Bank select MSB"; break;
148
		case MIDI_CONTROLLER_VOLUME: name = "Volume"; break;
149
		case MIDI_CONTROLLER_BANK_LSB: name = "Bank select LSB"; break;
150
		case MIDI_CONTROLLER_MODULATION: name = "Modulation wheel"; break;
151
		case MIDI_CONTROLLER_PAN: name = "Pan"; break;
152
		case MIDI_CONTROLLER_SUSTAIN: name = "Sustain"; break;
153
		case MIDI_CONTROLLER_RESONANCE: name = "Resonance"; break;
154
		case MIDI_CONTROLLER_RELEASE: name = "Release"; break;
155
		case MIDI_CONTROLLER_ATTACK: name = "Attack"; break;
156
		case MIDI_CONTROLLER_FILTER: name = "Filter"; break;
157
		case MIDI_CONTROLLER_REVERB: name = "Reverb"; break;
158
		case MIDI_CONTROLLER_CHORUS: name = "Chorus"; break;
159
		case MIDI_CONTROLLER_NRPN_1: name = "NRPN 1"; break;
160
		case MIDI_CONTROLLER_NRPN_2: name = "NRPN 2"; break;
161
		case MIDI_CONTROLLER_RPN_1: name = "RPN 1"; break;
162
		case MIDI_CONTROLLER_RPN_2: name = "RPN 2"; break;
163
		case MIDI_CONTROLLER_SOUNDS_OFF: name = "All sounds off"; break;
164
		case MIDI_CONTROLLER_RESET: name = "Reset"; break;
165
		case MIDI_CONTROLLER_LOCAL: name = "Local"; break;
166
		case MIDI_CONTROLLER_ALL_NOTES_OFF: name = "All notes off"; break;
167
		}
168
		cout << t << ": Controller change: channel " << ch
169
		     << " controller " << j->getData1();
170
		if (name != "") cout << " (" << name << ")";
171
		cout << " value " << j->getData2() << endl;
172
	    }
173
		break;
174

  
175
	    case MIDI_PROG_CHANGE:
176
		cout << t << ": Program change: channel " << ch
177
		     << " program " << j->getData1() << endl;
178
		break;
179

  
180
	    case MIDI_CHNL_AFTERTOUCH:
181
		cout << t << ": Channel aftertouch: channel " << ch
182
		     << " pressure " << j->getData1() << endl;
183
		break;
184

  
185
	    case MIDI_PITCH_BEND:
186
		cout << t << ": Pitch bend: channel " << ch
187
		     << " value " << (int)j->getData2() * 128 + (int)j->getData1() << endl;
188
		break;
189

  
190
	    case MIDI_SYSTEM_EXCLUSIVE:
191
		cout << t << ": System exclusive: code "
192
		     << (int)j->getMessageType() << " message length " <<
193
		    j->getMetaMessage().length() << endl;
194
		break;
195
		
196

  
197
	    }
198

  
199
	    
200
	}
201

  
202

  
203
    }
204

  
205
}
206

  
207

  
midifile.pro
1

  
2
TEMPLATE = app
3
TARGET = 
4
QT -= gui
5
DEPENDPATH += .
6
INCLUDEPATH += .
7

  
8
HEADERS += MIDIEvent.h MIDIComposition.h MIDIFileReader.h
9
SOURCES += MIDIFileReader.cpp main.cpp

Also available in: Unified diff