shithub: heretic

ref: 7110aba73616e4f84b933a0db1ff96c4c22a9d6f
dir: heretic/i_sound.c

View raw version
//**************************************************************************
//**
//** i_soundpi.c: unix sound driver using a plugin interface
//**
//** $Revision: 512 $
//** $Date: 2009-06-04 18:00:34 +0300 (Thu, 04 Jun 2009) $
//**
//**************************************************************************


#include "h2stdinc.h"
#include "doomdef.h"
#include "sounds.h"
#include "i_sound.h"
#include "audio_plugin.h"

#define SAMPLE_ZERO	0
#define SAMPLE_RATE	11025	/* Hz */
#define SAMPLE_CHANNELS	2
#define TARGET_RATE	44100

#define SAMPLE_TYPE	short

/*
 *	SOUND HEADER & DATA
 */

int snd_Channels;
int snd_MaxVolume,		/* maximum volume for sound */
	snd_MusicVolume;	/* maximum volume for music */
boolean snd_MusicAvail,		/* whether music is available */
	snd_SfxAvail;		/* whether sfx are available */

/*
 *	SOUND FX API
 */

typedef struct
{
	unsigned char	*begin;		/* pointers into Sample.firstSample */
	unsigned char	*end;

	SAMPLE_TYPE	*lvol_table;	/* point into vol_lookup */
	SAMPLE_TYPE	*rvol_table;

	unsigned int	pitch_step;
	unsigned int	step_remainder;	/* 0.16 bit remainder of last step. */

	int		pri;
	unsigned int	time;
} Channel;

#pragma pack on
typedef struct
{
/* Sample data is a lump from a wad: byteswap the a, freq
 * and the length fields before using them		*/
	short		a;		/* always 3	*/
	short		freq;		/* always 11025	*/
	int32_t		length;		/* sample length */
	unsigned char	firstSample;
} Sample;
#pragma pack off

static int	audio_exit_thread = 1;

#define CHAN_COUNT	8
static Channel	channel[CHAN_COUNT];

#define MAX_VOL		64	/* 64 keeps our table down to 16Kb */
static SAMPLE_TYPE	vol_lookup[MAX_VOL * 256];

static int	steptable[256];		/* Pitch to stepping lookup */

#define BUF_LEN		(256 * 2 * 4)

static int audiofd;
static int audiopid = -1;

static QLock audiolk;

boolean mus_paused = false;

static int mpfd[2] = {-1, -1};

void I_ShutdownMusic(void);

static void audioproc(void)
{
	Channel* chan;
	Channel* cend;
	static char buf[BUF_LEN];
	SAMPLE_TYPE *begin;
	SAMPLE_TYPE *end;
	unsigned int sample;
	register int dl, ml;
	register int dr, mr;
	int i;

	end = (SAMPLE_TYPE *) (buf + BUF_LEN);
	cend = channel + CHAN_COUNT;

	for(;;){
		memset(buf, 0, sizeof buf);
		if(mpfd[0]>=0 && !mus_paused && readn(mpfd[0], buf, sizeof buf) < 0){
			fprint(2, "I_UpdateSound: disabling music: %r\n");
			I_ShutdownMusic();
		}
		begin = (SAMPLE_TYPE *) buf;
		while (begin < end){
			// Mix all the channels together.
			dl = SAMPLE_ZERO;
			dr = SAMPLE_ZERO;

			qlock(&audiolk);
			chan = channel;
			for ( ; chan < cend; chan++){
				if(!chan->begin)
					continue;
				// Get the sample from the channel.
				sample = *chan->begin;

				// Adjust volume accordingly.
				dl += chan->lvol_table[sample];
				dr += chan->rvol_table[sample];

				// Increment sample pointer with pitch adjustment.
				chan->step_remainder += chan->pitch_step;
				chan->begin += chan->step_remainder >> 16;
				chan->step_remainder &= 65535;

				// Check whether we are done.
				if (chan->begin >= chan->end)
				{
					chan->begin = NULL;
				//printf ("  channel done %d\n", chan);
				}
			}
			qunlock(&audiolk);
			for(i=0; i < TARGET_RATE/SAMPLE_RATE; i++){
				ml = dl + *begin;
				if (ml > 0x7fff)
					ml = 0x7fff;
				else if (ml < -0x8000)
					ml = -0x8000;
				*begin++ = ml;

				mr = dr + *begin;
				if (mr > 0x7fff)
					mr = 0x7fff;
				else if (mr < -0x8000)
					mr = -0x8000;
				*begin++ = mr;
			}
		}
		write(audiofd, buf, BUF_LEN);
	}
}


