view src/audio/macrom/SDL_romaudio.c @ 1662:782fd950bd46 SDL-1.3

Revamp of the video system in progress - adding support for multiple displays, multiple windows, and a full video mode selection API. WARNING: None of the video drivers have been updated for the new API yet! The API is still under design and very fluid. The code is now run through a consistent indent format: indent -i4 -nut -nsc -br -ce The headers are being converted to automatically generate doxygen documentation.
author Sam Lantinga <slouken@libsdl.org>
date Sun, 28 May 2006 13:04:16 +0000
parents f12379c41042
children 4da1ee79c9af
line wrap: on
line source

/*
    SDL - Simple DirectMedia Layer
    Copyright (C) 1997-2006 Sam Lantinga

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2.1 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
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

    Sam Lantinga
    slouken@libsdl.org
*/
#include "SDL_config.h"

#if defined(__APPLE__) && defined(__MACH__)
#  include <Carbon/Carbon.h>
#elif TARGET_API_MAC_CARBON && (UNIVERSAL_INTERFACES_VERSION > 0x0335)
#  include <Carbon.h>
#else
#  include <Sound.h>            /* SoundManager interface */
#  include <Gestalt.h>
#  include <DriverServices.h>
#endif

#if !defined(NewSndCallBackUPP) && (UNIVERSAL_INTERFACES_VERSION < 0x0335)
#if !defined(NewSndCallBackProc)        /* avoid circular redefinition... */
#define NewSndCallBackUPP NewSndCallBackProc
#endif
#if !defined(NewSndCallBackUPP)
#define NewSndCallBackUPP NewSndCallBackProc
#endif
#endif

#include "SDL_audio.h"
#include "../SDL_audio_c.h"
#include "../SDL_sysaudio.h"
#include "SDL_romaudio.h"

/* Audio driver functions */

static void Mac_CloseAudio (_THIS);
static int Mac_OpenAudio (_THIS, SDL_AudioSpec * spec);
static void Mac_LockAudio (_THIS);
static void Mac_UnlockAudio (_THIS);

/* Audio driver bootstrap functions */


static int
Audio_Available (void)
{
    return (1);
}

static void
Audio_DeleteDevice (SDL_AudioDevice * device)
{
    SDL_free (device->hidden);
    SDL_free (device);
}

static SDL_AudioDevice *
Audio_CreateDevice (int devindex)
{
    SDL_AudioDevice *this;

    /* Initialize all variables that we clean on shutdown */
    this = (SDL_AudioDevice *) SDL_malloc (sizeof (SDL_AudioDevice));
    if (this) {
        SDL_memset (this, 0, (sizeof *this));
        this->hidden = (struct SDL_PrivateAudioData *)
            SDL_malloc ((sizeof *this->hidden));
    }
    if ((this == NULL) || (this->hidden == NULL)) {
        SDL_OutOfMemory ();
        if (this) {
            SDL_free (this);
        }
        return (0);
    }
    SDL_memset (this->hidden, 0, (sizeof *this->hidden));

    /* Set the function pointers */
    this->OpenAudio = Mac_OpenAudio;
    this->CloseAudio = Mac_CloseAudio;
    this->LockAudio = Mac_LockAudio;
    this->UnlockAudio = Mac_UnlockAudio;
    this->free = Audio_DeleteDevice;

#ifdef __MACOSX__               /* Mac OS X uses threaded audio, so normal thread code is okay */
    this->LockAudio = NULL;
    this->UnlockAudio = NULL;
#endif
    return this;
}

AudioBootStrap SNDMGR_bootstrap = {
    "sndmgr", "MacOS SoundManager 3.0",
    Audio_Available, Audio_CreateDevice
};

#if defined(TARGET_API_MAC_CARBON) || defined(USE_RYANS_SOUNDCODE)
/* This works correctly on Mac OS X */

#pragma options align=power

static volatile SInt32 audio_is_locked = 0;
static volatile SInt32 need_to_mix = 0;

static UInt8 *buffer[2];
static volatile UInt32 running = 0;
static CmpSoundHeader header;
static volatile Uint32 fill_me = 0;

