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 @ 4:a98a66b43882

History | View | Annotate | Download (17.7 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-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
*/
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 <QString>
43
#include <QVector>
44

    
45
#include <sstream>
46

    
47
using std::string;
48
using std::ifstream;
49
using std::stringstream;
50
using std::cerr;
51
using std::endl;
52
using std::ends;
53
using std::ios;
54

    
55
using namespace MIDIConstants;
56

    
57
//#define DEBUG_MIDI_FILE_READER 1
58

    
59

    
60
MIDIFileReader::MIDIFileReader(QString path) :
61
    m_timingDivision(0),
62
    m_format(MIDI_FILE_BAD_FORMAT),
63
    m_numberOfTracks(0),
64
    m_trackByteCount(0),
65
    m_decrementCount(false),
66
    m_path(path),
67
    m_midiFile(0),
68
    m_fileSize(0)
69
{
70
    if (parseFile()) {
71
        m_error = "";
72
    }
73
}
74

    
75
MIDIFileReader::~MIDIFileReader()
76
{
77
}
78

    
79
bool
80
MIDIFileReader::isOK() const
81
{
82
    return (m_error == "");
83
}
84

    
85
QString
86
MIDIFileReader::getError() const
87
{
88
    return m_error;
89
}
90

    
91
long
92
MIDIFileReader::midiBytesToLong(const string& bytes)
93
{
94
    if (bytes.length() != 4) {
95
        throw MIDIException(QObject::tr("Wrong length for long data in MIDI stream (%1, should be %2)").arg(bytes.length()).arg(4));
96
    }
97

    
98
    long longRet = ((long)(((MIDIByte)bytes[0]) << 24)) |
99
                   ((long)(((MIDIByte)bytes[1]) << 16)) |
100
                   ((long)(((MIDIByte)bytes[2]) << 8)) |
101
                   ((long)((MIDIByte)(bytes[3])));
102

    
103
    return longRet;
104
}
105

    
106
int
107
MIDIFileReader::midiBytesToInt(const string& bytes)
108
{
109
    if (bytes.length() != 2) {
110
        throw MIDIException(QObject::tr("Wrong length for int data in MIDI stream (%1, should be %2)").arg(bytes.length()).arg(2));
111
    }
112

    
113
    int intRet = ((int)(((MIDIByte)bytes[0]) << 8)) |
114
                 ((int)(((MIDIByte)bytes[1])));
115
    return(intRet);
116
}
117

    
118

    
119
// Gets a single byte from the MIDI byte stream.  For each track
120
// section we can read only a specified number of bytes held in
121
// m_trackByteCount.
122
//
123
MIDIByte
124
MIDIFileReader::getMIDIByte()
125
{
126
    if (!m_midiFile) {
127
        throw MIDIException(QObject::tr("getMIDIByte called but no MIDI file open"));
128
    }
129

    
130
    if (m_midiFile->eof()) {
131
        throw MIDIException(QObject::tr("End of MIDI file encountered while reading"));
132
    }
133

    
134
    if (m_decrementCount && m_trackByteCount <= 0) {
135
        throw MIDIException(QObject::tr("Attempt to get more bytes than expected on Track"));
136
    }
137

    
138
    char byte;
139
    if (m_midiFile->read(&byte, 1)) {
140
        --m_trackByteCount;
141
        return (MIDIByte)byte;
142
    }
143

    
144
    throw MIDIException(QObject::tr("Attempt to read past MIDI file end"));
145
}
146

    
147

    
148
// Gets a specified number of bytes from the MIDI byte stream.  For
149
// each track section we can read only a specified number of bytes
150
// held in m_trackByteCount.
151
//
152
string
153
MIDIFileReader::getMIDIBytes(unsigned long numberOfBytes)
154
{
155
    if (!m_midiFile) {
156
        throw MIDIException(QObject::tr("getMIDIBytes called but no MIDI file open"));
157
    }
158

    
159
    if (m_midiFile->eof()) {
160
        throw MIDIException(QObject::tr("End of MIDI file encountered while reading"));
161
    }
162

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

    
167
    string stringRet;
168
    char fileMIDIByte;
169

    
170
    while (stringRet.length() < numberOfBytes &&
171
           m_midiFile->read(&fileMIDIByte, 1)) {
172
        stringRet += fileMIDIByte;
173
    }
174

    
175
    // if we've reached the end of file without fulfilling the
176
    // quota then panic as our parsing has performed incorrectly
177
    //
178
    if (stringRet.length() < numberOfBytes) {
179
        stringRet = "";
180
        throw MIDIException(QObject::tr("Attempt to read past MIDI file end"));
181
    }
182

    
183
    // decrement the byte count
184
    if (m_decrementCount)
185
        m_trackByteCount -= stringRet.length();
186

    
187
    return stringRet;
188
}
189

    
190

    
191
// Get a long number of variable length from the MIDI byte stream.
192
//
193
long
194
MIDIFileReader::getNumberFromMIDIBytes(int firstByte)
195
{
196
    if (!m_midiFile) {
197
        throw MIDIException(QObject::tr("getNumberFromMIDIBytes called but no MIDI file open"));
198
    }
199

    
200
    long longRet = 0;
201
    MIDIByte midiByte;
202

    
203
    if (firstByte >= 0) {
204
        midiByte = (MIDIByte)firstByte;
205
    } else if (m_midiFile->eof()) {
206
        return longRet;
207
    } else {
208
        midiByte = getMIDIByte();
209
    }
210

    
211
    longRet = midiByte;
212
    if (midiByte & 0x80) {
213
        longRet &= 0x7F;
214
        do {
215
            midiByte = getMIDIByte();
216
            longRet = (longRet << 7) + (midiByte & 0x7F);
217
        } while (!m_midiFile->eof() && (midiByte & 0x80));
218
    }
219

    
220
    return longRet;
221
}
222

    
223

    
224
// Seek to the next track in the midi file and set the number
225
// of bytes to be read in the counter m_trackByteCount.
226
//
227
bool
228
MIDIFileReader::skipToNextTrack()
229
{
230
    if (!m_midiFile) {
231
        throw MIDIException(QObject::tr("skipToNextTrack called but no MIDI file open"));
232
    }
233

    
234
    string buffer, buffer2;
235
    m_trackByteCount = -1;
236
    m_decrementCount = false;
237

    
238
    while (!m_midiFile->eof() && (m_decrementCount == false)) {
239
        buffer = getMIDIBytes(4); 
240
        if (buffer.compare(0, 4, MIDI_TRACK_HEADER) == 0) {
241
            m_trackByteCount = midiBytesToLong(getMIDIBytes(4));
242
            m_decrementCount = true;
243
        }
244
    }
245

    
246
    if (m_trackByteCount == -1) { // we haven't found a track
247
        return false;
248
    } else {
249
        return true;
250
    }
251
}
252

    
253

    
254
// Read in a MIDI file.  The parsing process throws exceptions back up
255
// here if we run into trouble which we can then pass back out to
256
// whoever called us using a nice bool.
257
//
258
bool
259
MIDIFileReader::parseFile()
260
{
261
    m_error = "";
262

    
263
#ifdef DEBUG_MIDI_FILE_READER
264
    cerr << "MIDIFileReader::open() : fileName = " << m_path.toStdString() << endl;
265
#endif
266

    
267
    // Open the file
268
    m_midiFile = new ifstream(m_path.toLocal8Bit().data(),
269
                              ios::in | ios::binary);
270

    
271
    if (!*m_midiFile) {
272
        m_error = "File not found or not readable.";
273
        m_format = MIDI_FILE_BAD_FORMAT;
274
        delete m_midiFile;
275
        m_midiFile = 0;
276
        return false;
277
    }
278

    
279
    bool retval = false;
280

    
281
    try {
282

    
283
        // Set file size so we can count it off
284
        //
285
        m_midiFile->seekg(0, ios::end);
286
        m_fileSize = m_midiFile->tellg();
287
        m_midiFile->seekg(0, ios::beg);
288

    
289
        // Parse the MIDI header first.  The first 14 bytes of the file.
290
        if (!parseHeader(getMIDIBytes(14))) {
291
            m_format = MIDI_FILE_BAD_FORMAT;
292
            m_error = "Not a MIDI file.";
293
            goto done;
294
        }
295

    
296
        for (unsigned int j = 0; j < m_numberOfTracks; ++j) {
297

    
298
#ifdef DEBUG_MIDI_FILE_READER
299
            cerr << "Parsing Track " << j << endl;
300
#endif
301

    
302
            if (!skipToNextTrack()) {
303
#ifdef DEBUG_MIDI_FILE_READER
304
                cerr << "Couldn't find Track " << j << endl;
305
#endif
306
                m_error = "File corrupted or in non-standard format?";
307
                m_format = MIDI_FILE_BAD_FORMAT;
308
                goto done;
309
            }
310

    
311
#ifdef DEBUG_MIDI_FILE_READER
312
            cerr << "Track has " << m_trackByteCount << " bytes" << endl;
313
#endif
314

    
315
            // Run through the events taking them into our internal
316
            // representation.
317
            if (!parseTrack(j)) {
318
#ifdef DEBUG_MIDI_FILE_READER
319
                cerr << "Track " << j << " parsing failed" << endl;
320
#endif
321
                m_error = "File corrupted or in non-standard format?";
322
                m_format = MIDI_FILE_BAD_FORMAT;
323
                goto done;
324
            }
325
        }
326
        
327
        retval = true;
328

    
329
    } catch (MIDIException e) {
330

    
331
        cerr << "MIDIFileReader::open() - caught exception - " << e.what() << endl;
332
        m_error = e.what();
333
    }
334
    
335
done:
336
    m_midiFile->close();
337
    delete m_midiFile;
338

    
339
    for (unsigned int track = 0; track < m_numberOfTracks; ++track) {
340

    
341
        // Convert the deltaTime to an absolute time since the track
342
        // start.  The addTime method returns the sum of the current
343
        // MIDI Event delta time plus the argument.
344

    
345
        unsigned long acc = 0;
346

    
347
        for (MIDITrack::iterator i = m_midiComposition[track].begin();
348
             i != m_midiComposition[track].end(); ++i) {
349
#ifdef DEBUG_MIDI_FILE_READER
350
            cerr << "converting delta time " << i->getTime();
351
#endif
352
            acc = i->addTime(acc);
353
#ifdef DEBUG_MIDI_FILE_READER
354
            cerr << " to " << i->getTime() << endl;
355
#endif
356
        }
357

    
358
        consolidateNoteOffEvents(track);
359
    }
360

    
361
    return retval;
362
}
363

    
364
// Parse and ensure the MIDI Header is legitimate
365
//
366
bool
367
MIDIFileReader::parseHeader(const string &midiHeader)
368
{
369
    if (midiHeader.size() < 14) {
370
#ifdef DEBUG_MIDI_FILE_READER
371
        cerr << "MIDIFileReader::parseHeader() - file header undersized" << endl;
372
#endif
373
        return false;
374
    }
375

    
376
    if (midiHeader.compare(0, 4, MIDI_FILE_HEADER) != 0) {
377
#ifdef DEBUG_MIDI_FILE_READER
378
        cerr << "MIDIFileReader::parseHeader()"
379
             << "- file header not found or malformed"
380
             << endl;
381
#endif
382
        return false;
383
    }
384

    
385
    if (midiBytesToLong(midiHeader.substr(4,4)) != 6L) {
386
#ifdef DEBUG_MIDI_FILE_READER
387
        cerr << "MIDIFileReader::parseHeader()"
388
             << " - header length incorrect"
389
             << endl;
390
#endif
391
        return false;
392
    }
393

    
394
    m_format = (MIDIFileFormatType) midiBytesToInt(midiHeader.substr(8,2));
395
    m_numberOfTracks = midiBytesToInt(midiHeader.substr(10,2));
396
    m_timingDivision = midiBytesToInt(midiHeader.substr(12,2));
397

    
398
#ifdef DEBUG_MIDI_FILE_READER
399
    if (m_timingDivision < 0) {
400
        cerr << "MIDIFileReader::parseHeader()"
401
                  << " - file uses SMPTE timing"
402
                  << endl;
403
    }
404
#endif
405

    
406
    return true; 
407
}
408

    
409
// Extract the contents from a MIDI file track and places it into
410
// our local map of MIDI events.
411
//
412
bool
413
MIDIFileReader::parseTrack(unsigned int trackNum)
414
{
415
    MIDIByte midiByte, metaEventCode, data1, data2;
416
    MIDIByte eventCode = 0x80;
417
    string metaMessage;
418
    unsigned int messageLength;
419
    unsigned long deltaTime;
420
    unsigned long accumulatedTime = 0;
421

    
422
    // Remember the last non-meta status byte (-1 if we haven't seen one)
423
    int runningStatus = -1;
424

    
425
    while (!m_midiFile->eof() && (m_trackByteCount > 0)) {
426

    
427
        if (eventCode < 0x80) {
428
#ifdef DEBUG_MIDI_FILE_READER
429
            cerr << "WARNING: Invalid event code " << eventCode
430
                 << " in MIDI file" << endl;
431
#endif
432
            throw MIDIException(QObject::tr("Invalid event code %1 found").arg(int(eventCode)));
433
        }
434

    
435
        deltaTime = getNumberFromMIDIBytes();
436

    
437
#ifdef DEBUG_MIDI_FILE_READER
438
        cerr << "read delta time " << deltaTime << endl;
439
#endif
440

    
441
        // Get a single byte
442
        midiByte = getMIDIByte();
443

    
444
        if (!(midiByte & MIDI_STATUS_BYTE_MASK)) {
445

    
446
            if (runningStatus < 0) {
447
                throw MIDIException(QObject::tr("Running status used for first event in track"));
448
            }
449

    
450
            eventCode = (MIDIByte)runningStatus;
451
            data1 = midiByte;
452

    
453
#ifdef DEBUG_MIDI_FILE_READER
454
            cerr << "using running status (byte " << int(midiByte) << " found)" << endl;
455
#endif
456
        } else {
457
#ifdef DEBUG_MIDI_FILE_READER
458
            cerr << "have new event code " << int(midiByte) << endl;
459
#endif
460
            eventCode = midiByte;
461
            data1 = getMIDIByte();
462
        }
463

    
464
        if (eventCode == MIDI_FILE_META_EVENT) {
465

    
466
            metaEventCode = data1;
467
            messageLength = getNumberFromMIDIBytes();
468

    
469
#ifdef DEBUG_MIDI_FILE_READER
470
                cerr << "Meta event of type " << int(metaEventCode) << " and " << messageLength << " bytes found, putting on track " << metaTrack << endl;
471
#endif
472
            metaMessage = getMIDIBytes(messageLength);
473

    
474
            accumulatedTime += deltaTime;
475

    
476
            MIDIEvent e(deltaTime,
477
                        MIDI_FILE_META_EVENT,
478
                        metaEventCode,
479
                        metaMessage);
480

    
481
            m_midiComposition[trackNum].push_back(e);
482

    
483
            if (metaEventCode == MIDI_TRACK_NAME) {
484
                m_trackNames[trackNum] = metaMessage.c_str();
485
            }
486

    
487
        } else { // non-meta events
488

    
489
            runningStatus = eventCode;
490

    
491
            int channel = (eventCode & MIDI_CHANNEL_NUM_MASK);
492
            
493
            accumulatedTime += deltaTime;
494

    
495
            switch (eventCode & MIDI_MESSAGE_TYPE_MASK) {
496

    
497
            case MIDI_NOTE_ON:
498
            case MIDI_NOTE_OFF:
499
            case MIDI_POLY_AFTERTOUCH:
500
            case MIDI_CTRL_CHANGE:
501
                data2 = getMIDIByte();
502

    
503
                {
504
                // create and store our event
505
                MIDIEvent midiEvent(deltaTime, eventCode, data1, data2);
506

    
507
#ifdef DEBUG_MIDI_FILE_READER
508
                cerr << "MIDI event for channel " << channel << " (track "
509
                     << trackNum << ") with delta time " << deltaTime << endl;
510
#endif
511

    
512
                m_midiComposition[trackNum].push_back(midiEvent);
513
                }
514
                break;
515

    
516
            case MIDI_PITCH_BEND:
517
                data2 = getMIDIByte();
518

    
519
                {
520
                // create and store our event
521
                MIDIEvent midiEvent(deltaTime, eventCode, data1, data2);
522
                m_midiComposition[trackNum].push_back(midiEvent);
523
                }
524
                break;
525

    
526
            case MIDI_PROG_CHANGE:
527
            case MIDI_CHNL_AFTERTOUCH:
528
                
529
                {
530
                // create and store our event
531
                MIDIEvent midiEvent(deltaTime, eventCode, data1);
532
                m_midiComposition[trackNum].push_back(midiEvent);
533
                }
534
                break;
535

    
536
            case MIDI_SYSTEM_EXCLUSIVE:
537
                messageLength = getNumberFromMIDIBytes(data1);
538

    
539
#ifdef DEBUG_MIDI_FILE_READER
540
                cerr << "SysEx of " << messageLength << " bytes found" << endl;
541
#endif
542

    
543
                metaMessage= getMIDIBytes(messageLength);
544

    
545
                if (MIDIByte(metaMessage[metaMessage.length() - 1]) !=
546
                        MIDI_END_OF_EXCLUSIVE)
547
                {
548
#ifdef DEBUG_MIDI_FILE_READER
549
                    cerr << "MIDIFileReader::parseTrack() - "
550
                              << "malformed or unsupported SysEx type"
551
                              << endl;
552
#endif
553
                    continue;
554
                }
555

    
556
                // chop off the EOX 
557
                // length fixed by Pedro Lopez-Cabanillas (20030523)
558
                //
559
                metaMessage = metaMessage.substr(0, metaMessage.length()-1);
560

    
561
                {
562
                MIDIEvent midiEvent(deltaTime,
563
                                    MIDI_SYSTEM_EXCLUSIVE,
564
                                    metaMessage);
565
                m_midiComposition[trackNum].push_back(midiEvent);
566
                }
567
                break;
568

    
569
            case MIDI_END_OF_EXCLUSIVE:
570
#ifdef DEBUG_MIDI_FILE_READER
571
                cerr << "MIDIFileReader::parseTrack() - "
572
                          << "Found a stray MIDI_END_OF_EXCLUSIVE" << endl;
573
#endif
574
                break;
575

    
576
            default:
577
#ifdef DEBUG_MIDI_FILE_READER
578
                cerr << "MIDIFileReader::parseTrack()" 
579
                          << " - Unsupported MIDI Event Code:  "
580
                          << (int)eventCode << endl;
581
#endif
582
                break;
583
            } 
584
        }
585
    }
586

    
587
    return true;
588
}
589

    
590
// Delete dead NOTE OFF and NOTE ON/Zero Velocity Events after
591
// reading them and modifying their relevant NOTE ONs.  Return true
592
// if there are some notes in this track.
593
//
594
bool
595
MIDIFileReader::consolidateNoteOffEvents(unsigned int track)
596
{
597
    bool notesOnTrack = false;
598
    bool noteOffFound;
599

    
600
    MIDITrack &t = m_midiComposition[track];
601

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

    
604
        if (i->getMessageType() == MIDI_NOTE_ON && i->getVelocity() > 0) {
605

    
606
#ifdef DEBUG_MIDI_FILE_READER
607
            cerr << "Looking for note-offs for note at " << i->getTime() << " (pitch " << (int)i->getPitch() << ")" <<  endl;
608
#endif
609

    
610
            notesOnTrack = true;
611
            noteOffFound = false;
612

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

    
615
                if ((j->getChannelNumber() == i->getChannelNumber()) &&
616
                    (j->getPitch() == i->getPitch()) &&
617
                    (j->getMessageType() == MIDI_NOTE_OFF ||
618
                    (j->getMessageType() == MIDI_NOTE_ON &&
619
                     j->getVelocity() == 0x00))) {
620

    
621
#ifdef DEBUG_MIDI_FILE_READER
622
                    cerr << "Found note-off at " << j->getTime() << " for note at " << i->getTime() << endl;
623
#endif
624

    
625
                    i->setDuration(j->getTime() - i->getTime());
626

    
627
#ifdef DEBUG_MIDI_FILE_READER
628
                    cerr << "Duration is now " << i->getDuration() << endl;
629
#endif
630

    
631
                    t.erase(j);
632

    
633
                    noteOffFound = true;
634
                    break;
635
                }
636
            }
637

    
638
            // If no matching NOTE OFF has been found then set
639
            // Event duration to length of track
640
            //
641
            if (!noteOffFound) {
642
#ifdef DEBUG_MIDI_FILE_READER
643
                cerr << "Failed to find note-off for note at " << i->getTime() << endl;
644
#endif
645
                MIDITrack::iterator j = t.end();
646
                --j;
647
                i->setDuration(j->getTime() - i->getTime());
648
            }
649
        }
650
    }
651

    
652
    return notesOnTrack;
653
}
654

    
655
MIDIComposition
656
MIDIFileReader::load() const
657
{
658
    return m_midiComposition;
659
}
660

    
661