shithub: riscv

Download patch

ref: 4bc74b8aefb222cbc58f42b0fdbc28f50e5f1a35
parent: 334c58f95e3c0834037448a37ca5d4f1c8589b4e
author: cinap_lenrek <cinap_lenrek@localhost>
date: Fri May 20 14:30:46 EDT 2011

audioif, mixer control

--- a/sys/src/9/pc/audioac97.c
+++ b/sys/src/9/pc/audioac97.c
@@ -5,6 +5,7 @@
 #include "fns.h"
 #include "io.h"
 #include "../port/error.h"
+#include "../port/audioif.h"
 
 typedef struct Hwdesc Hwdesc;
 typedef struct Ctlr Ctlr;
@@ -65,10 +66,6 @@
 	ulong civstat[Ndesc];
 	ulong lvistat[Ndesc];
 
-	int targetrate;
-	int hardrate;
-
-	int attachok;
 	int sis7012;
 
 	/* for probe */
@@ -145,8 +142,8 @@
 #define csr32w(c, r, w)	(outl((c)->port+(r), (ulong)(w)))
 
 /* audioac97mix */
-extern int ac97hardrate(Audio *, int);
-extern void ac97mixreset(Audio *, void (*wr)(Audio*,int,ushort), 
+extern void ac97mixreset(Audio *,
+	void (*wr)(Audio*,int,ushort), 
 	ushort (*rr)(Audio*,int));
 
 static void
@@ -202,11 +199,9 @@
 	adev = arg;
 	ctlr = adev->ctlr;
 	stat = csr32r(ctlr, Sta);
-
 	stat &= S2ri | Sri | Pri | Mint | Point | Piint | Moint | Miint | Gsci;
-
-	ilock(ctlr);
 	if(stat & Point){
+		ilock(ctlr);
 		if(ctlr->sis7012)
 			csr16w(ctlr, Out + Picb, csr16r(ctlr, Out + Picb) & ~Dch);
 		else
@@ -232,10 +227,11 @@
 		if(ctlr->outavail > Bufsize/2)
 			wakeup(&ctlr->outr);
 		stat &= ~Point;	
+		iunlock(ctlr);
 	}
-	iunlock(ctlr);
 	if(stat) /* have seen 0x400, which is sdin0 resume */
-		print("#A%d: ac97 unhandled interrupt(s): stat 0x%lux\n", adev->ctlrno, stat);
+		iprint("#A%d: ac97 unhandled interrupt(s): stat 0x%lux\n",
+			adev->ctlrno, stat);
 }
 
 static int
@@ -272,52 +268,30 @@
 	return ts[Nts/2] / BytesPerSample;
 }
 
-static void
-ac97volume(Audio *adev, char *msg)
-{
-	adev->volwrite(adev, msg, strlen(msg), 0);
-}
-
-static void
-ac97attach(Audio *adev)
-{
-	Ctlr *ctlr;
-	ctlr = adev->ctlr;
-	if(!ctlr->attachok){
-		ac97hardrate(adev, ctlr->hardrate);
-		ac97volume(adev, "audio 75");
-		ac97volume(adev, "head 100");
-		ac97volume(adev, "master 100");
-		ctlr->attachok = 1;
-	}
-}
-
 static long
-ac97status(Audio *adev, void *a, long n, vlong off)
+ac97status(Audio *adev, void *a, long n, vlong)
 {
+	char *p, *e;
 	Ctlr *ctlr;
-	char *buf;
-	long i, l;
+	int i;
+
 	ctlr = adev->ctlr;
-	l = 0;
-	buf = malloc(READSTR);
-	l += snprint(buf + l, READSTR - l, "rate %d\n", ctlr->targetrate);
-	l += snprint(buf + l, READSTR - l, "median rate %lud\n", ac97medianoutrate(adev));
-	l += snprint(buf + l, READSTR - l, "hard rate %d\n", ac97hardrate(adev, -1));
 
-	l += snprint(buf + l, READSTR - l, "civ stats");
+	p = a;
+	e = p + n;
+
+	p += snprint(p, e - p, "median rate %lud\n", ac97medianoutrate(adev));
+	p += snprint(p, e - p, "civ stats");
 	for(i = 0; i < Ndesc; i++)
-		l += snprint(buf + l, READSTR - l, " %lud", ctlr->civstat[i]);
-	l += snprint(buf + l, READSTR - l, "\n");
+		p += snprint(p, e - p, " %lud", ctlr->civstat[i]);
+	p += snprint(p, e - p, "\n");
 
-	l += snprint(buf + l, READSTR - l, "lvi stats");
+	p += snprint(p, e - p, "lvi stats");
 	for(i = 0; i < Ndesc; i++)
-		l += snprint(buf + l, READSTR - l, " %lud", ctlr->lvistat[i]);
-	snprint(buf + l, READSTR - l, "\n");
+		p += snprint(p, e - p, " %lud", ctlr->lvistat[i]);
+	p += snprint(p, e - p, "\n");
 
-	n = readstr(off, a, n, buf);
-	free(buf);
-	return n;
+	return p - (char*)a;
 }
 
 static long
@@ -328,41 +302,6 @@
 	return Bufsize - Bufsize/Ndesc - ctlr->outavail;
 }
 
