//___________________________________________________________________________________________________________nFO // "cd_osx.m" - MacOS X audio CD driver. // // Written by: Axel "awe" Wefers [mailto:awe@fruitz-of-dojo.de]. // (C)2001-2002 Fruitz Of Dojo [http://www.fruitz-of-dojo.de]. // // QuakeŞ is copyrighted by id software [http://www.idsoftware.com]. // // Version History: // // TenebraeQuake: // v1.0.0: Initial release. // // Standard Quake branch [pre-TenebraeQuake]: // v1.0.1: Added "cdda" as extension for detection of audio-tracks [required by MacOS X v10.1 or later]. // v1.0.0: Initial release. //______________________________________________________________________________________________________iNCLUDES #pragma mark =Includes= #include "quakedef.h" #include #include #include #include #pragma mark - //_______________________________________________________________________________________________________dEFINES #pragma mark =Defines= #define CD_SAMPLE_BUFFER (4 * 1024) #pragma mark - //______________________________________________________________________________________________________tYPEDEFS #pragma mark =TypeDefs= typedef int SInt; typedef unsigned int UInt; typedef struct { UInt chunkID; UInt chunkSize; UInt fileType; } AIFFChunkHeader; typedef struct { UInt chunkID; UInt chunkSize; } AIFFGenericChunk; typedef struct { UInt offset; UInt blockSize; } AIFFSSNDData; typedef struct { FILE *file; } AIFFInfo; #pragma mark - //_____________________________________________________________________________________________________vARIABLES #pragma mark =Variables= extern cvar_t bgmvolume; extern AudioDeviceID gSoundDeviceID; extern Boolean gCDIOProcIsInstalled; static Boolean gAudioCDIsPaused, gLoopTrack; static UInt8 gPlayAudioCD; static UInt16 gCDTrackCount, gCurCDTrack; static SInt16 gCDSampleBuffer[CD_SAMPLE_BUFFER]; static NSMutableArray * gCDTracks; static AIFFInfo * gAIFFStream; static float gCDVolume; static char gCDDevice[256]; #pragma mark - //___________________________________________________________________________________________fUNCTION_pROTOTYPES #pragma mark =Function Prototypes= OSStatus CD_AudioIOProc (AudioDeviceID, const AudioTimeStamp *, const AudioBufferList *, const AudioTimeStamp *, AudioBufferList *, const AudioTimeStamp *, void *); static AIFFInfo * CD_OpenTrack(NSString *); static void CD_CloseTrack(AIFFInfo *); static Boolean CD_GetTrackList(void); static void CD_f(void); #pragma mark - //________________________________________________________________________________________________CD_OpenTrack() AIFFInfo * CD_OpenTrack (NSString *thePath) { const char *myPathStr; AIFFInfo *myAIFFStream; FILE *myFile; AIFFChunkHeader myChunkHeader; AIFFGenericChunk myChunk; AIFFSSNDData mySsndData; // convert the path to POSIX compatible string and open the file: myPathStr = [thePath fileSystemRepresentation]; myFile = fopen (myPathStr, "r"); if (!myFile) { Con_Printf ("Can\'t open audio track!\n"); return (NULL); } // initialize the new stream: myAIFFStream = malloc (sizeof(*myAIFFStream)); if (myAIFFStream == NULL) { Con_Printf ("Can\'t open audio track!\n"); return (NULL); } myAIFFStream->file = myFile; // get the header and check the format [must be AIFF]: fread (&myChunkHeader, 1, sizeof (myChunkHeader), myAIFFStream->file); if (myChunkHeader.chunkID != 'FORM') { Con_Printf ("Can\'t open audio track!\n"); CD_CloseTrack (myAIFFStream); return (NULL); } if (myChunkHeader.fileType != 'AIFC') { Con_Printf ("Can\'t open audio track!\n"); CD_CloseTrack (myAIFFStream); return (NULL); } // look for the "SSND" chunk: while (1) { fread (&myChunk, 1, sizeof (myChunk), myAIFFStream->file); if (myChunk.chunkID == 'SSND') { break; } fseek (myAIFFStream->file, myChunk.chunkSize, SEEK_CUR); if (ferror (myAIFFStream->file)) { return (NULL); } } fread (&mySsndData, 1, sizeof (mySsndData), myAIFFStream->file); return (myAIFFStream); } //_______________________________________________________________________________________________CD_CloseTrack() void CD_CloseTrack (AIFFInfo *theAIFFStream) { // close the file and free memory: if (theAIFFStream != NULL) { fclose (theAIFFStream->file); free (theAIFFStream); } } //______________________________________________________________________________________________CD_AudioIOProc() OSStatus CD_AudioIOProc (AudioDeviceID inDevice, const AudioTimeStamp *inNow, const AudioBufferList *inInputData, const AudioTimeStamp *inInputTime, AudioBufferList *outOutputData, const AudioTimeStamp *inOutputTime, void *inClientData) { float *myOutBuffer; UInt i = 0; // get the current CoreAudio buffer: myOutBuffer = (float *)outOutputData->mBuffers[0].mData; // is a stream open? if (gAIFFStream) { SInt16 *myInBuffer; UInt mySampleCount; float myScale = gCDVolume / 32768.0f; // read the current AIFF sample data from CD: myInBuffer = gCDSampleBuffer; mySampleCount = fread (gCDSampleBuffer, sizeof (SInt16), CD_SAMPLE_BUFFER, gAIFFStream->file); // was the buffer filled? if (mySampleCount < CD_SAMPLE_BUFFER) { // no: probably end of file! so get the next track! if (feof (gAIFFStream->file)) { if (gLoopTrack) { // loop? restart the track. fseek (gAIFFStream->file, 0L, SEEK_SET); mySampleCount += fread (gCDSampleBuffer + mySampleCount, sizeof (SInt16), CD_SAMPLE_BUFFER - mySampleCount, gAIFFStream->file); } else { // next track: so close the current track CD_CloseTrack (gAIFFStream); // was it the last track? if (gCurCDTrack == gCDTrackCount) { gCurCDTrack = 1; } else { gCurCDTrack++; } // open the new track: gAIFFStream = CD_OpenTrack ([gCDTracks objectAtIndex: gCurCDTrack - 1]); // fill the rest of the buffer: mySampleCount += fread (gCDSampleBuffer + mySampleCount, sizeof (SInt16), CD_SAMPLE_BUFFER - mySampleCount, gAIFFStream->file); } } } for (i = 0; i < mySampleCount; i++) { // convert the buffer to float: *myOutBuffer++ = ((SInt16) (((*myInBuffer & 0xff00) >> 8) | ((*myInBuffer & 0x00ff) << 8))) * myScale; myInBuffer++; } } // fill the rest of the buffer with zeros: for (; i < CD_SAMPLE_BUFFER; i++) { *myOutBuffer++ = 0.0f; } return (0); } //_____________________________________________________________________________________________CD_GetTrackList() Boolean CD_GetTrackList (void) { NSAutoreleasePool *myPool; NSDirectoryEnumerator *myDirEnum; NSFileManager *myFileManager; UInt myMountCount; struct statfs *myMountList; NSString *myMountPath; NSString *myFilePath; UInt16 i, myStrLength; // release previously allocated memory: if (gCDTracks) { [gCDTracks release]; } // get memory for the new tracklisting: gCDTracks = [[NSMutableArray alloc] init]; myPool = [[NSAutoreleasePool alloc] init]; gCDTrackCount = 0; // get number of mounted devices: myMountCount = getmntinfo (&myMountList, MNT_NOWAIT); // zero devices? return. if (myMountCount <= 0) { [gCDTracks release]; gCDTracks = NULL; gCDTrackCount = 0; Con_Printf ("No Audio-CD found.\n"); return (0); } myFileManager = [NSFileManager defaultManager]; while (myMountCount--) { // is the device read only? if ((myMountList[myMountCount].f_flags & MNT_RDONLY) != MNT_RDONLY) continue; // is the device local? if ((myMountList[myMountCount].f_flags & MNT_LOCAL) != MNT_LOCAL) continue; // is the device "cdda"? if (strcmp (myMountList[myMountCount].f_fstypename, "cddafs")) continue; // is the device a directory? if (strrchr (myMountList[myMountCount].f_mntonname, '/') == NULL) continue; // we have found a Audio-CD! Con_Printf ("Found Audio-CD at mount entry: \"%s\".\n", myMountList[myMountCount].f_mntonname); // preserve the device name: myStrLength = strlen (myMountList[myMountCount].f_mntonname); if (myStrLength > 255) myStrLength = 255; for (i = 0; i < myStrLength; i++) { gCDDevice[i] = myMountList[myMountCount].f_mntonname[i]; } gCDDevice[255] = 0x00; myMountPath = [NSString stringWithCString: myMountList[myMountCount].f_mntonname]; myDirEnum = [myFileManager enumeratorAtPath: myMountPath]; // get all audio tracks: while ((myFilePath = [myDirEnum nextObject])) { if ([[myFilePath pathExtension] isEqualToString: @"cdda"] || // MacOS X 10.1+ [[myFilePath pathExtension] isEqualToString: @"aiff"]) // MacOS X 10.0.0 - 10.0.4 { [gCDTracks addObject: [myMountPath stringByAppendingPathComponent: myFilePath]]; gCDTrackCount++; } } break; } // release the pool: [myPool release]; // just security: if (![gCDTracks count]) { [gCDTracks release]; gCDTracks = NULL; gCDTrackCount = 0; Con_Printf ("No audio tracks found.\n"); return (0); } return (1); } //________________________________________________________________________________________________CDAudio_Play() void CDAudio_Play (UInt8 theTrack, qboolean theLooping) { if (gCDTracks && gCDIOProcIsInstalled) { // check for mismatching CD no.: if (theTrack > gCDTrackCount) { theTrack = gCDTrackCount; } else { if (theTrack <= 0) { theTrack = 1; } } gCurCDTrack = theTrack; gAIFFStream = CD_OpenTrack ([gCDTracks objectAtIndex: theTrack - 1]); // start the Audio IO: if(AudioDeviceStart (gSoundDeviceID, CD_AudioIOProc)) { Con_Printf ("Failed to play audio CD.\n"); } else { // pause the Audio-CD if volume is zero: if (gCDVolume == 0.0) { CDAudio_Pause (); } gPlayAudioCD = 1; gAudioCDIsPaused = NO; gLoopTrack = theLooping; } } } //________________________________________________________________________________________________CDAudio_Stop() void CDAudio_Stop (void) { // just stop the audio IO: if (gCDIOProcIsInstalled) { AudioDeviceStop (gSoundDeviceID, CD_AudioIOProc); CD_CloseTrack (gAIFFStream); gAIFFStream = NULL; gPlayAudioCD = 0; } } //_______________________________________________________________________________________________CDAudio_Pause() void CDAudio_Pause (void) { // just stop the audio IO: if (gCDIOProcIsInstalled) { AudioDeviceStop (gSoundDeviceID, CD_AudioIOProc); gAudioCDIsPaused = YES; gPlayAudioCD = 0; } } //______________________________________________________________________________________________CDAudio_Resume() void CDAudio_Resume (void) { // resume only, if CD audio was previously paused: if (gCDIOProcIsInstalled && gAudioCDIsPaused) { if (AudioDeviceStart (gSoundDeviceID, CD_AudioIOProc)) { Con_Printf ("Can\'t resume CD auio. Use \"cd reset\" to fix the problem.\n"); } else { gAudioCDIsPaused = NO; } } } //______________________________________________________________________________________________CDAudio_Update() void CDAudio_Update (void) { // just update volume settings here: if (gCDVolume != bgmvolume.value) { if (gCDVolume) { gCDVolume = bgmvolume.value; if (!gCDVolume) { CDAudio_Pause (); } } else { gCDVolume = bgmvolume.value; if (!gCDVolume) { CDAudio_Resume (); } } } } //________________________________________________________________________________________________CDAudio_Init() SInt CDAudio_Init (void) { // add "cd" console command: Cmd_AddCommand ("cd", CD_f); gCDVolume = bgmvolume.value; gPlayAudioCD = 0; // init SNDDMA, if "-nosound" was used: if (COM_CheckParm ("-nosound")) { if (SNDDMA_Init ()) { // get the track listing: if (CD_GetTrackList ()) { gAudioCDIsPaused = NO; Con_Print ("CoreAudio CD driver initialized...\n"); return(1); } } } else { // get the track listing: if (CD_GetTrackList ()) { gAudioCDIsPaused = NO; Con_Print ("CoreAudio CD driver initialized...\n"); return (1); } } // failure. return 0. Con_Print ("CoreAudio CD driver failed. Reason: No CD found.\n"); return (0); } //____________________________________________________________________________________________CDAudio_Shutdown() void CDAudio_Shutdown (void) { // shutdown the audio IO: if (gCDIOProcIsInstalled) { CDAudio_Stop (); AudioDeviceRemoveIOProc (gSoundDeviceID, CD_AudioIOProc); if (gCDTracks != NULL) { [gCDTracks release]; gCDTracks = NULL; } } } //________________________________________________________________________________________________________CD_f() void CD_f (void) { char *myCommandOption; // this command requires options! if (Cmd_Argc () < 2) { return; } // get the option: myCommandOption = Cmd_Argv (1); // turn CD playback on: if (Q_strcasecmp (myCommandOption, "on") == 0) { if (!gCDTrackCount) { CD_GetTrackList(); } CDAudio_Play(1, 0); return; } // turn CD playback off: if (Q_strcasecmp (myCommandOption, "off") == 0) { CDAudio_Stop (); return; } // reset the current CD: if (Q_strcasecmp (myCommandOption, "reset") == 0) { CDAudio_Stop (); if (CD_GetTrackList ()) { Con_Printf ("CD found. %d tracks [\"%s\"].\n", gCDTrackCount, gCDDevice); gAudioCDIsPaused = NO; } else { Con_Printf ("No CD found.\n"); } return; } // the following commands require a valid track array, so build it, if not present: if (!gCDTrackCount) { CD_GetTrackList (); if (!gCDTrackCount) { Con_Printf ("No CD in player.\n"); return; } } // play the selected track: if (Q_strcasecmp (myCommandOption, "play") == 0) { CDAudio_Play (Q_atoi (Cmd_Argv (2)), 0); return; } // loop the selected track: if (Q_strcasecmp (myCommandOption, "loop") == 0) { CDAudio_Play (Q_atoi (Cmd_Argv (2)), 1); return; } // stop the current track: if (Q_strcasecmp (myCommandOption, "stop") == 0) { CDAudio_Stop (); return; } // pause the current track: if (Q_strcasecmp (myCommandOption, "pause") == 0) { CDAudio_Pause (); return; } // resume the current track: if (Q_strcasecmp (myCommandOption, "resume") == 0) { CDAudio_Resume (); return; } // eject the CD: if (Q_strcasecmp (myCommandOption, "eject") == 0) { // stop CD playback. CDAudio_Stop (); // get the current device: if (!gCDTracks) { CD_GetTrackList(); } // eject the CD: if (gCDTracks) { if ([[NSWorkspace sharedWorkspace] unmountAndEjectDeviceAtPath:[NSString stringWithCString:gCDDevice]]) { [gCDTracks release]; gCDTracks = NULL; gCDTrackCount = 0; gCurCDTrack = 0; gPlayAudioCD = 0; } else { Con_Printf ("Can't eject Audio-CD!\n"); } } else { Con_Printf ("No Audio-CD in drive!\n"); } return; } // output CD info: if (Q_strcasecmp(myCommandOption, "info") == 0) { Con_Printf ("Playing track %d of %d [\"%s\"].\n", gCurCDTrack, gCDTrackCount, gCDDevice); Con_Printf ("Volume is: %.2f.\n", gCDVolume); return; } } //___________________________________________________________________________________________________________eOF