To check out this repository please hg clone the following URL, or open the URL using EasyMercurial or your preferred Mercurial client.

Statistics Download as Zip
| Branch: | Revision:

root / MIDIFileReader.cpp

History | View | Annotate | Download (17.5 KB)

1 1:3e65e0344413 cannam
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
2
3
/*
4 4:a98a66b43882 Chris
    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-2010 Richard Bown and Chris Cannam.
7

8
    Permission is hereby granted, free of charge, to any person
9
    obtaining a copy of this software and associated documentation
10
    files (the "Software"), to deal in the Software without
11
    restriction, including without limitation the rights to use, copy,
12
    modify, merge, publish, distribute, sublicense, and/or sell copies
13
    of the Software, and to permit persons to whom the Software is
14
    furnished to do so, subject to the following conditions:
15

16
    The above copyright notice and this permission notice shall be
17
    included in all copies or substantial portions of the Software.
18

19
    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
20
    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
21
    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
22
    NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
23
    ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
24
    CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
25
    WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26

27
    Except as contained in this notice, the names of the authors
28
    shall not be used in advertising or otherwise to promote the sale,
29
    use or other dealings in this Software without prior written
30
    authorization.
31 1:3e65e0344413 cannam
*/
32
33
34
#include <iostream>
35
#include <fstream>
36
#include <string>
37
#include <cstdio>
38
39
#include "MIDIFileReader.h"
40
#include "MIDIEvent.h"
41
42
#include <sstream>
43
44
using std::string;
45
using std::ifstream;
46
using std::stringstream;
47
using std::cerr;
48
using std::endl;
49
using std::ends;
50
using std::ios;
51
52
using namespace MIDIConstants;
53
54
//#define DEBUG_MIDI_FILE_READER 1
55
56 5:7fde3cc109dc Chris
#define throw_exception(...) do { \
57
        char message[128]; \
58
        snprintf(message, 128, __VA_ARGS__); \
59
        throw MIDIException(std::string(message)); \
60
    } while (0)
61
62 1:3e65e0344413 cannam
63 5:7fde3cc109dc Chris
64
MIDIFileReader::MIDIFileReader(std::string path) :
65 1:3e65e0344413 cannam
    m_timingDivision(0),
66
    m_format(MIDI_FILE_BAD_FORMAT),
67
    m_numberOfTracks(0),
68
    m_trackByteCount(0),
69
    m_decrementCount(false),
70
    m_path(path),
71
    m_midiFile(0),
72
    m_fileSize(0)
