shithub: neindaw

Download patch

ref: c951f1864a755bb827da3031c03d9578b648b7a6
parent: dfa2da9cf2e227226350201c9c1d07c2194c2632
author: Sigrid Haflínudóttir <[email protected]>
date: Sat Mar 21 21:21:21 EDT 2020

split common logic out of AY and DSP

--- a/aux.h
+++ /dev/null
@@ -1,29 +1,0 @@
-typedef enum {
-	Xclone,
-	Xctl,
-	Xmetadata,
-
-	Xdsp,
-	Xdspctl,
-	Xdspdata,
-
-	Xui,
-	Xuictl,
-	Xuimeta,
-}Auxtype;
-
-typedef struct Aux Aux;
-typedef struct Auxdsp Auxdsp;
-
-struct UI;
-
-struct Aux {
-	Auxtype type;
-	int id;
-	int ctl;
-	int data;
-	int metadata;
-
-	Auxdsp *dsp;
-	struct UI *ui;
-};
--- a/ay/ay.c
+++ b/ay/ay.c
@@ -7,8 +7,8 @@
 #define CHIPS_ASSERT assert
 #include "ay38910.h"
 #include "common.h"
-#include "uiglue.h"
-#include "aux.h"
+#include "ui.h"
+#include "fs.h"
 
 #define MIN(a,b) ((a)<=(b)?(a):(b))
 #define MAX(a,b) ((a)>=(b)?(a):(b))
@@ -38,21 +38,8 @@
 	int envmode;
 };
 
-static Aux rootaux[] = {
-	[Xctl] = {.type = Xctl},
-	[Xmetadata] = {.type = Xmetadata},
-	[Xclone] = {.type = Xclone},
-};
-
 static int tickhz = 1773400;
-
-extern Srv fs;
-
 static int rate = 44100;
-static Aux *objs[32];
-static char *meta =
-	"name\tAY-3-8910\n"
-	"group\tSynthesis\n";
 
 static void
 regw(ay38910_t *ay, int reg, int v)
@@ -106,9 +93,9 @@
 }
 
 static int
-writeay(UI *ui, int auxtype, char *s)
+writectl(UI *ui, int auxtype, char *s)
 {
-	Auxdsp *dsp;
+	struct Auxdsp *dsp;
 	ay38910_t *ay;
 	int r, i, level;
 	vlong pd;
@@ -187,254 +174,121 @@
 	return 0;
 }
 
-static void
-buildui(Auxdsp *dsp, UIGlue *ui)
+#define BIND(x) do { ui = x; ui->userdata = dsp; ui->writestr = writectl; writectl(ui, Xuictl, "reset"); } while(0)
+
+extern File *uif;
+
+static Auxdsp *
+dspnew(void)
 {
-	float min, step, max;
+	struct Auxdsp *dsp;
+	UI *ui;
 	char s[32];
+	float min, step, max;
+	ay38910_desc_t desc = {
+		.type = AY38910_TYPE_8910,
+		.tick_hz = tickhz,
+		.sound_hz = rate,
+		.magnitude = 1.0,
+	};
 	int i;
 
+	if ((dsp = malloc(sizeof(*dsp))) == nil)
+		return nil;
+	ay38910_init(&dsp->ay, &desc);
+	regw(&dsp->ay, AY38910_REG_ENABLE, 0xff); /* disable everything */
+
 	min = ceil(tickhz/65520);
 	max = floor(tickhz/16);
 	step = MAX(1, ceil(tickhz/65504 - tickhz/65520));
 
-	ui->userdata = dsp;
-	ui->writestr = writeay;
-	ui->openVerticalBox(ui->f, "AY-3-8910");
-	ui->addHorizontalSlider(ui->f, "Volume", &dsp->ay.mag, 1.0f, 0.0f, 1.0f, 0.001f);
+	ui_vgroup("AY-3-8910");
 
+	BIND(ui_hslider("Volume", &dsp->ay.mag, 1.0f, 0.0f, 1.0f, 0.001f));
+
 	for (i = 0; i < nelem(dsp->chan); i++) {
 		sprint(s, "%c", 'A'+i);
-		ui->openVerticalBox(ui->f, s);
+		ui_tgroup(s);
+			ui_hgroup("Tone");
+				ui_declare(&dsp->chan[i].freq, "0", "");
+				ui_declare(&dsp->chan[i].freq, "unit", "Hz");
+				BIND(ui_hslider("Frequency", &dsp->chan[i].freq, min, min, max, step));
 
-		ui->openHorizontalBox(ui->f, "Tone");
-		ui->declare(ui->f, &dsp->chan[i].freq, "0", "");
-		ui->declare(ui->f, &dsp->chan[i].freq, "unit", "Hz");
-		ui->addHorizontalSlider(ui->f, "Frequency", &dsp->chan[i].freq, min, min, max, step);
-		ui->declare(ui->f, &dsp->chan[i].enable, "1", "");
-		ui->addCheckButton(ui->f, "Enable", &dsp->chan[i].enable);
-		ui->closeBox(ui->f);
+				ui_declare(&dsp->chan[i].enable, "1", "");
+				BIND(ui_checkbox("Enable", &dsp->chan[i].enable));
+			ui_endgroup();
 
-		ui->declare(ui->f, &dsp->chan[i].amp, "0", "");
-		ui->addHorizontalSlider(ui->f, "Volume", &dsp->chan[i].amp, 1.0f, 0.0f, 1.0f, 1.0f/15.0f);
+			ui_declare(&dsp->chan[i].amp, "0", "");
+			BIND(ui_hslider("Volume", &dsp->chan[i].amp, 1.0f, 0.0f, 1.0f, 1.0f/15.0f));
 
-		ui->declare(ui->f, &dsp->chan[i].envelope, "1", "");
-		ui->addCheckButton(ui->f, "Envelope", &dsp->chan[i].envelope);
+			ui_declare(&dsp->chan[i].envelope, "1", "");
+			BIND(ui_checkbox("Envelope", &dsp->chan[i].envelope));
 
-		ui->declare(ui->f, &dsp->chan[i].noise, "2", "");
-		ui->addCheckButton(ui->f, "Noise", &dsp->chan[i].noise);
-
-		ui->closeBox(ui->f);
+			ui_declare(&dsp->chan[i].noise, "2", "");
+			BIND(ui_checkbox("Noise", &dsp->chan[i].noise));
+		ui_endgroup();
 	}
 
 	min = ceil(tickhz/496);
 	max = floor(tickhz/16);
 	step = MAX(1, tickhz/480 - tickhz/496);
-	ui->declare(ui->f, &dsp->noisefreq, "unit", "Hz");
-	ui->addHorizontalSlider(ui->f, "Noise", &dsp->noisefreq, min, min, max, step);
+	ui_declare(&dsp->noisefreq, "unit", "Hz");
+	BIND(ui_hslider("Noise", &dsp->noisefreq, min, min, max, step));
 
-	ui->openVerticalBox(ui->f, "Envelope");
-	min = MAX(1, 256000/tickhz);
-	max = floor(16776960000LL/tickhz);
-	ui->declare(ui->f, &dsp->envperiod, "0", "");
-	ui->declare(ui->f, &dsp->envperiod, "unit", "ms");
-	ui->addHorizontalSlider(ui->f, "Period", &dsp->envperiod, 500, min, max, 1);
-	ui->declare(ui->f, &dsp->hold, "1", "");
-	ui->addCheckButton(ui->f, "Hold", &dsp->hold);
-	ui->declare(ui->f, &dsp->alternate, "2", "");
-	ui->addCheckButton(ui->f, "Alternate", &dsp->alternate);
-	ui->declare(ui->f, &dsp->attack, "3", "");
-	ui->addCheckButton(ui->f, "Attack", &dsp->attack);
-	ui->declare(ui->f, &dsp->cont, "4", "");
-	ui->addCheckButton(ui->f, "Continue", &dsp->cont);
-	ui->declare(ui->f, &dsp->hit, "5", "");
-	ui->addButton(ui->f, "Hit", &dsp->hit);
-	ui->closeBox(ui->f);
+	ui_vgroup("Envelope");
+		min = MAX(1, 256000/tickhz);
+		max = floor(16776960000LL/tickhz);
+		ui_declare(&dsp->envperiod, "0", "");
+		ui_declare(&dsp->envperiod, "unit", "ms");
+		BIND(ui_hslider("Period", &dsp->envperiod, 500, min, max, 1));
 
-	ui->closeBox(ui->f);
-}
+		ui_declare(&dsp->hold, "1", "");
+		BIND(ui_checkbox("Hold", &dsp->hold));
 
