/*****************************************************************************
 * sounds.c
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston,
 * MA 02111-1307, USA.
 *
 * Copyright (C) 2003-2005, Erica Andrews
 * (Phrozensmoke ['at'] yahoo.com)
 * http://phpaint.sourceforge.net/pyvoicechat/
 * 
 * Released under the terms of the GPL.
 * *NO WARRANTY*
 *
 * Erica Andrews, PhrozenSmoke ['at'] yahoo.com
 * added 8.31.2003, support for playing sound events on the ESound daemon
 *
 * Stefan Sikora, Hoshy ['at'] schrauberstube.de
 * 2006 added support for playing sound events with ALSA
 *****************************************************************************/

#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif

#include <ctype.h>
#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#include <netdb.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <expat.h>
#include <malloc.h>
#include <glib.h>

#include "sounds.h"  /* added, PhrozenSmoke */
#include "util.h"
#include "gyach.h"
#include "users.h"
#include "bootprevent.h"

/* added, PhrozenSmoke, support for ESound sound events */

char *mp3_player=NULL;
int xml_aud_counter=0;

GYAUDIBLE gyache_auds[] = {  /* current capacity is 75 */ 
 { NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, NULL }, 
 { NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, NULL }, 
 { NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, NULL }, 
 { NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, NULL }, 

 { NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, NULL }, 
 { NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, NULL }, 
 { NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, NULL }, 
 { NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, NULL }, 

 { NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, NULL }, 
 { NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, NULL }, 
 { NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, NULL }, 
 { NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, NULL }, 

 { NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, NULL }, 
 { NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, NULL }, 
 { NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, NULL }, 
 { NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, NULL }, 

 { NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, NULL }, 
 { NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, NULL }, 
 { NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, NULL }, 
 { NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, NULL }, 

 { NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, NULL }, 
 { NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, NULL }, 
 { NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, NULL }, 
 { NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, NULL }, 

 { NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, NULL }, { NULL, NULL, NULL, NULL }
};


int auds_callback_enc(void *enc_data, const XML_Char *name, XML_Encoding *info) {return 0;}
void auds_callback_cdata(void *user_data, const XML_Char *data, int len) {return;}
void auds_callback_end(void *user_data, const XML_Char *name) {	xml_aud_counter++;}

void auds_callback_start(void *user_data, const XML_Char *name, const XML_Char **attrs) {
	if (xml_aud_counter>71) {return;}
	if (strncmp((char*)name,"audible",7)==0) {
		if (!attrs) {return;}
		if (! *attrs) {return;}
		else {
			GYAUDIBLE *sm_ptr;
			XML_Char **cptr=(XML_Char **) attrs;
			gyache_audibles=gyache_auds;
			sm_ptr = &gyache_audibles[xml_aud_counter];
			while (cptr && *cptr) {
				if (strncmp(cptr[0],"yname",5)==0) {sm_ptr->aud_file=strdup(cptr[1]);}
				if (strncmp(cptr[0],"quote",5)==0) {sm_ptr->aud_text=strdup(cptr[1]);}
				if (strncmp(cptr[0],"ahash",5)==0) {sm_ptr->aud_hash=strdup(cptr[1]);}
				if (strncmp(cptr[0],"filename",8)==0) {sm_ptr->aud_disk_name=strdup(cptr[1]);}
				cptr += 2;
			}
		}
	}
}

int load_xml_audibles() {
	char filename[256];
	int fd;
	int bytes;
	XML_Parser p;
	void *buff;
	snprintf(filename,254,"%s/audibles/gyaudibles.xml",  PACKAGE_DATA_DIR);
	fd = open( filename, O_RDONLY, 0600 );
	if ( fd == -1 ) {return( 0 );	}
	p = XML_ParserCreate(NULL);      /* XML_ParserCreate( "iso-8859-1"); */
	XML_SetElementHandler(p, auds_callback_start, auds_callback_end);
	XML_SetCharacterDataHandler(p, auds_callback_cdata);
	XML_SetUnknownEncodingHandler(p, auds_callback_enc, NULL);
	XML_SetUserData(p, "");
	buff = XML_GetBuffer(p, 18432);		
	if (buff == NULL) {	return -1;}
	bytes = read( fd, (char *)buff, 18400 );
	close( fd ); 
	xml_aud_counter=0;
	if (! XML_ParseBuffer(p, bytes, bytes == 0)) {return 1;}
	XML_ParserFree(p);	
	return 1;
}

