/* SoundIt library 0.021

   Copyright 1994 Brad Pitzel  pitzel@cs.sfu.ca

   Feel free to use/distribute/modify as long as proper credits
   are included.
*/

#include "soundIt.h"
#include <malloc.h>
#include <limits.h>	/* PATH_MAX */
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <linux/soundcard.h>
#include <sys/ioctl.h>
#include <sys/wait.h>

/*==========================================================================*/
/* the mix buff, where the channels are mixed into. The mix buffer is then
   dumped to the sound device (/dev/dsp). Samples are mixed in
   Vunclipbuf (buffer of ints), then the values in Vunclipbuf are clipped to
   values between 0 and 255, and stored into Vclippedbuf (buffer of unsigned
   chars).
*/

struct Mix
    {
    unsigned char *Vclippedbuf;	
    int *Vunclipbuf;
    int Vsize;
    };
typedef struct Mix Mix;

/*==========================================================================*/
struct Channel
    {
    unsigned char *Vstart,*Vcurrent;	/* ptr's into a playing sample */
    int	Vlen;				/* length of sample in bytes */
    int	Vleft;				/* bytes left of sample to play */
    };
typedef struct Channel Channel;

/*==========================================================================*/

/* variables prefixed with S_ are static */
/* 0 if mixer isn't initialized or init failed, 1 if mixer is good */
static int sampleMixerStatus = 0;
	
static const Sample 	*S_sounds = NULL; /* ptr to array of samples */
static int S_num_sounds = 0;		/* size of 'sounds' array above */
static int S_fd_snddev = -1;		/* file # for sound device once open */
static int S_fd_pipe[2] = { -1, -1 };	/* pipe to talk to child process */
static int S_son_pid = -1;		/* process ID for the forked sound mixer */
static const char *S_snddev = NULL;	/* char string for device, ie "/dev/dsp" */
static int S_num_channels = 6;		/* number of channels to mix */
static int S_playback_freq = 0;		/* playback frequency (in Hz) */
/*==========================================================================*/
/* non-public functions, used only within this file*/

int Snd_init_dev();
int Snd_restore_dev();

void Chan_reset( Channel *chan );	/* init channel structure */

	/* start a sample playing on a channel */
void Chan_assign( Channel *chan, const Sample *snd );

	/* mix all channels together into the 'mix' structure */
int  Chan_mixAll( Mix *mix, Channel *ch );

	/* used by Chan_mixAll to mix the 1st channel */
int  Chan_copyIn( Channel *chan, Mix *mix );

	/* used by Chan_mixAll to mix the middle channels */
int  Chan_mixIn( Channel *chan, Mix *mix );

	/* used by Chan_mixAll to mix the last channel */
int  Chan_finalMixIn( Channel *chan, Mix *mix );


/* alloc mem for mix buffer, and deallocate function */
/* The sound channels are mixed together into the mix buffer   */
/* then the mix buffer data is sent directly to the sound device */
void Mix_alloc( Mix *mix, int size );
void Mix_dealloc( Mix *mix );

/*==========================================================================*/
/* justing for testing, normally not called */
void dump_snd_list()
	{
	int i=0;
	
	for(i=0; i<S_num_sounds; i++)
		{
		printf("snd %d: len = %d \n", i, S_sounds[i].len );
		}
	}
	
/*==========================================================================*/
int Snd_init( int num_snd, const Sample *sa, int frequency, 
              int channels, const char *dev )
	{
	int result;

	S_num_sounds     = num_snd;
	S_sounds         = sa;	/* array of sound samples*/
	S_playback_freq  = frequency;
	S_num_channels   = channels; 
	S_snddev= dev;	/* sound device, eg /dev/dsp*/
	
	if (S_sounds==NULL)
		return EXIT_FAILURE;
	
	result=Snd_init_dev();

	if (result==EXIT_SUCCESS)
		{
		sampleMixerStatus=1;
		}
	else
		{
		sampleMixerStatus=0;
		}

	return result;
	}

/*==========================================================================*/
int Snd_restore()
	{
	int result;

	if (!sampleMixerStatus)
		return EXIT_FAILURE;
	
	result=Snd_restore_dev();

	if (result==EXIT_SUCCESS)
		{
		sampleMixerStatus=0;
		}
	else
		{
		sampleMixerStatus=0;
		}

	return result;
	}

/*==========================================================================*/
/* volume control not implemented yet.*/
int Snd_effect( int sound_num, int channel )
	{
	if(! sampleMixerStatus )
		return EXIT_FAILURE;
		
	if(S_sounds[sound_num].data != NULL)
		{	
		write(S_fd_pipe[1], &sound_num, sizeof(sound_num));
		write(S_fd_pipe[1], &channel, sizeof(channel));
		}
	else
		fprintf(stderr,"Referencing NULL sound entry\n");
	
	return EXIT_SUCCESS;
	}