-static Aux *
-newobj(char *name)
-{
-	File *f;
-	Aux *o;
-	Auxdsp *dsp;
-	int i;
-	ay38910_desc_t d = {
-		.type = AY38910_TYPE_8910,
-		.tick_hz = tickhz,
-		.sound_hz = rate,
-		.magnitude = 1.0,
-	};
+		ui_declare(&dsp->alternate, "2", "");
+		BIND(ui_checkbox("Alternate", &dsp->alternate));
 
-	for (i = 0, o = nil; o == nil && i < nelem(objs); i++) {
-		if (objs[i] == nil){
-			o = objs[i] = calloc(1, sizeof(*o) + sizeof(Auxdsp));
-			break;
-		}
-	}
-	if (o == nil)
-		return nil;
+		ui_declare(&dsp->attack, "3", "");
+		BIND(ui_checkbox("Attack", &dsp->attack));
 
-	o->id = i;
-	o->type = Xdsp;
-	o->ctl = Xdspctl;
-	o->data = Xdspdata;
-	o->dsp = dsp = (Auxdsp*)(o+1);
-	ay38910_init(&dsp->ay, &d);
-	regw(&dsp->ay, AY38910_REG_ENABLE, 0xff); /* disable everything */
+		ui_declare(&dsp->cont, "4", "");
+		BIND(ui_checkbox("Continue", &dsp->cont));
 
-	sprint(name, "%d", o->id);
-	if ((f = createfile(fs.tree->root, name, nil, DMDIR|0775, o)) == nil)
-		return nil;
-	closefile(createfile(f, "ctl", nil, 0664, &o->ctl));
-	closefile(createfile(f, "data", nil, 0664, &o->data));
-	closefile(f);
+		ui_declare(&dsp->hit, "5", "");
+		BIND(ui_button("Hit", &dsp->hit));
+	ui_endgroup();
 
-	uiglue.f = f;
-	buildui(dsp, &uiglue);
+	ui_endgroup();
 
-	return o;
+	return dsp;
 }
 
-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)
+dspfree(Auxdsp *dsp)
 {
-	respond(r, nil);
+	free(dsp);
 }
 
 static void
-fsread(Req *r)
+dspreset(Auxdsp *dsp)
 {
-	Aux *a, *o;
-	Auxdsp *dsp;
-	char b[256];
-	float *p;
-	int n;
-
-	a = r->fid->file->aux;
-	switch (a->type) {
-	case Xctl:
-		respond(r, nil);
-		break;
-	case Xmetadata:
-		readstr(r, meta);
-		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);
-		dsp = o->dsp;
-		n = r->ifcall.count;
-		for (p = (float*)r->ofcall.data; n >= sizeof(float); p++) {
-			while (!ay38910_tick(&dsp->ay));
-			*p = dsp->ay.sample;
-			n -= sizeof(float);
-		}
-		r->ofcall.count = r->ifcall.count - n;
-		respond(r, nil);
-		break;
-	default:
-		respond(r, "not implemented");
-		break;
-	}
+	ay38910_reset(&dsp->ay);
 }
 
