
/* The sound server for Maelstrom!

   Much of this code has been adapted from 'sfxserver', written by
   Terry Evans, 1994.  Thanks! :)
*/

#include <sys/time.h>
#include <sys/file.h>
#include <sys/ioctl.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <signal.h>
#include <unistd.h>

#ifdef _SGI_SOURCE
#include <bstring.h>		/* For bzero() definition */
#endif

#include "soundres.h"
#include "mixer.h"
#include "sound_cmds.h"

extern char  *file2libpath(char *filename);	// From shared.cc

/* This is the mixing device */
static Mixer mixer(NULL, 0);
static SoundRes sounds(file2libpath("Maelstrom Sounds"));

static void Handle_IO(int sig);
static int socket_fd = -1;

void Handle_Signal(int sig)
{
	fprintf(stderr, "SoundServer: Killed with signal %d\r\n", sig);
	if ( sig == SIGSEGV )	// Dump core
		abort();
	exit(sig);
}

main(int argc, char *argv[])
{
	struct sigaction action;
	fd_set fdset;
	struct timeval tv;
	long flags;
	int c;
	
	/* Exit when parent's pipe is closed */
	signal(SIGPIPE, exit);

	/* Get socket fd argument. :) */
	while ( (c=getopt(argc, argv, "s:")) != EOF ) {
		switch (c) {
			case 's': socket_fd=atoi(optarg);
				  break;
		}
	}
	if ( socket_fd < 0 ) {
		fprintf(stderr, "Must be called from Maelstrom!\n");
		exit(1);
	}

	/* Set ourselves up for asynchronous input */
	action.sa_handler = Handle_IO;
	sigemptyset(&action.sa_mask);
#ifdef USE_RESTART		/* SA_RESTART seems to be slower! */
	action.sa_flags   = SA_RESTART;
#else
	action.sa_flags   = 0;
#endif
	sigaction(SIGIO, &action, NULL);
#ifdef _INCLUDE_HPUX_SOURCE
	flags = 1;
	if ( ioctl(socket_fd, FIOASYNC, &flags) < 0 ) {
		perror("SoundServer: Can't set asynchronous I/O on socket");
		exit(3);
	}
	flags = getpid();
	if ( ioctl(socket_fd, SIOCSPGRP, &flags) < 0 ) {
		perror("SoundClient: Can't set process group for socket");
		exit(3);
	}
#else  /* linux, SGI, sparc, etc.. */
	flags = fcntl(socket_fd, F_GETFL, 0);
	flags |= FASYNC;
	if ( fcntl(socket_fd, F_SETFL, flags) < 0 ) {
		perror("SoundServer: Can't set asynchronous I/O on socket");
		exit(3);
	}
	if ( fcntl(socket_fd, F_SETOWN, (long)getpid()) < 0 ) {
		perror("SoundClient: Can't set process group for socket");
		exit(3);
	}
#endif
	/* Set up signal handler */
	signal(SIGHUP, Handle_Signal);
	signal(SIGINT, Handle_Signal);
	signal(SIGQUIT, Handle_Signal);
	signal(SIGBUS, Handle_Signal);
	signal(SIGSEGV, Handle_Signal);
	signal(SIGTERM, Handle_Signal);

	/* See if there is pending I/O */
	FD_ZERO(&fdset);
	FD_SET(socket_fd, &fdset);
	memset(&tv, 0, sizeof(tv));
#ifdef _INCLUDE_HPUX_SOURCE
	if ( select(64, (int *)&fdset, NULL, NULL, &tv) == 1 )
#else
	if ( select(64, &fdset, NULL, NULL, &tv) == 1 )
#endif
		Handle_IO(SIGIO);

	/* Now loop, waiting for events and playing sounds */
	for ( ; ; )
		mixer.Play();
}

static inline ReplyOkay(void)
{
	char replybuf[128];

	sprintf(replybuf, OKAY_FORMAT, OKAY_CMD);
	(void) write(socket_fd, replybuf, strlen(replybuf)+1);
}

static inline ReplyNOkay(void)
{
	char replybuf[128];

	sprintf(replybuf, NOKAY_FORMAT, NOKAY_CMD);
	(void) write(socket_fd, replybuf, strlen(replybuf)+1);
}

static void CallBack(unsigned short channel)
{
	char cmdbuf[128];

#ifdef DEBUG
fprintf(stderr, "Calling back on channel %hu\n", channel);
#endif
	sprintf(cmdbuf, CALLBACK_FORMAT, CALLBACK_CMD, channel);
	(void) write(socket_fd, cmdbuf, strlen(cmdbuf)+1);
}

/* This function is run asynchronously, when we are ready to receive commands */
void Handle_IO(int sig)
{
	static Sample *samples[4];
	char           cmdbuf[128], cmd;
	short          sound_num;
	short          priority;
	short          callback;
	unsigned short channel, channels[NUM_CHANNELS];
	unsigned short volume;
	char          *command;
	int            cmdlen, len;

#if defined(_SGI_SOURCE) || defined(_INCLUDE_HPUX_SOURCE)  /* Can we talk? */
	fd_set fds; struct timeval tv = { 0, 0 };

	FD_ZERO(&fds); FD_SET(socket_fd, &fds);
#ifdef _INCLUDE_HPUX_SOURCE
	if ( select(socket_fd+1, (int *)&fds, NULL, NULL, &tv) != 1 )
#else
	if ( select(socket_fd+1, &fds, NULL, NULL, &tv) != 1 )
#endif
		return;
#endif /* _SGI_SOURCE || _INCLUDE_HPUX_SOURCE */

	/* Read the command */
	if ( (len=read(socket_fd, cmdbuf, 128)) <= 0 ) {
		/* The socket is probably closed */
		exit(0);
	}

	for ( command=cmdbuf; len>0; ) {
		switch (*command) {
			case LOAD_CMD:	sscanf(cmdbuf, LOAD_FORMAT, &cmd,
								&sound_num);
					if (sounds.LoadSound(sound_num) < 0) {
						fprintf(stderr, 
				"SoundServer: Invalid sound id: %hd\n",
								sound_num);
						ReplyNOkay();
					} else
						ReplyOkay();
					break;
			case PLAY_CMD:	sscanf(cmdbuf, PLAY_FORMAT, &cmd,
							&sound_num, &channel);
		  			samples[channel] =
						sounds.GetSound(sound_num);
		  			if ( ! samples[channel] ) {
						fprintf(stderr, 
				"SoundServer: Couldn't find sound id: %hd\n",
								sound_num);
						ReplyNOkay();
						break;
		  			}
					samples[channel]->callback = CallBack;
		  			mixer.Play_Sample(channel, 
							samples[channel]);
					ReplyOkay();
					break;
			case VOL_CMD:	sscanf(cmdbuf, VOL_FORMAT, &cmd,
								&volume);
					if ( mixer.SetVolume(volume) < 0 )
						ReplyNOkay();
					else
						ReplyOkay();
					break;
			case HALT_CMD:	sscanf(cmdbuf, HALT_FORMAT, &cmd,
								&channel);
					mixer.Halt(channel);
					ReplyOkay();
					break;
			case HALTALL_CMD:
					mixer.HaltAll();
					ReplyOkay();
					break;
			case QUERY_CMD:	
					ReplyOkay();
					break;
			case QUIT_CMD:	exit(0);
			default:	fprintf(stderr, 
			"SoundServer: Warning: undefined command: '%c'\n", 
								cmdbuf[0]);
					break;
		}
		cmdlen = (strlen(command)+1); 
		command += cmdlen;
		len -= cmdlen;
	}
}
