Logo Search packages:      
Sourcecode: ia32-libs-sdl version File versions  Download package

native_midi_common.c

/*
    native_midi:  Hardware Midi support for the SDL_mixer library
    Copyright (C) 2000,2001  Florian 'Proff' Schulze

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License as published by the Free Software Foundation; either
    version 2 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Library General Public License for more details.

    You should have received a copy of the GNU Library General Public
    License along with this library; if not, write to the Free
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

    Florian 'Proff' Schulze
    florian.proff.schulze@gmx.net
*/


#include "native_midi_common.h"

#include "SDL_mixer.h"

#include <stdlib.h>
#include <string.h>
#include <limits.h>


/* The maximum number of midi tracks that we can handle 
#define MIDI_TRACKS 32 */


/* A single midi track as read from the midi file */
typedef struct
{
      Uint8 *data;                              /* MIDI message stream */
      int len;                                  /* length of the track data */
} MIDITrack;

/* A midi file, stripped down to the absolute minimum - divison & track data */
typedef struct
{
      int division;                             /* number of pulses per quarter note (ppqn) */
    int nTracks;                    /* number of tracks */
      MIDITrack *track;               /* tracks */
} MIDIFile;


/* Some macros that help us stay endianess-independant */
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
#define BE_SHORT(x) (x)
#define BE_LONG(x) (x)
#else
#define BE_SHORT(x)     ((((x)&0xFF)<<8) | (((x)>>8)&0xFF))
#define BE_LONG(x)      ((((x)&0x0000FF)<<24) | \
                   (((x)&0x00FF00)<<8) | \
                   (((x)&0xFF0000)>>8) | \
                   (((x)>>24)&0xFF))
#endif



/* Get Variable Length Quantity */
static int GetVLQ(MIDITrack *track, int *currentPos)
{
      int l = 0;
      Uint8 c;
      while(1)
      {
            c = track->data[*currentPos];
            (*currentPos)++;
            l += (c & 0x7f);
            if (!(c & 0x80)) 
                  return l;
            l <<= 7;
      }
}

/* Create a single MIDIEvent */
static MIDIEvent *CreateEvent(Uint32 time, Uint8 event, Uint8 a, Uint8 b)
{
      MIDIEvent *newEvent;
      
      newEvent = calloc(1, sizeof(MIDIEvent));

      if (newEvent)
      {
            newEvent->time = time;
            newEvent->status = event;
            newEvent->data[0] = a;
            newEvent->data[1] = b;
      }
      else
            Mix_SetError("Out of memory");
      
      return newEvent;
}

/* Convert a single midi track to a list of MIDIEvents */
static MIDIEvent *MIDITracktoStream(MIDITrack *track)
{
      Uint32 atime = 0;
      Uint32 len = 0;
      Uint8 event,type,a,b;
      Uint8 laststatus = 0;
      Uint8 lastchan = 0;
      int currentPos = 0;
      int end = 0;
      MIDIEvent *head = CreateEvent(0,0,0,0);   /* dummy event to make handling the list easier */
      MIDIEvent *currentEvent = head;

      while (!end)
      {
            if (currentPos >= track->len)
                  break; /* End of data stream reached */

            atime += GetVLQ(track, &currentPos);
            event = track->data[currentPos++];
            
            /* Handle SysEx seperatly */
            if (((event>>4) & 0x0F) == MIDI_STATUS_SYSEX)
            {
                  if (event == 0xFF)
                  {
                        type = track->data[currentPos];
                        currentPos++;
                        switch(type)
                        {
                              case 0x2f: /* End of data marker */
                                    end = 1;
                              case 0x51: /* Tempo change */
                                    /*
                                    a=track->data[currentPos];
                                    b=track->data[currentPos+1];
                                    c=track->data[currentPos+2];
                                    AddEvent(song, atime, MEVT_TEMPO, c, b, a);
                                    */
                                    break;
                        }
                  }
                  else
                        type = 0;

                  len = GetVLQ(track, &currentPos);
                  
                  /* Create an event and attach the extra data, if any */
                  currentEvent->next = CreateEvent(atime, event, type, 0);
                  currentEvent = currentEvent->next;
                  if (NULL == currentEvent)
                  {
                        FreeMIDIEventList(head);
                        return NULL;
                  }
                  if (len)
                  {
                        currentEvent->extraLen = len;
                        currentEvent->extraData = malloc(len);
                        memcpy(currentEvent->extraData, &(track->data[currentPos]), len);
                        currentPos += len;
                  }
            }
            else
            {
                  a = event;
                  if (a & 0x80) /* It's a status byte */
                  {
                        /* Extract channel and status information */
                        lastchan = a & 0x0F;
                        laststatus = (a>>4) & 0x0F;
                        
                        /* Read the next byte which should always be a data byte */
                        a = track->data[currentPos++] & 0x7F;
                  }
                  switch(laststatus)
                  {
                        case MIDI_STATUS_NOTE_OFF:
                        case MIDI_STATUS_NOTE_ON: /* Note on */
                        case MIDI_STATUS_AFTERTOUCH: /* Key Pressure */
                        case MIDI_STATUS_CONTROLLER: /* Control change */
                        case MIDI_STATUS_PITCH_WHEEL: /* Pitch wheel */
                              b = track->data[currentPos++] & 0x7F;
                              currentEvent->next = CreateEvent(atime, (Uint8)((laststatus<<4)+lastchan), a, b);
                              currentEvent = currentEvent->next;
                              if (NULL == currentEvent)
                              {
                                    FreeMIDIEventList(head);
                                    return NULL;
                              }
                              break;

                        case MIDI_STATUS_PROG_CHANGE: /* Program change */
                        case MIDI_STATUS_PRESSURE: /* Channel pressure */
                              a &= 0x7f;
                              currentEvent->next = CreateEvent(atime, (Uint8)((laststatus<<4)+lastchan), a, 0);
                              currentEvent = currentEvent->next;
                              if (NULL == currentEvent)
                              {
                                    FreeMIDIEventList(head);
                                    return NULL;
                              }
                              break;

                        default: /* Sysex already handled above */
                              break;
                  }
            }
      }
      
      currentEvent = head->next;
      free(head); /* release the dummy head event */
      return currentEvent;
}

