-
Notifications
You must be signed in to change notification settings - Fork 187
/
Copy pathMidiFileIn.cpp
353 lines (309 loc) · 11.6 KB
/
MidiFileIn.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
/**********************************************************************/
/*! \class MidiFileIn
\brief A standard MIDI file reading/parsing class.
This class can be used to read events from a standard MIDI file.
Event bytes are copied to a C++ vector and must be subsequently
interpreted by the user. The function getNextMidiEvent() skips
meta and sysex events, returning only MIDI channel messages.
Event delta-times are returned in the form of "ticks" and a
function is provided to determine the current "seconds per tick".
Tempo changes are internally tracked by the class and reflected in
the values returned by the function getTickSeconds().
by Gary P. Scavone, 2003--2023.
*/
/**********************************************************************/
#include "MidiFileIn.h"
#include <cstring>
#include <iostream>
namespace stk {
MidiFileIn :: MidiFileIn( std::string fileName )
{
// Attempt to open the file.
file_.open( fileName.c_str(), std::ios::in | std::ios::binary );
if ( !file_ ) {
oStream_ << "MidiFileIn: error opening or finding file (" << fileName << ").";
handleError( StkError::FILE_NOT_FOUND );
}
// Parse header info.
char chunkType[4];
char buffer[4];
SINT32 *length;
if ( !file_.read( chunkType, 4 ) ) goto error;
if ( !file_.read( buffer, 4 ) ) goto error;
#ifdef __LITTLE_ENDIAN__
swap32((unsigned char *)&buffer);
#endif
length = (SINT32 *) &buffer;
if ( strncmp( chunkType, "MThd", 4 ) || ( *length != 6 ) ) {
oStream_ << "MidiFileIn: file (" << fileName << ") does not appear to be a MIDI file!";
handleError( StkError::FILE_UNKNOWN_FORMAT );
}
// Read the MIDI file format.
SINT16 *data;
if ( !file_.read( buffer, 2 ) ) goto error;
#ifdef __LITTLE_ENDIAN__
swap16((unsigned char *)&buffer);
#endif
data = (SINT16 *) &buffer;
if ( *data < 0 || *data > 2 ) {
oStream_ << "MidiFileIn: the file (" << fileName << ") format is invalid!";
handleError( StkError::FILE_ERROR );
}
format_ = *data;
// Read the number of tracks.
if ( !file_.read( buffer, 2 ) ) goto error;
#ifdef __LITTLE_ENDIAN__
swap16((unsigned char *)&buffer);
#endif
if ( format_ == 0 && *data != 1 ) {
oStream_ << "MidiFileIn: invalid number of tracks (>1) for a file format = 0!";
handleError( StkError::FILE_ERROR );
}
nTracks_ = *data;
// Read the beat division.
if ( !file_.read( buffer, 2 ) ) goto error;
#ifdef __LITTLE_ENDIAN__
swap16((unsigned char *)&buffer);
#endif
division_ = (int) *data;
double tickrate;
usingTimeCode_ = false;
if ( *data & 0x8000 ) {
// Determine ticks per second from time-code formats.
signed char tmp = -(*data & 0xFF00)>>8;
tickrate = (double) tmp;
// If frames per second value is 29, it really should be 29.97.
if ( tickrate == 29.0 ) tickrate = 29.97;
tickrate *= (*data & 0x00FF);
usingTimeCode_ = true;
}
else {
tickrate = (double) (*data & 0x7FFF); // ticks per quarter note
}
// Now locate the track offsets and lengths. If not using time
// code, we can initialize the "tick time" using a default tempo of
// 120 beats per minute. We will then check for tempo meta-events
// afterward.
unsigned int i;
for ( i=0; i<nTracks_; i++ ) {
if ( !file_.read( chunkType, 4 ) ) goto error;
if ( strncmp( chunkType, "MTrk", 4 ) ) goto error;
if ( !file_.read( buffer, 4 ) ) goto error;
#ifdef __LITTLE_ENDIAN__
swap32((unsigned char *)&buffer);
#endif
length = (SINT32 *) &buffer;
trackLengths_.push_back( *length );
trackOffsets_.push_back( (long) file_.tellg() );
trackPointers_.push_back( (long) file_.tellg() );
trackStatus_.push_back( 0 );
file_.seekg( *length, std::ios_base::cur );
if ( usingTimeCode_ ) tickSeconds_.push_back( (double) (1.0 / tickrate) );
else tickSeconds_.push_back( (double) (0.5 / tickrate) );
}
// Save the initial tickSeconds parameter.
TempoChange tempoEvent;
tempoEvent.count = 0;
tempoEvent.tickSeconds = tickSeconds_[0];
tempoEvents_.push_back( tempoEvent );
// If format 1 and not using time code, parse and save the tempo map
// on track 0.
if ( format_ == 1 && !usingTimeCode_ ) {
std::vector<unsigned char> event;
unsigned long value, count;
// We need to temporarily change the usingTimeCode_ value here so
// that the getNextEvent() function doesn't try to check the tempo
// map (which we're creating here).
usingTimeCode_ = true;
count = getNextEvent( &event, 0 );
while ( event.size() ) {
if ( ( event.size() == 6 ) && ( event[0] == 0xff ) &&
( event[1] == 0x51 ) && ( event[2] == 0x03 ) ) {
tempoEvent.count = count;
value = ( event[3] << 16 ) + ( event[4] << 8 ) + event[5];
tempoEvent.tickSeconds = (double) (0.000001 * value / tickrate);
if ( count > tempoEvents_.back().count )
tempoEvents_.push_back( tempoEvent );
else
tempoEvents_.back() = tempoEvent;
}
count += getNextEvent( &event, 0 );
}
rewindTrack( 0 );
for ( unsigned int i=0; i<nTracks_; i++ ) {
trackCounters_.push_back( 0 );
trackTempoIndex_.push_back( 0 );
}
// Change the time code flag back!
usingTimeCode_ = false;
}
return;
error:
oStream_ << "MidiFileIn: error reading from file (" << fileName << ").";
handleError( StkError::FILE_ERROR );
}
MidiFileIn :: ~MidiFileIn()
{
// An ifstream object implicitly closes itself during destruction
// but we'll make an explicit call to "close" anyway.
file_.close();
}
void MidiFileIn :: rewindTrack( unsigned int track )
{
if ( track >= nTracks_ ) {
oStream_ << "MidiFileIn::getNextEvent: invalid track argument (" << track << ").";
handleError( StkError::WARNING ); return;
}
trackPointers_[track] = trackOffsets_[track];
trackStatus_[track] = 0;
tickSeconds_[track] = tempoEvents_[0].tickSeconds;
}
double MidiFileIn :: getTickSeconds( unsigned int track )
{
// Return the current tick value in seconds for the given track.
if ( track >= nTracks_ ) {
oStream_ << "MidiFileIn::getTickSeconds: invalid track argument (" << track << ").";
handleError( StkError::WARNING ); return 0.0;
}
return tickSeconds_[track];
}
unsigned long MidiFileIn :: getNextEvent( std::vector<unsigned char> *event, unsigned int track )
{
// Fill the user-provided vector with the next event in the
// specified track (default = 0) and return the event delta time in
// ticks. This function assumes that the stored track pointer is
// positioned at the start of a track event. If the track has
// reached its end, the event vector size will be zero.
//
// If we have a format 0 or 2 file and we're not using timecode, we
// should check every meta-event for tempo changes and make
// appropriate updates to the tickSeconds_ parameter if so.
//
// If we have a format 1 file and we're not using timecode, keep a
// running sum of ticks for each track and update the tickSeconds_
// parameter as needed based on the stored tempo map.
event->clear();
if ( track >= nTracks_ ) {
oStream_ << "MidiFileIn::getNextEvent: invalid track argument (" << track << ").";
handleError( StkError::WARNING ); return 0;
}
// Check for the end of the track.
if ( (trackPointers_[track] - trackOffsets_[track]) >= trackLengths_[track] )
return 0;
unsigned long ticks = 0, bytes = 0;
bool isTempoEvent = false;
// Read the event delta time.
file_.seekg( trackPointers_[track], std::ios_base::beg );
if ( !readVariableLength( &ticks ) ) goto error;
// Parse the event stream to determine the event length.
unsigned char c;
if ( !file_.read( (char *)&c, 1 ) ) goto error;
switch ( c ) {
case 0xFF: // A Meta-Event
unsigned long position;
trackStatus_[track] = 0;
event->push_back( c );
if ( !file_.read( (char *)&c, 1 ) ) goto error;
event->push_back( c );
if ( format_ != 1 && ( c == 0x51 ) ) isTempoEvent = true;
position = (unsigned long)file_.tellg();
if ( !readVariableLength( &bytes ) ) goto error;
bytes += ( (unsigned long)file_.tellg() - position );
file_.seekg( position, std::ios_base::beg );
break;
case 0xF0:
case 0xF7: // The start or continuation of a Sysex event
trackStatus_[track] = 0;
event->push_back( c );
position = (unsigned long)file_.tellg();
if ( !readVariableLength( &bytes ) ) goto error;
bytes += ( (unsigned long)file_.tellg() - position );
file_.seekg( position, std::ios_base::beg );
break;
default: // Should be a MIDI channel event
if ( c & 0x80 ) { // MIDI status byte
if ( c > 0xF0 ) goto error;
trackStatus_[track] = c;
event->push_back( c );
c &= 0xF0;
if ( (c == 0xC0) || (c == 0xD0) ) bytes = 1;
else bytes = 2;
}
else if ( trackStatus_[track] & 0x80 ) { // Running status
event->push_back( trackStatus_[track] );
event->push_back( c );
c = trackStatus_[track] & 0xF0;
if ( (c != 0xC0) && (c != 0xD0) ) bytes = 1;
}
else goto error;
}
// Read the rest of the event into the event vector.
unsigned long i;
for ( i=0; i<bytes; i++ ) {
if ( !file_.read( (char *)&c, 1 ) ) goto error;
event->push_back( c );
}
if ( !usingTimeCode_ ) {
if ( isTempoEvent ) {
// Parse the tempo event and update tickSeconds_[track].
double tickrate = (double) (division_ & 0x7FFF);
unsigned long value = ( event->at(3) << 16 ) + ( event->at(4) << 8 ) + event->at(5);
tickSeconds_[track] = (double) (0.000001 * value / tickrate);
}
if ( format_ == 1 ) {
// Update track counter and check the tempo map.
trackCounters_[track] += ticks;
TempoChange tempoEvent = tempoEvents_[ trackTempoIndex_[track] ];
if ( trackCounters_[track] >= tempoEvent.count && trackTempoIndex_[track] < tempoEvents_.size() - 1 ) {
trackTempoIndex_[track]++;
tickSeconds_[track] = tempoEvent.tickSeconds;
}
}
}
// Save the current track pointer value.
trackPointers_[track] = (long)file_.tellg();
return ticks;
error:
oStream_ << "MidiFileIn::getNextEvent: file read error!";
handleError( StkError::FILE_ERROR );
return 0;
}
unsigned long MidiFileIn :: getNextMidiEvent( std::vector<unsigned char> *midiEvent, unsigned int track )
{
// Fill the user-provided vector with the next MIDI event in the
// specified track (default = 0) and return the event delta time in
// ticks. Meta-Events preceeding this event are skipped and ignored.
if ( track >= nTracks_ ) {
oStream_ << "MidiFileIn::getNextMidiEvent: invalid track argument (" << track << ").";
handleError( StkError::WARNING ); return 0;
}
unsigned long ticks = getNextEvent( midiEvent, track );
while ( midiEvent->size() && ( midiEvent->at(0) >= 0xF0 ) ) {
//for ( unsigned int i=0; i<midiEvent->size(); i++ )
//std::cout << "event byte = " << i << ", value = " << (int)midiEvent->at(i) << std::endl;
ticks = getNextEvent( midiEvent, track );
}
//for ( unsigned int i=0; i<midiEvent->size(); i++ )
//std::cout << "event byte = " << i << ", value = " << (int)midiEvent->at(i) << std::endl;
return ticks;
}
bool MidiFileIn :: readVariableLength( unsigned long *value )
{
// It is assumed that this function is called with the file read
// pointer positioned at the start of a variable-length value. The
// function returns "true" if the value is successfully parsed and
// "false" otherwise.
*value = 0;
char c;
if ( !file_.read( &c, 1 ) ) return false;
*value = (unsigned long) c;
if ( *value & 0x80 ) {
*value &= 0x7f;
do {
if ( !file_.read( &c, 1 ) ) return false;
*value = ( *value << 7 ) + ( c & 0x7f );
} while ( c & 0x80 );
}
return true;
}
} // stk namespace