| /* |
| SDL - Simple DirectMedia Layer |
| Copyright (C) 1997, 1998, 1999, 2000, 2001, 2002 Sam Lantinga |
| |
| This library is free software; you can redistribute it and/or |
| modify it under the terms of the GNU Library General Public |
| License as published by the Free Software Foundation; either |
| version 2 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 |
| Library General Public License for more details. |
| |
| You should have received a copy of the GNU Library General Public |
| License along with this library; if not, write to the Free |
| Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| |
| Sam Lantinga |
| slouken@libsdl.org |
| |
| This file based on Apple sample code. We haven't changed the file name, |
| so if you want to see the original search for it on apple.com/developer |
| */ |
| #include "SDL_config.h" |
| |
| /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| AudioFilePlayer.cpp |
| */ |
| #include "AudioFilePlayer.h" |
| |
| /* |
| void ThrowResult (OSStatus result, const char* str) |
| { |
| SDL_SetError ("Error: %s %d", str, result); |
| throw result; |
| } |
| */ |
| |
| #if DEBUG |
| static void PrintStreamDesc (AudioStreamBasicDescription *inDesc) |
| { |
| if (!inDesc) { |
| printf ("Can't print a NULL desc!\n"); |
| return; |
| } |
| |
| printf ("- - - - - - - - - - - - - - - - - - - -\n"); |
| printf (" Sample Rate:%f\n", inDesc->mSampleRate); |
| printf (" Format ID:%s\n", (char*)&inDesc->mFormatID); |
| printf (" Format Flags:%lX\n", inDesc->mFormatFlags); |
| printf (" Bytes per Packet:%ld\n", inDesc->mBytesPerPacket); |
| printf (" Frames per Packet:%ld\n", inDesc->mFramesPerPacket); |
| printf (" Bytes per Frame:%ld\n", inDesc->mBytesPerFrame); |
| printf (" Channels per Frame:%ld\n", inDesc->mChannelsPerFrame); |
| printf (" Bits per Channel:%ld\n", inDesc->mBitsPerChannel); |
| printf ("- - - - - - - - - - - - - - - - - - - -\n"); |
| } |
| #endif |
| |
| |
| static int AudioFilePlayer_SetDestination (AudioFilePlayer *afp, AudioUnit *inDestUnit) |
| { |
| /*if (afp->mConnected) throw static_cast<OSStatus>(-1);*/ /* can't set dest if already engaged */ |
| if (afp->mConnected) |
| return 0 ; |
| |
| SDL_memcpy(&afp->mPlayUnit, inDestUnit, sizeof (afp->mPlayUnit)); |
| |
| OSStatus result = noErr; |
| |
| |
| /* we can "down" cast a component instance to a component */ |
| ComponentDescription desc; |
| result = GetComponentInfo ((Component)*inDestUnit, &desc, 0, 0, 0); |
| if (result) return 0; /*THROW_RESULT("GetComponentInfo")*/ |
| |
| /* we're going to use this to know which convert routine to call |
| a v1 audio unit will have a type of 'aunt' |
| a v2 audio unit will have one of several different types. */ |
| if (desc.componentType != kAudioUnitComponentType) { |
| result = badComponentInstance; |
| /*THROW_RESULT("BAD COMPONENT")*/ |
| if (result) return 0; |
| } |
| |
| /* Set the input format of the audio unit. */ |
| result = AudioUnitSetProperty (*inDestUnit, |
| kAudioUnitProperty_StreamFormat, |
| kAudioUnitScope_Input, |
| 0, |
| &afp->mFileDescription, |
| sizeof (afp->mFileDescription)); |
| /*THROW_RESULT("AudioUnitSetProperty")*/ |
| if (result) return 0; |
| return 1; |
| } |
| |
| static void AudioFilePlayer_SetNotifier(AudioFilePlayer *afp, AudioFilePlayNotifier inNotifier, void *inRefCon) |
| { |
| afp->mNotifier = inNotifier; |
| afp->mRefCon = inRefCon; |
| } |
| |
| static int AudioFilePlayer_IsConnected(AudioFilePlayer *afp) |
| { |
| return afp->mConnected; |
| } |
| |
| static AudioUnit AudioFilePlayer_GetDestUnit(AudioFilePlayer *afp) |
| { |
| return afp->mPlayUnit; |
| } |
| |
| static void AudioFilePlayer_Print(AudioFilePlayer *afp) |
| { |
| #if DEBUG |
| printf ("Is Connected:%s\n", (IsConnected() ? "true" : "false")); |
| printf ("- - - - - - - - - - - - - - \n"); |
| #endif |
| } |
| |
| static void AudioFilePlayer_SetStartFrame (AudioFilePlayer *afp, int frame) |
| { |
| SInt64 position = frame * 2352; |
| |
| afp->mStartFrame = frame; |
| afp->mAudioFileManager->SetPosition (afp->mAudioFileManager, position); |
| } |
| |
| |
| static int AudioFilePlayer_GetCurrentFrame (AudioFilePlayer *afp) |
| { |
| return afp->mStartFrame + (afp->mAudioFileManager->GetByteCounter(afp->mAudioFileManager) / 2352); |
| } |
| |
| static void AudioFilePlayer_SetStopFrame (AudioFilePlayer *afp, int frame) |
| { |
| SInt64 position = frame * 2352; |
| |
| afp->mAudioFileManager->SetEndOfFile (afp->mAudioFileManager, position); |
| } |
| |
| void delete_AudioFilePlayer(AudioFilePlayer *afp) |
| { |
| if (afp != NULL) |
| { |
| afp->Disconnect(afp); |
| |
| if (afp->mAudioFileManager) { |
| delete_AudioFileManager(afp->mAudioFileManager); |
| afp->mAudioFileManager = 0; |
| } |
| |
| if (afp->mForkRefNum) { |
| FSCloseFork (afp->mForkRefNum); |
| afp->mForkRefNum = 0; |
| } |
| SDL_free(afp); |
| } |
| } |
| |
| static int AudioFilePlayer_Connect(AudioFilePlayer *afp) |
| { |
| #if DEBUG |
| printf ("Connect:%x, engaged=%d\n", (int)afp->mPlayUnit, (afp->mConnected ? 1 : 0)); |
| #endif |
| if (!afp->mConnected) |
| { |
| if (!afp->mAudioFileManager->DoConnect(afp->mAudioFileManager)) |
| return 0; |
| |
| /* set the render callback for the file data to be supplied to the sound converter AU */ |
| afp->mInputCallback.inputProc = afp->mAudioFileManager->FileInputProc; |
| afp->mInputCallback.inputProcRefCon = afp->mAudioFileManager; |
| |
| OSStatus result = AudioUnitSetProperty (afp->mPlayUnit, |
| kAudioUnitProperty_SetInputCallback, |
| kAudioUnitScope_Input, |
| 0, |
| &afp->mInputCallback, |
| sizeof(afp->mInputCallback)); |
| if (result) return 0; /*THROW_RESULT("AudioUnitSetProperty")*/ |
| afp->mConnected = 1; |
| } |
| |
| return 1; |
| } |
| |
| /* warning noted, now please go away ;-) */ |
| /* #warning This should redirect the calling of notification code to some other thread */ |
| static void AudioFilePlayer_DoNotification (AudioFilePlayer *afp, OSStatus inStatus) |
| { |
| if (afp->mNotifier) { |
| (*afp->mNotifier) (afp->mRefCon, inStatus); |
| } else { |
| SDL_SetError ("Notification posted with no notifier in place"); |
| |
| if (inStatus == kAudioFilePlay_FileIsFinished) |
| afp->Disconnect(afp); |
| else if (inStatus != kAudioFilePlayErr_FilePlayUnderrun) |
| afp->Disconnect(afp); |
| } |
| } |
| |
| static void AudioFilePlayer_Disconnect (AudioFilePlayer *afp) |
| { |
| #if DEBUG |
| printf ("Disconnect:%x,%ld, engaged=%d\n", (int)afp->mPlayUnit, 0, (afp->mConnected ? 1 : 0)); |
| #endif |
| if (afp->mConnected) |
| { |
| afp->mConnected = 0; |
| |
| afp->mInputCallback.inputProc = 0; |
| afp->mInputCallback.inputProcRefCon = 0; |
| OSStatus result = AudioUnitSetProperty (afp->mPlayUnit, |
| kAudioUnitProperty_SetInputCallback, |
| kAudioUnitScope_Input, |
| 0, |
| &afp->mInputCallback, |
| sizeof(afp->mInputCallback)); |
| if (result) |
| SDL_SetError ("AudioUnitSetProperty:RemoveInputCallback:%ld", result); |
| |
| afp->mAudioFileManager->Disconnect(afp->mAudioFileManager); |
| } |
| } |
| |
| typedef struct { |
| UInt32 offset; |
| UInt32 blockSize; |
| } SSNDData; |
| |
| static int AudioFilePlayer_OpenFile (AudioFilePlayer *afp, const FSRef *inRef, SInt64 *outFileDataSize) |
| { |
| ContainerChunk chunkHeader; |
| ChunkHeader chunk; |
| SSNDData ssndData; |
| |
| OSErr result; |
| HFSUniStr255 dfName; |
| ByteCount actual; |
| SInt64 offset; |
| |
| /* Open the data fork of the input file */ |
| result = FSGetDataForkName(&dfName); |
| if (result) return 0; /*THROW_RESULT("AudioFilePlayer::OpenFile(): FSGetDataForkName")*/ |
| |
| result = FSOpenFork(inRef, dfName.length, dfName.unicode, fsRdPerm, &afp->mForkRefNum); |
| if (result) return 0; /*THROW_RESULT("AudioFilePlayer::OpenFile(): FSOpenFork")*/ |
| |
| /* Read the file header, and check if it's indeed an AIFC file */ |
| result = FSReadFork(afp->mForkRefNum, fsAtMark, 0, sizeof(chunkHeader), &chunkHeader, &actual); |
| if (result) return 0; /*THROW_RESULT("AudioFilePlayer::OpenFile(): FSReadFork")*/ |
| |
| if (chunkHeader.ckID != 'FORM') { |
| result = -1; |
| if (result) return 0; /*THROW_RESULT("AudioFilePlayer::OpenFile(): chunk id is not 'FORM'");*/ |
| } |
| |
| if (chunkHeader.formType != 'AIFC') { |
| result = -1; |
| if (result) return 0; /*THROW_RESULT("AudioFilePlayer::OpenFile(): file format is not 'AIFC'");*/ |
| } |
| |
| /* Search for the SSND chunk. We ignore all compression etc. information |
| in other chunks. Of course that is kind of evil, but for now we are lazy |
| and rely on the cdfs to always give us the same fixed format. |
| TODO: Parse the COMM chunk we currently skip to fill in mFileDescription. |
| */ |
| offset = 0; |
| do { |
| result = FSReadFork(afp->mForkRefNum, fsFromMark, offset, sizeof(chunk), &chunk, &actual); |
| if (result) return 0; /*THROW_RESULT("AudioFilePlayer::OpenFile(): FSReadFork")*/ |
| |
| /* Skip the chunk data */ |
| offset = chunk.ckSize; |
| } while (chunk.ckID != 'SSND'); |
| |
| /* Read the header of the SSND chunk. After this, we are positioned right |
| at the start of the audio data. */ |
| result = FSReadFork(afp->mForkRefNum, fsAtMark, 0, sizeof(ssndData), &ssndData, &actual); |
| if (result) return 0; /*THROW_RESULT("AudioFilePlayer::OpenFile(): FSReadFork")*/ |
| |
| result = FSSetForkPosition(afp->mForkRefNum, fsFromMark, ssndData.offset); |
| if (result) return 0; /*THROW_RESULT("AudioFilePlayer::OpenFile(): FSSetForkPosition")*/ |
| |
| /* Data size */ |
| *outFileDataSize = chunk.ckSize - ssndData.offset - 8; |
| |
| /* File format */ |
| afp->mFileDescription.mSampleRate = 44100; |
| afp->mFileDescription.mFormatID = kAudioFormatLinearPCM; |
| afp->mFileDescription.mFormatFlags = kLinearPCMFormatFlagIsPacked | kLinearPCMFormatFlagIsSignedInteger; |
| afp->mFileDescription.mBytesPerPacket = 4; |
| afp->mFileDescription.mFramesPerPacket = 1; |
| afp->mFileDescription.mBytesPerFrame = 4; |
| afp->mFileDescription.mChannelsPerFrame = 2; |
| afp->mFileDescription.mBitsPerChannel = 16; |
| |
| return 1; |
| } |
| |
| AudioFilePlayer *new_AudioFilePlayer (const FSRef *inFileRef) |
| { |
| SInt64 fileDataSize = 0; |
| |
| AudioFilePlayer *afp = (AudioFilePlayer *) SDL_malloc(sizeof (AudioFilePlayer)); |
| if (afp == NULL) |
| return NULL; |
| SDL_memset(afp, '\0', sizeof (*afp)); |
| |
| #define SET_AUDIOFILEPLAYER_METHOD(m) afp->m = AudioFilePlayer_##m |
| SET_AUDIOFILEPLAYER_METHOD(SetDestination); |
| SET_AUDIOFILEPLAYER_METHOD(SetNotifier); |
| SET_AUDIOFILEPLAYER_METHOD(SetStartFrame); |
| SET_AUDIOFILEPLAYER_METHOD(GetCurrentFrame); |
| SET_AUDIOFILEPLAYER_METHOD(SetStopFrame); |
| SET_AUDIOFILEPLAYER_METHOD(Connect); |
| SET_AUDIOFILEPLAYER_METHOD(Disconnect); |
| SET_AUDIOFILEPLAYER_METHOD(DoNotification); |
| SET_AUDIOFILEPLAYER_METHOD(IsConnected); |
| SET_AUDIOFILEPLAYER_METHOD(GetDestUnit); |
| SET_AUDIOFILEPLAYER_METHOD(Print); |
| SET_AUDIOFILEPLAYER_METHOD(OpenFile); |
| #undef SET_AUDIOFILEPLAYER_METHOD |
| |
| if (!afp->OpenFile (afp, inFileRef, &fileDataSize)) |
| { |
| SDL_free(afp); |
| return NULL; |
| } |
| |
| /* we want about 4 seconds worth of data for the buffer */ |
| int bytesPerSecond = (UInt32) (4 * afp->mFileDescription.mSampleRate * afp->mFileDescription.mBytesPerFrame); |
| |
| #if DEBUG |
| printf("File format:\n"); |
| PrintStreamDesc (&afp->mFileDescription); |
| #endif |
| |
| afp->mAudioFileManager = new_AudioFileManager(afp, afp->mForkRefNum, |
| fileDataSize, |
| bytesPerSecond); |
| if (afp->mAudioFileManager == NULL) |
| { |
| delete_AudioFilePlayer(afp); |
| return NULL; |
| } |
| |
| return afp; |
| } |
| |