static void
mix_buffer (SDL_AudioDevice * audio, UInt8 * buffer)
{
    if (!audio->paused) {
#ifdef __MACOSX__
        SDL_mutexP (audio->mixer_lock);
#endif
        if (audio->convert.needed) {
            audio->spec.callback (audio->spec.userdata,
                                  (Uint8 *) audio->convert.buf,
                                  audio->convert.len);
            SDL_ConvertAudio (&audio->convert);
            if (audio->convert.len_cvt != audio->spec.size) {
                /* Uh oh... probably crashes here */ ;
            }
            SDL_memcpy (buffer, audio->convert.buf, audio->convert.len_cvt);
        } else {
            audio->spec.callback (audio->spec.userdata, buffer,
                                  audio->spec.size);
        }
#ifdef __MACOSX__
        SDL_mutexV (audio->mixer_lock);
#endif
    }

    DecrementAtomic ((SInt32 *) & need_to_mix);
}

static void
Mac_LockAudio (_THIS)
{
    IncrementAtomic ((SInt32 *) & audio_is_locked);
}

static void
Mac_UnlockAudio (_THIS)
{
    SInt32 oldval;

    oldval = DecrementAtomic ((SInt32 *) & audio_is_locked);
    if (oldval != 1)            /* != 1 means audio is still locked. */
        return;

    /* Did we miss the chance to mix in an interrupt? Do it now. */
    if (BitAndAtomic (0xFFFFFFFF, (UInt32 *) & need_to_mix)) {
        /*
         * Note that this could be a problem if you missed an interrupt
         *  while the audio was locked, and get preempted by a second
         *  interrupt here, but that means you locked for way too long anyhow.
         */
        mix_buffer (this, buffer[fill_me]);
    }
}

static void
callBackProc (SndChannel * chan, SndCommand * cmd_passed)
{
    UInt32 play_me;
    SndCommand cmd;
    SDL_AudioDevice *audio = (SDL_AudioDevice *) chan->userInfo;

    IncrementAtomic ((SInt32 *) & need_to_mix);

    fill_me = cmd_passed->param2;       /* buffer that has just finished playing, so fill it */
    play_me = !fill_me;         /* filled buffer to play _now_ */

    if (!audio->enabled) {
        return;
    }

    /* queue previously mixed buffer for playback. */
    header.samplePtr = (Ptr) buffer[play_me];
    cmd.cmd = bufferCmd;
    cmd.param1 = 0;
    cmd.param2 = (long) &header;
    SndDoCommand (chan, &cmd, 0);

    memset (buffer[fill_me], 0, audio->spec.size);

    /*
     * if audio device isn't locked, mix the next buffer to be queued in
     *  the memory block that just finished playing.
     */
    if (!BitAndAtomic (0xFFFFFFFF, (UInt32 *) & audio_is_locked)) {
        mix_buffer (audio, buffer[fill_me]);
    }

    /* set this callback to run again when current buffer drains. */
    if (running) {
        cmd.cmd = callBackCmd;
        cmd.param1 = 0;
        cmd.param2 = play_me;

        SndDoCommand (chan, &cmd, 0);
    }
}

