]> git.lizzy.rs Git - plan9front.git/blob - sys/src/9/port/devaudio.c
merge
[plan9front.git] / sys / src / 9 / port / devaudio.c
1 #include        "u.h"
2 #include        "../port/lib.h"
3 #include        "mem.h"
4 #include        "dat.h"
5 #include        "fns.h"
6 #include        "io.h"
7 #include        "../port/error.h"
8 #include        "../port/audioif.h"
9
10 typedef struct Audioprobe Audioprobe;
11 typedef struct Audiochan Audiochan;
12
13 struct Audioprobe
14 {
15         char *name;
16         int (*probe)(Audio*);
17 };
18
19 struct Audiochan
20 {
21         QLock;
22
23         Chan *owner;
24         Audio *adev;
25
26         char *data;
27         char buf[1024+1];
28 };
29
30 enum {
31         Qdir = 0,
32         Qaudio,
33         Qaudioctl,
34         Qaudiostat,
35         Qvolume,
36 };
37
38 static Dirtab audiodir[] = {
39         ".",    {Qdir, 0, QTDIR},       0,      DMDIR|0555,
40         "audio",        {Qaudio},       0,      0666,
41         "audioctl",     {Qaudioctl},    0,      0222,
42         "audiostat",    {Qaudiostat},   0,      0444,
43         "volume",       {Qvolume},      0,      0666,
44 };
45
46
47 static int naudioprobes;
48 static Audioprobe audioprobes[16];
49 static Audio *audiodevs;
50
51 static char Evolume[] = "illegal volume specifier";
52 static char Ebusy[] = "device is busy";
53
54 void
55 addaudiocard(char *name, int (*probefn)(Audio *))
56 {
57         Audioprobe *probe;
58
59         if(naudioprobes >= nelem(audioprobes))
60                 return;
61
62         probe = &audioprobes[naudioprobes++];
63         probe->name = name;
64         probe->probe = probefn;
65 }
66
67 static void
68 audioreset(void)
69 {
70         int i, ctlrno = 0;
71         Audio **pp;
72         Audioprobe *probe;
73
74         pp = &audiodevs;
75         *pp = malloc(sizeof(Audio));
76
77         for(i=0; i<naudioprobes; i++){
78                 probe = &audioprobes[i];
79
80                 for(;;){
81                         if(*pp == nil){
82                                 print("audio: no memory\n");
83                                 break;
84                         }
85                         memset(*pp, 0, sizeof(Audio));
86                         (*pp)->ctlrno = ctlrno;
87                         (*pp)->name = probe->name;
88                         if(probe->probe(*pp))
89                                 break;
90
91                         ctlrno++;
92                         pp = &(*pp)->next;
93                         *pp = malloc(sizeof(Audio));
94                 }
95         }
96
97         free(*pp);
98         *pp = nil;
99 }
100
101 static Audiochan*
102 audioclone(Chan *c, Audio *adev)
103 {
104         Audiochan *ac;
105
106         ac = malloc(sizeof(Audiochan));
107         if(ac == nil){
108                 cclose(c);
109                 return nil;
110         }
111
112         c->aux = ac;
113         ac->owner = c;
114         ac->adev = adev;
115         ac->data = nil;
116
117         return ac;
118 }
119
120 static Chan*
121 audioattach(char *spec)
122 {
123         static int attached = 0;
124         Audiochan *ac;
125         Audio *adev;
126         Chan *c;
127         int i;
128
129         if(spec != nil && *spec != '\0')
130                 i = strtol(spec, 0, 10);
131         else
132                 i = 0;
133         for(adev = audiodevs; adev; adev = adev->next)
134                 if(adev->ctlrno == i)
135                         break;
136         if(adev == nil)
137                 error(Enodev);
138
139         c = devattach('A', spec);
140         c->qid.path = Qdir;
141
142         if((ac = audioclone(c, adev)) == nil)
143                 error(Enomem);
144
145         i = 1<<adev->ctlrno;
146         if((attached & i) == 0){
147                 static char *settings[] = {
148                         "speed 44100",
149                         "delay 1764",   /* 40 ms */
150                         "master 100",
151                         "audio 100",
152                         "head 100",
153                         "recgain 0",
154                 };
155
156                 attached |= i;
157                 for(i=0; i<nelem(settings) && adev->volwrite; i++){
158                         strcpy(ac->buf, settings[i]);
159                         if(!waserror()){
160                                 adev->volwrite(adev, ac->buf, strlen(ac->buf), 0);
161                                 poperror();
162                         }
163                 }
164         }
165
166         return c;
167 }
168
169 static Chan*
170 audioopen(Chan *c, int omode)
171 {
172         Audiochan *ac;
173         Audio *adev;
174         int mode;
175
176         ac = c->aux;
177         adev = ac->adev;
178         if(c->qid.path == Qaudio){
179                 mode = openmode(omode);
180                 if(mode == OWRITE || mode == ORDWR)
181                         if(incref(&adev->audioopenw) != 1){
182                                 decref(&adev->audioopenw);
183                                 error(Ebusy);
184                         }
185                 if(mode == OREAD || mode == ORDWR)
186                         if(incref(&adev->audioopenr) != 1){
187                                 decref(&adev->audioopenr);
188                                 if(mode == ORDWR)
189                                         decref(&adev->audioopenw);
190                                 error(Ebusy);
191                         }
192         }
193         return devopen(c, omode, audiodir, nelem(audiodir), devgen);
194 }
195
196 static long
197 audioread(Chan *c, void *a, long n, vlong off)
198 {
199         Audiochan *ac;
200         Audio *adev;
201         long (*fn)(Audio *, void *, long, vlong);
202
203         ac = c->aux;
204         adev = ac->adev;
205
206         fn = nil;
207         switch((ulong)c->qid.path){
208         case Qdir:
209                 audiodir[Qaudio].length = adev->buffered ? adev->buffered(adev) : 0;
210                 return devdirread(c, a, n, audiodir, nelem(audiodir), devgen);
211         case Qaudio:
212                 fn = adev->read;
213                 break;
214         case Qaudiostat:
215                 fn = adev->status;
216                 break;
217         case Qvolume:
218                 fn = adev->volread;
219                 break;
220         }
221         if(fn == nil)
222                 error(Egreg);
223
224         eqlock(ac);
225         if(waserror()){
226                 qunlock(ac);
227                 nexterror();
228         }
229         switch((ulong)c->qid.path){
230         case Qaudiostat:
231         case Qvolume:
232                 /* generate the text on first read */
233                 if(ac->data == nil || off == 0){
234                         long l;
235
236                         ac->data = nil;
237                         l = fn(adev, ac->buf, sizeof(ac->buf)-1, 0);
238                         if(l < 0)
239                                 l = 0;
240                         ac->buf[l] = 0;
241                         ac->data = ac->buf;
242                 }
243                 /* then serve all requests from buffer */
244                 n = readstr(off, a, n, ac->data);
245                 break;
246
247         default:
248                 n = fn(adev, a, n, off);
249         }
250         qunlock(ac);
251         poperror();
252         return n;
253 }
254
255 static long
256 audiowrite(Chan *c, void *a, long n, vlong off)
257 {
258         Audiochan *ac;
259         Audio *adev;
260         long (*fn)(Audio *, void *, long, vlong);
261
262         ac = c->aux;
263         adev = ac->adev;
264
265         fn = nil;
266         switch((ulong)c->qid.path){
267         case Qaudio:
268                 fn = adev->write;
269                 break;
270         case Qaudioctl:
271                 fn = adev->ctl;
272                 break;
273         case Qvolume:
274                 fn = adev->volwrite;
275                 break;
276         }
277         if(fn == nil)
278                 error(Egreg);
279
280         eqlock(ac);
281         if(waserror()){
282                 qunlock(ac);
283                 nexterror();
284         }
285         switch((ulong)c->qid.path){
286         case Qaudioctl:
287         case Qvolume:
288                 if(n >= sizeof(ac->buf))
289                         error(Etoobig);
290
291                 /* copy data to audiochan buffer so it can be modified */
292                 ac->data = nil;
293                 memmove(ac->buf, a, n);
294                 ac->buf[n] = 0;
295                 a = ac->buf;
296                 off = 0;
297         }
298         n = fn(adev, a, n, off);
299         qunlock(ac);
300         poperror();
301         return n;
302 }
303
304 static void
305 audioclose(Chan *c)
306 {
307         Audiochan *ac;
308         Audio *adev;
309
310         ac = c->aux;
311         adev = ac->adev;
312         if((c->qid.path == Qaudio) && (c->flag & COPEN)){
313                 if(adev->close){
314                         if(!waserror()){
315                                 adev->close(adev, c->mode);
316                                 poperror();
317                         }
318                 }
319                 if(c->mode == OWRITE || c->mode == ORDWR)
320                         decref(&adev->audioopenw);
321                 if(c->mode == OREAD || c->mode == ORDWR)
322                         decref(&adev->audioopenr);
323         }
324         if(ac->owner == c){
325                 ac->owner = nil;
326                 c->aux = nil;
327                 free(ac);
328         }
329 }
330
331 static Walkqid*
332 audiowalk(Chan *c, Chan *nc, char **name, int nname)
333 {
334         Audiochan *ac;
335         Audio *adev;
336         Walkqid *wq;
337
338         ac = c->aux;
339         adev = ac->adev;
340         wq = devwalk(c, nc, name, nname, audiodir, nelem(audiodir), devgen);
341         if(wq && wq->clone){
342                 if(audioclone(wq->clone, adev) == nil){
343                         free(wq);
344                         wq = nil;
345                 }
346         }
347         return wq;
348 }
349
350 static int
351 audiostat(Chan *c, uchar *dp, int n)
352 {
353         Audiochan *ac;
354         Audio *adev;
355
356         ac = c->aux;
357         adev = ac->adev;
358         if((ulong)c->qid.path == Qaudio)
359                 audiodir[Qaudio].length = adev->buffered ? adev->buffered(adev) : 0;
360         return devstat(c, dp, n, audiodir, nelem(audiodir), devgen);
361 }
362
363 /*
364  * audioread() made sure the buffer is big enougth so a full volume
365  * table can be serialized in one pass.
366  */
367 long
368 genaudiovolread(Audio *adev, void *a, long n, vlong,
369         Volume *vol, int (*volget)(Audio *, int, int *), ulong caps)
370 {
371         int i, j, r, v[2];
372         char *p, *e;
373
374         p = a;
375         e = p + n;
376         for(i = 0; vol[i].name != 0; ++i){
377                 if(vol[i].cap && (vol[i].cap & caps) == 0)
378                         continue;
379                 v[0] = 0;
380                 v[1] = 0;
381                 if((*volget)(adev, i, v) != 0)
382                         continue;
383                 if(vol[i].type == Absolute)
384                         p += snprint(p, e - p, "%s %d\n", vol[i].name, v[0]);
385                 else {
386                         r = abs(vol[i].range);
387                         if(r == 0)
388                                 continue;
389                         for(j=0; j<2; j++){
390                                 if(v[j] < 0)
391                                         v[j] = 0;
392                                 if(v[j] > r)
393                                         v[j] = r;
394                                 if(vol[i].range < 0)
395                                         v[j] = r - v[j];
396                                 v[j] = (v[j]*100)/r;
397                         }
398                         switch(vol[i].type){
399                         case Left:
400                                 p += snprint(p, e - p, "%s %d\n", vol[i].name, v[0]);
401                                 break;
402                         case Right:
403                                 p += snprint(p, e - p, "%s %d\n", vol[i].name, v[1]);
404                                 break;
405                         case Stereo:
406                                 p += snprint(p, e - p, "%s %d %d\n", vol[i].name, v[0], v[1]);
407                                 break;
408                         }
409                 }
410         }
411
412         return p - (char*)a;
413 }
414
415 /*
416  * genaudiovolwrite modifies the buffer that gets passed to it. this
417  * is ok as long as it is called from inside Audio.volwrite() because
418  * audiowrite() copies the data to Audiochan.buf[] and inserts a
419  * terminating \0 byte before calling Audio.volwrite().
420  */
421 long
422 genaudiovolwrite(Audio *adev, void *a, long n, vlong,
423         Volume *vol, int (*volset)(Audio *, int, int *), ulong caps)
424 {
425         int ntok, i, j, r, v[2];
426         char *p, *e, *x, *tok[4];
427
428         p = a;
429         e = p + n;
430
431         for(;p < e; p = x){
432                 if(x = strchr(p, '\n'))
433                         *x++ = 0;
434                 else
435                         x = e;
436                 ntok = tokenize(p, tok, 4);
437                 if(ntok <= 0)
438                         continue;
439                 if(ntok == 1){
440                         tok[1] = tok[0];
441                         tok[0] = "master";
442                         ntok = 2;
443                 }
444                 for(i = 0; vol[i].name != 0; i++){
445                         if(vol[i].cap && (vol[i].cap & caps) == 0)
446                                 continue;
447                         if(cistrcmp(vol[i].name, tok[0]))
448                                 continue;
449         
450                         if((ntok>2) && (!cistrcmp(tok[1], "out") || !cistrcmp(tok[1], "in")))
451                                 memmove(tok+1, tok+2, --ntok);
452
453                         v[0] = 0;
454                         v[1] = 0;
455                         if(ntok > 1)
456                                 v[0] = v[1] = atoi(tok[1]);
457                         if(ntok > 2)
458                                 v[1] = atoi(tok[2]);
459                         if(vol[i].type == Absolute)
460                                 (*volset)(adev, i, v);
461                         else {
462                                 r = abs(vol[i].range);
463                                 for(j=0; j<2; j++){
464                                         v[j] = (50+(v[j]*r))/100;
465                                         if(v[j] < 0)
466                                                 v[j] = 0;
467                                         if(v[j] > r)
468                                                 v[j] = r;
469                                         if(vol[i].range < 0)
470                                                 v[j] = r - v[j];
471                                 }
472                                 (*volset)(adev, i, v);
473                         }
474                         break;
475                 }
476                 if(vol[i].name == nil)
477                         error(Evolume);
478         }
479
480         return n;
481 }
482
483 Dev audiodevtab = {
484         'A',
485         "audio",
486         audioreset,
487         devinit,
488         devshutdown,
489         audioattach,
490         audiowalk,
491         audiostat,
492         audioopen,
493         devcreate,
494         audioclose,
495         audioread,
496         devbread,
497         audiowrite,
498         devbwrite,
499         devremove,
500         devwstat,
501 };