/*	WebDownloader for X-Window
 *	Copyright (C) 1999-2002 Koshelev Maxim
 *	This Program is free but not GPL!!! You can't modify it
 *	without agreement with author. You can't distribute modified
 *	program but you can distribute unmodified program.
 *
 *	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.
 */

#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <signal.h>
#include "sndserv.h"
#include "locstr.h"
#include "var.h"

#define	WAVE_FORMAT_UNKNOWN		(0x0000)
#define	WAVE_FORMAT_PCM			(0x0001)
#define	WAVE_FORMAT_ADPCM		(0x0002)
#define	WAVE_FORMAT_ALAW		(0x0006)
#define	WAVE_FORMAT_MULAW		(0x0007)
#define	WAVE_FORMAT_OKI_ADPCM		(0x0010)
#define	WAVE_FORMAT_DIGISTD		(0x0015)
#define	WAVE_FORMAT_DIGIFIX		(0x0016)
#define	IBM_FORMAT_MULAW         	(0x0101)
#define	IBM_FORMAT_ALAW			(0x0102)
#define	IBM_FORMAT_ADPCM         	(0x0103)

enum FMT_ENUM{
	FMT_U8,
	FMT_S8,
	FMT_U16_LE,
	FMT_U16_BE,
	FMT_U16_NE,
	FMT_S16_LE,
	FMT_S16_BE,
	FMT_S16_NE
};

class d4xAudio{
public:
	d4xAudio();
	virtual int write(void *data, int length);
	virtual int open(int fmt, int rate, int nch);
	virtual ~d4xAudio();
};

#ifdef D4X_WITH_OSS
#include <sys/soundcard.h>

class d4xOssAudio:public d4xAudio{
	int fragsize,oss_format,format,channels,frequency;
	int fd;
	int bps,ebps;
	int blk_size;
	int convert_stereo;
	void setup_format(int fmt,int rate, int nch);
	void set_audio_params();
public:
	d4xOssAudio();
	~d4xOssAudio();
	int write(void *data, int length);
	int open(int fmt, int rate, int nch);
};

#endif //D4X_WITH_OSS


#ifdef D4X_WITH_AO
#include <ao/ao.h>

class d4xAOAudio:public d4xAudio{
        ao_device *snd_device;
        ao_sample_format snd_format;
        int ao_drv_id;
 public:
        d4xAOAudio();
        ~d4xAOAudio();
        int write(void *data, int length);
        int open(int fmt, int rate, int nch);
};

#endif //D4X_WITH_AO


class d4xSndFile{
public:
	d4xSndFile(){};
	d4xSndFile(char *filename);
	virtual void play()=0;
	virtual ~d4xSndFile(){};
};

class d4xWaveFile:public d4xSndFile{
	FILE *file;
	short format,channels,align,bits,eof;
	long samples_per_sec, avg_bytes_per_sec;
	int position, length;
	int data_offset;
	d4xAudio *audio;
	int read_long(long *len);
	int read_short(short *val);
	void play_begin();
public:
	d4xWaveFile(char *filename);
	void play();
	~d4xWaveFile();
};

#ifdef HAVE_ESD
#include <esd.h>
class d4xEsdFile:public d4xSndFile{
	int fd;
	char *file;
public:
	d4xEsdFile(char *filename);
	void play();
	~d4xEsdFile();
};

/***********************************************************/

d4xEsdFile::d4xEsdFile(char *filename){
	file=copy_string(filename);
	fd=-1;
};

void d4xEsdFile::play(){
	if (fd<0)
		fd=esd_open_sound(NULL);//"localhost");
	if (fd>=0)
		esd_play_file(NULL,file,0); // ???
};

d4xEsdFile::~d4xEsdFile(){
	if (fd>=0) esd_close(fd);
};
#endif //HAVE_ESD

/***********************************************************/

int d4xWaveFile::read_long(long *len){
	unsigned char buf[4];

	if (fread(buf, 1, 4, file) != 4)
		return 0;

#if G_BYTE_ORDER == G_LITTLE_ENDIAN
	*len =(buf[3] << 24) | (buf[2] << 16) | (buf[1] << 8) | buf[0];
#else
	*len =(buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3];
#endif

	return 1;
};

