/*
$Id: audio_voxware.c,v 1.24 1999/11/17 13:16:49 daeron Exp $
*/

#ifdef HAVE_SYS_SOUNDCARD_H
#  include <sys/soundcard.h>
#else
#  ifdef HAVE_SOUNDCARD_H
#    include <soundcard.h>
#  else
#    include <machine/soundcard.h>
#  endif
#endif

Bool	NO_AFMT_S8 = False;

static int openAudioDevice(char *audiodev, int mode);
static int closeAudioDevice(int audiofd);
static int resetAudioDevice(int audiofd);
static int initAudioDevice(int audiofd, SAudioFileInfo *afInfo);

static int initWAVE(int audiofd, SAudioFileInfo *afInfo);
static int initAIFF(int audiofd, SAudioFileInfo *afInfo);
static int initAUSND(int audiofd, SAudioFileInfo *afInfo);

static int initAFMT_U8(int audiofd, SAudioFileInfo *afInfo);
static int initAFMT_S8(int audiofd, SAudioFileInfo *afInfo);
static int initAFMT_S16_LE(int audiofd, SAudioFileInfo *afInfo);
static int initAFMT_S16_BE(int audiofd, SAudioFileInfo *afInfo);

static int initStereo(int audiofd, SAudioFileInfo *afInfo);
static int initSampleRate(int audiofd, SAudioFileInfo *afInfo);

static int writeAudioData(int audiofd, SAudioFileInfo *afInfo);
static int write8bitAudioData(int audiofd, SAudioFileInfo *afInfo);
static int write16bitAudioData(int audiofd, SAudioFileInfo *afInfo);



int
SPerformAudio(SAudioFileInfo *afInfo)
{
	int             audiofd;

	/* Open audio device */
	audiofd = openAudioDevice(SGetStringForKey(S_DEVICE), O_WRONLY);
	if (audiofd == -1)
		return -1;
	
	/* Play with audio device */
	if (initAudioDevice(audiofd, afInfo) == -1) {
		closeAudioDevice(audiofd);
		SDestroyAudioFileInfo(afInfo);
		return -1;
	}
	
	/* Clean up */
	closeAudioDevice(audiofd);
	SDestroyAudioFileInfo(afInfo);

	return 0;
}

/* Open audio device */
static int
openAudioDevice(char *audiodev, int mode)
{
	int	audiofd;
	
	assert(audiodev);

	audiofd = open(audiodev, mode, 0);

	if (audiofd == -1) {
		SErrorCode = SERR_DEVOPEN;
		return -1;
	}

#ifdef DEBUG
	else
		fprintf(stderr, "-=> audio device opened\n");
#endif
	return audiofd;
}

/* Close audio device */
static int
closeAudioDevice(int audiofd)
{
	assert(audiofd > 0);
	
	if (close(audiofd) == -1) {
		SErrorCode = SERR_DEVCLOSE;
		return -1;
	}
#ifdef DEBUG
	else
		fprintf(stderr, "<=- audio device closed\n");
#endif
	return 0;
}

/* Reset audio device */
static int
resetAudioDevice(int audiofd)
{
	assert(audiofd > 0);

	if (ioctl(audiofd, SNDCTL_DSP_RESET) == -1) {
		perror("SNDCTL_DSP_RESET");
		SErrorCode = SERR_DEVRESET;
		return -1;
	}

#ifdef DEBUG
	fprintf(stderr, " >> audio device resetted\n");
#endif
	return 0;
}

/* Initialize audio device for sound playback */
static int
initAudioDevice(int audiofd, SAudioFileInfo *afInfo)
{
#ifdef DEBUG
	fprintf(stderr, " >> initializing audio device\n");
#endif

	if (resetAudioDevice(audiofd) == -1) {
		return -1;
	}
	
	switch (afInfo->FileFormat) {
		case AF_FILE_AIFF:
			return initAIFF(audiofd, afInfo);
		case AF_FILE_WAVE:
			return initWAVE(audiofd, afInfo);
		case AF_FILE_NEXTSND:
			return initAUSND(audiofd, afInfo);
		default:
			SErrorCode = SERR_BADFORMAT;
			return -1;
	}
}

/* ---------------------------------------------- */