/*============================================================================*/
int Snd_init_dev()
	{
	int whoami;
	S_fd_snddev = -1;

	S_son_pid = 0;


	if(access(S_snddev,W_OK) != 0)
		{	
		perror("No access to sound device");
		return EXIT_FAILURE;
		}

	S_fd_snddev = open(S_snddev,O_WRONLY);

	if(S_fd_snddev < 0)
		{	
		fprintf(stderr,"int_snddev: Cannot open sound device \n");
		return EXIT_FAILURE;
		}
		
	close(S_fd_snddev);

	if(pipe(S_fd_pipe) < 0)
		{	
		fprintf(stderr,"Cannot create pipe for sound control \n");
		return EXIT_FAILURE;
		}

	/* now setup 2nd process for writing the data... */
	if((whoami = fork()) < 0)
		{	
		fprintf(stderr,"Cannot fork sound driver\n");
		return EXIT_FAILURE;
		}
		
	if(whoami != 0)	/* successfully created son */
		{	
		close(S_fd_pipe[0]);	/* close end for reading */
		S_son_pid = whoami;
		return EXIT_SUCCESS;
		}
		
		/* Here is the code for the son... */
		{
		int sound_num,ch,i;
		struct timeval tval = {0L,0L};
		fd_set readfds,dsp;

		Mix mix;
		
		int frag, fragsize;

		Channel *chan = (Channel*)malloc( sizeof(Channel)*S_num_channels );

		for (i=0; i<S_num_channels; i++)
			Chan_reset( chan+i );
			
		S_fd_snddev = open(S_snddev,O_WRONLY );
		if(S_fd_snddev < 0)
			{	
			perror("Cannot open sound device: ");
			exit(1);
			}

		frag = FRAG_SPEC; /*defined in soundIt.h */
		
 		ioctl(S_fd_snddev, SNDCTL_DSP_SETFRAGMENT, &frag);
 
		if ( ioctl(S_fd_snddev,SNDCTL_DSP_SPEED, &S_playback_freq)==-1 )
			perror("Sound driver ioctl ");

		fragsize=0;
		if ( ioctl(S_fd_snddev,SNDCTL_DSP_GETBLKSIZE, &fragsize)==-1 ) 
			perror("Sound driver ioctl ");
			
		/* printf("after: block size: %d \n",fragsize); */

		/* init mixer object*/
		Mix_alloc( &mix, fragsize );

		close(S_fd_pipe[1]);	/* close end for writing */

		FD_ZERO(&dsp); 
		FD_SET(S_fd_snddev, &dsp);
		
		FD_ZERO(&readfds); 
		FD_SET(S_fd_pipe[0], &readfds);
		
		printf("Sound driver initialized.\n");
		
		for(;;)
			{
			FD_SET(S_fd_pipe[0], &readfds);
			tval.tv_sec=0L;
			tval.tv_usec=0L;
			select(S_fd_pipe[0]+1, &readfds,NULL,NULL,&tval);

			if (FD_ISSET(S_fd_pipe[0], &readfds))
				{
				if (read(S_fd_pipe[0], &sound_num, sizeof(int))==0)
					break;

				read(S_fd_pipe[0], &ch, sizeof(int));

				/* printf("chan=%d snd=%d len=%d \n", ch, sound_num, S_sounds[sound_num].len ); */
				Chan_assign( &(chan[ch]), &(S_sounds[sound_num]) );
				}
			
			Chan_mixAll(&mix,chan);
			write(S_fd_snddev, mix.Vclippedbuf, fragsize );
			}

		Mix_dealloc( &mix );			
		printf("Sound process exiting..\n");
		close(S_fd_pipe[0]);
		close(S_fd_pipe[1]);
		exit (0);
		} /*end of child process */
	}


/*==========================================================================*/
int Snd_restore_dev()
	{
	close(S_fd_pipe[0]);
	close(S_fd_pipe[1]);
	
	/* wait for child process to die*/
	wait(NULL);
	return EXIT_SUCCESS;
	}

/*==========================================================================*/
/*   CHANNEL MIXING FUNCTIONS						    */
/*==========================================================================*/
void Chan_reset( Channel *chan )
    {
    chan->Vstart=NULL;
    chan->Vcurrent=NULL;
    chan->Vlen=0;
    chan->Vleft=0;
    }

