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

native_midi_mac.c

/*
    native_midi_mac:  Native Midi support on MacOS for the SDL_mixer library
    Copyright (C) 2001  Max Horn

    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

    Max Horn
    max@quendi.de
*/

#if defined(macintosh) || defined(MACOSX)

#include "native_midi.h"
#include "native_midi_common.h"

#ifdef MACOSX
#include <QuickTime/QuickTimeMusic.h>
#else
#include <QuickTimeMusic.h>
#endif

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


/* Native Midi song */
struct _NativeMidiSong
{
      Uint32            *tuneSequence;
      Uint32            *tuneHeader;
};

enum
{
      /* number of (32-bit) long words in a note request event */
      kNoteRequestEventLength = ((sizeof(NoteRequest)/sizeof(long)) + 2),

      /* number of (32-bit) long words in a marker event */
      kMarkerEventLength      = 1,

      /* number of (32-bit) long words in a general event, minus its data */
      kGeneralEventLength     = 2
};

#define ERROR_BUF_SIZE              256
#define     BUFFER_INCREMENT        5000

#define REST_IF_NECESSARY()   do {\
                  int timeDiff = eventPos->time - lastEventTime;  \
                  if(timeDiff)      \
                  {     \
                        timeDiff = (int)(timeDiff*tick);    \
                        qtma_StuffRestEvent(*tunePos, timeDiff);  \
                        tunePos++;  \
                        lastEventTime = eventPos->time;     \
                  }     \
            } while(0)


static Uint32 *BuildTuneSequence(MIDIEvent *evntlist, int ppqn, int part_poly_max[32], int part_to_inst[32], int *numParts);
static Uint32 *BuildTuneHeader(int part_poly_max[32], int part_to_inst[32], int numParts);

/* The global TunePlayer instance */
static TunePlayer gTunePlayer = NULL;
static int              gInstaceCount = 0;
static Uint32           *gCurrentTuneSequence = NULL;
static char             gErrorBuffer[ERROR_BUF_SIZE] = "";


/* Check whether QuickTime is available */
int native_midi_detect()
{
      /* TODO */
      return 1;
}

NativeMidiSong *native_midi_loadsong(char *midifile)
{
      NativeMidiSong    *song = NULL;
      MIDIEvent         *evntlist = NULL;
      int                     part_to_inst[32];
      int                     part_poly_max[32];
      int                     numParts = 0;
      Uint16                  ppqn;

      /* Init the arrays */
      memset(part_poly_max,0,sizeof(part_poly_max));
      memset(part_to_inst,-1,sizeof(part_to_inst));
      
      /* Attempt to load the midi file */
      evntlist = CreateMIDIEventList(midifile, &ppqn);
      if (!evntlist)
            goto bail;

      /* Allocate memory for the song struct */
      song = malloc(sizeof(NativeMidiSong));
      if (!song)
            goto bail;

      /* Build a tune sequence from the event list */
      song->tuneSequence = BuildTuneSequence(evntlist, ppqn, part_poly_max, part_to_inst, &numParts);
      if(!song->tuneSequence)
            goto bail;

      /* Now build a tune header from the data we collect above, create
         all parts as needed and assign them the correct instrument.
      */
      song->tuneHeader = BuildTuneHeader(part_poly_max, part_to_inst, numParts);
      if(!song->tuneHeader)
            goto bail;
      
      /* Increment the instance count */
      gInstaceCount++;
      if (gTunePlayer == NULL)
            gTunePlayer = OpenDefaultComponent(kTunePlayerComponentType, 0);

      /* Finally, free the event list */
      FreeMIDIEventList(evntlist);
      
      return song;
      
bail:
      if (evntlist)
            FreeMIDIEventList(evntlist);
      
      if (song)
      {
            if(song->tuneSequence)
                  free(song->tuneSequence);
            
            if(song->tuneHeader)
                  DisposePtr((Ptr)song->tuneHeader);

            free(song);
      }
      
      return NULL;
}

