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