void init_audibles() {
	GYAUDIBLE *sm_ptr;
	gyache_audibles=gyache_auds;
	sm_ptr = &gyache_audibles[0];
	if (! sm_ptr->aud_file) {load_xml_audibles(); }
}


int check_gy_audible( char *str ) {
	GYAUDIBLE *sm_ptr;
	if (!str) {return 0;}
	gyache_audibles=gyache_auds;
	sm_ptr = &gyache_audibles[0];
	while( sm_ptr->aud_file ) {
		if (!strncmp( str, sm_ptr->aud_file, strlen(sm_ptr->aud_file))) {
			return( 1 );
		}
		if (!strncmp( str, sm_ptr->aud_disk_name, strlen(sm_ptr->aud_disk_name))) {
			return( 1 );
		}
		sm_ptr++;
	}
	return( 0 );
}

char *play_audible(char *aud) {
	char audbuf[256];
	if (sounds_opening_too_fast()) {return NULL;}
	if ( capture_fp ) {	
		fprintf(capture_fp,"\n[%s] Y! AUDIBLE ANIMATION: %s [%s], Using MP3 Player Command for Audibles: '%s'\n",
			gyach_timestamp(),
			aud?aud:"None", 
			check_gy_audible(aud)?"Available":"Not Available",
			mp3_player?mp3_player:"mplayer");
		fflush( capture_fp );
	}
	if (!aud) {return NULL;}
	if (!check_gy_audible(aud)) {return NULL;}
	else {
		char *audy_file=NULL;
		audy_file=get_gy_audible_disk_name( aud );
		if (! audy_file) {return NULL;}
		snprintf(audbuf, sizeof(audbuf)-1, "%s %s/audibles/%s.mp3",
			 mp3_player?mp3_player:"mplayer",
			 PACKAGE_DATA_DIR,
			 audy_file);
		play_audible_command(strdup(audbuf));
		free(audy_file);
	}
	return NULL;
}

char *get_gy_audible_disk_name( char *str ) {
	GYAUDIBLE *sm_ptr;
	gyache_audibles=gyache_auds;
	sm_ptr = &gyache_audibles[0];
	while( sm_ptr->aud_file ) {
		if (!strncmp( str, sm_ptr->aud_file, strlen(sm_ptr->aud_file))) {
			return strdup(sm_ptr->aud_disk_name);
		}
		if (!strncmp( str, sm_ptr->aud_disk_name, strlen(sm_ptr->aud_disk_name))) {
			return strdup(sm_ptr->aud_disk_name);
		}
		sm_ptr++;
	}
	return NULL;
}


char *get_gy_audible_hash( char *str ) {
	GYAUDIBLE *sm_ptr;
	gyache_audibles=gyache_auds;
	sm_ptr = &gyache_audibles[0];
	while( sm_ptr->aud_file ) {
		if (!strncmp( str, sm_ptr->aud_file, strlen(sm_ptr->aud_file))) {
			return strdup(sm_ptr->aud_hash);
		}
		sm_ptr++;
	}
	return NULL;
}

char *get_gy_audible_text( char *str ) {
	GYAUDIBLE *sm_ptr;
	gyache_audibles=gyache_auds;
	sm_ptr = &gyache_audibles[0];
	while( sm_ptr->aud_file ) {
		if (!strncmp( str, sm_ptr->aud_file, strlen(sm_ptr->aud_file))) {
			return strdup(sm_ptr->aud_text);
		}
		sm_ptr++;
	}
	return NULL;
}