void native_midi_freesong(NativeMidiSong *song)
{
      if(!song || !song->tuneSequence)
            return;

      /* If this is the currently playing song, stop it now */    
      if (song->tuneSequence == gCurrentTuneSequence)
            native_midi_stop();
      
      /* Finally, free the data storage */
      free(song->tuneSequence);
      DisposePtr((Ptr)song->tuneHeader);
      free(song);

      /* Increment the instance count */
      gInstaceCount--;
      if ((gTunePlayer != NULL) && (gInstaceCount == 0))
      {
            CloseComponent(gTunePlayer);
            gTunePlayer = NULL;
      }
}

void native_midi_start(NativeMidiSong *song)
{
      UInt32            queueFlags = 0;
      ComponentResult tpError;
      
      assert (gTunePlayer != NULL);
      
      SDL_PauseAudio(1);
      SDL_UnlockAudio();
    
      /* First, stop the currently playing music */
      native_midi_stop();
      
      /* Set up the queue flags */
      queueFlags = kTuneStartNow;

      /* Set the time scale (units per second), we want milliseconds */
      tpError = TuneSetTimeScale(gTunePlayer, 1000);
      if (tpError != noErr)
      {
            strncpy (gErrorBuffer, "MIDI error during TuneSetTimeScale", ERROR_BUF_SIZE);
            goto done;
      }

      /* Set the header, to tell what instruments are used */
      tpError = TuneSetHeader(gTunePlayer, (UInt32 *)song->tuneHeader);
      if (tpError != noErr)
      {
            strncpy (gErrorBuffer, "MIDI error during TuneSetHeader", ERROR_BUF_SIZE);
            goto done;
      }
      
      /* Have it allocate whatever resources are needed */
      tpError = TunePreroll(gTunePlayer);
      if (tpError != noErr)
      {
            strncpy (gErrorBuffer, "MIDI error during TunePreroll", ERROR_BUF_SIZE);
            goto done;
      }

      /* We want to play at normal volume */
      tpError = TuneSetVolume(gTunePlayer, 0x00010000);
      if (tpError != noErr)
      {
            strncpy (gErrorBuffer, "MIDI error during TuneSetVolume", ERROR_BUF_SIZE);
            goto done;
      }
      
      /* Finally, start playing the full song */
      gCurrentTuneSequence = song->tuneSequence;
      tpError = TuneQueue(gTunePlayer, (UInt32 *)song->tuneSequence, 0x00010000, 0, 0xFFFFFFFF, queueFlags, NULL, 0);
      if (tpError != noErr)
      {
            strncpy (gErrorBuffer, "MIDI error during TuneQueue", ERROR_BUF_SIZE);
            goto done;
      }
    
done:
      SDL_LockAudio();
      SDL_PauseAudio(0);
}

void native_midi_stop()
{
      if (gTunePlayer == NULL)
            return;

      /* Stop music */
      TuneStop(gTunePlayer, 0);
      
      /* Deallocate all instruments */
      TuneUnroll(gTunePlayer);
}

int native_midi_active()
{
      if (gTunePlayer != NULL)
      {
            TuneStatus  ts;

            TuneGetStatus(gTunePlayer,&ts);
            return ts.queueTime != 0;
      }
      else
            return 0;
}

void native_midi_setvolume(int volume)
{
      if (gTunePlayer == NULL)
            return;

      /* QTMA olume may range from 0.0 to 1.0 (in 16.16 fixed point encoding) */
      TuneSetVolume(gTunePlayer, (0x00010000 * volume)/SDL_MIX_MAXVOLUME);
}

char *native_midi_error()
{
      return gErrorBuffer;
}