static int
Mac_OpenAudio (_THIS, SDL_AudioSpec * spec)
{

    SndCallBackUPP callback;
    int sample_bits;
    int i;
    long initOptions;

    /* Very few conversions are required, but... */
    switch (spec->format) {
    case AUDIO_S8:
        spec->format = AUDIO_U8;
        break;
    case AUDIO_U16LSB:
        spec->format = AUDIO_S16LSB;
        break;
    case AUDIO_U16MSB:
        spec->format = AUDIO_S16MSB;
        break;
    }
    SDL_CalculateAudioSpec (spec);

    /* initialize bufferCmd header */
    memset (&header, 0, sizeof (header));
    callback = (SndCallBackUPP) NewSndCallBackUPP (callBackProc);
    sample_bits = spec->size / spec->samples / spec->channels * 8;

#ifdef DEBUG_AUDIO
    fprintf (stderr,
             "Audio format 0x%x, channels = %d, sample_bits = %d, frequency = %d\n",
             spec->format, spec->channels, sample_bits, spec->freq);
#endif /* DEBUG_AUDIO */

    header.numChannels = spec->channels;
    header.sampleSize = sample_bits;
    header.sampleRate = spec->freq << 16;
    header.numFrames = spec->samples;
    header.encode = cmpSH;

    /* Note that we install the 16bitLittleEndian Converter if needed. */
    if (spec->format == 0x8010) {
        header.compressionID = fixedCompression;
        header.format = k16BitLittleEndianFormat;
    }

    /* allocate 2 buffers */
    for (i = 0; i < 2; i++) {
        buffer[i] = (UInt8 *) malloc (sizeof (UInt8) * spec->size);
        if (buffer[i] == NULL) {
            SDL_OutOfMemory ();
            return (-1);
        }
        memset (buffer[i], 0, spec->size);
    }

    /* Create the sound manager channel */
    channel = (SndChannelPtr) SDL_malloc (sizeof (*channel));
    if (channel == NULL) {
        SDL_OutOfMemory ();
        return (-1);
    }
    if (spec->channels >= 2) {
        initOptions = initStereo;
    } else {
        initOptions = initMono;
    }
    channel->userInfo = (long) this;
    channel->qLength = 128;
    if (SndNewChannel (&channel, sampledSynth, initOptions, callback) !=
        noErr) {
        SDL_SetError ("Unable to create audio channel");
        SDL_free (channel);
        channel = NULL;
        return (-1);
    }

    /* start playback */
    {
        SndCommand cmd;
        cmd.cmd = callBackCmd;
        cmd.param2 = 0;
        running = 1;
        SndDoCommand (channel, &cmd, 0);
    }

    return 1;
}

static void
Mac_CloseAudio (_THIS)
{

    int i;

    running = 0;

    if (channel) {
        SndDisposeChannel (channel, true);
        channel = NULL;
    }

    for (i = 0; i < 2; ++i) {
        if (buffer[i]) {
            SDL_free (buffer[i]);
            buffer[i] = NULL;
        }
    }
}

#else /* !TARGET_API_MAC_CARBON && !USE_RYANS_SOUNDCODE */

static void
Mac_LockAudio (_THIS)
{
    /* no-op. */
}

static void
Mac_UnlockAudio (_THIS)
{
    /* no-op. */
}


/* This function is called by Sound Manager when it has exhausted one of
   the buffers, so we'll zero it to silence and fill it with audio if
   we're not paused.
*/
static pascal void
sndDoubleBackProc (SndChannelPtr chan, SndDoubleBufferPtr newbuf)
{
    SDL_AudioDevice *audio = (SDL_AudioDevice *) newbuf->dbUserInfo[0];

    /* If audio is quitting, don't do anything */
    if (!audio->enabled) {
        return;
    }
    memset (newbuf->dbSoundData, 0, audio->spec.size);
    newbuf->dbNumFrames = audio->spec.samples;
    if (!audio->paused) {
        if (audio->convert.needed) {
            audio->spec.callback (audio->spec.userdata,
                                  (Uint8 *) audio->convert.buf,
                                  audio->convert.len);
            SDL_ConvertAudio (&audio->convert);
#if 0
            if (audio->convert.len_cvt != audio->spec.size) {
                /* Uh oh... probably crashes here */ ;
            }
#endif
            SDL_memcpy (newbuf->dbSoundData, audio->convert.buf,
                        audio->convert.len_cvt);
        } else {
            audio->spec.callback (audio->spec.userdata,
                                  (Uint8 *) newbuf->dbSoundData,
                                  audio->spec.size);
        }
    }
    newbuf->dbFlags |= dbBufferReady;
}

static int
DoubleBufferAudio_Available (void)
{
    int available;
    NumVersion sndversion;
    long response;

    available = 0;
    sndversion = SndSoundManagerVersion ();
    if (sndversion.majorRev >= 3) {
        if (Gestalt (gestaltSoundAttr, &response) == noErr) {
            if ((response & (1 << gestaltSndPlayDoubleBuffer))) {
                available = 1;
            }
        }
    } else {
        if (Gestalt (gestaltSoundAttr, &response) == noErr) {
            if ((response & (1 << gestaltHasASC))) {
                available = 1;
            }
        }
    }
    return (available);
}