/*==========================================================================*/
void Chan_assign( Channel *chan, const Sample *snd )
    {
    chan->Vstart  = snd->data;
    chan->Vcurrent= chan->Vstart;
    chan->Vlen    = snd->len;
    chan->Vleft   = snd->len;
    } 

/*==========================================================================*/
int Chan_copyIn( Channel *chan, Mix *mix )
    {
    int    i,*p = mix->Vunclipbuf, result, min;

    result = (chan->Vleft>0) ? 1 : 0;
    min = (chan->Vleft < mix->Vsize) ? chan->Vleft : mix->Vsize;

    for(i=0; i<min; i++)
        {
        *p++ = (int) *chan->Vcurrent++;
        }
    chan->Vleft -= i;

    /* fill the remaining (if any) part of the mix buffer with silence */
    while (i<mix->Vsize) 
            { 
            *p++ = 128; 
            i++; 
            }
    return result;            
    }

/*==========================================================================*/
int Chan_mixIn( Channel *chan, Mix *mix ) 
    {
    int    i,*p = mix->Vunclipbuf, result, min;

    result = (chan->Vleft>0) ? 1 : 0;
    min = (chan->Vleft < mix->Vsize) ? chan->Vleft : mix->Vsize;
    
    for(i=0; i<min; i++)
        {
        *p++ += (int) (*chan->Vcurrent++) - 128;
        }

    chan->Vleft -= i;
    return result;
    }

/*========================================================================*/
/* clip an int to a value between 0 and 255 */
static inline 
unsigned char clip(int i)
    {
    return (i<0) ? 0 : ( (i>255) ? 255 : i );
    }
    
/*==========================================================================*/
int Chan_finalMixIn( Channel *chan, Mix *mix )
    {
    register int    i;
    int   *p = mix->Vunclipbuf, result, min;
    unsigned char *final = mix->Vclippedbuf;

    result = (chan->Vleft>0) ? 1 : 0;
    min = (chan->Vleft < mix->Vsize) ? chan->Vleft : mix->Vsize;
    
    for(i=0; i<min; i++)
        {
        *p += (int) (*chan->Vcurrent++) - 128;
        *final++ = clip(*p++);
        }
    chan->Vleft -= i;

    /* copy rest of Vunclipbuf over to Vclippedbuf */
    while (i<mix->Vsize) 
            {
            *final++ = clip(*p++);
            i++;
            }
            
    return result;
    }


/*==========================================================================*/
void Mix_alloc(Mix *mix, int size)
    {
    mix->Vclippedbuf = (unsigned char *)calloc( sizeof(char), size);
    mix->Vunclipbuf = (int *)calloc( sizeof(int), size);
    mix->Vsize  = size;
    
    if ((mix->Vclippedbuf==NULL)||(mix->Vunclipbuf==NULL))
    	{
   	fprintf(stderr,"Unable to allocate memory for mixer buffer\n");
    	exit(-1);
    	}
    }

/*==========================================================================*/
void Mix_dealloc( Mix *mix)
    { 
    if (mix->Vclippedbuf) free(mix->Vclippedbuf); 
    if (mix->Vunclipbuf) free(mix->Vunclipbuf); 
    }

/*==========================================================================*/
/* Mixes together the channels into one sound.
   Returns # of channels currently playing *any* sound
   Therefore, return 0 means to channels have a sample, therefore no
   sound is playing
*/ 
int Chan_mixAll( Mix *mix, Channel *chan )
    {
    int result  = 0,i=0;
    
    result  = Chan_copyIn( chan,  mix);

    /* we want to loop for S_num_channels-2 */
    for(i=2;i<S_num_channels;i++)
    	result += Chan_mixIn( ++chan, mix);
    	
    result += Chan_finalMixIn( ++chan, mix);
    
    return result;
    }

/*==========================================================================*/
/* given the name of a .raw sound file, load it into the Sample struct */ 
/* pointed to by 'sample'                                              */
/* Returns -1 couldn't open/read file				       */
/*         -2 couldn't alloc memory)                                   */
int
Snd_loadRawSample( const char *file, Sample *sample )
   {
   FILE *fp;

   sample->data = NULL;
   sample->len  = 0;
   
   fp = fopen(file,"r");   
   
   if (fp==NULL) return -1;
   
   /* get length of the file */
   sample->len = lseek( fileno(fp), 0, SEEK_END );
   
   /* go back to beginning of file */
   lseek( fileno(fp), 0, SEEK_SET );

   /* alloc memory for sample */
   sample->data = (unsigned char *)malloc( sample->len );
   
   if (sample->data==NULL)
   	{
   	fclose(fp);
   	return -2;
   	}
   
   fread( sample->data, 1, sample->len, fp ); 	
  
   fclose(fp);
   
   return 0;
   }
