

/* The sound server for Maelstrom!

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

#include <sys/types.h>
#include <sys/time.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <sys/ioctl.h>

#ifdef linux
#include <linux/soundcard.h>
#else
#ifdef sparc	/* Solaris 2.4 */
#include <sys/audioio.h>
#else
#endif /* sparc */
#endif /* linux */


#include "mixer.h"

#if defined(_SGI_SOURCE) || defined(_INCLUDE_HPUX_SOURCE)
extern void usleep(unsigned long usec);
#endif

extern "C" {
	extern char *getenv(char *);
};

Mixer:: Mixer(char *device, unsigned short initvolume)
{
	int   i;

	/* Set the sound card device */
	if ( device )
		dsp_device = device;
	/* AUDIODEV is a Solaris-2.4 recommended environment variable */
	else if ( (dsp_device=getenv("AUDIODEV")) == NULL )
		dsp_device = _PATH_DEV_DSP;

	dsp_fd = -1;
	frag_size = 1024;

	/* Now set up the channels, and load some sounds */
	for ( i=0; i<NUM_CHANNELS; ++i ) {
		channels[i].position = 0;
		channels[i].in_use = 0;
		channels[i].sample = NULL;
	}
	clipped = new unsigned char[frag_size];
	silence = new unsigned char[frag_size];
	/* Tell the silence to be quiet :) */
	memset(silence, 128, frag_size);

	/* Start up with initial volume */
	(void) SetVolume(initvolume);
}

Mixer:: ~Mixer()
{
	delete[] clipped;
	delete[] silence;
	close(dsp_fd);
}

int
Mixer:: DSPopen(void)
{
	int  frag_spec = FRAG_SPEC;

	if ( dsp_fd >= 0 ) {	// The device is already open.
		return(0);
	}

#if defined(linux) || defined(sparc) || defined(_SGI_SOURCE)
	if ( (dsp_fd=open(dsp_device, (O_WRONLY|O_NONBLOCK), 0)) < 0 ) {
		perror("Mixer: Can't open sound card");
		return(-1);
	}
#else
	frag_size = 1024;
#endif

	speed = 11127;			/* Sampling speed of Maelstrom sound */
#ifdef linux
	if ( ioctl(dsp_fd, SNDCTL_DSP_SETFRAGMENT, &frag_spec) < 0 ) {
		perror("Mixer: Can't set frag spec");
		DSPclose();
		return(-1);
	}
	if ( ioctl(dsp_fd, SOUND_PCM_WRITE_RATE, &speed) < 0 ) {
		perror("Mixer: Can't set sampling rate");
		DSPclose();
		return(-1);
	}
	if ( ioctl(dsp_fd, SNDCTL_DSP_GETBLKSIZE, &frag_size) < 0 ) {
		perror("Mixer: Can't get fragment size");
		DSPclose();
		return(-1);
	}
	long flags;
	flags = fcntl(dsp_fd, F_GETFL, 0);
	flags |= O_SYNC;
	(void) fcntl(dsp_fd, F_SETFL, flags);
#else
#ifdef sparc	/* NOT TESTED */
	struct audio_prinfo audio_info;

	if ( ioctl(dsp_fd, AUDIO_GETINFO, &audio_info) < 0 ) {
		perror("Mixer: Can't get audio information");
		DSPclose();
		return(-1);
	}
	audio_info.sample_rate = speed;
	audio_info.channels    = 1;
	audio_info.precision   = 8;
	audio_info.encoding    = AUDIO_ENCODING_LINEAR;
	audio_info.gain        = AUDIO_MAX_GAIN;
	for ( int i=0, frag_size=(frag_spec>>4); i<(frag_spec&0xFFFF); ++i )
		frag_size *= (frag_spec>>4);
printf("frag_size = %d\n", frag_size);
	audio_info.buffer_size = frag_size;
	audio_info.port        = audio_info.avail_ports;

	if ( ioctl(dsp_fd, AUDIO_SETINFO, &audio_info) < 0 ) {
		perror("Mixer: Can't set audio information");
		DSPclose();
		return(-1);
	}
#endif /* sparc */
#endif /* linux */
	return(0);
}

void
Mixer:: DSPclose(void)
{
	if ( dsp_fd >= 0 ) {
		(void) close(dsp_fd);
		dsp_fd = -1;
	}
}