#ifdef  SUPPORT_SOUND_EVENTS
typedef struct gy_sounds {
        int   sound_value;
        char *sound_event;
        char *filename;
        int   file_size;
        char *file_content;
} GY_SOUND_FILE;

      
GY_SOUND_FILE sound_events[] = { {SOUND_EVENT_BUDDY_ON,  "buddon.raw",    0, 0, 0},
				 {SOUND_EVENT_BUDDY_OFF, "buddoff.raw",   0, 0, 0},
				 {SOUND_EVENT_MAIL,      "yahoomail.raw", 0, 0, 0},
				 {SOUND_EVENT_PM,        "pm.raw",        0, 0, 0},
				 {SOUND_EVENT_BUZZ,      "buzz.raw",      0, 0, 0},
				 {SOUND_EVENT_OTHER,     "other.raw",     0, 0, 0},
				 {SOUND_EVENT_REJECT,    "reject.raw",    0, 0, 0},
				 {-1,                    0,               0, 0, 0}};
  
#endif





/* alot of this code comes from the PyESD ESound library, modified to suit this application */



#ifdef  SUPPORT_SOUND_EVENTS

#ifdef HAVE_ESD
/* ******************************
  * PyESD
  * Copyright (c) 2003 Erica Andrews
  * <PhrozenSmoke ['at'] yahoo.com>
  *
  * License: GNU General Public License
  *
  * A Python-wrapped library for 
  * playing raw sound over the ESound
  * daemon. This library also facilitates 
  * recording raw sound from  
  * ESound. Also, this library allows you
  * get basic, useful information about 
  * the ESound Daemon being used.
  * for playback over ESD.  This library
  * uses highly modified versions of 
  * MPlayer's 'ao_esd.c' module and 
  * the 'main.c' module from Gnome's 
  * Vumeter application.
  *
****************************** */

/*
 * ao_esd - EsounD audio output driver for MPlayer
 *
 * Juergen Keil <jk@tools.de>
 *
 * This driver is distributed under the terms of the GPL
 *
 * TODO / known problems:
 * - does not work well when the esd daemon has autostandby disabled
 *   (workaround: run esd with option "-as 2" - fortunatelly this is
 *    the default)
 * - plays noise on a linux 2.4.4 kernel with a SB16PCI card, when using
 *   a local tcp connection to the esd daemon; there is no noise when using
 *   a unix domain socket connection.
 *   (there are EIO errors reported by the sound card driver, so this is
 *   most likely a linux sound card driver problem)
 */



#include <sys/types.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <time.h>
#include <malloc.h>
#include <esd.h>

/*  ***************************************  */
/* BEGIN modified version of ao_esd.c from MPlayer */

#define	ESD_CLIENT_NAME	"PyESound"
#define	ESD_MAX_DELAY	(1.0f)	/* max amount of data buffered in esd (#sec) */

int esd_fd = -1;
int esd_play_fd = -1;
int esd_play_fdR = -1;   /*for recording */
int esd_latency;
int esd_bytes_per_sample;
unsigned long esd_samples_written;
int ao_samplerate=1;
int ao_bps;
struct timeval esd_play_start;
char *ao_subdevice="localhost";
int is_blocking=0;
int stop_playback=0;


/*  ***************************************  */
/* END modified version of ao_esd.c from MPlayer */

char PYESD_ERROR_MSG[255]="";

char *get_esd_error_msg() {return PYESD_ERROR_MSG;}

/*
 * open & setup audio device for playback
 * return: 1=success 0=fail
 * rate = 22050, 44100, etc.
 * channels is either 1 or 2
 * format is either 8 or 16
 */