int d4xWaveFile::read_short(short *val){
	unsigned char buf[2];
	if (fread(buf, 1, 2, file) != 2)
		return 0;
#if G_BYTE_ORDER == G_LITTLE_ENDIAN
	*val = (buf[1] << 8) | buf[0];
#else
	*val = (buf[0] << 8) | buf[1];
#endif
	return 1;
}

d4xWaveFile::d4xWaveFile(char *filename){
#ifdef D4X_WITH_OSS
	audio=new d4xOssAudio;
#elif defined(D4X_WITH_AO)
	audio=new d4xAOAudio;
#else
	audio=new d4xAudio;
#endif
	char data[4];
	format=channels=align=bits=eof=0;
	samples_per_sec=avg_bytes_per_sec=0;
	if ((file = fopen(filename, "r"))){
		fread(data, 1, 4, file);
		if (strncmp(data, "RIFF", 4)){
			fclose(file);
			file = NULL;
			return;
		}
		long tmp;
		read_long(&tmp);
		fread(data, 1, 4, file);
		if (strncmp(data, "WAVE", 4)){
			fclose(file);
			file = NULL;
			return;
		}
	};
};

d4xWaveFile::~d4xWaveFile(){
	if (file) fclose(file);
	if (audio) delete(audio);
};

void d4xWaveFile::play(){
	char data[4];
	unsigned long len;

	int audio_error = 0;
	if (file){
		while(1){
			fread(data, 1, 4, file);
			if (!read_long((long *)&len))
				return;
			if (!strncmp("fmt ", data, 4))
				break;
			fseek(file,len,SEEK_CUR);
		}
		if (len < 16){
			return;
		}
		read_short(&format);
		switch (format)
		{
			case WAVE_FORMAT_UNKNOWN:
			case WAVE_FORMAT_ALAW:
			case WAVE_FORMAT_MULAW:
			case WAVE_FORMAT_ADPCM:
			case WAVE_FORMAT_OKI_ADPCM:
			case WAVE_FORMAT_DIGISTD:
			case WAVE_FORMAT_DIGIFIX:
			case IBM_FORMAT_MULAW:
			case IBM_FORMAT_ALAW:
			case IBM_FORMAT_ADPCM:
				return;
		}
		if (!read_short(&channels)) return;
		if (!read_long(&samples_per_sec)) return;
		if (!read_long(&avg_bytes_per_sec)) return;
		if (!read_short(&align)) return;
		if (!read_short(&bits)) return;
/*
		printf("channels = %i\n",int(channels));
		printf("sps = %li\n",samples_per_sec);
		printf("abps = %li\n",avg_bytes_per_sec);
		printf("bps = %i\n",int(bits));
*/
		if (bits != 8 && bits != 16)
			return;
		len -= 16;
		if (len)
			fseek(file, len, SEEK_CUR);

		while(1){
			fread(data, 4, 1, file);
			if (!read_long((long *)&len)) return;
			if (!strncmp("data", data, 4))
				break;
			fseek(file, len, SEEK_CUR);
		}
		length = len;
		position = 0;

		if (audio->open((bits == 16) ? FMT_S16_LE : FMT_U8, samples_per_sec, channels) == 0){
			audio_error = 1;
			return;
		}
		eof=0;
		play_begin();
	}
}

void d4xWaveFile::play_begin(){
	char data[2048 * 2];

	int blk_size = 512 * (bits / 8) * channels;
//	printf("playing (blk_size=%i)\n",blk_size);
	while (!eof){
		int bytes = blk_size;
		if (length - position < bytes)
			bytes = length - position;
		if (bytes > 0){
			int actual_read = fread(data, 1, bytes, file);
			if (actual_read == -1){
				eof = 1;
			}else{
			        audio->write(data, bytes);
				position += actual_read;
			};
		}else{
			eof = 1;
		}
	}
}

/***********************************************************/
d4xAudio::d4xAudio(){
	/* abstract */
};

int d4xAudio::write(void *data, int length){
	return(length);
};

int d4xAudio::open(int fmt, int rate, int nch){
	return(1);
};

d4xAudio::~d4xAudio(){
	/* absctract */
};
/***********************************************************/

