native_midi/native_midi_macosx.c
author Sam Lantinga <slouken@libsdl.org>
Sat, 01 Jun 2013 19:52:15 -0700
changeset 625 1d489d8ec2e0
parent 617 87116a42526e
child 718 fb0562cc1559
permissions -rw-r--r--
SDL_LoadMUS_RW() now takes an argument telling whether or not the data source should be freed when done.
Fixes crashes cleaning up the data source for music when loading fails.
     1 /*
     2   native_midi_macosx:  Native Midi support on Mac OS X for the SDL_mixer library
     3   Copyright (C) 2009  Ryan C. Gordon <icculus@icculus.org>
     4 
     5   This software is provided 'as-is', without any express or implied
     6   warranty.  In no event will the authors be held liable for any damages
     7   arising from the use of this software.
     8 
     9   Permission is granted to anyone to use this software for any purpose,
    10   including commercial applications, and to alter it and redistribute it
    11   freely, subject to the following restrictions:
    12 
    13   1. The origin of this software must not be misrepresented; you must not
    14      claim that you wrote the original software. If you use this software
    15      in a product, an acknowledgment in the product documentation would be
    16      appreciated but is not required.
    17   2. Altered source versions must be plainly marked as such, and must not be
    18      misrepresented as being the original software.
    19   3. This notice may not be removed or altered from any source distribution.
    20 */
    21 
    22 /* This is Mac OS X only, using Core MIDI.
    23    Mac OS 9 support via QuickTime is in native_midi_mac.c */
    24 
    25 #include "SDL_config.h"
    26 
    27 #if __MACOSX__
    28 
    29 #include <CoreServices/CoreServices.h>      /* ComponentDescription */
    30 #include <AudioUnit/AudioUnit.h>
    31 #include <AudioToolbox/AudioToolbox.h>
    32 #include <AvailabilityMacros.h>
    33 
    34 #include "../SDL_mixer.h"
    35 #include "SDL_endian.h"
    36 #include "native_midi.h"
    37 
    38 /* Native Midi song */
    39 struct _NativeMidiSong
    40 {
    41     MusicPlayer player;
    42     MusicSequence sequence;
    43     MusicTimeStamp endTime;
    44     AudioUnit audiounit;
    45     int loops;
    46 };
    47 
    48 static NativeMidiSong *currentsong = NULL;
    49 static int latched_volume = MIX_MAX_VOLUME;
    50 
    51 static OSStatus
    52 GetSequenceLength(MusicSequence sequence, MusicTimeStamp *_sequenceLength)
    53 {
    54     // http://lists.apple.com/archives/Coreaudio-api/2003/Jul/msg00370.html
    55     // figure out sequence length
    56     UInt32 ntracks, i;
    57     MusicTimeStamp sequenceLength = 0;
    58     OSStatus err;
    59 
    60     err = MusicSequenceGetTrackCount(sequence, &ntracks);
    61     if (err != noErr)
    62         return err;
    63 
    64     for (i = 0; i < ntracks; ++i)
    65     {
    66         MusicTrack track;
    67         MusicTimeStamp tracklen = 0;
    68         UInt32 tracklenlen = sizeof (tracklen);
    69 
    70         err = MusicSequenceGetIndTrack(sequence, i, &track);
    71         if (err != noErr)
    72             return err;
    73 
    74         err = MusicTrackGetProperty(track, kSequenceTrackProperty_TrackLength,
    75                                     &tracklen, &tracklenlen);
    76         if (err != noErr)
    77             return err;
    78 
    79         if (sequenceLength < tracklen)
    80             sequenceLength = tracklen;
    81     }
    82 
    83     *_sequenceLength = sequenceLength;
    84 
    85     return noErr;
    86 }
    87 
    88 
    89 /* we're looking for the sequence output audiounit. */
    90 static OSStatus
    91 GetSequenceAudioUnit(MusicSequence sequence, AudioUnit *aunit)
    92 {
    93     AUGraph graph;
    94     UInt32 nodecount, i;
    95     OSStatus err;
    96 
    97     err = MusicSequenceGetAUGraph(sequence, &graph);
    98     if (err != noErr)
    99         return err;
   100 
   101     err = AUGraphGetNodeCount(graph, &nodecount);
   102     if (err != noErr)
   103         return err;
   104 
   105     for (i = 0; i < nodecount; i++) {
   106         AUNode node;
   107 
   108         if (AUGraphGetIndNode(graph, i, &node) != noErr)
   109             continue;  /* better luck next time. */
   110 
   111 #if MAC_OS_X_VERSION_MIN_REQUIRED < 1050 /* this is deprecated, but works back to 10.0 */
   112         {
   113             struct ComponentDescription desc;
   114             UInt32 classdatasize = 0;
   115             void *classdata = NULL;
   116             err = AUGraphGetNodeInfo(graph, node, &desc, &classdatasize,
   117                                      &classdata, aunit);
   118             if (err != noErr)
   119                 continue;
   120             else if (desc.componentType != kAudioUnitType_Output)
   121                 continue;
   122             else if (desc.componentSubType != kAudioUnitSubType_DefaultOutput)
   123                 continue;
   124         }
   125         #else  /* not deprecated, but requires 10.5 or later */
   126         {
   127         # if !defined(AUDIO_UNIT_VERSION) || ((AUDIO_UNIT_VERSION + 0) < 1060)
   128          /* AUGraphAddNode () is changed to take an AudioComponentDescription*
   129           * desc parameter instead of a ComponentDescription* in the 10.6 SDK.
   130           * AudioComponentDescription is in 10.6 or newer, but it is actually
   131           * the same as struct ComponentDescription with 20 bytes of size and
   132           * the same offsets of all members, therefore, is binary compatible. */
   133         #   define AudioComponentDescription ComponentDescription
   134         # endif
   135             AudioComponentDescription desc;
   136             if (AUGraphNodeInfo(graph, node, &desc, aunit) != noErr)
   137                 continue;
   138             else if (desc.componentType != kAudioUnitType_Output)
   139                 continue;
   140             else if (desc.componentSubType != kAudioUnitSubType_DefaultOutput)
   141                 continue;
   142         }
   143         #endif
   144 
   145         return noErr;  /* found it! */
   146     }
   147 
   148     return kAUGraphErr_NodeNotFound;
   149 }
   150 
   151 
   152 int native_midi_detect()
   153 {
   154     return 1;  /* always available. */
   155 }
   156 
   157 NativeMidiSong *native_midi_loadsong_RW(SDL_RWops *src, int freesrc)
   158 {
   159     NativeMidiSong *retval = NULL;
   160     void *buf = NULL;
   161     Sint64 len = 0;
   162     CFDataRef data = NULL;
   163 
   164     if (SDL_RWseek(src, 0, RW_SEEK_END) < 0)
   165         goto fail;
   166     len = SDL_RWtell(src);
   167     if (len < 0)
   168         goto fail;
   169     if (SDL_RWseek(src, 0, RW_SEEK_SET) < 0)
   170         goto fail;
   171 
   172     buf = malloc(len);
   173     if (buf == NULL)
   174         goto fail;
   175 
   176     if (SDL_RWread(src, buf, len, 1) != 1)
   177         goto fail;
   178 
   179     retval = malloc(sizeof(NativeMidiSong));
   180     if (retval == NULL)
   181         goto fail;
   182 
   183     memset(retval, '\0', sizeof (*retval));
   184 
   185     if (NewMusicPlayer(&retval->player) != noErr)
   186         goto fail;
   187     if (NewMusicSequence(&retval->sequence) != noErr)
   188         goto fail;
   189 
   190     data = CFDataCreate(NULL, (const UInt8 *) buf, len);
   191     if (data == NULL)
   192         goto fail;
   193 
   194     free(buf);
   195     buf = NULL;
   196 
   197     #if MAC_OS_X_VERSION_MIN_REQUIRED < 1050
   198     /* MusicSequenceLoadSMFData() (avail. in 10.2, no 64 bit) is
   199      * equivalent to calling MusicSequenceLoadSMFDataWithFlags()
   200      * with a flags value of 0 (avail. in 10.3, avail. 64 bit).
   201      * So, we use MusicSequenceLoadSMFData() for powerpc versions
   202      * but the *WithFlags() on intel which require 10.4 anyway. */
   203     # if defined(__ppc__) || defined(__POWERPC__)
   204     if (MusicSequenceLoadSMFData(song->sequence, data) != noErr)
   205         goto fail;
   206     # else
   207     if (MusicSequenceLoadSMFDataWithFlags(retval->sequence, data, 0) != noErr)
   208         goto fail;
   209     # endif
   210     #else  /* MusicSequenceFileLoadData() requires 10.5 or later.  */
   211     if (MusicSequenceFileLoadData(retval->sequence, data, 0, 0) != noErr)
   212         goto fail;
   213     #endif
   214 
   215     CFRelease(data);
   216     data = NULL;
   217 
   218     if (GetSequenceLength(retval->sequence, &retval->endTime) != noErr)
   219         goto fail;
   220 
   221     if (MusicPlayerSetSequence(retval->player, retval->sequence) != noErr)
   222         goto fail;
   223 
   224     if (freesrc)
   225         SDL_RWclose(src);
   226 
   227     return retval;
   228 
   229 fail:
   230     if (retval) {
   231         if (retval->sequence)
   232             DisposeMusicSequence(retval->sequence);
   233         if (retval->player)
   234             DisposeMusicPlayer(retval->player);
   235         free(retval);
   236     }
   237 
   238     if (data)
   239         CFRelease(data);
   240 
   241     if (buf)
   242         free(buf);
   243 
   244     return NULL;
   245 }
   246 
   247 void native_midi_freesong(NativeMidiSong *song)
   248 {
   249     if (song != NULL) {
   250         if (currentsong == song)
   251             currentsong = NULL;
   252         MusicPlayerStop(song->player);
   253         DisposeMusicSequence(song->sequence);
   254         DisposeMusicPlayer(song->player);
   255         free(song);
   256     }
   257 }
   258 
   259 void native_midi_start(NativeMidiSong *song, int loops)
   260 {
   261     int vol;
   262 
   263     if (song == NULL)
   264         return;
   265 
   266     SDL_PauseAudio(1);
   267     SDL_UnlockAudio();
   268 
   269     if (currentsong)
   270         MusicPlayerStop(currentsong->player);
   271 
   272     currentsong = song;
   273     currentsong->loops = loops;
   274 
   275     MusicPlayerPreroll(song->player);
   276     MusicPlayerSetTime(song->player, 0);
   277     MusicPlayerStart(song->player);
   278 
   279     GetSequenceAudioUnit(song->sequence, &song->audiounit);
   280 
   281     vol = latched_volume;
   282     latched_volume++;  /* just make this not match. */
   283     native_midi_setvolume(vol);
   284 
   285     SDL_LockAudio();
   286     SDL_PauseAudio(0);
   287 }
   288 
   289 void native_midi_stop()
   290 {
   291     if (currentsong) {
   292         SDL_PauseAudio(1);
   293         SDL_UnlockAudio();
   294         MusicPlayerStop(currentsong->player);
   295         currentsong = NULL;
   296         SDL_LockAudio();
   297         SDL_PauseAudio(0);
   298     }
   299 }
   300 
   301 int native_midi_active()
   302 {
   303     MusicTimeStamp currentTime = 0;
   304     if (currentsong == NULL)
   305         return 0;
   306 
   307     MusicPlayerGetTime(currentsong->player, &currentTime);
   308     if ((currentTime < currentsong->endTime) ||
   309         (currentTime >= kMusicTimeStamp_EndOfTrack)) {
   310         return 1;
   311     } else if (currentsong->loops) {
   312         --currentsong->loops;
   313         MusicPlayerSetTime(currentsong->player, 0);
   314         return 1;
   315     }
   316     return 0;
   317 }
   318 
   319 void native_midi_setvolume(int volume)
   320 {
   321     if (latched_volume == volume)
   322         return;
   323 
   324     latched_volume = volume;
   325     if ((currentsong) && (currentsong->audiounit)) {
   326         const float floatvol = ((float) volume) / ((float) MIX_MAX_VOLUME);
   327         AudioUnitSetParameter(currentsong->audiounit, kHALOutputParam_Volume,
   328                               kAudioUnitScope_Global, 0, floatvol, 0);
   329     }
   330 }
   331 
   332 const char *native_midi_error(void)
   333 {
   334     return "";  /* !!! FIXME */
   335 }
   336 
   337 #endif /* Mac OS X native MIDI support */
   338