int init_esd_play(int rate_hz, int channels, int format)
{
	esd_format_t esd_fmt;
	int bytes_per_sample;
	int fl;
	char *server = ao_subdevice;  /* NULL for localhost */

	if (esd_play_fd>0)  {return 1;}

	if (esd_fd < 0) {
		esd_fd = esd_open_sound(server);
		if (esd_fd < 0) {
			snprintf(PYESD_ERROR_MSG, 253, 
				 "AO: [esd] esd_open_sound failed: %d\n",
				 errno);
			return 0;
		}
	}


	esd_fmt = ESD_STREAM | ESD_PLAY;

	/* EsounD can play mono or stereo */
	ao_samplerate = rate_hz;

	switch (channels) {
	case 1:
		esd_fmt |= ESD_MONO;
		bytes_per_sample = 1;
		break;
	default:
		esd_fmt |= ESD_STEREO;
		bytes_per_sample = 2;
		break;
	}

	/* EsounD can play 8bit unsigned and 16bit signed native */
	switch (format) {
	case 8:
		esd_fmt |= ESD_BITS8;
		break;
	default:
		esd_fmt |= ESD_BITS16;
		bytes_per_sample *= 2;
		break;
	}

	/* modify audio_delay depending on esd_latency
	 * latency is number of samples @ 44.1khz stereo 16 bit
	 * adjust according to rate_hz & bytes_per_sample
	 */
#ifdef HAVE_ESD_LATENCY
	esd_latency = esd_get_latency(esd_fd);
#else
	esd_latency = ((channels == 1 ? 2 : 1) * ESD_DEFAULT_RATE * 
		   (ESD_BUF_SIZE + 64 * (4.0f / bytes_per_sample))
		   ) / rate_hz;  
	esd_latency += ESD_BUF_SIZE * 2; 
#endif
    
	esd_play_fd = esd_play_stream_fallback(esd_fmt, rate_hz,
					   server, ESD_CLIENT_NAME);
	if (esd_play_fd < 0) {
		snprintf(PYESD_ERROR_MSG, 253, 
			 "AO: [esd] failed to open esd playback stream: %d\n",
			 errno);
		return 0;
	}


	/* enable non-blocking i/o on the socket connection to the esd server */
	if ((fl = fcntl(esd_play_fd, F_GETFL)) >= 0)
		fcntl(esd_play_fd, F_SETFL, O_NDELAY|fl);

	ao_bps = bytes_per_sample * rate_hz;
	esd_play_start.tv_sec = 0;
	esd_samples_written = 0;
	esd_bytes_per_sample = bytes_per_sample;

	return 1;
}


/*
 * close audio device
 */
void esd_uninitPlayback()  /* close playback */
{
	if (esd_play_fd >= 0) {
		esd_close(esd_play_fd);
		esd_play_fd = -1;
	}
	if (esd_fd >= 0) {
		esd_close(esd_fd);
		esd_fd = -1;
	}
}


void* esdmalloc(size_t s)
{
	void* p = malloc(s);
	if (!p) {
		snprintf(PYESD_ERROR_MSG, 253, "PyESD: Could not allocate memory: out of memory\n");
	}

	return p;
}



/*
 * plays 'len' bytes of 'data'
 * it should round it down to outburst*n
 * return: number of bytes played
 */
int esd_play(void  *_data, int len)
{
	int offs;
	int nwritten;
	int nsamples;
	int remainder, n;
	int saved_fl;

	if (! esd_play_fd) {return -1;}
	if (! _data) {return -1;}

	/* round down buffersize to a multiple of ESD_BUF_SIZE bytes */
	len = len / ESD_BUF_SIZE * ESD_BUF_SIZE;

	if (len <= 0)
		return 0;

#define	SINGLE_WRITE 0
#if	SINGLE_WRITE
	nwritten = write(esd_play_fd, _data, len);
#else
	for (offs = 0; offs + ESD_BUF_SIZE <= len; offs += ESD_BUF_SIZE) {
		/*
		 * note: we're writing to a non-blocking socket here.
		 * A partial write means, that the socket buffer is full.
		 */
		nwritten = write(esd_play_fd, (char*)_data + offs, ESD_BUF_SIZE);
		if (nwritten != ESD_BUF_SIZE) {
			if (nwritten < 0 && errno != EAGAIN) {
				snprintf(PYESD_ERROR_MSG, 253, "esd play: write failed: %d\n", errno);
			}
			break;
		}
	}
	nwritten = offs;
#endif

	if (nwritten > 0 && nwritten % ESD_BUF_SIZE != 0) {

		/*
		 * partial write of an audio block of ESD_BUF_SIZE bytes.
		 *
		 * Send the remainder of that block as well; this avoids a busy
		 * polling loop in the esd daemon, which waits for the rest of
		 * the incomplete block using reads from a non-blocking
		 * socket. This busy polling loop wastes CPU cycles on the
		 * esd server machine, and we're trying to avoid that.
		 * (esd 0.2.28+ has the busy polling read loop, 0.2.22 inserts
		 * 0 samples which is bad as well)
		 *
		 * Let's hope the blocking write does not consume too much time.
		 *
		 * (fortunatelly, this piece of code is not used when playing
		 * sound on the local machine - on solaris at least)
		 */
		remainder = ESD_BUF_SIZE - nwritten % ESD_BUF_SIZE;
		snprintf(PYESD_ERROR_MSG, 253, "esd play: partial audio block written, remainder %d \n",
			 remainder);

		/* blocking write of remaining bytes for the partial audio block */
		saved_fl = fcntl(esd_play_fd, F_GETFL);
		fcntl(esd_play_fd, F_SETFL, saved_fl & ~O_NDELAY);
		n = write(esd_play_fd, (char *)_data + nwritten, remainder);
		fcntl(esd_play_fd, F_SETFL, saved_fl);

		if (n != remainder) {
			snprintf(PYESD_ERROR_MSG, 253, 
				 "AO PLAY: [esd] send remainder of audio block failed, %d/%d\n",
				 n, remainder);
		}
		else {
			nwritten += n;
		}
	}

	is_blocking=0;

	if (nwritten > 0) {
		if (!esd_play_start.tv_sec)
			gettimeofday(&esd_play_start, NULL);
		nsamples = nwritten / esd_bytes_per_sample;
		esd_samples_written += nsamples; 
	}
	else {
		snprintf(PYESD_ERROR_MSG, 253, "esd play: blocked / %lu\n", esd_samples_written);
		is_blocking=1;
	}

	if (nwritten<0) {nwritten=0;}
	return nwritten;
}


