about summary refs log tree commit diff
path: root/src/engine/client/snd.c
diff options
context:
space:
mode:
authorJoel de Vahl <joel@stalverk80.se>2007-09-25 19:48:52 +0000
committerJoel de Vahl <joel@stalverk80.se>2007-09-25 19:48:52 +0000
commitaca6dd808b19072b653ce81384490c8b08edbedd (patch)
tree804fc5f8e5e554f4065fd063f4d3f587b6c20b5d /src/engine/client/snd.c
parent06c341be51a75fd8a24f2441654e184fd3fdefcd (diff)
downloadzcatch-aca6dd808b19072b653ce81384490c8b08edbedd.tar.gz
zcatch-aca6dd808b19072b653ce81384490c8b08edbedd.zip
new mixer and ppc fixes
Diffstat (limited to 'src/engine/client/snd.c')
-rw-r--r--src/engine/client/snd.c609
1 files changed, 280 insertions, 329 deletions
diff --git a/src/engine/client/snd.c b/src/engine/client/snd.c
index 6b490f72..7ea5e752 100644
--- a/src/engine/client/snd.c
+++ b/src/engine/client/snd.c
@@ -4,356 +4,306 @@
 
 #include <engine/external/portaudio/portaudio.h>
 #include <engine/external/wavpack/wavpack.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
 
 enum
 {
-	NUM_FRAMES_STOP = 512,
-	NUM_FRAMES_LERP = 512,
+	NUM_SOUNDS = 512,
+	NUM_VOICES = 64,
+	NUM_CHANNELS = 4,
 };
 
-static const float NUM_FRAMES_STOP_INV = 1.0f/(float)NUM_FRAMES_STOP;
-static const float NUM_FRAMES_LERP_INV = 1.0f/(float)NUM_FRAMES_LERP;
-
-static const float GLOBAL_VOLUME_SCALE = 0.75f;
-static float master_volume = 1.0f;
-
-static const float GLOBAL_SOUND_DELAY = 0.05f;
-
-// --- sound ---
-typedef struct
+enum
 {
-	/*
-public:
-	sound_data() :
-		data(0x0),
-		num_samples(0),
-		rate(0),
-		channels(0),
-		sustain_start(-1),
-		sustain_end(-1),
-		last_played(0)
-	{ }*/
+	MAX_FRAMES = 1024
+};
 
+static struct sound
+{
 	short *data;
 	int num_samples;
 	int rate;
 	int channels;
-	int sustain_start;
-	int sustain_end;
-	int64 last_played;
-} SOUND_DATA;
+	int loop_start;
+	int loop_end;
+} sounds[NUM_SOUNDS] = { {0x0, 0, 0, 0, -1, -1} };
 
-
-static float clampf(float val, float lower, float upper)
+static struct voice
 {
-	if(val > upper)
-		return upper;
-	if(val < lower)
-		return lower;
-	return val;
-}
+	volatile struct sound *sound;
+	int tick;
+	int stop;
+	int loop;
+	float vol;
+	float pan;
+	float x;
+	float y;
+	volatile struct voice *prev;
+	volatile struct voice *next;
+} voices[NUM_VOICES] = { {0x0, 0, -1, -1, 1.0f, 0.0f, 0.0f, 0.0f, 0x0, 0x0} };
 
+#define CHANNEL_POSITION_VOLUME 1
+#define CHANNEL_POSITION_PAN 2
 
-static int clampi(int val, int lower, int upper)
+static struct channel
 {
-	if(val > upper)
-		return upper;
-	if(val < lower)
-		return lower;
-	return val;
-}
-/*
-template<typename T>
-inline const T clamp(const T val, const T lower, const T upper)
+	volatile struct voice *first_voice;
+	float vol;
+	float pan;
+	int flags;
+} channels[NUM_CHANNELS] = { {0x0, 1.0f, 0.0f, 0} };
+
+static float center_x = 0.0f;
+static float center_y = 0.0f;
+static float master_vol = 1.0f;
+static float master_pan = 0.0f;
+static float pan_deadzone = 256.0f;
+static float pan_falloff = 1.0f;
+static float volume_deadzone = 256.0f;
+static float volume_falloff = 1.0f;
+
+static inline short int2short(int i)
 {
-	if(val > upper)
-		return upper;
-	if(val < lower)
-		return lower;
-	return val;
-}*/
-
+	if(i > 0x7fff)
+		return 0x7fff;
+	else if(i < -0x7fff)
+		return -0x7fff;
+	return i;
+}
 