int
Mixer:: SetVolume(unsigned short Volume)
{
	if ( Volume > 0x08 ) {
		fprintf(stderr, "Mixer: Warning: Volume is a range 0-8\n");
		return(-1);
	}
	if ( Volume ) {	// Don't set the volume if we can't open the device.
		if ( DSPopen() < 0 )
			return(-1);
		volume = (float)(Volume*32)/255.0;
	} else {
		volume = 0;
		DSPclose();
	}
	return(0);
}

int
Mixer:: SoundID(unsigned short channel)
{
	if ( channel > NUM_CHANNELS-1 )
		return(-1);
	if ( channels[channel].in_use )
		return(channels[channel].sample->ID);
	return(-1);
}

int
Mixer:: Play_Sample(unsigned short channel, Sample *sample)
{
	if ( channel > NUM_CHANNELS-1 )
		return(-1);

	channels[channel].position = 0;
	channels[channel].in_use = 1;
	channels[channel].sample = sample;
	return(0);
}

void
Mixer:: PlaySleep(void)
{
	/* Sleep for the experimentally determined value of
	   50000 microseconds (the duration of a device write.)
	 */
	for ( long timeleft=50000; timeleft > 0; ) {
		struct timeval then, now;
		long           ticks;

		gettimeofday(&then, NULL);
		usleep(timeleft);
		gettimeofday(&now, NULL);
		ticks = ((now.tv_sec-then.tv_sec)*1000000);
		ticks += (now.tv_usec-then.tv_usec);
		timeleft -= ticks;
	}
}

void
Mixer:: Play(void)
{
	int frag_offset;
	int num_playing;
	unsigned short i;
	char data;

	/* Check to see if there are sounds playing */
	for( num_playing=0, i=0; i<NUM_CHANNELS; ++i ) {
		if ( channels[i].in_use )
			++num_playing;
	}
	/* If there is nothing to play, just write out silence */
	if ( num_playing == 0 ) {
		if ( dsp_fd >= 0 )
			(void) write(dsp_fd, silence, frag_size);
		else
			PlaySleep();
		return;
	}

	/* This is for mono output */
	for( frag_offset=0; frag_offset<frag_size; ++frag_offset ) {
		unclipped = 0;
		num_playing = 0;

		for( i=0; i<NUM_CHANNELS; ++i ) {
			/* See if the channel is in use */
			if ( channels[i].in_use ) {
				/* Normalize the data */
				data = 
		(*(channels[i].sample->data + channels[i].position)) ^ 0x80;
				unclipped += (int)(volume * (float)data);

				/* See if this sample is done being played */
				if( ++channels[i].position 
						>= channels[i].sample->len ) {
					channels[i].in_use = 0;
					if ( channels[i].sample->callback )
						(*channels[i].sample->callback)(i);
				}
			}
		}
		/* Re-normalize the values */
		unclipped += 128;
		if(unclipped < 0)
			clipped[frag_offset] = 0;
		else if(unclipped > 255)
			clipped[frag_offset] = 255;
		else
			clipped[frag_offset] = unclipped;
	}
#ifdef DEBUG
for( i=0; i<NUM_CHANNELS; ++i ) {
	if ( channels[i].in_use )
		fprintf(stderr, "Channel %hu: position = %d, len = %d\n", i, channels[i].position, channels[i].sample->len);
}
#endif
	/* Write out the data */
	if ( dsp_fd >= 0 ) {
	write_frag:
		if ( write(dsp_fd, clipped, frag_size) != frag_size ) {
			if ( errno == EINTR )  // Interrupted system call...
				// This should happen (SA_RESTART)
				goto write_frag;
			else {
				perror("Mixer: Can't write to audio device");
				DSPclose();
				return;
			}
		}
#ifdef sparc
		else if ( ioctl(dsp_fd, AUDIO_DRAIN, 0) < 0 )
			perror("Mixer: Can't drain the audio device");
#endif
	} else
		PlaySleep();
}

void
Mixer:: Halt(unsigned short channel)
{
	channels[channel].in_use = 0;
}

void
Mixer:: HaltAll(void)
{
	for ( unsigned short i=0; i<NUM_CHANNELS; ++i )
		Halt(i);
}
