/* this file is a part of amp software

   buffer.c: written by Andrew Richards  <A.Richards@phys.canterbury.ac.nz>
             (except printout())

   Last modified by:
   Karl Anders Oygard added flushing of audio buffers, 13 May 1997
   Tinic Urou BeOS code and linear resampling for 44100Hz replay.
*/
#include "amp.h"
#include "transform.h"

#include <sys/types.h>
#ifdef HAVE_MLOCK
#ifdef OS_Linux
#include <sys/mman.h>
#endif
#endif
#ifdef HAVE_SYS_SELECT_H
#include <sys/select.h>
#endif
#include <sys/time.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "audioIO.h"
#include "audio.h"

#ifndef __BEOS__

struct ringBuffer {		/* A ring buffer to store the data in */
	char *bufferPtr;	/* buffer pointer */
	int inPos, outPos;	/* positions for reading and writing */
};

static int buffer_fd;
static int control_fd;

/* little endian systems do not require any byte swapping whatsoever. 
 * big endian systems require byte swapping when writing .wav files, 
 * but not when playing directly
 */

/* This is a separate (but inlined) function to make it easier to implement */
/* a threaded buffer */

inline void
audioBufferWrite(char *buf,int bytes)
{
   write(buffer_fd, buf, bytes);
}
#endif

#ifdef __POWERPC__
static asm void c256(register double *dst, register double *src)
{
	// Tinic: I love PowerPC 8-)

	lfd		fp0, 0(src)
	lfd		fp1, 8(src)
	lfd		fp2, 16(src)
	lfd		fp3, 24(src)
	lfd		fp4, 32(src)
	lfd		fp5, 40(src)
	lfd		fp6, 48(src)
	lfd		fp7, 56(src)
	lfd		fp8, 64(src)
	lfd		fp9, 72(src)
	lfd		fp10,80(src)
	lfd		fp11,88(src)
	lfd		fp12,96(src)
	lfd		fp13,104(src)
	lfd		fp14,112(src)
	lfd		fp15,120(src)	

	stfd	fp0, 0(dst)
	stfd	fp1, 8(dst)
	stfd	fp2, 16(dst)
	stfd	fp3, 24(dst)
	stfd	fp4, 32(dst)
	stfd	fp5, 40(dst)
	stfd	fp6, 48(dst)
	stfd	fp7, 56(dst)
	stfd	fp8, 64(dst)
	stfd	fp9, 72(dst)
	stfd	fp10,80(dst)
	stfd	fp11,88(dst)
	stfd	fp12,96(dst)
	stfd	fp13,104(dst)
	stfd	fp14,112(dst)
	stfd	fp15,120(dst)

	lfd		fp0,128(src)
	lfd		fp1,136(src)
	lfd		fp2,144(src)
	lfd		fp3,152(src)
	lfd		fp4,160(src)
	lfd		fp5,168(src)
	lfd		fp6,176(src)
	lfd		fp7,184(src)
	lfd		fp8,192(src)
	lfd		fp9,200(src)
	lfd		fp10,208(src)
	lfd		fp11,216(src)
	lfd		fp12,224(src)
	lfd		fp13,232(src)
	lfd		fp14,240(src)
	lfd		fp15,248(src)

	stfd	fp0,128(dst)
	stfd	fp1,136(dst)
	stfd	fp2,144(dst)
	stfd	fp3,152(dst)
	stfd	fp4,160(dst)
	stfd	fp5,168(dst)
	stfd	fp6,176(dst)
	stfd	fp7,184(dst)
	stfd	fp8,192(dst)
	stfd	fp9,200(dst)
	stfd	fp10,208(dst)
	stfd	fp11,216(dst)
	stfd	fp12,224(dst)
	stfd	fp13,232(dst)
	stfd	fp14,240(dst)
	stfd	fp15,248(dst)

	blr
}

// Resamplingtable for 44100Hz replay, rounded values
static const uint32 resample_adder[2][3] = 
{
	{ 32768 /* 22050 */, 35666 /* 24000 */, 23777 /* 16000 */},
	{ 65536 /* 44100 */, 71332 /* 48000 */, 47554 /* 32000 */},
};