-static void
-fswrite(Req *r)
+static int
+dspread(Auxdsp *dsp, float *b, int n)
 {
-	Aux *a, *o;
-	char b[256];
+	int i;
 
-	if (r->ifcall.count >= sizeof(b)) {
-		respond(r, "can't fit into buffer");
-		return;
+	for (i = 0; i < n; i++) {
+		while (!ay38910_tick(&dsp->ay));
+		b[i] = dsp->ay.sample;
 	}
 
-	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 (o->ui->writestr == nil)
-			respond(r, "not implemented");
-		else if (o->ui->writestr(o->ui, 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 */
-			ay38910_reset(&o->dsp->ay);
-		respond(r, nil);
-		break;
-	case Xmetadata: /* FIXME should be possible to add new key/value */
-	default:
-		respond(r, "not implemented");
-		break;
-	}
+	return n;
 }
 
-Srv fs = {
-	.open = fsopen,
-	.read = fsread,
-	.write = fswrite,
-};
-
 static void
-freeobj(Aux *o)
-{
-	if (o == nil)
-		return;
-
-	if (o->type == Xdsp)
-		objs[o->id] = nil;
-
-	free(o);
-}
-
-static void
 usage(void)
 {
 	print("usage: %s [-s srv] [-m mtpt] [-r rate] [-t HZ]\n", argv0);
@@ -441,22 +295,16 @@
 	threadexitsall("usage");
 }
 
-static void
-fsdestroyfile(File *f)
-{
-	Aux *a;
+static Fs fs = {
+	.metadata ="name\tAY-3-8910\ngroup\tSynthesis\n",
+	.dsp = {
+		.new = dspnew,
+		.free = dspfree,
+		.reset = dspreset,
+		.read = dspread,
+	},
+};
 
-	if ((a = f->aux) == nil)
-		return;
-	switch (a->type) {
-	case Xdsp:
-	case Xui:
-		freeobj(a);
-		f->aux = nil;
-		break;
-	}
-}
-
 void
 threadmain(int argc, char **argv)
 {
@@ -490,10 +338,7 @@
 	if (srv == nil && mtpt == nil)
 		sysfatal("must specify -s or -m option");
 
-	fs.tree = alloctree(nil, nil, DMDIR|0775, fsdestroyfile);
-	closefile(createfile(fs.tree->root, "ctl", nil, 0666, &rootaux[Xctl]));
-	closefile(createfile(fs.tree->root, "metadata", nil, 0444, &rootaux[Xmetadata]));
-	closefile(createfile(fs.tree->root, "clone", nil, 0444, &rootaux[Xclone]));
-	threadpostmountsrv(&fs, srv, mtpt, MREPL);
+	fsinit(&fs);
+	threadpostmountsrv(&fs.srv, srv, mtpt, MREPL);
 	threadexits(nil);
 }
--- a/ay/mkfile
+++ b/ay/mkfile
@@ -1,20 +1,11 @@
 </$objtype/mkfile
 
 TARG=ay
-CFLAGS=$CFLAGS -p -I..
-BIN=/$objtype/bin/daw
 
 OFILES=\
 	ay.$O\
-	common.$O\
-	uiglue.$O\
 
 default:V:	all
 
-</sys/src/cmd/mkone
-
-common.$O: ../common.c
-	$CC $CFLAGS $prereq
-
-uiglue.$O: ../uiglue.c
-	$CC $CFLAGS $prereq
+MANY=one
+<../mkfs
--- a/cfg/cfg.c
+++ b/cfg/cfg.c
@@ -19,6 +19,8 @@
 	const char *label;
 	int index;
 
+	uvlong qidpath;
+
 	int ivalue;
 	double value;
 	double init;
@@ -136,7 +138,7 @@
 newui(char *path)
 {
 	UI *ui;
-	Dir *dirs;
+	Dir *dirs, *d;
 	char *s, *name, tmp[64];
 	long i, n;
 	int f;
@@ -170,7 +172,10 @@
 			ui->flags |= Hasclone;
 		} else if (strcmp(name, "ctl") == 0) {
 			ui->ctl = open(s, ORDWR);
-			readctl(ui);
+			if (readctl(ui) == 0 && ui->ctl >= 0 && (d = dirfstat(ui->ctl)) != nil) {
+				ui->qidpath = d->qid.path;
+				free(d);
+			}
 		} else if (dirs[i].mode & DMDIR) {
 			ui->child = realloc(ui->child, (ui->numchild+1) * sizeof(*ui->child));
 			ui->child[ui->numchild++] = newui(s);
--- a/common.h
+++ b/common.h
@@ -1,4 +1,4 @@
-enum {
+typedef enum {
 	UITGroup,
 	UIHGroup,
 	UIVGroup,
@@ -10,6 +10,6 @@
 	UIHBarGraph,
 	UIVBarGraph,
 	UInum,
-};
+}UItype;
 
 extern char *uitypenames[UInum];
--- a/dsp/fs.c
+++ /dev/null
@@ -1,331 +1,0 @@
-#include <u.h>
-#include <libc.h>
-#include <fcall.h>
-#include <thread.h>
-#include <9p.h>
-#include "uiglue.h"
-typedef struct DSP DSP;
-#include "dspf.h"
-#include "common.h"
-#include "aux.h"
-
-enum {
-	Inmax = 2048, /* float = 8192 bytes */
-	Outmax = 2048, /* float = 8192 bytes */
-};
-
-struct Auxdsp {
-	void *dsp;
-	float **in, **out;
-	int numin, numout;
-	int inmax, outmax;
-};
-
-static Aux rootaux[] = {
-	[Xctl] = {.type = Xctl},
-	[Xmetadata] = {.type = Xmetadata},
-	[Xclone] = {.type = Xclone},
-};
-static Aux *objs[32];
-static char *meta = nil;
-static int metalen = 0;
-static int rate = 44100;
-static DSPf *dspf;
-extern Srv fs;
-
-static char Elocked[] = "file locked";
-
-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 Aux *
-newobj(char *name)
-{
-	File *f;
-	Aux *o;
-	Auxdsp *dsp;
-	int i;
-
-	for (i = 0, o = nil; o == nil && i < nelem(objs); i++) {
-		if (objs[i] == nil){
-			o = objs[i] = calloc(1, sizeof(*o)+sizeof(Auxdsp));
-			break;
-		}
-	}
-	if (o == nil)
-		return nil;
-
-	o->id = i;
-	o->type = Xdsp;
-	o->ctl = Xdspctl;
-	o->data = Xdspdata;
-	o->dsp = dsp = (Auxdsp*)(o+1);
-	dsp->dsp = dspf->new();
-
-	dsp->in = dsp->out = nil;
-	if ((dsp->numin = dspf->num_in(dsp->dsp)) > 0) {
-		dsp->in = malloc(sizeof(*dsp->in) * dsp->numin);
-		dsp->inmax = Inmax;
-		for (i = 0; i < dsp->numin; i++)
-			dsp->in[i] = malloc(sizeof(**dsp->in) * dsp->inmax);
-	}
-	if ((dsp->numout = dspf->num_out(dsp->dsp)) > 0) {
-		dsp->out = malloc(sizeof(*dsp->out) * dsp->numout);
-		dsp->outmax = Outmax;
-		for (i = 0; i < dsp->numout; i++)
-			dsp->out[i] = malloc(sizeof(**dsp->out) * dsp->outmax);
-	}
-
-	sprint(name, "%d", o->id);
-	if ((f = createfile(fs.tree->root, name, nil, DMDIR|0775, o)) == nil)
-		return nil;
-	closefile(createfile(f, "ctl", nil, 0664, &o->ctl));
-	closefile(createfile(f, "data", nil, 0664, &o->data));
-	closefile(f);
-	dspf->init(dsp->dsp, rate);
-	uiglue.f = f;
-	dspf->build_ui(dsp->dsp, &uiglue);
-
-	return o;
-}
-
-static void
-freeobj(Aux *o)
-{
-	int i;
-
-	if (o == nil)
-		return;
-
-	if (o->type == Xdsp) {
-		objs[o->id] = nil;
-		dspf->delete(o->dsp->dsp);
-		for (i = 0; i < o->dsp->numin; i++)
-			free(o->dsp->in[i]);
-		free(o->dsp->in);
-		for (i = 0; i < o->dsp->numout; i++)
-			free(o->dsp->out[i]);
-		free(o->dsp->out);
-	}
-
-	free(o);
-}
-
-static void
-addmeta(void *metaInterface, const char *k, const char *v)
-{
-	int klen, vlen;
-
-	USED(metaInterface);
-
-	if (strchr(k, '/') != nil) /* ignore library-specific meta */ 
-		return;
-
-	klen = strlen(k);
-	vlen = strlen(v);
-	meta = realloc(meta, metalen + klen + 1 + vlen + 2);
-	strcpy(meta+metalen, k);
-	metalen += klen;
-	meta[metalen++] = '\t';
-	strcpy(meta+metalen, v);
-	metalen += vlen;
-	meta[metalen++] = '\n';
-	meta[metalen] = 0;
-}
-
-static void
-fsopen(Req *r)
-{
-	respond(r, nil);
-}
-
-static void
-fsread(Req *r)
-{
-	Aux *a, *o;
-	Auxdsp *dsp;
-	char b[256];
-	float *p;
-	int i, j, n, numframes, framesz;
-
-	a = r->fid->file->aux;
-	switch (a->type) {
-	case Xctl:
-		respond(r, nil);
-		break;
-	case Xmetadata:
-		readstr(r, meta);
-		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);
-		dsp = o->dsp;
-		if (r->ifcall.offset == 0) /* clear every time the offset is reset */
-			dspf->clear(dsp->dsp);
-		framesz = dsp->numout * sizeof(*p);
-		n = r->ifcall.count;
-		for (p = (float*)r->ofcall.data; n >= framesz;) {
-			numframes = n / framesz;
-			if (numframes > dsp->outmax)
-				numframes = dsp->outmax;
-			dspf->compute(dsp->dsp, numframes, dsp->in, dsp->out);
-			for (i = 0; i < numframes; i++) {
-				for (j = 0; j < dsp->numout; j++)
-					*p++ = dsp->out[j][i];
-			}
-			n -= numframes * framesz;
-		}
-		r->ofcall.count = r->ifcall.count - n;
-		respond(r, nil);
-		break;
-	default:
-		respond(r, "not implemented");
-		break;
-	}
-}
-
-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 (o->ui->writestr == nil)
-			respond(r, "not implemented");
-		else if (o->ui->writestr(o->ui, a->type, b) >= 0)
-			respond(r, nil);
-		else
-			responderror(r);
-		break;
-	case Xdspctl: /* FIXME changing sampling rate */
-		o = auxtype2obj(&a->type);
-		if (strncmp(b, "clear", 5) == 0)
-			dspf->clear(o->dsp->dsp);
-		else if (strncmp(b, "reset", 5) == 0)
-			dspf->reset_ui(o->dsp->dsp);
-		else if (strncmp(b, "init", 4) == 0)
-			dspf->init(o->dsp->dsp, rate);
-		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;
-	}
-}
-
-Srv fs = {
-	.open = fsopen,
-	.read = fsread,
-	.write = fswrite,
-};
-
-static void
-usage(void)
-{
-	print("usage: %s [-s srv] [-m mtpt] [-r rate]\n", argv0);
-	exits("usage");
-}
-
-void
-threadmain(int argc, char **argv)
-{
-	char *srv, *mtpt;
-	MetaGlue mg;
-
-	srv = nil;
-	mtpt = nil;
-	ARGBEGIN{
-	case 'D':
-		chatty9p++;
-		break;
-	case 's':
-		srv = EARGF(usage());
-		break;
-	case 'm':
-		mtpt = EARGF(usage());
-		break;
-	case 'r':
-		rate = atoi(EARGF(usage()));
-		break;
-	default:
-		usage();
-	}ARGEND
-
-	if (srv == nil && mtpt == nil)
-		sysfatal("must specify -s or -m option");
-
-	mg.declare = addmeta;
-	dspf = class_init(rate);
-	dspf->metadata(&mg);
-
-	fs.tree = alloctree(nil, nil, DMDIR|0775, fsdestroyfile);
-	closefile(createfile(fs.tree->root, "ctl", nil, 0666, &rootaux[Xctl]));
-	closefile(createfile(fs.tree->root, "metadata", nil, 0444, &rootaux[Xmetadata]));
-	closefile(createfile(fs.tree->root, "clone", nil, 0444, &rootaux[Xclone]));
-	threadpostmountsrv(&fs, srv, mtpt, MREPL);
-	threadexits(nil);
-}
--- /dev/null
+++ b/dsp/main.c
@@ -1,0 +1,169 @@
+#include <u.h>
+#include <libc.h>
+#include <fcall.h>
+#include <thread.h>
+#include <9p.h>
+#include "uiglue.h"
+typedef struct DSP DSP;
+#include "dspf.h"
+#include "common.h"
+#include "fs.h"
+#include "ui.h"
+
+enum {
+	Inmax = 2048, /* float = 8192 bytes */
+	Outmax = 2048, /* float = 8192 bytes */
+};
+
+struct Auxdsp {
+	void *dsp;
+	float **in, **out;
+	int numin, numout;
+	int inmax, outmax;
+};
+
+static int rate = 44100;
+static DSPf *dspf;
+
+static Auxdsp *
+dspnew(void)
+{
+	Auxdsp *dsp;
+	int i;
+
+	if ((dsp = calloc(1, sizeof(*dsp))) == nil)
+		return nil;
+
+	dsp->dsp = dspf->new();
+
+	dsp->in = dsp->out = nil;
+	if ((dsp->numin = dspf->num_in(dsp->dsp)) > 0) {
+		dsp->in = malloc(sizeof(*dsp->in) * dsp->numin);
+		dsp->inmax = Inmax;
+		for (i = 0; i < dsp->numin; i++)
+			dsp->in[i] = malloc(sizeof(**dsp->in) * dsp->inmax);
+	}
+	if ((dsp->numout = dspf->num_out(dsp->dsp)) > 0) {
+		dsp->out = malloc(sizeof(*dsp->out) * dsp->numout);
+		dsp->outmax = Outmax;
+		for (i = 0; i < dsp->numout; i++)
+			dsp->out[i] = malloc(sizeof(**dsp->out) * dsp->outmax);
+	}
+
+	dspf->init(dsp->dsp, rate);
+	dspf->build_ui(dsp->dsp, &uiglue);
+
+	return dsp;
+}
+
+static void
+dspfree(Auxdsp *dsp)
+{
+	int i;
+
+	dspf->delete(dsp->dsp);
+	for (i = 0; i < dsp->numin; i++)
+		free(dsp->in[i]);
+	free(dsp->in);
+	for (i = 0; i < dsp->numout; i++)
+		free(dsp->out[i]);
+	free(dsp->out);
+	free(dsp);
+}
+
+static void
+dspreset(Auxdsp *dsp)
+{
+	dspf->reset_ui(dsp->dsp);
+}
+
+static int
+dspread(Auxdsp *dsp, float *b, int n)
+{
+	int i, j, numframes;
+
+	numframes = n / dsp->numout;
+	dspf->compute(dsp->dsp, numframes, dsp->in, dsp->out);
+	for (i = 0; i < numframes; i++) {
+		for (j = 0; j < dsp->numout; j++)
+			*b++ = dsp->out[j][i];
+	}
+
+	return n;
+}
+
+static void
+usage(void)
+{
+	print("usage: %s [-s srv] [-m mtpt] [-r rate]\n", argv0);
+	exits("usage");
+}
+
+static Fs fs = {
+	.dsp = {
+		.new = dspnew,
+		.free = dspfree,
+		.reset = dspreset,
+		.read = dspread,
+	},
+};
+
+static void
+addmeta(void *metaInterface, const char *k, const char *v)
+{
+	int klen, vlen;
+	static int metalen;
+
+	USED(metaInterface);
+
+	if (strchr(k, '/') != nil) /* ignore library-specific meta */ 
+		return;
+
+	klen = strlen(k);
+	vlen = strlen(v);
+	fs.metadata = realloc(fs.metadata, metalen + klen + 1 + vlen + 2);
+	strcpy(fs.metadata+metalen, k);
+	metalen += klen;
+	fs.metadata[metalen++] = '\t';
+	strcpy(fs.metadata+metalen, v);
+	metalen += vlen;
+	fs.metadata[metalen++] = '\n';
+	fs.metadata[metalen] = 0;
+}
+
+void
+threadmain(int argc, char **argv)
+{
+	char *srv, *mtpt;
+	MetaGlue mg;
+
+	srv = nil;
+	mtpt = nil;
+	ARGBEGIN{
+	case 'D':
+		chatty9p++;
+		break;
+	case 's':
+		srv = EARGF(usage());
+		break;
+	case 'm':
+		mtpt = EARGF(usage());
+		break;
+	case 'r':
+		rate = atoi(EARGF(usage()));
+		break;
+	default:
+		usage();
+	}ARGEND
+
+	if (srv == nil && mtpt == nil)
+		sysfatal("must specify -s or -m option");
+
+	mg.declare = addmeta;
+	dspf = class_init(rate);
+	dspf->metadata(&mg);
+
+	fsinit(&fs);
+	threadpostmountsrv(&fs.srv, srv, mtpt, MREPL);
+	threadexits(nil);
+}
--- a/dsp/mkfile
+++ b/dsp/mkfile
@@ -6,28 +6,19 @@
 BIN=/$objtype/bin/daw
 
 OFILES=\
