Home   Information   Classes   Download   Usage   Mail List   Requirements   Links   Tutorial


MidiFileIn.cpp

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.