-static long
-ac97ctl(Audio *adev, void *a, long n, vlong)
-{
-	Ctlr *ctlr;
-	char *tok[2], *p;
-	int ntok;
-	long t;
-
-	ctlr = adev->ctlr;
-	if(n > READSTR)
-		n = READSTR - 1;
-	p = malloc(READSTR);
-
-	if(waserror()){
-		free(p);
-		nexterror();
-	}
-	memmove(p, a, n);
-	p[n] = 0;
-	ntok = tokenize(p, tok, nelem(tok));
-	if(ntok > 1 && !strcmp(tok[0], "rate")){
-		t = strtol(tok[1], 0, 10);
-		if(t < 8000 || t > 48000)
-			error("rate must be between 8000 and 48000");
-		ctlr->targetrate = t;
-		ctlr->hardrate = t;
-		ac97hardrate(adev, ctlr->hardrate);
-		poperror();
-		free(p);
-		return n;
-	}
-	error("invalid ctl");
-	return n; /* shut up, you compiler you */
-}
-
 static void
 ac97kick(Ctlr *ctlr, long reg)
 {
@@ -490,9 +429,6 @@
 
 Found:
 	adev->ctlr = ctlr;
-	ctlr->targetrate = 44100;
-	ctlr->hardrate = 44100;
-
 	if(p->vid == 0x1039 && p->did == 0x7012)
 		ctlr->sis7012 = 1;
 
@@ -588,13 +524,11 @@
 	csr8w(ctlr, Out+Cr, Ioce);	/*  | Lvbie | Feie */
 	csr8w(ctlr, Mic+Cr, Ioce);	/*  | Lvbie | Feie */
 
-	adev->attach = ac97attach;
+	ac97mixreset(adev, ac97mixw, ac97mixr);
+
 	adev->write = ac97write;
 	adev->status = ac97status;
-	adev->ctl = ac97ctl;
 	adev->buffered = ac97buffered;
-	
-	ac97mixreset(adev, ac97mixw, ac97mixr);
 
 	intrenable(irq, ac97interrupt, adev, tbdf, adev->name);
 
--- a/sys/src/9/pc/audioac97mix.c
+++ b/sys/src/9/pc/audioac97mix.c
@@ -5,20 +5,8 @@
 #include "fns.h"
 #include "io.h"
 #include "../port/error.h"
+#include "../port/audioif.h"
 
-typedef ushort (*ac97rdfn)(Audio *, int);
-typedef void (*ac97wrfn)(Audio *, int, ushort);
-
-typedef struct Mixer Mixer;
-typedef struct Volume Volume;
-
-struct Mixer {
-	QLock;
-	ac97wrfn wr;
-	ac97rdfn rr;
-	int vra;
-};
-
 enum { Maxbusywait = 500000 };
 
 enum {
@@ -33,22 +21,8 @@
 		Capadc18 = 0x100,
 		Capadc20 = 0x200,
 		Capenh = 0xfc00,
-	Master = 0x02,
-	Headphone = 0x04,
-	Monomaster = 0x06,
-	Mastertone = 0x08,
-	Pcbeep = 0x0A,
-	Phone = 0x0C,
-	Mic = 0x0E,
-	Line = 0x10,
-	Cd = 0x12,
-	Video = 0x14,
-	Aux = 0x16,
-	Pcmout = 0x18,
-		Mute = 0x8000,
+
 	Recsel = 0x1A,
-	Recgain = 0x1C,
-	Micgain = 0x1E,
 	General = 0x20,
 	ThreeDctl = 0x22,
 	Ac97RESER = 0x24,
@@ -118,17 +92,9 @@
 		Spdifv = 1<<15,
 	VID1 = 0x7c,
 	VID2 = 0x7e,
-	Speed = 0x1234567,
 };
 
 enum {
-	Left,
-	Right,
-	Stereo,
-	Absolute,
-};
-
-enum {
 	Vmaster,
 	Vhead,
 	Vaudio,
@@ -143,195 +109,125 @@
 	Vaux,
 	Vrecgain,
 	Vmicgain,
+	Vspeed,
 };
 
-struct Volume {
-	int reg;
-	int range;
-	int type;
-	int cap;
-	char *name;
+static Volume voltab[] = {
+	[Vmaster] "master", 0x02, 63, Stereo, 0,
+	[Vaudio] "audio", 0x18, 31, Stereo, 0,
+	[Vhead] "head", 0x04, 31, Stereo, Capheadphones,
+	[Vbass] "bass", 0x08, 15, Left, Captonectl,
+	[Vtreb] "treb", 0x08, 15, Right, Captonectl,
+	[Vbeep] "beep", 0x0a, 31, Right, 0,
+	[Vphone] "phone", 0x0c, 31, Right, 0,
+	[Vmic] "mic", 0x0e, 31, Right, Capmic,
+	[Vline] "line", 0x10, 31, Stereo, 0,
+	[Vcd] "cd", 0x12, 31, Stereo,	0,
+	[Vvideo] "video", 0x14, 31, Stereo, 0,
+	[Vaux] "aux", 0x16, 63, Stereo, 0,
+	[Vrecgain] "recgain", 0x1c, 15, Stereo, 0,
+	[Vmicgain] "micgain", 0x1e, 15, Right, Capmic,
+	[Vspeed] "speed", 0x2c, 0, Absolute, 0,
+	0
 };
 
-struct Topology {
-	Volume *this;
-	Volume *next[2];
+typedef struct Mixer Mixer;
+struct Mixer
+{
+	ushort (*rr)(Audio *, int);
+	void (*wr)(Audio *, int, ushort);
+	int vra;
 };
 
-Volume vol[] = {
-[Vmaster]	{Master, 63, Stereo, 0, "master"},
-[Vaudio]	{Pcmout, 31, Stereo,	0, "audio"},
-[Vhead]	{Headphone, 31, Stereo, Capheadphones, "head"},
-[Vbass]	{Mastertone, 15, Left, Captonectl, "bass"},
-[Vtreb]	{Mastertone, 15, Right, Captonectl, "treb"},
-[Vbeep]	{Pcbeep, 31, Right, 0, "beep"},
-[Vphone]	{Phone, 31, Right, 0, "phone"},
-[Vmic]	{Mic, 31, Right, Capmic, "mic"},
-[Vline]	{Line, 31, Stereo, 0, "line"},
-[Vcd]	{Cd, 31, Stereo,	0, "cd"},
-[Vvideo]	{Video, 31, Stereo, 0, "video"},
-[Vaux]	{Aux, 63, Stereo, 0, "aux"},
-[Vrecgain]	{Recgain, 15, Stereo, 0, "recgain"},
-[Vmicgain]	{Micgain, 15, Right, Capmic, "micgain"},
-	{0, 0, 0, 0, 0},
-};
-
-long
-ac97mixtopology(Audio *adev, void *a, long n, vlong off)
+static int
+ac97volget(Audio *adev, int x, int a[2])
 {
-	Mixer *m;
-	char *buf;
-	long l;
-	ulong caps;
-	m = adev->mixer;
-	qlock(m);
-	caps = m->rr(adev, Reset);
-	caps |= m->rr(adev, Extid) << 16;
-	l = 0;
-	buf = malloc(READSTR);
-	l += snprint(buf+l, READSTR-l, "not implemented. have fun.\n");
-	USED(caps);
-	USED(l);
-	qunlock(m);
-	n = readstr(off, a, n, buf);
-	free(buf);
-	return n;
-}
-
-long
-ac97mixread(Audio *adev, void *a, long n, vlong off)
-{
-	Mixer *m;
-	char *nam, *buf;
-	long l;
+	Mixer *m = adev->mixer;
+	Volume *vol;
 	ushort v;
-	ulong caps;
-	int i, rang, le, ri;
-	buf = malloc(READSTR);
-	m = adev->mixer;
-	qlock(m);
-	l = 0;
-	caps = m->rr(adev, Reset);
-	caps |= m->rr(adev, Extid) << 16;
-	for(i = 0; vol[i].name != 0; ++i){
-		if(vol[i].cap && ((vol[i].cap & caps) == 0))
-			continue;
-		v = m->rr(adev, vol[i].reg);
-		nam = vol[i].name;
-		rang = vol[i].range;
-		if(vol[i].type == Absolute){
-			l += snprint(buf+l, READSTR-l, "%s %d", nam, v);
+
+	vol = voltab+x;
+	switch(vol->type){
+	case Absolute:
+		a[0] = m->rr(adev, vol->reg);
+		break;
+	default:
+		v = m->rr(adev, vol->reg);
+		if(v & 0x8000){
+			a[0] = 0;
+			a[1] = 0;
 		} else {
-			ri = ((rang-(v&rang)) * 100) / rang;
-			le = ((rang-((v>>8)&rang)) * 100) / rang;
-			if(vol[i].type == Stereo)
-				l += snprint(buf+l, READSTR-l, "%s %d %d", nam, le, ri);
-			if(vol[i].type == Left)
-				l += snprint(buf+l, READSTR-l, "%s %d", nam, le);
-			if(vol[i].type == Right)
-				l += snprint(buf+l, READSTR-l, "%s %d", nam, ri);
-			if(v&Mute)
-				l += snprint(buf+l, READSTR-l, " mute");
+			a[0] = vol->range - ((v>>8) & 0x7f);
+			a[1] = vol->range - (v & 0x7f);
 		}
-		l += snprint(buf+l, READSTR-l, "\n");
 	}
-	qunlock(m);
-	n = readstr(off, a, n, buf);
-	free(buf);
-	return n;
+	return 0;
 }
 
-long
-ac97mixwrite(Audio *adev, void *a, long n, vlong)
+static int
+ac97volset(Audio *adev, int x, int a[2])
 {
-	Mixer *m;
-	char *tok[4];
-	int ntok, i, left, right, rang, reg;
-	ushort v;
-	m = adev->mixer;
-	qlock(m);
-	ntok = tokenize(a, tok, 4);
-	for(i = 0; vol[i].name != 0; ++i){
-		if(!strcmp(vol[i].name, tok[0])){
-			rang = vol[i].range;
-			reg = vol[i].reg;
-			left = right = 0;
-			if(ntok > 1)
-				left = right = atoi(tok[1]);
-			if(ntok > 2)
-				right = atoi(tok[2]);
+	Mixer *m = adev->mixer;
+	Volume *vol;
+	ushort v, w;
 
-			if(vol[i].type == Absolute){
-				m->wr(adev, reg, left);
-			} else {
-				left = rang - ((left*rang)) / 100;
-				right = rang - ((right*rang)) / 100;
-				switch(vol[i].type){
-				default:
-					break;
-				case Left:
-					v = m->rr(adev, reg);
-					v = (v & 0x007f) | (left << 8);
-					m->wr(adev, reg, v);
-					break;
-				case Right:
-					v = m->rr(adev, reg);
-					v = (v & 0x7f00) | right;
-					m->wr(adev, reg, v);
-					break;
-				case Stereo:
-					v = (left<<8) | right;
-					m->wr(adev, reg, v);
-					break;
-				}
-			}
-			qunlock(m);
-			return n;
-		}
+	vol = voltab+x;
+	switch(vol->type){
+	case Absolute:
+		m->wr(adev, vol->reg, a[0]);
+		break;
+	case Left:
+		v = (vol->range - a[0]) & 0x7f;
+		w = m->rr(adev, vol->reg) & 0x7f;
+		m->wr(adev, vol->reg, (v<<8)|w);
+		break;
+	case Right:
+		v = m->rr(adev, vol->reg) & 0x7f00;
+		w = (vol->range - a[1]) & 0x7f;
+		m->wr(adev, vol->reg, v|w);
+		break;
+	case Stereo:
+		v = (vol->range - a[0]) & 0x7f;
+		w = (vol->range - a[1]) & 0x7f;
+		m->wr(adev, vol->reg, (v<<8)|w);
+		break;
 	}
-	if(vol[i].name == nil){
-		char *p;
-		for(p = tok[0]; *p; ++p)
-			if(*p < '0' || *p > '9') {
-				qunlock(m);
-				error("no such volume setting");
-			}
-		rang = vol[0].range;
-		reg = vol[0].reg;
-		left = right = rang - ((atoi(tok[0])*rang)) / 100;
-		v = (left<<8) | right;
-		m->wr(adev, reg, v);
-	}
-	qunlock(m);
+	return 0;
+}
 
-	return n;
+
+static long
+ac97mixread(Audio *adev, void *a, long n, vlong)
+{
+	Mixer *m = adev->mixer;
+	ulong caps;
+
+	caps = m->rr(adev, Reset);
+	caps |= m->rr(adev, Extid) << 16;
+	return genaudiovolread(adev, a, n, 0, voltab, ac97volget, caps);
 }
 
-int
-ac97hardrate(Audio *adev, int rate)
+static long
+ac97mixwrite(Audio *adev, void *a, long n, vlong)
 {
-	Mixer *m;
-	int oldrate;
-	m = adev->mixer;
-	oldrate = m->rr(adev, Pcmfrontdacrate);
-	if(rate > 0)
-		m->wr(adev, Pcmfrontdacrate, rate);
-	return oldrate;
+	Mixer *m = adev->mixer;
+	ulong caps;
+
+	caps = m->rr(adev, Reset);
+	caps |= m->rr(adev, Extid) << 16;
+	return genaudiovolwrite(adev, a, n, 0, voltab, ac97volset, caps);
 }
 
 void
-ac97mixreset(Audio *adev, ac97wrfn wr, ac97rdfn rr)
+ac97mixreset(Audio *adev, void (*wr)(Audio*,int,ushort), ushort (*rr)(Audio*,int))
 {
 	Mixer *m;
-	int i;
 	ushort t;
-	if(adev->mixer == nil)
-		adev->mixer = malloc(sizeof(Mixer));
-	m = adev->mixer;
+	int i;
+
+	m = malloc(sizeof(Mixer));
 	m->wr = wr;
 	m->rr = rr;
-	adev->volread = ac97mixread;
-	adev->volwrite = ac97mixwrite;
 	m->wr(adev, Reset, 0);
 	m->wr(adev, Powerdowncsr, 0);
 
@@ -361,4 +257,8 @@
 		print("#A%d: ac97 vra extension not supported\n", adev->ctlrno);
 		m->vra = 0;
 	}
+
+	adev->mixer = m;
+	adev->volread = ac97mixread;
+	adev->volwrite = ac97mixwrite;
 }
--- a/sys/src/9/pc/audiosb16.c
+++ b/sys/src/9/pc/audiosb16.c
@@ -8,6 +8,7 @@
 #include "fns.h"
 #include "io.h"
 #include "../port/error.h"
+#include "../port/audioif.h"
 
 typedef struct	Ring	Ring;
 typedef struct	Blaster	Blaster;
@@ -15,11 +16,8 @@
 
 enum
 {
-	Fmono		= 1,
-	Fin		= 2,
-	Fout		= 4,
-
-	Vaudio		= 0,
+	Vmaster,
+	Vaudio,
 	Vsynth,
 	Vcd,
 	Vline,
@@ -27,12 +25,11 @@
 	Vspeaker,
 	Vtreb,
 	Vbass,
+	Vigain,
+	Vogain,
 	Vspeed,
 	Nvol,
 
-	Speed		= 44100,
-	Ncmd		= 50,		/* max volume command words */
-
 	Blocksize	= 4096,
 	Blocks		= 65536/Blocksize,
 };
@@ -70,10 +67,6 @@
 	QLock;
 	Rendez	vous;
 	int	active;		/* boolean dma running */
-	int	rivol[Nvol];	/* right/left input/output volumes */
-	int	livol[Nvol];
-	int	rovol[Nvol];
-	int	lovol[Nvol];
 	int	major;		/* SB16 major version number (sb 4) */
 	int	minor;		/* SB16 minor version number */
 	ulong	totcount;	/* how many bytes processed since open */
@@ -80,35 +73,29 @@
 	vlong	tottime;	/* time at which totcount bytes were processed */
 	Ring	ring;		/* dma ring buffer */
 	Blaster	blaster;
+	int	lvol[Nvol];
+	int	rvol[Nvol];
 	Audio	*adev;
 };
 
-static struct
-{
-	char*	name;
-	int	flag;
-	int	ilval;		/* initial values */
-	int	irval;
-} volumes[] = {
-	[Vaudio]		"audio",	Fout, 		50,	50,
-	[Vsynth]		"synth",	Fin|Fout,	0,	0,
-	[Vcd]		"cd",		Fin|Fout,	0,	0,
-	[Vline]		"line",	Fin|Fout,	0,	0,
-	[Vmic]		"mic",	Fin|Fout|Fmono,	0,	0,
-	[Vspeaker]	"speaker",	Fout|Fmono,	0,	0,
-
-	[Vtreb]		"treb",		Fout, 		50,	50,
-	[Vbass]		"bass",		Fout, 		50,	50,
-
-	[Vspeed]	"speed",	Fin|Fout|Fmono,	Speed,	Speed,
-	0
+static Volume voltab[] = {
+	[Vmaster] "master", 0x30, 0xff, Stereo, 0,
+	[Vaudio] "audio", 0x32, 0xff, Stereo, 0,
+	[Vsynth] "synth", 0x34, 0xff, Stereo, 0,
+	[Vcd] "cd", 0x36, 0xff, Stereo, 0,
+	[Vline] "line", 0x38, 0xff, Stereo, 0,
+	[Vmic] "mic", 0x3a, 0xff, Mono, 0,
+	[Vspeaker] "speaker", 0x3b, 0xff, Mono, 0,
+	[Vtreb] "treb", 0x44, 0xff, Stereo, 0,
+	[Vbass] "bass", 0x46, 0xff, Stereo, 0,
+	[Vigain] "recgain", 0x3f, 0xff, Stereo, 0,
+	[Vogain] "outgain", 0x41, 0xff, Stereo, 0,
+	[Vspeed] "speed", 0, 0, Absolute, 0,
+	0,
 };
 
 static	char	Emajor[]	= "soundblaster not responding/wrong version";
-static	char	Emode[]		= "illegal open mode";
-static	char	Evolume[]	= "illegal volume specifier";
 
-
 static long
 buffered(Ring *r)
 {
@@ -242,109 +229,44 @@
 	return s;
 }
 
-static	void
-mxcmds(Blaster *blaster, int s, int v)
+static int
+mxsetvol(Audio *adev, int x, int a[2])
 {
-
-	if(v > 100)
-		v = 100;
-	if(v < 0)
-		v = 0;
-	mxcmd(blaster, s, (v*255)/100);
-}
-
-static	void
-mxcmdt(Blaster *blaster, int s, int v)
-{
-
-	if(v > 100)
-		v = 100;
-	if(v <= 0)
-		mxcmd(blaster, s, 0);
-	else
-		mxcmd(blaster, s, 255-100+v);
-}
-
-static	void
-mxcmdu(Blaster *blaster, int s, int v)
-{
-
-	if(v > 100)
-		v = 100;
-	if(v <= 0)
-		v = 0;
-	mxcmd(blaster, s, 128-50+v);
-}
-
-static	void
-mxvolume(Ctlr *ctlr)
-{
 	Blaster *blaster;
-	int *left, *right;
-	int source;
+	Ctlr *ctlr = adev->ctlr;
+	Volume *vol;
 
-	if(0){
-		left = ctlr->livol;
-		right = ctlr->rivol;
-	}else{
-		left = ctlr->lovol;
-		right = ctlr->rovol;
+	if(x == Vspeed){
+		ctlr->lvol[x] = ctlr->rvol[x] = a[0];
+		return 0;
 	}
 
+	vol = voltab+x;
 	blaster = &ctlr->blaster;
-
 	ilock(blaster);
+	switch(vol->type){
+	case Stereo:
+		ctlr->rvol[x] = a[1];
+		mxcmd(blaster, vol->reg+1, a[1]);
+		/* no break */
+	case Mono:
+		ctlr->lvol[x] = a[0];
+		mxcmd(blaster, vol->reg, a[0]);
+	}
+	iunlock(blaster);
 
-	mxcmd(blaster, 0x30, 255);		/* left master */
-	mxcmd(blaster, 0x31, 255);		/* right master */
-	mxcmd(blaster, 0x3f, 0);		/* left igain */
-	mxcmd(blaster, 0x40, 0);		/* right igain */
-	mxcmd(blaster, 0x41, 0);		/* left ogain */
-	mxcmd(blaster, 0x42, 0);		/* right ogain */
+	return 0;
+}
 
-	mxcmds(blaster, 0x32, left[Vaudio]);
-	mxcmds(blaster, 0x33, right[Vaudio]);
+static int
+mxgetvol(Audio *adev, int x, int a[2])
+{
+	Ctlr *ctlr = adev->ctlr;
 
-	mxcmds(blaster, 0x34, left[Vsynth]);
-	mxcmds(blaster, 0x35, right[Vsynth]);
+	a[0] = ctlr->lvol[x];
+	a[1] = ctlr->rvol[x];
 
-	mxcmds(blaster, 0x36, left[Vcd]);
-	mxcmds(blaster, 0x37, right[Vcd]);
-
-	mxcmds(blaster, 0x38, left[Vline]);
-	mxcmds(blaster, 0x39, right[Vline]);
-
-	mxcmds(blaster, 0x3a, left[Vmic]);
-	mxcmds(blaster, 0x3b, left[Vspeaker]);
-
-	mxcmdu(blaster, 0x44, left[Vtreb]);
-	mxcmdu(blaster, 0x45, right[Vtreb]);
-
-	mxcmdu(blaster, 0x46, left[Vbass]);
-	mxcmdu(blaster, 0x47, right[Vbass]);
-
-	source = 0;
-	if(left[Vsynth])
-		source |= 1<<6;
-	if(right[Vsynth])
-		source |= 1<<5;
-	if(left[Vaudio])
-		source |= 1<<4;
-	if(right[Vaudio])
-		source |= 1<<3;
-	if(left[Vcd])
-		source |= 1<<2;
-	if(right[Vcd])
-		source |= 1<<1;
-	if(left[Vmic])
-		source |= 1<<0;
-	if(0)
-		mxcmd(blaster, 0x3c, 0);		/* output switch */
-	else
-		mxcmd(blaster, 0x3c, source);
-	mxcmd(blaster, 0x3d, source);		/* input left switch */
-	mxcmd(blaster, 0x3e, source);		/* input right switch */
-	iunlock(blaster);
+	return 0;
 }
 
 static	void
@@ -385,22 +307,20 @@
 	ring = &ctlr->ring;
 	ilock(blaster);
 	dmaend(blaster->dma);
-	if(0) {
-		sbcmd(blaster, 0x42);			/* input sampling rate */
-		speed = ctlr->livol[Vspeed];
-	} else {
-		sbcmd(blaster, 0x41);			/* output sampling rate */
-		speed = ctlr->lovol[Vspeed];
-	}
+	if(0)
+		sbcmd(blaster, 0x42);	/* input sampling rate */
+	else
+		sbcmd(blaster, 0x41);	/* output sampling rate */
+	speed = ctlr->lvol[Vspeed];
 	sbcmd(blaster, speed>>8);
 	sbcmd(blaster, speed);
 
 	if(0)
-		sbcmd(blaster, 0xbe);			/* A/D, autoinit */
+		sbcmd(blaster, 0xbe);	/* A/D, autoinit */
 	else
-		sbcmd(blaster, 0xb6);			/* D/A, autoinit */
+		sbcmd(blaster, 0xb6);	/* D/A, autoinit */
 
-	sbcmd(blaster, 0x30);				/* stereo, signed 16 bit */
+	sbcmd(blaster, 0x30);	/* stereo, signed 16 bit */
 
 	count = (Blocksize>>1) - 1;
 	sbcmd(blaster, count);
@@ -420,7 +340,7 @@
 	int i;
 
 	outb(blaster->reset, 3);
-	delay(1);			/* >3 υs */
+	delay(1);	/* >3 υs */
 	outb(blaster->reset, 0);
 	delay(1);
 
@@ -430,7 +350,7 @@
 		return 1;
 	}
 
-	if(sbcmd(blaster, 0xC6)){		/* extended mode */
+	if(sbcmd(blaster, 0xC6)){	/* extended mode */
 		print("#A%d: barf 3\n", ctlrno);
 		return 1;
 	}
@@ -457,15 +377,11 @@
 	/*
 	 * Set the speed.
 	 */
-	if(0)
-		speed = ctlr->livol[Vspeed];
-	else
-		speed = ctlr->lovol[Vspeed];
+	speed = ctlr->lvol[Vspeed];
 	if(speed < 4000)
 		speed = 4000;
 	else if(speed > 48000)
 		speed = 48000;
-
 	if(speed > 22000)
 		  x = 0x80|(256-(795500+speed/2)/speed);
 	else
@@ -477,15 +393,15 @@
 	ess1688w(blaster, 0xA2, x & 0xFF);
 
 	if(0)
-		ess1688w(blaster, 0xB8, 0x0E);		/* A/D, autoinit */
+		ess1688w(blaster, 0xB8, 0x0E);	/* A/D, autoinit */
 	else
-		ess1688w(blaster, 0xB8, 0x04);		/* D/A, autoinit */
+		ess1688w(blaster, 0xB8, 0x04);	/* D/A, autoinit */
 	x = ess1688r(blaster, 0xA8) & ~0x03;
-	ess1688w(blaster, 0xA8, x|0x01);			/* 2 channels */
-	ess1688w(blaster, 0xB9, 2);			/* demand mode, 4 bytes per request */
+	ess1688w(blaster, 0xA8, x|0x01);	/* 2 channels */
+	ess1688w(blaster, 0xB9, 2);	/* demand mode, 4 bytes per request */
 
 	if(1)
-		ess1688w(blaster, 0xB6, 0);		/* for output */
+		ess1688w(blaster, 0xB6, 0);	/* for output */
 
 	ess1688w(blaster, 0xB7, 0x71);
 	ess1688w(blaster, 0xB7, 0xBC);
@@ -496,7 +412,7 @@
 	ess1688w(blaster, 0xB2, x|0x50);
 
 	if(1)
-		sbcmd(blaster, 0xD1);			/* speaker on */
+		sbcmd(blaster, 0xD1);	/* speaker on */
 
 	count = -Blocksize;
 	ess1688w(blaster, 0xA4, count & 0xFF);
@@ -571,19 +487,6 @@
 	iunlock(&ctlr->blaster);
 }
 
-static void
-resetlevel(Ctlr *ctlr)
-{
-	int i;
-
-	for(i=0; volumes[i].name; i++) {
-		ctlr->lovol[i] = volumes[i].ilval;
-		ctlr->rovol[i] = volumes[i].irval;
-		ctlr->livol[i] = volumes[i].ilval;
-		ctlr->rivol[i] = volumes[i].irval;
-	}
-}
-
 static long
 audiobuffered(Audio *adev)
 {
@@ -591,17 +494,15 @@
 }
 
 static long
-audiostatus(Audio *adev, void *a, long n, vlong off)
+audiostatus(Audio *adev, void *a, long n, vlong)
 {
-	char buf[300];
-	Ctlr *ctlr;
+	Ctlr *ctlr = adev->ctlr;
 
-	ctlr = adev->ctlr;
-	snprint(buf, sizeof(buf), 
-		"buffered %.4lx/%.4lx  offset %10lud time %19lld\n",
-		buffered(&ctlr->ring), available(&ctlr->ring),
+	return snprint((char*)a, n, 
+		"bufsize %6lud buffered %6ld "
+		"offset %10lud time %19lld\n",
+		ctlr->ring.nbuf, buffered(&ctlr->ring),
 		ctlr->totcount, ctlr->tottime);
-	return readstr(off, a, n, buf);
 }
 
 static int
@@ -626,7 +527,6 @@
 	uchar *p, *e;
 	Ctlr *ctlr;
 	Ring *ring;
-	long m;
 
 	p = vp;
 	e = p + n;
@@ -638,17 +538,15 @@
 	}
 	ring = &ctlr->ring;
 	while(p < e) {
-		if((m = writering(ring, p, e - p)) <= 0){
+		if((n = writering(ring, p, e - p)) <= 0){
 			if(!ctlr->active && ring->ri == 0)
 				ctlr->blaster.startdma(ctlr);
-			if(!ctlr->active){
+			if(!ctlr->active)
 				setempty(ctlr);
-				continue;
-			}
-			sleep(&ctlr->vous, anybuf, ctlr);
-			continue;
+			else
+				sleep(&ctlr->vous, anybuf, ctlr);
 		}
-		p += m;
+		p += n;
 	}
 	poperror();
 	qunlock(ctlr);
@@ -673,6 +571,49 @@
 	qunlock(ctlr);
 }
 
+static long
+audiovolread(Audio *adev, void *a, long n, vlong)
+{
+	return genaudiovolread(adev, a, n, 0, voltab, mxgetvol, 0);
+}
+
+static long
+audiovolwrite(Audio *adev, void *a, long n, vlong)
+{
+	Blaster *blaster;
+	Ctlr *ctlr;
+	int source;
+
+	ctlr = adev->ctlr;
+	blaster = &ctlr->blaster;
+
+	n = genaudiovolwrite(adev, a, n, 0, voltab, mxsetvol, 0);
+
+	source = 0;
+	if(ctlr->lvol[Vsynth])
+		source |= 1<<6;
+	if(ctlr->rvol[Vsynth])
+		source |= 1<<5;
+	if(ctlr->lvol[Vaudio])
+		source |= 1<<4;
+	if(ctlr->rvol[Vaudio])
+		source |= 1<<3;
+	if(ctlr->lvol[Vcd])
+		source |= 1<<2;
+	if(ctlr->rvol[Vcd])
+		source |= 1<<1;
+	if(ctlr->lvol[Vmic])
+		source |= 1<<0;
+
+	ilock(blaster);
+	mxcmd(blaster, 0x3c, source);	/* output switch */
+	mxcmd(blaster, 0x3d, source);	/* input left switch */
+	mxcmd(blaster, 0x3e, source);	/* input right switch */
+	iunlock(blaster);
+
+	return n;
+}
+
 static int
 ess1688(ISAConf* sbconf, Blaster *blaster, int ctlrno)
 {
@@ -685,7 +626,8 @@
 	major = sbread(blaster);
 	minor = sbread(blaster);
 	if(major != 0x68 || minor != 0x8B){
-		print("#A%d: model %#.2x %#.2x; not ESS1688 compatible\n", ctlrno, major, minor);
+		print("#A%d: model %#.2x %#.2x; not ESS1688 compatible\n",
+			ctlrno, major, minor);
 		return -1;
 	}
 
@@ -763,12 +705,14 @@
 	}
 
 	if(ioalloc(sbconf.port, 0x10, 0, "audio") < 0){
-		print("#A%d: cannot ioalloc range %lux+0x10\n", adev->ctlrno, sbconf.port);
+		print("#A%d: cannot ioalloc range %lux+0x10\n",
+			adev->ctlrno, sbconf.port);
 		return -1;
 	}
 	if(ioalloc(sbconf.port+0x100, 1, 0, "audio.mpu401") < 0){
 		iofree(sbconf.port);
-		print("#A%d: cannot ioalloc range %lux+0x01\n", adev->ctlrno, sbconf.port+0x100);
+		print("#A%d: cannot ioalloc range %lux+0x01\n",
+			adev->ctlrno, sbconf.port+0x100);
 		return -1;
 	}
 
@@ -792,8 +736,6 @@
 	blaster->startdma = sb16startdma;
 	blaster->intr = sb16intr;
 
-	resetlevel(ctlr);
-
 	outb(blaster->reset, 1);
 	delay(1);			/* >3 υs */
 	outb(blaster->reset, 0);
@@ -802,6 +744,7 @@
 	i = sbread(blaster);
 	if(i != 0xaa) {
 		print("#A%d: no response #%.2x\n", adev->ctlrno, i);
+Errout:
 		iofree(sbconf.port);
 		iofree(sbconf.port+0x100);
 		free(ctlr);
@@ -813,12 +756,11 @@
 	ctlr->minor = sbread(blaster);
 
 	if(ctlr->major != 4) {
-		if(ctlr->major != 3 || ctlr->minor != 1 || ess1688(&sbconf, blaster, adev->ctlrno)){
+		if(ctlr->major != 3 || ctlr->minor != 1 ||
+			ess1688(&sbconf, blaster, adev->ctlrno)){
 			print("#A%d: model %#.2x %#.2x; not SB 16 compatible\n",
 				adev->ctlrno, ctlr->major, ctlr->minor);
-			iofree(sbconf.port);
-			iofree(sbconf.port+0x100);
-			return -1;
+			goto Errout;
 		}
 		ctlr->major = 4;
 	}
@@ -827,8 +769,15 @@
 	 * initialize the mixer
 	 */
 	mxcmd(blaster, 0x00, 0);			/* Reset mixer */
-	mxvolume(ctlr);
 
+	for(i=0; i<Nvol; i++){
+		int a[2];
+
+		a[0] = 0;
+		a[1] = 0;
+		mxsetvol(adev, i, a);
+	}
+
 	/* set irq */
 	for(i=0; i<nelem(irq); i++){
 		if(sbconf.irq == irq[i]){
@@ -857,11 +806,10 @@
 				break;
 			}
 		}
-		if(blaster->dma<5){
-			blaster->dma = 7;
-			continue;
-		}
-		break;
+		if(blaster->dma>=5)
+			break;
+
+		blaster->dma = 7;
 	}
 
 	print("#A%d: %s port 0x%04lux irq %d dma %d\n", adev->ctlrno, sbconf.type,
@@ -868,21 +816,22 @@
 		sbconf.port, sbconf.irq, blaster->dma);
 
 	ctlr->ring.nbuf = Blocks*Blocksize;
-	if(dmainit(blaster->dma, ctlr->ring.nbuf)){
-		free(ctlr);
-		return -1;
-	}
+	if(dmainit(blaster->dma, ctlr->ring.nbuf))
+		goto Errout;
 	ctlr->ring.buf = dmabva(blaster->dma);
+	print("#A%d: %s dma buffer %p-%p\n", adev->ctlrno, sbconf.type,
+		ctlr->ring.buf, ctlr->ring.buf+ctlr->ring.nbuf);
 
-	intrenable(sbconf.irq, audiointr, adev, BUSUNKNOWN, sbconf.type);
-
 	setempty(ctlr);
-	mxvolume(ctlr);
 
 	adev->write = audiowrite;
 	adev->close = audioclose;
+	adev->volread = audiovolread;
+	adev->volwrite = audiovolwrite;
 	adev->status = audiostatus;
 	adev->buffered = audiobuffered;
+
+	intrenable(sbconf.irq, audiointr, adev, BUSUNKNOWN, sbconf.type);
 
 	return 0;
 }
--- /dev/null
+++ b/sys/src/9/port/audioif.h
@@ -1,0 +1,50 @@
+typedef struct Audio Audio;
+typedef struct Volume Volume;
+
+struct Audio
+{
+	char *name;
+
+	void *ctlr;
+	void *mixer;
+
+	long (*read)(Audio *, void *, long, vlong);
+	long (*write)(Audio *, void *, long, vlong);
+	void (*close)(Audio *);
+
+	long (*volread)(Audio *, void *, long, vlong);
+	long (*volwrite)(Audio *, void *, long, vlong);
+
+	long (*ctl)(Audio *, void *, long, vlong);
+	long (*status)(Audio *, void *, long, vlong);
+	long (*buffered)(Audio *);
+
+	int ctlrno;
+	Audio *next;
+};
+
+enum {
+	Left,
+	Right,
+	Stereo,
+	Absolute,
+};
+
+#define Mono Left
+
+struct Volume
+{
+	char *name;
+	int reg;
+	int range;
+	int type;
+	int cap;
+};
+
+extern void addaudiocard(char *, int (*)(Audio *));
+extern long genaudiovolread(Audio *adev, void *a, long n, vlong off,
+	Volume *vol, int (*volget)(Audio *, int, int *),
+	ulong caps);
+extern long genaudiovolwrite(Audio *adev, void *a, long n, vlong off,
+	Volume *vol, int (*volset)(Audio *, int, int *),
+	ulong caps);
--- a/sys/src/9/port/devaudio.c
+++ b/sys/src/9/port/devaudio.c
@@ -5,13 +5,28 @@
 #include	"fns.h"
 #include	"io.h"
 #include	"../port/error.h"
+#include	"../port/audioif.h"
 
 typedef struct Audioprobe Audioprobe;
-struct Audioprobe {
+typedef struct Audiochan Audiochan;
+
+struct Audioprobe
+{
 	char *name;
 	int (*probe)(Audio*);
 };
 
+struct Audiochan
+{
+	QLock;
+
+	Chan *owner;
+	Audio *adev;
+
+	char *data;
+	char buf[1024+1];
+};
+
 enum {
 	Qdir = 0,
 	Qaudio,
@@ -18,14 +33,8 @@
 	Qaudioctl,
 	Qaudiostatus,
 	Qvolume,
-
-	Maxaudioprobes = 8,
 };
 
-static int naudioprobes;
-static Audioprobe audioprobes[Maxaudioprobes];
-static Audio *audiodevs;
-
 static Dirtab audiodir[] = {
 	".",	{Qdir, 0, QTDIR},	0,	DMDIR|0555,
 	"audio",	{Qaudio},			0,	0666,
@@ -34,10 +43,22 @@
 	"volume",	{Qvolume},			0,	0666,
 };
 
+
+static int naudioprobes;
+static Audioprobe audioprobes[16];
+static Audio *audiodevs;
+
+static char Evolume[] = "illegal volume specifier";
+
+
 void
 addaudiocard(char *name, int (*probefn)(Audio *))
 {
 	Audioprobe *probe;
+
+	if(naudioprobes >= nelem(audioprobes))
+		return;
+
 	probe = &audioprobes[naudioprobes++];
 	probe->name = name;
 	probe->probe = probefn;
@@ -73,42 +94,98 @@
 	*pp = nil;
 }
 
+static Audiochan*
+audioclone(Chan *c, Audio *adev)
+{
+	Audiochan *ac;
+
+	ac = malloc(sizeof(Audiochan));
+	if(ac == nil){
+		cclose(c);
+		return nil;
+	}
+
+	c->aux = ac;
+	ac->owner = c;
+	ac->adev = adev;
+	ac->data = nil;
+
+	return ac;
+}
+
 static Chan*
 audioattach(char *spec)
 {
+	static int first = 1;
+	Audiochan *ac;
+	Audio *adev;
 	Chan *c;
-	Audio *p;
 	int i;
+
 	if(spec != nil && *spec != '\0')
 		i = strtol(spec, 0, 10);
 	else
 		i = 0;
-	for(p = audiodevs; p; p = p->next)
+	for(adev = audiodevs; adev; adev = adev->next)
 		if(i-- == 0)
 			break;
-	if(p == nil)
+	if(adev == nil)
 		error(Enodev);
+
 	c = devattach('A', spec);
 	c->qid.path = Qdir;
-	c->aux = p;
-	if(p->attach)
-		p->attach(p);
+
+	if((ac = audioclone(c, adev)) == nil)
+		error(Enomem);
+
+	if(first && adev->volwrite){
+		first = 0;
+ 
+		strcpy(ac->buf, "speed 44100");
+		if(!waserror()){
+			adev->volwrite(adev, ac->buf, strlen(ac->buf), 0);
+			poperror();
+		}
+		strcpy(ac->buf, "master 100");
+		if(!waserror()){
+			adev->volwrite(adev, ac->buf, strlen(ac->buf), 0);
+			poperror();
+		}
+		strcpy(ac->buf, "audio 100");
+		if(!waserror()){
+			adev->volwrite(adev, ac->buf, strlen(ac->buf), 0);
+			poperror();
+		}
+		strcpy(ac->buf, "head 100");
+		if(!waserror()){
+			adev->volwrite(adev, ac->buf, strlen(ac->buf), 0);
+			poperror();
+		}
+	}
+
 	return c;
 }
 
+static Chan*
+audioopen(Chan *c, int omode)
+{
+	return devopen(c, omode, audiodir, nelem(audiodir), devgen);
+}
+
 static long
 audioread(Chan *c, void *a, long n, vlong off)
 {
+	Audiochan *ac;
 	Audio *adev;
 	long (*fn)(Audio *, void *, long, vlong);
-	adev = c->aux;
+
+	ac = c->aux;
+	adev = ac->adev;
+
+	fn = nil;
 	switch((ulong)c->qid.path){
-	default:
-		error("audio bugger (rd)");
-	case Qaudioctl:
-		fn = adev->ctl;
-		break;
 	case Qdir:
+		/* BUG: race */
 		if(adev->buffered)
 			audiodir[Qaudio].length = adev->buffered(adev);
 		return devdirread(c, a, n, audiodir, nelem(audiodir), devgen);
@@ -115,6 +192,9 @@
 	case Qaudio:
 		fn = adev->read;
 		break;
+	case Qaudioctl:
+		fn = adev->ctl;
+		break;
 	case Qaudiostatus:
 		fn = adev->status;
 		break;
@@ -123,7 +203,34 @@
 		break;
 	}
 	if(fn == nil)
-		error("not implemented");
+		error(Egreg);
+
+	switch((ulong)c->qid.path){
+	case Qaudioctl:
+	case Qaudiostatus:
+	case Qvolume:
+		qlock(ac);
+		if(waserror()){
+			qunlock(ac);
+			nexterror();
+		}
+		/* generate the text on first read */
+		if(ac->data == nil || off == 0){
+			long l;
+
+			ac->data = nil;
+			l = fn(adev, ac->buf, sizeof(ac->buf)-1, 0);
+			if(l < 0)
+				l = 0;
+			ac->buf[l] = 0;
+			ac->data = ac->buf;
+		}
+		/* then serve all requests from buffer */
+		n = readstr(off, a, n, ac->data);
+		qunlock(ac);
+		poperror();
+		return n;
+	}
 	return fn(adev, a, n, off);
 }
 
@@ -130,12 +237,15 @@
 static long
 audiowrite(Chan *c, void *a, long n, vlong off)
 {
+	Audiochan *ac;
 	Audio *adev;
 	long (*fn)(Audio *, void *, long, vlong);
-	adev = c->aux;
+
+	ac = c->aux;
+	adev = ac->adev;
+
+	fn = nil;
 	switch((ulong)c->qid.path){
-	default:
-		error("audio bugger (wr)");
 	case Qaudio:
 		fn = adev->write;
 		break;
@@ -147,7 +257,28 @@
 		break;
 	}
 	if(fn == nil)
-		error("not implemented");
+		error(Egreg);
+
+	switch((ulong)c->qid.path){
+	case Qaudioctl:
+	case Qvolume:
+		if(n >= sizeof(ac->buf))
+			error(Etoobig);
+
+		/* copy data to audiochan buffer so it can be modified */
+		qlock(ac);
+		if(waserror()){
+			qunlock(ac);
+			nexterror();
+		}
+		ac->data = nil;
+		memmove(ac->buf, a, n);
+		ac->buf[n] = 0;
+		n = fn(adev, ac->buf, n, 0);
+		qunlock(ac);
+		poperror();
+		return n;
+	}
 	return fn(adev, a, n, off);
 }
 
@@ -154,16 +285,19 @@
 static void
 audioclose(Chan *c)
 {
+	Audiochan *ac;
 	Audio *adev;
-	adev = c->aux;
-	switch((ulong)c->qid.path){
-	default:
-		return;
-	case Qaudio:
-		if(adev->close == nil)
-			return;
-		adev->close(adev);
-		return;
+
+	ac = c->aux;
+	adev = ac->adev;
+	if(c->qid.path == Qaudio && (c->flag & COPEN))
+		if(adev->close)
+			adev->close(adev);
+
+	if(ac->owner == c){
+		ac->owner = nil;
+		c->aux = nil;
+		free(ac);
 	}
 }
 
@@ -170,23 +304,148 @@
 static Walkqid*
 audiowalk(Chan *c, Chan *nc, char **name, int nname)
 {
-	return devwalk(c, nc, name, nname, audiodir, nelem(audiodir), devgen);
+	Audiochan *ac;
+	Audio *adev;
+	Walkqid *wq;
+
+	ac = c->aux;
+	adev = ac->adev;
+	wq = devwalk(c, nc, name, nname, audiodir, nelem(audiodir), devgen);
+	if(wq && wq->clone){
+		if(audioclone(wq->clone, adev) == nil){
+			free(wq);
+			wq = nil;
+		}
+	}
+	return wq;
 }
 
 static int
 audiostat(Chan *c, uchar *dp, int n)
 {
+	Audiochan *ac;
 	Audio *adev;
-	adev = c->aux;
+
+	ac = c->aux;
+	adev = ac->adev;
+
+	/* BUG: race */
 	if(adev->buffered && (ulong)c->qid.path == Qaudio)
 		audiodir[Qaudio].length = adev->buffered(adev);
+
 	return devstat(c, dp, n, audiodir, nelem(audiodir), devgen);
 }
 
-static Chan*
-audioopen(Chan *c, int omode)
+/*
+ * audioread() made sure the buffer is big enougth so a full volume
+ * table can be serialized in one pass.
+ */
+long
+genaudiovolread(Audio *adev, void *a, long n, vlong,
+	Volume *vol, int (*volget)(Audio *, int, int *), ulong caps)
 {
-	return devopen(c, omode, audiodir, nelem(audiodir), devgen);
+	int i, j, v[2];
+	char *p, *e;
+
+	p = a;
+	e = p + n;
+	for(i = 0; vol[i].name != 0; ++i){
+		if(vol[i].cap && (vol[i].cap & caps) == 0)
+			continue;
+		v[0] = 0;
+		v[1] = 0;
+		if((*volget)(adev, i, v) != 0)
+			continue;
+		if(vol[i].type == Absolute)
+			p += snprint(p, e - p, "%s %d\n", vol[i].name, v[0]);
+		else {
+			for(j=0; j<2; j++){
+				if(v[j] < 0)
+					v[j] = 0;
+				if(v[j] > vol[i].range)
+					v[j] = vol[i].range;
+				v[j] = (v[j]*100)/vol[i].range;
+			}
+			switch(vol[i].type){
+			case Left:
+				p += snprint(p, e - p, "%s %d\n", vol[i].name, v[0]);
+				break;
+			case Right:
+				p += snprint(p, e - p, "%s %d\n", vol[i].name, v[1]);
+				break;
+			case Stereo:
+				p += snprint(p, e - p, "%s %d %d\n", vol[i].name, v[0], v[1]);
+				break;
+			}
+		}
+	}
+
+	return p - (char*)a;
+}
+
+/*
+ * genaudiovolwrite modifies the buffer that gets passed to it. this
+ * is ok as long as it is called from inside Audio.volwrite() because
+ * audiowrite() copies the data to Audiochan.buf[] and inserts a
+ * terminating \0 byte before calling Audio.volwrite().
+ */
+long
+genaudiovolwrite(Audio *adev, void *a, long n, vlong,
+	Volume *vol, int (*volset)(Audio *, int, int *), ulong caps)
+{
+	int ntok, i, j, v[2];
+	char *p, *e, *x, *tok[4];
+
+	p = a;
+	e = p + n;
+
+	for(;p < e; p = x){
+		if(x = strchr(p, '\n'))
+			*x++ = 0;
+		else
+			x = e;
+		ntok = tokenize(p, tok, 4);
+		if(ntok <= 0)
+			continue;
+		if(ntok == 1){
+			tok[1] = tok[0];
+			tok[0] = "master";
+			ntok = 2;
+		}
+		for(i = 0; vol[i].name != 0; i++){
+			if(vol[i].cap && (vol[i].cap & caps) == 0)
+				continue;
+			if(cistrcmp(vol[i].name, tok[0]))
+				continue;
+	
+			if((ntok>2) && (!cistrcmp(tok[1], "out") || !cistrcmp(tok[1], "in")))
+				memmove(tok+1, tok+2, --ntok);
+
+			v[0] = 0;
+			v[1] = 0;
+			if(ntok > 1)
+				v[0] = v[1] = atoi(tok[1]);
+			if(ntok > 2)
+				v[1] = atoi(tok[2]);
+			if(vol[i].type == Absolute)
+				(*volset)(adev, i, v);
+			else {
+				for(j=0; j<2; j++){
+					v[j] = (50+(v[j]*vol[i].range))/100;
+					if(v[j] < 0)
+						v[j] = 0;
+					if(v[j] > vol[i].range)
+						v[j] = vol[i].range;
+				}
+				(*volset)(adev, i, v);
+			}
+			break;
+		}
+		if(vol[i].name == nil)
+			error(Evolume);
+	}
+
+	return n;
 }
 
 Dev audiodevtab = {
--- a/sys/src/9/port/portdat.h
+++ b/sys/src/9/port/portdat.h
@@ -1,5 +1,4 @@
 typedef struct Alarms	Alarms;
-typedef struct Audio	Audio;
 typedef struct Block	Block;
 typedef struct Chan	Chan;
 typedef struct Cmdbuf	Cmdbuf;
@@ -918,23 +917,6 @@
 };
 
 extern	Uart*	consuart;
-
-struct Audio {
-	Audio *next;
-	char *name;
-	void *ctlr;
-	void *mixer;
-	void (*attach)(Audio *);
-	long (*read)(Audio *, void *, long, vlong);
-	long (*write)(Audio *, void *, long, vlong);
-	long (*volread)(Audio *, void *, long, vlong);
-	long (*volwrite)(Audio *, void *, long, vlong);
-	void (*close)(Audio *);
-	long (*ctl)(Audio *, void *, long, vlong);
-	long (*status)(Audio *, void *, long, vlong);
-	long (*buffered)(Audio *);
-	int ctlrno;
-};
 
 /*
  *  performance timers, all units in perfticks
--- a/sys/src/9/port/portfns.h
+++ b/sys/src/9/port/portfns.h
@@ -4,7 +4,6 @@
 int		addphysseg(Physseg*);
 void		addbootfile(char*, uchar*, ulong);
 void		addwatchdog(Watchdog*);
-void		addaudiocard(char *, int (*)(Audio *));
 Block*		adjustblock(Block*, int);
 void		alarmkproc(void*);
 Block*		allocb(int);