summaryrefslogtreecommitdiff
path: root/sys/src/9/port/devaudio.c
diff options
context:
space:
mode:
Diffstat (limited to 'sys/src/9/port/devaudio.c')
-rw-r--r--sys/src/9/port/devaudio.c335
1 files changed, 297 insertions, 38 deletions
diff --git a/sys/src/9/port/devaudio.c b/sys/src/9/port/devaudio.c
index 9234e15a9..2af6d9779 100644
--- a/sys/src/9/port/devaudio.c
+++ b/sys/src/9/port/devaudio.c
@@ -5,27 +5,36 @@
#include "fns.h"
#include "io.h"
#include "../port/error.h"
+#include "../port/audioif.h"
typedef struct Audioprobe Audioprobe;
-struct 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,
Qaudiostatus,
Qvolume,
-
- Maxaudioprobes = 8,
};
-static int naudioprobes;
-static Audioprobe audioprobes[Maxaudioprobes];
-static Audio *audiodevs;
-
static Dirtab audiodir[] = {
".", {Qdir, 0, QTDIR}, 0, DMDIR|0555,
"audio", {Qaudio}, 0, 0666,
@@ -34,10 +43,22 @@ static Dirtab audiodir[] = {
"volume", {Qvolume}, 0, 0666,
};
+
+static int naudioprobes;
+static Audioprobe audioprobes[16];
+static Audio *audiodevs;
+
+static char Evolume[] = "illegal volume specifier";
+
+
void
addaudiocard(char *name, int (*probefn)(Audio *))
{
Audioprobe *probe;
+
+ if(naudioprobes >= nelem(audioprobes))
+ return;
+
probe = &audioprobes[naudioprobes++];
probe->name = name;
probe->probe = probefn;
@@ -73,48 +94,107 @@ audioreset(void)
*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)
{
+ static int first = 1;
+ Audiochan *ac;
+ Audio *adev;
Chan *c;
- Audio *p;
int i;
+
if(spec != nil && *spec != '\0')
i = strtol(spec, 0, 10);
else
i = 0;
- for(p = audiodevs; p; p = p->next)
+ for(adev = audiodevs; adev; adev = adev->next)
if(i-- == 0)
break;
- if(p == nil)
+ if(adev == nil)
error(Enodev);
+
c = devattach('A', spec);
c->qid.path = Qdir;
- c->aux = p;
- if(p->attach)
- p->attach(p);
+
+ if((ac = audioclone(c, adev)) == nil)
+ error(Enomem);
+
+ if(first && adev->volwrite){
+ first = 0;
+
+ strcpy(ac->buf, "speed 44100");
+ if(!waserror()){
+ adev->volwrite(adev, ac->buf, strlen(ac->buf), 0);
+ poperror();
+ }
+ strcpy(ac->buf, "master 100");
+ if(!waserror()){
+ adev->volwrite(adev, ac->buf, strlen(ac->buf), 0);
+ poperror();
+ }
+ strcpy(ac->buf, "audio 100");
+ if(!waserror()){
+ adev->volwrite(adev, ac->buf, strlen(ac->buf), 0);
+ poperror();
+ }
+ strcpy(ac->buf, "head 100");
+ if(!waserror()){
+ adev->volwrite(adev, ac->buf, strlen(ac->buf), 0);
+ poperror();
+ }
+ }
+
return c;
}
+static Chan*
+audioopen(Chan *c, int omode)
+{
+ 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);
- adev = c->aux;
+
+ ac = c->aux;
+ adev = ac->adev;
+
+ fn = nil;
switch((ulong)c->qid.path){
- default:
- error("audio bugger (rd)");
- case Qaudioctl:
- fn = adev->ctl;
- break;
case Qdir:
+ /* BUG: race */
if(adev->buffered)
audiodir[Qaudio].length = adev->buffered(adev);
return devdirread(c, a, n, audiodir, nelem(audiodir), devgen);
case Qaudio:
fn = adev->read;
break;
+ case Qaudioctl:
+ fn = adev->ctl;
+ break;
case Qaudiostatus:
fn = adev->status;
break;
@@ -123,19 +203,49 @@ audioread(Chan *c, void *a, long n, vlong off)
break;
}
if(fn == nil)
- error("not implemented");
+ error(Egreg);
+
+ switch((ulong)c->qid.path){
+ case Qaudioctl:
+ case Qaudiostatus:
+ case Qvolume:
+ qlock(ac);
+ if(waserror()){
+ qunlock(ac);
+ nexterror();
+ }
+ /* 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);
+ qunlock(ac);
+ poperror();
+ return n;
+ }
return fn(adev, a, n, off);
}
static long
audiowrite(Chan *c, void *a, long n, vlong off)
{
+ Audiochan *ac;
Audio *adev;
long (*fn)(Audio *, void *, long, vlong);
- adev = c->aux;
+
+ ac = c->aux;
+ adev = ac->adev;
+
+ fn = nil;
switch((ulong)c->qid.path){
- default:
- error("audio bugger (wr)");
case Qaudio:
fn = adev->write;
break;
@@ -147,46 +257,195 @@ audiowrite(Chan *c, void *a, long n, vlong off)
break;
}
if(fn == nil)
- error("not implemented");
+ error(Egreg);
+
+ 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 */
+ qlock(ac);
+ if(waserror()){
+ qunlock(ac);
+ nexterror();
+ }
+ ac->data = nil;
+ memmove(ac->buf, a, n);
+ ac->buf[n] = 0;
+ n = fn(adev, ac->buf, n, 0);
+ qunlock(ac);
+ poperror();
+ return n;
+ }
return fn(adev, a, n, off);
}
static void
audioclose(Chan *c)
{
+ Audiochan *ac;
Audio *adev;
- adev = c->aux;
- switch((ulong)c->qid.path){
- default:
- return;
- case Qaudio:
- if(adev->close == nil)
- return;
- adev->close(adev);
- return;
+
+ ac = c->aux;
+ adev = ac->adev;
+ if(c->qid.path == Qaudio && (c->flag & COPEN))
+ if(adev->close)
+ adev->close(adev);
+
+ if(ac->owner == c){
+ ac->owner = nil;
+ c->aux = nil;
+ free(ac);
}
}
static Walkqid*
audiowalk(Chan *c, Chan *nc, char **name, int nname)
{
- return devwalk(c, nc, name, nname, audiodir, nelem(audiodir), devgen);
+ 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;
- adev = c->aux;
+
+ ac = c->aux;
+ adev = ac->adev;
+
+ /* BUG: race */
if(adev->buffered && (ulong)c->qid.path == Qaudio)
audiodir[Qaudio].length = adev->buffered(adev);
+
return devstat(c, dp, n, audiodir, nelem(audiodir), devgen);
}
-static Chan*
-audioopen(Chan *c, int omode)
+/*
+ * 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)
{
- return devopen(c, omode, audiodir, nelem(audiodir), devgen);
+ 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 {
+ 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 = {