Uint32 *BuildTuneSequence(MIDIEvent *evntlist, int ppqn, int part_poly_max[32], int part_to_inst[32], int *numParts)
{
      int               part_poly[32];
      int               channel_to_part[16];
      
      int               channel_pan[16];
      int               channel_vol[16];
      int               channel_pitch_bend[16];
      
      int               lastEventTime = 0;
      int               tempo = 500000;
      double            Ippqn = 1.0 / (1000*ppqn);
      double            tick = tempo * Ippqn;
      MIDIEvent   *eventPos = evntlist;
      MIDIEvent   *noteOffPos;
      Uint32            *tunePos, *endPos;
      Uint32            *tuneSequence;
      size_t            tuneSize;
      
      /* allocate space for the tune header */
      tuneSize = 5000;
      tuneSequence = (Uint32 *)malloc(tuneSize * sizeof(Uint32));
      if (tuneSequence == NULL)
            return NULL;
      
      /* Set starting position in our tune memory */
      tunePos = tuneSequence;
      endPos = tuneSequence + tuneSize;

      /* Initialise the arrays */
      memset(part_poly,0,sizeof(part_poly));
      
      memset(channel_to_part,-1,sizeof(channel_to_part));
      memset(channel_pan,-1,sizeof(channel_pan));
      memset(channel_vol,-1,sizeof(channel_vol));
      memset(channel_pitch_bend,-1,sizeof(channel_pitch_bend));
      
      *numParts = 0;
      
      /*
       * Now the major work - iterate over all GM events,
       * and turn them into QuickTime Music format.
       * At the same time, calculate the max. polyphony for each part,
       * and also the part->instrument mapping.
       */
      while(eventPos)
      {
            int status = (eventPos->status&0xF0)>>4;
            int channel = eventPos->status&0x0F;
            int part = channel_to_part[channel];
        int velocity, pitch;
        int value, controller;
        int bend;
        int newInst;
            
            /* Check if we are running low on space... */
            if((tunePos+16) > endPos)
            {
                  /* Resize our data storage. */
                  Uint32            *oldTuneSequence = tuneSequence;

                  tuneSize += BUFFER_INCREMENT;
                  tuneSequence = (Uint32 *)realloc(tuneSequence, tuneSize * sizeof(Uint32));
                  if(oldTuneSequence != tuneSequence)
                        tunePos += tuneSequence - oldTuneSequence;
                  endPos = tuneSequence + tuneSize;
            }
            
            switch (status)
            {
            case MIDI_STATUS_NOTE_OFF:
                  assert(part>=0 && part<=31);

                  /* Keep track of the polyphony of the current part */
                  part_poly[part]--;
                  break;
            case MIDI_STATUS_NOTE_ON:
                  if (part < 0)
                  {
                        /* If no part is specified yet, we default to the first instrument, which
                           is piano (or the first drum kit if we are on the drum channel)
                        */
                        int newInst;
                        
                        if (channel == 9)
                              newInst = kFirstDrumkit + 1;        /* the first drum kit is the "no drum" kit! */
                        else
                              newInst = kFirstGMInstrument;
                        part = channel_to_part[channel] = *numParts;
                        part_to_inst[(*numParts)++] = newInst;
                  }
                  /* TODO - add support for more than 32 parts using eXtended QTMA events */
                  assert(part<=31);
                  
                  /* Decode pitch & velocity */
                  pitch = eventPos->data[0];
                  velocity = eventPos->data[1];
                  
                  if (velocity == 0)
                  {
                        /* was a NOTE OFF in disguise, so we decrement the polyphony */
                        part_poly[part]--;
                  }
                  else
                  {
                        /* Keep track of the polyphony of the current part */
                        int foo = ++part_poly[part];
                        if (part_poly_max[part] < foo)
                              part_poly_max[part] = foo;

                        /* Now scan forward to find the matching NOTE OFF event */
                        for(noteOffPos = eventPos; noteOffPos; noteOffPos = noteOffPos->next)
                        {
                              if ((noteOffPos->status&0xF0)>>4 == MIDI_STATUS_NOTE_OFF
                                    && channel == (eventPos->status&0x0F)
                                    && pitch == noteOffPos->data[0])
                                    break;
                              /* NOTE ON with velocity == 0 is the same as a NOTE OFF */
                              if ((noteOffPos->status&0xF0)>>4 == MIDI_STATUS_NOTE_ON
                                    && channel == (eventPos->status&0x0F)
                                    && pitch == noteOffPos->data[0]
                                    && 0 == noteOffPos->data[1])
                                    break;
                        }
                        
                        /* Did we find a note off? Should always be the case, but who knows... */
                        if (noteOffPos)
                        {
                              /* We found a NOTE OFF, now calculate the note duration */
                              int duration = (int)((noteOffPos->time - eventPos->time)*tick);
                              
                              REST_IF_NECESSARY();
                              /* Now we need to check if we get along with a normal Note Event, or if we need an extended one... */
                              if (duration < 2048 && pitch>=32 && pitch<=95 && velocity>=0 && velocity<=127)
                              {
                                    qtma_StuffNoteEvent(*tunePos, part, pitch, velocity, duration);
                                    tunePos++;
                              }
                              else
                              {
                                    qtma_StuffXNoteEvent(*tunePos, *(tunePos+1), part, pitch, velocity, duration);
                                    tunePos+=2;
                              }
                        }
                  }
                  break;
            case MIDI_STATUS_AFTERTOUCH:
                  /* NYI - use kControllerAfterTouch. But how are the parameters to be mapped? */
                  break;
            case MIDI_STATUS_CONTROLLER:
                  controller = eventPos->data[0];
                  value = eventPos->data[1];

                  switch(controller)
                  {
                  case 0:     /* bank change - igore for now */
                        break;
                  case kControllerVolume:
                        if(channel_vol[channel] != value<<8)
                        {
                              channel_vol[channel] = value<<8;
                              if(part>=0 && part<=31)
                              {
                                    REST_IF_NECESSARY();
                                    qtma_StuffControlEvent(*tunePos, part, kControllerVolume, channel_vol[channel]);
                                    tunePos++;
                              }
                        }
                        break;
                  case kControllerPan:
                        if(channel_pan[channel] != (value << 1) + 256)
                        {
                              channel_pan[channel] = (value << 1) + 256;
                              if(part>=0 && part<=31)
                              {
                                    REST_IF_NECESSARY();
                                    qtma_StuffControlEvent(*tunePos, part, kControllerPan, channel_pan[channel]);
                                    tunePos++;
                              }
                        }
                        break;
                  default:
                        /* No other controllers implemented yet */;
                        break;
                  }
                  
                  break;
            case MIDI_STATUS_PROG_CHANGE:
                  /* Instrument changed */
                  newInst = eventPos->data[0];
                  
                  /* Channel 9 (the 10th channel) is different, it indicates a drum kit */
                  if (channel == 9)
                        newInst += kFirstDrumkit;
                  else
                        newInst += kFirstGMInstrument;
                  /* Only if the instrument for this channel *really* changed, add a new part. */
                  if(newInst != part_to_inst[part])
                  {
                        /* TODO maybe make use of kGeneralEventPartChange here,
                           to help QT reuse note channels?
                        */
                        part = channel_to_part[channel] = *numParts;
                        part_to_inst[(*numParts)++] = newInst;

                        if(channel_vol[channel] >= 0)
                        {
                              REST_IF_NECESSARY();
                              qtma_StuffControlEvent(*tunePos, part, kControllerVolume, channel_vol[channel]);
                              tunePos++;
                        }
                        if(channel_pan[channel] >= 0)
                        {
                              REST_IF_NECESSARY();
                              qtma_StuffControlEvent(*tunePos, part, kControllerPan, channel_pan[channel]);
                              tunePos++;
                        }
                        if(channel_pitch_bend[channel] >= 0)
                        {
                              REST_IF_NECESSARY();
                              qtma_StuffControlEvent(*tunePos, part, kControllerPitchBend, channel_pitch_bend[channel]);
                              tunePos++;
                        }                 
                  }
                  break;
            case MIDI_STATUS_PRESSURE:
                  /* NYI */
                  break;
            case MIDI_STATUS_PITCH_WHEEL:
                  /* In the midi spec, 0x2000 = center, 0x0000 = - 2 semitones, 0x3FFF = +2 semitones
                     but for QTMA, we specify it as a 8.8 fixed point of semitones
                     TODO: detect "pitch bend range changes" & honor them!
                  */
                  bend = (eventPos->data[0] & 0x7f) | ((eventPos->data[1] & 0x7f) << 7);
                  
                  /* "Center" the bend */
                  bend -= 0x2000;
                  
                  /* Move it to our format: */
                  bend <<= 4;
                  
                  /* If it turns out the pitch bend didn't change, stop here */
                  if(channel_pitch_bend[channel] == bend)
                        break;
                  
                  channel_pitch_bend[channel] = bend;
                  if(part>=0 && part<=31)
                  {
                        /* Stuff a control event */
                        REST_IF_NECESSARY();
                        qtma_StuffControlEvent(*tunePos, part, kControllerPitchBend, bend);
                        tunePos++;
                  }                 
                  break;
            case MIDI_STATUS_SYSEX:
                  if (eventPos->status == 0xFF && eventPos->data[0] == 0x51) /* Tempo change */
                  {
                        tempo = (eventPos->extraData[0] << 16) +
                              (eventPos->extraData[1] << 8) +
                              eventPos->extraData[2];
                        
                        tick = tempo * Ippqn;
                  }
                  break;
            }
            
            /* on to the next event */
            eventPos = eventPos->next;
      } 
      
      /* Finally, place an end marker */
      *tunePos = kEndMarkerValue;
      
      return tuneSequence;
}