-typedef struct
+static inline float sgn(float f)
 {
-/*
-public:
-	channel()
-	{ data = 0; lerp = -1; stop = -1; }
-*/
-	
-	SOUND_DATA *data;
-	int tick;
-	int loop;
-	float pan;
-	float vol;
-	float old_vol;
-	float new_vol;
-	int lerp;
-	int stop;
-} MIXER_CHANNEL;
+	if(f < 0.0f)
+		return -1.0f;
+	return 1.0f;
+}
 
-enum
+static void reset_voice(struct voice *v)
 {
-	MAX_CHANNELS=32,
-	MAX_FILL_FRAMES=256,
-};
-
-static MIXER_CHANNEL channels[MAX_CHANNELS];
-static int buffer[MAX_FILL_FRAMES*2];
+	v->sound = 0x0;
+	v->tick = 0;
+	v->stop = -1;
+	v->loop = -1;
+	v->vol = 1.0f;
+	v->pan = 0.0f;
+	v->x = 0.0f;
+	v->y = 0.0f;
+	v->next = 0x0;
+	v->prev = 0x0;
+}
 
-static void mixer_fill_mono(int *out, unsigned long frames, MIXER_CHANNEL *c, float dv)
+static inline void fill_mono(int *out, unsigned frames, struct voice *v, float fvol, float fpan)
 {
-	float pl = clampf(1.0f - c->pan, 0.0f, 1.0f);
-	float pr = clampf(1.0f + c->pan, 0.0f, 1.0f);
-	unsigned long i;
+	int ivol = (int) (31.0f * fvol);
+	int ipan = (int) (31.0f * ipan);
 
+	unsigned i;
 	for(i = 0; i < frames; i++)
 	{
-		float val = c->vol * master_volume * c->data->data[c->tick];
-
-		out[i<<1] += (int)(pl*val);
-		out[(i<<1)+1] += (int)(pr*val);
-		c->tick++;
-		c->vol += dv;
-		if(c->vol < 0.0f) c->vol = 0.0f;
+		unsigned j = i<<1;
+		int val = v->sound->data[v->tick] * ivol;
+		out[j] += val;
+		out[j+1] += val;
+		v->tick++;
 	}
 }
 
-static void mixer_fill_stereo(int *out, unsigned long frames, MIXER_CHANNEL *c, float dv)
+static inline void fill_stereo(int *out, unsigned frames, struct voice *v, float fvol, float fpan)
 {
-	float pl = clampf(1.0f - c->pan, 0.0f, 1.0f);
-	float pr = clampf(1.0f + c->pan, 0.0f, 1.0f);
-	unsigned long i;
+	int ivol = (int) (31.0f * fvol);
+	int ipan = (int) (31.0f * ipan);
 
+	unsigned i;
 	for(i = 0; i < frames; i++)
 	{
-		int vl = (int)(pl*c->vol * master_volume * c->data->data[c->tick]);
-		int vr = (int)(pr*c->vol * master_volume * c->data->data[c->tick + 1]);
-		out[i<<1] += vl;
-		out[(i<<1)+1] += vr;
-		c->tick += 2;
-		c->vol += dv;
-		if(c->vol < 0.0f) c->vol = 0.0f;
+		unsigned j = i<<1;
+		out[j] += v->sound->data[v->tick] * ivol;
+		out[j+1] += v->sound->data[v->tick+1] * ivol;
+		v->tick += 2;
 	}
 }
 