static int
initWAVE(int audiofd, SAudioFileInfo *afInfo)
{
	int	endianness;

	endianness = SGetEndianness();

	if (afInfo->SampleWidth == 8)
		return (initAFMT_U8(audiofd, afInfo));

	if ((afInfo->SampleWidth == 16) && (endianness == LITTLEENDIAN))
		return (initAFMT_S16_LE(audiofd, afInfo));
	
	if ((afInfo->SampleWidth == 16) && (endianness == BIGENDIAN))
		return (initAFMT_S16_BE(audiofd, afInfo));
	else {
		SErrorCode = SERR_BADFORMAT;
		return -1;
	}
}

static int
initAIFF(int audiofd, SAudioFileInfo *afInfo)
{
	int     endianness;

	endianness = SGetEndianness();
	
	if (afInfo->SampleWidth == 8) {
		return (initAFMT_U8(audiofd, afInfo));
	}
	
	if ((afInfo->SampleWidth == 16) && (endianness == LITTLEENDIAN))
		return (initAFMT_S16_LE(audiofd, afInfo));
	
	if ((afInfo->SampleWidth == 16) && (endianness == BIGENDIAN))
		return (initAFMT_S16_BE(audiofd, afInfo));
	else {
		SErrorCode = SERR_BADFORMAT;
		return -1;
	}
}

static int
initAUSND(int audiofd, SAudioFileInfo *afInfo)
{
	int     endianness;
	
	endianness = SGetEndianness();
	
	if (afInfo->SampleWidth == 8) {
		return (initAFMT_U8(audiofd, afInfo));
	}
	
	if ((afInfo->SampleWidth == 16) && (endianness == LITTLEENDIAN))
		return (initAFMT_S16_LE(audiofd, afInfo));
	
	if ((afInfo->SampleWidth == 16) && (endianness == BIGENDIAN))
		return (initAFMT_S16_BE(audiofd, afInfo));
	else {
		SErrorCode = SERR_BADFORMAT;
		return -1;
	}
}



static int
initAFMT_U8(int audiofd, SAudioFileInfo *afInfo)
{
	int	format;

	format = AFMT_U8;
	if (ioctl(audiofd, SNDCTL_DSP_SETFMT, &format) == -1) {
		perror("SNDCTL_DSP_SETFMT");
		SErrorCode = SERR_BADFORMAT;
		return -1;
	}
	
	if (format != AFMT_U8) {
		SErrorCode = SERR_DEVSUPPORT;
		return -1;
	}

#ifdef DEBUG
	fprintf(stderr, " >> SNDCTL_DSP_SETFMT  AFMT_U8\n");
#endif

	if ((initStereo(audiofd, afInfo) == -1) || (initSampleRate(audiofd, afInfo) == -1))
		return -1;
	else
		return writeAudioData(audiofd, afInfo);
}



static int
initAFMT_S8(int audiofd, SAudioFileInfo *afInfo)
{
	int	format;

	format = AFMT_S8;
	if (ioctl(audiofd, SNDCTL_DSP_SETFMT, &format) == -1) {
		perror("SNDCTL_DSP_SETFMT");
		SErrorCode = SERR_BADFORMAT;
		return -1;
	}
	
	if (format != AFMT_S8) {
		NO_AFMT_S8 = True;

		if (format != AFMT_U8) {
			SErrorCode = SERR_DEVSUPPORT;
			return -1;
		}
	}

#ifdef DEBUG
	if (format == AFMT_S8)
		fprintf(stderr, " >> SNDCTL_DSP_SETFMT  AFMT_S8\n");
	else
		fprintf(stderr, " >> SNDCTL_DSP_SETFMT  AFMT_S8 unsupported ... using AFMT_U8\n");
#endif

	if ((initStereo(audiofd, afInfo) == -1) || (initSampleRate(audiofd, afInfo) == -1))
		return -1;
	else
		return writeAudioData(audiofd, afInfo);
}