#ifdef D4X_WITH_OSS
void d4xOssAudio::setup_format(int fmt,int rate, int nch){
	format = fmt;
	frequency = rate;
	channels = nch;
	switch (fmt){
	case FMT_U8:
		oss_format = AFMT_U8;
		break;
	case FMT_S16_LE:
		oss_format = AFMT_S16_LE;
		break;
	}

	bps = rate * nch;
	if (oss_format == AFMT_S16_LE)
		bps *= 2;
	fragsize = 0;
	while ((1L << fragsize) < bps / 25)
		fragsize++;
	fragsize--;

}

void d4xOssAudio::set_audio_params(){
	ioctl(fd, SNDCTL_DSP_RESET, 0);
	int frag = (32 << 16) | fragsize;
	ioctl(fd, SNDCTL_DSP_SETFRAGMENT, &frag);
	ioctl(fd, SNDCTL_DSP_SETFMT, &oss_format);
	int stereo = channels - 1;
	ioctl(fd, SNDCTL_DSP_STEREO, &stereo);
	ioctl(fd, SNDCTL_DSP_SPEED, &frequency);

	if(ioctl(fd, SNDCTL_DSP_GETBLKSIZE, &blk_size) == -1)
		blk_size = 1L << fragsize;

	ebps = frequency * channels;
	if (oss_format == AFMT_S16_LE)
		ebps *= 2;
	
}

int d4xOssAudio::open(int fmt, int rate, int nch){
	fd = ::open("/dev/dsp", O_WRONLY);//|O_NONBLOCK);
	if (fd == -1){
//		printf("open_audio(): Failed to open audio device (%s): %s",
//		       "/dev/dsp", strerror(errno));
		return 0;
	}
	setup_format(fmt,rate,nch);
	set_audio_params();
	return 1;
}

int d4xOssAudio::write(void * data, int length){
	return (::write(fd, data, length));
}

d4xOssAudio::d4xOssAudio(){
	fd=-1;
};

d4xOssAudio::~d4xOssAudio(){
	if (fd>=0){
		ioctl(fd, SNDCTL_DSP_POST, 0);
		close(fd);
	};
};
#endif //D4X_WITH_OSS

/***********************************************************/

#ifdef D4X_WITH_AO

int d4xAOAudio::open(int fmt, int rate, int nch){
	switch (fmt){
	case FMT_U8:
	        snd_format.bits = 8;
	        snd_format.byte_format = AO_FMT_LITTLE;
		break;
	case FMT_S16_LE:
		snd_format.bits = 16;
		snd_format.byte_format = AO_FMT_LITTLE;
		break;
	}

	snd_format.rate = rate;
	snd_format.channels = nch;

	ao_drv_id = ao_default_driver_id();

	if (ao_drv_id < 0)
	  return 0;

	snd_device = ao_open_live(ao_drv_id, &snd_format, NULL);

	if (snd_device == NULL)
	  return 0;

	return 1;
}

int d4xAOAudio::write(void * data, int length){
	return (ao_play(snd_device, (char *)data, length));
}

d4xAOAudio::d4xAOAudio(){
  snd_device = NULL;
  ao_drv_id = -1;
};

d4xAOAudio::~d4xAOAudio(){
  if (snd_device != NULL)
    ao_close(snd_device);
};

#endif //D4X_WITH_AO

/***********************************************************/

d4xSndServer *SOUND_SERVER;

static void *_snd_serv_run_(void *serv){
	d4xSndServer *server=(d4xSndServer *)serv;

	sigset_t oldmask,newmask;
	sigemptyset(&newmask);
	sigaddset(&newmask,SIGTERM);
	sigaddset(&newmask,SIGINT);
	sigaddset(&newmask,SIGUSR1);
	sigaddset(&newmask,SIGUSR2);
	pthread_sigmask(SIG_BLOCK,&newmask,&oldmask);

	server->run();
	pthread_exit(NULL);
	return(NULL);
};

d4xSndServer::d4xSndServer(){
	stop_now=0;
	for (int i=0;i<SND_LAST;i++)
		snd_table[i]=NULL;
	thread_id=0;
	pthread_cond_init(&cond,NULL);

#ifdef D4X_WITH_AO
	ao_initialize();
#endif //D4X_WITH_AO

};

d4xSndServer::~d4xSndServer(){
	pthread_cond_destroy(&cond);
	for (int i=0;i<SND_LAST;i++)
		if (snd_table[i]) delete[] snd_table[i];

#ifdef D4X_WITH_AO
	ao_shutdown();
#endif //D4X_WITH_AO

};