void I_SetSfxVolume(int volume)
{
	USED(volume);
}

// Gets lump nums of the named sound.  Returns pointer which will be
// passed to I_StartSound() when you want to start an SFX.  Must be
// sure to pass this to UngetSoundEffect() so that they can be
// freed!

int I_GetSfxLumpNum(sfxinfo_t *sound)
{
	if (sound->name[0] == 0)
		return 0;
	if (sound->link)
		sound = sound->link;
	return W_GetNumForName(sound->name);
}


// Id is unused.
// Data is a pointer to a Sample structure.
// Volume ranges from 0 to 127.
// Separation (orientation/stereo) ranges from 0 to 255.  128 is balanced.
// Pitch ranges from 0 to 255.  Normal is 128.
// Priority looks to be unused (always 0).

int I_StartSound(int id, void *data, int vol, int sep, int pitch, int priority)
{
	// Relative time order to find oldest sound.
	static unsigned int soundTime = 0;
	int chanId;
	Sample *sample;
	Channel *chan;
	int oldest;
	int i;

	USED(id);
	// Find an empty channel, the oldest playing channel, or default to 0.
	// Currently ignoring priority.

	chanId = 0;
	oldest = soundTime;
	for (i = 0; i < CHAN_COUNT; i++)
	{
		if (! channel[ i ].begin)
		{
			chanId = i;
			break;
		}
		if (channel[ i ].time < oldest)
		{
			chanId = i;
			oldest = channel[ i ].time;
		}
	}

	sample = (Sample *) data;
	chan = &channel[chanId];

	I_UpdateSoundParams(chanId + 1, vol, sep, pitch);

	// begin must be set last because the audio thread will access the channel
	// once it is non-zero.  Perhaps this should be protected by a mutex.
	chan->pri = priority;
	chan->time = soundTime;
	chan->end = &sample->firstSample + LONG(sample->length);
	chan->begin = &sample->firstSample;

	soundTime++;

#if 0
	printf ("I_StartSound %d: v:%d s:%d p:%d pri:%d | %d %d %d %d\n",
		id, vol, sep, pitch, priority,
		chanId, chan->pitch_step, SHORT(sample->a), SHORT(sample->freq));
#endif

	return chanId + 1;
}

void I_StopSound(int handle)
{
	handle--;
	handle &= 7;
	channel[handle].begin = NULL;
}

int I_SoundIsPlaying(int handle)
{
	handle--;
	handle &= 7;
	return (channel[ handle ].begin != NULL);
}

void I_UpdateSoundParams(int handle, int vol, int sep, int pitch)
{
	int lvol, rvol;
	Channel *chan;

	qlock(&audiolk);
	// Set left/right channel volume based on seperation.
	sep += 1;	// range 1 - 256
	lvol = vol - ((vol * sep * sep) >> 16);	// (256*256);
	sep = sep - 257;
	rvol = vol - ((vol * sep * sep) >> 16);

	// Sanity check, clamp volume.
	if (rvol < 0)
	{
	//	printf ("rvol out of bounds %d, id %d\n", rvol, handle);
		rvol = 0;
	}
	else if (rvol > 127)
	{
	//	printf ("rvol out of bounds %d, id %d\n", rvol, handle);
		rvol = 127;
	}

	if (lvol < 0)
	{
	//	printf ("lvol out of bounds %d, id %d\n", lvol, handle);
		lvol = 0;
	}
	else if (lvol > 127)
	{
	//	printf ("lvol out of bounds %d, id %d\n", lvol, handle);
		lvol = 127;
	}

	// Limit to MAX_VOL (64)
	lvol >>= 1;
	rvol >>= 1;

	handle--;
	handle &= 7;
	chan = &channel[handle];
	chan->pitch_step = steptable[pitch];
	chan->step_remainder = 0;
	chan->lvol_table = &vol_lookup[lvol * 256];
	chan->rvol_table = &vol_lookup[rvol * 256];
	qunlock(&audiolk);
}


/*
 *	SOUND STARTUP STUFF
 */

// inits all sound stuff
void I_StartupSound (void)
{
	snd_SfxAvail = false;

	if (M_CheckParm("--nosound") || M_CheckParm("-s") || M_CheckParm("-nosound"))
	{
		fprintf(stderr, "I_StartupSound: Sound Disabled.\n");
		return;
	}

	audiofd = open("/dev/audio", OWRITE);
	if(audiofd < 0){
		fprintf(stderr, "I_StartupSound: /dev/audio could not be opened\n");
		return;
	}

	snd_SfxAvail = true;

	if((audiopid = rfork(RFPROC|RFMEM)) == 0){
		audioproc();
		exits(nil);
	}
}

