shithub: neindaw

ref: 11bee08c7da60c752092e5ada1ede8a9dc916a68
dir: neindaw/fs.c

View raw version
#include <u.h>
#include <libc.h>
#include <fcall.h>
#include <thread.h>
#include <9p.h>
#include "common.h"
#include "ui.h"
#include "fs.h"

#define MAX(a,b) ((a)>=(b)?(a):(b))
#define MIN(a,b) ((a)<=(b)?(a):(b))
#define CROSS(a,b) (a <= 0.0f && a <= b && b >= 0.0f)
#define Silence 0.00003f

enum {
	Maxobjs = 64,
};

static Aux *objs[Maxobjs];

static Aux rootaux[] = {
	[Xctl] = {.type = Xctl},
	[Xmetadata] = {.type = Xmetadata},
	[Xclone] = {.type = Xclone},
};

extern File *uif;
extern State *uis;

static Fs *fs;

static void
shutup(State *s, Voice *v)
{
	if (s->voice != v && v->dsp != nil) {
		fs->dsp.free(v->dsp);
		v->dsp = nil;
	}
	v->state = Vsilent;
}

static Voice *
newvoice(State *s, Auxdsp *dsp)
{
	Voice *v, *f;
	int i;

	f = s->voice;
	for (i = 0, v = s->voice; i < nelem(s->voice); i++, v++) {
		if (v->dsp == nil) {
			f = v;
			break;
		}
		if (v->state == Vplaying && f->samples < v->samples)
			f = v;
	}

	shutup(s, f);
	f->dsp = dsp;

	return f;
}

static Aux *
newobj(char *name)
{
	File *f;
	Aux *o;
	Voice *v;
	int i, mode;

	for (i = 0, o = nil; o == nil && i < nelem(objs); i++) {
		if (objs[i] == nil){
			o = objs[i] = calloc(1, sizeof(*o));
			break;
		}
	}
	if (o == nil)
		return nil;

	o->id = i;
	o->type = Xdsp;
	o->ctl = Xdspctl;
	o->data = Xdspdata;

	sprint(name, "%d", o->id);
	if ((f = createfile(fs->srv.tree->root, name, nil, DMDIR|0775, o)) == nil)
		return nil;
	closefile(createfile(f, "ctl", nil, 0664, &o->ctl));
	mode = 0;
	if (fs->dsp.read != nil)
		mode |= 0444;
	if (fs->dsp.write != nil)
		mode |= 0222;
	closefile(createfile(f, "data", nil, mode, &o->data));

	uif = f;
	o->state = uis = calloc(1, sizeof(State));
	v = newvoice(o->state, fs->dsp.new(&o->numin, &o->numout, &o->rate));
	v->state = Vsilent;
	closefile(f);

	return o;
}

static void
freeobj(Aux *o)
{
	int i;

	if (o == nil)
		return;

	if (o->type == Xdsp) {
		objs[o->id] = nil;
		for (i = 0; i < nelem(o->state->voice); i++) {
			if (o->state->voice[i].dsp != nil)
				fs->dsp.free(o->state->voice[i].dsp);
		}
		free(o->state);
	}

	free(o);
}

static void *
auxtype2obj(int *type)
{
	switch (*type) {
	case Xdspctl:
	case Xuictl:
		return (uchar*)type - offsetof(Aux, ctl);
	case Xdspdata:
		return (uchar*)type - offsetof(Aux, data);
	case Xuimeta:
		return (uchar*)type - offsetof(Aux, metadata);
	default:
		sysfatal("trying to get aux out of type %d", *type);
	}

	return nil;
}

static void
fsopen(Req *r)
{
	respond(r, nil);
}

static int
auxreaddsp(Aux *a, float *b, int total)
{
	int i, j, n, cross, nzeromax;
	Voice *v;
	float *m;
	State *s;

	s = a->state;

	/* make sure we have a buffer for mixing */
	m = s->mixer;
	if (s->mixersz < total) {
		if ((m = realloc(m, total*sizeof(float))) != nil) {
			s->mixer = m;
			s->mixersz = total;
		} else {
			total = s->mixersz;
		}
	}

	/* silence first */
	memset(b, 0, sizeof(*b)*total);

	v = s->voice;
	cross = -1;
	a->numvoices = 0;
	nzeromax = a->rate / 5; /* 1/5th of a second, assuming it's mono */
	for (i = 0, n = total; i < nelem(s->voice) && n > 0; i++, v++) {
		if (v->dsp == nil)
			continue;

		switch (v->state) {
		case Vsilent: /* nothing to do here, ignore it */
			continue;
		case Vstarting: /* should start playing it */
			if (s->crossvoice && i > 0 && v[-1].state != Vsilent) {
				/* always start another voice at zero crossing to avoid clicks & pops */
				if (cross < 0) /* no crossing yet? give up for now */
					goto done;
				memset(b+cross, 0, sizeof(*b)*(n - cross)); /* silence the prev voice after zero crossing */
				cross++; /* leaving one as 0 */
				n -= cross;
				b += cross;
				cross = 0;
				shutup(s, &v[-1]);
			}
			v->state = Vplaying;
			v->samples = 0;
			v->nzero = 0;
			/* slippery floor */
		case Vplaying:
			if (n < 1)
				break;
			if (fs->dsp.read(v->dsp, m, n) != n)
				return -1;
			v->samples += n;

			for (j = 0; j < n; j++) {
				b[j] += m[j];
				if (s->crossvoice && cross < 0 && j > 0 && CROSS(m[j-1], m[j]))
					cross = j;
				if (m[j] >= -Silence && m[j] <= Silence)
					v->nzero++;
				else
					v->nzero = 0;
			}

			if (v->nzero >= nzeromax) { /* been quite for a while? shut it */
				cross = 0;
				shutup(s, v);
			} else {
				a->numvoices++;
			}
		}
	}

done:

	/* relocate the voices */
	for (i = j = 1; i < nelem(s->voice); i++) {
		s->voice[j] = s->voice[i];
		if(s->voice[j].dsp != nil)
			j++;
	}
	memset(s->voice+j, 0, sizeof(*s->voice)*(i - j));

	return total;
}