void d4xSndServer::stop_thread(){
	void *val;
	my_mutex.lock();
	stop_now=1;
	pthread_cond_signal(&cond);
	my_mutex.unlock();
//	sleep(1);
	pthread_cancel(thread_id);
	pthread_join(thread_id,&val);
	thread_id=0;
};

void d4xSndServer::run_thread(){
	pthread_attr_t attr_p;
	pthread_attr_init(&attr_p);
	pthread_attr_setdetachstate(&attr_p,PTHREAD_CREATE_JOINABLE);
	pthread_attr_setscope(&attr_p,PTHREAD_SCOPE_SYSTEM);
	pthread_create(&thread_id,&attr_p,_snd_serv_run_,(void *)this);
};

void d4xSndServer::add_event(int event){
	if (thread_id==0 || CFG.ENABLE_SOUNDS==0 || CFG.WITHOUT_FACE)
		return;
	my_mutex.lock();
	queue.push_back(d4x::SndEvent(event));
	pthread_cond_signal(&cond);
	my_mutex.unlock();
};

void d4xSndServer::play_sound(int event){
	if (event>=0 && event<SND_LAST &&
	    snd_table[event]!=NULL){
		d4xSndFile *wav=NULL;
#ifdef HAVE_ESD
		if (CFG.ESD_SOUND)
			wav=new d4xEsdFile(snd_table[event]);
		else
#endif// HAVE_ESD
			wav=new d4xWaveFile(snd_table[event]);
		wav->play();
		delete(wav);
	};
};

void d4xSndServer::run(){
	while(true){
		my_mutex.lock();
		pthread_cond_wait(&cond,&(my_mutex.m));
		std::list<d4x::SndEvent> tmpqueue=queue;
		queue.clear();
		my_mutex.unlock();
		while(!tmpqueue.empty()){
			d4x::SndEvent snd=tmpqueue.front();
			time_t now=time(NULL);
			if (snd.birth-now<4 && snd.birth-now>-4){
				/* playing */
				play_sound(snd.event);
			};
			tmpqueue.pop_front();
		};
		if (stop_now) break;
	};
};

void d4xSndServer::set_sound_file(int event,char *path){
	my_mutex.lock();
	if (event>=0 && event<SND_LAST){
		if (snd_table[event]) delete[] snd_table[event];
		if (path && *path)
			snd_table[event]=copy_string(path);
		else
			snd_table[event]=NULL;
	};
	my_mutex.unlock();
};

char *d4xSndServer::get_sound_file(int event){
	if (event>=0 && event<SND_LAST)
		return(snd_table[event]);
	return(NULL);
};

#define SND_REINIT(a,b){		\
	if (!equal(snd_table[a],b))	\
		set_sound_file(a,b);	\
};

void d4xSndServer::reinit_sounds(){
	SND_REINIT(SND_STARTUP,CFG.SOUND_STARTUP);
	SND_REINIT(SND_FAIL,CFG.SOUND_FAIL);
	SND_REINIT(SND_COMPLETE,CFG.SOUND_COMPLETE);
	SND_REINIT(SND_ADD,CFG.SOUND_ADD);
	SND_REINIT(SND_DND_DROP,CFG.SOUND_DND_DROP);
	SND_REINIT(SND_QUEUE_FINISH,CFG.SOUND_QUEUE_FINISH);

#ifdef D4X_WITH_AO
       ao_shutdown();
       ao_initialize();
#endif //D4X_WITH_AO
};

void d4xSndServer::init_sounds(){
	set_sound_file(SND_STARTUP,CFG.SOUND_STARTUP);
	set_sound_file(SND_ADD,CFG.SOUND_ADD);
	set_sound_file(SND_DND_DROP,CFG.SOUND_DND_DROP);
	set_sound_file(SND_COMPLETE,CFG.SOUND_COMPLETE);
	set_sound_file(SND_FAIL,CFG.SOUND_FAIL);
	set_sound_file(SND_QUEUE_FINISH,CFG.SOUND_QUEUE_FINISH);
//	set_sound_file(SND_,CFG.SOUND_);
};


syntax highlighted by Code2HTML, v. 0.9.1