xref: /plan9/sys/src/cmd/usb/audio/audioctl.c (revision 906943f9f6b8411972abb5e3a03ed19f74be7ccc)
1*906943f9SDavid du Colombier #include <u.h>
2*906943f9SDavid du Colombier #include <libc.h>
3*906943f9SDavid du Colombier #include <thread.h>
4*906943f9SDavid du Colombier #include "usb.h"
5*906943f9SDavid du Colombier #include "audio.h"
6*906943f9SDavid du Colombier #include "audioctl.h"
7*906943f9SDavid du Colombier 
8*906943f9SDavid du Colombier int endpt[2] =		{-1, -1};
9*906943f9SDavid du Colombier int interface[2] =	{-1, -1};
10*906943f9SDavid du Colombier int featureid[2] =	{-1, -1};
11*906943f9SDavid du Colombier int selectorid[2] =	{-1, -1};
12*906943f9SDavid du Colombier int mixerid[2] =	{-1, -1};
13*906943f9SDavid du Colombier int curalt[2] =		{-1, -1};
14*906943f9SDavid du Colombier int buttonendpt =	-1;
15*906943f9SDavid du Colombier 
16*906943f9SDavid du Colombier int id;
17*906943f9SDavid du Colombier Dev *ad;
18*906943f9SDavid du Colombier 
19*906943f9SDavid du Colombier Audiocontrol controls[2][Ncontrol] = {
20*906943f9SDavid du Colombier 	{
21*906943f9SDavid du Colombier 	[Speed_control] = {		"speed",	0, {0}, 0,	44100,	Undef},
22*906943f9SDavid du Colombier 	[Mute_control] = {		"mute",		0, {0}, 0,	0,	Undef},
23*906943f9SDavid du Colombier 	[Volume_control] = {		"volume",	0, {0}, 0,	0,	Undef},
24*906943f9SDavid du Colombier 	[Bass_control] = {		"bass",		0, {0}, 0,	0,	Undef},
25*906943f9SDavid du Colombier 	[Mid_control] = {		"mid",		0, {0}, 0,	0,	Undef},
26*906943f9SDavid du Colombier 	[Treble_control] = {		"treble",	0, {0}, 0,	0,	Undef},
27*906943f9SDavid du Colombier 	[Equalizer_control] = {		"equalizer",	0, {0}, 0,	0,	Undef},
28*906943f9SDavid du Colombier 	[Agc_control] = {		"agc",		0, {0}, 0,	0,	Undef},
29*906943f9SDavid du Colombier 	[Delay_control] = {		"delay",	0, {0}, 0,	0,	Undef},
30*906943f9SDavid du Colombier 	[Bassboost_control] = {		"bassboost",	0, {0}, 0,	0,	Undef},
31*906943f9SDavid du Colombier 	[Loudness_control] = {		"loudness",	0, {0}, 0,	0,	Undef},
32*906943f9SDavid du Colombier 	[Channel_control] = {		"channels",	0, {0}, 0,	2,	Undef},
33*906943f9SDavid du Colombier 	[Resolution_control] = {	"resolution",	0, {0}, 0,	16,	Undef},
34*906943f9SDavid du Colombier //	[Selector_control] = {		"selector",	0, {0}, 0,	0,	Undef},
35*906943f9SDavid du Colombier 	}, {
36*906943f9SDavid du Colombier 	[Speed_control] = {		"speed",	0, {0}, 0,	44100,	Undef},
37*906943f9SDavid du Colombier 	[Mute_control] = {		"mute",		0, {0}, 0,	0,	Undef},
38*906943f9SDavid du Colombier 	[Volume_control] = {		"volume",	0, {0}, 0,	0,	Undef},
39*906943f9SDavid du Colombier 	[Bass_control] = {		"bass",		0, {0}, 0,	0,	Undef},
40*906943f9SDavid du Colombier 	[Mid_control] = {		"mid",		0, {0}, 0,	0,	Undef},
41*906943f9SDavid du Colombier 	[Treble_control] = {		"treble",	0, {0}, 0,	0,	Undef},
42*906943f9SDavid du Colombier 	[Equalizer_control] = {		"equalizer",	0, {0}, 0,	0,	Undef},
43*906943f9SDavid du Colombier 	[Agc_control] = {		"agc",		0, {0}, 0,	0,	Undef},
44*906943f9SDavid du Colombier 	[Delay_control] = {		"delay",	0, {0}, 0,	0,	Undef},
45*906943f9SDavid du Colombier 	[Bassboost_control] = {		"bassboost",	0, {0}, 0,	0,	Undef},
46*906943f9SDavid du Colombier 	[Loudness_control] = {		"loudness",	0, {0}, 0,	0,	Undef},
47*906943f9SDavid du Colombier 	[Channel_control] = {		"channels",	0, {0}, 0,	2,	Undef},
48*906943f9SDavid du Colombier 	[Resolution_control] = {	"resolution",	0, {0}, 0,	16,	Undef},
49*906943f9SDavid du Colombier //	[Selector_control] = {		"selector",	0, {0}, 0,	0,	Undef},
50*906943f9SDavid du Colombier 	}
51*906943f9SDavid du Colombier };
52*906943f9SDavid du Colombier 
53*906943f9SDavid du Colombier int
setaudioalt(int rec,Audiocontrol * c,int control)54*906943f9SDavid du Colombier setaudioalt(int rec, Audiocontrol *c, int control)
55*906943f9SDavid du Colombier {
56*906943f9SDavid du Colombier 	dprint(2, "setcontrol %s: Set alt %d\n", c->name, control);
57*906943f9SDavid du Colombier 	curalt[rec] = control;
58*906943f9SDavid du Colombier 	if(usbcmd(ad, Rh2d|Rstd|Riface, Rsetiface, control, interface[rec], nil, 0) < 0){
59*906943f9SDavid du Colombier 		dprint(2, "setcontrol: setupcmd %s failed\n", c->name);
60*906943f9SDavid du Colombier 		return -1;
61*906943f9SDavid du Colombier 	}
62*906943f9SDavid du Colombier 	return control;
63*906943f9SDavid du Colombier }
64*906943f9SDavid du Colombier 
65*906943f9SDavid du Colombier int
findalt(int rec,int nchan,int res,int speed)66*906943f9SDavid du Colombier findalt(int rec, int nchan, int res, int speed)
67*906943f9SDavid du Colombier {
68*906943f9SDavid du Colombier 	Ep *ep;
69*906943f9SDavid du Colombier 	Audioalt *a;
70*906943f9SDavid du Colombier 	Altc *da;
71*906943f9SDavid du Colombier 	int i, j, k, retval;
72*906943f9SDavid du Colombier 
73*906943f9SDavid du Colombier 	retval = -1;
74*906943f9SDavid du Colombier 	controls[rec][Channel_control].min = 1000000;
75*906943f9SDavid du Colombier 	controls[rec][Channel_control].max = 0;
76*906943f9SDavid du Colombier 	controls[rec][Channel_control].step = Undef;
77*906943f9SDavid du Colombier 	controls[rec][Resolution_control].min = 1000000;
78*906943f9SDavid du Colombier 	controls[rec][Resolution_control].max = 0;
79*906943f9SDavid du Colombier 	controls[rec][Resolution_control].step = Undef;
80*906943f9SDavid du Colombier 	for(i = 0; i < nelem(ad->usb->ep); i++){
81*906943f9SDavid du Colombier 		if((ep = ad->usb->ep[i]) == nil)
82*906943f9SDavid du Colombier 			continue;
83*906943f9SDavid du Colombier 		if(ep->iface == nil){
84*906943f9SDavid du Colombier 			fprint(2, "\tno interface\n");
85*906943f9SDavid du Colombier 			return 0;
86*906943f9SDavid du Colombier 		}
87*906943f9SDavid du Colombier 		if(ep->iface->csp != CSP(Claudio, 2, 0))
88*906943f9SDavid du Colombier 			continue;
89*906943f9SDavid du Colombier 		if((rec == Play && (ep->addr &  0x80))
90*906943f9SDavid du Colombier 		|| (rec == Record && (ep->addr &  0x80) == 0))
91*906943f9SDavid du Colombier 			continue;
92*906943f9SDavid du Colombier 		for(j = 0; j < 16; j++){
93*906943f9SDavid du Colombier 			if((da = ep->iface->altc[j]) == nil || (a = da->aux) == nil)
94*906943f9SDavid du Colombier 				continue;
95*906943f9SDavid du Colombier 			if(a->nchan < controls[rec][Channel_control].min)
96*906943f9SDavid du Colombier 				controls[rec][Channel_control].min = a->nchan;
97*906943f9SDavid du Colombier 			if(a->nchan > controls[rec][Channel_control].max)
98*906943f9SDavid du Colombier 				controls[rec][Channel_control].max = a->nchan;
99*906943f9SDavid du Colombier 			if(a->res < controls[rec][Resolution_control].min)
100*906943f9SDavid du Colombier 				controls[rec][Resolution_control].min = a->res;
101*906943f9SDavid du Colombier 			if(a->res > controls[rec][Resolution_control].max)
102*906943f9SDavid du Colombier 				controls[rec][Resolution_control].max = a->res;
103*906943f9SDavid du Colombier 			controls[rec][Channel_control].settable = 1;
104*906943f9SDavid du Colombier 			controls[rec][Channel_control].readable = 1;
105*906943f9SDavid du Colombier 			controls[rec][Resolution_control].settable = 1;
106*906943f9SDavid du Colombier 			controls[rec][Resolution_control].readable = 1;
107*906943f9SDavid du Colombier 			controls[rec][Speed_control].settable = 1;
108*906943f9SDavid du Colombier 			controls[rec][Speed_control].readable = 1;
109*906943f9SDavid du Colombier 			if(a->nchan == nchan && a->res == res){
110*906943f9SDavid du Colombier 				if(speed == Undef)
111*906943f9SDavid du Colombier 					retval = j;
112*906943f9SDavid du Colombier 				else if(a->caps & (has_discfreq|onefreq)){
113*906943f9SDavid du Colombier 					for(k = 0; k < nelem(a->freqs); k++){
114*906943f9SDavid du Colombier 						if(a->freqs[k] == speed){
115*906943f9SDavid du Colombier 							retval = j;
116*906943f9SDavid du Colombier 							break;
117*906943f9SDavid du Colombier 						}
118*906943f9SDavid du Colombier 					}
119*906943f9SDavid du Colombier 				}else{
120*906943f9SDavid du Colombier 					if(speed >= a->minfreq && speed <= a->maxfreq)
121*906943f9SDavid du Colombier 						retval = j;
122*906943f9SDavid du Colombier 				}
123*906943f9SDavid du Colombier 			}
124*906943f9SDavid du Colombier 		}
125*906943f9SDavid du Colombier 	}
126*906943f9SDavid du Colombier 	if(usbdebug && retval < 0)
127*906943f9SDavid du Colombier 		fprint(2, "findalt(%d, %d, %d, %d) failed\n", rec, nchan, res, speed);
128*906943f9SDavid du Colombier 	return retval;
129*906943f9SDavid du Colombier }
130*906943f9SDavid du Colombier 
131*906943f9SDavid du Colombier int
setspeed(int rec,int speed)132*906943f9SDavid du Colombier setspeed(int rec, int speed)
133*906943f9SDavid du Colombier {
134*906943f9SDavid du Colombier 	int ps, n, no, dist, i;
135*906943f9SDavid du Colombier 	Audioalt *a;
136*906943f9SDavid du Colombier 	Altc *da;
137*906943f9SDavid du Colombier 	Ep *ep;
138*906943f9SDavid du Colombier 	uchar buf[3];
139*906943f9SDavid du Colombier 
140*906943f9SDavid du Colombier 	if(rec == Record && !setrec)
141*906943f9SDavid du Colombier 		return Undef;
142*906943f9SDavid du Colombier 	if(curalt[rec] < 0){
143*906943f9SDavid du Colombier 		fprint(2, "Must set channels and resolution before speed\n");
144*906943f9SDavid du Colombier 		return Undef;
145*906943f9SDavid du Colombier 	}
146*906943f9SDavid du Colombier 	if(endpt[rec] < 0)
147*906943f9SDavid du Colombier 		sysfatal("endpt[%s] not set", rec?"Record":"Playback");
148*906943f9SDavid du Colombier 	ep = ad->usb->ep[endpt[rec]];
149*906943f9SDavid du Colombier 	if(ep->iface == nil)
150*906943f9SDavid du Colombier 		sysfatal("no interface");
151*906943f9SDavid du Colombier 	if(curalt[rec] < 0)
152*906943f9SDavid du Colombier 		sysfatal("curalt[%s] not set", rec?"Record":"Playback");
153*906943f9SDavid du Colombier 	da = ep->iface->altc[curalt[rec]];
154*906943f9SDavid du Colombier 	a = da->aux;
155*906943f9SDavid du Colombier 	if(a->caps & onefreq){
156*906943f9SDavid du Colombier 		dprint(2, "setspeed %d: onefreq\n", speed);
157*906943f9SDavid du Colombier 		/* speed not settable, but packet size must still be set */
158*906943f9SDavid du Colombier 		speed = a->freqs[0];
159*906943f9SDavid du Colombier 	}else if(a->caps & has_contfreq){
160*906943f9SDavid du Colombier 		dprint(2, "setspeed %d: contfreq\n", speed);
161*906943f9SDavid du Colombier 		if(speed < a->minfreq)
162*906943f9SDavid du Colombier 			speed = a->minfreq;
163*906943f9SDavid du Colombier 		else if(speed > a->maxfreq)
164*906943f9SDavid du Colombier 			speed = a->maxfreq;
165*906943f9SDavid du Colombier 		dprint(2, "Setting continuously variable %s speed to %d\n",
166*906943f9SDavid du Colombier 				rec?"record":"playback", speed);
167*906943f9SDavid du Colombier 	}else if(a->caps & has_discfreq){
168*906943f9SDavid du Colombier 		dprint(2, "setspeed %d: discfreq\n", speed);
169*906943f9SDavid du Colombier 		dist = 1000000;
170*906943f9SDavid du Colombier 		no = -1;
171*906943f9SDavid du Colombier 		for(i = 0; a->freqs[i] > 0; i++)
172*906943f9SDavid du Colombier 			if(abs(a->freqs[i] - speed) < dist){
173*906943f9SDavid du Colombier 				dist = abs(a->freqs[i] - speed);
174*906943f9SDavid du Colombier 				no = i;
175*906943f9SDavid du Colombier 			}
176*906943f9SDavid du Colombier 		if(no == -1){
177*906943f9SDavid du Colombier 			dprint(2, "no = -1\n");
178*906943f9SDavid du Colombier 			return Undef;
179*906943f9SDavid du Colombier 		}
180*906943f9SDavid du Colombier 		speed = a->freqs[no];
181*906943f9SDavid du Colombier 		dprint(2, "Setting discreetly variable %s speed to %d\n",
182*906943f9SDavid du Colombier 				rec?"record":"playback", speed);
183*906943f9SDavid du Colombier 	}else{
184*906943f9SDavid du Colombier 		dprint(2, "can't happen\n?");
185*906943f9SDavid du Colombier 		return Undef;
186*906943f9SDavid du Colombier 	}
187*906943f9SDavid du Colombier 	if(a->caps & has_setspeed){
188*906943f9SDavid du Colombier 		dprint(2, "Setting %s speed to %d Hz;", rec?"record":"playback", speed);
189*906943f9SDavid du Colombier 		buf[0] = speed;
190*906943f9SDavid du Colombier 		buf[1] = speed >> 8;
191*906943f9SDavid du Colombier 		buf[2] = speed >> 16;
192*906943f9SDavid du Colombier 		n = endpt[rec];
193*906943f9SDavid du Colombier 		if(rec)
194*906943f9SDavid du Colombier 			n |= 0x80;
195*906943f9SDavid du Colombier 		if(usbcmd(ad, Rh2d|Rclass|Rep, Rsetcur, sampling_freq_control<<8, n, buf, 3) < 0){
196*906943f9SDavid du Colombier 			fprint(2, "Error in setupcmd\n");
197*906943f9SDavid du Colombier 			return Undef;
198*906943f9SDavid du Colombier 		}
199*906943f9SDavid du Colombier 		if((n=usbcmd(ad, Rd2h|Rclass|Rep, Rgetcur, sampling_freq_control<<8, n, buf, 3)) < 0){
200*906943f9SDavid du Colombier 			fprint(2, "Error in setupreq\n");
201*906943f9SDavid du Colombier 			return Undef;
202*906943f9SDavid du Colombier 		}
203*906943f9SDavid du Colombier 		if(n != 3)
204*906943f9SDavid du Colombier 			fprint(2, "Error in setupreply: %d\n", n);
205*906943f9SDavid du Colombier 		else{
206*906943f9SDavid du Colombier 			n = buf[0] | buf[1] << 8 | buf[2] << 16;
207*906943f9SDavid du Colombier 			if(buf[2] || n == 0){
208*906943f9SDavid du Colombier 				dprint(2, "Speed out of bounds %d (0x%x)\n", n, n);
209*906943f9SDavid du Colombier 			}else if(n != speed && ad->usb->vid == 0x077d &&
210*906943f9SDavid du Colombier 			    (ad->usb->did == 0x0223 || ad->usb->did == 0x07af)){
211*906943f9SDavid du Colombier 				/* Griffin iMic responds incorrectly to sample rate inquiry */
212*906943f9SDavid du Colombier 				dprint(2, " reported as %d (iMic bug?);", n);
213*906943f9SDavid du Colombier 			}else
214*906943f9SDavid du Colombier 				speed = n;
215*906943f9SDavid du Colombier 		}
216*906943f9SDavid du Colombier 		dprint(2, " speed now %d Hz;", speed);
217*906943f9SDavid du Colombier 	}
218*906943f9SDavid du Colombier 	ps = ((speed * da->interval + 999) / 1000)
219*906943f9SDavid du Colombier 		* controls[rec][Channel_control].value[0]
220*906943f9SDavid du Colombier 		* controls[rec][Resolution_control].value[0]/8;
221*906943f9SDavid du Colombier 	if(ps > ep->maxpkt){
222*906943f9SDavid du Colombier 		fprint(2, "%s: setspeed(rec %d, speed %d): packet size %d > "
223*906943f9SDavid du Colombier 			"maximum packet size %d\n",
224*906943f9SDavid du Colombier 			argv0, rec, speed, ps, ep->maxpkt);
225*906943f9SDavid du Colombier 		return Undef;
226*906943f9SDavid du Colombier 	}
227*906943f9SDavid du Colombier 	dprint(2, "Configuring %s endpoint for %d Hz\n",
228*906943f9SDavid du Colombier 				rec?"record":"playback", speed);
229*906943f9SDavid du Colombier 	epdev[rec] = openep(ad, endpt[rec]);
230*906943f9SDavid du Colombier 	if(epdev[rec] == nil)
231*906943f9SDavid du Colombier 		sysfatal("openep rec %d: %r", rec);
232*906943f9SDavid du Colombier 
233*906943f9SDavid du Colombier 	devctl(epdev[rec], "pollival %d", da->interval);
234*906943f9SDavid du Colombier 	devctl(epdev[rec], "samplesz %ld", controls[rec][Channel_control].value[0] *
235*906943f9SDavid du Colombier 				controls[rec][Resolution_control].value[0]/8);
236*906943f9SDavid du Colombier 	devctl(epdev[rec], "hz %d", speed);
237*906943f9SDavid du Colombier 
238*906943f9SDavid du Colombier 	/* NO: the client uses the endpoint file directly
239*906943f9SDavid du Colombier 	if(opendevdata(epdev[rec], rec ? OREAD : OWRITE) < 0)
240*906943f9SDavid du Colombier 		sysfatal("openep rec %d: %r", rec);
241*906943f9SDavid du Colombier 	*/
242*906943f9SDavid du Colombier 	return speed;
243*906943f9SDavid du Colombier }
244*906943f9SDavid du Colombier 
245*906943f9SDavid du Colombier long
getspeed(int rec,int which)246*906943f9SDavid du Colombier getspeed(int rec, int which)
247*906943f9SDavid du Colombier {
248*906943f9SDavid du Colombier 	int i, n;
249*906943f9SDavid du Colombier 	Audioalt *a;
250*906943f9SDavid du Colombier 	Altc *da;
251*906943f9SDavid du Colombier 	Ep *ep;
252*906943f9SDavid du Colombier 	uchar buf[3];
253*906943f9SDavid du Colombier 	int r;
254*906943f9SDavid du Colombier 
255*906943f9SDavid du Colombier 	if(curalt[rec] < 0){
256*906943f9SDavid du Colombier 		fprint(2, "Must set channels and resolution before getspeed\n");
257*906943f9SDavid du Colombier 		return Undef;
258*906943f9SDavid du Colombier 	}
259*906943f9SDavid du Colombier 	if(endpt[rec] < 0)
260*906943f9SDavid du Colombier 		sysfatal("endpt[%s] not set", rec?"Record":"Playback");
261*906943f9SDavid du Colombier 	dprint(2, "getspeed: endpt[%d] == %d\n", rec, endpt[rec]);
262*906943f9SDavid du Colombier 	ep = ad->usb->ep[endpt[rec]];
263*906943f9SDavid du Colombier 	if(ep->iface == nil)
264*906943f9SDavid du Colombier 		sysfatal("no interface");
265*906943f9SDavid du Colombier 	if(curalt[rec] < 0)
266*906943f9SDavid du Colombier 		sysfatal("curalt[%s] not set", rec?"Record":"Playback");
267*906943f9SDavid du Colombier 	da = ep->iface->altc[curalt[rec]];
268*906943f9SDavid du Colombier 	a = da->aux;
269*906943f9SDavid du Colombier 	if(a->caps & onefreq){
270*906943f9SDavid du Colombier 		dprint(2, "getspeed: onefreq\n");
271*906943f9SDavid du Colombier 		if(which == Rgetres)
272*906943f9SDavid du Colombier 			return Undef;
273*906943f9SDavid du Colombier 		return a->freqs[0];		/* speed not settable */
274*906943f9SDavid du Colombier 	}
275*906943f9SDavid du Colombier 	if(a->caps & has_setspeed){
276*906943f9SDavid du Colombier 		dprint(2, "getspeed: has_setspeed, ask\n");
277*906943f9SDavid du Colombier 		n = endpt[rec];
278*906943f9SDavid du Colombier 		if(rec)
279*906943f9SDavid du Colombier 			n |= 0x80;
280*906943f9SDavid du Colombier 		r = Rd2h|Rclass|Rep;
281*906943f9SDavid du Colombier 		if(usbcmd(ad,r,which,sampling_freq_control<<8, n, buf, 3) < 0)
282*906943f9SDavid du Colombier 			return Undef;
283*906943f9SDavid du Colombier 		if(n == 3){
284*906943f9SDavid du Colombier 			if(buf[2]){
285*906943f9SDavid du Colombier 				dprint(2, "Speed out of bounds\n");
286*906943f9SDavid du Colombier 				if((a->caps & has_discfreq) && (buf[0] | buf[1] << 8) < 8)
287*906943f9SDavid du Colombier 					return a->freqs[buf[0] | buf[1] << 8];
288*906943f9SDavid du Colombier 			}
289*906943f9SDavid du Colombier 			return buf[0] | buf[1] << 8 | buf[2] << 16;
290*906943f9SDavid du Colombier 		}
291*906943f9SDavid du Colombier 		dprint(2, "getspeed: n = %d\n", n);
292*906943f9SDavid du Colombier 	}
293*906943f9SDavid du Colombier 	if(a->caps & has_contfreq){
294*906943f9SDavid du Colombier 		dprint(2, "getspeed: has_contfreq\n");
295*906943f9SDavid du Colombier 		if(which == Rgetcur)
296*906943f9SDavid du Colombier 			return controls[rec][Speed_control].value[0];
297*906943f9SDavid du Colombier 		if(which == Rgetmin)
298*906943f9SDavid du Colombier 			return a->minfreq;
299*906943f9SDavid du Colombier 		if(which == Rgetmax)
300*906943f9SDavid du Colombier 			return a->maxfreq;
301*906943f9SDavid du Colombier 		if(which == Rgetres)
302*906943f9SDavid du Colombier 			return 1;
303*906943f9SDavid du Colombier 	}
304*906943f9SDavid du Colombier 	if(a->caps & has_discfreq){
305*906943f9SDavid du Colombier 		dprint(2, "getspeed: has_discfreq\n");
306*906943f9SDavid du Colombier 		if(which == Rgetcur)
307*906943f9SDavid du Colombier 			return controls[rec][Speed_control].value[0];
308*906943f9SDavid du Colombier 		if(which == Rgetmin)
309*906943f9SDavid du Colombier 			return a->freqs[0];
310*906943f9SDavid du Colombier 		for(i = 0; i < 8 && a->freqs[i] > 0; i++)
311*906943f9SDavid du Colombier 			;
312*906943f9SDavid du Colombier 		if(which == Rgetmax)
313*906943f9SDavid du Colombier 			return a->freqs[i-1];
314*906943f9SDavid du Colombier 		if(which == Rgetres)
315*906943f9SDavid du Colombier 			return Undef;
316*906943f9SDavid du Colombier 	}
317*906943f9SDavid du Colombier 	dprint(2, "can't happen\n?");
318*906943f9SDavid du Colombier 	return Undef;
319*906943f9SDavid du Colombier }
320*906943f9SDavid du Colombier 
321*906943f9SDavid du Colombier int
setcontrol(int rec,char * name,long * value)322*906943f9SDavid du Colombier setcontrol(int rec, char *name, long *value)
323*906943f9SDavid du Colombier {
324*906943f9SDavid du Colombier 	int i, ctl, m;
325*906943f9SDavid du Colombier 	byte buf[3];
326*906943f9SDavid du Colombier 	int type, req, control, index, count;
327*906943f9SDavid du Colombier 	Audiocontrol *c;
328*906943f9SDavid du Colombier 
329*906943f9SDavid du Colombier 	c = nil;
330*906943f9SDavid du Colombier 	for(ctl = 0; ctl < Ncontrol; ctl++){
331*906943f9SDavid du Colombier 		c = &controls[rec][ctl];
332*906943f9SDavid du Colombier 		if(strcmp(name, c->name) == 0)
333*906943f9SDavid du Colombier 			break;
334*906943f9SDavid du Colombier 	}
335*906943f9SDavid du Colombier 	if(ctl == Ncontrol){
336*906943f9SDavid du Colombier 		dprint(2, "setcontrol: control not found\n");
337*906943f9SDavid du Colombier 		return -1;
338*906943f9SDavid du Colombier 	}
339*906943f9SDavid du Colombier 	if(c->settable == 0){
340*906943f9SDavid du Colombier 		dprint(2, "setcontrol: control %d.%d not settable\n", rec, ctl);
341*906943f9SDavid du Colombier 		if(c->chans){
342*906943f9SDavid du Colombier 			for(i = 0; i < 8; i++)
343*906943f9SDavid du Colombier 				if((c->chans & 1 << i) && c->value[i] != value[i])
344*906943f9SDavid du Colombier 					return -1;
345*906943f9SDavid du Colombier 			return 0;
346*906943f9SDavid du Colombier 		}
347*906943f9SDavid du Colombier 		if(c->value[0] != value[0])
348*906943f9SDavid du Colombier 			return -1;
349*906943f9SDavid du Colombier 		return 0;
350*906943f9SDavid du Colombier 	}
351*906943f9SDavid du Colombier 	if(c->chans){
352*906943f9SDavid du Colombier 		value[0] = 0;	// set to average
353*906943f9SDavid du Colombier 		m = 0;
354*906943f9SDavid du Colombier 		for(i = 1; i < 8; i++)
355*906943f9SDavid du Colombier 			if(c->chans & 1 << i){
356*906943f9SDavid du Colombier 				if(c->min != Undef && value[i] < c->min)
357*906943f9SDavid du Colombier 					value[i] = c->min;
358*906943f9SDavid du Colombier 				if(c->max != Undef && value[i] > c->max)
359*906943f9SDavid du Colombier 					value[i] = c->max;
360*906943f9SDavid du Colombier 				value[0] += value[i];
361*906943f9SDavid du Colombier 				m++;
362*906943f9SDavid du Colombier 			}else
363*906943f9SDavid du Colombier 				value[i] = Undef;
364*906943f9SDavid du Colombier 		if(m) value[0] /= m;
365*906943f9SDavid du Colombier 	}else{
366*906943f9SDavid du Colombier 		if(c->min != Undef && value[0] < c->min)
367*906943f9SDavid du Colombier 			value[0] = c->min;
368*906943f9SDavid du Colombier 		if(c->max != Undef && value[0] > c->max)
369*906943f9SDavid du Colombier 			value[0] = c->max;
370*906943f9SDavid du Colombier 	}
371*906943f9SDavid du Colombier 	req = Rsetcur;
372*906943f9SDavid du Colombier 	count = 1;
373*906943f9SDavid du Colombier 	switch(ctl){
374*906943f9SDavid du Colombier 	default:
375*906943f9SDavid du Colombier 		dprint(2, "setcontrol: can't happen\n");
376*906943f9SDavid du Colombier 		return -1;
377*906943f9SDavid du Colombier 	case Speed_control:
378*906943f9SDavid du Colombier 		if((rec != Record || setrec) && (value[0] = setspeed(rec, value[0])) < 0)
379*906943f9SDavid du Colombier 			return -1;
380*906943f9SDavid du Colombier 		c->value[0] = value[0];
381*906943f9SDavid du Colombier 		return 0;
382*906943f9SDavid du Colombier 	case Equalizer_control:
383*906943f9SDavid du Colombier 		/* not implemented */
384*906943f9SDavid du Colombier 		return -1;
385*906943f9SDavid du Colombier 	case Resolution_control:
386*906943f9SDavid du Colombier 		control = findalt(rec, controls[rec][Channel_control].value[0], value[0], defaultspeed[rec]);
387*906943f9SDavid du Colombier 		if(control < 0 || setaudioalt(rec, c, control) < 0){
388*906943f9SDavid du Colombier 			dprint(2, "setcontrol: can't find setting for %s\n", c->name);
389*906943f9SDavid du Colombier 			return -1;
390*906943f9SDavid du Colombier 		}
391*906943f9SDavid du Colombier 		c->value[0] = value[0];
392*906943f9SDavid du Colombier 		controls[rec][Speed_control].value[0] = defaultspeed[rec];
393*906943f9SDavid du Colombier 		return 0;
394*906943f9SDavid du Colombier 	case Volume_control:
395*906943f9SDavid du Colombier 	case Delay_control:
396*906943f9SDavid du Colombier 		count = 2;
397*906943f9SDavid du Colombier 		/* fall through */
398*906943f9SDavid du Colombier 	case Mute_control:
399*906943f9SDavid du Colombier 	case Bass_control:
400*906943f9SDavid du Colombier 	case Mid_control:
401*906943f9SDavid du Colombier 	case Treble_control:
402*906943f9SDavid du Colombier 	case Agc_control:
403*906943f9SDavid du Colombier 	case Bassboost_control:
404*906943f9SDavid du Colombier 	case Loudness_control:
405*906943f9SDavid du Colombier 		type = Rh2d|Rclass|Riface;
406*906943f9SDavid du Colombier 		control = ctl<<8;
407*906943f9SDavid du Colombier 		index = featureid[rec]<<8;
408*906943f9SDavid du Colombier 		break;
409*906943f9SDavid du Colombier 	case Selector_control:
410*906943f9SDavid du Colombier 		type = Rh2d|Rclass|Riface;
411*906943f9SDavid du Colombier 		control = 0;
412*906943f9SDavid du Colombier 		index = selectorid[rec]<<8;
413*906943f9SDavid du Colombier 		break;
414*906943f9SDavid du Colombier 	case Channel_control:
415*906943f9SDavid du Colombier 		control = findalt(rec, value[0], controls[rec][Resolution_control].value[0], defaultspeed[rec]);
416*906943f9SDavid du Colombier 		if(control < 0 || setaudioalt(rec, c, control) < 0){
417*906943f9SDavid du Colombier 			dprint(2, "setcontrol: can't find setting for %s\n", c->name);
418*906943f9SDavid du Colombier 			return -1;
419*906943f9SDavid du Colombier 		}
420*906943f9SDavid du Colombier 		c->value[0] = value[0];
421*906943f9SDavid du Colombier 		controls[rec][Speed_control].value[0] = defaultspeed[rec];
422*906943f9SDavid du Colombier 		return 0;
423*906943f9SDavid du Colombier 	}
424*906943f9SDavid du Colombier 	if(c->chans){
425*906943f9SDavid du Colombier 		for(i = 1; i < 8; i++)
426*906943f9SDavid du Colombier 			if(c->chans & 1 << i){
427*906943f9SDavid du Colombier 				switch(count){
428*906943f9SDavid du Colombier 				case 2:
429*906943f9SDavid du Colombier 					buf[1] = value[i] >> 8;
430*906943f9SDavid du Colombier 				case 1:
431*906943f9SDavid du Colombier 					buf[0] = value[i];
432*906943f9SDavid du Colombier 				}
433*906943f9SDavid du Colombier 				if(usbcmd(ad, type, req, control | i, index, buf, count) < 0){
434*906943f9SDavid du Colombier 					dprint(2, "setcontrol: setupcmd %s failed\n",
435*906943f9SDavid du Colombier 						controls[rec][ctl].name);
436*906943f9SDavid du Colombier 					return -1;
437*906943f9SDavid du Colombier 				}
438*906943f9SDavid du Colombier 				c->value[i] = value[i];
439*906943f9SDavid du Colombier 			}
440*906943f9SDavid du Colombier 	}else{
441*906943f9SDavid du Colombier 		switch(count){
442*906943f9SDavid du Colombier 		case 2:
443*906943f9SDavid du Colombier 			buf[1] = value[0] >> 8;
444*906943f9SDavid du Colombier 		case 1:
445*906943f9SDavid du Colombier 			buf[0] = value[0];
446*906943f9SDavid du Colombier 		}
447*906943f9SDavid du Colombier 		if(usbcmd(ad, type, req, control, index, buf, count) < 0){
448*906943f9SDavid du Colombier 			dprint(2, "setcontrol: setupcmd %s failed\n", c->name);
449*906943f9SDavid du Colombier 			return -1;
450*906943f9SDavid du Colombier 		}
451*906943f9SDavid du Colombier 	}
452*906943f9SDavid du Colombier 	c->value[0] = value[0];
453*906943f9SDavid du Colombier 	return 0;
454*906943f9SDavid du Colombier }
455*906943f9SDavid du Colombier 
456*906943f9SDavid du Colombier int
getspecialcontrol(int rec,int ctl,int req,long * value)457*906943f9SDavid du Colombier getspecialcontrol(int rec, int ctl, int req, long *value)
458*906943f9SDavid du Colombier {
459*906943f9SDavid du Colombier 	byte buf[3];
460*906943f9SDavid du Colombier 	int m, n, i;
461*906943f9SDavid du Colombier 	int type, control, index, count, signedbyte;
462*906943f9SDavid du Colombier 	short svalue;
463*906943f9SDavid du Colombier 
464*906943f9SDavid du Colombier 	count = 1;
465*906943f9SDavid du Colombier 	signedbyte = 0;
466*906943f9SDavid du Colombier 	switch(ctl){
467*906943f9SDavid du Colombier 	default:
468*906943f9SDavid du Colombier 		return Undef;
469*906943f9SDavid du Colombier 	case Speed_control:
470*906943f9SDavid du Colombier 		value[0] =  getspeed(rec, req);
471*906943f9SDavid du Colombier 		return 0;
472*906943f9SDavid du Colombier 	case Channel_control:
473*906943f9SDavid du Colombier 	case Resolution_control:
474*906943f9SDavid du Colombier 		if(req == Rgetmin)
475*906943f9SDavid du Colombier 			value[0] = controls[rec][ctl].min;
476*906943f9SDavid du Colombier 		if(req == Rgetmax)
477*906943f9SDavid du Colombier 			value[0] = controls[rec][ctl].max;
478*906943f9SDavid du Colombier 		if(req == Rgetres)
479*906943f9SDavid du Colombier 			value[0] = controls[rec][ctl].step;
480*906943f9SDavid du Colombier 		if(req == Rgetcur)
481*906943f9SDavid du Colombier 			value[0] = controls[rec][ctl].value[0];
482*906943f9SDavid du Colombier 		return 0;
483*906943f9SDavid du Colombier 	case Volume_control:
484*906943f9SDavid du Colombier 	case Delay_control:
485*906943f9SDavid du Colombier 		count = 2;
486*906943f9SDavid du Colombier 		/* fall through */
487*906943f9SDavid du Colombier 	case Bass_control:
488*906943f9SDavid du Colombier 	case Mid_control:
489*906943f9SDavid du Colombier 	case Treble_control:
490*906943f9SDavid du Colombier 	case Equalizer_control:
491*906943f9SDavid du Colombier 		signedbyte = 1;
492*906943f9SDavid du Colombier 		type = Rd2h|Rclass|Riface;
493*906943f9SDavid du Colombier 		control = ctl<<8;
494*906943f9SDavid du Colombier 		index = featureid[rec]<<8;
495*906943f9SDavid du Colombier 		break;
496*906943f9SDavid du Colombier 	case Selector_control:
497*906943f9SDavid du Colombier 		type = Rd2h|Rclass|Riface;
498*906943f9SDavid du Colombier 		control = 0;
499*906943f9SDavid du Colombier 		index = selectorid[rec]<<8;
500*906943f9SDavid du Colombier 		break;
501*906943f9SDavid du Colombier 	case Mute_control:
502*906943f9SDavid du Colombier 	case Agc_control:
503*906943f9SDavid du Colombier 	case Bassboost_control:
504*906943f9SDavid du Colombier 	case Loudness_control:
505*906943f9SDavid du Colombier 		if(req != Rgetcur)
506*906943f9SDavid du Colombier 			return Undef;
507*906943f9SDavid du Colombier 		type = Rd2h|Rclass|Riface;
508*906943f9SDavid du Colombier 		control = ctl<<8;
509*906943f9SDavid du Colombier 		index = featureid[rec]<<8;
510*906943f9SDavid du Colombier 		break;
511*906943f9SDavid du Colombier 	}
512*906943f9SDavid du Colombier 	if(controls[rec][ctl].chans){
513*906943f9SDavid du Colombier 		m = 0;
514*906943f9SDavid du Colombier 		value[0] = 0; // set to average
515*906943f9SDavid du Colombier 		for(i = 1; i < 8; i++){
516*906943f9SDavid du Colombier 			value[i] = Undef;
517*906943f9SDavid du Colombier 			if(controls[rec][ctl].chans & 1 << i){
518*906943f9SDavid du Colombier 				n=usbcmd(ad, type,req, control|i,index,buf,count);
519*906943f9SDavid du Colombier 				if(n < 0)
520*906943f9SDavid du Colombier 					return Undef;
521*906943f9SDavid du Colombier 				if(n != count)
522*906943f9SDavid du Colombier 					return -1;
523*906943f9SDavid du Colombier 				switch (count){
524*906943f9SDavid du Colombier 				case 2:
525*906943f9SDavid du Colombier 					svalue = buf[1] << 8 | buf[0];
526*906943f9SDavid du Colombier 					if(req == Rgetcur){
527*906943f9SDavid du Colombier 						value[i] = svalue;
528*906943f9SDavid du Colombier 						value[0] += svalue;
529*906943f9SDavid du Colombier 						m++;
530*906943f9SDavid du Colombier 					}else
531*906943f9SDavid du Colombier 						value[0] = svalue;
532*906943f9SDavid du Colombier 					break;
533*906943f9SDavid du Colombier 				case 1:
534*906943f9SDavid du Colombier 					svalue = buf[0];
535*906943f9SDavid du Colombier 					if(signedbyte && (svalue&0x80))
536*906943f9SDavid du Colombier 						svalue |= 0xFF00;
537*906943f9SDavid du Colombier 					if(req == Rgetcur){
538*906943f9SDavid du Colombier 						value[i] = svalue;
539*906943f9SDavid du Colombier 						value[0] += svalue;
540*906943f9SDavid du Colombier 						m++;
541*906943f9SDavid du Colombier 					}else
542*906943f9SDavid du Colombier 						value[0] = svalue;
543*906943f9SDavid du Colombier 				}
544*906943f9SDavid du Colombier 			}
545*906943f9SDavid du Colombier 		}
546*906943f9SDavid du Colombier 		if(m) value[0] /= m;
547*906943f9SDavid du Colombier 		return 0;
548*906943f9SDavid du Colombier 	}
549*906943f9SDavid du Colombier 	value[0] = Undef;
550*906943f9SDavid du Colombier 	if(usbcmd(ad, type, req, control, index, buf, count) != count)
551*906943f9SDavid du Colombier 		return -1;
552*906943f9SDavid du Colombier 	switch (count){
553*906943f9SDavid du Colombier 	case 2:
554*906943f9SDavid du Colombier 		svalue = buf[1] << 8 | buf[0];
555*906943f9SDavid du Colombier 		value[0] = svalue;
556*906943f9SDavid du Colombier 		break;
557*906943f9SDavid du Colombier 	case 1:
558*906943f9SDavid du Colombier 		svalue = buf[0];
559*906943f9SDavid du Colombier 		if(signedbyte && (svalue&0x80))
560*906943f9SDavid du Colombier 			svalue |= 0xFF00;
561*906943f9SDavid du Colombier 		value[0] = svalue;
562*906943f9SDavid du Colombier 	}
563*906943f9SDavid du Colombier 	return 0;
564*906943f9SDavid du Colombier }
565*906943f9SDavid du Colombier 
566*906943f9SDavid du Colombier int
getcontrol(int rec,char * name,long * value)567*906943f9SDavid du Colombier getcontrol(int rec, char *name, long *value)
568*906943f9SDavid du Colombier {
569*906943f9SDavid du Colombier 	int i;
570*906943f9SDavid du Colombier 
571*906943f9SDavid du Colombier 	for(i = 0; i < Ncontrol; i++){
572*906943f9SDavid du Colombier 		if(strcmp(name, controls[rec][i].name) == 0)
573*906943f9SDavid du Colombier 			break;
574*906943f9SDavid du Colombier 	}
575*906943f9SDavid du Colombier 	if(i == Ncontrol)
576*906943f9SDavid du Colombier 		return -1;
577*906943f9SDavid du Colombier 	if(controls[rec][i].readable == 0)
578*906943f9SDavid du Colombier 		return -1;
579*906943f9SDavid du Colombier 	if(getspecialcontrol(rec, i, Rgetcur, value) < 0)
580*906943f9SDavid du Colombier 		return -1;
581*906943f9SDavid du Colombier 	memmove(controls[rec][i].value, value, sizeof controls[rec][i].value);
582*906943f9SDavid du Colombier 	return 0;
583*906943f9SDavid du Colombier }
584*906943f9SDavid du Colombier 
585*906943f9SDavid du Colombier void
getcontrols(void)586*906943f9SDavid du Colombier getcontrols(void)
587*906943f9SDavid du Colombier {
588*906943f9SDavid du Colombier 	int rec, ctl, i;
589*906943f9SDavid du Colombier 	Audiocontrol *c;
590*906943f9SDavid du Colombier 	long v[8];
591*906943f9SDavid du Colombier 
592*906943f9SDavid du Colombier 	for(rec = 0; rec < 2; rec++){
593*906943f9SDavid du Colombier 		if(rec == Record && !setrec)
594*906943f9SDavid du Colombier 			continue;
595*906943f9SDavid du Colombier 		for(ctl = 0; ctl < Ncontrol; ctl++){
596*906943f9SDavid du Colombier 			c = &controls[rec][ctl];
597*906943f9SDavid du Colombier 			if(c->readable){
598*906943f9SDavid du Colombier 				if(verbose)
599*906943f9SDavid du Colombier 					fprint(2, "%s %s control",
600*906943f9SDavid du Colombier 						rec?"Record":"Playback", controls[rec][ctl].name);
601*906943f9SDavid du Colombier 				c->min = (getspecialcontrol(rec, ctl, Rgetmin, v) < 0) ? Undef : v[0];
602*906943f9SDavid du Colombier 				if(verbose && c->min != Undef)
603*906943f9SDavid du Colombier 					fprint(2, ", min %ld", c->min);
604*906943f9SDavid du Colombier 				c->max = (getspecialcontrol(rec, ctl, Rgetmax, v) < 0) ? Undef : v[0];
605*906943f9SDavid du Colombier 				if(verbose && c->max != Undef)
606*906943f9SDavid du Colombier 					fprint(2, ", max %ld", c->max);
607*906943f9SDavid du Colombier 				c->step = (getspecialcontrol(rec, ctl, Rgetres, v) < 0) ? Undef : v[0];
608*906943f9SDavid du Colombier 				if(verbose && c->step != Undef)
609*906943f9SDavid du Colombier 					fprint(2, ", step %ld", c->step);
610*906943f9SDavid du Colombier 				if(getspecialcontrol(rec, ctl, Rgetcur, c->value) == 0){
611*906943f9SDavid du Colombier 					if(verbose){
612*906943f9SDavid du Colombier 						if(c->chans){
613*906943f9SDavid du Colombier 							fprint(2, ", values");
614*906943f9SDavid du Colombier 							for(i = 1; i < 8; i++)
615*906943f9SDavid du Colombier 								if(c->chans & 1 << i)
616*906943f9SDavid du Colombier 									fprint(2, "[%d] %ld  ", i, c->value[i]);
617*906943f9SDavid du Colombier 						}else
618*906943f9SDavid du Colombier 							fprint(2, ", value %ld", c->value[0]);
619*906943f9SDavid du Colombier 					}
620*906943f9SDavid du Colombier 				}
621*906943f9SDavid du Colombier 				if(verbose)
622*906943f9SDavid du Colombier 					fprint(2, "\n");
623*906943f9SDavid du Colombier 			}else{
624*906943f9SDavid du Colombier 				c->min = Undef;
625*906943f9SDavid du Colombier 				c->max = Undef;
626*906943f9SDavid du Colombier 				c->step = Undef;
627*906943f9SDavid du Colombier 				c->value[0] = Undef;
628*906943f9SDavid du Colombier 				dprint(2, "%s %s control not settable\n",
629*906943f9SDavid du Colombier 					rec?"Playback":"Record", controls[rec][ctl].name);
630*906943f9SDavid du Colombier 			}
631*906943f9SDavid du Colombier 		}
632*906943f9SDavid du Colombier 	}
633*906943f9SDavid du Colombier }
634*906943f9SDavid du Colombier 
635*906943f9SDavid du Colombier int
ctlparse(char * s,Audiocontrol * c,long * v)636*906943f9SDavid du Colombier ctlparse(char *s, Audiocontrol *c, long *v)
637*906943f9SDavid du Colombier {
638*906943f9SDavid du Colombier 	int i, j, nf, m;
639*906943f9SDavid du Colombier 	char *vals[9];
640*906943f9SDavid du Colombier 	char *p;
641*906943f9SDavid du Colombier 	long val;
642*906943f9SDavid du Colombier 
643*906943f9SDavid du Colombier 	nf = tokenize(s, vals, nelem(vals));
644*906943f9SDavid du Colombier 	if(nf <= 0)
645*906943f9SDavid du Colombier 		return -1;
646*906943f9SDavid du Colombier 	if(c->chans){
647*906943f9SDavid du Colombier 		j = 0;
648*906943f9SDavid du Colombier 		m = 0;
649*906943f9SDavid du Colombier 		SET(val);
650*906943f9SDavid du Colombier 		v[0] = 0;	// will compute average of v[i]
651*906943f9SDavid du Colombier 		for(i = 1; i < 8; i++)
652*906943f9SDavid du Colombier 			if(c->chans & 1 << i){
653*906943f9SDavid du Colombier 				if(j < nf){
654*906943f9SDavid du Colombier 					val = strtol(vals[j], &p, 0);
655*906943f9SDavid du Colombier 					if(val == 0 && *p != '\0' && *p != '%')
656*906943f9SDavid du Colombier 						return -1;
657*906943f9SDavid du Colombier 					if(*p == '%' && c->min != Undef)
658*906943f9SDavid du Colombier 						val = (val*c->max + (100-val)*c->min)/100;
659*906943f9SDavid du Colombier 					j++;
660*906943f9SDavid du Colombier 				}
661*906943f9SDavid du Colombier 				v[i] = val;
662*906943f9SDavid du Colombier 				v[0] += val;
663*906943f9SDavid du Colombier 				m++;
664*906943f9SDavid du Colombier 			}else
665*906943f9SDavid du Colombier 				v[i] = Undef;
666*906943f9SDavid du Colombier 		if(m) v[0] /= m;
667*906943f9SDavid du Colombier 	}else{
668*906943f9SDavid du Colombier 		val = strtol(vals[0], &p, 0);
669*906943f9SDavid du Colombier 		if(*p == '%' && c->min != Undef)
670*906943f9SDavid du Colombier 			val = (val*c->max + (100-val)*c->min)/100;
671*906943f9SDavid du Colombier 		v[0] = val;
672*906943f9SDavid du Colombier 	}
673*906943f9SDavid du Colombier 	return 0;
674*906943f9SDavid du Colombier }
675*906943f9SDavid du Colombier 
676*906943f9SDavid du Colombier int
Aconv(Fmt * fp)677*906943f9SDavid du Colombier Aconv(Fmt *fp)
678*906943f9SDavid du Colombier {
679*906943f9SDavid du Colombier 	char str[256];
680*906943f9SDavid du Colombier 	Audiocontrol *c;
681*906943f9SDavid du Colombier 	int fst, i;
682*906943f9SDavid du Colombier 	char *p;
683*906943f9SDavid du Colombier 
684*906943f9SDavid du Colombier 	c = va_arg(fp->args, Audiocontrol*);
685*906943f9SDavid du Colombier 	p = str;
686*906943f9SDavid du Colombier 	if(c->chans){
687*906943f9SDavid du Colombier 		fst = 1;
688*906943f9SDavid du Colombier 		for(i = 1; i < 8; i++)
689*906943f9SDavid du Colombier 			if(c->chans & 1 << i){
690*906943f9SDavid du Colombier 				p = seprint(p, str+sizeof str, "%s%ld", fst?"'":" ", c->value[i]);
691*906943f9SDavid du Colombier 				fst = 0;
692*906943f9SDavid du Colombier 			}
693*906943f9SDavid du Colombier 		seprint(p, str+sizeof str, "'");
694*906943f9SDavid du Colombier 	}else
695*906943f9SDavid du Colombier 		seprint(p, str+sizeof str, "%ld", c->value[0]);
696*906943f9SDavid du Colombier 	return fmtstrcpy(fp, str);
697*906943f9SDavid du Colombier }
698