int esd_play_sound_file(char *_data,  int rate_hz, int channels, int format) {
	int written=-1;
	void* myptr;
	FILE *myfile;
	int ptrsize=4096;
	int inbytes;
	int mylen=4096;  /* len should always be at least 4096 */

	if (! _data) {snprintf(PYESD_ERROR_MSG, 253, "Cannot open a NULL file."); return written;}
	myfile=fopen(_data,"rb");
	if (! myfile) {snprintf(PYESD_ERROR_MSG, 253, "Could not open file: %s",_data); return written;}

	/* open ESD */
	if (! init_esd_play( rate_hz,  channels,  format)) { return written; }

	myptr=esdmalloc(ptrsize);
	if (! myptr) {return written;}
	written=0;
	stop_playback=0;

	/* open_volume();   */

 	while (1)   {
		mylen=4096;
		if (stop_playback) {break;}
		inbytes = fread(myptr, 1, ptrsize, myfile);	
		if (inbytes>mylen) {mylen=inbytes;}
		written=written+esd_play(myptr,mylen); 

		if  (is_blocking) { 
			while (is_blocking) {
				if (is_blocking) { sleep(0.02);} 
				written=written+esd_play(myptr,mylen); 
			}
		}

		if (inbytes != ptrsize) {
			if (feof(myfile)) { break;}      
			snprintf(PYESD_ERROR_MSG, 253, "File read error.\n");
			break;
		}
	}

	fclose(myfile);
	esd_uninitPlayback();
	/* close_volume();  */
	free(myptr);
	return written;
}

#else	// use ALSA instead (default)

#include <stdlib.h>
#include <stdio.h>
#include <alsa/asoundlib.h>

