Milán Major

netscape-revival

1 branch
Code

sun-java/mmedia/macos/audio.c


#include <stdio.h>
#include <fcntl.h>
#include <types.h>
#include <errno.h>

#include "oobj.h"
#include "interpreter.h"

#include <Sound.h>
#include <Resources.h>
#include <Gestalt.h>
#include <ConditionalMacros.h>

// Declare this here since including fp.h conflicts with math.h.
// We want to use math.h everywhere else in the project.
extern void ldtox80 ( const long double *x, extended80 *x80 );


//
// Constants
//
static const UInt32 kJavaSoundSampleRate		= 8000;
static const Fixed	kJavaSoundSampleRateFixed 	= (kJavaSoundSampleRate << 16);	
static const UInt32	kJavaSoundBufferSize		= 800L;


//
// JavaSoundBuffer: Our sample-data buffer.
//
// We allocate fixed-size buffers so we can reuse them easily.
// Buffers are only deallocated when the audio channel is closed.
//
// Windows uses a buffer free-list, but we cannot do so because we 
// are notified that a sound sample is finished at interrupt time,
// and we have no synchronization primitives.  
//
// So all allocated buffers are kept in a single list.  They are
// marked as "inUse".  When a new buffer is needed, the list is
// scanned for a buffer not in use.  If one is not available, 
// a new one is allocated.
//
typedef struct JavaSoundBuffer JavaSoundBuffer;
typedef struct JavaSoundBuffer
{
	JavaSoundBuffer	*next;
	UInt32			inUse;
	ExtSoundHeader	header;								// header includes the sample data at the end (.sampleArea)
	char			samples[kJavaSoundBufferSize-1];	// -1 because ExtSoundHeader has 1 character at the end
};



//
// Gestalt fun
//
inline static void		CheckSoundCapabilities(void);
inline static Boolean	CanPlaySixteenBit(void);


// 
// Conversion routines
//
static inline short	ULAW_ToMacLinear(int datum);
static Boolean		ConvertBufferToLinear(char *ulawSample,	short *linearSample, UInt32 ulawLen);

//
// Buffer setup/maintenance
//
inline static JavaSoundBuffer *	GetJavaSoundBuffer(void);
inline static void				ReleaseJavaSoundBuffer(JavaSoundBuffer *buffer);
static void						SetupJavaSoundHeader(ExtSoundHeader *header);
static void						FreeUnusedJavaSoundBuffers(void);


//
// Callback to let us know a sound command is finished
//
static pascal void 	JavaSoundCallback(SndChannelPtr chan, SndCommand *cmd);
#if GENERATINGCFM
static RoutineDescriptor gJavaSoundCallbackRD = BUILD_ROUTINE_DESCRIPTOR(uppSndCallBackProcInfo, JavaSoundCallback);
#endif


//
// mmm... globals
//
static JavaSoundBuffer		*gJavaSoundBufferList 			= NULL;		// allocated buffers
static SndChannel			*gJavaSoundChannel 				= NULL;		// our one channel - LAME
static UInt32				gJavaSoundChannelRefCount 		= 0;		// # of open calls for the one lame channel
static SInt32				gSoundCapabilities				= -1;		// -1 unknown, 0 no 16-bit, 1 16-bit ok

static UInt32				gSilenceCount					= 0;

#ifdef DEBUG
static UInt32				gMaxJavaSoundBufferCount		= 0;
static UInt32				gJavaSoundBufferCount			= 0;
#endif


//
// Determine whether we can play 16-bit audio...
//
inline static void
CheckSoundCapabilities(void)
{
	OSErr	err;
	long	soundBits;
	
	err = Gestalt(gestaltSoundAttr, &soundBits);
	
	if (err == 0 && (soundBits & (1 << gestalt16BitAudioSupport)) != 0)
		gSoundCapabilities = 1;
	else
		gSoundCapabilities = 0;
}

inline static Boolean	
CanPlaySixteenBit(void)	
{ 
	if (gSoundCapabilities == -1)
		CheckSoundCapabilities();
		
	return (gSoundCapabilities == 1);
}



//
//	sun_audio_AudioDevice_audioOpen
//
//	Opens a Mac sound channel, if one has not already been opened.
//	If one is already open, this bumps a ref count for it.
//
long
sun_audio_AudioDevice_audioOpen(struct Hsun_audio_AudioDevice *ap)
{		
#pragma unused (ap)

	OSErr	err;
	long	result = -1;
		
	if ( ! CanPlaySixteenBit() )
		return 1;
			
	if (gJavaSoundChannel != NULL)
	{
		gJavaSoundChannelRefCount++;
		return 1;
	}
	
		
	
	gJavaSoundChannel = (SndChannel *) malloc(sizeof(SndChannel));
	if (gJavaSoundChannel == NULL) return -1;
	memset(gJavaSoundChannel, 0, sizeof(SndChannel));
	
	//
	// std queue length is 128, but playing sampled sound
	// really only requires one command at a time...
	//
	gJavaSoundChannel->qLength = 32;
	
	err	= SndNewChannel(	&gJavaSoundChannel,						// channel to initialize
							sampledSynth,							// will play sampled sound
							initStereo, 							// ??, sure...
#if GENERATINGCFM
							&gJavaSoundCallbackRD					// no callback
#else
							JavaSoundCallback
#endif
							);

	if (err == noErr)
	{
		result = 1;
		gJavaSoundChannelRefCount++;
	}
	else
	{
		if (gJavaSoundChannelRefCount == 0)
		{
			free(gJavaSoundChannel);
			gJavaSoundChannel = NULL;
		}
	}
	
	return result;
}



