shithub: battleship

ref: b221c56a6bb90de4631f4b2eb999db8dad05a006
dir: /mixer.c/

View raw version
/*
 * standalone sound mixer based on rxi's cmixer.
 */
#include <u.h>
#include <libc.h>
#include <bio.h>
#include <thread.h>
#include "mixer.h"

static Mixer mixer;


static int wav_init(AudioSourceInfo*, void*, int, int);
static int mp3_init(AudioSourceInfo*, int);

static int
min(int a, int b)
{
	return a < b? a: b;
}

static int
max(int a, int b)
{
	return a > b? a: b;
}

static int
clamp(int n, int min, int max)
{
	return n < min? min: n > max? max: n;
}

static double
fclamp(double n, double min, double max)
{
	return n < min? min: n > max? max: n;
}

static int
float2fixed(double n)
{
	return n*FIXED_UNIT;
}

static int
fixedlerp(int a, int b, int t)
{
	return a + ((b - a)*t >> FIXED_BITS);
}

//static void
//fprintsource(int fd, AudioSource *src)
//{
//	fprint(fd, "src 0x%p:\n"
//		"udata\t0x%p\n"
//		"samplerate\t%d\n"
//		"length\t%d\n"
//		"end\t%d\n"
//		"state\t%d\n"
//		"position\t%lld\n"
//		"gain l/r\t%d/%d\n"
//		"rate\t%d\n"
//		"nextfill\t%d\n"
//		"loop\t%d\n"
//		"rewind\t%d\n"
//		"active\t%d\n"
//		"gain\t%g\n"
//		"pan\t%g\n",
//		src, src->udata, src->samplerate, src->length, src->end, src->state, src->position,
//		src->lgain, src->rgain, src->rate, src->nextfill, src->loop, src->rewind,
//		src->active, src->gain, src->pan);
//
//}

void
audio_init(int samplerate)
{
	mixer.samplerate = samplerate;
	mixer.sources = nil;
	mixer.gain = FIXED_UNIT;
}

void
audio_set_master_gain(double gain)
{
	mixer.gain = float2fixed(gain);
}

static void
rewind_source(AudioSource *src)
{
	AudioEvent e;

	e.type = AUDIO_EVENT_REWIND;
	e.udata = src->udata;
	src->handler(&e);
	src->position = 0;
	src->rewind = 0;
	src->end = src->length;
	src->nextfill = 0;
}

static void
fill_source_buffer(AudioSource *src, int offset, int length)
{
	AudioEvent e;

	e.type = AUDIO_EVENT_SAMPLES;
	e.udata = src->udata;
	e.buffer = src->buffer + offset;
	e.length = length;
	src->handler(&e);
}

static void
process_source(AudioSource *src, int len)
{
	int i, n, a, b, p;
	int frame, count;
	s32int *dst;

	dst = mixer.buffer;

	/* Do rewind if flag is set */
	if(src->rewind)
		rewind_source(src);

	/* Process audio */
	while(len > 0){
		/* Get current position frame */
		frame = src->position >> FIXED_BITS;

		/* Fill buffer if required */
		if(frame + 3 >= src->nextfill){
			fill_source_buffer(src, 2*src->nextfill & MIXBUFMASK, MIXBUFSIZE/2);
			src->nextfill += MIXBUFSIZE/4;
		}

		/* Handle reaching the end of the playthrough */
		if(frame >= src->end){
			/*
			 * As streams continously fill the raw buffer in a loop we simply
			 * increment the end idx by one length and continue reading from it for
			 * another play-through
			 */
			src->end = frame + src->length;
			/* Set state and stop processing if we're not set to loop */
			if(!src->loop){
				src->state = AUDIO_STATE_STOPPED;
				break;
			}
		}

		/* Work out how many frames we should process in the loop */
		n = min(src->nextfill - 2, src->end) - frame;
		count = (n << FIXED_BITS) / src->rate;
		count = max(count, 1);
		count = min(count, len/2);
		len -= count * 2;

		/* Add audio to master buffer */
		if(src->rate == FIXED_UNIT){
			/* Add audio to buffer -- basic */
			n = 2*frame;
			for(i = 0; i < count; i++){
				dst[0] += (src->buffer[n&MIXBUFMASK] * src->lgain) >> FIXED_BITS;
				dst[1] += (src->buffer[(n+1)&MIXBUFMASK] * src->rgain) >> FIXED_BITS;
				n += 2;
				dst += 2;
			}
			src->position += count * FIXED_UNIT;
		}else{
			/* Add audio to buffer -- interpolated */
			for(i = 0; i < count; i++){
				n = (src->position >> FIXED_BITS) * 2;
				p = src->position & FIXED_MASK;

				a = src->buffer[n & MIXBUFMASK];
				b = src->buffer[(n+2) & MIXBUFMASK];
				dst[0] += fixedlerp(a, b, p)*src->lgain >> FIXED_BITS;
				n++;
				a = src->buffer[n & MIXBUFMASK];
				b = src->buffer[(n+2) & MIXBUFMASK];
				dst[1] += fixedlerp(a, b, p)*src->rgain >> FIXED_BITS;

				src->position += src->rate;
				dst += 2;
			}
		}
	}
}

