/* * standalone sound mixer based on rxi's cmixer. */ #include #include #include #include #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; }