-static void mixer_fill(void *output, unsigned long frames)
+static void mix(short *out, unsigned frames)
 {
-	short *out = (short*)output;
+	static int main_buffer[MAX_FRAMES*2];
 
-	dbg_assert(frames <= MAX_FILL_FRAMES, "not enough fill frames in buffer");
-	unsigned long i;
-	int c;
+	dbg_assert(frames <= MAX_FRAMES, "too many frames to fill");
 
+	unsigned i;
 	for(i = 0; i < frames; i++)
 	{
-		buffer[i<<1] = 0;
-		buffer[(i<<1)+1] = 0;
+		unsigned j = i<<1;
+		main_buffer[j] = 0;
+		main_buffer[j+1] = 0;
 	}
 
-	for(c = 0; c < MAX_CHANNELS; c++)
+	unsigned cid;
+	for(cid = 0; cid < NUM_CHANNELS; cid++)
 	{
-		unsigned long filled = 0;
-		while(channels[c].data && filled < frames)
+		struct channel *c = &channels[cid];
+		struct voice *v = (struct voice*)c->first_voice;
+
+		while(v)
 		{
-			unsigned long frames_left = (channels[c].data->num_samples - channels[c].tick) >> (channels[c].data->channels-1);
-			unsigned long to_fill = frames>frames_left?frames_left:frames;
-			float dv = 0.0f;
-
-			if(channels[c].stop >= 0)
-				to_fill = (unsigned)channels[c].stop>frames_left?frames:channels[c].stop;
-			if(channels[c].loop >= 0 &&
-					channels[c].data->sustain_start >= 0)
+			unsigned filled = 0;
+			while(v->sound && filled < frames)
 			{
-				unsigned long tmp = channels[c].data->sustain_end - channels[c].tick;
-				to_fill = tmp>frames?frames:tmp;
-			}
+				// calculate maximum frames to fill
+				unsigned frames_left = (v->sound->num_samples - v->tick) >> (v->sound->channels-1);
+				unsigned long to_fill = frames>frames_left?frames_left:frames;
+				float vol = 1.0f;
+				float pan = 0.0f;
+
+				// clamp to_fill if voice should stop
+				if(v->stop >= 0)
+					to_fill = (unsigned)v->stop>frames_left?frames:v->stop;
+
+				// clamp to_fill if we are about to loop
+				if(v->loop >= 0 && v->sound->loop_start >= 0)
+				{
+					unsigned tmp = v->sound->loop_end - v->tick;
+					to_fill = tmp>to_fill?to_fill:tmp;
+				}
 
-			if(channels[c].lerp >= 0)
-			{
-					dv = (channels[c].new_vol - channels[c].old_vol) * NUM_FRAMES_LERP_INV;
-			}
+				// calculate voice volume and delta
+				if(c->flags & CHANNEL_POSITION_VOLUME)
+				{
+					float dx = v->x - center_x;
+					float dy = v->y - center_y;
+					float dist = dx*dx + dy*dy;
+					if(dist < volume_deadzone*volume_deadzone)
+						vol = master_vol * c->vol;
+					else
+						vol = master_vol * c->vol / ((dist - volume_deadzone*volume_deadzone)*volume_falloff); //TODO: use some fast 1/x^2
+				}
+				else
+				{
+					vol = master_vol * c->vol * v->vol;
+				}
 
-			if(channels[c].data->channels == 1)
-				mixer_fill_mono(buffer, to_fill, &channels[c], dv);
-			else
-				mixer_fill_stereo(buffer, to_fill, &channels[c], dv);
+				// calculate voice pan and delta
+				if(c->flags & CHANNEL_POSITION_PAN)
+				{
+					float dx = v->x - center_x;
+					if(fabs(dx) < pan_deadzone)
+						pan = master_pan + c->pan;
+					else
+						pan = master_pan + c->pan + sgn(dx)*(fabs(dx) - pan_deadzone)/pan_falloff;
+				}
+				else
+				{
+					pan = master_pan + c->pan + v->pan;
+				}
 
-			if(channels[c].loop >= 0 &&
-					channels[c].data->sustain_start >= 0 &&
-					channels[c].tick >= channels[c].data->sustain_end)
-				channels[c].tick = channels[c].data->sustain_start;
+				// fill the main buffer
+				if(v->sound->channels == 1)
+					fill_mono(&main_buffer[filled], to_fill, v, vol, pan);
+				else
+					fill_stereo(&main_buffer[filled], to_fill, v, vol, pan);
+
+				// reset tick of we hit loop point
+				if(v->loop >= 0 &&
+						v->sound->loop_start >= 0 &&
+						v->tick >= v->sound->loop_end)
+					v->tick = v->sound->loop_start;
+
+				// stop sample if nessecary
+				if(v->stop >= 0)
+					v->stop -=  to_fill;
+				if(v->tick >= v->sound->num_samples || v->stop == 0)
+				{
+					if(v->next)
+						v->next->prev = v->prev;
 
-			if(channels[c].stop >= 0)
-				channels[c].stop -=  to_fill;
-			if(channels[c].tick >= channels[c].data->num_samples ||
-					channels[c].stop == 0)
-				channels[c].data = 0;
+					if(v->prev)
+						v->prev->next = v->next;
+					else
+						channels[cid].first_voice = v->next;
 
-			channels[c].lerp -= to_fill;
-			if(channels[c].lerp < 0)
-				channels[c].lerp = -1;
+					dbg_msg("snd", "sound stopped");
 
+					reset_voice(v);
+				}
+
+				filled += to_fill;
+			}
 
-			filled += to_fill;
+			v = (struct voice*)v->next;
 		}
 	}
 