//
// JavaSoundCallback
//
// Called when a sound sample is finished. 
// We mark the buffer as unused.
//
static pascal void 
JavaSoundCallback(SndChannelPtr chan, SndCommand *cmd)
{
#pragma unused (chan)

	if (cmd != 0 && cmd->param2 != 0)
	{
		JavaSoundBuffer	*buffer = (JavaSoundBuffer *) cmd->param2;
		buffer->inUse = 0;
	}
}



//
// sun_audio_AudioDevice_audioClose
//
// Useless.  Apparently never called.
//
void
sun_audio_AudioDevice_audioClose(struct Hsun_audio_AudioDevice *aP)
{
#pragma unused (aP)

	if ( ! CanPlaySixteenBit() )
		return;

	if (gJavaSoundChannel != NULL && (--gJavaSoundChannelRefCount) == 0)
	{
		JavaSoundBuffer	*	buf;
	
		//
		// Kill the sound channel
		//
		SndDisposeChannel(gJavaSoundChannel, true);
		free(gJavaSoundChannel);
		gJavaSoundChannel = NULL;
		
		
		//
		// Free all allocated sample buffers
		// 
		for (buf = gJavaSoundBufferList; buf != NULL;)
		{
			JavaSoundBuffer	*next = buf->next;
		
			free(buf);
			buf = next;
		}
		
	#ifdef DEBUG
		gJavaSoundBufferCount = 0;
	#endif
	
		gJavaSoundBufferList = NULL;
	}
}


//
// sun_audio_AudioDevice_audioWrite
//
void
sun_audio_AudioDevice_audioWrite(	struct Hsun_audio_AudioDevice	*	aP,
				       				HArrayOfByte 					*	buf,
				       				long 								len	)
{
#pragma unused (aP)

	OSErr				err;
	char 			*	data;			// ULAW data buffer
	JavaSoundBuffer	*	linearBuffer;	// sound header + data
	ExtSoundHeader	* 	header;			// ptr to header in linearBuffer
	SndCommand			command;		// sound command to play from buffer
		
	if ( ! CanPlaySixteenBit() )
		return;
		
	// Handle goofy cases
    if (buf == 0) 
    {
		SignalError(0, JAVAPKG "NullPointerException", 0);
		return;
    }
    if (len <= 0) 
    {	
		return;
    }
    
 	
 	//
 	// Get the ULAW data buffer, and allocate
 	// the linear data buffer.
 	//
 	
    data = unhand(buf)->body; 
    if (obj_length(buf) < len)
    	len = obj_length(buf);
	
	len = len << 1;	// 8 bit -> 16 bit
	
	
	
	//
	// Loop 'til we've played all the data
	//
	
	while (len > 0)
	{
		long	linearBlockLen;	// 16 bit length	
		long	ulawBlockLen;	// 8 
		
		linearBlockLen = MIN(len, kJavaSoundBufferSize);
		ulawBlockLen   = linearBlockLen >> 1;
		
		// Get an available buffer or allocate a new one
		linearBuffer = GetJavaSoundBuffer();
		if (linearBuffer == NULL)	return;		
	
		// Convert the 8-bit ULAW to 16-bit linear
		header = &(linearBuffer->header);
		if ( ! ConvertBufferToLinear(data, (short *) &(header->sampleArea[0]), ulawBlockLen))
		{
			// SILENCE !
			ReleaseJavaSoundBuffer(linearBuffer);
			
			// ¥¥¥ÊFIX ME HACK !!! - 
			// 
			// this is the only reasonable chance we get
			// to free our allocated buffers
			//
			gSilenceCount++;
			
			if (gSilenceCount > 20)
			{
				FreeUnusedJavaSoundBuffers();
				gSilenceCount = 0;
			}
			return;
		}
		
		gSilenceCount = 0;
		
		// The rest of the header is set up by GetJavaSoundBuffer()
		header->numFrames = ulawBlockLen;	// # bytes, not samples
		
		// Play this puppy.
		command.cmd 	= bufferCmd;
		command.param1	= 0;
		command.param2	= (long) header;		
		err = SndDoCommand(	gJavaSoundChannel, 		// our sound channel
							&command, 				// command to play sample from buffer
							true	);				// wait for space in command queue, if necessary 
		
		
		// Send a callback command so that
		// our callback func will run when the
		// sound sample is finished.
		// 
		// This lets us mark the sample buffer
		// as unused.
		//
		if (err == noErr)
		{
			command.cmd		= callBackCmd;
			command.param1	= 0;
			command.param2	= (long) linearBuffer;
			
			SndDoCommand(	gJavaSoundChannel,
							&command,
							true	);
		}
		else
		{
			ReleaseJavaSoundBuffer(linearBuffer);	// BAIL !
			return;
		}
			
		// moving on....
		len		-= linearBlockLen;	
		data	+= ulawBlockLen;
	}

}