int alsa_play_sound_file(GY_SOUND_FILE *gy_sound,  int rate, int channels, int format) {
	int   total;
	char *data;
	static unsigned period_time = 0;
	static unsigned buffer_time = 0;
	snd_pcm_uframes_t period_frames = 0;
	snd_pcm_uframes_t buffer_frames = 0;
	snd_pcm_hw_params_t *hwparams;
	snd_pcm_t *pcm_handle;          
	snd_pcm_stream_t stream = SND_PCM_STREAM_PLAYBACK;
	char *pcm_name = "default";
	int exact_rate;   // Sample rate returned by snd_pcm_hw_params_set_rate_near
	int io_count;

	snd_pcm_hw_params_alloca(&hwparams);
	if (snd_pcm_open(&pcm_handle, pcm_name, stream, 0) < 0) {
        	fprintf(stderr, "Error opening PCM device %s\n", pcm_name);
		return(-1);
	}
  
	/* Init hwparams with full configuration space */
	if (snd_pcm_hw_params_any(pcm_handle, hwparams) < 0) {
		fprintf(stderr, "Can not configure this PCM device.\n");
		return(-1);
	}


	if (snd_pcm_hw_params_set_access(pcm_handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED) < 0) {
		fprintf(stderr, "Error setting access.\n");
		return(-1);
	}
  
	/* Set sample format */
	if (snd_pcm_hw_params_set_format(pcm_handle, hwparams, format) < 0) {
		fprintf(stderr, "Error setting format.\n");
		return(-1);
	}

	/* Set sample rate. If the exact rate is not supported */
	/* by the hardware, use nearest possible rate.         */ 
	exact_rate = rate;
	if (snd_pcm_hw_params_set_rate_near(pcm_handle, hwparams, &exact_rate, 0) < 0) {
		fprintf(stderr, "Error setting rate.\n");
		return(-1);
	}
	if (rate != exact_rate) {
        	fprintf(stderr, "The rate %d Hz is not supported by your hardware.\nUsing %d Hz instead.\n", rate, exact_rate);
	}

	/* Set number of channels */
	if (snd_pcm_hw_params_set_channels(pcm_handle, hwparams, channels) < 0) {
		fprintf(stderr, "Error setting channels.\n");
		return(-1);
	}

	snd_pcm_hw_params_get_buffer_time_max(hwparams, &buffer_time, 0);
	if (buffer_time > 500000) buffer_time = 500000;

	if (buffer_time > 0) period_time = buffer_time / 4;
	else period_frames = buffer_frames / 4;

	if (period_time > 0) snd_pcm_hw_params_set_period_time_near(pcm_handle, hwparams, &period_time, 0);
	else snd_pcm_hw_params_set_period_size_near(pcm_handle, hwparams, &period_frames, 0);

	if (buffer_time > 0) snd_pcm_hw_params_set_buffer_time_near(pcm_handle, hwparams, &buffer_time, 0);
	else snd_pcm_hw_params_set_buffer_size_near(pcm_handle, hwparams, &buffer_frames);
						
	/* Apply HW parameter settings to */
	/* PCM device and prepare device  */
	if (snd_pcm_hw_params(pcm_handle, hwparams) < 0) {
		fprintf(stderr, "Error setting HW params.\n");
		return(-1);
	}
  
	data  = gy_sound->file_content;
	total = gy_sound->file_size;

	while (total > 0) {
		io_count=(4096<total)?4096:total;

		while (snd_pcm_writei(pcm_handle, data, io_count) < 0) {
			snd_pcm_prepare(pcm_handle);
		}
		data  += 4096;
		total -= 4096;
	}

	/* Stop PCM device after pending frames have been played */ 
	snd_pcm_drain(pcm_handle);
	snd_pcm_close(pcm_handle);
	return(1); /* this function should really be declared void... */
}
#endif //HAVE_ESD

/* forward declarations */
static GY_SOUND_FILE *find_sound_file(int sound_value);
static void gy_play_sound_event(GY_SOUND_FILE *gy_sound);

#ifdef G_THREADS_ENABLED
/* local thread related variables. Note, local to process, not local to thread */
GMutex  *play_sound_list_mutex;
GCond   *play_sound_list_wait_cond;
GThread *p_play_sound_thread;
int      play_sound_exit_flag = FALSE;

typedef struct play_sound_list_entry {
	GY_SOUND_FILE *gy_sound; /* these 2 are mutually exclusive */
	char *system_command;    /* i.e. either you have a sound event, or a system command to spawn off */
	struct play_sound_list_entry *next;
} GYACHI_PLAYLIST_ENTRY;
static GYACHI_PLAYLIST_ENTRY *play_sound_list;
static GYACHI_PLAYLIST_ENTRY *play_sound_list_freelist;

/* forward declarations */
static GY_SOUND_FILE *find_sound_file(int sound_value);
void *play_sound_thread(void *arg);

void play_sound_thread_init() {
	play_sound_list = NULL;
	play_sound_list_freelist = NULL;
	play_sound_list_mutex = g_mutex_new();
	play_sound_list_wait_cond = g_cond_new();
	play_sound_exit_flag = FALSE;
	p_play_sound_thread = g_thread_create(play_sound_thread, (gpointer)0, FALSE, NULL );

	/*
	  struct timespec initialization_sleep;

	  initialization_sleep.tv_sec=0;
	  initialization_sleep.tv_nsec=500;
	  // allow player thread to start up :)
	  nanosleep(&initialization_sleep, NULL);
	*/
}

