1/*
2    SDL - Simple DirectMedia Layer
3    Copyright (C) 1997-2012 Sam Lantinga
4
5    This library is free software; you can redistribute it and/or
6    modify it under the terms of the GNU Library General Public
7    License as published by the Free Software Foundation; either
8    version 2 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    Library General Public License for more details.
14
15    You should have received a copy of the GNU Library General Public
16    License along with this library; if not, write to the Free
17    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18
19    Sam Lantinga
20    slouken@libsdl.org
21
22    This file based on Apple sample code. We haven't changed the file name,
23    so if you want to see the original search for it on apple.com/developer
24*/
25#include "SDL_config.h"
26#include "SDL_endian.h"
27
28/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
29    AudioFilePlayer.cpp
30*/
31#include "AudioFilePlayer.h"
32
33/*
34void ThrowResult (OSStatus result, const char* str)
35{
36    SDL_SetError ("Error: %s %d", str, result);
37    throw result;
38}
39*/
40
41#if DEBUG
42static void PrintStreamDesc (AudioStreamBasicDescription *inDesc)
43{
44    if (!inDesc) {
45        printf ("Can't print a NULL desc!\n");
46        return;
47    }
48
49    printf ("- - - - - - - - - - - - - - - - - - - -\n");
50    printf ("  Sample Rate:%f\n", inDesc->mSampleRate);
51    printf ("  Format ID:%s\n", (char*)&inDesc->mFormatID);
52    printf ("  Format Flags:%lX\n", inDesc->mFormatFlags);
53    printf ("  Bytes per Packet:%ld\n", inDesc->mBytesPerPacket);
54    printf ("  Frames per Packet:%ld\n", inDesc->mFramesPerPacket);
55    printf ("  Bytes per Frame:%ld\n", inDesc->mBytesPerFrame);
56    printf ("  Channels per Frame:%ld\n", inDesc->mChannelsPerFrame);
57    printf ("  Bits per Channel:%ld\n", inDesc->mBitsPerChannel);
58    printf ("- - - - - - - - - - - - - - - - - - - -\n");
59}
60#endif
61
62
63static int AudioFilePlayer_SetDestination (AudioFilePlayer *afp, AudioUnit  *inDestUnit)
64{
65    /*if (afp->mConnected) throw static_cast<OSStatus>(-1);*/ /* can't set dest if already engaged */
66    if (afp->mConnected)
67        return 0 ;
68
69    SDL_memcpy(&afp->mPlayUnit, inDestUnit, sizeof (afp->mPlayUnit));
70
71    OSStatus result = noErr;
72
73
74        /* we can "down" cast a component instance to a component */
75    ComponentDescription desc;
76    result = GetComponentInfo ((Component)*inDestUnit, &desc, 0, 0, 0);
77    if (result) return 0; /*THROW_RESULT("GetComponentInfo")*/
78
79        /* we're going to use this to know which convert routine to call
80           a v1 audio unit will have a type of 'aunt'
81           a v2 audio unit will have one of several different types. */
82    if (desc.componentType != kAudioUnitType_Output) {
83        result = badComponentInstance;
84        /*THROW_RESULT("BAD COMPONENT")*/
85        if (result) return 0;
86    }
87
88    /* Set the input format of the audio unit. */
89    result = AudioUnitSetProperty (*inDestUnit,
90                               kAudioUnitProperty_StreamFormat,
91                               kAudioUnitScope_Input,
92                               0,
93                               &afp->mFileDescription,
94                               sizeof (afp->mFileDescription));
95        /*THROW_RESULT("AudioUnitSetProperty")*/
96    if (result) return 0;
97    return 1;
98}
99
100static void AudioFilePlayer_SetNotifier(AudioFilePlayer *afp, AudioFilePlayNotifier inNotifier, void *inRefCon)
101{
102    afp->mNotifier = inNotifier;
103    afp->mRefCon = inRefCon;
104}
105
106static int AudioFilePlayer_IsConnected(AudioFilePlayer *afp)
107{
108    return afp->mConnected;
109}
110
111static AudioUnit AudioFilePlayer_GetDestUnit(AudioFilePlayer *afp)
112{
113   return afp->mPlayUnit;
114}
115
116static void AudioFilePlayer_Print(AudioFilePlayer *afp)
117{
118#if DEBUG
119    printf ("Is Connected:%s\n", (IsConnected() ? "true" : "false"));
120    printf ("- - - - - - - - - - - - - - \n");
121#endif
122}
123
124static void    AudioFilePlayer_SetStartFrame (AudioFilePlayer *afp, int frame)
125{
126    SInt64 position = frame * 2352;
127
128    afp->mStartFrame = frame;
129    afp->mAudioFileManager->SetPosition (afp->mAudioFileManager, position);
130}
131
132
133static int    AudioFilePlayer_GetCurrentFrame (AudioFilePlayer *afp)
134{
135    return afp->mStartFrame + (afp->mAudioFileManager->GetByteCounter(afp->mAudioFileManager) / 2352);
136}
137
138static void    AudioFilePlayer_SetStopFrame (AudioFilePlayer *afp, int frame)
139{
140    SInt64 position  = frame * 2352;
141
142    afp->mAudioFileManager->SetEndOfFile (afp->mAudioFileManager, position);
143}
144
145void delete_AudioFilePlayer(AudioFilePlayer *afp)
146{
147    if (afp != NULL)
148    {
149        afp->Disconnect(afp);
150
151        if (afp->mAudioFileManager) {
152            delete_AudioFileManager(afp->mAudioFileManager);
153            afp->mAudioFileManager = 0;
154        }
155
156        if (afp->mForkRefNum) {
157            FSCloseFork (afp->mForkRefNum);
158            afp->mForkRefNum = 0;
159        }
160        SDL_free(afp);
161    }
162}
163
164static int    AudioFilePlayer_Connect(AudioFilePlayer *afp)
165{
166#if DEBUG
167    printf ("Connect:%x, engaged=%d\n", (int)afp->mPlayUnit, (afp->mConnected ? 1 : 0));
168#endif
169    if (!afp->mConnected)
170    {
171        if (!afp->mAudioFileManager->DoConnect(afp->mAudioFileManager))
172            return 0;
173
174        /* set the render callback for the file data to be supplied to the sound converter AU */
175        afp->mInputCallback.inputProc = afp->mAudioFileManager->FileInputProc;
176        afp->mInputCallback.inputProcRefCon = afp->mAudioFileManager;
177
178        OSStatus result = AudioUnitSetProperty (afp->mPlayUnit,
179                            kAudioUnitProperty_SetRenderCallback,
180                            kAudioUnitScope_Input,
181                            0,
182                            &afp->mInputCallback,
183                            sizeof(afp->mInputCallback));
184        if (result) return 0;  /*THROW_RESULT("AudioUnitSetProperty")*/
185        afp->mConnected = 1;
186    }
187
188    return 1;
189}
190
191/* warning noted, now please go away ;-) */
192/* #warning This should redirect the calling of notification code to some other thread */
193static void    AudioFilePlayer_DoNotification (AudioFilePlayer *afp, OSStatus inStatus)
194{
195    if (afp->mNotifier) {
196        (*afp->mNotifier) (afp->mRefCon, inStatus);
197    } else {
198        SDL_SetError ("Notification posted with no notifier in place");
199
200        if (inStatus == kAudioFilePlay_FileIsFinished)
201            afp->Disconnect(afp);
202        else if (inStatus != kAudioFilePlayErr_FilePlayUnderrun)
203            afp->Disconnect(afp);
204    }
205}
206
207static void    AudioFilePlayer_Disconnect (AudioFilePlayer *afp)
208{
209#if DEBUG
210    printf ("Disconnect:%x,%ld, engaged=%d\n", (int)afp->mPlayUnit, 0, (afp->mConnected ? 1 : 0));
211#endif
212    if (afp->mConnected)
213    {
214        afp->mConnected = 0;
215
216        afp->mInputCallback.inputProc = 0;
217        afp->mInputCallback.inputProcRefCon = 0;
218        OSStatus result = AudioUnitSetProperty (afp->mPlayUnit,
219                                        kAudioUnitProperty_SetRenderCallback,
220                                        kAudioUnitScope_Input,
221                                        0,
222                                        &afp->mInputCallback,
223                                        sizeof(afp->mInputCallback));
224        if (result)
225            SDL_SetError ("AudioUnitSetProperty:RemoveInputCallback:%ld", result);
226
227        afp->mAudioFileManager->Disconnect(afp->mAudioFileManager);
228    }
229}
230
231typedef struct {
232    UInt32 offset;
233    UInt32 blockSize;
234} SSNDData;
235
236static int    AudioFilePlayer_OpenFile (AudioFilePlayer *afp, const FSRef *inRef, SInt64 *outFileDataSize)
237{
238    ContainerChunk chunkHeader;
239    ChunkHeader chunk;
240    SSNDData ssndData;
241
242    OSErr result;
243    HFSUniStr255 dfName;
244    ByteCount actual;
245    SInt64 offset;
246
247    /* Open the data fork of the input file */
248    result = FSGetDataForkName(&dfName);
249       if (result) return 0; /*THROW_RESULT("AudioFilePlayer::OpenFile(): FSGetDataForkName")*/
250
251    result = FSOpenFork(inRef, dfName.length, dfName.unicode, fsRdPerm, &afp->mForkRefNum);
252       if (result) return 0; /*THROW_RESULT("AudioFilePlayer::OpenFile(): FSOpenFork")*/
253
254    /* Read the file header, and check if it's indeed an AIFC file */
255    result = FSReadFork(afp->mForkRefNum, fsAtMark, 0, sizeof(chunkHeader), &chunkHeader, &actual);
256       if (result) return 0; /*THROW_RESULT("AudioFilePlayer::OpenFile(): FSReadFork")*/
257
258    if (SDL_SwapBE32(chunkHeader.ckID) != 'FORM') {
259        result = -1;
260        if (result) return 0; /*THROW_RESULT("AudioFilePlayer::OpenFile(): chunk id is not 'FORM'");*/
261    }
262
263    if (SDL_SwapBE32(chunkHeader.formType) != 'AIFC') {
264        result = -1;
265        if (result) return 0; /*THROW_RESULT("AudioFilePlayer::OpenFile(): file format is not 'AIFC'");*/
266    }
267
268    /* Search for the SSND chunk. We ignore all compression etc. information
269       in other chunks. Of course that is kind of evil, but for now we are lazy
270       and rely on the cdfs to always give us the same fixed format.
271       TODO: Parse the COMM chunk we currently skip to fill in mFileDescription.
272    */
273    offset = 0;
274    do {
275        result = FSReadFork(afp->mForkRefNum, fsFromMark, offset, sizeof(chunk), &chunk, &actual);
276        if (result) return 0; /*THROW_RESULT("AudioFilePlayer::OpenFile(): FSReadFork")*/
277
278        chunk.ckID = SDL_SwapBE32(chunk.ckID);
279        chunk.ckSize = SDL_SwapBE32(chunk.ckSize);
280
281        /* Skip the chunk data */
282        offset = chunk.ckSize;
283    } while (chunk.ckID != 'SSND');
284
285    /* Read the header of the SSND chunk. After this, we are positioned right
286       at the start of the audio data. */
287    result = FSReadFork(afp->mForkRefNum, fsAtMark, 0, sizeof(ssndData), &ssndData, &actual);
288    if (result) return 0; /*THROW_RESULT("AudioFilePlayer::OpenFile(): FSReadFork")*/
289
290    ssndData.offset = SDL_SwapBE32(ssndData.offset);
291
292    result = FSSetForkPosition(afp->mForkRefNum, fsFromMark, ssndData.offset);
293    if (result) return 0; /*THROW_RESULT("AudioFilePlayer::OpenFile(): FSSetForkPosition")*/
294
295    /* Data size */
296    *outFileDataSize = chunk.ckSize - ssndData.offset - 8;
297
298    /* File format */
299    afp->mFileDescription.mSampleRate = 44100;
300    afp->mFileDescription.mFormatID = kAudioFormatLinearPCM;
301    afp->mFileDescription.mFormatFlags = kLinearPCMFormatFlagIsPacked | kLinearPCMFormatFlagIsSignedInteger;
302    afp->mFileDescription.mBytesPerPacket = 4;
303    afp->mFileDescription.mFramesPerPacket = 1;
304    afp->mFileDescription.mBytesPerFrame = 4;
305    afp->mFileDescription.mChannelsPerFrame = 2;
306    afp->mFileDescription.mBitsPerChannel = 16;
307
308    return 1;
309}
310
311AudioFilePlayer *new_AudioFilePlayer (const FSRef *inFileRef)
312{
313    SInt64 fileDataSize  = 0;
314
315    AudioFilePlayer *afp = (AudioFilePlayer *) SDL_malloc(sizeof (AudioFilePlayer));
316    if (afp == NULL)
317        return NULL;
318    SDL_memset(afp, '\0', sizeof (*afp));
319
320    #define SET_AUDIOFILEPLAYER_METHOD(m) afp->m = AudioFilePlayer_##m
321    SET_AUDIOFILEPLAYER_METHOD(SetDestination);
322    SET_AUDIOFILEPLAYER_METHOD(SetNotifier);
323    SET_AUDIOFILEPLAYER_METHOD(SetStartFrame);
324    SET_AUDIOFILEPLAYER_METHOD(GetCurrentFrame);
325    SET_AUDIOFILEPLAYER_METHOD(SetStopFrame);
326    SET_AUDIOFILEPLAYER_METHOD(Connect);
327    SET_AUDIOFILEPLAYER_METHOD(Disconnect);
328    SET_AUDIOFILEPLAYER_METHOD(DoNotification);
329    SET_AUDIOFILEPLAYER_METHOD(IsConnected);
330    SET_AUDIOFILEPLAYER_METHOD(GetDestUnit);
331    SET_AUDIOFILEPLAYER_METHOD(Print);
332    SET_AUDIOFILEPLAYER_METHOD(OpenFile);
333    #undef SET_AUDIOFILEPLAYER_METHOD
334
335    if (!afp->OpenFile (afp, inFileRef, &fileDataSize))
336    {
337        SDL_free(afp);
338        return NULL;
339    }
340
341    /* we want about 4 seconds worth of data for the buffer */
342    int bytesPerSecond = (UInt32) (4 * afp->mFileDescription.mSampleRate * afp->mFileDescription.mBytesPerFrame);
343
344#if DEBUG
345    printf("File format:\n");
346    PrintStreamDesc (&afp->mFileDescription);
347#endif
348
349    afp->mAudioFileManager = new_AudioFileManager(afp, afp->mForkRefNum,
350                                                  fileDataSize,
351                                                  bytesPerSecond);
352    if (afp->mAudioFileManager == NULL)
353    {
354        delete_AudioFilePlayer(afp);
355        return NULL;
356    }
357
358    return afp;
359}
360
361