// shuts down all sound stuff
void I_ShutdownSound (void)
{
	snd_SfxAvail = false;

	if(audiopid != -1){
		postnote(PNPROC, audiopid, "shutdown");
		audiopid = -1;
	}
	I_ShutdownMusic();
}

void I_SetChannels(int channels)
{
	int v, j;
	int *steptablemid;

	// We always have CHAN_COUNT channels.
	USED(channels);

	for (j = 0; j < CHAN_COUNT; j++)
	{
		channel[j].begin = NULL;
		channel[j].end   = NULL;
		channel[j].time = 0;
	}

	// This table provides step widths for pitch parameters.
	steptablemid = steptable + 128;
	for (j = -128; j < 128; j++)
	{
		steptablemid[j] = (int) (pow(2.0, (j/64.0)) * 65536.0);
	}

	// Generate the volume lookup tables.
	for (v = 0; v < MAX_VOL; v++)
	{
		for (j = 0; j < 256; j++)
		{
		//	vol_lookup[v*256+j] = 128 + ((v * (j-128)) / (MAX_VOL-1));

		// Turn the unsigned samples into signed samples.

			vol_lookup[v*256+j] = (v * (j-128) * 256) / (MAX_VOL-1);
		//	printf ("vol_lookup[%d*256+%d] = %d\n", v, j, vol_lookup[v*256+j]);
		}
	}
}


/*
 *	SONG API
 */

static int didgen = 0;

static void genmidi(void)
{
	int fd, n, sz;
	char name[64];
	uchar *gm;

	n = W_GetNumForName("GENMIDI");
	sz = W_LumpLength(n);
	gm = (uchar *)W_CacheLumpNum(n, PU_STATIC);
	snprint(name, sizeof(name), "/tmp/genmidi.%d", getpid());
	if((fd = create(name, ORDWR|ORCLOSE, 0666)) < 0)
		sysfatal("create: %r");
	if(write(fd, gm, sz) != sz)
			sysfatal("write: %r");
	Z_Free(gm);
}

void I_ShutdownMusic(void)
{
	if(mpfd[0] >= 0){
		close(mpfd[0]);
		mpfd[0] = -1;
		waitpid();
	}
}


/* In theory this allows register step allows 
 * the use of external files in place of internal ones. */
static void *currentsong = nil;
static int currentsize = 0;

int I_RegisterSong(void *data, int siz)
{
	if(!didgen){
		genmidi();
		didgen++;
	}
	if(currentsong != nil)
		return 0;

	currentsong = data;
	currentsize = siz;
	return 1;
}

int I_RegisterExternalSong(const char *nm)
{
	USED(nm);
	return 0;
}

void I_UnRegisterSong(int handle)
{
	USED(handle);
	currentsong = nil;
}

void I_PauseSong(int handle)
{
	if(handle <= 0)
		return;
	mus_paused = true;
}

void I_ResumeSong(int handle)
{
	if(handle <= 0)
		return;
	mus_paused = false;
}

void I_SetMusicVolume(int volume)
{
	USED(volume);
}

int I_QrySongPlaying(int handle)
{
	USED(handle);
	return 0;
}

// Stops a song.  MUST be called before I_UnregisterSong().
void I_StopSong(int handle)
{
	if(handle <= 0)
		return;
	I_ShutdownMusic();
}

void I_PlaySong(int handle, boolean loop)
{
	char name[64];
	int n;

	if(M_CheckParm("-nomusic") || audiofd < 0 || handle <= 0)
		return;

	I_ShutdownMusic();
	if(pipe(mpfd) < 0)
		return;
	switch(rfork(RFPROC|RFFDG|RFNAMEG)){
	case -1:
		fprint(2, "I_PlaySong: %r\n");
		break;
	case 0:
		dup(mpfd[1], 1);
		for(n=3; n<20; n++) close(n);
		close(0);
		snprint(name, sizeof(name), "/tmp/heretic.%d", getpid());
		if(create(name, ORDWR|ORCLOSE, 0666) != 0)
			sysfatal("create: %r");
		if(write(0, currentsong, currentsize) != currentsize)
			sysfatal("write: %r");
		if(seek(0, 0, 0) != 0)
			sysfatal("seek: %r");
		if(bind("/fd/1", "/dev/audio", MREPL) == -1)
			sysfatal("bind: %r");
		while(loop && fork() > 0){
			if(waitpid() < 0 || write(1, "", 0) < 0)
				exits(nil);
		}
		execl("/bin/dmus", "dmus", name, nil);
		execl("/bin/play", "play", name, nil);
		sysfatal("execl: %r");
	default:
		close(mpfd[1]);
	}
}