void
audio_process(s16int *dst, int len)
{
	int i, x;
	AudioSource **s;

	/* Process in chunks of MIXBUFSIZE if `len` is larger than MIXBUFSIZE */
	while(len > MIXBUFSIZE){
		audio_process(dst, MIXBUFSIZE);
		dst += MIXBUFSIZE;
		len -= MIXBUFSIZE;
	}

	/* Zeroset internal buffer */
	memset(mixer.buffer, 0, len * sizeof(mixer.buffer[0]));

	/* Process active sources */
	s = &mixer.sources;
	while(*s){
		process_source(*s, len);
		/* Remove source from list if it is no longer playing */
		if((*s)->state != AUDIO_STATE_PLAYING){
			(*s)->active = 0;
			*s = (*s)->next;
		}else
			s = &(*s)->next;
	}

	/* Copy internal buffer to destination and clip */
	for(i = 0; i < len; i++){
		x = (mixer.buffer[i] * mixer.gain) >> FIXED_BITS;
		dst[i] = clamp(x, -32768, 32767);
	}
}

AudioSource *
audio_new_source(AudioSourceInfo *info)
{
	AudioSource *src;

	src = malloc(sizeof *src);
	if(src == nil){
		werrstr("allocation failed");
		return nil;
	}

	memset(src, 0, sizeof *src);
	src->handler = info->handler;
	src->length = info->length;
	src->samplerate = info->samplerate;
	src->udata = info->udata;
	audio_set_gain(src, 1);
	audio_set_pan(src, 0);
	audio_set_pitch(src, 1);
	audio_set_loop(src, 0);
	audio_stop(src);
	return src;
}

static int
check_header(void *data, int size, char *str, int offset)
{
	int len;

	len = strlen(str);
	return size >= offset + len &&
		memcmp((char*)data + offset, str, len) == 0;
}

static AudioSource *
new_source_from_mem(void *data, int size, int ownsdata)
{
	AudioSourceInfo info;

	if(check_header(data, size, "WAVE", 8)){
		if(wav_init(&info, data, size, ownsdata) < 0)
			return nil;
		return audio_new_source(&info);
	}

	werrstr("unknown format or invalid data");
	return nil;
}

static void *
load_file(char *filename, int *size)
{
	Biobuf *fp;
	void *data;
	int n;

	fp = Bopen(filename, OREAD);
	if(fp == nil)
		return nil;

	Bseek(fp, 0, 2);
	*size = Boffset(fp);
	Bseek(fp, 0, 0);

	data = malloc(*size);
	if(data == nil){
		Bterm(fp);
		return nil;
	}
	n = Bread(fp, data, *size);
	Bterm(fp);
	if(n != *size){
		free(data);
		return nil;
	}

	return data;
}

AudioSource *
audio_new_source_from_file(char *filename)
{
	int size;
	AudioSource *src;
	void *data;

	/* Load file into memory */
	data = load_file(filename, &size);
	if(data == nil){
		werrstr("could not load file");
		return nil;
	}

	/* Try to load and return */
	src = new_source_from_mem(data, size, 1);
	if(src == nil){
		free(data);
		return nil;
	}

	return src;
}

AudioSource *
audio_new_source_from_mp3file(char *path)
{
	AudioSourceInfo info;
	uchar buf[3];
	int fd;

	fd = open(path, OREAD);
	if(fd < 0)
		return nil;

	memset(buf, 0, sizeof buf);
	readn(fd, buf, sizeof buf);
	seek(fd, 0, 0);
	if(memcmp(buf, "ID3", 3) != 0 && (buf[0] != 0xFF || buf[1] != 0xFB)){
		werrstr("bad mp3 file");
		close(fd);
		return nil;
	}

	if(mp3_init(&info, fd) < 0){
		close(fd);
		return nil;
	}
	close(fd);

	return audio_new_source(&info);
}

AudioSource *
audio_new_source_from_mem(void *data, int size)
{
	return new_source_from_mem(data, size, 0);
}