/*
 *  Convert a midi song, consisting of up to 32 tracks, to a list of MIDIEvents.
 *  To do so, first convert the tracks seperatly, then interweave the resulting
 *  MIDIEvent-Lists to one big list.
 */
static MIDIEvent *MIDItoStream(MIDIFile *mididata)
{
      MIDIEvent **track;
      MIDIEvent *head = CreateEvent(0,0,0,0);   /* dummy event to make handling the list easier */
      MIDIEvent *currentEvent = head;
      int trackID;

    if (NULL == head)
            return NULL;
        
    track = (MIDIEvent**) calloc(1, sizeof(MIDIEvent*) * mididata->nTracks);
      if (NULL == head)
        return NULL;
      
      /* First, convert all tracks to MIDIEvent lists */
      for (trackID = 0; trackID < mididata->nTracks; trackID++)
            track[trackID] = MIDITracktoStream(&mididata->track[trackID]);

      /* Now, merge the lists. */
      /* TODO */
      while(1)
      {
            Uint32 lowestTime = INT_MAX;
            int currentTrackID = -1;
            
            /* Find the next event */
            for (trackID = 0; trackID < mididata->nTracks; trackID++)
            {
                  if (track[trackID] && (track[trackID]->time < lowestTime))
                  {
                        currentTrackID = trackID;
                        lowestTime = track[currentTrackID]->time;
                  }
            }
            
            /* Check if we processes all events */
            if (currentTrackID == -1)
                  break;
            
            currentEvent->next = track[currentTrackID];
            track[currentTrackID] = track[currentTrackID]->next;

            currentEvent = currentEvent->next;
            
            
            lowestTime = 0;
      }

      /* Make sure the list is properly terminated */
      currentEvent->next = 0;

      currentEvent = head->next;
    free(track);
      free(head); /* release the dummy head event */
      return currentEvent;
}

static int ReadMIDIFile(MIDIFile *mididata, FILE *fp)
{
      int i = 0;
      Uint32      ID;
      Uint32      size;
      Uint16      format;
      Uint16      tracks;
      Uint16      division;

      if (!mididata)
            return 0;
      if (!fp)
            return 0;

      /* Make sure this is really a MIDI file */
      fread(&ID, 1, 4, fp);
      if (BE_LONG(ID) != 'MThd')
            return 0;
      
      /* Header size must be 6 */
      fread(&size, 1, 4, fp);
      size = BE_LONG(size);
      if (size != 6)
            return 0;
      
      /* We only support format 0 and 1, but not 2 */
      fread(&format, 1, 2, fp);
      format = BE_SHORT(format);
      if (format != 0 && format != 1)
            return 0;
      
      fread(&tracks, 1, 2, fp);
      tracks = BE_SHORT(tracks);
      mididata->nTracks = tracks;
    
    /* Allocate tracks */
    mididata->track = (MIDITrack*) calloc(1, sizeof(MIDITrack) * mididata->nTracks);
    if (NULL == mididata->track)
    {
        Mix_SetError("Out of memory");
        goto bail;
    }
    
      /* Retrieve the PPQN value, needed for playback */
      fread(&division, 1, 2, fp);
      mididata->division = BE_SHORT(division);
      
      
      for (i=0; i<tracks; i++)
      {
            fread(&ID, 1, 4, fp);   /* We might want to verify this is MTrk... */
            fread(&size, 1, 4, fp);
            size = BE_LONG(size);
            mididata->track[i].len = size;
            mididata->track[i].data = malloc(size);
            if (NULL == mididata->track[i].data)
            {
                  Mix_SetError("Out of memory");
                  goto bail;
            }
            fread(mididata->track[i].data, 1, size, fp);
      }
      return 1;

bail:
      for(;i >= 0; i--)
      {
            if (mididata->track[i].data)
                  free(mididata->track[i].data);
      }

      return 0;
}

MIDIEvent *CreateMIDIEventList(char *midifile, Uint16 *division)
{
      FILE *fp = NULL;
      MIDIFile *mididata = NULL;
      MIDIEvent *eventList;
      int trackID;
      
      mididata = calloc(1, sizeof(MIDIFile));
      if (!mididata)
            return NULL;

      /* Open the file */
      fp = fopen(midifile, "rb");
      if ( fp != NULL )
      {
            /* Read in the data */
            if ( ! ReadMIDIFile(mididata, fp))
            {
                  free(mididata);
                  fclose(fp);
                  return NULL;
            }
            fclose(fp);
      }
      else
      {
            free(mididata);
            return NULL;
      }
      
      if (division)
            *division = mididata->division;
      
      eventList = MIDItoStream(mididata);
      
      for(trackID = 0; trackID < mididata->nTracks; trackID++)
      {
            if (mididata->track[trackID].data)
                  free(mididata->track[trackID].data);
      }
      free(mididata->track);
    free(mididata);
      
      return eventList;
}

void FreeMIDIEventList(MIDIEvent *head)
{
      MIDIEvent *cur, *next;
      
      cur = head;

      while (cur)
      {
            next = cur->next;
            if (cur->extraData) 
                  free (cur->extraData);
            free (cur);
            cur = next;
      }
}

Generated by  Doxygen 1.6.0   Back to index