#endif

void printout(struct AUDIO_HEADER *header)
{
#ifdef __BEOS__

	extern char *au_buf;
	extern int32 au_siz;
	extern sem_id ok_to_read;
	extern sem_id ok_to_write;

	static int32 ptr=0;
	short *dst=(short *)au_buf;
	short *src=(short *)sample_buffer;
	uint32 ssi=au_siz/sizeof(short);

	// Resampling needed?
	if(((header->ID!=1) || (header->sampling_frequency!=0)))
	{
		static uint32 offset=0;
		uint32 mover=offset;
		uint32 adder=resample_adder[header->ID][header->sampling_frequency];
		uint32 realo;

		if(nch!=1)
		{			
			for(;(mover>>16)<(64*9);mover+=adder)
			{		
				if(dst==0 || ptr==0)
				{
					if(acquire_sem(ok_to_write)!=B_NO_ERROR) return;
					ssi=au_siz/sizeof(short);
					dst=(short *)au_buf;
				}

				realo=((mover>>15)&0xFFFFE);
				
				dst[ptr++]=src[realo+0];
				dst[ptr++]=src[realo+1]; 
				
				if (ptr>=ssi) 
				{
					ptr=0;
					release_sem(ok_to_read);
				}
			}		
			offset=mover-((64*9)<<16); // for quality assurance
		}
		else
		{
			int k=0;
			for(;(mover>>16)<(128*9);mover+=adder)
			{		
				if(k>=64)k=0;
				if(k<32) 
				{
					if(dst==0 || ptr==0)
					{
						if(acquire_sem(ok_to_write)!=B_NO_ERROR) return;
						ssi=au_siz/sizeof(short);
						dst=(short *)au_buf;
					}

					realo=mover>>16;
					dst[ptr++]=src[realo];
					dst[ptr++]=src[realo]; 

					if (ptr>=ssi) 
					{
						ptr=0;
						release_sem(ok_to_read);
					}
				}
				k++;		
			}	
			offset=mover-((128*9)<<16); 
		}
	}
	else
	{
		// This will be naturally faster ;-)
		if(nch!=1)
		{
			for(int c=0;c<9;c++)
			{
				if(dst==0 || ptr==0)
				{
					if(acquire_sem(ok_to_write)!=B_NO_ERROR) return;
					ssi=au_siz/sizeof(short);
					dst=(short *)au_buf;
				}

				c256((double *)&dst[ptr],(double *)src);
				ptr+=128;
				src+=128;
			
				if (ptr>=ssi) 
				{
					ptr=0;
					release_sem(ok_to_read);
				}
			}
		}
		else
		{
			short dat;
			for(int c=0;c<9;c++)
			{
				if(dst==0 || ptr==0)
				{
					if(acquire_sem(ok_to_write)!=B_NO_ERROR) return;
					ssi=au_siz/sizeof(short);
					dst=(short *)au_buf;
				}			

				for(int k=0;k<32;k++)
				{
					dat=src[k];
					dst[ptr++]=dat;
					dst[ptr++]=dat;
				}
				src+=64;
				for(int k=0;k<32;k++)
				{
					dat=src[k];
					dst[ptr++]=dat;
					dst[ptr++]=dat;
				}
				src+=64;
			
				if (ptr>=ssi) 
				{
					ptr=0;
					release_sem(ok_to_read);
				}
			}
		}
	}
#else
int j;

        if (nch==2)
                j=32 * 18 * 2;
        else
                j=32 * 18;

        if (A_WRITE_TO_FILE) {
#ifndef NO_BYTE_SWAPPING
        short *ptr = (short*) sample_buffer;
	int i = j;

                for (;i>=0;--i)
                        ptr[i] = ptr[i] << 8 | ptr[i] >> 8;
#endif

                fwrite(sample_buffer, sizeof(short), j, out_file);
        }

        if (A_AUDIO_PLAY) {
#ifdef LINUX_REALTIME
                rt_printout((short*) sample_buffer, j * sizeof(short));
#else /* LINUX_REALTIME */
                if (AUDIO_BUFFER_SIZE==0)
                        audioWrite((char*)sample_buffer, j * sizeof(short));
                else
                        audioBufferWrite((char*)sample_buffer, j * sizeof(short));
#endif /* LINUX_REALTIME */
        }
#endif
}

