tenebrae2/macosx/cd_osx.m
2003-01-17 21:18:53 +00:00

674 lines
18 KiB
Objective-C

//___________________________________________________________________________________________________________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 <AppKit/AppKit.h>
#include <Foundation/Foundation.h>
#include <CoreAudio/AudioHardware.h>
#include <sys/mount.h>
#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