+	// clamp accumulated values
 	for(i = 0; i < frames; i++)
 	{
-		out[i<<1] = (short)clampi(buffer[i<<1], -0x7fff, 0x7fff);
-		out[(i<<1)+1] = (short)clampi(buffer[(i<<1)+1], -0x7fff, 0x7fff);
-	}
-}
-
-int mixer_play(SOUND_DATA *sound, unsigned loop, float vol, float pan)
-{
-	if(time_get() - sound->last_played < (int64)(time_freq()*GLOBAL_SOUND_DELAY))
-		return -1;
+		int j = i<<1;
+		int vl = main_buffer[j];
+		int vr = main_buffer[j+1];
 
-	int c;
-	for(c = 0; c < MAX_CHANNELS; c++)
-	{
-		if(channels[c].data == 0)
-		{
-			channels[c].data = sound;
-			channels[c].tick = 0;
-			channels[c].loop = loop;
-			channels[c].vol = vol * GLOBAL_VOLUME_SCALE;
-			channels[c].pan = pan;
-			channels[c].stop = -1;
-			channels[c].lerp = -1;
-			sound->last_played = time_get();
-			return c;
-		}
+		out[j] = int2short(vl>>5);
+		out[j+1] = int2short(vr>>5);
 	}
-
-	return -1;
-}
-
-static void mixer_stop(int id)
-{
-	dbg_assert(id >= 0 && id < MAX_CHANNELS, "id out of bounds");
-	channels[id].old_vol = channels[id].vol;
-	channels[id].stop = NUM_FRAMES_STOP;
-}
-
-static void mixer_set_vol(int id, float vol)
-{
-	dbg_assert(id >= 0 && id < MAX_CHANNELS, "id out of bounds");
-	channels[id].new_vol = vol * GLOBAL_VOLUME_SCALE;
-	channels[id].old_vol = channels[id].vol;
-	channels[id].lerp = NUM_FRAMES_LERP;
 }
 
-typedef struct
-{
-	SOUND_DATA sound;
-	int next;
-} SOUND;
-
-enum
-{
-	MAX_SOUNDS = 1024,
-};
-
-static SOUND sounds[MAX_SOUNDS];
-static int first_free_sound;
-
-static PaStream *stream = 0;
-
 static int pacallback(const void *in, void *out, unsigned long frames, const PaStreamCallbackTimeInfo* time, PaStreamCallbackFlags status, void *user)
 {
-	mixer_fill(out, frames);
+	mix(out, frames);
 	return 0;
 }
 