int AUDIO_BUFFER_SIZE;

#ifndef __BEOS__
#define bufferSize(A) (((A)->inPos+AUDIO_BUFFER_SIZE-(A)->outPos) % AUDIO_BUFFER_SIZE)
#define bufferFree(A) (AUDIO_BUFFER_SIZE-1-bufferSize(A))

void 
initBuffer(struct ringBuffer *buffer)
{
	buffer->bufferPtr=(char *)malloc(AUDIO_BUFFER_SIZE);
	if (buffer->bufferPtr==NULL) {
		die("Unable to allocate write buffer\n");
	}
#ifdef HAVE_MLOCK
	mlock(buffer->bufferPtr,AUDIO_BUFFER_SIZE);
#endif
	buffer->inPos = 0;
	buffer->outPos = 0;
	DB(buffer2, msg("Allocated %d bytes for audio buffer\n",AUDIO_BUFFER_SIZE) );
}

void
freeBuffer(struct ringBuffer *buffer)
{
#ifdef HAVE_MLOCK
  munlock(buffer->bufferPtr,AUDIO_BUFFER_SIZE);
#endif
	free(buffer->bufferPtr);
}

/* This just sends some bogus data on the control pipe, which will cause */
/* the reader to flush its buffer. */

void
audioBufferFlush()
{
	if (AUDIO_BUFFER_SIZE!=0) {
		int dummy;

		/* We could use the control pipe for passing commands to the */
		/* audio player process, but so far we haven't bothered. */

        	write(control_fd, &dummy, sizeof dummy);
	} else
		audioFlush();
}
	
/* The decoded data are stored in a the ring buffer until they can be sent */
/* to the audio device. Variables are named in relation to the buffer */
/* ie writes are writes from the codec to the buffer and reads are reads */
/* from the buffer to the audio device */

