#include <u.h>#include <libc.h>#include <fcall.h>#include <thread.h>#include <9p.h>#include "common.h"#include "ui.h"#include "fs.h" #define MAX(a,b) ((a)>=(b)?(a):(b))#define MIN(a,b) ((a)<=(b)?(a):(b))#define CROSS(a,b) (a <= 0.0f && a <= b && b >= 0.0f)#define Silence 0.00003f enum { Maxobjs = 64,}; static Aux *objs[Maxobjs]; static Aux rootaux[] = { [Xctl] = {.type = Xctl}, [Xmetadata] = {.type = Xmetadata}, [Xclone] = {.type = Xclone},}; extern File *uif;extern State *uis; static Fs *fs; static voidshutup(State *s, Voice *v){ if (s->voice != v && v->dsp != nil) { fs->dsp.free(v->dsp); v->dsp = nil; } v->state = Vsilent;} static Voice *newvoice(State *s, Auxdsp *dsp){ Voice *v, *f; int i; f = s->voice; for (i = 0, v = s->voice; i < nelem(s->voice); i++, v++) { if (v->dsp == nil) { f = v; break; } if (v->state == Vplaying && f->samples < v->samples) f = v; } shutup(s, f); f->dsp = dsp; return f;} static Aux *newobj(char *name){ File *f; Aux *o; Voice *v; int i, mode; for (i = 0, o = nil; o == nil && i < nelem(objs); i++) { if (objs[i] == nil){ o = objs[i] = calloc(1, sizeof(*o)); break; } } if (o == nil) return nil; o->id = i; o->type = Xdsp; o->ctl = Xdspctl; o->data = Xdspdata; sprint(name, "%d", o->id); if ((f = createfile(fs->srv.tree->root, name, nil, DMDIR|0775, o)) == nil) return nil; closefile(createfile(f, "ctl", nil, 0664, &o->ctl)); mode = 0; if (fs->dsp.read != nil) mode |= 0444; if (fs->dsp.write != nil) mode |= 0222; closefile(createfile(f, "data", nil, mode, &o->data)); uif = f; o->state = uis = calloc(1, sizeof(State)); v = newvoice(o->state, fs->dsp.new(&o->numin, &o->numout, &o->rate)); v->state = Vsilent; closefile(f); return o;} static voidfreeobj(Aux *o){ int i; if (o == nil) return; if (o->type == Xdsp) { objs[o->id] = nil; for (i = 0; i < nelem(o->state->voice); i++) { if (o->state->voice[i].dsp != nil) fs->dsp.free(o->state->voice[i].dsp); } free(o->state); } free(o);} static void *auxtype2obj(int *type){ switch (*type) { case Xdspctl: case Xuictl: return (uchar*)type - offsetof(Aux, ctl); case Xdspdata: return (uchar*)type - offsetof(Aux, data); case Xuimeta: return (uchar*)type - offsetof(Aux, metadata); default: sysfatal("trying to get aux out of type %d", *type); } return nil;} static voidfsopen(Req *r){ respond(r, nil);} static intauxreaddsp(Aux *a, float *b, int total){ int i, j, n, cross, nzeromax; Voice *v; float *m; State *s; s = a->state; /* make sure we have a buffer for mixing */ m = s->mixer; if (s->mixersz < total) { if ((m = realloc(m, total*sizeof(float))) != nil) { s->mixer = m; s->mixersz = total; } else { total = s->mixersz; } } /* silence first */ memset(b, 0, sizeof(*b)*total); v = s->voice; cross = -1; a->numvoices = 0; nzeromax = a->rate / 5; /* 1/5th of a second, assuming it's mono */ for (i = 0, n = total; i < nelem(s->voice) && n > 0; i++, v++) { if (v->dsp == nil) continue; switch (v->state) { case Vsilent: /* nothing to do here, ignore it */ continue; case Vstarting: /* should start playing it */ if (s->crossvoice && i > 0 && v[-1].state != Vsilent) { /* always start another voice at zero crossing to avoid clicks & pops */ if (cross < 0) /* no crossing yet? give up for now */ goto done; memset(b+cross, 0, sizeof(*b)*(n - cross)); /* silence the prev voice after zero crossing */ cross++; /* leaving one as 0 */ n -= cross; b += cross; cross = 0; shutup(s, &v[-1]); } v->state = Vplaying; v->samples = 0; v->nzero = 0; /* slippery floor */ case Vplaying: if (n < 1) break; if (fs->dsp.read(v->dsp, m, n) != n) return -1; v->samples += n; for (j = 0; j < n; j++) { b[j] += m[j]; if (s->crossvoice && cross < 0 && j > 0 && CROSS(m[j-1], m[j])) cross = j; if (m[j] >= -Silence && m[j] <= Silence) v->nzero++; else v->nzero = 0; } if (v->nzero >= nzeromax) { /* been quite for a while? shut it */ cross = 0; shutup(s, v); } else { a->numvoices++; } } } done: /* relocate the voices */ for (i = j = 1; i < nelem(s->voice); i++) { s->voice[j] = s->voice[i]; if(s->voice[j].dsp != nil) j++; } memset(s->voice+j, 0, sizeof(*s->voice)*(i - j)); return total;} static voidauxreset(Aux *a){ int i; for (i = 0; i < nelem(a->state->voice); i++) { if (a->state->voice[i].dsp != nil) fs->dsp.reset(a->state->voice[i].dsp); }} static voidfsread(Req *r){ Aux *a, *o; char b[256]; a = r->fid->file->aux; switch (a->type) { case Xctl: respond(r, nil); break; case Xdspctl: o = auxtype2obj(&a->type); sprint(b, "numin\t%d\nnumout\t%d\nrate\t%d\nnumvoices\t%d\n", o->numin, o->numout, o->rate, o->numvoices); readstr(r, b); respond(r, nil); break; case Xmetadata: readstr(r, fs->metadata); respond(r, nil); break; case Xclone: if (r->ifcall.offset == 0) { if (newobj(b) != nil) { readstr(r, b); } else { snprint(b, sizeof(b), "no free objects: %r"); respond(r, b); break; } } respond(r, nil); break; case Xuictl: case Xuimeta: o = auxtype2obj(&a->type); if (o->ui->readstr != nil) readstr(r, o->ui->readstr(o->ui, a->type, b, sizeof(b))); respond(r, nil); break; case Xdspdata: o = auxtype2obj(&a->type); r->ofcall.count = auxreaddsp(o, (float*)r->ofcall.data, r->ifcall.count/sizeof(float))*sizeof(float); respond(r, nil); break; default: respond(r, "not implemented"); break; }} static intauxwrite(Aux *a, int type, char *data){ State *s; Auxdsp *clone; Voice *v; void *so, *sc; u8int *tmp; int i, r, sz, nused; if (a->ui->writestr == nil) { werrstr("not implemented"); return -1; } s = a->state; s->crossvoice |= a->ui->crossvoice; for (i = nused = 0; i < nelem(s->voice); i++) { if (s->voice[i].state != Vsilent) nused++; } /* autovoice means every write needs to use (possibly) a new instance */ if (a->ui->autovoice && nused > 0 && fs->dsp.clone != nil && fs->dsp.state != nil) { /* now do the impossible */ so = fs->dsp.state(s->voice[0].dsp, &sz); tmp = malloc(sz); memmove(tmp, so, sz); /* save the original state */ /* write to the original and check if a new voice has to be created */ if ((r = a->ui->writestr(a->ui, type, data)) < 0 || *a->ui->zone == 0.0f) { free(tmp); return r; } clone = fs->dsp.clone(s->voice[0].dsp); /* clone the original */ sc = fs->dsp.state(clone, &sz); memmove(sc, so, sz); /* copy the changed state to the clone */ memmove(so, tmp, sz); /* revert the original state */ free(tmp); /* now we have the original dsp intact, with a cloned dsp actually having the changed state */ v = newvoice(s, clone); v->state = Vstarting; return r; } /* in any other case, just write to the original */ s->voice[0].state = Vstarting; return a->ui->writestr(a->ui, type, data);} static voidfswrite(Req *r){ Aux *a, *o; char b[256]; if (r->ifcall.count >= sizeof(b)) { respond(r, "can't fit into buffer"); return; } memmove(b, r->ifcall.data, r->ifcall.count); b[r->ifcall.count] = '\0'; r->ofcall.count = r->ifcall.count; a = r->fid->file->aux; switch (a->type) { case Xuictl: o = auxtype2obj(&a->type); if (auxwrite(o, a->type, b) >= 0) respond(r, nil); else responderror(r); break; case Xdspctl: /* FIXME changing sampling rate */ o = auxtype2obj(&a->type); if (strncmp(b, "reset", 5) == 0) /* FIXME ui needs to be reset as well */ auxreset(o); respond(r, nil); break; case Xmetadata: /* FIXME should be possible to add new key/value */ default: respond(r, "not implemented"); break; }} static voidfsdestroyfile(File *f){ Aux *a; if ((a = f->aux) == nil) return; switch (a->type) { case Xdsp: case Xui: freeobj(a); f->aux = nil; break; }} voidfsinit(void *fs_){ fs = fs_; fs->srv.open = fsopen; fs->srv.read = fsread; fs->srv.write = fswrite; fs->srv.tree = alloctree(nil, nil, DMDIR|0775, fsdestroyfile); closefile(createfile(fs->srv.tree->root, "ctl", nil, 0666, &rootaux[Xctl])); closefile(createfile(fs->srv.tree->root, "metadata", nil, 0444, &rootaux[Xmetadata])); closefile(createfile(fs->srv.tree->root, "clone", nil, 0444, &rootaux[Xclone]));}
#include <u.h>
#include <libc.h>
#include <fcall.h>
#include <thread.h>
#include <9p.h>
#include "common.h"
#include "ui.h"
#include "fs.h"
#define MAX(a,b) ((a)>=(b)?(a):(b))
#define MIN(a,b) ((a)<=(b)?(a):(b))
#define CROSS(a,b) (a <= 0.0f && a <= b && b >= 0.0f)
#define Silence 0.00003f
enum {
Maxobjs = 64,
};
static Aux *objs[Maxobjs];
static Aux rootaux[] = {
[Xctl] = {.type = Xctl},
[Xmetadata] = {.type = Xmetadata},
[Xclone] = {.type = Xclone},
};
extern File *uif;
extern State *uis;
static Fs *fs;
static void
shutup(State *s, Voice *v)
{
if (s->voice != v && v->dsp != nil) {
fs->dsp.free(v->dsp);
v->dsp = nil;
}
v->state = Vsilent;
}
static Voice *
newvoice(State *s, Auxdsp *dsp)
{
Voice *v, *f;
int i;
f = s->voice;
for (i = 0, v = s->voice; i < nelem(s->voice); i++, v++) {
if (v->dsp == nil) {
f = v;
break;
}
if (v->state == Vplaying && f->samples < v->samples)
f = v;
}
shutup(s, f);
f->dsp = dsp;
return f;
}
static Aux *
newobj(char *name)
{
File *f;
Aux *o;
Voice *v;
int i, mode;
for (i = 0, o = nil; o == nil && i < nelem(objs); i++) {
if (objs[i] == nil){
o = objs[i] = calloc(1, sizeof(*o));
break;
}
}
if (o == nil)
return nil;
o->id = i;
o->type = Xdsp;
o->ctl = Xdspctl;
o->data = Xdspdata;
sprint(name, "%d", o->id);
if ((f = createfile(fs->srv.tree->root, name, nil, DMDIR|0775, o)) == nil)
return nil;
closefile(createfile(f, "ctl", nil, 0664, &o->ctl));
mode = 0;
if (fs->dsp.read != nil)
mode |= 0444;
if (fs->dsp.write != nil)
mode |= 0222;
closefile(createfile(f, "data", nil, mode, &o->data));
uif = f;
o->state = uis = calloc(1, sizeof(State));
v = newvoice(o->state, fs->dsp.new(&o->numin, &o->numout, &o->rate));
v->state = Vsilent;
closefile(f);
return o;
}
static void
freeobj(Aux *o)
{
int i;
if (o == nil)
return;
if (o->type == Xdsp) {
objs[o->id] = nil;
for (i = 0; i < nelem(o->state->voice); i++) {
if (o->state->voice[i].dsp != nil)
fs->dsp.free(o->state->voice[i].dsp);
}
free(o->state);
}
free(o);
}
static void *
auxtype2obj(int *type)
{
switch (*type) {
case Xdspctl:
case Xuictl:
return (uchar*)type - offsetof(Aux, ctl);
case Xdspdata:
return (uchar*)type - offsetof(Aux, data);
case Xuimeta:
return (uchar*)type - offsetof(Aux, metadata);
default:
sysfatal("trying to get aux out of type %d", *type);
}
return nil;
}
static void
fsopen(Req *r)
{
respond(r, nil);
}
static int
auxreaddsp(Aux *a, float *b, int total)
{
int i, j, n, cross, nzeromax;
Voice *v;
float *m;
State *s;
s = a->state;
/* make sure we have a buffer for mixing */
m = s->mixer;
if (s->mixersz < total) {
if ((m = realloc(m, total*sizeof(float))) != nil) {
s->mixer = m;
s->mixersz = total;
} else {
total = s->mixersz;
}
}
/* silence first */
memset(b, 0, sizeof(*b)*total);
v = s->voice;
cross = -1;
a->numvoices = 0;
nzeromax = a->rate / 5; /* 1/5th of a second, assuming it's mono */
for (i = 0, n = total; i < nelem(s->voice) && n > 0; i++, v++) {
if (v->dsp == nil)
continue;
switch (v->state) {
case Vsilent: /* nothing to do here, ignore it */
continue;
case Vstarting: /* should start playing it */
if (s->crossvoice && i > 0 && v[-1].state != Vsilent) {
/* always start another voice at zero crossing to avoid clicks & pops */
if (cross < 0) /* no crossing yet? give up for now */
goto done;
memset(b+cross, 0, sizeof(*b)*(n - cross)); /* silence the prev voice after zero crossing */
cross++; /* leaving one as 0 */
n -= cross;
b += cross;
cross = 0;
shutup(s, &v[-1]);
}
v->state = Vplaying;
v->samples = 0;
v->nzero = 0;
/* slippery floor */
case Vplaying:
if (n < 1)
break;
if (fs->dsp.read(v->dsp, m, n) != n)
return -1;
v->samples += n;
for (j = 0; j < n; j++) {
b[j] += m[j];
if (s->crossvoice && cross < 0 && j > 0 && CROSS(m[j-1], m[j]))
cross = j;
if (m[j] >= -Silence && m[j] <= Silence)
v->nzero++;
else
v->nzero = 0;
}
if (v->nzero >= nzeromax) { /* been quite for a while? shut it */
cross = 0;
shutup(s, v);
} else {
a->numvoices++;
}
}
}
done:
/* relocate the voices */
for (i = j = 1; i < nelem(s->voice); i++) {
s->voice[j] = s->voice[i];
if(s->voice[j].dsp != nil)
j++;
}
memset(s->voice+j, 0, sizeof(*s->voice)*(i - j));
return total;
}
static void
auxreset(Aux *a)
{
int i;
for (i = 0; i < nelem(a->state->voice); i++) {
if (a->state->voice[i].dsp != nil)
fs->dsp.reset(a->state->voice[i].dsp);
}
}
static void
fsread(Req *r)
{
Aux *a, *o;
char b[256];
a = r->fid->file->aux;
switch (a->type) {
case Xctl:
respond(r, nil);
break;
case Xdspctl:
o = auxtype2obj(&a->type);
sprint(b, "numin\t%d\nnumout\t%d\nrate\t%d\nnumvoices\t%d\n", o->numin, o->numout, o->rate, o->numvoices);
readstr(r, b);
respond(r, nil);
break;
case Xmetadata:
readstr(r, fs->metadata);
respond(r, nil);
break;
case Xclone:
if (r->ifcall.offset == 0) {
if (newobj(b) != nil) {
readstr(r, b);
} else {
snprint(b, sizeof(b), "no free objects: %r");
respond(r, b);
break;
}
}
respond(r, nil);
break;
case Xuictl:
case Xuimeta:
o = auxtype2obj(&a->type);
if (o->ui->readstr != nil)
readstr(r, o->ui->readstr(o->ui, a->type, b, sizeof(b)));
respond(r, nil);
break;
case Xdspdata:
o = auxtype2obj(&a->type);
r->ofcall.count = auxreaddsp(o, (float*)r->ofcall.data, r->ifcall.count/sizeof(float))*sizeof(float);
respond(r, nil);
break;
default:
respond(r, "not implemented");
break;
}
}
static int
auxwrite(Aux *a, int type, char *data)
{
State *s;
Auxdsp *clone;
Voice *v;
void *so, *sc;
u8int *tmp;
int i, r, sz, nused;
if (a->ui->writestr == nil) {
werrstr("not implemented");
return -1;
}
s = a->state;
s->crossvoice |= a->ui->crossvoice;
for (i = nused = 0; i < nelem(s->voice); i++) {
if (s->voice[i].state != Vsilent)
nused++;
}
/* autovoice means every write needs to use (possibly) a new instance */
if (a->ui->autovoice && nused > 0 && fs->dsp.clone != nil && fs->dsp.state != nil) {
/* now do the impossible */
so = fs->dsp.state(s->voice[0].dsp, &sz);
tmp = malloc(sz);
memmove(tmp, so, sz); /* save the original state */
/* write to the original and check if a new voice has to be created */
if ((r = a->ui->writestr(a->ui, type, data)) < 0 || *a->ui->zone == 0.0f) {
free(tmp);
return r;
}
clone = fs->dsp.clone(s->voice[0].dsp); /* clone the original */
sc = fs->dsp.state(clone, &sz);
memmove(sc, so, sz); /* copy the changed state to the clone */
memmove(so, tmp, sz); /* revert the original state */
free(tmp);
/* now we have the original dsp intact, with a cloned dsp actually having the changed state */
v = newvoice(s, clone);
v->state = Vstarting;
return r;
}
/* in any other case, just write to the original */
s->voice[0].state = Vstarting;
return a->ui->writestr(a->ui, type, data);
}
static void
fswrite(Req *r)
{
Aux *a, *o;
char b[256];
if (r->ifcall.count >= sizeof(b)) {
respond(r, "can't fit into buffer");
return;
}
memmove(b, r->ifcall.data, r->ifcall.count);
b[r->ifcall.count] = '\0';
r->ofcall.count = r->ifcall.count;
a = r->fid->file->aux;
switch (a->type) {
case Xuictl:
o = auxtype2obj(&a->type);
if (auxwrite(o, a->type, b) >= 0)
respond(r, nil);
else
responderror(r);
break;
case Xdspctl: /* FIXME changing sampling rate */
o = auxtype2obj(&a->type);
if (strncmp(b, "reset", 5) == 0) /* FIXME ui needs to be reset as well */
auxreset(o);
respond(r, nil);
break;
case Xmetadata: /* FIXME should be possible to add new key/value */
default:
respond(r, "not implemented");
break;
}
}
static void
fsdestroyfile(File *f)
{
Aux *a;
if ((a = f->aux) == nil)
return;
switch (a->type) {
case Xdsp:
case Xui:
freeobj(a);
f->aux = nil;
break;
}
}
void
fsinit(void *fs_)
{
fs = fs_;
fs->srv.open = fsopen;
fs->srv.read = fsread;
fs->srv.write = fswrite;
fs->srv.tree = alloctree(nil, nil, DMDIR|0775, fsdestroyfile);
closefile(createfile(fs->srv.tree->root, "ctl", nil, 0666, &rootaux[Xctl]));
closefile(createfile(fs->srv.tree->root, "metadata", nil, 0444, &rootaux[Xmetadata]));
closefile(createfile(fs->srv.tree->root, "clone", nil, 0444, &rootaux[Xclone]));
}