-	common.$O\
-	fs.$O\
+	main.$O\
 	uiglue.$O\
 
 CFLAGS=$CFLAGS -I..
 
-default:V:	all
-
 CLEANFILES=\
 
-</sys/src/cmd/mkmany
+MANY=many
 
+<../mkfs
+
 %.c: %.dsp
 	os -d `{../osdir} ./dsp2c $"prereq || \
 	{ echo $target needs to be regenerated with "./dsp2c $prereq"; exit 1 }
 
-$BIN/%:	$O.%
-	mkdir -p $BIN
-	cp $O.$stem $BIN/$stem
-
-common.$O: ../common.c
-	$CC $CFLAGS $prereq
-
-uiglue.$O: ../uiglue.c
-	$CC $CFLAGS $prereq
+</sys/src/cmd/mkmany
--- /dev/null
+++ b/fs.c
@@ -1,0 +1,214 @@
+#include <u.h>
+#include <libc.h>
+#include <fcall.h>
+#include <thread.h>
+#include <9p.h>
+#include "common.h"
+#include "ui.h"
+#include "fs.h"
+
+enum {
+	Maxobjs = 32,
+};
+
+static Aux *objs[Maxobjs];
+
+static Aux rootaux[] = {
+	[Xctl] = {.type = Xctl},
+	[Xmetadata] = {.type = Xmetadata},
+	[Xclone] = {.type = Xclone},
+};
+
+extern File *uif;
+
+static Fs *fs;
+
+static Aux *
+newobj(char *name)
+{
+	File *f;
+	Aux *o;
+	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->dsp = fs->dsp.new();
+	closefile(f);
+
+	return o;
+}
+
+static void
+freeobj(Aux *o)
+{
+	if (o == nil)
+		return;
+
+	if (o->type == Xdsp) {
+		objs[o->id] = nil;
+		fs->dsp.free(o->dsp);
+	}
+
+	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 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 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 = fs->dsp.read(o->dsp, (float*)r->ofcall.data, r->ifcall.count/sizeof(float))*sizeof(float);
+		respond(r, nil);
+		break;
+	default:
+		respond(r, "not implemented");
+		break;
+	}
+}
+
+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 (o->ui->writestr == nil)
+			respond(r, "not implemented");
+		else if (o->ui->writestr(o->ui, 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 */
+			fs->dsp.reset(o->dsp);
+		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]));
+}
--- /dev/null
+++ b/fs.h
@@ -1,0 +1,49 @@
+typedef struct Aux Aux;
+typedef struct Auxdsp Auxdsp;
+typedef struct Fs Fs;
+
+typedef enum {
+	Xclone,
+	Xctl,
+	Xmetadata,
+
+	Xdsp,
+	Xdspctl,
+	Xdspdata,
+
+	Xui,
+	Xuictl,
+	Xuimeta,
+}Auxtype;
+
+struct UI;
+
+struct Aux {
+	Auxtype type;
+	int id;
+	int ctl;
+	int data;
+	int metadata;
+
+	Auxdsp *dsp;
+	struct UI *ui;
+};
+
+struct Fs {
+	Srv srv;
+	char *metadata;
+
+	struct {
+		Auxdsp *(*new)(void);
+		void (*free)(Auxdsp *dsp);
+		void (*reset)(Auxdsp *dsp);
+
+		/* optional, n is always modulo number of channels */
+		int (*read)(Auxdsp *dsp, float *b, int n);
+
+		/* optional, n is always modulo number of channels */
+		int (*write)(Auxdsp *dsp, float *b, int n);
+	}dsp;
+};
+
+void fsinit(void *fs);
--- a/mkfile
+++ b/mkfile
@@ -12,8 +12,7 @@
 
 BIN=/$objtype/bin/daw
 
-none:VQ:
-	echo mk all, install, clean, nuke, or libs
+default:V:	all
 
 all install clean nuke:VQ:
 	for (i in $LIBS $CMDS) @{
--- /dev/null
+++ b/mkfs
@@ -1,0 +1,27 @@
+BIN=/$objtype/bin/daw
+
+CFLAGS=$CFLAGS -p -I..
+
+OFILES=\
+	$OFILES\
+	common.$O\
+	fs.$O\
+	ui.$O\
+
+</sys/src/cmd/mk$MANY
+
+$BIN/%: $O.%
+	mkdir -p $BIN
+	cp $O.$stem $BIN/$stem
+
+common.$O: ../common.c
+	$CC $CFLAGS $prereq
+
+fs.$O: ../fs.c
+	$CC $CFLAGS $prereq
+
+ui.$O: ../ui.c
+	$CC $CFLAGS $prereq
+
+uiglue.$O: ../uiglue.c
+	$CC $CFLAGS $prereq
--- /dev/null
+++ b/ui.c
@@ -1,0 +1,289 @@
+#include <u.h>
+#include <libc.h>
+#include <fcall.h>
+#include <thread.h>
+#include <9p.h>
+#include "common.h"
+#include "ui.h"
+#include "fs.h"
+#include "uiglue.h"
+
+static struct {
+	float *zone;
+	Meta *meta;
+	int nummeta;
+}decl;
+
+File *uif;
+
+char *
+ui_readstr(UI *ui, int auxtype, char *s, int sz)
+{
+	char *x, *t;
+	int i;
+
+	if (auxtype == Xuictl) {
+		if (ui->type < 0 || ui->type >= UInum)
+			sysfatal("unknown ui type %d", ui->type);
+		t = uitypenames[ui->type];
+		switch (ui->type) {
+		case UITGroup:
+		case UIHGroup:
+		case UIVGroup:
+			snprint(s, sz, "%s\n", t);
+			return s;
+		case UIButton:
+		case UICheckBox:
+			snprint(s, sz, "%s\t%g\n", t, *ui->zone);
+			return s;
+		case UIVSlider:
+		case UIHSlider:
+		case UINEntry:
+			snprint(s, sz, "%s\t%g\t%g\t%g\t%g\t%g\n", t, *ui->zone, ui->init, ui->min, ui->max, ui->step);
+			return s;
+		case UIHBarGraph:
+		case UIVBarGraph:
+			snprint(s, sz, "%s\t%g\t%g\t%g\n", t, *ui->zone, ui->min, ui->max);
+			return s;
+		default:
+			sysfatal("readstr not implemented for ui type %d", ui->type);
+		}
+	} else if (auxtype == Xuimeta) {
+		x = s;
+		*x = 0;
+		for (i = 0; i < ui->nummeta; i++)
+			x = seprint(x, s+sz-1, "%s\t%s\n", ui->meta[i].k, ui->meta[i].v);
+		return s;
+	} else {
+		sysfatal("unsupported ui aux %d", auxtype);
+	}
+
+	return nil;
+}
+
+int
+ui_writestr(UI *ui, int auxtype, char *s)
+{
+	char *e;
+	int failoor;
+	float v;
+
+	if (auxtype != Xuictl)
+		sysfatal("unsupported ui aux %d", auxtype);
+
+	/* FIXME optional argument should specify at which frame to apply the change */
+
+	v = 0.0f;
+	failoor = 0;
+	if (strncmp(s, "reset", 5) == 0) { /* FIXME reset for a box should reset ALL controls inside it */
+		v = ui->init;
+	} else if (strncmp(s, "add", 3) == 0) {
+		if (ui->zone != nil)
+			v = *ui->zone + atof(s+3);
+	} else if (strncmp(s, "sub", 3) == 0) {
+		if (ui->zone != nil)
+			v = *ui->zone - atof(s+3);
+	} else {
+		v = strtod(s, &e);
+		if (*e != 0 && *e != '\n')
+			return -1;
+		failoor = 1;
+	}
+
+	if (ui->zone != nil) {
+		if (ui->type == UIButton || ui->type == UICheckBox) {
+			*ui->zone = v > 0 ? 1 : 0;
+		} else {
+			if (v < ui->min) {
+				if (failoor)
+					return -1;
+				v = ui->min;
+			} else if (v > ui->max) {
+				if (failoor)
+					return -1;
+				v = ui->max;
+			}
+			*ui->zone = v;
+		}
+	}
+
+	return 0;
+}
+
+static UI *
+newui(const char *label, int type)
+{
+	Aux *a;
+	File *f, *d;
+
+	a = calloc(1, sizeof(*a) + sizeof(UI) + sizeof(Meta)*decl.nummeta);
+	a->type = Xui;
+	a->ui = (UI*)(a+1);
+	a->ui->type = type;
+	a->ui->meta = (Meta*)(a->ui+1);
+	a->ui->nummeta = decl.nummeta;
+	memmove(a->ui->meta, decl.meta, sizeof(Meta)*decl.nummeta);
+	a->ctl = Xuictl;
+	a->metadata = Xuimeta;
+	a->ui->zone = decl.zone;
+	a->ui->label = label;
+	a->ui->readstr = ui_readstr;
+	a->ui->writestr = ui_writestr;
+	if ((d = createfile(uif, label, nil, DMDIR|0775, a)) == nil)
+		sysfatal("failed to create '%s': %r", label);
+	if ((f = createfile(d, "ctl", nil, 0664, &a->ctl)) == nil)
+		sysfatal("failed to create '%s/ctl': %r", label);
+	closefile(f);
+	if ((f = createfile(d, "metadata", nil, 0664, &a->metadata)) == nil)
+		sysfatal("failed to create '%s/metadata': %r", label);
+	closefile(f);
+	closefile(d);
+
+	free(decl.meta);
+	decl.zone = nil;
+	decl.meta = nil;
+	decl.nummeta = 0;
+
+	if (type == UITGroup || type == UIHGroup || type == UIVGroup)
+		uif = d;
+
+	return a->ui;
+}
+
+static UI *
+newdef(int type, const char *label, float *zone)
+{
+	if (zone != decl.zone) { /* no "declare" called before */
+		decl.zone = zone;
+		free(decl.meta);
+		decl.meta = nil;
+		decl.nummeta = 0;
+	}
+	return newui(label, type);
+}
+
+void
+ui_tgroup(const char *label)
+{
+	newui(label, UITGroup);
+}
+
+void
+ui_hgroup(const char *label)
+{
+	newui(label, UIHGroup);
+}
+
+void
+ui_vgroup(const char *label)
+{
+	newui(label, UIVGroup);
+}
+
+void
+ui_endgroup(void)
+{
+	uif = uif->parent;
+}
+
+UI *
+ui_button(const char *label, float *zone)
+{
+	UI *ui;
+
+	ui = newdef(UIButton, label, zone);
+	*zone = 0;
+
+	return ui;
+}
+
+UI *
+ui_checkbox(const char *label, float *zone)
+{
+	UI *ui;
+
+	ui = newdef(UICheckBox, label, zone);
+	*zone = 0;
+
+	return ui;
+}
+
+UI *
+ui_vslider(const char *label, float *zone, float init, float min, float max, float step)
+{
+	UI *ui;
+
+	ui = newdef(UIVSlider, label, zone);
+	ui->init = *zone = init;
+	ui->min = min;
+	ui->max = max;
+	ui->step = step;
+
+	return ui;
+}
+
+UI *
+ui_hslider(const char *label, float *zone, float init, float min, float max, float step)
+{
+	UI *ui;
+
+	ui = newdef(UIHSlider, label, zone);
+	ui->init = *zone = init;
+	ui->min = min;
+	ui->max = max;
+	ui->step = step;
+
+	return ui;
+}
+
+UI *
+ui_nentry(const char *label, float *zone, float init, float min, float max, float step)
+{
+	UI *ui;
+
+	ui = newdef(UINEntry, label, zone);
+	ui->init = *zone = init;
+	ui->min = min;
+	ui->max = max;
+	ui->step = step;
+
+	return ui;
+}
+
+UI *
+ui_hbargraph(const char *label, float *zone, float min, float max)
+{
+	UI *ui;
+
+	ui = newdef(UIHBarGraph, label, zone);
+	ui->min = min;
+	ui->max = max;
+	*zone = 0;
+
+	return ui;
+}
+
+UI *
+ui_vbargraph(const char *label, float *zone, float min, float max)
+{
+	UI *ui;
+
+	ui = newdef(UIVBarGraph, label, zone);
+	ui->min = min;
+	ui->max = max;
+	*zone = 0;
+
+	return ui;
+}
+
+void
+ui_declare(float *zone, const char *key, const char *value)
+{
+	if (decl.zone != nil && decl.zone != zone)
+		sysfatal("%s=\"%s\": zone mismatch during declaration (%p != %p)", key, value, decl.zone, zone);
+	decl.zone = zone;
+	decl.meta = realloc(decl.meta, sizeof(Meta)*(decl.nummeta+1));
+	decl.meta[decl.nummeta].k = key;
+	decl.meta[decl.nummeta].v = value;
+	decl.nummeta++;
+}
--- /dev/null
+++ b/ui.h
@@ -1,0 +1,41 @@
+typedef struct Meta Meta;
+typedef struct UI UI;
+
+struct Meta {
+	const char *k;
+	const char *v;
+};
+
+struct UI {
+	int type;
+	const char *label;
+	float *zone;
+	float init;
+	float min;
+	float max;
+	float step;
+
+	Meta *meta;
+	int nummeta;
+
+	/* optional */
+	char *(*readstr)(UI *ui, int auxtype, char *s, int sz);
+	int (*writestr)(UI *ui, int auxtype, char *s);
+	void *userdata;
+};
+
+char *ui_readstr(UI *ui, int auxtype, char *s, int sz);
+int ui_writestr(UI *ui, int auxtype, char *s);
+
+void ui_tgroup(const char *label);
+void ui_hgroup(const char *label);
+void ui_vgroup(const char *label);
+void ui_endgroup(void);
+UI *ui_button(const char *label, float *zone);
+UI *ui_checkbox(const char *label, float *zone);
+UI *ui_vslider(const char *label, float *zone, float init, float min, float max, float step);
+UI *ui_hslider(const char *label, float *zone, float init, float min, float max, float step);
+UI *ui_nentry(const char *label, float *zone, float init, float min, float max, float step);
+UI *ui_hbargraph(const char *label, float *zone, float min, float max);
+UI *ui_vbargraph(const char *label, float *zone, float min, float max);
+void ui_declare(float *zone, const char *key, const char *value);
--- a/uiglue.c
+++ b/uiglue.c
@@ -4,298 +4,34 @@
 #include <thread.h>
 #include <9p.h>
 #include "common.h"
+#include "ui.h"
+#include "fs.h"
 #include "uiglue.h"
-#include "aux.h"
 
-static struct {
-	float *zone;
-	Meta *meta;
-	int nummeta;
-}decl;
+static void openTabBox(void *, const char *label) { ui_tgroup(label); }
+static void openHorizontalBox(void *, const char *label) { ui_hgroup(label); }
+static void openVerticalBox(void *, const char *label) { ui_vgroup(label); }
+static void closeBox(void *) { ui_endgroup(); }
+static void addButton(void *, const char *label, float *zone) { ui_button(label, zone); }
+static void addCheckButton(void *, const char *label, float *zone) { ui_checkbox(label, zone); }
+static void addVerticalSlider(void *, const char *label, float *zone, float init, float min, float max, float step) { ui_vslider(label, zone, init, min, max, step); }
+static void addHorizontalSlider(void *, const char *label, float *zone, float init, float min, float max, float step) { ui_hslider(label, zone, init, min, max, step); }
+static void addNumEntry(void *, const char *label, float *zone, float init, float min, float max, float step) { ui_nentry(label, zone, init, min, max, step); }
+static void addVerticalBargraph(void *, const char *label, float *zone, float min, float max) { ui_vbargraph(label, zone, min, max); }
+static void addHorizontalBargraph(void *, const char *label, float *zone, float min, float max) { ui_hbargraph(label, zone, min, max); }
+static void declare(void *, float *zone, const char *key, const char *value) { ui_declare(zone, key, value); }
 
-char *
-ui_readstr(UI *ui, int auxtype, char *s, int sz)
-{
-	char *x, *t;
-	int i;
-
-	if (auxtype == Xuictl) {
-		if (ui->type < 0 || ui->type >= UInum)
-			sysfatal("unknown ui type %d", ui->type);
-		t = uitypenames[ui->type];
-		switch (ui->type) {
-		case UITGroup:
-		case UIHGroup:
-		case UIVGroup:
-			snprint(s, sz, "%s\n", t);
-			return s;
-		case UIButton:
-		case UICheckBox:
-			snprint(s, sz, "%s\t%g\n", t, *ui->zone);
-			return s;
-		case UIVSlider:
-		case UIHSlider:
-		case UINEntry:
-			snprint(s, sz, "%s\t%g\t%g\t%g\t%g\t%g\n", t, *ui->zone, ui->init, ui->min, ui->max, ui->step);
-			return s;
-		case UIHBarGraph:
-		case UIVBarGraph:
-			snprint(s, sz, "%s\t%g\t%g\t%g\n", t, *ui->zone, ui->min, ui->max);
-			return s;
-		default:
-			sysfatal("readstr not implemented for ui type %d", ui->type);
-		}
-	} else if (auxtype == Xuimeta) {
-		x = s;
-		*x = 0;
-		for (i = 0; i < ui->nummeta; i++)
-			x = seprint(x, s+sz-1, "%s\t%s\n", ui->meta[i].k, ui->meta[i].v);
-		return s;
-	} else {
-		sysfatal("unsupported ui aux %d", auxtype);
-	}
-
-	return nil;
-}
-
-int
-ui_writestr(UI *ui, int auxtype, char *s)
-{
-	char *e;
-	int failoor;
-	float v;
-
-	if (auxtype != Xuictl)
-		sysfatal("unsupported ui aux %d", auxtype);
-
-	/* FIXME optional argument should specify at which frame to apply the change */
-
-	v = 0.0f;
-	failoor = 0;
-	if (strncmp(s, "reset", 5) == 0) { /* FIXME reset for a box should reset ALL controls inside it */
-		v = ui->init;
-	} else if (strncmp(s, "add", 3) == 0) {
-		if (ui->zone != nil)
-			v = *ui->zone + atof(s+3);
-	} else if (strncmp(s, "sub", 3) == 0) {
-		if (ui->zone != nil)
-			v = *ui->zone - atof(s+3);
-	} else {
-		v = strtod(s, &e);
-		if (*e != 0 && *e != '\n')
-			return -1;
-		failoor = 1;
-	}
-
-	if (ui->zone != nil) {
-		if (ui->type == UIButton || ui->type == UICheckBox) {
-			*ui->zone = v > 0 ? 1 : 0;
-		} else {
-			if (v < ui->min) {
-				if (failoor)
-					return -1;
-				v = ui->min;
-			} else if (v > ui->max) {
-				if (failoor)
-					return -1;
-				v = ui->max;
-			}
-			*ui->zone = v;
-		}
-	}
-
-	return 0;
-}
-
-static UI *
-newui(File *f, const char *label, int type)
-{
-	Aux *a;
-
-	a = calloc(1, sizeof(*a) + sizeof(UI) + sizeof(Meta)*decl.nummeta);
-	a->type = Xui;
-	a->ui = (UI*)(a+1);
-	a->ui->type = type;
-	a->ui->meta = (Meta*)(a->ui+1);
-	a->ui->nummeta = decl.nummeta;
-	memmove(a->ui->meta, decl.meta, sizeof(Meta)*decl.nummeta);
-	a->ctl = Xuictl;
-	a->metadata = Xuimeta;
-	a->ui->zone = decl.zone;
-	a->ui->label = label;
-	a->ui->readstr = uiglue.readstr != nil ? uiglue.readstr : ui_readstr;
-	a->ui->writestr = uiglue.writestr != nil ? uiglue.writestr : ui_writestr;
-	a->ui->userdata = uiglue.userdata;
-	if ((uiglue.f = createfile(f, label, nil, DMDIR|0775, a)) == nil)
-		sysfatal("failed to create ui: %r");
-	if ((f = createfile(uiglue.f, "ctl", nil, 0664, &a->ctl)) == nil)
-		sysfatal("failed to create ui ctl: %r");
-	closefile(f);
-	if ((f = createfile(uiglue.f, "metadata", nil, 0664, &a->metadata)) == nil)
-		sysfatal("failed to create ui metadata: %r");
-	closefile(f);
-	closefile(uiglue.f);
-
-	free(decl.meta);
-	decl.zone = nil;
-	decl.meta = nil;
-	decl.nummeta = 0;
-
-	return a->ui;
-}
-
-static void
-ui_tgroup(void *f, const char *label)
-{
-	newui(f, label, UITGroup);
-}
-
-static void
-ui_hgroup(void *f, const char *label)
-{
-	newui(f, label, UIHGroup);
-}
-
-static void
-ui_vgroup(void *f, const char *label)
-{
-	newui(f, label, UIVGroup);
-}
-
-static void
-ui_close_group(void *file)
-{
-	File *f;
-
-	f = file;
-	uiglue.f = f->parent;
-}
-
-static UI *
-ui_define(File *f, int type, const char *label, float *zone)
-{
-	UI *ui;
-
-	if (zone != decl.zone) { /* no "declare" called before */
-		decl.zone = zone;
-		free(decl.meta);
-		decl.meta = nil;
-		decl.nummeta = 0;
-	}
-	ui = newui(f, label, type);
-	uiglue.f = f;
-
-	return ui;
-}
-
-static void
-ui_button(void *f, const char *label, float *zone)
-{
-	UI *ui;
-
-	ui = ui_define(f, UIButton, label, zone);
-	*zone = 0;
-	ui->writestr(ui, Xuictl, "reset");
-}
-
-static void
-ui_checkbox(void *f, const char *label, float *zone)
-{
-	UI *ui;
-
-	ui = ui_define(f, UICheckBox, label, zone);
-	*zone = 0;
-	ui->writestr(ui, Xuictl, "reset");
-}
-
-static void
-ui_vslider(void *f, const char *label, float *zone, float init, float min, float max, float step)
-{
-	UI *ui;
-
-	ui = ui_define(f, UIVSlider, label, zone);
-	ui->init = *zone = init;
-	ui->min = min;
-	ui->max = max;
-	ui->step = step;
-	ui->writestr(ui, Xuictl, "reset");
-}
-
-static void
-ui_hslider(void *f, const char *label, float *zone, float init, float min, float max, float step)
-{
-	UI *ui;
-
-	ui = ui_define(f, UIHSlider, label, zone);
-	ui->init = *zone = init;
-	ui->min = min;
-	ui->max = max;
-	ui->step = step;
-	ui->writestr(ui, Xuictl, "reset");
-}
-
-static void
-ui_nentry(void *f, const char *label, float *zone, float init, float min, float max, float step)
-{
-	UI *ui;
-
-	ui = ui_define(f, UINEntry, label, zone);
-	ui->init = *zone = init;
-	ui->min = min;
-	ui->max = max;
-	ui->step = step;
-	ui->writestr(ui, Xuictl, "reset");
-}
-
-static void
-ui_hbargraph(void *f, const char *label, float *zone, float min, float max)
-{
-	UI *ui;
-
-	ui = ui_define(f, UIHBarGraph, label, zone);
-	ui->min = min;
-	ui->max = max;
-	*zone = 0;
-	ui->writestr(ui, Xuictl, "reset");
-}
-
-static void
-ui_vbargraph(void *f, const char *label, float *zone, float min, float max)
-{
-	UI *ui;
-
-	ui = ui_define(f, UIVBarGraph, label, zone);
-	ui->min = min;
-	ui->max = max;
-	*zone = 0;
-	ui->writestr(ui, Xuictl, "reset");
-}
-
-static void
-ui_declare(void *f, float *zone, const char *key, const char *value)
-{
-	USED(f);
-
-	if (decl.zone != nil && decl.zone != zone)
-		sysfatal("%s=\"%s\": zone mismatch during declaration (%p != %p)", key, value, decl.zone, zone);
-	decl.zone = zone;
-	decl.meta = realloc(decl.meta, sizeof(Meta)*(decl.nummeta+1));
-	decl.meta[decl.nummeta].k = key;
-	decl.meta[decl.nummeta].v = value;
-	decl.nummeta++;
-}
-
 UIGlue uiglue = {
-	.openTabBox = ui_tgroup,
-	.openHorizontalBox = ui_hgroup,
-	.openVerticalBox = ui_vgroup,
-	.closeBox = ui_close_group,
-	.addButton = ui_button,
-	.addCheckButton = ui_checkbox,
-	.addVerticalSlider = ui_vslider,
-	.addHorizontalSlider = ui_hslider,
-	.addNumEntry = ui_nentry,
-	.addHorizontalBargraph = ui_hbargraph,
-	.addVerticalBargraph = ui_vbargraph,
-	.declare = ui_declare,
+	.openTabBox = openTabBox,
+	.openHorizontalBox = openHorizontalBox,
+	.openVerticalBox = openVerticalBox,
+	.closeBox = closeBox,
+	.addButton = addButton,
+	.addCheckButton = addCheckButton,
+	.addVerticalSlider = addVerticalSlider,
+	.addHorizontalSlider = addHorizontalSlider,
+	.addNumEntry = addNumEntry,
+	.addVerticalBargraph = addVerticalBargraph,
+	.addHorizontalBargraph = addHorizontalBargraph,
+	.declare = declare,
 };
--- a/uiglue.h
+++ b/uiglue.h
@@ -1,37 +1,13 @@
-typedef struct UIGlue UIGlue;
 typedef struct MetaGlue MetaGlue;
+typedef struct UIGlue UIGlue;
 
-struct File;
-
-typedef struct Meta Meta;
-typedef struct UI UI;
-
-struct Meta {
-	const char *k;
-	const char *v;
+struct MetaGlue {
+	void *metaInterface;
+	void (*declare)(void *metaInterface, const char *key, const char *value);
 };
 
-struct UI {
-	int type;
-	const char *label;
-	float *zone;
-	float init;
-	float min;
-	float max;
-	float step;
-	char *(*readstr)(UI *ui, int auxtype, char *s, int sz);
-	int (*writestr)(UI *ui, int auxtype, char *s);
-
-	void *userdata;
-	Meta *meta;
-	int nummeta;
-};
-
 struct UIGlue {
-	union {
-		void *uiInterface;
-		struct File *f;
-	};
+	void *uiInterface;
 	void (*openTabBox)(void *uiInterface, const char *label);
 	void (*openHorizontalBox)(void *uiInterface, const char *label);
 	void (*openVerticalBox)(void *uiInterface, const char *label);
@@ -44,19 +20,6 @@
 	void (*addHorizontalBargraph)(void *uiInterface, const char *label, float *zone, float min, float max);
 	void (*addVerticalBargraph)(void *uiInterface, const char *label, float *zone, float min, float max);
 	void (*declare)(void *uiInterface, float *zone, const char *key, const char *value);
-
-	char *(*readstr)(UI *ui, int auxtype, char *s, int sz);
-	int (*writestr)(UI *ui, int auxtype, char *s);
-
-	void *userdata;
 };
 
-struct MetaGlue {
-	void *metaInterface;
-	void (*declare)(void *metaInterface, const char *key, const char *value);
-};
-
 extern UIGlue uiglue;
-
-char *ui_readstr(UI *ui, int auxtype, char *s, int sz);
-int ui_writestr(UI *ui, int auxtype, char *s);