#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"; voidaddaudiocard(char *name, int (*probefn)(Audio *)){ Audioprobe *probe; if(naudioprobes >= nelem(audioprobes)) return; probe = &audioprobes[naudioprobes++]; probe->name = name; probe->probe = probefn;} static voidaudioreset(void){ int i, ctlrno; Audio **pp; Audioprobe *probe; ctlrno = 0; pp = &audiodevs; *pp = malloc(sizeof(Audio)); for(i=0; i<naudioprobes; i++){ probe = &audioprobes[i]; for(;;){ if(*pp == nil){ print("audio: no memory\n"); break; } memset(*pp, 0, sizeof(Audio)); (*pp)->ctlrno = 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<<adev->ctlrno; if((attached & i) == 0){ static char *settings[] = { "speed 44100", "delay 882", /* 20 ms */ "master 100", "audio 100", "head 100", }; attached |= i; for(i=0; i<nelem(settings) && adev->volwrite; 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 longaudioread(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 longaudiowrite(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 voidaudioclose(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 intaudiostat(Chan *c, uchar *dp, int 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. */longgenaudiovolread(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(). */longgenaudiovolwrite(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,};
#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; i<naudioprobes; i++){
probe = &audioprobes[i];
for(;;){
if(*pp == nil){
print("audio: no memory\n");
break;
}
memset(*pp, 0, sizeof(Audio));
(*pp)->ctlrno = 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<<adev->ctlrno;
if((attached & i) == 0){
static char *settings[] = {
"speed 44100",
"delay 882", /* 20 ms */
"master 100",
"audio 100",
"head 100",
};
attached |= i;
for(i=0; i<nelem(settings) && adev->volwrite; 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 int
audiostat(Chan *c, uchar *dp, int 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,
};