aboutsummaryrefslogtreecommitdiff
path: root/mixer.c
diff options
context:
space:
mode:
Diffstat (limited to 'mixer.c')
-rw-r--r--mixer.c762
1 files changed, 762 insertions, 0 deletions
diff --git a/mixer.c b/mixer.c
new file mode 100644
index 0000000..d2eb2a9
--- /dev/null
+++ b/mixer.c
@@ -0,0 +1,762 @@
+/*
+ * 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;
+}