+static PaStream *stream;
+
 int snd_init()
 {
-	int i;
-	first_free_sound = 0;
-	for(i = 0; i < MAX_SOUNDS; i++)
-		sounds[i].next = i+1;
-	sounds[MAX_SOUNDS-1].next = -1;
-	
-	// init PA
 	PaStreamParameters params;
 	PaError err = Pa_Initialize();
-	if(err != paNoError)
-	{
-		dbg_msg("audio_stream", "portaudio error: %s", Pa_GetErrorText(err));
-		return 0;
-	}
-
 	params.device = Pa_GetDefaultOutputDevice();
-	if(params.device == -1)
-	{
-		dbg_msg("audio_stream", "no default output device");
-		return 0;
-	}
 	params.channelCount = 2;
 	params.sampleFormat = paInt16;
 	params.suggestedLatency = Pa_GetDeviceInfo(params.device)->defaultLowOutputLatency;
 	params.hostApiSpecificStreamInfo = 0x0;
 
 	err = Pa_OpenStream(
-		&stream,        /* passes back stream pointer */
-		0,              /* no input channels */
-		&params,		/* pointer to parameters */
-		44100,          /* sample rate */
-		128,            /* frames per buffer */
-		paClipOff,		/* no clamping */
-		pacallback,		/* specify our custom callback */
-		0); /* pass our data through to callback */	
-
-	if(err != paNoError)
-	{
-		dbg_msg("audio_stream", "portaudio error: %s", Pa_GetErrorText(err));
-		return 0;
-	}
-	
+			&stream,        /* passes back stream pointer */
+			0,              /* no input channels */
+			&params,                /* pointer to parameters */
+			44100,          /* sample rate */
+			128,            /* frames per buffer */
+			paClipOff,              /* no clamping */
+			pacallback,             /* specify our custom callback */
+			0x0); /* pass our data through to callback */
 	err = Pa_StartStream(stream);
-	if(err != paNoError)
-	{
-		dbg_msg("audio_stream", "portaudio error: %s", Pa_GetErrorText(err));
-		return 0;
-	}
-	
-	return 1;	
+
+	return 0;
 }
 
 int snd_shutdown()
 {
 	Pa_StopStream(stream);
-	stream = NULL;
-	Pa_Terminate();	
-	return 1;
-}
+	Pa_Terminate();
 
-float snd_get_master_volume()
-{
-	return master_volume;
+	return 0;
 }
 
-void snd_set_master_volume(float val)
+void snd_set_center(int x, int y)
 {
-	if(val < 0.0f)
-		val = 0.0f;
-	else if(val > 1.0f)
-		val = 1.0f;
-
-	master_volume = val;
+	center_x = x;
+	center_y = y;
 }
 
-static int snd_alloc_sound()
+int snd_alloc_id()
 {
-	if(first_free_sound < 0)
-		return -1;
-	int id = first_free_sound;
-	first_free_sound = sounds[id].next;
-	sounds[id].next = -1;
-	return id;
+	unsigned sid;
+	for(sid = 0; sid < NUM_SOUNDS; sid++)
+	{
+		if(sounds[sid].data == 0x0)
+		{
+			return sid;
+		}
+	}
+
+	return -1;
 }
 
 static FILE *file = NULL;