Uint32 *BuildTuneHeader(int part_poly_max[32], int part_to_inst[32], int numParts)
{
      Uint32                  *myHeader;
      Uint32                  *myPos1, *myPos2;       /* pointers to the head and tail long words of a music event */
      NoteRequest       *myNoteRequest;
      NoteAllocator     myNoteAllocator;        /* for the NAStuffToneDescription call */
      ComponentResult   myErr = noErr;
      int                     part;

      myHeader = NULL;
      myNoteAllocator = NULL;

      /*
       * Open up the Note Allocator
       */
      myNoteAllocator = OpenDefaultComponent(kNoteAllocatorComponentType,0);
      if (myNoteAllocator == NULL)
            goto bail;
      
      /*
       * Allocate space for the tune header
       */
      myHeader = (Uint32 *)
                  NewPtrClear((numParts * kNoteRequestEventLength + kMarkerEventLength) * sizeof(Uint32));
      if (myHeader == NULL)
            goto bail;
      
      myPos1 = myHeader;
      
      /*
       * Loop over all parts
       */
      for(part = 0; part < numParts; ++part)
      {
            /*
             * Stuff request for the instrument with the given polyphony
             */
            myPos2 = myPos1 + (kNoteRequestEventLength - 1); /* last longword of general event */
            qtma_StuffGeneralEvent(*myPos1, *myPos2, part, kGeneralEventNoteRequest, kNoteRequestEventLength);
            myNoteRequest = (NoteRequest *)(myPos1 + 1);
            myNoteRequest->info.flags = 0;
            myNoteRequest->info.polyphony = part_poly_max[part];
            myNoteRequest->info.typicalPolyphony = 0x00010000;
            myErr = NAStuffToneDescription(myNoteAllocator,part_to_inst[part],&myNoteRequest->tone);
            if (myErr != noErr)
                  goto bail;
            
            /* move pointer to beginning of next event */
            myPos1 += kNoteRequestEventLength;
      }

      *myPos1 = kEndMarkerValue;          /* end of sequence marker */


bail:
      if(myNoteAllocator)
            CloseComponent(myNoteAllocator);

      /* if we encountered an error, dispose of the storage we allocated and return NULL */
      if (myErr != noErr) {
            DisposePtr((Ptr)myHeader);
            myHeader = NULL;
      }

      return myHeader;
}

#endif /* MacOS native MIDI support */

Generated by  Doxygen 1.6.0   Back to index