void play_sound_thread_terminate() {
	play_sound_exit_flag = TRUE;
	g_cond_signal(play_sound_list_wait_cond);
}

/*  Note: *MUST* be called with play_sound_mutex unlocked */
void static add_item_to_play_sound_list(GYACHI_PLAYLIST_ENTRY *new_entry){
	GYACHI_PLAYLIST_ENTRY *next_entry;

        g_mutex_lock(play_sound_list_mutex);

	new_entry->next=NULL;

	if (play_sound_list == NULL) {
	        play_sound_list = new_entry;
	}
	else {
	        for (next_entry=play_sound_list;
		     next_entry->next != NULL;
		     next_entry=next_entry->next);
		next_entry->next = new_entry;
	}

	g_cond_signal(play_sound_list_wait_cond);
	g_mutex_unlock(play_sound_list_mutex);
}

void add_sound_event_command(GY_SOUND_FILE *gy_sound, char *system_command) {
        GYACHI_PLAYLIST_ENTRY *new_entry;

        g_mutex_lock(play_sound_list_mutex);
	if (play_sound_list_freelist) {
	        new_entry=play_sound_list_freelist;
		play_sound_list_freelist = new_entry->next;
	}
	else {
	        new_entry = malloc(sizeof(GYACHI_PLAYLIST_ENTRY));
	}
	g_mutex_unlock(play_sound_list_mutex);

	new_entry->gy_sound       = gy_sound;
	new_entry->system_command = system_command;
	new_entry->next           = NULL;
	add_item_to_play_sound_list(new_entry);
}

void play_sound_event(int sound_value) {
	GY_SOUND_FILE *gy_sound = find_sound_file(sound_value);

	if (!gy_sound) { return; }
	add_sound_event_command(gy_sound, NULL);
}

void play_audible_command(char *system_command) {
	if (!system_command) { return;}
	add_sound_event_command(NULL, system_command);	
}


/* the sound player thread */
void *play_sound_thread(void *arg) {
	GYACHI_PLAYLIST_ENTRY *sound_event;
	GY_SOUND_FILE *gy_sound;
	char *system_command;

        while (1) {
		if (play_sound_exit_flag) {
			g_mutex_free(play_sound_list_mutex);
			g_cond_free(play_sound_list_wait_cond);
			g_thread_exit(0);
		}

	        g_mutex_lock(play_sound_list_mutex);
		sound_event = play_sound_list;
		if (sound_event == NULL) {
		        g_cond_wait(play_sound_list_wait_cond, play_sound_list_mutex);
			g_mutex_unlock(play_sound_list_mutex);
			continue;
		}

	        play_sound_list = sound_event->next;
		gy_sound=sound_event->gy_sound;
		system_command = sound_event->system_command;
		/* recycle. link used sound event to top of freelist */
		sound_event->next = play_sound_list_freelist;
		play_sound_list_freelist = sound_event;
		g_mutex_unlock(play_sound_list_mutex);

		if (system_command) {
			system(system_command);
			free(system_command);
		}
		if (gy_sound) {
			gy_play_sound_event(gy_sound);
		}
	}
}
#else
/* stubs if no threading library */
void play_sound_thread_init() {
}

void play_sound_thread_terminate() {
}

void play_sound_event(int sound_value) {
        GY_SOUND_FILE *gy_sound = find_sound_file(int sound_value);

	if (!gy_sound) { return; }
	gy_play_sound_event(gy_sound);
}

#endif