@@ -365,11 +315,15 @@ static int read_data(void *buffer, int size)
 
 int snd_load_wv(const char *filename)
 {
-	SOUND_DATA snd;
-	int id = -1;
-
+	struct sound *snd;
+	int sid = -1;
 	char error[100];
 
+	sid = snd_alloc_id();
+	if(sid < 0)
+		return -1;
+	snd = &sounds[sid];
+
 	file = fopen(filename, "rb"); // TODO: use system.h stuff for this
 
 	WavpackContext *context = WavpackOpenFileInput(read_data, error);
@@ -380,18 +334,18 @@ int snd_load_wv(const char *filename)
 		unsigned int samplerate = WavpackGetSampleRate(context);
 		int channels = WavpackGetNumChannels(context);
 
-		snd.channels = channels;
-		snd.rate = samplerate;
+		snd->channels = channels;
+		snd->rate = samplerate;
 
-		if(snd.channels > 2)
+		if(snd->channels > 2)
 		{
 			dbg_msg("sound/wv", "file is not mono or stereo. filename='%s'", filename);
 			return -1;
 		}
 
-		if(snd.rate != 44100)
+		if(snd->rate != 44100)
 		{
-			dbg_msg("sound/wv", "file is %d Hz, not 44100 Hz. filename='%s'", snd.rate, filename);
+			dbg_msg("sound/wv", "file is %d Hz, not 44100 Hz. filename='%s'", snd->rate, filename);
 			return -1;
 		}
 		
@@ -405,8 +359,8 @@ int snd_load_wv(const char *filename)
 		WavpackUnpackSamples(context, data, samples); // TODO: check return value
 		int *src = data;
 		
-		snd.data = (short *)mem_alloc(2*samples*channels, 1);
-		short *dst = snd.data;
+		snd->data = (short *)mem_alloc(2*samples*channels, 1);
+		short *dst = snd->data;
 
 		int i;
 		for (i = 0; i < samples*channels; i++)
@@ -414,12 +368,9 @@ int snd_load_wv(const char *filename)
 
 		mem_free(data);
 
-		snd.num_samples = samples;
-		snd.sustain_start = -1;
-		snd.sustain_end = -1;
-		snd.last_played = 0;
-		id = snd_alloc_sound();
-		sounds[id].sound = snd;
+		snd->num_samples = samples;
+		snd->loop_start = -1;
+		snd->loop_end = -1;
 	}
 	else
 	{
@@ -429,23 +380,14 @@ int snd_load_wv(const char *filename)
 	fclose(file);
 	file = NULL;
 
-	if(id >= 0)
-	{
-		if(config.debug)
-			dbg_msg("sound/wv", "loaded %s", filename);
-	}
-	else
-	{
-		dbg_msg("sound/wv", "failed to load %s", filename);
-	}
+	if(config.debug)
+		dbg_msg("sound/wv", "loaded %s", filename);
 
-	return id;
+	return sid;
 }
 
 int snd_load_wav(const char *filename)
 {
-	SOUND_DATA snd;
-	
 	// open file for reading
 	IOHANDLE file;
 	file = io_open(filename, IOFLAG_READ);
@@ -455,7 +397,14 @@ int snd_load_wav(const char *filename)
 		return -1;
 	}
 