int
audioBufferOpen(int frequency, int stereo, int volume)
{
	struct ringBuffer audioBuffer;
	
	int inFd,outFd,ctlFd,cnt,cntr=0,pid;
	int inputFinished=FALSE;
	int percentFull;
	fd_set inFdSet,outFdSet;
	fd_set *outFdPtr; 
	struct timeval timeout;
	int filedes[2];
	int controldes[2];
	
	DB(buffer, msg("Starting audio buffer\n") );
	
	if (pipe(filedes) || pipe(controldes)) {
		perror("pipe");
		exit(-1);
	}
	DB(buffer, msg("readpipe=%d writepipe=%d\n",filedes[0],filedes[1]) );
	if ((pid=fork())!=0) {  /* if we are the parent */
		control_fd=controldes[1];
		close(filedes[0]);
		buffer_fd=filedes[1];
		close(controldes[0]);
		return(pid);	        /* return the pid */
	}
	
	DB(buffer, msg("Audio buffer (pipe) starting\n") );
	
	/* we are the child */
	close(filedes[1]);
	inFd=filedes[0];
	close(controldes[1]);
	ctlFd=controldes[0];
	audioOpen(frequency,stereo,volume);
	outFd=getAudioFd();

	initBuffer(&audioBuffer);
	
	while(1) {
		timeout.tv_sec=0;
		timeout.tv_usec=0;
		FD_ZERO(&inFdSet);
		FD_ZERO(&outFdSet);
		FD_SET(ctlFd,&inFdSet);
		FD_SET(outFd,&outFdSet);
		
		DB(buffer,
			 msg("BS=%d inPos=%d outPos=%d bufferSize=%d bufferFree=%d\n",
					 AUDIO_BUFFER_SIZE,audioBuffer.inPos,audioBuffer.outPos,
					 bufferSize(&audioBuffer),bufferFree(&audioBuffer)) );
		
		if (bufferSize(&audioBuffer)<AUSIZ) {					/* is the buffer too empty */
			outFdPtr=NULL;															/* yes, don't try to write */
			if (inputFinished)					/* no more input, buffer exhausted -> exit */
				break;
		} else
			outFdPtr=&outFdSet;															/* no, select on write */
		
		/* check we have at least AUSIZ bytes left (don't want <1k bits) */
		if ((bufferFree(&audioBuffer)>=AUSIZ) && !inputFinished)
			FD_SET(inFd,&inFdSet);
		
/* The following selects() are basically all that is left of the system
   dependent code outside the audioIO_*.c files. These selects really
   need to be moved into the audioIO_*.c files and replaced with a
   function like audioIOReady(inFd, &checkIn, &checkAudio, wait) where
   it checks the status of the input or audio output if checkIn or
   checkAudio are set and returns with checkIn or checkAudio set to TRUE
   or FALSE depending on whether or not data is available. If wait is
   FALSE the function should return immediately, if wait is TRUE the
   process should BLOCK until the required condition is met. NB: The
   process MUST relinquish the CPU during this check or it will gobble
   up all the available CPU which sort of defeats the purpose of the
   buffer.

   This is tricky for people who don't have file descriptors (and
   select) to do the job. In that case a buffer implemented using
   threads should work. The way things are set up now a threaded version
   shouldn't be to hard to implement. When I get some time... */

		/* check if we can read or write */
		if (select(MAX3(inFd,outFd,ctlFd)+1,&inFdSet,outFdPtr,NULL,NULL) > -1) {
			if (outFdPtr && FD_ISSET(outFd,outFdPtr)) {							/* need to write */
				int bytesToEnd = AUDIO_BUFFER_SIZE - audioBuffer.outPos;

				percentFull=100*bufferSize(&audioBuffer)/AUDIO_BUFFER_SIZE;
#if defined(DEBUG)
				if ((cntr++ % (16/(AUSIZ/4096)))==0) msg("\rBuffer (%2d%%) %6d",percentFull,bufferSize(&audioBuffer));
#endif
				if (AUSIZ>bytesToEnd) {
					cnt = audioWrite(audioBuffer.bufferPtr + audioBuffer.outPos, bytesToEnd);
					cnt += audioWrite(audioBuffer.bufferPtr, AUSIZ - bytesToEnd);
					audioBuffer.outPos = AUSIZ - bytesToEnd;
				} else {
					cnt = audioWrite(audioBuffer.bufferPtr + audioBuffer.outPos, AUSIZ);
					audioBuffer.outPos += AUSIZ;
				}

				DB(buffer2, msg("buffer: wrote %d bytes (to audio)\n",cnt) );
			}
			if (FD_ISSET(inFd,&inFdSet)) {								 /* need to read */
			        cnt = read(inFd, audioBuffer.bufferPtr + audioBuffer.inPos, MIN(AUSIZ, AUDIO_BUFFER_SIZE - audioBuffer.inPos));
				if (cnt >= 0) {
					audioBuffer.inPos = (audioBuffer.inPos + cnt) % AUDIO_BUFFER_SIZE;

					DB(buffer2, msg("buffer: read %d bytes\n",cnt) );

					if (cnt==0) {
						inputFinished=TRUE;
						DB(buffer, msg("inputFinished\n") );
					}
				} else {
				        perror("read");
					exit(-1);
				}
			}
			if (FD_ISSET(ctlFd,&inFdSet)) {
				int dummy;

			        cnt = read(ctlFd, &dummy, sizeof dummy);
				if (cnt >= 0) {
					audioBuffer.inPos = audioBuffer.outPos = 0;
					audioFlush();
				} else {
				        perror("read");
					exit(-1);
				}
			}
		} else {
			perror("select");
			exit(-1);
		}
	}
	close(inFd);
	DB(buffer, msg("Buffer pipe closed %d\n",inFd); )
	audioClose();
	exit(0);
	return 0; /* just to get rid of warnings */
}

void
audioBufferClose()
{
	/* Close the buffer pipe and wait for the buffer process to close */
	close(buffer_fd);
	waitpid(0,0,0);
}
#endif