static GY_SOUND_FILE *find_sound_file(int sound_value)
{
	char           sound_f[256];
	GY_SOUND_FILE *gy_sound;
	struct stat    stat_buf;
	int            fd, needed;
	char          *buff;
	int            rv;

	if (sounds_opening_too_fast()) {return 0;}

	if (sound_value==SOUND_EVENT_PM) { if (!enable_sound_events_pm) {return 0;}}
	else {
		if (!enable_sound_events) {return 0;}
	}
	for (gy_sound=sound_events; gy_sound->sound_value != sound_value && gy_sound->sound_value != -1; gy_sound++);

	if (gy_sound->sound_value == -1) {
	        if ( capture_fp ) {
			fprintf(capture_fp, "\n[%s] Cannot locate sound file for sound event #%d\n",
				gyach_timestamp(), sound_value);
		        fflush( capture_fp );
		}
	        fprintf(stderr, "Cannot locate sound file for sound event #%d\n", sound_value);
		return 0;
	}

	if (gy_sound->filename == NULL) {
		snprintf(sound_f,254, "%s/sounds/%s", PACKAGE_DATA_DIR, gy_sound->sound_event);
		gy_sound->filename = strdup(sound_f);
		rv = stat(gy_sound->filename, &stat_buf);
		if (rv) {
		        if ( capture_fp ) {
				fprintf(capture_fp, "\n[%s] Cannot stat sound file %s. %s\n",
					gyach_timestamp(), gy_sound->filename, strerror(errno));
			        fflush( capture_fp );
			}
		        fprintf(stderr, "Cannot stat sound file %s. %s\n", gy_sound->filename, strerror(errno));
			return 0;
		}
		gy_sound->file_size = stat_buf.st_size;
		gy_sound->file_content = malloc(gy_sound->file_size);
		fd=open(gy_sound->filename, O_RDONLY);
		if (!fd) {
		        if ( capture_fp ) {
				fprintf(capture_fp, "\n[%s] Cannot open sound file %s. %s\n",
					gyach_timestamp(), gy_sound->filename, strerror(errno));
			        fflush( capture_fp );
			}
		        fprintf(stderr, "Cannot open sound file %s. %s\n", gy_sound->filename, strerror(errno));
			free(gy_sound->file_content);
			gy_sound->file_content=NULL;
			return 0;
		}
		needed = gy_sound->file_size;
		buff   = gy_sound->file_content;
		do {
		        rv=read(fd, buff, needed);
			if ((rv == -1) && (errno == EINTR)) {
			        continue;
			}
			if (rv == -1) {
			        if ( capture_fp ) {
				        fprintf(capture_fp, "\n[%s] Error reading sound file %s. %s\n",
						gyach_timestamp(), gy_sound->filename, strerror(errno));
				        fflush( capture_fp );
				}
			        fprintf(stderr, "Error reading sound file %s. %s\n",
					gy_sound->filename, strerror(errno));
				free(gy_sound->file_content);
				gy_sound->file_content=NULL;
				return 0;
			}
			needed -= rv;
			buff   += rv;
		} while (needed > 0);
		
		if ( capture_fp ) {
			fprintf(capture_fp,"\n[%s] SOUND EVENT File %s is now loaded.\n", gyach_timestamp(), gy_sound->filename);
			fflush( capture_fp );
		}
	}

	if (gy_sound->file_content == NULL) { return 0; };

	if ( capture_fp ) {	
		fprintf(capture_fp,"\n[%s] SOUND EVENT (ESound), File: %s\n", gyach_timestamp(), gy_sound->filename);
		fflush( capture_fp );
	}
	return(gy_sound);
}

static void gy_play_sound_event(GY_SOUND_FILE *gy_sound) {

#ifdef HAVE_ESD
        if (gy_sound->sound_value==SOUND_EVENT_BUZZ)
		esd_play_sound_file(gy_sound,8000,1,16);
	else if (gy_sound->sound_value==SOUND_EVENT_REJECT)
	        esd_play_sound_file(gy_sound,8000,1,8);
	else    esd_play_sound_file(gy_sound,11025,1,8);
	}
#else
	if (gy_sound->sound_value==SOUND_EVENT_BUZZ)
	        alsa_play_sound_file(gy_sound,8000,1,SND_PCM_FORMAT_S16_LE);
	else if (gy_sound->sound_value==SOUND_EVENT_REJECT)
	        alsa_play_sound_file(gy_sound,8000,1,SND_PCM_FORMAT_U8);
	else    alsa_play_sound_file(gy_sound,11025,1,SND_PCM_FORMAT_U8);
#endif //HAVE_ESD							

}

#else  /* sound events not compiled int */
	/* a callback that does nothing if sound events aren't supported */
	void play_sound_event(int sound_value) {}
#endif