void
audio_destroy_source(AudioSource *src)
{
	AudioSource **s;
	AudioEvent e;

	if(src->active){
		s = &mixer.sources;
		while(*s) /* TODO potential spinlock. no bueno */
			if(*s == src){
				*s = src->next;
				break;
			}
	}

	e.type = AUDIO_EVENT_DESTROY;
	e.udata = src->udata;
	src->handler(&e);
	free(src);
}

double
audio_get_length(AudioSource *src)
{
	return src->length / (double)src->samplerate;
}

double
audio_get_position(AudioSource *src)
{
	return ((src->position >> FIXED_BITS) % src->length) / (double)src->samplerate;
}

int
audio_get_state(AudioSource *src)
{
	return src->state;
}

static void
recalc_source_gains(AudioSource *src)
{
	double l, r;
	double pan;

	pan = src->pan;
	l = src->gain * (pan <= 0 ? 1 : 1.0 - pan);
	r = src->gain * (pan >= 0 ? 1 : 1.0 + pan);
	src->lgain = float2fixed(l);
	src->rgain = float2fixed(r);
}

void
audio_set_gain(AudioSource *src, double gain)
{
	src->gain = gain;
	recalc_source_gains(src);
}

void
audio_set_pan(AudioSource *src, double pan)
{
	src->pan = fclamp(pan, -1.0, 1.0);
	recalc_source_gains(src);
}

void
audio_set_pitch(AudioSource *src, double pitch)
{
	double rate;

	if(pitch > 0)
		rate = (double)src->samplerate / mixer.samplerate * pitch;
	else
		rate = 0.001;
	src->rate = float2fixed(rate);
}

void
audio_set_loop(AudioSource *src, int loop)
{
	src->loop = loop;
}

void
audio_play(AudioSource *src)
{
	src->state = AUDIO_STATE_PLAYING;
	if(!src->active){
		src->active = 1;
		src->next = mixer.sources;
		mixer.sources = src;
	}
}

void
audio_pause(AudioSource *src)
{
	src->state = AUDIO_STATE_PAUSED;
}

void
audio_stop(AudioSource *src)
{
	src->state = AUDIO_STATE_STOPPED;
	src->rewind = 1;
}

/*
 * Wav stream processing
 */

static char *
find_subchunk(char *data, int len, char *id, int *size)
{
	/* TODO : Error handling on malformed wav file */
	int idlen;
	char *p;

	idlen = strlen(id);
	p = data+12;
next:
	*size = *((u32int*)(p+4));
	if(memcmp(p, id, idlen) != 0){
		p += 8 + *size;
		if(p > data + len)
			return nil;
		goto next;
	}
	return p+8;
}

static int
read_wav(Wav *w, void *data, int len)
{
	uint format, channels, samplerate, bitdepth;
	int sz;
	char *p;

	p = data;
	memset(w, 0, sizeof(*w));

	/* Check header */
	if(memcmp(p, "RIFF", 4) != 0 || memcmp(p+8, "WAVE", 4) != 0){
		werrstr("bad wav header");
		return -1;
	}

	/* Find fmt subchunk */
	p = find_subchunk(data, len, "fmt", &sz);
	if(p == nil){
		werrstr("no fmt subchunk");
		return -1;
	}

	/* Load fmt info */
	format		= *((u16int*)(p));
	channels	= *((u16int*)(p+2));
	samplerate	= *((u32int*)(p+4));
	bitdepth	= *((u16int*)(p+14));

	if(format != 1){
		werrstr("unsupported format");
		return -1;
	}
	if(channels == 0 || samplerate == 0 || bitdepth == 0){
		werrstr("bad format");
		return -1;
	}
	if(channels > 2 || (bitdepth != 16 && bitdepth != 8)){
		werrstr("unsupported wav format");
		return -1;
	}

	/* Find data subchunk */
	p = find_subchunk(data, len, "data", &sz);
	if(p == nil){
		werrstr("no data subchunk");
		return -1;
	}

	/* Init struct */
	w->data = (void*)p;
	w->samplerate = samplerate;
	w->channels = channels;
	w->length = (sz / (bitdepth/8)) / channels;
	w->bitdepth = bitdepth;
	return 0;
}