static void
auxreset(Aux *a)
{
	int i;

	for (i = 0; i < nelem(a->state->voice); i++) {
		if (a->state->voice[i].dsp != nil)
			fs->dsp.reset(a->state->voice[i].dsp);
	}
}

static void
fsread(Req *r)
{
	Aux *a, *o;
	char b[256];

	a = r->fid->file->aux;
	switch (a->type) {
	case Xctl:
		respond(r, nil);
		break;
	case Xdspctl:
		o = auxtype2obj(&a->type);
		sprint(b, "numin\t%d\nnumout\t%d\nrate\t%d\nnumvoices\t%d\n", o->numin, o->numout, o->rate, o->numvoices);
		readstr(r, b);
		respond(r, nil);
		break;
	case Xmetadata:
		readstr(r, fs->metadata);
		respond(r, nil);
		break;
	case Xclone:
		if (r->ifcall.offset == 0) {
			if (newobj(b) != nil) {
				readstr(r, b);
			} else {
				snprint(b, sizeof(b), "no free objects: %r");
				respond(r, b);
				break;
			}
		}
		respond(r, nil);
		break;
	case Xuictl:
	case Xuimeta:
		o = auxtype2obj(&a->type);
		if (o->ui->readstr != nil)
			readstr(r, o->ui->readstr(o->ui, a->type, b, sizeof(b)));
		respond(r, nil);
		break;
	case Xdspdata:
		o = auxtype2obj(&a->type);
		r->ofcall.count = auxreaddsp(o, (float*)r->ofcall.data, r->ifcall.count/sizeof(float))*sizeof(float);
		respond(r, nil);
		break;
	default:
		respond(r, "not implemented");
		break;
	}
}

static int
auxwrite(Aux *a, int type, char *data)
{
	State *s;
	Auxdsp *clone;
	Voice *v;
	void *so, *sc;
	u8int *tmp;
	int i, r, sz, nused;

	if (a->ui->writestr == nil) {
		werrstr("not implemented");
		return -1;
	}

	s = a->state;
	s->crossvoice |= a->ui->crossvoice;

	for (i = nused = 0; i < nelem(s->voice); i++) {
		if (s->voice[i].state != Vsilent)
			nused++;
	}

	/* autovoice means every write needs to use (possibly) a new instance */
	if (a->ui->autovoice && nused > 0 && fs->dsp.clone != nil && fs->dsp.state != nil) {
		/* now do the impossible */
		so = fs->dsp.state(s->voice[0].dsp, &sz);
		tmp = malloc(sz);
		memmove(tmp, so, sz); /* save the original state */
		/* write to the original and check if a new voice has to be created */
		if ((r = a->ui->writestr(a->ui, type, data)) < 0 || *a->ui->zone == 0.0f) {
			free(tmp);
			return r;
		}
		clone = fs->dsp.clone(s->voice[0].dsp); /* clone the original */
		sc = fs->dsp.state(clone, &sz);
		memmove(sc, so, sz); /* copy the changed state to the clone */
		memmove(so, tmp, sz); /* revert the original state */
		free(tmp);
		/* now we have the original dsp intact, with a cloned dsp actually having the changed state */

		v = newvoice(s, clone);
		v->state = Vstarting;

		return r;
	}

	/* in any other case, just write to the original */
	s->voice[0].state = Vstarting;
	return a->ui->writestr(a->ui, type, data);
}

static void
fswrite(Req *r)
{
	Aux *a, *o;
	char b[256];

	if (r->ifcall.count >= sizeof(b)) {
		respond(r, "can't fit into buffer");
		return;
	}

	memmove(b, r->ifcall.data, r->ifcall.count);
	b[r->ifcall.count] = '\0';
	r->ofcall.count = r->ifcall.count;

	a = r->fid->file->aux;
	switch (a->type) {
	case Xuictl:
		o = auxtype2obj(&a->type);
		if (auxwrite(o, a->type, b) >= 0)
			respond(r, nil);
		else
			responderror(r);
		break;
	case Xdspctl: /* FIXME changing sampling rate */
		o = auxtype2obj(&a->type);
		if (strncmp(b, "reset", 5) == 0) /* FIXME ui needs to be reset as well */
			auxreset(o);
		respond(r, nil);
		break;
	case Xmetadata: /* FIXME should be possible to add new key/value */
	default:
		respond(r, "not implemented");
		break;
	}
}

static void
fsdestroyfile(File *f)
{
	Aux *a;

	if ((a = f->aux) == nil)
		return;
	switch (a->type) {
	case Xdsp:
	case Xui:
		freeobj(a);
		f->aux = nil;
		break;
	}
}

void
fsinit(void *fs_)
{
	fs = fs_;
	fs->srv.open = fsopen;
	fs->srv.read = fsread;
	fs->srv.write = fswrite;
	fs->srv.tree = alloctree(nil, nil, DMDIR|0775, fsdestroyfile);
	closefile(createfile(fs->srv.tree->root, "ctl", nil, 0666, &rootaux[Xctl]));
	closefile(createfile(fs->srv.tree->root, "metadata", nil, 0444, &rootaux[Xmetadata]));
	closefile(createfile(fs->srv.tree->root, "clone", nil, 0444, &rootaux[Xclone]));
}