static int
initAFMT_S16_LE(int audiofd, SAudioFileInfo *afInfo)
{
	int	format;

	format = AFMT_S16_LE;
	if (ioctl(audiofd, SNDCTL_DSP_SETFMT, &format) == -1) {
		perror("SNDCTL_DSP_SETFMT");
		SErrorCode = SERR_BADFORMAT;
		return -1;
	}
	if (format != AFMT_S16_LE) {
		SErrorCode = SERR_DEVSUPPORT;
		return -1;
	}

#ifdef DEBUG
	fprintf(stderr, " >> SNDCTL_DSP_SETFMT  AFMT_S16_LE\n");
#endif

	if ((initStereo(audiofd, afInfo) == -1) || (initSampleRate(audiofd, afInfo) == -1))
		return -1;
	else
		return writeAudioData(audiofd, afInfo);
}



static int
initAFMT_S16_BE(int audiofd, SAudioFileInfo *afInfo)
{
	int	format;

	format = AFMT_S16_BE;
	if (ioctl(audiofd, SNDCTL_DSP_SETFMT, &format) == -1) {
		perror("SNDCTL_DSP_SETFMT");
		SErrorCode = SERR_BADFORMAT;
		return -1;
	}
	if (format != AFMT_S16_BE) {
		SErrorCode = SERR_DEVSUPPORT;
		return -1;
	}

#ifdef DEBUG
	fprintf(stderr, " >> SNDCTL_DSP_SETFMT  AFMT_S16_BE\n");
#endif

	if ((initStereo(audiofd, afInfo) == -1) || (initSampleRate(audiofd, afInfo) == -1))
		return -1;
	else
		return writeAudioData(audiofd, afInfo);
}



static int
initStereo(int audiofd, SAudioFileInfo *afInfo)
{
	int stereo = afInfo->Channels - 1;	/* 0 == mono   == 1 channel
						 * 1 == stereo == 2 channels
						 */

	if (ioctl(audiofd, SNDCTL_DSP_STEREO, &stereo) == -1) {
		perror("SNDCTL_DSP_STEREO");
		SErrorCode = SERR_DEVSTEREO;
		return -1;
	}
	if (stereo != (afInfo->Channels - 1)) {
		SErrorCode = SERR_DEVSTEREO;
		return -1;
	}

#ifdef DEBUG
	if (afInfo->Channels == 2)
		fprintf(stderr, " >> SNDCTL_DSP_STEREO  stereo\n");
	else
		fprintf(stderr, " >> SNDCTL_DSP_STEREO  mono\n");
#endif

	return 0;
}



static int
initSampleRate(int audiofd, SAudioFileInfo *afInfo)
{
	int	speed = (int)afInfo->SampleRate;

	if (ioctl(audiofd, SNDCTL_DSP_SPEED, &speed) == -1) {
		perror("SNDCTL_DSP_SPEED");
		SErrorCode = SERR_DEVSPEED;
		return -1;
	}

	if (0) { /* TODO: check for extreme difference between returned and requested speeds */
		SErrorCode = SERR_DEVSPEED;
		return -1;
	}

#ifdef DEBUG
	fprintf(stderr, " >> SNDCTL_DSP_SPEED   %d Hz\n", speed);
#endif

	return 0;
}


static int
writeAudioData(int audiofd, SAudioFileInfo *afInfo)
{
	if (afInfo->SampleWidth == 8)
		return (write8bitAudioData(audiofd, afInfo));
	else
		return (write16bitAudioData(audiofd, afInfo));
}