73
{
74
    if (parseFile()) {
75
        m_error = "";
76
    }
77
}
78
79
MIDIFileReader::~MIDIFileReader()
80
{
81
}
82
83
bool
84
MIDIFileReader::isOK() const
85
{
86
    return (m_error == "");
87
}
88
89 5:7fde3cc109dc Chris
std::string
90 1:3e65e0344413 cannam
MIDIFileReader::getError() const
91
{
92
    return m_error;
93
}
94
95
long
96
MIDIFileReader::midiBytesToLong(const string& bytes)
97
{
98
    if (bytes.length() != 4) {
99 5:7fde3cc109dc Chris
        throw_exception("Wrong length for long data in MIDI stream (%d, should be %d)", (int)bytes.length(), 4);
100 1:3e65e0344413 cannam
    }
101
102
    long longRet = ((long)(((MIDIByte)bytes[0]) << 24)) |
103
                   ((long)(((MIDIByte)bytes[1]) << 16)) |
104
                   ((long)(((MIDIByte)bytes[2]) << 8)) |
105
                   ((long)((MIDIByte)(bytes[3])));
106
107
    return longRet;
108
}
109
110
int
111
MIDIFileReader::midiBytesToInt(const string& bytes)
112
{
113
    if (bytes.length() != 2) {
114 5:7fde3cc109dc Chris
        throw_exception("Wrong length for int data in MIDI stream (%d, should be %d)", (int)bytes.length(), 2);
115 1:3e65e0344413 cannam
    }
116
117
    int intRet = ((int)(((MIDIByte)bytes[0]) << 8)) |
118
                 ((int)(((MIDIByte)bytes[1])));
119
    return(intRet);
120
}
121
122
123
// Gets a single byte from the MIDI byte stream.  For each track
124
// section we can read only a specified number of bytes held in
125
// m_trackByteCount.
126
//
127
MIDIByte
128
MIDIFileReader::getMIDIByte()
129
{
130
    if (!m_midiFile) {
131 5:7fde3cc109dc Chris
        throw_exception("getMIDIByte called but no MIDI file open");
132 1:3e65e0344413 cannam
    }
133
134
    if (m_midiFile->eof()) {
135 5:7fde3cc109dc Chris
        throw_exception("End of MIDI file encountered while reading");
136 1:3e65e0344413 cannam
    }
137
138
    if (m_decrementCount && m_trackByteCount <= 0) {
139 5:7fde3cc109dc Chris
        throw_exception("Attempt to get more bytes than expected on Track");
140 1:3e65e0344413 cannam
    }
141
142
    char byte;
143
    if (m_midiFile->read(&byte, 1)) {
144
        --m_trackByteCount;
145
        return (MIDIByte)byte;
146
    }
147
148 5:7fde3cc109dc Chris
    throw_exception("Attempt to read past MIDI file end");
149 1:3e65e0344413 cannam
}
150
151
152
// Gets a specified number of bytes from the MIDI byte stream.  For
153
// each track section we can read only a specified number of bytes
154
// held in m_trackByteCount.
155
//
156
string
157
MIDIFileReader::getMIDIBytes(unsigned long numberOfBytes)
158
{
159
    if (!m_midiFile) {
160 5:7fde3cc109dc Chris
        throw_exception("getMIDIBytes called but no MIDI file open");
161 1:3e65e0344413 cannam
    }
162
163
    if (m_midiFile->eof()) {
164 5:7fde3cc109dc Chris
        throw_exception("End of MIDI file encountered while reading");
165 1:3e65e0344413 cannam
    }
166
167
    if (m_decrementCount && (numberOfBytes > (unsigned long)m_trackByteCount)) {
168 5:7fde3cc109dc Chris
        throw_exception("Attempt to get more bytes than available on Track (%lu, only have %ld)", numberOfBytes, m_trackByteCount);
169 1:3e65e0344413 cannam
    }
170
171
    string stringRet;
172
    char fileMIDIByte;
173
174
    while (stringRet.length() < numberOfBytes &&
175
           m_midiFile->read(&fileMIDIByte, 1)) {
176
        stringRet += fileMIDIByte;
177
    }
178
179
    // if we've reached the end of file without fulfilling the
180
    // quota then panic as our parsing has performed incorrectly
181
    //
182
    if (stringRet.length() < numberOfBytes) {
183
        stringRet = "";
184 5:7fde3cc109dc Chris
        throw_exception("Attempt to read past MIDI file end");
185 1:3e65e0344413 cannam
    }
186
187
    // decrement the byte count
188
    if (m_decrementCount)
189
        m_trackByteCount -= stringRet.length();
190
191
    return stringRet;
192
}
193
194
195
// Get a long number of variable length from the MIDI byte stream.
196
//
197
long
198
MIDIFileReader::getNumberFromMIDIBytes(int firstByte)
199
{
200
    if (!m_midiFile) {
201 5:7fde3cc109dc Chris
        throw_exception("getNumberFromMIDIBytes called but no MIDI file open");
202 1:3e65e0344413 cannam
    }
203
204
    long longRet = 0;
205
    MIDIByte midiByte;
206
207
    if (firstByte >= 0) {
208
        midiByte = (MIDIByte)firstByte;
209
    } else if (m_midiFile->eof()) {
210
        return longRet;
211
    } else {
212
        midiByte = getMIDIByte();
213
    }
214
215
    longRet = midiByte;
216
    if (midiByte & 0x80) {
217
        longRet &= 0x7F;
218
        do {
219
            midiByte = getMIDIByte();
220
            longRet = (longRet << 7) + (midiByte & 0x7F);
221
        } while (!m_midiFile->eof() && (midiByte & 0x80));
222
    }
223
224
    return longRet;
225
}
226
227
228
// Seek to the next track in the midi file and set the number
229
// of bytes to be read in the counter m_trackByteCount.
230
//
231
bool
232
MIDIFileReader::skipToNextTrack()
233
{
234
    if (!m_midiFile) {
235 5:7fde3cc109dc Chris
        throw_exception("skipToNextTrack called but no MIDI file open");
236 1:3e65e0344413 cannam
    }
237
238
    string buffer, buffer2;
239
    m_trackByteCount = -1;
240
    m_decrementCount = false;
241
242
    while (!m_midiFile->eof() && (m_decrementCount == false)) {
243
        buffer = getMIDIBytes(4);
244
        if (buffer.compare(0, 4, MIDI_TRACK_HEADER) == 0) {
245
            m_trackByteCount = midiBytesToLong(getMIDIBytes(4));
246
            m_decrementCount = true;
247
        }
248
    }
249
250
    if (m_trackByteCount == -1) { // we haven't found a track
251
        return false;
252
    } else {
253
        return true;
254
    }
255
}
256
257
258
// Read in a MIDI file.  The parsing process throws exceptions back up
259
// here if we run into trouble which we can then pass back out to
260
// whoever called us using a nice bool.
261
//
262
bool
263
MIDIFileReader::parseFile()
264
{
265
    m_error = "";
266
267
#ifdef DEBUG_MIDI_FILE_READER
268 7:b9a2f08e2c62 Chris
    cerr << "MIDIFileReader::open() : fileName = " << m_path << endl;
269 1:3e65e0344413 cannam
#endif
270
271
    // Open the file
272 5:7fde3cc109dc Chris
    m_midiFile = new ifstream(m_path.c_str(), ios::in | ios::binary);
273 1:3e65e0344413 cannam
274
    if (!*m_midiFile) {
275
        m_error = "File not found or not readable.";
276
        m_format = MIDI_FILE_BAD_FORMAT;
277
        delete m_midiFile;
278
        m_midiFile = 0;
279
        return false;
280
    }
281
282
    bool retval = false;
283
284
    try {
285
286
        // Set file size so we can count it off
287
        //
288
        m_midiFile->seekg(0, ios::end);
289
        m_fileSize = m_midiFile->tellg();
290
        m_midiFile->seekg(0, ios::beg);
291
292
        // Parse the MIDI header first.  The first 14 bytes of the file.
293
        if (!parseHeader(getMIDIBytes(14))) {
294
            m_format = MIDI_FILE_BAD_FORMAT;
295
            m_error = "Not a MIDI file.";
296
            goto done;
297
        }
298
299
        for (unsigned int j = 0; j < m_numberOfTracks; ++j) {
300
301
#ifdef DEBUG_MIDI_FILE_READER
302
            cerr << "Parsing Track " << j << endl;
303
#endif
304
305
            if (!skipToNextTrack()) {
306
#ifdef DEBUG_MIDI_FILE_READER
307
                cerr << "Couldn't find Track " << j << endl;
308
#endif
309
                m_error = "File corrupted or in non-standard format?";
310
                m_format = MIDI_FILE_BAD_FORMAT;
311
                goto done;
312
            }
313
314
#ifdef DEBUG_MIDI_FILE_READER
315
            cerr << "Track has " << m_trackByteCount << " bytes" << endl;
316
#endif
317
318
            // Run through the events taking them into our internal
319
            // representation.
320
            if (!parseTrack(j)) {
321
#ifdef DEBUG_MIDI_FILE_READER
322
                cerr << "Track " << j << " parsing failed" << endl;
323
#endif
324
                m_error = "File corrupted or in non-standard format?";
325
                m_format = MIDI_FILE_BAD_FORMAT;
326
                goto done;
327
            }
328
        }
329
330
        retval = true;
331
332
    } catch (MIDIException e) {
333
334
        cerr << "MIDIFileReader::open() - caught exception - " << e.what() << endl;
335
        m_error = e.what();
336
    }
337
338
done:
339
    m_midiFile->close();
340
    delete m_midiFile;
341
342
    for (unsigned int track = 0; track < m_numberOfTracks; ++track) {
343
344
        // Convert the deltaTime to an absolute time since the track
345
        // start.  The addTime method returns the sum of the current
346
        // MIDI Event delta time plus the argument.
347
348
        unsigned long acc = 0;
349
350
        for (MIDITrack::iterator i = m_midiComposition[track].begin();
351
             i != m_midiComposition[track].end(); ++i) {
352
#ifdef DEBUG_MIDI_FILE_READER
353
            cerr << "converting delta time " << i->getTime();
354
#endif
355
            acc = i->addTime(acc);
356
#ifdef DEBUG_MIDI_FILE_READER
357
            cerr << " to " << i->getTime() << endl;
358
#endif
359
        }
360
361
        consolidateNoteOffEvents(track);
362
    }
363
364
    return retval;
365
}
366
367
// Parse and ensure the MIDI Header is legitimate
368
//
369
bool
370
MIDIFileReader::parseHeader(const string &midiHeader)
371
{
372
    if (midiHeader.size() < 14) {
373
#ifdef DEBUG_MIDI_FILE_READER
374
        cerr << "MIDIFileReader::parseHeader() - file header undersized" << endl;
375
#endif
376
        return false;
377
    }
378
379
    if (midiHeader.compare(0, 4, MIDI_FILE_HEADER) != 0) {
380
#ifdef DEBUG_MIDI_FILE_READER
381
        cerr << "MIDIFileReader::parseHeader()"
382
             << "- file header not found or malformed"
383
             << endl;
384
#endif
385
        return false;
386
    }
387
388
    if (midiBytesToLong(midiHeader.substr(4,4)) != 6L) {
389
#ifdef DEBUG_MIDI_FILE_READER
390
        cerr << "MIDIFileReader::parseHeader()"
391
             << " - header length incorrect"
392
             << endl;
393
#endif
394
        return false;
395
    }
396
397
    m_format = (MIDIFileFormatType) midiBytesToInt(midiHeader.substr(8,2));
398
    m_numberOfTracks = midiBytesToInt(midiHeader.substr(10,2));
399
    m_timingDivision = midiBytesToInt(midiHeader.substr(12,2));
400
401
#ifdef DEBUG_MIDI_FILE_READER
402
    if (m_timingDivision < 0) {
403
        cerr << "MIDIFileReader::parseHeader()"
404
                  << " - file uses SMPTE timing"
405
                  << endl;
406
    }
407
#endif
408
409
    return true;
410
}
411
412
// Extract the contents from a MIDI file track and places it into
413
// our local map of MIDI events.
414
//
415
bool
416
MIDIFileReader::parseTrack(unsigned int trackNum)
417
{
418
    MIDIByte midiByte, metaEventCode, data1, data2;
419
    MIDIByte eventCode = 0x80;
420
    string metaMessage;
421
    unsigned int messageLength;
422
    unsigned long deltaTime;
423
    unsigned long accumulatedTime = 0;
424
425
    // Remember the last non-meta status byte (-1 if we haven't seen one)
426
    int runningStatus = -1;
427
428
    while (!m_midiFile->eof() && (m_trackByteCount > 0)) {
429
430
        if (eventCode < 0x80) {
431
#ifdef DEBUG_MIDI_FILE_READER
432
            cerr << "WARNING: Invalid event code " << eventCode
433
                 << " in MIDI file" << endl;
434
#endif
435 5:7fde3cc109dc Chris
            throw_exception("Invalid event code %d found", int(eventCode));
436 1:3e65e0344413 cannam
        }
437
438
        deltaTime = getNumberFromMIDIBytes();
439
440
#ifdef DEBUG_MIDI_FILE_READER
441
        cerr << "read delta time " << deltaTime << endl;
442
#endif
443
444
        // Get a single byte
445
        midiByte = getMIDIByte();
446
447
        if (!(midiByte & MIDI_STATUS_BYTE_MASK)) {
448
449
            if (runningStatus < 0) {
450 5:7fde3cc109dc Chris
                throw_exception("Running status used for first event in track");
451 1:3e65e0344413 cannam
            }
452
453
            eventCode = (MIDIByte)runningStatus;
454
            data1 = midiByte;
455
456
#ifdef DEBUG_MIDI_FILE_READER
457
            cerr << "using running status (byte " << int(midiByte) << " found)" << endl;
458
#endif
459
        } else {
460
#ifdef DEBUG_MIDI_FILE_READER
461
            cerr << "have new event code " << int(midiByte) << endl;
462
#endif
463
            eventCode = midiByte;
464
            data1 = getMIDIByte();
465
        }
466
467
        if (eventCode == MIDI_FILE_META_EVENT) {
468
469
            metaEventCode = data1;
470
            messageLength = getNumberFromMIDIBytes();
471
472
#ifdef DEBUG_MIDI_FILE_READER
473 7:b9a2f08e2c62 Chris
            cerr << "Meta event of type " << int(metaEventCode) << " and " << messageLength << " bytes found" << endl;
474 1:3e65e0344413 cannam
#endif
475
            metaMessage = getMIDIBytes(messageLength);
476
477
            accumulatedTime += deltaTime;
478
479
            MIDIEvent e(deltaTime,
480
                        MIDI_FILE_META_EVENT,
481
                        metaEventCode,
482
                        metaMessage);
483
484
            m_midiComposition[trackNum].push_back(e);
485
486
            if (metaEventCode == MIDI_TRACK_NAME) {
487
                m_trackNames[trackNum] = metaMessage.c_str();
488
            }
489
490
        } else { // non-meta events
491
492
            runningStatus = eventCode;
493
494
            int channel = (eventCode & MIDI_CHANNEL_NUM_MASK);
495
496
            accumulatedTime += deltaTime;
497
498
            switch (eventCode & MIDI_MESSAGE_TYPE_MASK) {
499
500
            case MIDI_NOTE_ON:
501
            case MIDI_NOTE_OFF:
502
            case MIDI_POLY_AFTERTOUCH:
503
            case MIDI_CTRL_CHANGE:
504
                data2 = getMIDIByte();
505
506
                {
507
                // create and store our event
508
                MIDIEvent midiEvent(deltaTime, eventCode, data1, data2);
509
510
#ifdef DEBUG_MIDI_FILE_READER
511
                cerr << "MIDI event for channel " << channel << " (track "
512
                     << trackNum << ") with delta time " << deltaTime << endl;
513
#endif
514
515
                m_midiComposition[trackNum].push_back(midiEvent);
516
                }
517
                break;
518
519
            case MIDI_PITCH_BEND:
520
                data2 = getMIDIByte();
521
522
                {
523
                // create and store our event
524
                MIDIEvent midiEvent(deltaTime, eventCode, data1, data2);
525
                m_midiComposition[trackNum].push_back(midiEvent);
526
                }
527
                break;
528
529
            case MIDI_PROG_CHANGE:
530
            case MIDI_CHNL_AFTERTOUCH:
531
532
                {
533
                // create and store our event
534
                MIDIEvent midiEvent(deltaTime, eventCode, data1);
535
                m_midiComposition[trackNum].push_back(midiEvent);
536
                }
537
                break;
538
539
            case MIDI_SYSTEM_EXCLUSIVE:
540
                messageLength = getNumberFromMIDIBytes(data1);
541
542
#ifdef DEBUG_MIDI_FILE_READER
543
                cerr << "SysEx of " << messageLength << " bytes found" << endl;
544
#endif
545
546
                metaMessage= getMIDIBytes(messageLength);
547
548
                if (MIDIByte(metaMessage[metaMessage.length() - 1]) !=
549
                        MIDI_END_OF_EXCLUSIVE)
550
                {
551
#ifdef DEBUG_MIDI_FILE_READER
552
                    cerr << "MIDIFileReader::parseTrack() - "
553
                              << "malformed or unsupported SysEx type"
554
                              << endl;
555
#endif
556
                    continue;
557
                }
558
559
                // chop off the EOX
560
                // length fixed by Pedro Lopez-Cabanillas (20030523)
561
                //
562
                metaMessage = metaMessage.substr(0, metaMessage.length()-1);
563
564
                {
565
                MIDIEvent midiEvent(deltaTime,
566
                                    MIDI_SYSTEM_EXCLUSIVE,
567
                                    metaMessage);
568
                m_midiComposition[trackNum].push_back(midiEvent);
569
                }
570
                break;
571
572
            case MIDI_END_OF_EXCLUSIVE:
573
#ifdef DEBUG_MIDI_FILE_READER
574
                cerr << "MIDIFileReader::parseTrack() - "
575
                          << "Found a stray MIDI_END_OF_EXCLUSIVE" << endl;
576
#endif
577
                break;
578
579
            default:
580
#ifdef DEBUG_MIDI_FILE_READER
581
                cerr << "MIDIFileReader::parseTrack()"
582
                          << " - Unsupported MIDI Event Code:  "
583
                          << (int)eventCode << endl;
584
#endif
585
                break;
586
            }
587
        }
588
    }
589
590
    return true;
591
}
592
593
// Delete dead NOTE OFF and NOTE ON/Zero Velocity Events after
594
// reading them and modifying their relevant NOTE ONs.  Return true
595
// if there are some notes in this track.
596
//
597
bool
598
MIDIFileReader::consolidateNoteOffEvents(unsigned int track)
599
{
600
    bool notesOnTrack = false;
601
    bool noteOffFound;
602
603
    MIDITrack &t = m_midiComposition[track];
604
605
    for (MIDITrack::iterator i = t.begin(); i != t.end(); ++i) {
606
607
        if (i->getMessageType() == MIDI_NOTE_ON && i->getVelocity() > 0) {
608
609
#ifdef DEBUG_MIDI_FILE_READER
610
            cerr << "Looking for note-offs for note at " << i->getTime() << " (pitch " << (int)i->getPitch() << ")" <<  endl;
611
#endif
612
613
            notesOnTrack = true;
614
            noteOffFound = false;
615
616
            for (MIDITrack::iterator j = i; j != t.end(); ++j) {
617
618
                if ((j->getChannelNumber() == i->getChannelNumber()) &&
619
                    (j->getPitch() == i->getPitch()) &&
620
                    (j->getMessageType() == MIDI_NOTE_OFF ||
621
                    (j->getMessageType() == MIDI_NOTE_ON &&
622
                     j->getVelocity() == 0x00))) {
623
624
#ifdef DEBUG_MIDI_FILE_READER
625
                    cerr << "Found note-off at " << j->getTime() << " for note at " << i->getTime() << endl;
626
#endif
627
628
                    i->setDuration(j->getTime() - i->getTime());
629
630
#ifdef DEBUG_MIDI_FILE_READER
631
                    cerr << "Duration is now " << i->getDuration() << endl;
632
#endif
633
634
                    t.erase(j);
635
636
                    noteOffFound = true;
637
                    break;
638
                }
639
            }
640
641
            // If no matching NOTE OFF has been found then set
642
            // Event duration to length of track
643
            //
644
            if (!noteOffFound) {
645
#ifdef DEBUG_MIDI_FILE_READER
646
                cerr << "Failed to find note-off for note at " << i->getTime() << endl;
647
#endif
648
                MIDITrack::iterator j = t.end();
649
                --j;
650
                i->setDuration(j->getTime() - i->getTime());
651
            }
652
        }
653
    }
654
655
    return notesOnTrack;
656
}
657
658
MIDIComposition
659
MIDIFileReader::load() const
660
{
661
    return m_midiComposition;
662
}
663