ddnet/src/engine/client/snd.c
2007-12-11 23:10:07 +00:00

442 lines
8.6 KiB
C

/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */
#include <engine/system.h>
#include <engine/interface.h>
#include <engine/config.h>
#include <engine/external/portaudio/portaudio.h>
#include <engine/external/wavpack/wavpack.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
enum
{
NUM_SAMPLES = 512,
NUM_VOICES = 64,
NUM_CHANNELS = 16,
MAX_FRAMES = 1024
};
typedef struct
{
short *data;
int num_frames;
int rate;
int channels;
int loop_start;
int loop_end;
} SAMPLE;
typedef struct
{
int vol;
int pan;
} CHANNEL;
typedef struct VOICE_t
{
SAMPLE *snd;
CHANNEL *channel;
int tick;
int vol; /* 0 - 255 */
int flags;
int x, y;
} VOICE;
static SAMPLE samples[NUM_SAMPLES] = { {0} };
static VOICE voices[NUM_VOICES] = { {0} };
static CHANNEL channels[NUM_CHANNELS] = { {255, 0} };
static LOCK sound_lock = 0;
static int sound_enabled = 0;
static int center_x = 0;
static int center_y = 0;
static int mixing_rate = 48000;
void snd_set_channel(int cid, float vol, float pan)
{
channels[cid].vol = (int)(vol*255.0f);
channels[cid].pan = (int)(pan*255.0f); /* TODO: this is only on and off right now */
}
static int play(int cid, int sid, int flags, float x, float y)
{
int vid = -1;
int i;
lock_wait(sound_lock);
/* search for voice */
/* TODO: fix this linear search */
for(i = 0; i < NUM_VOICES; i++)
{
if(!voices[i].snd)
{
vid = i;
break;
}
}
/* voice found, use it */
if(vid != -1)
{
voices[vid].snd = &samples[sid];
voices[vid].channel = &channels[cid];
voices[vid].tick = 0;
voices[vid].vol = 255;
voices[vid].flags = flags;
voices[vid].x = (int)x;
voices[vid].y = (int)y;
}
lock_release(sound_lock);
return vid;
}
int snd_play_at(int cid, int sid, int flags, float x, float y)
{
return play(cid, sid, flags|SNDFLAG_POS, x, y);
}
int snd_play(int cid, int sid, int flags)
{
return play(cid, sid, flags, 0, 0);
}
void snd_stop(int vid)
{
/* TODO: a nice fade out */
lock_wait(sound_lock);
voices[vid].snd = 0;
lock_release(sound_lock);
}
/* TODO: there should be a faster way todo this */
static short int2short(int i)
{
if(i > 0x7fff)
return 0x7fff;
else if(i < -0x7fff)
return -0x7fff;
return i;
}
static int iabs(int i)
{
if(i<0)
return -i;
return i;
}
static void mix(short *final_out, unsigned frames)
{
int mix_buffer[MAX_FRAMES*2] = {0};
int i, s;
/* aquire lock while we are mixing */
lock_wait(sound_lock);
for(i = 0; i < NUM_VOICES; i++)
{
if(voices[i].snd)
{
/* mix voice */
VOICE *v = &voices[i];
int *out = mix_buffer;
int step = v->snd->channels; /* setup input sources */
short *in_l = &v->snd->data[v->tick*step];
short *in_r = &v->snd->data[v->tick*step+1];
int end = v->snd->num_frames-v->tick;
int rvol = v->channel->vol;
int lvol = v->channel->vol;
/* make sure that we don't go outside the sound data */
if(frames < end)
end = frames;
/* check if we have a mono sound */
if(v->snd->channels == 1)
in_r = in_l;
/* volume calculation */
if(v->flags&SNDFLAG_POS && v->channel->pan)
{
/* TODO: we should respect the channel panning value */
const int range = 1500; /* magic value, remove */
int dx = v->x - center_x;
int dy = v->y - center_y;
int dist = sqrt(dx*dx+dy*dy); /* double here. nasty */
int p = iabs(dx);
if(dist < range)
{
/* panning */
if(dx > 0)
lvol = ((range-p)*lvol)/range;
else
rvol = ((range-p)*rvol)/range;
/* falloff */
lvol = (lvol*(range-dist))/range;
rvol = (rvol*(range-dist))/range;
}
else
{
lvol = 0;
rvol = 0;
}
}
/* process all frames */
for(s = 0; s < end; s++)
{
*out++ += (*in_l)*lvol;
*out++ += (*in_r)*rvol;
in_l += step;
in_r += step;
v->tick++;
}
/* free voice if not used any more */
if(v->tick == v->snd->num_frames)
v->snd = 0;
}
}
/* release the lock */
lock_release(sound_lock);
{
int master_vol = config.snd_volume;
/* clamp accumulated values */
/* TODO: this seams slow */
for(i = 0; i < frames; i++)
{
int j = i<<1;
int vl = ((mix_buffer[j]*master_vol)/101)>>8;
int vr = ((mix_buffer[j+1]*master_vol)/101)>>8;
final_out[j] = int2short(vl);
final_out[j+1] = int2short(vr);
}
}
}
static int pacallback(const void *in, void *out, unsigned long frames, const PaStreamCallbackTimeInfo* time, PaStreamCallbackFlags status, void *user)
{
mix(out, frames);
return 0;
}
static PaStream *stream;
int snd_init()
{
PaStreamParameters params;
PaError err = Pa_Initialize();
sound_lock = lock_create();
if(!config.snd_enable)
return 0;
mixing_rate = config.snd_rate;
params.device = Pa_GetDefaultOutputDevice();
if(params.device < 0)
return 1;
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 */
mixing_rate, /* 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);
sound_enabled = 1;
return 0;
}
int snd_shutdown()
{
Pa_StopStream(stream);
Pa_Terminate();
lock_destroy(sound_lock);
return 0;
}
int snd_alloc_id()
{
/* TODO: linear search, get rid of it */
unsigned sid;
for(sid = 0; sid < NUM_SAMPLES; sid++)
{
if(samples[sid].data == 0x0)
return sid;
}
return -1;
}
static void rate_convert(int sid)
{
SAMPLE *snd = &samples[sid];
int num_frames = 0;
short *new_data = 0;
int i;
/* make sure that we need to convert this sound */
if(!snd->data || snd->rate == mixing_rate)
return;
/* allocate new data */
num_frames = (int)((snd->num_frames/(float)snd->rate)*mixing_rate);
new_data = mem_alloc(num_frames*snd->channels*sizeof(short), 1);
for(i = 0; i < num_frames; i++)
{
/* resample TODO: this should be done better, like linear atleast */
float a = i/(float)num_frames;
int f = (int)(a*snd->num_frames);
if(f >= snd->num_frames)
f = snd->num_frames-1;
/* set new data */
if(snd->channels == 1)
new_data[i] = snd->data[f];
else if(snd->channels == 2)
{
new_data[i*2] = snd->data[f*2];
new_data[i*2+1] = snd->data[f*2+1];
}
}
/* free old data and apply new */
mem_free(snd->data);
snd->data = new_data;
snd->num_frames = num_frames;
}
static FILE *file = NULL;
static int read_data(void *buffer, int size)
{
return fread(buffer, 1, size, file);
}
int snd_load_wv(const char *filename)
{
SAMPLE *snd;
int sid = -1;
char error[100];
WavpackContext *context;
/* don't waste memory on sound when we are stress testing */
if(config.dbg_stress)
return -1;
/* no need to load sound when we are running with no sound */
if(!sound_enabled)
return 1;
file = fopen(filename, "rb"); /* TODO: use system.h stuff for this */
if(!file)
{
dbg_msg("sound/wv", "failed to open %s", filename);
return -1;
}
sid = snd_alloc_id();
if(sid < 0)
return -1;
snd = &samples[sid];
context = WavpackOpenFileInput(read_data, error);
if (context)
{
int samples = WavpackGetNumSamples(context);
int bitspersample = WavpackGetBitsPerSample(context);
unsigned int samplerate = WavpackGetSampleRate(context);
int channels = WavpackGetNumChannels(context);
int *data;
int *src;
short *dst;
int i;
snd->channels = channels;
snd->rate = samplerate;
if(snd->channels > 2)
{
dbg_msg("sound/wv", "file is not mono or stereo. filename='%s'", filename);
return -1;
}
/*
if(snd->rate != 44100)
{
dbg_msg("sound/wv", "file is %d Hz, not 44100 Hz. filename='%s'", snd->rate, filename);
return -1;
}*/
if(bitspersample != 16)
{
dbg_msg("sound/wv", "bps is %d, not 16, filname='%s'", bitspersample, filename);
return -1;
}
data = (int *)mem_alloc(4*samples*channels, 1);
WavpackUnpackSamples(context, data, samples); /* TODO: check return value */
src = data;
snd->data = (short *)mem_alloc(2*samples*channels, 1);
dst = snd->data;
for (i = 0; i < samples*channels; i++)
*dst++ = (short)*src++;
mem_free(data);
snd->num_frames = samples;
snd->loop_start = -1;
snd->loop_end = -1;
}
else
{
dbg_msg("sound/wv", "failed to open %s: %s", filename, error);
}
fclose(file);
file = NULL;
if(config.debug)
dbg_msg("sound/wv", "loaded %s", filename);
rate_convert(sid);
return sid;
}
void snd_set_listener_pos(float x, float y)
{
center_x = (int)x;
center_y = (int)y;
}