1/*
2    SDL - Simple DirectMedia Layer
3    Copyright (C) 1997-2006 Sam Lantinga
4
5    This library is free software; you can redistribute it and/or
6    modify it under the terms of the GNU Lesser General Public
7    License as published by the Free Software Foundation; either
8    version 2.1 of the License, or (at your option) any later version.
9
10    This library is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13    Lesser General Public License for more details.
14
15    You should have received a copy of the GNU Lesser General Public
16    License along with this library; if not, write to the Free Software
17    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
18
19    Sam Lantinga
20    slouken@libsdl.org
21*/
22#include "SDL_config.h"
23
24#if defined(__APPLE__) && defined(__MACH__)
25#  include <Carbon/Carbon.h>
26#elif TARGET_API_MAC_CARBON && (UNIVERSAL_INTERFACES_VERSION > 0x0335)
27#  include <Carbon.h>
28#else
29#  include <Sound.h> /* SoundManager interface */
30#  include <Gestalt.h>
31#  include <DriverServices.h>
32#endif
33
34#if !defined(NewSndCallBackUPP) && (UNIVERSAL_INTERFACES_VERSION < 0x0335)
35#if !defined(NewSndCallBackProc) /* avoid circular redefinition... */
36#define NewSndCallBackUPP NewSndCallBackProc
37#endif
38#if !defined(NewSndCallBackUPP)
39#define NewSndCallBackUPP NewSndCallBackProc
40#endif
41#endif
42
43#include "SDL_audio.h"
44#include "../SDL_audio_c.h"
45#include "../SDL_sysaudio.h"
46#include "SDL_romaudio.h"
47
48/* Audio driver functions */
49
50static void Mac_CloseAudio(_THIS);
51static int Mac_OpenAudio(_THIS, SDL_AudioSpec *spec);
52static void Mac_LockAudio(_THIS);
53static void Mac_UnlockAudio(_THIS);
54
55/* Audio driver bootstrap functions */
56
57
58static int Audio_Available(void)
59{
60    return(1);
61}
62
63static void Audio_DeleteDevice(SDL_AudioDevice *device)
64{
65    SDL_free(device->hidden);
66    SDL_free(device);
67}
68
69static SDL_AudioDevice *Audio_CreateDevice(int devindex)
70{
71    SDL_AudioDevice *this;
72
73    /* Initialize all variables that we clean on shutdown */
74    this = (SDL_AudioDevice *)SDL_malloc(sizeof(SDL_AudioDevice));
75    if ( this ) {
76        SDL_memset(this, 0, (sizeof *this));
77        this->hidden = (struct SDL_PrivateAudioData *)
78                SDL_malloc((sizeof *this->hidden));
79    }
80    if ( (this == NULL) || (this->hidden == NULL) ) {
81        SDL_OutOfMemory();
82        if ( this ) {
83            SDL_free(this);
84        }
85        return(0);
86    }
87    SDL_memset(this->hidden, 0, (sizeof *this->hidden));
88
89    /* Set the function pointers */
90    this->OpenAudio   = Mac_OpenAudio;
91    this->CloseAudio  = Mac_CloseAudio;
92    this->LockAudio   = Mac_LockAudio;
93    this->UnlockAudio = Mac_UnlockAudio;
94    this->free        = Audio_DeleteDevice;
95
96#ifdef __MACOSX__	/* Mac OS X uses threaded audio, so normal thread code is okay */
97    this->LockAudio   = NULL;
98    this->UnlockAudio = NULL;
99#endif
100    return this;
101}
102
103AudioBootStrap SNDMGR_bootstrap = {
104	"sndmgr", "MacOS SoundManager 3.0",
105	Audio_Available, Audio_CreateDevice
106};
107
108#if defined(TARGET_API_MAC_CARBON) || defined(USE_RYANS_SOUNDCODE)
109/* This works correctly on Mac OS X */
110
111#pragma options align=power
112
113static volatile SInt32 audio_is_locked = 0;
114static volatile SInt32 need_to_mix = 0;
115
116static UInt8  *buffer[2];
117static volatile UInt32 running = 0;
118static CmpSoundHeader header;
119static volatile Uint32 fill_me = 0;
120
121static void mix_buffer(SDL_AudioDevice *audio, UInt8 *buffer)
122{
123   if ( ! audio->paused ) {
124#ifdef __MACOSX__
125        SDL_mutexP(audio->mixer_lock);
126#endif
127        if ( audio->convert.needed ) {
128            audio->spec.callback(audio->spec.userdata,
129                    (Uint8 *)audio->convert.buf,audio->convert.len);
130            SDL_ConvertAudio(&audio->convert);
131            if ( audio->convert.len_cvt != audio->spec.size ) {
132                /* Uh oh... probably crashes here */;
133            }
134            SDL_memcpy(buffer, audio->convert.buf, audio->convert.len_cvt);
135        } else {
136            audio->spec.callback(audio->spec.userdata, buffer, audio->spec.size);
137        }
138#ifdef __MACOSX__
139        SDL_mutexV(audio->mixer_lock);
140#endif
141    }
142
143    DecrementAtomic((SInt32 *) &need_to_mix);
144}
145
146static void Mac_LockAudio(_THIS)
147{
148    IncrementAtomic((SInt32 *) &audio_is_locked);
149}
150
151static void Mac_UnlockAudio(_THIS)
152{
153    SInt32 oldval;
154
155    oldval = DecrementAtomic((SInt32 *) &audio_is_locked);
156    if ( oldval != 1 )  /* != 1 means audio is still locked. */
157        return;
158
159    /* Did we miss the chance to mix in an interrupt? Do it now. */
160    if ( BitAndAtomic (0xFFFFFFFF, (UInt32 *) &need_to_mix) ) {
161        /*
162         * Note that this could be a problem if you missed an interrupt
163         *  while the audio was locked, and get preempted by a second
164         *  interrupt here, but that means you locked for way too long anyhow.
165         */
166        mix_buffer (this, buffer[fill_me]);
167    }
168}
169
170static void callBackProc (SndChannel *chan, SndCommand *cmd_passed ) {
171   UInt32 play_me;
172   SndCommand cmd;
173   SDL_AudioDevice *audio = (SDL_AudioDevice *)chan->userInfo;
174
175   IncrementAtomic((SInt32 *) &need_to_mix);
176
177   fill_me = cmd_passed->param2;  /* buffer that has just finished playing, so fill it */
178   play_me = ! fill_me;           /* filled buffer to play _now_ */
179
180   if ( ! audio->enabled ) {
181      return;
182   }
183
184   /* queue previously mixed buffer for playback. */
185   header.samplePtr = (Ptr)buffer[play_me];
186   cmd.cmd = bufferCmd;
187   cmd.param1 = 0;
188   cmd.param2 = (long)&header;
189   SndDoCommand (chan, &cmd, 0);
190
191   memset (buffer[fill_me], 0, audio->spec.size);
192
193   /*
194    * if audio device isn't locked, mix the next buffer to be queued in
195    *  the memory block that just finished playing.
196    */
197   if ( ! BitAndAtomic(0xFFFFFFFF, (UInt32 *) &audio_is_locked) ) {
198      mix_buffer (audio, buffer[fill_me]);
199   }
200
201   /* set this callback to run again when current buffer drains. */
202   if ( running ) {
203      cmd.cmd = callBackCmd;
204      cmd.param1 = 0;
205      cmd.param2 = play_me;
206
207      SndDoCommand (chan, &cmd, 0);
208   }
209}
210
211static int Mac_OpenAudio(_THIS, SDL_AudioSpec *spec) {
212
213   SndCallBackUPP callback;
214   int sample_bits;
215   int i;
216   long initOptions;
217
218   /* Very few conversions are required, but... */
219    switch (spec->format) {
220        case AUDIO_S8:
221        spec->format = AUDIO_U8;
222        break;
223        case AUDIO_U16LSB:
224        spec->format = AUDIO_S16LSB;
225        break;
226        case AUDIO_U16MSB:
227        spec->format = AUDIO_S16MSB;
228        break;
229    }
230    SDL_CalculateAudioSpec(spec);
231
232    /* initialize bufferCmd header */
233    memset (&header, 0, sizeof(header));
234    callback = (SndCallBackUPP) NewSndCallBackUPP (callBackProc);
235    sample_bits = spec->size / spec->samples / spec->channels * 8;
236
237#ifdef DEBUG_AUDIO
238    fprintf(stderr,
239	"Audio format 0x%x, channels = %d, sample_bits = %d, frequency = %d\n",
240	spec->format, spec->channels, sample_bits, spec->freq);
241#endif /* DEBUG_AUDIO */
242
243    header.numChannels = spec->channels;
244    header.sampleSize  = sample_bits;
245    header.sampleRate  = spec->freq << 16;
246    header.numFrames   = spec->samples;
247    header.encode      = cmpSH;
248
249    /* Note that we install the 16bitLittleEndian Converter if needed. */
250    if ( spec->format == 0x8010 ) {
251        header.compressionID = fixedCompression;
252        header.format = k16BitLittleEndianFormat;
253    }
254
255    /* allocate 2 buffers */
256    for (i=0; i<2; i++) {
257       buffer[i] = (UInt8*)malloc (sizeof(UInt8) * spec->size);
258      if (buffer[i] == NULL) {
259         SDL_OutOfMemory();
260         return (-1);
261      }
262     memset (buffer[i], 0, spec->size);
263   }
264
265   /* Create the sound manager channel */
266    channel = (SndChannelPtr)SDL_malloc(sizeof(*channel));
267    if ( channel == NULL ) {
268        SDL_OutOfMemory();
269        return(-1);
270    }
271    if ( spec->channels >= 2 ) {
272        initOptions = initStereo;
273    } else {
274        initOptions = initMono;
275    }
276    channel->userInfo = (long)this;
277    channel->qLength = 128;
278    if ( SndNewChannel(&channel, sampledSynth, initOptions, callback) != noErr ) {
279        SDL_SetError("Unable to create audio channel");
280        SDL_free(channel);
281        channel = NULL;
282        return(-1);
283    }
284
285   /* start playback */
286   {
287      SndCommand cmd;
288      cmd.cmd = callBackCmd;
289      cmd.param2 = 0;
290      running = 1;
291      SndDoCommand (channel, &cmd, 0);
292   }
293
294   return 1;
295}
296
297static void Mac_CloseAudio(_THIS) {
298
299   int i;
300
301   running = 0;
302
303   if (channel) {
304      SndDisposeChannel (channel, true);
305      channel = NULL;
306   }
307
308    for ( i=0; i<2; ++i ) {
309        if ( buffer[i] ) {
310            SDL_free(buffer[i]);
311            buffer[i] = NULL;
312        }
313    }
314}
315
316#else /* !TARGET_API_MAC_CARBON && !USE_RYANS_SOUNDCODE */
317
318static void Mac_LockAudio(_THIS)
319{
320    /* no-op. */
321}
322
323static void Mac_UnlockAudio(_THIS)
324{
325    /* no-op. */
326}
327
328
329/* This function is called by Sound Manager when it has exhausted one of
330   the buffers, so we'll zero it to silence and fill it with audio if
331   we're not paused.
332*/
333static pascal
334void sndDoubleBackProc (SndChannelPtr chan, SndDoubleBufferPtr newbuf)
335{
336    SDL_AudioDevice *audio = (SDL_AudioDevice *)newbuf->dbUserInfo[0];
337
338    /* If audio is quitting, don't do anything */
339    if ( ! audio->enabled ) {
340        return;
341    }
342    memset (newbuf->dbSoundData, 0, audio->spec.size);
343    newbuf->dbNumFrames = audio->spec.samples;
344    if ( ! audio->paused ) {
345        if ( audio->convert.needed ) {
346            audio->spec.callback(audio->spec.userdata,
347                (Uint8 *)audio->convert.buf,audio->convert.len);
348            SDL_ConvertAudio(&audio->convert);
349#if 0
350            if ( audio->convert.len_cvt != audio->spec.size ) {
351                /* Uh oh... probably crashes here */;
352            }
353#endif
354            SDL_memcpy(newbuf->dbSoundData, audio->convert.buf,
355                            audio->convert.len_cvt);
356        } else {
357            audio->spec.callback(audio->spec.userdata,
358                (Uint8 *)newbuf->dbSoundData, audio->spec.size);
359        }
360    }
361    newbuf->dbFlags    |= dbBufferReady;
362}
363
364static int DoubleBufferAudio_Available(void)
365{
366    int available;
367    NumVersion sndversion;
368    long response;
369
370    available = 0;
371    sndversion = SndSoundManagerVersion();
372    if ( sndversion.majorRev >= 3 ) {
373        if ( Gestalt(gestaltSoundAttr, &response) == noErr ) {
374            if ( (response & (1 << gestaltSndPlayDoubleBuffer)) ) {
375                available = 1;
376            }
377        }
378    } else {
379        if ( Gestalt(gestaltSoundAttr, &response) == noErr ) {
380            if ( (response & (1 << gestaltHasASC)) ) {
381                available = 1;
382            }
383        }
384    }
385    return(available);
386}
387
388static void Mac_CloseAudio(_THIS)
389{
390    int i;
391
392    if ( channel != NULL ) {
393        /* Clean up the audio channel */
394        SndDisposeChannel(channel, true);
395        channel = NULL;
396    }
397    for ( i=0; i<2; ++i ) {
398        if ( audio_buf[i] ) {
399            SDL_free(audio_buf[i]);
400            audio_buf[i] = NULL;
401        }
402    }
403}
404
405static int Mac_OpenAudio(_THIS, SDL_AudioSpec *spec)
406{
407    SndDoubleBufferHeader2 audio_dbh;
408    int i;
409    long initOptions;
410    int sample_bits;
411    SndDoubleBackUPP doubleBackProc;
412
413    /* Check to make sure double-buffered audio is available */
414    if ( ! DoubleBufferAudio_Available() ) {
415        SDL_SetError("Sound manager doesn't support double-buffering");
416        return(-1);
417    }
418
419    /* Very few conversions are required, but... */
420    switch (spec->format) {
421        case AUDIO_S8:
422        spec->format = AUDIO_U8;
423        break;
424        case AUDIO_U16LSB:
425        spec->format = AUDIO_S16LSB;
426        break;
427        case AUDIO_U16MSB:
428        spec->format = AUDIO_S16MSB;
429        break;
430    }
431    SDL_CalculateAudioSpec(spec);
432
433    /* initialize the double-back header */
434    SDL_memset(&audio_dbh, 0, sizeof(audio_dbh));
435    doubleBackProc = NewSndDoubleBackProc (sndDoubleBackProc);
436    sample_bits = spec->size / spec->samples / spec->channels * 8;
437
438    audio_dbh.dbhNumChannels = spec->channels;
439    audio_dbh.dbhSampleSize    = sample_bits;
440    audio_dbh.dbhCompressionID = 0;
441    audio_dbh.dbhPacketSize    = 0;
442    audio_dbh.dbhSampleRate    = spec->freq << 16;
443    audio_dbh.dbhDoubleBack    = doubleBackProc;
444    audio_dbh.dbhFormat    = 0;
445
446    /* Note that we install the 16bitLittleEndian Converter if needed. */
447    if ( spec->format == 0x8010 ) {
448        audio_dbh.dbhCompressionID = fixedCompression;
449        audio_dbh.dbhFormat = k16BitLittleEndianFormat;
450    }
451
452    /* allocate the 2 double-back buffers */
453    for ( i=0; i<2; ++i ) {
454        audio_buf[i] = SDL_calloc(1, sizeof(SndDoubleBuffer)+spec->size);
455        if ( audio_buf[i] == NULL ) {
456            SDL_OutOfMemory();
457            return(-1);
458        }
459        audio_buf[i]->dbNumFrames = spec->samples;
460        audio_buf[i]->dbFlags = dbBufferReady;
461        audio_buf[i]->dbUserInfo[0] = (long)this;
462        audio_dbh.dbhBufferPtr[i] = audio_buf[i];
463    }
464
465    /* Create the sound manager channel */
466    channel = (SndChannelPtr)SDL_malloc(sizeof(*channel));
467    if ( channel == NULL ) {
468        SDL_OutOfMemory();
469        return(-1);
470    }
471    if ( spec->channels >= 2 ) {
472        initOptions = initStereo;
473    } else {
474        initOptions = initMono;
475    }
476    channel->userInfo = 0;
477    channel->qLength = 128;
478    if ( SndNewChannel(&channel, sampledSynth, initOptions, 0L) != noErr ) {
479        SDL_SetError("Unable to create audio channel");
480        SDL_free(channel);
481        channel = NULL;
482        return(-1);
483    }
484
485    /* Start playback */
486    if ( SndPlayDoubleBuffer(channel, (SndDoubleBufferHeaderPtr)&audio_dbh)
487                                != noErr ) {
488        SDL_SetError("Unable to play double buffered audio");
489        return(-1);
490    }
491
492    return 1;
493}
494
495#endif /* TARGET_API_MAC_CARBON || USE_RYANS_SOUNDCODE */
496
497