//
// GetJavaSoundBuffer
//
// Returns an unused sound buffer, either from the buffer
// list, or newly allocated and added to the list.
//
inline static JavaSoundBuffer *
GetJavaSoundBuffer(void)
{
	JavaSoundBuffer	*curBuffer;

	//
	// It would be really nice to have a free-list for the buffers,
	// but since we're notified that a buffer is free at interrupt time,
	// we can't go manipulating the list at that point (unless we were to
	// turn interrupts off)
	//	
	
	for (curBuffer = gJavaSoundBufferList; curBuffer != NULL; curBuffer = curBuffer->next)
	{
		if (curBuffer->inUse == 0)
		{
			curBuffer->inUse = 1;
			return curBuffer;
		}
	}

	
	// Must allocate a new buffer
	//
	// Set up the sound header, mark the buffer
	// as used, and add it to the buffer list.
	//
	curBuffer = malloc(sizeof(JavaSoundBuffer));
	if (curBuffer != NULL)
	{
		SetupJavaSoundHeader(&(curBuffer->header));
		curBuffer->inUse 	= 1;
		curBuffer->next		= gJavaSoundBufferList;
		gJavaSoundBufferList= curBuffer;

#ifdef DEBUG
		gJavaSoundBufferCount++;
		
		if (gJavaSoundBufferCount > gMaxJavaSoundBufferCount)
			gMaxJavaSoundBufferCount = gJavaSoundBufferCount;
#endif		
	}
	
	return curBuffer;
}



//
// ReleaseJavaSoundBuffer
//
// Mark a buffer as unused
// 
inline static void
ReleaseJavaSoundBuffer(JavaSoundBuffer *buffer)
{
	buffer->inUse = 0;
}

//
// FreeUnusedJavaSoundBuffers
//
// Free all unused buffers
//
static void
FreeUnusedJavaSoundBuffers(void)
{
	JavaSoundBuffer	*curBuffer, *prevBuffer = NULL;
	
	for (curBuffer = gJavaSoundBufferList; curBuffer != NULL;)
	{
		JavaSoundBuffer	*nextBuffer = curBuffer->next;
		
		// If the buffer isn't being used, get rid of it
		if (curBuffer->inUse == 0)
		{
			if (prevBuffer != NULL)
				prevBuffer->next = curBuffer->next;
			else
				gJavaSoundBufferList = curBuffer->next;
							
			free(curBuffer);	
		
		#ifdef DEBUG
			gJavaSoundBufferCount--;
		#endif
			
		}
		else
			// don't bump prevBuffer if we just freed curBuffer
			prevBuffer = curBuffer;
			
		curBuffer = nextBuffer;
	}
	
}


//
// SetupJavaSoundHeader
//
// Setup the static values of the sound header
//
static void
SetupJavaSoundHeader(ExtSoundHeader	*	header)
						
{
	long double		aiffRate;
	
	memset(header, 0, sizeof(*header));
//	header->samplePtr 		= NULL;							// sample will be embedded at end of header
	header->numChannels		= 1;
	header->sampleRate		= kJavaSoundSampleRateFixed;		
//	header->loopStart		= 0;					
//	header->loopEnd			= 0;					
	header->encode			= extSH;
	header->baseFrequency	= 127;	
//	header->numFrames		= 0;
//	header->markerChunk		= NULL;
//	header->instrumentChunks= NULL;
//	header->AESRecording	= NULL;
	header->sampleSize		= 16;
	
	aiffRate = 8000.0;
#if GENERATINGPOWERPC
	ldtox80(&aiffRate, &(header->AIFFSampleRate));
#endif
}


//
// ConvertBufferToLinear
//
// Convert a buffer of ulaw data to linear data.
// The given length is the length in bytes of the ulaw data.
//
// Returns false if the sample is silence.
//
static Boolean
ConvertBufferToLinear(	char	*	ulawSample,
						short	*	linearSample,
						UInt32		ulawLen		)
{
	int		i;
	Boolean	silent = true;
	
	for (i=0; i<ulawLen; i++, linearSample++, ulawSample++)
	{
		*linearSample = ULAW_ToMacLinear(*ulawSample);
		silent = (silent && *ulawSample == 0x7F);
	}
	
	return !silent;
}			



//
// ULAW_ToMacLinear
//
// Convert sample from 8-bit ULAW encoding to 16-bit linear
//
static inline short
ULAW_ToMacLinear(int datum)
{
    int sign, exponent, mantissa;

	static const int exp_lut[8] =
	{ 0, 132, 396, 924, 1980, 4092, 8316, 16764 };
   
    datum		= ~datum;
    sign 		= datum >> 7;
    exponent 	= (datum >> 4) & 0x07;
    mantissa 	= datum & 0x0f;
    datum 		= exp_lut[exponent] + (mantissa << (exponent + 3));

    return (sign != 0 ? -datum : datum);
}