#include "u.h" #include "../port/lib.h" #include "mem.h" #include "dat.h" #include "fns.h" #include "../port/error.h" #include "../port/audioif.h" typedef struct Audioprobe 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, Qaudioctl, Qaudiostat, Qvolume, }; static Dirtab audiodir[] = { ".", {Qdir, 0, QTDIR}, 0, DMDIR|0555, "audio", {Qaudio}, 0, 0666, "audioctl", {Qaudioctl}, 0, 0222, "audiostat", {Qaudiostat}, 0, 0444, "volume", {Qvolume}, 0, 0666, }; static int naudioprobes; static Audioprobe audioprobes[16]; static Audio *audiodevs; static char Evolume[] = "illegal volume specifier"; static char Ebusy[] = "device is busy"; void addaudiocard(char *name, int (*probefn)(Audio *)) { Audioprobe *probe; if(naudioprobes >= nelem(audioprobes)) return; probe = &audioprobes[naudioprobes++]; probe->name = name; probe->probe = probefn; } static void audioreset(void) { int i, ctlrno; Audio **pp; Audioprobe *probe; ctlrno = 0; pp = &audiodevs; *pp = malloc(sizeof(Audio)); for(i=0; ictlrno = ctlrno; (*pp)->name = probe->name; if(probe->probe(*pp)) break; ctlrno++; pp = &(*pp)->next; *pp = malloc(sizeof(Audio)); } } free(*pp); *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) { Audiochan *ac; Audio *adev; Chan *c; int i; static int attached; if(spec != nil && *spec != '\0') i = strtol(spec, 0, 10); else i = 0; for(adev = audiodevs; adev; adev = adev->next) if(adev->ctlrno == i) break; if(adev == nil) error(Enodev); c = devattach('A', spec); c->qid.path = Qdir; if((ac = audioclone(c, adev)) == nil) error(Enomem); i = 1<ctlrno; if((attached & i) == 0){ static char *settings[] = { "speed 44100", "bits 16", "chan 2", "delay 882", /* 20 ms */ "master 100", "audio 100", "head 100", }; attached |= i; for(i=0; ivolwrite; i++){ strcpy(ac->buf, settings[i]); if(!waserror()){ adev->volwrite(adev, ac->buf, strlen(ac->buf), 0); poperror(); } } } return c; } static Chan* audioopen(Chan *c, int omode) { Audiochan *ac; Audio *adev; ac = c->aux; adev = ac->adev; if((c->qid.path == Qaudio) && (incref(&adev->audioopen) != 1)){ decref(&adev->audioopen); error(Ebusy); } 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); ac = c->aux; adev = ac->adev; fn = nil; switch((ulong)c->qid.path){ case Qdir: audiodir[Qaudio].length = adev->buffered ? adev->buffered(adev) : 0; return devdirread(c, a, n, audiodir, nelem(audiodir), devgen); case Qaudio: fn = adev->read; break; case Qaudiostat: fn = adev->status; break; case Qvolume: fn = adev->volread; break; } if(fn == nil) error(Egreg); qlock(ac); if(waserror()){ qunlock(ac); nexterror(); } switch((ulong)c->qid.path){ case Qaudiostat: case Qvolume: /* 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); break; default: n = fn(adev, a, n, off); } qunlock(ac); poperror(); return n; } static long audiowrite(Chan *c, void *a, long n, vlong off) { Audiochan *ac; Audio *adev; long (*fn)(Audio *, void *, long, vlong); ac = c->aux; adev = ac->adev; fn = nil; switch((ulong)c->qid.path){ case Qaudio: fn = adev->write; break; case Qaudioctl: fn = adev->ctl; break; case Qvolume: fn = adev->volwrite; break; } if(fn == nil) error(Egreg); qlock(ac); if(waserror()){ qunlock(ac); nexterror(); } 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 */ ac->data = nil; memmove(ac->buf, a, n); ac->buf[n] = 0; a = ac->buf; off = 0; } n = fn(adev, a, n, off); qunlock(ac); poperror(); return n; } static void audioclose(Chan *c) { Audiochan *ac; Audio *adev; ac = c->aux; adev = ac->adev; if((c->qid.path == Qaudio) && (c->flag & COPEN)){ if(adev->close){ if(!waserror()){ adev->close(adev); poperror(); } } decref(&adev->audioopen); } if(ac->owner == c){ ac->owner = nil; c->aux = nil; free(ac); } } static Walkqid* audiowalk(Chan *c, Chan *nc, char **name, int nname) { 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 long audiostat(Chan *c, uchar *dp, long n) { Audiochan *ac; Audio *adev; ac = c->aux; adev = ac->adev; if((ulong)c->qid.path == Qaudio) audiodir[Qaudio].length = adev->buffered ? adev->buffered(adev) : 0; return devstat(c, dp, n, audiodir, nelem(audiodir), devgen); } /* * 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) { 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 { if(vol[i].range == 0) continue; 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', "audio", audioreset, devinit, devshutdown, audioattach, audiowalk, audiostat, audioopen, devcreate, audioclose, audioread, devbread, audiowrite, devbwrite, devremove, devwstat, };