static void
Mac_CloseAudio (_THIS)
{
    int i;

    if (channel != NULL) {
        /* Clean up the audio channel */
        SndDisposeChannel (channel, true);
        channel = NULL;
    }
    for (i = 0; i < 2; ++i) {
        if (audio_buf[i]) {
            SDL_free (audio_buf[i]);
            audio_buf[i] = NULL;
        }
    }
}

static int
Mac_OpenAudio (_THIS, SDL_AudioSpec * spec)
{
    SndDoubleBufferHeader2 audio_dbh;
    int i;
    long initOptions;
    int sample_bits;
    SndDoubleBackUPP doubleBackProc;

    /* Check to make sure double-buffered audio is available */
    if (!DoubleBufferAudio_Available ()) {
        SDL_SetError ("Sound manager doesn't support double-buffering");
        return (-1);
    }

    /* Very few conversions are required, but... */
    switch (spec->format) {
    case AUDIO_S8:
        spec->format = AUDIO_U8;
        break;
    case AUDIO_U16LSB:
        spec->format = AUDIO_S16LSB;
        break;
    case AUDIO_U16MSB:
        spec->format = AUDIO_S16MSB;
        break;
    }
    SDL_CalculateAudioSpec (spec);

    /* initialize the double-back header */
    SDL_memset (&audio_dbh, 0, sizeof (audio_dbh));
    doubleBackProc = NewSndDoubleBackProc (sndDoubleBackProc);
    sample_bits = spec->size / spec->samples / spec->channels * 8;

    audio_dbh.dbhNumChannels = spec->channels;
    audio_dbh.dbhSampleSize = sample_bits;
    audio_dbh.dbhCompressionID = 0;
    audio_dbh.dbhPacketSize = 0;
    audio_dbh.dbhSampleRate = spec->freq << 16;
    audio_dbh.dbhDoubleBack = doubleBackProc;
    audio_dbh.dbhFormat = 0;

    /* Note that we install the 16bitLittleEndian Converter if needed. */
    if (spec->format == 0x8010) {
        audio_dbh.dbhCompressionID = fixedCompression;
        audio_dbh.dbhFormat = k16BitLittleEndianFormat;
    }

    /* allocate the 2 double-back buffers */
    for (i = 0; i < 2; ++i) {
        audio_buf[i] = SDL_calloc (1, sizeof (SndDoubleBuffer) + spec->size);
        if (audio_buf[i] == NULL) {
            SDL_OutOfMemory ();
            return (-1);
        }
        audio_buf[i]->dbNumFrames = spec->samples;
        audio_buf[i]->dbFlags = dbBufferReady;
        audio_buf[i]->dbUserInfo[0] = (long) this;
        audio_dbh.dbhBufferPtr[i] = audio_buf[i];
    }

    /* Create the sound manager channel */
    channel = (SndChannelPtr) SDL_malloc (sizeof (*channel));
    if (channel == NULL) {
        SDL_OutOfMemory ();
        return (-1);
    }
    if (spec->channels >= 2) {
        initOptions = initStereo;
    } else {
        initOptions = initMono;
    }
    channel->userInfo = 0;
    channel->qLength = 128;
    if (SndNewChannel (&channel, sampledSynth, initOptions, 0L) != noErr) {
        SDL_SetError ("Unable to create audio channel");
        SDL_free (channel);
        channel = NULL;
        return (-1);
    }

    /* Start playback */
    if (SndPlayDoubleBuffer (channel, (SndDoubleBufferHeaderPtr) & audio_dbh)
        != noErr) {
        SDL_SetError ("Unable to play double buffered audio");
        return (-1);
    }

    return 1;
}

#endif /* TARGET_API_MAC_CARBON || USE_RYANS_SOUNDCODE */
/* vi: set ts=4 sw=4 expandtab: */