-	int id = -1;
+	struct sound *snd;
+	int sid = -1;
+
+	sid = snd_alloc_id();
+	if(sid < 0)
+		return -1;
+	snd = &sounds[sid];
+
 	int state = 0;
 	while(1)
 	{
@@ -503,8 +452,8 @@ int snd_load_wav(const char *filename)
 				
 				// decode format
 				int compression_code = fmt[0] | (fmt[1]<<8);
-				snd.channels = fmt[2] | (fmt[3]<<8);
-				snd.rate = fmt[4] | (fmt[5]<<8) | (fmt[6]<<16) | (fmt[7]<<24);
+				snd->channels = fmt[2] | (fmt[3]<<8);
+				snd->rate = fmt[4] | (fmt[5]<<8) | (fmt[6]<<16) | (fmt[7]<<24);
 
 				if(compression_code != 1)
 				{
@@ -512,15 +461,15 @@ int snd_load_wav(const char *filename)
 					return -1;
 				}
 				
-				if(snd.channels > 2)
+				if(snd->channels > 2)
 				{
 					dbg_msg("sound/wav", "file is not mono or stereo. filename='%s'", filename);
 					return -1;
 				}
 
-				if(snd.rate != 44100)
+				if(snd->rate != 44100)
 				{
-					dbg_msg("sound/wav", "file is %d Hz, not 44100 Hz. filename='%s'", snd.rate, filename);
+					dbg_msg("sound/wav", "file is %d Hz, not 44100 Hz. filename='%s'", snd->rate, filename);
 					return -1;
 				}
 				
@@ -542,21 +491,14 @@ int snd_load_wav(const char *filename)
 			// read the data
 			if(head[0] == 'd' && head[1] == 'a' && head[2] == 't' && head[3] == 'a')
 			{
-				snd.data = (short*)mem_alloc(chunk_size, 1);
-				io_read(file, snd.data, chunk_size);
-				snd.num_samples = chunk_size/(2);
+				snd->data = (short*)mem_alloc(chunk_size, 1);
+				io_read(file, snd->data, chunk_size);
+				snd->num_samples = chunk_size/(2);
 #if defined(CONF_ARCH_ENDIAN_BIG)
-				for(unsigned i = 0; i < (unsigned)snd.num_samples; i++)
-				{
-					unsigned j = i << 1;
-					snd.data[i] = ((short)((char*)snd.data)[j]) + ((short)((char*)snd.data)[j+1] << 8);
-				}
+				swap_endian(snd->data, sizeof(short), snd->num_samples);
 #endif
-				snd.sustain_start = -1;
-				snd.sustain_end = -1;
-				snd.last_played = 0;
-				id = snd_alloc_sound();
-				sounds[id].sound = snd;
+				snd->loop_start = -1;
+				snd->loop_end = -1;
 				state++;
 			}
 			else
@@ -580,8 +522,8 @@ int snd_load_wav(const char *filename)
 					io_read(file, loop, sizeof(loop));
 					unsigned start = (loop[8] | (loop[9]<<8) | (loop[10]<<16) | (loop[11]<<24));
 					unsigned end = (loop[12] | (loop[13]<<8) | (loop[14]<<16) | (loop[15]<<24));
-					sounds[id].sound.sustain_start = start * sounds[id].sound.channels;
-					sounds[id].sound.sustain_end = end * sounds[id].sound.channels;
+					snd->loop_start = start * snd->channels;
+					snd->loop_end = end * snd->channels;
 				}
 
 				if(num_loops > 1)
@@ -597,38 +539,47 @@ int snd_load_wav(const char *filename)
 			io_skip(file, chunk_size);
 	}
 
-	if(id >= 0)
-	{
-		if(config.debug)
-			dbg_msg("sound/wav", "loaded %s", filename);
-	}
-	else
-		dbg_msg("sound/wav", "failed to load %s", filename);
+	if(config.debug)
+		dbg_msg("sound/wav", "loaded %s", filename);
 
-	return id;
+	return sid;
 }
 
-int snd_play(int id, int loop, float vol, float pan)
+int snd_play(int cid, int sid, int loop, int x, int y)
 {
-	if(id < 0)
+	int vid;
+	for(vid = 0; vid < NUM_VOICES; vid++)
 	{
-		dbg_msg("snd", "bad sound id");
-		return -1;
+		if(voices[vid].sound == 0x0)
+		{
+			voices[vid].tick = 0;
+			voices[vid].x = x;
+			voices[vid].y = y;
+			voices[vid].sound = &sounds[sid];
+			if(loop == SND_LOOP)
+				voices[vid].loop = voices[vid].sound->loop_end;
+			else
+				voices[vid].loop = -1;
+
+			// add voice to channel last, to avoid threding errors
+			voices[vid].next = channels[cid].first_voice;
+			if(channels[cid].first_voice)
+				channels[cid].first_voice->prev = &voices[vid];
+			channels[cid].first_voice = &voices[vid];
+			return vid;
+		}
 	}
 
-	dbg_assert(sounds[id].sound.data != 0, "null sound");
-	dbg_assert(sounds[id].next == -1, "sound isn't allocated");
-	return mixer_play(&sounds[id].sound, loop, vol, pan);
+	return -1;
 }
 
-void snd_stop(int id)
+void snd_set_master_volume(float val)
 {
-	if(id >= 0)
-		mixer_stop(id);
+	master_vol = val;
 }
 
-void snd_set_vol(int id, float vol)
+void snd_stop(int vid)
 {
-	if(id >= 0)
-		mixer_set_vol(id, vol);
+	//TODO: lerp volume to 0
+	voices[vid].stop = 0;
 }