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 @ 1:3e65e0344413

History | View | Annotate | Download (16.4 KB)

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