
Home Information Classes Download Usage Mail List Requirements Links Tutorial
00001 /**********************************************************************/ 00016 /**********************************************************************/ 00017 00018 #include "MidiFileIn.h" 00019 #include <iostream> 00020 00021 MidiFileIn :: MidiFileIn( std::string fileName ) 00022 { 00023 // Attempt to open the file. 00024 file_.open( fileName.c_str(), std::ios::in ); 00025 if ( !file_ ) { 00026 errorString_ << "MidiFileIn: error opening or finding file (" << fileName << ")."; 00027 handleError( errorString_.str(), StkError::FILE_NOT_FOUND ); 00028 } 00029 00030 // Parse header info. 00031 char chunkType[4]; 00032 char buffer[4]; 00033 SINT32 *length; 00034 if ( !file_.read( chunkType, 4 ) ) goto error; 00035 if ( !file_.read( buffer, 4 ) ) goto error; 00036 length = (SINT32 *) &buffer; 00037 if ( strncmp( chunkType, "MThd", 4 ) || ( *length != 6 ) ) { 00038 errorString_ << "MidiFileIn: file (" << fileName << ") does not appear to be a MIDI file!"; 00039 handleError( errorString_.str(), StkError::FILE_UNKNOWN_FORMAT ); 00040 } 00041 00042 // Read the MIDI file format. 00043 SINT16 *data; 00044 if ( !file_.read( buffer, 2 ) ) goto error; 00045 data = (SINT16 *) &buffer; 00046 if ( *data < 0 || *data > 2 ) { 00047 errorString_ << "MidiFileIn: the file (" << fileName << ") format is invalid!"; 00048 handleError( errorString_.str(), StkError::FILE_ERROR ); 00049 } 00050 format_ = *data; 00051 00052 // Read the number of tracks. 00053 if ( !file_.read( buffer, 2 ) ) goto error; 00054 if ( format_ == 0 && *data != 1 ) { 00055 errorString_ << "MidiFileIn: invalid number of tracks (>1) for a file format = 0!"; 00056 handleError( errorString_.str(), StkError::FILE_ERROR ); 00057 } 00058 nTracks_ = *data; 00059 00060 // Read the beat division. 00061 if ( !file_.read( buffer, 2 ) ) goto error; 00062 division_ = (int) *data; 00063 double tickrate; 00064 usingTimeCode_ = false; 00065 if ( *data & 0x8000 ) { 00066 // Determine ticks per second from time-code formats. 00067 tickrate = (double) -(*data & 0x7F00); 00068 // If frames per second value is 29, it really should be 29.97. 00069 if ( tickrate == 29.0 ) tickrate = 29.97; 00070 tickrate *= (*data & 0x00FF); 00071 usingTimeCode_ = true; 00072 } 00073 else { 00074 tickrate = (double) (*data & 0x7FFF); // ticks per quarter note 00075 } 00076 00077 // Now locate the track offsets and lengths. If not using time 00078 // code, we can initialize the "tick time" using a default tempo of 00079 // 120 beats per minute. We will then check for tempo meta-events 00080 // afterward. 00081 for ( unsigned int i=0; i<nTracks_; i++ ) { 00082 if ( !file_.read( chunkType, 4 ) ) goto error; 00083 if ( strncmp( chunkType, "MTrk", 4 ) ) goto error; 00084 if ( !file_.read( buffer, 4 ) ) goto error; 00085 length = (SINT32 *) &buffer; 00086 trackLengths_.push_back( *length ); 00087 trackOffsets_.push_back( file_.tellg() ); 00088 trackPointers_.push_back( file_.tellg() ); 00089 trackStatus_.push_back( 0 ); 00090 file_.seekg( *length, std::ios_base::cur ); 00091 if ( usingTimeCode_ ) tickSeconds_.push_back( (double) (1.0 / tickrate) ); 00092 else tickSeconds_.push_back( (double) (0.5 / tickrate) ); 00093 } 00094 00095 // Save the initial tickSeconds parameter. 00096 TempoChange tempoEvent; 00097 tempoEvent.count = 0; 00098 tempoEvent.tickSeconds = tickSeconds_[0]; 00099 tempoEvents_.push_back( tempoEvent ); 00100 00101 // If format 1 and not using time code, parse and save the tempo map 00102 // on track 0. 00103 if ( format_ == 1 && !usingTimeCode_ ) { 00104 std::vector<unsigned char> event; 00105 unsigned long value, count; 00106 00107 // We need to temporarily change the usingTimeCode_ value here so 00108 // that the getNextEvent() function doesn't try to check the tempo 00109 // map (which we're creating here). 00110 usingTimeCode_ = true; 00111 count = getNextEvent( &event, 0 ); 00112 while ( event.size() ) { 00113 if ( ( event.size() == 6 ) && ( event[0] == 0xff ) && 00114 ( event[1] == 0x51 ) && ( event[2] == 0x03 ) ) { 00115 tempoEvent.count = count; 00116 value = ( event[3] << 16 ) + ( event[4] << 8 ) + event[5]; 00117 tempoEvent.tickSeconds = (double) (0.000001 * value / tickrate); 00118 if ( count > tempoEvents_.back().count ) 00119 tempoEvents_.push_back( tempoEvent ); 00120 else 00121 tempoEvents_.back() = tempoEvent; 00122 } 00123 count += getNextEvent( &event, 0 ); 00124 } 00125 rewindTrack( 0 ); 00126 for ( unsigned int i=0; i<nTracks_; i++ ) { 00127 trackCounters_.push_back( 0 ); 00128 trackTempoIndex_.push_back( 0 ); 00129 } 00130 // Change the time code flag back! 00131 usingTimeCode_ = false; 00132 } 00133 00134 return; 00135 00136 error: 00137 errorString_ << "MidiFileIn: error reading from file (" << fileName << ")."; 00138 handleError( errorString_.str(), StkError::FILE_ERROR ); 00139 } 00140 00141 MidiFileIn :: ~MidiFileIn() 00142 { 00143 // An ifstream object implicitly closes itself during destruction 00144 // but we'll make an explicit call to "close" anyway. 00145 file_.close(); 00146 } 00147 00148 int MidiFileIn :: getFileFormat() const 00149 { 00150 return format_; 00151 } 00152 00153 unsigned int MidiFileIn :: getNumberOfTracks() const 00154 { 00155 return nTracks_; 00156 } 00157 00158 int MidiFileIn :: getDivision() const 00159 { 00160 return division_; 00161 } 00162 00163 void MidiFileIn :: rewindTrack( unsigned int track ) 00164 { 00165 if ( track >= nTracks_ ) { 00166 errorString_ << "MidiFileIn::getNextEvent: invalid track argument (" << track << ")."; 00167 handleError( errorString_.str(), StkError::FUNCTION_ARGUMENT ); 00168 } 00169 00170 trackPointers_[track] = trackOffsets_[track]; 00171 trackStatus_[track] = 0; 00172 tickSeconds_[track] = tempoEvents_[0].tickSeconds; 00173 } 00174 00175 double MidiFileIn :: getTickSeconds( unsigned int track ) 00176 { 00177 // Return the current tick value in seconds for the given track. 00178 if ( track >= nTracks_ ) { 00179 errorString_ << "MidiFileIn::getTickSeconds: invalid track argument (" << track << ")."; 00180 handleError( errorString_.str(), StkError::FUNCTION_ARGUMENT ); 00181 } 00182 00183 return tickSeconds_[track]; 00184 } 00185 00186 unsigned long MidiFileIn :: getNextEvent( std::vector<unsigned char> *event, unsigned int track ) 00187 { 00188 // Fill the user-provided vector with the next event in the 00189 // specified track (default = 0) and return the event delta time in 00190 // ticks. This function assumes that the stored track pointer is 00191 // positioned at the start of a track event. If the track has 00192 // reached its end, the event vector size will be zero. 00193 // 00194 // If we have a format 0 or 2 file and we're not using timecode, we 00195 // should check every meta-event for tempo changes and make 00196 // appropriate updates to the tickSeconds_ parameter if so. 00197 // 00198 // If we have a format 1 file and we're not using timecode, keep a 00199 // running sum of ticks for each track and update the tickSeconds_ 00200 // parameter as needed based on the stored tempo map. 00201 00202 if ( track >= nTracks_ ) { 00203 errorString_ << "MidiFileIn::getNextEvent: invalid track argument (" << track << ")."; 00204 handleError( errorString_.str(), StkError::FUNCTION_ARGUMENT ); 00205 } 00206 00207 event->clear(); 00208 // Check for the end of the track. 00209 if ( (trackPointers_[track] - trackOffsets_[track]) >= trackLengths_[track] ) 00210 return 0; 00211 00212 unsigned long ticks = 0, bytes = 0; 00213 bool isTempoEvent = false; 00214 00215 // Read the event delta time. 00216 file_.seekg( trackPointers_[track], std::ios_base::beg ); 00217 if ( !readVariableLength( &ticks ) ) goto error; 00218 00219 // Parse the event stream to determine the event length. 00220 unsigned char c; 00221 if ( !file_.read( (char *)&c, 1 ) ) goto error; 00222 switch ( c ) { 00223 00224 case 0xFF: // A Meta-Event 00225 unsigned long position; 00226 trackStatus_[track] = 0; 00227 event->push_back( c ); 00228 if ( !file_.read( (char *)&c, 1 ) ) goto error; 00229 event->push_back( c ); 00230 if ( format_ != 1 && ( c == 0x51 ) ) isTempoEvent = true; 00231 position = file_.tellg(); 00232 if ( !readVariableLength( &bytes ) ) goto error; 00233 bytes += ( (unsigned long)file_.tellg() - position ); 00234 file_.seekg( position, std::ios_base::beg ); 00235 break; 00236 00237 case 0xF0 || 0xF7: // The start or continuation of a Sysex event 00238 trackStatus_[track] = 0; 00239 event->push_back( c ); 00240 position = file_.tellg(); 00241 if ( !readVariableLength( &bytes ) ) goto error; 00242 bytes += ( (unsigned long)file_.tellg() - position ); 00243 file_.seekg( position, std::ios_base::beg ); 00244 break; 00245 00246 default: // Should be a MIDI channel event 00247 if ( c & 0x80 ) { // MIDI status byte 00248 if ( c > 0xF0 ) goto error; 00249 trackStatus_[track] = c; 00250 event->push_back( c ); 00251 c &= 0xF0; 00252 if ( (c == 0xC0) || (c == 0xD0) ) bytes = 1; 00253 else bytes = 2; 00254 } 00255 else if ( trackStatus_[track] & 0x80 ) { // Running status 00256 event->push_back( trackStatus_[track] ); 00257 event->push_back( c ); 00258 c = trackStatus_[track] & 0xF0; 00259 if ( (c != 0xC0) && (c != 0xD0) ) bytes = 1; 00260 } 00261 else goto error; 00262 00263 } 00264 00265 // Read the rest of the event into the event vector. 00266 for ( unsigned long i=0; i<bytes; i++ ) { 00267 if ( !file_.read( (char *)&c, 1 ) ) goto error; 00268 event->push_back( c ); 00269 } 00270 00271 if ( !usingTimeCode_ ) { 00272 if ( isTempoEvent ) { 00273 // Parse the tempo event and update tickSeconds_[track]. 00274 double tickrate = (double) (division_ & 0x7FFF); 00275 unsigned long value = ( event->at(3) << 16 ) + ( event->at(4) << 8 ) + event->at(5); 00276 tickSeconds_[track] = (double) (0.000001 * value / tickrate); 00277 } 00278 00279 if ( format_ == 1 ) { 00280 // Update track counter and check the tempo map. 00281 trackCounters_[track] += ticks; 00282 TempoChange tempoEvent = tempoEvents_[ trackTempoIndex_[track] ]; 00283 if ( trackCounters_[track] >= tempoEvent.count ) { 00284 trackTempoIndex_[track]++; 00285 tickSeconds_[track] = tempoEvent.tickSeconds; 00286 } 00287 } 00288 } 00289 00290 // Save the current track pointer value. 00291 trackPointers_[track] = file_.tellg(); 00292 00293 return ticks; 00294 00295 error: 00296 errorString_ << "MidiFileIn::getNextEvent: file read error!"; 00297 handleError( errorString_.str(), StkError::FILE_ERROR ); 00298 return 0; 00299 } 00300 00301 unsigned long MidiFileIn :: getNextMidiEvent( std::vector<unsigned char> *midiEvent, unsigned int track ) 00302 { 00303 // Fill the user-provided vector with the next MIDI event in the 00304 // specified track (default = 0) and return the event delta time in 00305 // ticks. Meta-Events preceeding this event are skipped and ignored. 00306 if ( track >= nTracks_ ) { 00307 errorString_ << "MidiFileIn::getNextMidiEvent: invalid track argument (" << track << ")."; 00308 handleError( errorString_.str(), StkError::FUNCTION_ARGUMENT ); 00309 } 00310 00311 unsigned long ticks = getNextEvent( midiEvent, track ); 00312 while ( midiEvent->size() && ( midiEvent->at(0) >= 0xF0 ) ) { 00313 for ( unsigned int i=0; i<midiEvent->size(); i++ ) 00314 std::cout << "event byte = " << i << ", value = " << (int)midiEvent->at(i) << std::endl; 00315 ticks = getNextEvent( midiEvent, track ); 00316 } 00317 00318 for ( unsigned int i=0; i<midiEvent->size(); i++ ) 00319 std::cout << "event byte = " << i << ", value = " << (int)midiEvent->at(i) << std::endl; 00320 00321 return ticks; 00322 } 00323 00324 bool MidiFileIn :: readVariableLength( unsigned long *value ) 00325 { 00326 // It is assumed that this function is called with the file read 00327 // pointer positioned at the start of a variable-length value. The 00328 // function returns "true" if the value is successfully parsed and 00329 // "false" otherwise. 00330 *value = 0; 00331 char c; 00332 00333 if ( !file_.read( &c, 1 ) ) return false; 00334 *value = (unsigned long) c; 00335 if ( *value & 0x80 ) { 00336 *value &= 0x7f; 00337 do { 00338 if ( !file_.read( &c, 1 ) ) return false; 00339 *value = ( *value << 7 ) + ( c & 0x7f ); 00340 } while ( c & 0x80 ); 00341 } 00342 00343 return true; 00344 }
| The Synthesis ToolKit in C++ (STK) |
| ©1995-2002 Perry R. Cook and Gary P. Scavone. All Rights Reserved. |