xref: /plan9/sys/src/cmd/usb/audio/audio.c (revision ed868a7c1f0ac853ba7f6fe1cfc03d41d6ae4523)
1906943f9SDavid du Colombier /*
2906943f9SDavid du Colombier  * USB audio driver for Plan 9
3906943f9SDavid du Colombier  * This needs a full rewrite.
4906943f9SDavid du Colombier  * As it is, it does not check for all errors,
5906943f9SDavid du Colombier  * mixes the audio data structures with the usb configuration,
6906943f9SDavid du Colombier  * may cross nil pointers, and is hard to debug and fix.
7906943f9SDavid du Colombier  * Also, it does not issue a dettach request to the endpoint
8906943f9SDavid du Colombier  * after the device is unplugged. This means that the old
9906943f9SDavid du Colombier  * endpoint would still be around until manually reclaimed.
10906943f9SDavid du Colombier  */
11906943f9SDavid du Colombier 
12906943f9SDavid du Colombier #include <u.h>
13906943f9SDavid du Colombier #include <libc.h>
14906943f9SDavid du Colombier #include <thread.h>
15906943f9SDavid du Colombier #include "usb.h"
16906943f9SDavid du Colombier #include "audio.h"
17906943f9SDavid du Colombier #include "audioctl.h"
18906943f9SDavid du Colombier 
19906943f9SDavid du Colombier #define STACKSIZE 16*1024
20906943f9SDavid du Colombier 
21906943f9SDavid du Colombier extern char* srvpost;
22906943f9SDavid du Colombier char * mntpt;
23906943f9SDavid du Colombier 
24906943f9SDavid du Colombier Channel *controlchan;
25906943f9SDavid du Colombier 
26906943f9SDavid du Colombier int verbose;
27906943f9SDavid du Colombier int setrec = 0;
28906943f9SDavid du Colombier int defaultspeed[2] = {44100, 44100};
29906943f9SDavid du Colombier Dev *buttondev;
30906943f9SDavid du Colombier Dev *epdev[2];
31906943f9SDavid du Colombier 
32906943f9SDavid du Colombier static void
audio_endpoint(Dev *,Desc * dd)33906943f9SDavid du Colombier audio_endpoint(Dev *, Desc *dd)
34906943f9SDavid du Colombier {
35906943f9SDavid du Colombier 	byte *b = (uchar*)&dd->data;
36906943f9SDavid du Colombier 	int n = dd->data.bLength;
37906943f9SDavid du Colombier 	char *hd;
38906943f9SDavid du Colombier 
39906943f9SDavid du Colombier 	switch(b[2]){
40906943f9SDavid du Colombier 	case 0x01:
41906943f9SDavid du Colombier 		if(usbdebug){
42906943f9SDavid du Colombier 			fprint(2, "CS_ENDPOINT for attributes 0x%x, lockdelayunits %d, lockdelay %#ux, ",
43906943f9SDavid du Colombier 				b[3], b[4], b[5] | (b[6]<<8));
44906943f9SDavid du Colombier 			if(b[3] & has_setspeed)
45906943f9SDavid du Colombier 				fprint(2, "has sampling-frequency control");
46906943f9SDavid du Colombier 			else
47906943f9SDavid du Colombier 				fprint(2, "does not have sampling-frequency control");
48906943f9SDavid du Colombier 			if(b[3] & 0x1<<1)
49906943f9SDavid du Colombier 				fprint(2, ", has pitch control");
50906943f9SDavid du Colombier 			else
51906943f9SDavid du Colombier 				fprint(2, ", does not have pitch control");
52906943f9SDavid du Colombier 			if(b[3] & 0x1<<7)
53906943f9SDavid du Colombier 				fprint(2, ", max packets only");
54906943f9SDavid du Colombier 			fprint(2, "\n");
55906943f9SDavid du Colombier 		}
56906943f9SDavid du Colombier 		if(dd->conf == nil)
57906943f9SDavid du Colombier 			sysfatal("conf == nil");
58906943f9SDavid du Colombier 		if(dd->iface == nil)
59906943f9SDavid du Colombier 			sysfatal("iface == nil");
60906943f9SDavid du Colombier 		if(dd->altc == nil)
61906943f9SDavid du Colombier 			sysfatal("alt == nil");
62906943f9SDavid du Colombier 		if(dd->altc->aux == nil)
63906943f9SDavid du Colombier 			dd->altc->aux= mallocz(sizeof(Audioalt),1);
64906943f9SDavid du Colombier 		((Audioalt*)dd->altc->aux)->caps |= b[3];
65906943f9SDavid du Colombier 		break;
66906943f9SDavid du Colombier 	case 0x02:
67906943f9SDavid du Colombier 		if(usbdebug){
68906943f9SDavid du Colombier 			fprint(2, "CS_INTERFACE FORMAT_TYPE %d, channels %d, subframesize %d, resolution %d, freqtype %d, ",
69906943f9SDavid du Colombier 				b[3], b[4], b[5], b[6], b[7]);
70906943f9SDavid du Colombier 			fprint(2, "freq0 %d, freq1 %d\n",
71906943f9SDavid du Colombier 				b[8] | (b[9]<<8) | (b[10]<<16), b[11] | (b[12]<<8) | (b[13]<<16));
72906943f9SDavid du Colombier 		}
73906943f9SDavid du Colombier 		break;
74906943f9SDavid du Colombier 	default:
75906943f9SDavid du Colombier 		if(usbdebug){
76906943f9SDavid du Colombier 			hd = hexstr(b, n);
77906943f9SDavid du Colombier 			fprint(2, "CS_INTERFACE: %s\n", hd);
78906943f9SDavid du Colombier 			free(hd);
79906943f9SDavid du Colombier 		}
80906943f9SDavid du Colombier 	}
81906943f9SDavid du Colombier }
82906943f9SDavid du Colombier 
83906943f9SDavid du Colombier enum {
84906943f9SDavid du Colombier 	None,
85906943f9SDavid du Colombier 	Volumeset,
86906943f9SDavid du Colombier 	Volumeget,
87906943f9SDavid du Colombier 	Altset,
88906943f9SDavid du Colombier 	Altget,
89906943f9SDavid du Colombier 	Speedget,
90906943f9SDavid du Colombier };
91906943f9SDavid du Colombier 
92906943f9SDavid du Colombier void
controlproc(void *)93906943f9SDavid du Colombier controlproc(void *)
94906943f9SDavid du Colombier {
95906943f9SDavid du Colombier 	/* Proc that looks after /dev/usb/%d/ctl */
96906943f9SDavid du Colombier 	int i, nf;
97906943f9SDavid du Colombier 	char *req, *args[8];
98906943f9SDavid du Colombier 	Audiocontrol *c;
99906943f9SDavid du Colombier 	long value[8];
100906943f9SDavid du Colombier 	Channel *replchan;
101906943f9SDavid du Colombier 
102906943f9SDavid du Colombier 	while(req = recvp(controlchan)){
103906943f9SDavid du Colombier 		int rec;
104906943f9SDavid du Colombier 
105906943f9SDavid du Colombier 		nf = tokenize(req, args, nelem(args));
106906943f9SDavid du Colombier 		if(nf < 3)
107906943f9SDavid du Colombier 			sysfatal("controlproc: not enough arguments");
108906943f9SDavid du Colombier 		replchan = (Channel*)strtol(args[0], nil, 0);
109906943f9SDavid du Colombier 		if(strcmp(args[2], "playback") == 0)
110906943f9SDavid du Colombier 			rec = Play;
111906943f9SDavid du Colombier 		else if(strcmp(args[2], "record") == 0)
112906943f9SDavid du Colombier 			rec = Record;
113906943f9SDavid du Colombier 		else{
114906943f9SDavid du Colombier 			/* illegal request */
115906943f9SDavid du Colombier 			dprint(2, "%s must be record or playback", args[2]);
116906943f9SDavid du Colombier 			if(replchan) chanprint(replchan, "%s must be record or playback", args[2]);
117906943f9SDavid du Colombier 			free(req);
118906943f9SDavid du Colombier 			continue;
119906943f9SDavid du Colombier 		}
120906943f9SDavid du Colombier 		c = nil;
121906943f9SDavid du Colombier 		for(i = 0; i < Ncontrol; i++){
122906943f9SDavid du Colombier 			c = &controls[rec][i];
123906943f9SDavid du Colombier 			if(strcmp(args[1], c->name) == 0)
124906943f9SDavid du Colombier 				break;
125906943f9SDavid du Colombier 		}
126906943f9SDavid du Colombier 		if(i == Ncontrol){
127906943f9SDavid du Colombier 			dprint(2, "Illegal control name: %s", args[1]);
128906943f9SDavid du Colombier 			if(replchan) chanprint(replchan, "Illegal control name: %s", args[1]);
129906943f9SDavid du Colombier 		}else if(!c->settable){
130906943f9SDavid du Colombier 			dprint(2, "%s %s is not settable", args[1], args[2]);
131906943f9SDavid du Colombier 			if(replchan)
132906943f9SDavid du Colombier 				chanprint(replchan, "%s %s is not settable", args[1], args[2]);
133906943f9SDavid du Colombier 		}else if(nf < 4){
134906943f9SDavid du Colombier 			dprint(2, "insufficient arguments for %s %s", args[1], args[2]);
135906943f9SDavid du Colombier 			if(replchan)
136906943f9SDavid du Colombier 				chanprint(replchan, "insufficient arguments for %s %s",
137906943f9SDavid du Colombier 					args[1], args[2]);
138906943f9SDavid du Colombier 		}else if(ctlparse(args[3], c, value) < 0){
139906943f9SDavid du Colombier 			if(replchan)
140906943f9SDavid du Colombier 				chanprint(replchan, "parse error in %s %s", args[1], args[2]);
141906943f9SDavid du Colombier 		}else{
142906943f9SDavid du Colombier 			dprint(2, "controlproc: setcontrol %s %s %s\n",
143906943f9SDavid du Colombier 					rec?"in":"out", args[1], args[3]);
144906943f9SDavid du Colombier 			if(setcontrol(rec, args[1], value) < 0){
145906943f9SDavid du Colombier 				if(replchan)
146906943f9SDavid du Colombier 					chanprint(replchan, "setting %s %s failed", args[1], args[2]);
147906943f9SDavid du Colombier 			}else{
148906943f9SDavid du Colombier 				if(replchan) chanprint(replchan, "ok");
149906943f9SDavid du Colombier 			}
150906943f9SDavid du Colombier 			ctlevent();
151906943f9SDavid du Colombier 		}
152906943f9SDavid du Colombier 		free(req);
153906943f9SDavid du Colombier 	}
154906943f9SDavid du Colombier }
155906943f9SDavid du Colombier 
156906943f9SDavid du Colombier void
buttonproc(void *)157906943f9SDavid du Colombier buttonproc(void *)
158906943f9SDavid du Colombier {
159906943f9SDavid du Colombier 	int	i, fd, b;
160906943f9SDavid du Colombier 	char err[32];
161906943f9SDavid du Colombier 	byte buf[1];
162906943f9SDavid du Colombier 	Audiocontrol *c;
163906943f9SDavid du Colombier 
164906943f9SDavid du Colombier 	fd = buttondev->dfd;
165906943f9SDavid du Colombier 
166906943f9SDavid du Colombier 	c = &controls[Play][Volume_control];
167906943f9SDavid du Colombier 	for(;;){
168906943f9SDavid du Colombier 		if((b = read(fd, buf, 1)) < 0){
169906943f9SDavid du Colombier 			rerrstr(err, sizeof err);
170906943f9SDavid du Colombier 			if(strcmp(err, "interrupted") == 0){
171906943f9SDavid du Colombier 				dprint(2, "read interrupted\n");
172906943f9SDavid du Colombier 				continue;
173906943f9SDavid du Colombier 			}
174906943f9SDavid du Colombier 			sysfatal("read %s/data: %r", buttondev->dir);
175906943f9SDavid du Colombier 		}
176906943f9SDavid du Colombier 		if(b == 0 || buf[0] == 0){
177906943f9SDavid du Colombier 			continue;
178906943f9SDavid du Colombier 		}else if(buf[0] == 1){
179906943f9SDavid du Colombier 			if(c->chans == 0)
180906943f9SDavid du Colombier 				c->value[0] += c->step;
181906943f9SDavid du Colombier 			else
182906943f9SDavid du Colombier 				for(i = 1; i < 8; i++)
183906943f9SDavid du Colombier 					if(c->chans & 1 << i)
184906943f9SDavid du Colombier 						c->value[i] += c->step;
185906943f9SDavid du Colombier 			chanprint(controlchan, "0 volume playback %A", c);
186906943f9SDavid du Colombier 		}else if(buf[0] == 2){
187906943f9SDavid du Colombier 			if(c->chans == 0)
188906943f9SDavid du Colombier 				c->value[0] -= c->step;
189906943f9SDavid du Colombier 			else
190906943f9SDavid du Colombier 				for(i = 1; i < 8; i++)
191906943f9SDavid du Colombier 					if(c->chans & 1 << i)
192906943f9SDavid du Colombier 						c->value[i] -= c->step;
193906943f9SDavid du Colombier 			chanprint(controlchan, "0 volume playback %A", c);
194906943f9SDavid du Colombier 		}else if(usbdebug){
195906943f9SDavid du Colombier 			fprint(2, "button");
196906943f9SDavid du Colombier 			for(i = 0; i < b; i++)
197906943f9SDavid du Colombier 				fprint(2, " %#2.2x", buf[i]);
198906943f9SDavid du Colombier 			fprint(2, "\n");
199906943f9SDavid du Colombier 		}
200906943f9SDavid du Colombier 	}
201906943f9SDavid du Colombier }
202906943f9SDavid du Colombier 
203906943f9SDavid du Colombier 
204906943f9SDavid du Colombier void
usage(void)205906943f9SDavid du Colombier usage(void)
206906943f9SDavid du Colombier {
207*ed868a7cSDavid du Colombier 	fprint(2, "usage: usbaudio [-dpV] [-N nb] [-m mountpoint] [-s srvname] "
208906943f9SDavid du Colombier 		"[-v volume] [dev]\n");
209906943f9SDavid du Colombier 	threadexitsall("usage");
210906943f9SDavid du Colombier }
211906943f9SDavid du Colombier 
212906943f9SDavid du Colombier void
threadmain(int argc,char ** argv)213906943f9SDavid du Colombier threadmain(int argc, char **argv)
214906943f9SDavid du Colombier {
215906943f9SDavid du Colombier 	char *devdir;
216906943f9SDavid du Colombier 	int i;
217906943f9SDavid du Colombier 	long value[8], volume[8];
218906943f9SDavid du Colombier 	Audiocontrol *c;
219906943f9SDavid du Colombier 	char *p;
220906943f9SDavid du Colombier 	extern int attachok;
221906943f9SDavid du Colombier 	Ep *ep;
222906943f9SDavid du Colombier 	int csps[] = { Audiocsp, 0};
223906943f9SDavid du Colombier 
224906943f9SDavid du Colombier 	devdir = nil;
225906943f9SDavid du Colombier 	volume[0] = Undef;
226906943f9SDavid du Colombier 	for(i = 0; i<8; i++)
227906943f9SDavid du Colombier 		value[i] = 0;
228906943f9SDavid du Colombier 	fmtinstall('A', Aconv);
229906943f9SDavid du Colombier 	fmtinstall('U', Ufmt);
230906943f9SDavid du Colombier 	quotefmtinstall();
231906943f9SDavid du Colombier 
232906943f9SDavid du Colombier 	ARGBEGIN{
233*ed868a7cSDavid du Colombier 	case 'N':
234*ed868a7cSDavid du Colombier 		p = EARGF(usage());	/* ignore dev nb */
235*ed868a7cSDavid du Colombier 		break;
236906943f9SDavid du Colombier 	case 'd':
237906943f9SDavid du Colombier 		usbdebug++;
238906943f9SDavid du Colombier 		verbose++;
239906943f9SDavid du Colombier 		break;
240906943f9SDavid du Colombier 	case 'm':
241906943f9SDavid du Colombier 		mntpt = EARGF(usage());
242906943f9SDavid du Colombier 		break;
243906943f9SDavid du Colombier 	case 'p':
244906943f9SDavid du Colombier 		attachok++;
245906943f9SDavid du Colombier 		break;
246906943f9SDavid du Colombier 	case 's':
247906943f9SDavid du Colombier 		srvpost = EARGF(usage());
248906943f9SDavid du Colombier 		break;
249906943f9SDavid du Colombier 	case 'v':
250906943f9SDavid du Colombier 		volume[0] = strtol(EARGF(usage()), &p, 0);
251906943f9SDavid du Colombier 		for(i = 1; i < 8; i++)
252906943f9SDavid du Colombier 			volume[i] = volume[0];
253906943f9SDavid du Colombier 		break;
254906943f9SDavid du Colombier 	case 'V':
255906943f9SDavid du Colombier 		verbose++;
256906943f9SDavid du Colombier 		break;
257906943f9SDavid du Colombier 	default:
258906943f9SDavid du Colombier 		usage();
259906943f9SDavid du Colombier 	}ARGEND
260906943f9SDavid du Colombier 	switch(argc){
261906943f9SDavid du Colombier 	case 0:
262906943f9SDavid du Colombier 		break;
263906943f9SDavid du Colombier 	case 1:
264906943f9SDavid du Colombier 		devdir = argv[0];
265906943f9SDavid du Colombier 		break;
266906943f9SDavid du Colombier 	default:
267906943f9SDavid du Colombier 		usage();
268906943f9SDavid du Colombier 	}
269906943f9SDavid du Colombier 	if(devdir == nil)
270906943f9SDavid du Colombier 		if(finddevs(matchdevcsp, csps, &devdir, 1) < 1){
271906943f9SDavid du Colombier 			fprint(2, "No usb audio\n");
272906943f9SDavid du Colombier 			threadexitsall("usbaudio not found");
273906943f9SDavid du Colombier 		}
274906943f9SDavid du Colombier 	ad = opendev(devdir);
275906943f9SDavid du Colombier 	if(ad == nil)
276906943f9SDavid du Colombier 		sysfatal("opendev: %r");
277906943f9SDavid du Colombier 	if(configdev(ad) < 0)
278906943f9SDavid du Colombier 		sysfatal("configdev: %r");
279906943f9SDavid du Colombier 
280906943f9SDavid du Colombier 	for(i = 0; i < nelem(ad->usb->ddesc); i++)
281906943f9SDavid du Colombier 		if(ad->usb->ddesc[i] != nil)
282906943f9SDavid du Colombier 		switch(ad->usb->ddesc[i]->data.bDescriptorType){
283906943f9SDavid du Colombier 		case AUDIO_INTERFACE:
284906943f9SDavid du Colombier 			audio_interface(ad, ad->usb->ddesc[i]);
285906943f9SDavid du Colombier 			break;
286906943f9SDavid du Colombier 		case AUDIO_ENDPOINT:
287906943f9SDavid du Colombier 			audio_endpoint(ad, ad->usb->ddesc[i]);
288906943f9SDavid du Colombier 			break;
289906943f9SDavid du Colombier 		}
290906943f9SDavid du Colombier 
291906943f9SDavid du Colombier 	controlchan = chancreate(sizeof(char*), 8);
292906943f9SDavid du Colombier 
293906943f9SDavid du Colombier 	for(i = 0; i < nelem(ad->usb->ep); i++)
294906943f9SDavid du Colombier 		if((ep = ad->usb->ep[i]) != nil){
295906943f9SDavid du Colombier 			if(ep->iface->csp == CSP(Claudio, 2, 0) && ep->dir == Eout)
296906943f9SDavid du Colombier 				endpt[0] = ep->id;
297906943f9SDavid du Colombier 			if(ep->iface->csp == CSP(Claudio, 2, 0) && ep->dir == Ein)
298906943f9SDavid du Colombier 				endpt[1] = ep->id;
299906943f9SDavid du Colombier 			if(buttonendpt<0 && Class(ep->iface->csp) == Clhid)
300906943f9SDavid du Colombier 				buttonendpt = ep->id;
301906943f9SDavid du Colombier 		}
302906943f9SDavid du Colombier 	if(endpt[0] != -1){
303906943f9SDavid du Colombier 		if(verbose)
304906943f9SDavid du Colombier 			fprint(2, "usb/audio: playback on ep %d\n", endpt[0]);
305906943f9SDavid du Colombier 		interface[0] = ad->usb->ep[endpt[0]]->iface->id;
306906943f9SDavid du Colombier 	}
307906943f9SDavid du Colombier 	if(endpt[1] != -1){
308906943f9SDavid du Colombier 		if(verbose)
309906943f9SDavid du Colombier 			fprint(2, "usb/audio: record on ep %d\n", endpt[0]);
310906943f9SDavid du Colombier 		interface[1] = ad->usb->ep[endpt[1]]->iface->id;
311906943f9SDavid du Colombier 	}
312906943f9SDavid du Colombier 	if(verbose && buttonendpt >= 0)
313906943f9SDavid du Colombier 		fprint(2, "usb/audio: buttons on ep %d\n", buttonendpt);
314906943f9SDavid du Colombier 
315906943f9SDavid du Colombier 	if(endpt[Play] >= 0){
316906943f9SDavid du Colombier 		if(verbose)
317906943f9SDavid du Colombier 			fprint(2, "Setting default play parameters: %d Hz, %d channels at %d bits\n",
318906943f9SDavid du Colombier 				defaultspeed[Play], 2, 16);
319906943f9SDavid du Colombier 		if(findalt(Play, 2, 16, defaultspeed[Play]) < 0){
320906943f9SDavid du Colombier 			if(findalt(Play, 2, 16, 48000) < 0)
321906943f9SDavid du Colombier 				sysfatal("Can't configure playout for %d or %d Hz", defaultspeed[Play], 48000);
322906943f9SDavid du Colombier 			fprint(2, "Warning, can't configure playout for %d Hz, configuring for %d Hz instead\n",
323906943f9SDavid du Colombier 				defaultspeed[Play], 48000);
324906943f9SDavid du Colombier 			defaultspeed[Play] = 48000;
325906943f9SDavid du Colombier 		}
326906943f9SDavid du Colombier 		value[0] = 2;
327906943f9SDavid du Colombier 		if(setcontrol(Play, "channels", value) == Undef)
328906943f9SDavid du Colombier 			sysfatal("Can't set play channels");
329906943f9SDavid du Colombier 		value[0] = 16;
330906943f9SDavid du Colombier 		if(setcontrol(Play, "resolution", value) == Undef)
331906943f9SDavid du Colombier 			sysfatal("Can't set play resolution");
332906943f9SDavid du Colombier 	}
333906943f9SDavid du Colombier 
334906943f9SDavid du Colombier 	if(endpt[Record] >= 0){
335906943f9SDavid du Colombier 		setrec = 1;
336906943f9SDavid du Colombier 		if(verbose)
337906943f9SDavid du Colombier 			fprint(2, "Setting default record parameters: "
338906943f9SDavid du Colombier 				"%d Hz, %d channels at %d bits\n",
339906943f9SDavid du Colombier 				defaultspeed[Record], 2, 16);
340906943f9SDavid du Colombier 		i = 2;
341906943f9SDavid du Colombier 		while(findalt(Record, i, 16, defaultspeed[Record]) < 0)
342906943f9SDavid du Colombier 			if(i == 2 && controls[Record][Channel_control].max == 1){
343906943f9SDavid du Colombier 				fprint(2, "Warning, can't configure stereo "
344906943f9SDavid du Colombier 					"recording, configuring mono instead\n");
345906943f9SDavid du Colombier 				i = 1;
346906943f9SDavid du Colombier 			}else
347906943f9SDavid du Colombier 				break;
348906943f9SDavid du Colombier 		if(findalt(Record, i, 16, 48000) < 0){
349906943f9SDavid du Colombier 			endpt[Record] = -1;	/* disable recording */
350906943f9SDavid du Colombier 			setrec = 0;
351906943f9SDavid du Colombier 			fprint(2, "Warning, can't configure record for %d Hz or %d Hz\n",
352906943f9SDavid du Colombier 				defaultspeed[Record], 48000);
353906943f9SDavid du Colombier 		}else
354906943f9SDavid du Colombier 			fprint(2, "Warning, can't configure record for %d Hz, "
355906943f9SDavid du Colombier 				"configuring for %d Hz instead\n",
356906943f9SDavid du Colombier 				defaultspeed[Record], 48000);
357906943f9SDavid du Colombier 		defaultspeed[Record] = 48000;
358906943f9SDavid du Colombier 		if(setrec){
359906943f9SDavid du Colombier 			value[0] = i;
360906943f9SDavid du Colombier 			if(setcontrol(Record, "channels", value) == Undef)
361906943f9SDavid du Colombier 				sysfatal("Can't set record channels");
362906943f9SDavid du Colombier 			value[0] = 16;
363906943f9SDavid du Colombier 			if(setcontrol(Record, "resolution", value) == Undef)
364906943f9SDavid du Colombier 				sysfatal("Can't set record resolution");
365906943f9SDavid du Colombier 		}
366906943f9SDavid du Colombier 	}
367906943f9SDavid du Colombier 
368906943f9SDavid du Colombier 	getcontrols();	/* Get the initial value of all controls */
369906943f9SDavid du Colombier 	value[0] = defaultspeed[Play];
370906943f9SDavid du Colombier 	if(endpt[Play] >= 0 && setcontrol(Play, "speed", value) < 0)
371906943f9SDavid du Colombier 		sysfatal("can't set play speed");
372906943f9SDavid du Colombier 	value[0] = defaultspeed[Record];
373906943f9SDavid du Colombier 	if(endpt[Record] >= 0 && setcontrol(Record, "speed", value) < 0)
374906943f9SDavid du Colombier 		fprint(2, "%s: can't set record speed\n", argv0);
375906943f9SDavid du Colombier 	value[0] = 0;
376906943f9SDavid du Colombier 	setcontrol(Play, "mute", value);
377906943f9SDavid du Colombier 
378906943f9SDavid du Colombier 	if(volume[0] != Undef){
379906943f9SDavid du Colombier 		c = &controls[Play][Volume_control];
380906943f9SDavid du Colombier 		if(*p == '%' && c->min != Undef)
381906943f9SDavid du Colombier 			for(i = 0; i < 8; i++)
382906943f9SDavid du Colombier 				volume[i] = (volume[i]*c->max + (100-volume[i])*c->min)/100;
383906943f9SDavid du Colombier 		if(c->settable)
384906943f9SDavid du Colombier 			setcontrol(Play, "volume", volume);
385906943f9SDavid du Colombier 		c = &controls[Record][Volume_control];
386906943f9SDavid du Colombier 		if(c->settable && setrec)
387906943f9SDavid du Colombier 			setcontrol(Record, "volume", volume);
388906943f9SDavid du Colombier 	}
389906943f9SDavid du Colombier 
390906943f9SDavid du Colombier 	if(buttonendpt > 0){
391906943f9SDavid du Colombier 		buttondev = openep(ad, buttonendpt);
392906943f9SDavid du Colombier 		if(buttondev == nil)
393906943f9SDavid du Colombier 			sysfatal("openep: buttons: %r");
394906943f9SDavid du Colombier 		if(opendevdata(buttondev, OREAD) < 0)
395906943f9SDavid du Colombier 			sysfatal("open buttons fd: %r");
396906943f9SDavid du Colombier 		proccreate(buttonproc, nil, STACKSIZE);
397906943f9SDavid du Colombier 	}
398906943f9SDavid du Colombier 	proccreate(controlproc, nil, STACKSIZE);
399906943f9SDavid du Colombier 	proccreate(serve, nil, STACKSIZE);
400906943f9SDavid du Colombier 
401906943f9SDavid du Colombier 	threadexits(nil);
402906943f9SDavid du Colombier }
403