MIDIfile player library API

From Wikiid
Revision as of 19:33, 18 January 2009 by SteveBaker (Talk | contribs) (New page: The MIDIfile player library contains several modules: * Timidity/GUS patchfile reader. * MIDIfile loader. * OpenAL audio library interface. The three parts work together - in order to ob...)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

The MIDIfile player library contains several modules:

  • Timidity/GUS patchfile reader.
  • MIDIfile loader.
  • OpenAL audio library interface.

The three parts work together - in order to obtain GeneralMIDI sound patches, it is most convenient to rely on the Timidity patch libraries which reference audio samples in 'GUS patch' form. Those can be handed to OpenAL to turn into audio. The MIDIfile handler deals with loading the MIDIfile and issuing sound 'rendering' calls.

Timidity/GUS Patchfile Reader.

 midiReadTimidityCfg ( const char *fname = NULL ) ;

Loads a timidity configuration file from the specified filename. If you omit the filename, by default, it loads the file '/etc/timidity.cfg'.

 midiGUSpatch *midiGetTimidityPatch ( int patch ) ;
 midiGUSpatch *midiGetTimidityPercussionPatch ( int key ) ;

A GUS patch comprises a number of sound samples - each suitable for a different range of notes:

 class midiGUSpatch
 {
   public:
     midiGUSpatch ( const char *fname ) ;
     midiGUSpatch ( FILE *fd ) ;
     bool isBad () ;
     bool isValidNote ( int note ) ;
     midiGUSsample *getNoteSample ( int  note ) ;
 } ;

...a GUS patch can be loaded from a file or a FILE* - and will provide a midiGUSsample for each note that is valid - or NULL is there is none.

 class midiGUSsample
 {
   public:
     midiGUSsample ( FILE *fd ) ;
     void show ( FILE *fd ) ;
     float getALpitch ( float freq ) ;
     midiALbuffer *getALstartBuffer () ;
     midiALbuffer *getALloopBuffer  () ;
     midiALbuffer *getALendBuffer   () ;
     float getLowIdealFrequency  () ;
     float getHighIdealFrequency () ;
     float getLowFrequency       () ;
     float getHighFrequency      () ;
     float getRootFrequency      () ;
 } ;

The sample contains three sound samples - an section that plays at the start, a section that may be looped as needed and a section that plays when the note is released. The audio has an ideal range of frequencies for which it will sound good - and a wider range for which it's possible for it to be played.

OpenAL interface

 class midiALbuffer
 {
   public:
     midiALbuffer ( unsigned short *data,
                    unsigned int    nsamp,
                    unsigned int    srate ) ;
     unsigned int getHandle     () ;
     unsigned int getLength     () ;
     unsigned int getNumSamples () ;
     unsigned int getRate       () ;
 } ;

This class wraps an OpenAL 'buffer'. The 'handle' may be passed to OpenAL functions requiring audio buffers.

The remainder of the OpenAL interface covers turning notes on and off plus intitialisation:

 void  midiALinit  () ;
 void  midiNoteOn  ( int chan, int note ) ;
 void  midiNoteOff ( int chan, int note ) ;
 void  midiAllOff  ( int chan ) ;

MIDI player

MIDI playback involves some number of midiNotePlayer classes:

 class midiNotePlayer
 {
   public:
     midiNotePlayer () ;
     bool isIdle () ;
     unsigned int getHandle () ;
     int getChannel () ;
     int getNote    () ;
     void noteOn    () ;
     void noteOff   () ;
     void play ( midiALbuffer *startBuffer,
                 midiALbuffer *loopBuffer,
                 float frequency ) ;
 } ;

Plus some functions:

 void            midiInitNotePlayers () ;
 midiNotePlayer *midiGetNotePlayer   () ;
 midiNotePlayer *midiFindNotePlayer  ( int chan, int note ) ;
 midiNotePlayer *midiFindAnyPlayer   ( int chan, int *returned_note ) ;
 void            midiProgramChange   ( int chan, midiGUSpatch *patch ) ;
 midiGUSpatch   *midiGetCurrPatch    ( int chan ) ;

Putting it all together:

Load up the Timidity patch library:

 midiReadTimidityCfg () ;

Set up a default set of patches (one for each of the 16 MIDI channels):

 for ( int i = 0 ; i < 16 ; i++ )
   midiProgramChange ( i, midiGetTimidityPatch ( 0 ) ) ;

Load the MIDIfile header chunk:

 midiHeaderChunk *hdr = (midiHeaderChunk *) midiReadChunk ( filedescriptor ) ;
 int numTracks = hdr -> getNumTracks () ;

Declare arrays per-track:

 midiTrack **trk      = new midiTrack* [ numTracks ] ;
 int        *trktimes = new int        [ numTracks ] ;
 int        *trkevent = new int        [ numTracks ] ;

Load all of the subsequent track chunks:

 for ( int i = 0 ; i < hdr -> getNumTracks () ; i++ )
 {
   trk [ i ] = (midiTrackChunk *)(midiReadChunk ( filedescriptor )) -> getTrack () ;
   trkevent [ i ] = 0 ;
   trktimes [ i ] = trk [ i ] -> getEvent ( 0 ) -> getDeltaTime () ;
 }

...then enter a loop to play the music:

 while ( 1 )
 {

For each tick, we need to find the next closest event:

   int smallest = 999999 ;
   for ( int i = 0 ; i < numTracks ; i++ )
      if ( trkevent [ i ] < trk [ i ] -> getNumEvents () &&
           trktimes [ i ] < smallest )
        smallest = trktimes [ i ] ;

...sleep until the nearest event needs to be replayed:

   usleep ( smallest * tempo ) ;

Then play pending events from each track:

   for ( int i = 0 ; i < numTracks ; i++ )
   {
      if ( trkevent [ i ] >= trk [ i ] -> getNumEvents () )
        continue ;

Subtract the time we spent sleeping from the time remaining for pending events:

     trktimes [ i ] -= smallest ;

All events that now have zero time remaining need to be played...there may be several per track:

     if ( trktimes [ i ] <= 0 )
     {

Play the next event:

       trk [ i ] -> getEvent ( trkevent [ i ] ) -> play () ;

Move this track along to the next event:

       trkevent [ i ]++ ;
       trktimes [ i ] = trk [ i ] -> getEvent ( trkevent [ i ] )
                                  -> getDeltaTime () ;
     }
   }
 }


Wikiid Pages relating to MIDIfile (edit)
MIDIfile player library API
MIDIfile documentation
Sources of MIDIfiles