/* SDL - Simple DirectMedia Layer Copyright (C) 1997-2012 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 #elif TARGET_API_MAC_CARBON && (UNIVERSAL_INTERFACES_VERSION > 0x0335) # include #else # include /* SoundManager interface */ # include # include #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 */