static int
write8bitAudioData(int audiofd, SAudioFileInfo *afInfo)
{
	u_int8_t	*buffer;		/* audio buffer */
	long		curFrame;		/* current framecount */
	long		blkFrames;		/* number of frames in current audio block */
	int		blockSize = 8192;	/* Size of an audio block buffer in frames */
	int		i;

#ifdef DEBUG
	fprintf(stderr, " >> writing data\n");
#endif

	buffer = (u_int8_t *) malloc( blockSize * (afInfo->SampleWidth/8) * afInfo->Channels * sizeof(u_int8_t));
	if (!buffer) {
		SErrorCode = SERR_NOMEMORY;
		return -1;
	}
	
	curFrame = 0;
	while (curFrame < afInfo->FrameCount) {
		if ((blkFrames = (afInfo->FrameCount - curFrame)) > blockSize) {
			blkFrames = blockSize;
		}

		if (afReadFrames(afInfo->FileHandle, AF_DEFAULT_TRACK, buffer, blkFrames) < 1) {
#ifdef DEBUG
			fprintf(stderr, " >> frames written     %ld\n", curFrame);
#endif
			free(buffer);
			SErrorCode = SERR_READ;
			return -1;
		}
		
		/* XXX LibAudioFile internally translates everything to signed
		   integer if your audio hardware doesn't support that
		   (like my GUS MAX) we need to convert it back to unsigned

		   UPDATE: Currently this is not the case and NO_AFMT_S8 will never
		   be set. Leaving the code in here in case things change again. */
		if (NO_AFMT_S8) {
			for (i = 0; i < (blkFrames*afInfo->Channels); i++)
				buffer[i] ^= 0x80;
		}

/*TODO:
 *	Make the volume a soundeffect
 *	Change sound into struct with:
 *		- sound file to play
 *		- effects to apply
 *		- number of effects to apply
 */

		/* Adjust Volume */
		if ((SGetVolume() < 1.0) /*TODO || (sound->effectCount > 0) */ )	{
			for (i = 0; i < (blkFrames*afInfo->Channels); i++) {
				int j;
				int x;

				/* The format for 8-bit unsigned audio is
				 * different from 2-complement !!! 
				 */
				x = buffer[i] - 128;
				x *= SGetVolume();

				/* TODO (for sound effects)
				 *
				 * Functions needed available:
				 * 	- SOpenStream		-> Open an audio stream that will be mixed later on.
				 *	- SGetSampleRate	-> Sample rate is required for timing.
				 *	- SCreatePool		-> This way it is clear that the memory 'pool' needs mallocing.
				 *	- SDestroyPool		-> ...and freeing.
				 *
				 
				for (j=0; j <= sound->effectCount; j++) {
					(SEffect8*)(sound->effects[j]->effect->e_8bit(buffer, i, sound->effects[j]->effectParameters), sound->effects[j]->pool);
				}
				
				*/
				
				buffer[i] = x + 128.5;

			}
		}

		if (write(audiofd, buffer, blkFrames * (afInfo->SampleWidth/8) * afInfo->Channels) == -1) {
			free(buffer);
			SErrorCode = SERR_DEVWRITE;
			return -1;
		}
		curFrame += blkFrames;
	}
#ifdef DEBUG
	fprintf(stderr, " >> frames written     %ld\n", curFrame);
#endif

	free(buffer);
	return 0;
}

static int
write16bitAudioData(int audiofd, SAudioFileInfo *afInfo)
{
	u_int16_t	*buffer;		/* audio buffer */
	long		curFrame;		/* current framecount */
	long		blkFrames;		/* number of frames in current audio block */
	int		blockSize = 4096;	/* Size of an audio block buffer in frames */
	int		i;

#ifdef DEBUG
	fprintf(stderr, " >> writing data\n");
#endif

	buffer = (u_int16_t *) malloc( blockSize * (afInfo->SampleWidth/8) * afInfo->Channels * sizeof(u_int16_t));
	if (!buffer) {
		SErrorCode = SERR_NOMEMORY;
		return -1;
	}
	
	curFrame = 0;
	while (curFrame < afInfo->FrameCount) {
		if ((blkFrames = (afInfo->FrameCount - curFrame)) > blockSize) {
			blkFrames = blockSize;
		}

		if (afReadFrames(afInfo->FileHandle, AF_DEFAULT_TRACK, buffer, blkFrames) < 1) {
#ifdef DEBUG
			fprintf(stderr, " >> frames written     %ld\n", curFrame);
#endif
			free(buffer);
			SErrorCode = SERR_READ;
			return -1;
		}

		/* Adjust Volume */
		if (SGetVolume() < 1.0) {
			for (i = 0; i < (blkFrames*afInfo->Channels); i++) {
				buffer[i] = (int16_t)buffer[i] * SGetVolume() + 0.5;
			}
		}

		if (write(audiofd, buffer, blkFrames * (afInfo->SampleWidth/8) * afInfo->Channels) == -1) {
			free(buffer);
			SErrorCode = SERR_DEVWRITE;
			return -1;
		}
		curFrame += blkFrames;
	}
#ifdef DEBUG
	fprintf(stderr, " >> frames written     %ld\n", curFrame);
#endif

	free(buffer);
	return 0;
}