static void
wav_handler(AudioEvent *e)
{
	int x, n;
	s16int *dst;
	WavStream *s;
	int len;

	s = e->udata;

	switch(e->type){
	case AUDIO_EVENT_DESTROY:
		free(s->data);
		free(s);
		break;
	case AUDIO_EVENT_SAMPLES:
		dst = e->buffer;
		len = e->length/2;
fill:
		n = min(len, s->wav.length - s->idx);
		len -= n;
		switch(s->wav.bitdepth){
		case 16:
			if(s->wav.channels == 1){
				while(n--){
					dst[0] = dst[1] = ((s16int*)s->wav.data)[s->idx];
					dst += 2;
					s->idx++;
				}
			}else if(s->wav.channels == 2){
				while(n--){
					x = s->idx * 2;
					dst[0] = ((s16int*)s->wav.data)[x];
					dst[1] = ((s16int*)s->wav.data)[x+1];
					dst += 2;
					s->idx++;
				}
			}
			break;
		case 8:
			if(s->wav.channels == 1){
				while(n--){
					dst[0] = dst[1] = (((uchar*)s->wav.data)[s->idx] - 128) << 8;
					dst += 2;
					s->idx++;
				}
			}else if(s->wav.channels == 2){
				while(n--){
					x = s->idx * 2;
					dst[0] = (((uchar*)s->wav.data)[x] - 128) << 8;
					dst[1] = (((uchar*)s->wav.data)[x+1] - 128) << 8;
					dst += 2;
					s->idx++;
				}
			}
			break;
		}
		/* Loop back and continue filling buffer if we didn't fill the buffer */
		if(len > 0){
			s->idx = 0;
			goto fill;
		}
		break;
	case AUDIO_EVENT_REWIND:
		s->idx = 0;
		break;
	}
}

static int
wav_init(AudioSourceInfo *info, void *data, int len, int ownsdata)
{
	WavStream *stream;

	stream = malloc(sizeof *stream);
	if(stream == nil){
		werrstr("allocation failed");
		return -1;
	}
	memset(stream, 0, sizeof *stream);

	if(read_wav(&stream->wav, data, len) < 0)
		return -1;

	if(ownsdata)
		stream->data = data;
	stream->idx = 0;

	info->udata = stream;
	info->handler = wav_handler;
	info->samplerate = stream->wav.samplerate;
	info->length = stream->wav.length;

	return 0;
}

static void
pcm_handler(AudioEvent *e)
{
	Pcm *pcm;
	s16int *dst;
	int len, i, n;

	pcm = e->udata;

	switch(e->type){
	case AUDIO_EVENT_DESTROY:
		free(pcm->data);
		free(pcm);
		break;
	case AUDIO_EVENT_SAMPLES:
		dst = e->buffer;
		len = e->length/2;
Fillbuf:
		n = min(len, pcm->len - pcm->off);
		len -= n;
		while(n--){
			i = pcm->off * 2;
			dst[0] = ((s16int*)pcm->data)[i];
			dst[1] = ((s16int*)pcm->data)[i+1];
			dst += 2;
			pcm->off++;
		}
		if(len > 0){
			pcm->off = 0;
			goto Fillbuf;
		}
		break;
	case AUDIO_EVENT_REWIND:
		pcm->off = 0;
		break;
	}
}

static void
mp3decproc(void *arg)
{
	int *pfd, fd;

	pfd = arg;
	fd = pfd[2];

	close(pfd[0]);
	dup(fd, 0);
	close(fd);
	dup(pfd[1], 1);
	close(pfd[1]);

	execl("/bin/audio/mp3dec", "mp3dec", nil);
	threadexitsall("execl: %r");
}

static int
mp3_init(AudioSourceInfo *info, int fd)
{
	Pcm *pcm;
	void *data;
	uchar buf[1024];
	int pfd[3], n, len;

	data = nil;
	len = 0;

	if(pipe(pfd) < 0){
		werrstr("pipe: %r");
		return -1;
	}
	pfd[2] = fd;

	procrfork(mp3decproc, pfd, mainstacksize, RFFDG|RFNAMEG|RFNOTEG);
	close(pfd[1]);
	while((n = read(pfd[0], buf, sizeof buf)) > 0){
		data = realloc(data, len+n);
		if(data == nil){
			werrstr("realloc: %r");
			return -1;
		}
		memmove((uchar*)data+len, buf, n);
		len += n;
	}
	close(pfd[0]);

	pcm = malloc(sizeof *pcm);
	if(pcm == nil){
		free(data);
		werrstr("malloc: %r");
		return -1;
	}
	pcm->depth = 16;
	pcm->chans = 2;
	pcm->rate = 44100;
	pcm->data = data;
	pcm->len = len/(pcm->depth/8)/pcm->chans;

	info->udata = pcm;
	info->handler = pcm_handler;
	info->samplerate = pcm->rate;
	info->length = pcm->len;

//	fprint(2, "pcm 0x%p:\ndata 0x%p\nlen %d\ndepth %d\nchans %d\nrate %d\n",
//		pcm, pcm->data, pcm->len, pcm->depth, pcm->chans, pcm->rate);
//	fprint(2, "info 0x%p:\nudata 0x%p\nhandler 0x%p\nsamplerate %d\nlength %d\n",
//		info, info->udata, info->handler, info->samplerate, info->length);

	return 0;
}