#include <stdio.h>
#include <string.h>
#include "abuf.h"
#include "defs.h"
#include "dev.h"
#include "dsp.h"
#include "siofile.h"
#include "midi.h"
#include "opt.h"
#include "sysex.h"
#include "utils.h"
void zomb_onmove(void *);
void zomb_onxrun(void *);
void zomb_onvol(void *);
void zomb_fill(void *);
void zomb_flush(void *);
void zomb_eof(void *);
void zomb_exit(void *);
void dev_mix_badd(struct dev *, struct slot *);
void dev_mix_adjvol(struct dev *);
void dev_sub_bcopy(struct dev *, struct slot *);
void dev_onmove(struct dev *, int);
void dev_master(struct dev *, unsigned int);
void dev_cycle(struct dev *);
void dev_adjpar(struct dev *, int, int, int);
int dev_allocbufs(struct dev *);
void dev_freebufs(struct dev *);
int dev_ref(struct dev *);
void dev_unref(struct dev *);
int dev_init(struct dev *);
void dev_done(struct dev *);
struct dev *dev_bynum(int);
void dev_del(struct dev *);
unsigned int dev_roundof(struct dev *, unsigned int);
void dev_wakeup(struct dev *);
void slot_del(struct slot *);
void slot_ready(struct slot *);
void slot_allocbufs(struct slot *);
void slot_freebufs(struct slot *);
void slot_skip_update(struct slot *);
void slot_write(struct slot *);
void slot_read(struct slot *);
int slot_skip(struct slot *);
struct slotops zomb_slotops = {
zomb_onmove,
zomb_onxrun,
zomb_onvol,
zomb_fill,
zomb_flush,
zomb_eof,
zomb_exit
};
struct ctl *ctl_list = NULL;
struct dev *dev_list = NULL;
unsigned int dev_sndnum = 0;
struct ctlslot ctlslot_array[DEV_NCTLSLOT];
struct slot slot_array[DEV_NSLOT];
struct mtc mtc_array[1] = {
{.dev = NULL, .tstate = MTC_STOP}
};
void
zomb_onmove(void *arg)
{
}
void
zomb_onxrun(void *arg)
{
}
void
zomb_onvol(void *arg)
{
}
void
zomb_fill(void *arg)
{
}
void
zomb_flush(void *arg)
{
}
void
zomb_eof(void *arg)
{
struct slot *s = arg;
#ifdef DEBUG
logx(3, "slot%zu: %s", s - slot_array, __func__);
#endif
s->ops = NULL;
}
void
zomb_exit(void *arg)
{
#ifdef DEBUG
struct slot *s = arg;
logx(3, "slot%zu: %s", s - slot_array, __func__);
#endif
}
size_t
chans_fmt(char *buf, size_t size, int mode, int pmin, int pmax, int rmin, int rmax)
{
const char *sep = "";
char *end = buf + size;
char *p = buf;
if (mode & MODE_PLAY) {
p += snprintf(p, p < end ? end - p : 0, "play %d:%d", pmin, pmax);
sep = ", ";
}
if (mode & MODE_REC) {
p += snprintf(p, p < end ? end - p : 0, "%srec %d:%d", sep, rmin, rmax);
sep = ", ";
}
if (mode & MODE_MON) {
p += snprintf(p, p < end ? end - p : 0, "%smon", sep);
}
return p - buf;
}
void
dev_midi_send(struct dev *d, void *msg, int msglen)
{
struct opt *o;
for (o = opt_list; o != NULL; o = o->next) {
if (o->dev != d)
continue;
midi_send(o->midi, msg, msglen);
}
}
void
mtc_midi_qfr(struct mtc *mtc, int delta)
{
unsigned char buf[2];
unsigned int data;
int qfrlen;
mtc->delta += delta * MTC_SEC;
qfrlen = mtc->dev->rate * (MTC_SEC / (4 * mtc->fps));
while (mtc->delta >= qfrlen) {
switch (mtc->qfr) {
case 0:
data = mtc->fr & 0xf;
break;
case 1:
data = mtc->fr >> 4;
break;
case 2:
data = mtc->sec & 0xf;
break;
case 3:
data = mtc->sec >> 4;
break;
case 4:
data = mtc->min & 0xf;
break;
case 5:
data = mtc->min >> 4;
break;
case 6:
data = mtc->hr & 0xf;
break;
case 7:
data = (mtc->hr >> 4) | (mtc->fps_id << 1);
mtc->fr += 2;
if (mtc->fr < mtc->fps)
break;
mtc->fr -= mtc->fps;
mtc->sec++;
if (mtc->sec < 60)
break;
mtc->sec = 0;
mtc->min++;
if (mtc->min < 60)
break;
mtc->min = 0;
mtc->hr++;
if (mtc->hr < 24)
break;
mtc->hr = 0;
break;
default:
data = 0;
}
buf[0] = 0xf1;
buf[1] = (mtc->qfr << 4) | data;
mtc->qfr++;
mtc->qfr &= 7;
dev_midi_send(mtc->dev, buf, 2);
mtc->delta -= qfrlen;
}
}
void
mtc_midi_full(struct mtc *mtc)
{
struct sysex x;
unsigned int fps;
mtc->delta = -MTC_SEC * (int)mtc->dev->bufsz;
if (mtc->dev->rate % (30 * 4 * mtc->dev->round) == 0) {
mtc->fps_id = MTC_FPS_30;
mtc->fps = 30;
} else if (mtc->dev->rate % (25 * 4 * mtc->dev->round) == 0) {
mtc->fps_id = MTC_FPS_25;
mtc->fps = 25;
} else {
mtc->fps_id = MTC_FPS_24;
mtc->fps = 24;
}
#ifdef DEBUG
logx(3, "%s: mtc full frame at %d, %d fps", mtc->dev->path, mtc->delta, mtc->fps);
#endif
fps = mtc->fps;
mtc->hr = (mtc->origin / (MTC_SEC * 3600)) % 24;
mtc->min = (mtc->origin / (MTC_SEC * 60)) % 60;
mtc->sec = (mtc->origin / (MTC_SEC)) % 60;
mtc->fr = (mtc->origin / (MTC_SEC / fps)) % fps;
x.start = SYSEX_START;
x.type = SYSEX_TYPE_RT;
x.dev = SYSEX_DEV_ANY;
x.id0 = SYSEX_MTC;
x.id1 = SYSEX_MTC_FULL;
x.u.full.hr = mtc->hr | (mtc->fps_id << 5);
x.u.full.min = mtc->min;
x.u.full.sec = mtc->sec;
x.u.full.fr = mtc->fr;
x.u.full.end = SYSEX_END;
mtc->qfr = 0;
dev_midi_send(mtc->dev, (unsigned char *)&x, SYSEX_SIZE(full));
}
void
dev_midi_master(struct dev *d)
{
struct ctl *c;
unsigned int master, v;
struct sysex x;
if (d->master_enabled)
master = d->master;
else {
master = 0;
for (c = ctl_list; c != NULL; c = c->next) {
if (c->type != CTL_NUM ||
strcmp(c->group, d->name) != 0 ||
strcmp(c->node0.name, "output") != 0 ||
strcmp(c->func, "level") != 0)
continue;
if (c->u.any.arg0 != d)
continue;
v = (c->curval * 127 + c->maxval / 2) / c->maxval;
if (master < v)
master = v;
}
}
memset(&x, 0, sizeof(struct sysex));
x.start = SYSEX_START;
x.type = SYSEX_TYPE_RT;
x.dev = SYSEX_DEV_ANY;
x.id0 = SYSEX_CONTROL;
x.id1 = SYSEX_MASTER;
x.u.master.fine = 0;
x.u.master.coarse = master;
x.u.master.end = SYSEX_END;
dev_midi_send(d, (unsigned char *)&x, SYSEX_SIZE(master));
}
int
slot_skip(struct slot *s)
{
unsigned char *data = (unsigned char *)0xdeadbeef;
int max, count;
max = s->skip;
while (s->skip > 0) {
if (s->pstate != SLOT_STOP && (s->mode & MODE_RECMASK)) {
data = abuf_wgetblk(&s->sub.buf, &count);
if (count < s->round * s->sub.bpf)
break;
}
if (s->mode & MODE_PLAY) {
if (s->mix.buf.used < s->round * s->mix.bpf)
break;
}
#ifdef DEBUG
logx(4, "slot%zu: skipped a cycle", s - slot_array);
#endif
if (s->pstate != SLOT_STOP && (s->mode & MODE_RECMASK)) {
if (s->sub.encbuf)
enc_sil_do(&s->sub.enc, data, s->round);
else
memset(data, 0, s->round * s->sub.bpf);
abuf_wcommit(&s->sub.buf, s->round * s->sub.bpf);
}
if (s->mode & MODE_PLAY) {
abuf_rdiscard(&s->mix.buf, s->round * s->mix.bpf);
}
s->skip--;
}
return max - s->skip;
}
void
dev_mix_badd(struct dev *d, struct slot *s)
{
adata_t *idata, *odata, *in;
int icount;
odata = DEV_PBUF(d);
idata = (adata_t *)abuf_rgetblk(&s->mix.buf, &icount);
#ifdef DEBUG
if (icount < s->round * s->mix.bpf) {
logx(0, "slot%zu: not enough data to mix (%u bytes)",
s - slot_array, icount);
panic();
}
#endif
if (!(s->opt->mode & MODE_PLAY)) {
abuf_rdiscard(&s->mix.buf, s->round * s->mix.bpf);
return;
}
in = idata;
if (s->mix.decbuf) {
dec_do(&s->mix.dec, (void *)in, s->mix.decbuf, s->round);
in = s->mix.decbuf;
}
if (s->mix.resampbuf) {
resamp_do(&s->mix.resamp,
in, s->mix.resampbuf, s->round, d->round);
in = s->mix.resampbuf;
}
cmap_do(&s->mix.cmap, in, odata,
ADATA_MUL(s->mix.weight, s->mix.vol),
d->round, 1);
abuf_rdiscard(&s->mix.buf, s->round * s->mix.bpf);
}
void
dev_mix_adjvol(struct dev *d)
{
unsigned int n;
struct slot *i, *j;
int jcmax, icmax, weight;
for (i = d->slot_list; i != NULL; i = i->next) {
if (!(i->mode & MODE_PLAY))
continue;
icmax = i->opt->pmin + i->mix.nch - 1;
weight = ADATA_UNIT;
if (d->autovol) {
n = 0;
for (j = d->slot_list; j != NULL; j = j->next) {
if (!(j->mode & MODE_PLAY))
continue;
jcmax = j->opt->pmin + j->mix.nch - 1;
if (i->opt->pmin <= jcmax &&
icmax >= j->opt->pmin)
n++;
}
weight /= n;
}
if (weight > i->opt->maxweight)
weight = i->opt->maxweight;
i->mix.weight = d->master_enabled ?
ADATA_MUL(weight, MIDI_TO_ADATA(d->master)) : weight;
#ifdef DEBUG
logx(3, "slot%zu: set weight: %d / %d", i - slot_array, i->mix.weight,
i->opt->maxweight);
#endif
}
}
void
dev_sub_bcopy(struct dev *d, struct slot *s)
{
adata_t *enc_out, *resamp_out, *cmap_out;
void *odata;
int ocount, moffs, mix;
odata = (adata_t *)abuf_wgetblk(&s->sub.buf, &ocount);
#ifdef DEBUG
if (ocount < s->round * s->sub.bpf) {
logx(0, "dev_sub_bcopy: not enough space");
panic();
}
#endif
if ((s->opt->mode & MODE_RECMASK) == 0) {
if (s->sub.encbuf)
enc_sil_do(&s->sub.enc, odata, s->round);
else
memset(odata, 0, s->round * s->sub.bpf);
abuf_wcommit(&s->sub.buf, s->round * s->sub.bpf);
return;
}
enc_out = odata;
resamp_out = s->sub.encbuf ? s->sub.encbuf : enc_out;
cmap_out = s->sub.resampbuf ? s->sub.resampbuf : resamp_out;
mix = 0;
if (s->opt->mode & MODE_MON) {
moffs = d->poffs + d->round;
if (moffs == d->psize)
moffs = 0;
cmap_do(&s->sub.cmap_mon, d->pbuf + moffs * d->pchan, cmap_out,
ADATA_UNIT, d->round, mix++);
}
if (s->opt->mode & MODE_REC) {
cmap_do(&s->sub.cmap_rec, d->rbuf, cmap_out,
ADATA_UNIT, d->round, mix++);
}
if (s->sub.resampbuf) {
resamp_do(&s->sub.resamp,
s->sub.resampbuf, resamp_out, d->round, s->round);
}
if (s->sub.encbuf)
enc_do(&s->sub.enc, s->sub.encbuf, (void *)enc_out, s->round);
abuf_wcommit(&s->sub.buf, s->round * s->sub.bpf);
}
void
dev_cycle(struct dev *d)
{
struct slot *s, **ps;
unsigned char *base;
int nsamp;
if (d->slot_list == NULL && d->idle >= d->bufsz &&
(mtc_array[0].dev != d || mtc_array[0].tstate != MTC_RUN)) {
logx(2, "%s: device stopped", d->path);
dev_sio_stop(d);
d->pstate = DEV_INIT;
if (d->refcnt == 0)
dev_close(d);
return;
}
if (d->prime > 0) {
#ifdef DEBUG
logx(4, "%s: empty cycle, prime = %u", d->path, d->prime);
#endif
base = (unsigned char *)DEV_PBUF(d);
nsamp = d->round * d->pchan;
memset(base, 0, nsamp * sizeof(adata_t));
if (d->encbuf) {
enc_do(&d->enc, (unsigned char *)DEV_PBUF(d),
d->encbuf, d->round);
}
d->prime -= d->round;
return;
}
d->delta -= d->round;
#ifdef DEBUG
logx(4, "%s: full cycle: delta = %d", d->path, d->delta);
#endif
if (d->mode & MODE_PLAY) {
base = (unsigned char *)DEV_PBUF(d);
nsamp = d->round * d->pchan;
memset(base, 0, nsamp * sizeof(adata_t));
}
if ((d->mode & MODE_REC) && d->decbuf)
dec_do(&d->dec, d->decbuf, (unsigned char *)d->rbuf, d->round);
ps = &d->slot_list;
while ((s = *ps) != NULL) {
#ifdef DEBUG
logx(4, "slot%zu: running, skip = %d", s - slot_array, s->skip);
#endif
d->idle = 0;
slot_skip(s);
if (s->skip < 0) {
s->skip++;
ps = &s->next;
continue;
}
#ifdef DEBUG
if (s->pstate == SLOT_STOP && !(s->mode & MODE_PLAY)) {
logx(0, "slot%zu: rec-only slots can't be drained",
s - slot_array);
panic();
}
#endif
if (s->pstate == SLOT_STOP &&
s->mix.buf.used < s->round * s->mix.bpf) {
*ps = s->next;
s->pstate = SLOT_INIT;
s->ops->eof(s->arg);
slot_freebufs(s);
dev_mix_adjvol(d);
#ifdef DEBUG
logx(3, "slot%zu: drained", s - slot_array);
#endif
continue;
}
if (((s->mode & MODE_PLAY) &&
s->mix.buf.used < s->round * s->mix.bpf) ||
((s->mode & MODE_RECMASK) &&
s->sub.buf.len - s->sub.buf.used <
s->round * s->sub.bpf)) {
if (!s->paused) {
#ifdef DEBUG
logx(3, "slot%zu: xrun, paused", s - slot_array);
#endif
s->paused = 1;
s->ops->onxrun(s->arg);
}
if (s->xrun == XRUN_IGNORE) {
s->delta -= s->round;
ps = &s->next;
} else if (s->xrun == XRUN_SYNC) {
s->skip++;
ps = &s->next;
} else if (s->xrun == XRUN_ERROR) {
s->ops->exit(s->arg);
*ps = s->next;
} else {
#ifdef DEBUG
logx(0, "slot%zu: bad xrun mode", s - slot_array);
panic();
#endif
}
continue;
} else {
if (s->paused) {
#ifdef DEBUG
logx(3, "slot%zu: resumed\n", s - slot_array);
#endif
s->paused = 0;
}
}
if ((s->mode & MODE_RECMASK) && !(s->pstate == SLOT_STOP)) {
if (s->sub.prime == 0) {
dev_sub_bcopy(d, s);
s->ops->flush(s->arg);
} else {
#ifdef DEBUG
logx(3, "slot%zu: prime = %d", s - slot_array,
s->sub.prime);
#endif
s->sub.prime--;
}
}
if (s->mode & MODE_PLAY) {
dev_mix_badd(d, s);
if (s->pstate != SLOT_STOP)
s->ops->fill(s->arg);
}
ps = &s->next;
}
if ((d->mode & MODE_PLAY) && d->encbuf) {
enc_do(&d->enc, (unsigned char *)DEV_PBUF(d),
d->encbuf, d->round);
}
}
void
dev_onmove(struct dev *d, int delta)
{
long long pos;
struct slot *s, *snext;
d->delta += delta;
if (d->slot_list == NULL)
d->idle += delta;
for (s = d->slot_list; s != NULL; s = snext) {
snext = s->next;
pos = s->delta_rem +
(long long)s->delta * d->round +
(long long)delta * s->round;
s->delta = pos / (int)d->round;
s->delta_rem = pos % d->round;
if (s->delta_rem < 0) {
s->delta_rem += d->round;
s->delta--;
}
if (s->delta >= 0)
s->ops->onmove(s->arg);
}
if (mtc_array[0].dev == d && mtc_array[0].tstate == MTC_RUN)
mtc_midi_qfr(&mtc_array[0], delta);
}
void
dev_master(struct dev *d, unsigned int master)
{
struct ctl *c;
unsigned int v;
logx(2, "%s: master volume set to %u", d->path, master);
if (d->master_enabled) {
d->master = master;
if (d->mode & MODE_PLAY)
dev_mix_adjvol(d);
} else {
for (c = ctl_list; c != NULL; c = c->next) {
if (c->scope != CTL_HW || c->u.hw.dev != d)
continue;
if (c->type != CTL_NUM ||
strcmp(c->group, d->name) != 0 ||
strcmp(c->node0.name, "output") != 0 ||
strcmp(c->func, "level") != 0)
continue;
v = (master * c->maxval + 64) / 127;
ctl_setval(c, v);
}
}
}
struct dev *
dev_new(char *path, struct aparams *par,
unsigned int mode, unsigned int bufsz, unsigned int round,
unsigned int rate, unsigned int hold, unsigned int autovol)
{
struct dev *d, **pd;
if (dev_sndnum == DEV_NMAX) {
logx(1, "too many devices");
return NULL;
}
d = xmalloc(sizeof(struct dev));
d->path = path;
d->num = dev_sndnum++;
d->reqpar = *par;
d->reqmode = mode;
d->reqpchan = d->reqrchan = 0;
d->reqbufsz = bufsz;
d->reqround = round;
d->reqrate = rate;
d->hold = hold;
d->autovol = autovol;
d->refcnt = 0;
d->pstate = DEV_CFG;
d->slot_list = NULL;
d->master = MIDI_MAXCTL;
d->master_enabled = 0;
snprintf(d->name, CTL_NAMEMAX, "%u", d->num);
for (pd = &dev_list; *pd != NULL; pd = &(*pd)->next)
;
d->next = *pd;
*pd = d;
return d;
}
void
dev_adjpar(struct dev *d, int mode,
int pmax, int rmax)
{
d->reqmode |= mode & MODE_AUDIOMASK;
if (mode & MODE_PLAY) {
if (d->reqpchan < pmax + 1)
d->reqpchan = pmax + 1;
}
if (mode & MODE_REC) {
if (d->reqrchan < rmax + 1)
d->reqrchan = rmax + 1;
}
}
int
dev_allocbufs(struct dev *d)
{
char enc_str[ENCMAX], chans_str[64];
d->rbuf = xmalloc(d->round * d->rchan * sizeof(adata_t));
if (!aparams_native(&d->par)) {
dec_init(&d->dec, &d->par, d->rchan);
d->decbuf = xmalloc(d->round * d->rchan * d->par.bps);
} else
d->decbuf = NULL;
d->poffs = 0;
d->psize = d->bufsz + d->round;
d->pbuf = xmalloc(d->psize * d->pchan * sizeof(adata_t));
d->mode |= MODE_MON;
if (!aparams_native(&d->par)) {
enc_init(&d->enc, &d->par, d->pchan);
d->encbuf = xmalloc(d->round * d->pchan * d->par.bps);
} else
d->encbuf = NULL;
memset(d->rbuf, 0, d->round * d->rchan * sizeof(adata_t));
logx(2, "%s: %dHz, %s, %s, %d blocks of %d frames",
d->path, d->rate,
(aparams_enctostr(&d->par, enc_str), enc_str),
(chans_fmt(chans_str, sizeof(chans_str),
d->mode & (MODE_PLAY | MODE_REC),
0, d->pchan - 1, 0, d->rchan - 1), chans_str),
d->bufsz / d->round, d->round);
return 1;
}
int
dev_open(struct dev *d)
{
d->mode = d->reqmode;
d->round = d->reqround;
d->bufsz = d->reqbufsz;
d->rate = d->reqrate;
d->pchan = d->reqpchan;
d->rchan = d->reqrchan;
d->par = d->reqpar;
if (d->pchan == 0)
d->pchan = 2;
if (d->rchan == 0)
d->rchan = 2;
if (!dev_sio_open(d)) {
logx(1, "%s: failed to open audio device", d->path);
return 0;
}
if (!dev_allocbufs(d))
return 0;
d->pstate = DEV_INIT;
return 1;
}
void
dev_abort(struct dev *d)
{
int i;
struct slot *s;
struct ctlslot *c;
struct opt *o;
for (i = 0, s = slot_array; i < DEV_NSLOT; i++, s++) {
if (s->opt == NULL || s->opt->dev != d)
continue;
if (s->ops) {
s->ops->exit(s->arg);
s->ops = NULL;
}
}
d->slot_list = NULL;
for (o = opt_list; o != NULL; o = o->next) {
if (o->dev != d)
continue;
for (c = ctlslot_array, i = 0; i < DEV_NCTLSLOT; i++, c++) {
if (c->ops == NULL)
continue;
if (c->opt == o) {
c->ops->exit(c->arg);
c->ops = NULL;
}
}
midi_abort(o->midi);
}
if (d->pstate != DEV_CFG)
dev_close(d);
}
void
dev_freebufs(struct dev *d)
{
#ifdef DEBUG
logx(3, "%s: closing", d->path);
#endif
if (d->mode & MODE_PLAY) {
if (d->encbuf != NULL)
xfree(d->encbuf);
xfree(d->pbuf);
}
if (d->mode & MODE_REC) {
if (d->decbuf != NULL)
xfree(d->decbuf);
xfree(d->rbuf);
}
}
void
dev_close(struct dev *d)
{
d->pstate = DEV_CFG;
dev_sio_close(d);
dev_freebufs(d);
if (d->master_enabled) {
d->master_enabled = 0;
ctl_del(CTL_DEV_MASTER, d, NULL);
}
}
int
dev_ref(struct dev *d)
{
#ifdef DEBUG
logx(3, "%s: device requested", d->path);
#endif
if (d->pstate == DEV_CFG && !dev_open(d))
return 0;
d->refcnt++;
return 1;
}
void
dev_unref(struct dev *d)
{
#ifdef DEBUG
logx(3, "%s: device released", d->path);
#endif
d->refcnt--;
if (d->refcnt == 0 && d->pstate == DEV_INIT)
dev_close(d);
}
int
dev_init(struct dev *d)
{
if ((d->reqmode & MODE_AUDIOMASK) == 0) {
#ifdef DEBUG
logx(1, "%s: has no streams", d->path);
#endif
return 0;
}
if (d->hold && !dev_ref(d))
return 0;
return 1;
}
void
dev_done(struct dev *d)
{
#ifdef DEBUG
logx(3, "%s: draining", d->path);
#endif
if (mtc_array[0].dev == d && mtc_array[0].tstate != MTC_STOP)
mtc_stop(&mtc_array[0]);
if (d->hold)
dev_unref(d);
}
struct dev *
dev_bynum(int num)
{
struct dev *d;
for (d = dev_list; d != NULL; d = d->next) {
if (d->num == num)
return d;
}
return NULL;
}
void
dev_del(struct dev *d)
{
struct dev **p;
#ifdef DEBUG
logx(3, "%s: deleting", d->path);
#endif
if (d->pstate != DEV_CFG)
dev_close(d);
for (p = &dev_list; *p != d; p = &(*p)->next) {
#ifdef DEBUG
if (*p == NULL) {
logx(0, "%s: not on the list", d->path);
panic();
}
#endif
}
*p = d->next;
xfree(d);
}
unsigned int
dev_roundof(struct dev *d, unsigned int newrate)
{
return (d->round * newrate + d->rate / 2) / d->rate;
}
void
dev_wakeup(struct dev *d)
{
if (d->pstate == DEV_INIT) {
logx(2, "%s: started", d->path);
if (d->mode & MODE_PLAY) {
d->prime = d->bufsz;
} else {
d->prime = 0;
}
d->idle = 0;
d->poffs = 0;
d->delta = 0;
d->pstate = DEV_RUN;
dev_sio_start(d);
}
}
int
dev_iscompat(struct dev *o, struct dev *n)
{
if (((long long)o->round * n->rate != (long long)n->round * o->rate) ||
((long long)o->bufsz * n->rate != (long long)n->bufsz * o->rate)) {
logx(1, "%s: not compatible with %s", n->name, o->name);
return 0;
}
return 1;
}
void
dev_migrate(struct dev *odev)
{
struct opt *o;
if (odev->pstate == DEV_CFG)
return;
for (o = opt_list; o != NULL; o = o->next) {
if (o->dev != odev)
continue;
opt_migrate(o, odev);
}
}
void
mtc_trigger(struct mtc *mtc)
{
int i;
struct slot *s;
if (mtc->tstate != MTC_START) {
logx(2, "%s: not started by mmc yet, waiting.", mtc->dev->path);
return;
}
for (i = 0, s = slot_array; i < DEV_NSLOT; i++, s++) {
if (s->opt == NULL || s->opt->mtc != mtc)
continue;
if (s->pstate != SLOT_READY) {
#ifdef DEBUG
logx(3, "slot%zu: not ready, start delayed", s - slot_array);
#endif
return;
}
}
if (!dev_ref(mtc->dev))
return;
for (i = 0, s = slot_array; i < DEV_NSLOT; i++, s++) {
if (s->opt == NULL || s->opt->mtc != mtc)
continue;
slot_attach(s);
s->pstate = SLOT_RUN;
}
mtc->tstate = MTC_RUN;
mtc_midi_full(mtc);
dev_wakeup(mtc->dev);
}
void
mtc_start(struct mtc *mtc)
{
if (mtc->tstate == MTC_STOP) {
mtc->tstate = MTC_START;
mtc_trigger(mtc);
#ifdef DEBUG
} else {
logx(3, "%s: ignoring mmc start", mtc->dev->path);
#endif
}
}
void
mtc_stop(struct mtc *mtc)
{
switch (mtc->tstate) {
case MTC_START:
mtc->tstate = MTC_STOP;
return;
case MTC_RUN:
mtc->tstate = MTC_STOP;
dev_unref(mtc->dev);
break;
default:
#ifdef DEBUG
logx(3, "%s: ignored mmc stop", mtc->dev->path);
#endif
return;
}
}
void
mtc_loc(struct mtc *mtc, unsigned int origin)
{
logx(2, "%s: relocated to %u", mtc->dev->path, origin);
if (mtc->tstate == MTC_RUN)
mtc_stop(mtc);
mtc->origin = origin;
if (mtc->tstate == MTC_RUN)
mtc_start(mtc);
}
void
mtc_setdev(struct mtc *mtc, struct dev *d)
{
struct opt *o;
if (mtc->dev == d)
return;
logx(2, "%s: set to be MIDI clock source", d->path);
if (mtc->tstate == MTC_RUN) {
mtc->delta -= mtc->dev->delta;
dev_unref(mtc->dev);
}
mtc->dev = d;
if (mtc->tstate == MTC_RUN) {
mtc->delta += mtc->dev->delta;
dev_ref(mtc->dev);
dev_wakeup(mtc->dev);
}
for (o = opt_list; o != NULL; o = o->next) {
if (o->mtc == mtc)
opt_setdev(o, mtc->dev);
}
}
void
slot_initconv(struct slot *s)
{
struct dev *d = s->opt->dev;
if (s->mode & MODE_PLAY) {
cmap_init(&s->mix.cmap,
s->opt->pmin, s->opt->pmin + s->mix.nch - 1,
s->opt->pmin, s->opt->pmin + s->mix.nch - 1,
0, d->pchan - 1,
s->opt->pmin, s->opt->pmax,
s->opt->dup);
s->mix.decbuf = NULL;
s->mix.resampbuf = NULL;
if (!aparams_native(&s->par)) {
dec_init(&s->mix.dec, &s->par, s->mix.nch);
s->mix.decbuf =
xmalloc(s->round * s->mix.nch * sizeof(adata_t));
}
if (s->rate != d->rate) {
resamp_init(&s->mix.resamp, s->round, d->round,
s->mix.nch);
s->mix.resampbuf =
xmalloc(d->round * s->mix.nch * sizeof(adata_t));
}
}
if (s->mode & MODE_RECMASK) {
s->sub.encbuf = NULL;
s->sub.resampbuf = NULL;
cmap_init(&s->sub.cmap_rec,
0, d->rchan - 1,
s->opt->rmin, s->opt->rmax,
s->opt->rmin, s->opt->rmin + s->sub.nch - 1,
s->opt->rmin, s->opt->rmin + s->sub.nch - 1,
s->opt->dup);
cmap_init(&s->sub.cmap_mon,
0, d->pchan - 1,
s->opt->pmin, s->opt->pmax,
s->opt->pmin, s->opt->pmin + s->sub.nch - 1,
s->opt->pmin, s->opt->pmin + s->sub.nch - 1,
s->opt->dup);
if (s->rate != d->rate) {
resamp_init(&s->sub.resamp, d->round, s->round,
s->sub.nch);
s->sub.resampbuf =
xmalloc(d->round * s->sub.nch * sizeof(adata_t));
}
if (!aparams_native(&s->par)) {
enc_init(&s->sub.enc, &s->par, s->sub.nch);
s->sub.encbuf =
xmalloc(s->round * s->sub.nch * sizeof(adata_t));
}
if (s->sub.resampbuf) {
memset(s->sub.resampbuf, 0,
d->round * s->sub.nch * sizeof(adata_t));
} else if (s->sub.encbuf) {
memset(s->sub.encbuf, 0,
s->round * s->sub.nch * sizeof(adata_t));
} else {
memset(s->sub.buf.data, 0,
s->appbufsz * s->sub.nch * sizeof(adata_t));
}
}
}
void
slot_allocbufs(struct slot *s)
{
if (s->mode & MODE_PLAY) {
s->mix.bpf = s->par.bps * s->mix.nch;
abuf_init(&s->mix.buf, s->appbufsz * s->mix.bpf);
}
if (s->mode & MODE_RECMASK) {
s->sub.bpf = s->par.bps * s->sub.nch;
abuf_init(&s->sub.buf, s->appbufsz * s->sub.bpf);
}
#ifdef DEBUG
logx(3, "slot%zu: allocated %u/%u fr buffers",
s - slot_array, s->appbufsz, SLOT_BUFSZ(s));
#endif
}
void
slot_freebufs(struct slot *s)
{
if (s->mode & MODE_RECMASK) {
abuf_done(&s->sub.buf);
}
if (s->mode & MODE_PLAY) {
abuf_done(&s->mix.buf);
}
}
struct slot *
slot_new(struct opt *opt, unsigned int id, char *who,
struct slotops *ops, void *arg, int mode)
{
struct app *a;
struct slot *s;
int i;
a = opt_mkapp(opt, who);
if (a == NULL)
return NULL;
for (i = 0, s = slot_array; i < DEV_NSLOT; i++, s++) {
if (s->ops == NULL)
break;
}
if (i == DEV_NSLOT) {
logx(1, "%s: too many connections", a->name);
return NULL;
}
if (!opt_ref(opt))
return NULL;
s->app = a;
s->opt = opt;
s->ops = ops;
s->arg = arg;
s->pstate = SLOT_INIT;
s->mode = mode;
aparams_init(&s->par);
if (s->mode & MODE_PLAY)
s->mix.nch = s->opt->pmax - s->opt->pmin + 1;
if (s->mode & MODE_RECMASK)
s->sub.nch = s->opt->rmax - s->opt->rmin + 1;
s->xrun = s->opt->mtc != NULL ? XRUN_SYNC : XRUN_IGNORE;
s->appbufsz = s->opt->dev->bufsz;
s->round = s->opt->dev->round;
s->rate = s->opt->dev->rate;
#ifdef DEBUG
logx(3, "slot%zu: %s/%s", s - slot_array, s->opt->name, s->app->name);
#endif
return s;
}
void
slot_del(struct slot *s)
{
s->arg = s;
s->ops = &zomb_slotops;
switch (s->pstate) {
case SLOT_INIT:
s->ops = NULL;
break;
case SLOT_START:
case SLOT_READY:
case SLOT_RUN:
case SLOT_STOP:
slot_stop(s, 0);
break;
}
opt_unref(s->opt);
}
void
slot_setvol(struct slot *s, unsigned int vol)
{
struct opt *o = s->opt;
struct app *a = s->app;
#ifdef DEBUG
logx(3, "slot%zu: setting volume %u", s - slot_array, vol);
#endif
if (a->vol != vol) {
opt_appvol(o, a, vol);
opt_midi_vol(o, a);
ctl_onval(CTL_APP_LEVEL, o, a, vol);
}
}
void
slot_attach(struct slot *s)
{
struct dev *d = s->opt->dev;
long long pos;
if (((s->mode & MODE_PLAY) && !(s->opt->mode & MODE_PLAY)) ||
((s->mode & MODE_RECMASK) && !(s->opt->mode & MODE_RECMASK))) {
logx(1, "slot%zu at %s: mode not allowed", s - slot_array, s->opt->name);
return;
}
slot_initconv(s);
dev_wakeup(d);
pos = s->delta_rem +
(long long)s->delta * d->round +
(long long)d->delta * s->round;
s->delta = pos / (int)d->round;
s->delta_rem = pos % d->round;
if (s->delta_rem < 0) {
s->delta_rem += d->round;
s->delta--;
}
#ifdef DEBUG
logx(2, "slot%zu: attached at %d + %d / %d",
s - slot_array, s->delta, s->delta_rem, s->round);
#endif
s->next = d->slot_list;
d->slot_list = s;
if (s->mode & MODE_PLAY) {
s->mix.vol = MIDI_TO_ADATA(s->app->vol);
dev_mix_adjvol(d);
}
}
void
slot_ready(struct slot *s)
{
if (s->opt->dev->pstate == DEV_CFG)
return;
if (s->opt->mtc == NULL) {
slot_attach(s);
s->pstate = SLOT_RUN;
} else
mtc_trigger(s->opt->mtc);
}
void
slot_start(struct slot *s)
{
struct dev *d = s->opt->dev;
#ifdef DEBUG
char enc_str[ENCMAX], chans_str[64];
if (s->pstate != SLOT_INIT) {
logx(0, "slot%zu: slot_start: wrong state", s - slot_array);
panic();
}
logx(2, "slot%zu: %dHz, %s, %s, %d blocks of %d frames",
s - slot_array, s->rate,
(aparams_enctostr(&s->par, enc_str), enc_str),
(chans_fmt(chans_str, sizeof(chans_str), s->mode,
s->opt->pmin, s->opt->pmin + s->mix.nch - 1,
s->opt->rmin, s->opt->rmin + s->sub.nch - 1), chans_str),
s->appbufsz / s->round, s->round);
#endif
slot_allocbufs(s);
if (s->mode & MODE_RECMASK) {
s->sub.prime = d->bufsz / d->round;
}
s->skip = 0;
s->paused = 0;
s->delta = -(long long)d->bufsz * s->round / d->round;
s->delta_rem = 0;
if (s->mode & MODE_PLAY) {
s->pstate = SLOT_START;
} else {
s->pstate = SLOT_READY;
slot_ready(s);
}
}
void
slot_detach(struct slot *s)
{
struct slot **ps;
struct dev *d = s->opt->dev;
long long pos;
for (ps = &d->slot_list; *ps != s; ps = &(*ps)->next) {
#ifdef DEBUG
if (*ps == NULL) {
logx(0, "slot%zu: can't detach, not on list", s - slot_array);
panic();
}
#endif
}
*ps = s->next;
pos = s->delta_rem +
(long long)s->delta * d->round -
(long long)d->delta * s->round;
s->delta = pos / (int)d->round;
s->delta_rem = pos % d->round;
if (s->delta_rem < 0) {
s->delta_rem += d->round;
s->delta--;
}
#ifdef DEBUG
logx(2, "slot%zu: detached at %d + %d / %d",
s - slot_array, s->delta, s->delta_rem, d->round);
#endif
if (s->mode & MODE_PLAY)
dev_mix_adjvol(d);
if (s->mode & MODE_RECMASK) {
if (s->sub.encbuf) {
xfree(s->sub.encbuf);
s->sub.encbuf = NULL;
}
if (s->sub.resampbuf) {
xfree(s->sub.resampbuf);
s->sub.resampbuf = NULL;
}
}
if (s->mode & MODE_PLAY) {
if (s->mix.decbuf) {
xfree(s->mix.decbuf);
s->mix.decbuf = NULL;
}
if (s->mix.resampbuf) {
xfree(s->mix.resampbuf);
s->mix.resampbuf = NULL;
}
}
}
void
slot_stop(struct slot *s, int drain)
{
#ifdef DEBUG
logx(3, "slot%zu: stopping (drain = %d)", s - slot_array, drain);
#endif
if (s->pstate == SLOT_START) {
s->pstate = SLOT_READY;
slot_ready(s);
}
if (s->pstate == SLOT_RUN) {
if ((s->mode & MODE_PLAY) && drain) {
s->pstate = SLOT_STOP;
return;
}
slot_detach(s);
} else if (s->pstate == SLOT_STOP) {
slot_detach(s);
} else {
#ifdef DEBUG
logx(3, "slot%zu: not drained (blocked by mmc)", s - slot_array);
#endif
}
s->pstate = SLOT_INIT;
s->ops->eof(s->arg);
slot_freebufs(s);
}
void
slot_skip_update(struct slot *s)
{
int skip;
skip = slot_skip(s);
while (skip > 0) {
#ifdef DEBUG
logx(4, "slot%zu: catching skipped block", s - slot_array);
#endif
if (s->mode & MODE_RECMASK)
s->ops->flush(s->arg);
if (s->mode & MODE_PLAY)
s->ops->fill(s->arg);
skip--;
}
}
void
slot_write(struct slot *s)
{
if (s->pstate == SLOT_START && s->mix.buf.used == s->mix.buf.len) {
#ifdef DEBUG
logx(4, "slot%zu: switching to READY state", s - slot_array);
#endif
s->pstate = SLOT_READY;
slot_ready(s);
}
slot_skip_update(s);
}
void
slot_read(struct slot *s)
{
slot_skip_update(s);
}
struct ctlslot *
ctlslot_new(struct opt *o, struct ctlops *ops, void *arg)
{
struct ctlslot *s;
struct ctl *c;
int i;
i = 0;
for (;;) {
if (i == DEV_NCTLSLOT)
return NULL;
s = ctlslot_array + i;
if (s->ops == NULL)
break;
i++;
}
s->opt = o;
s->self = 1 << i;
if (!opt_ref(o))
return NULL;
s->ops = ops;
s->arg = arg;
for (c = ctl_list; c != NULL; c = c->next) {
if (!ctlslot_visible(s, c))
continue;
c->refs_mask |= s->self;
}
return s;
}
void
ctlslot_del(struct ctlslot *s)
{
struct ctl *c, **pc;
pc = &ctl_list;
while ((c = *pc) != NULL) {
c->refs_mask &= ~s->self;
if (c->refs_mask == 0) {
*pc = c->next;
xfree(c);
} else
pc = &c->next;
}
s->ops = NULL;
opt_unref(s->opt);
}
int
ctlslot_visible(struct ctlslot *s, struct ctl *c)
{
if (s->opt == NULL)
return 1;
switch (c->scope) {
case CTL_HW:
if (strcmp(c->node0.name, "server") == 0 &&
strcmp(c->func, "device") == 0)
return 0;
case CTL_DEV_MASTER:
return (s->opt->dev == c->u.any.arg0);
case CTL_OPT_DEV:
return (s->opt == c->u.any.arg0);
case CTL_APP_LEVEL:
return (s->opt == c->u.app_level.opt);
default:
return 0;
}
}
struct ctl *
ctlslot_lookup(struct ctlslot *s, int addr)
{
struct ctl *c;
c = ctl_list;
while (1) {
if (c == NULL)
return NULL;
if (c->type != CTL_NONE && c->addr == addr)
break;
c = c->next;
}
if (!ctlslot_visible(s, c))
return NULL;
return c;
}
void
ctlslot_update(struct ctlslot *s)
{
struct ctl *c;
unsigned int refs_mask;
for (c = ctl_list; c != NULL; c = c->next) {
if (c->type == CTL_NONE)
continue;
refs_mask = ctlslot_visible(s, c) ? s->self : 0;
if (((c->refs_mask & s->self) ^ refs_mask) == 0)
continue;
if (refs_mask)
c->refs_mask |= s->self;
c->desc_mask |= s->self;
}
if (s->ops)
s->ops->sync(s->arg);
}
size_t
ctl_node_fmt(char *buf, size_t size, struct ctl_node *c)
{
char *end = buf + size;
char *p = buf;
p += snprintf(buf, size, "%s", c->name);
if (c->unit >= 0)
p += snprintf(p, p < end ? end - p : 0, "%d", c->unit);
return p - buf;
}
size_t
ctl_scope_fmt(char *buf, size_t size, struct ctl *c)
{
switch (c->scope) {
case CTL_HW:
return snprintf(buf, size, "hw:%s/%u",
c->u.hw.dev->name, c->u.hw.addr);
case CTL_DEV_MASTER:
return snprintf(buf, size, "dev_master:%s",
c->u.dev_master.dev->name);
case CTL_APP_LEVEL:
return snprintf(buf, size, "app_level:%s/%s",
c->u.app_level.opt->name, c->u.app_level.app->name);
case CTL_OPT_DEV:
return snprintf(buf, size, "opt_dev:%s/%s",
c->u.opt_dev.opt->name, c->u.opt_dev.dev->name);
default:
return snprintf(buf, size, "unknown");
}
}
size_t
ctl_fmt(char *buf, size_t size, struct ctl *c)
{
char *end = buf + size;
char *p = buf;
p += snprintf(p, size, "%s/", c->group);
p += ctl_node_fmt(p, p < end ? end - p : 0, &c->node0);
p += snprintf(p, p < end ? end - p : 0, ".%s", c->func);
switch (c->type) {
case CTL_VEC:
case CTL_LIST:
case CTL_SEL:
p += snprintf(p, p < end ? end - p : 0, "[");
p += ctl_node_fmt(p, p < end ? end - p : 0, &c->node1);
p += snprintf(p, p < end ? end - p : 0, "]");
}
if (c->display[0] != 0)
p += snprintf(p, size, " (%s)", c->display);
return p - buf;
}
int
ctl_setval(struct ctl *c, int val)
{
if (c->curval == val) {
logx(3, "ctl%u: already set", c->addr);
return 1;
}
if (val < 0 || val > c->maxval) {
logx(3, "ctl%u: %d: out of range", c->addr, val);
return 0;
}
switch (c->scope) {
case CTL_HW:
logx(3, "ctl%u: marked as dirty", c->addr);
c->curval = val;
c->dirty = 1;
return dev_ref(c->u.hw.dev);
case CTL_DEV_MASTER:
if (!c->u.dev_master.dev->master_enabled)
return 1;
dev_master(c->u.dev_master.dev, val);
dev_midi_master(c->u.dev_master.dev);
c->val_mask = ~0U;
c->curval = val;
return 1;
case CTL_APP_LEVEL:
opt_appvol(c->u.app_level.opt, c->u.app_level.app, val);
opt_midi_vol(c->u.app_level.opt, c->u.app_level.app);
c->val_mask = ~0U;
c->curval = val;
return 1;
case CTL_OPT_DEV:
if (opt_setdev(c->u.opt_dev.opt, c->u.opt_dev.dev)) {
opt_setalt(c->u.opt_dev.opt, c->u.opt_dev.dev);
}
return 1;
default:
logx(2, "ctl%u: not writable", c->addr);
return 1;
}
}
struct ctl *
ctl_new(int scope, void *arg0, void *arg1,
int type, char *display, char *gstr,
char *str0, int unit0, char *func,
char *str1, int unit1, int maxval, int val)
{
#ifdef DEBUG
char ctl_str[64], scope_str[32];
#endif
struct ctl *c, **pc;
struct ctlslot *s;
int addr;
int i;
addr = 0;
for (pc = &ctl_list; (c = *pc) != NULL; pc = &c->next) {
if (c->addr > addr)
addr = c->addr;
}
addr++;
c = xmalloc(sizeof(struct ctl));
c->type = type;
strlcpy(c->func, func, CTL_NAMEMAX);
strlcpy(c->group, gstr, CTL_NAMEMAX);
strlcpy(c->display, display, CTL_DISPLAYMAX);
strlcpy(c->node0.name, str0, CTL_NAMEMAX);
c->node0.unit = unit0;
if (c->type == CTL_VEC || c->type == CTL_LIST || c->type == CTL_SEL) {
strlcpy(c->node1.name, str1, CTL_NAMEMAX);
c->node1.unit = unit1;
} else
memset(&c->node1, 0, sizeof(struct ctl_node));
c->scope = scope;
c->u.any.arg0 = arg0;
switch (scope) {
case CTL_HW:
c->u.hw.addr = *(unsigned int *)arg1;
break;
case CTL_OPT_DEV:
case CTL_APP_LEVEL:
c->u.any.arg1 = arg1;
break;
default:
c->u.any.arg1 = NULL;
}
c->addr = addr;
c->maxval = maxval;
c->val_mask = ~0;
c->desc_mask = ~0;
c->curval = val;
c->dirty = 0;
c->refs_mask = CTL_DEVMASK;
for (s = ctlslot_array, i = 0; i < DEV_NCTLSLOT; i++, s++) {
if (s->ops == NULL)
continue;
if (ctlslot_visible(s, c))
c->refs_mask |= 1 << i;
}
c->next = *pc;
*pc = c;
#ifdef DEBUG
logx(2, "ctl%u: %s = %d at %s: added", c->addr,
(ctl_fmt(ctl_str, sizeof(ctl_str), c), ctl_str), c->curval,
(ctl_scope_fmt(scope_str, sizeof(scope_str), c), scope_str));
#endif
return c;
}
void
ctl_update(struct ctl *c)
{
struct ctlslot *s;
unsigned int refs_mask;
int i;
for (s = ctlslot_array, i = 0; i < DEV_NCTLSLOT; i++, s++) {
if (s->ops == NULL)
continue;
refs_mask = ctlslot_visible(s, c) ? s->self : 0;
if (((c->refs_mask & s->self) ^ refs_mask) == 0)
continue;
if (refs_mask)
c->refs_mask |= s->self;
c->desc_mask |= s->self;
s->ops->sync(s->arg);
}
}
int
ctl_match(struct ctl *c, int scope, void *arg0, void *arg1)
{
if (c->type == CTL_NONE || c->scope != scope)
return 0;
if (arg0 != NULL && c->u.any.arg0 != arg0)
return 0;
switch (scope) {
case CTL_HW:
if (arg1 != NULL && c->u.hw.addr != *(unsigned int *)arg1)
return 0;
break;
case CTL_OPT_DEV:
case CTL_APP_LEVEL:
if (arg1 != NULL && c->u.any.arg1 != arg1)
return 0;
break;
}
return 1;
}
struct ctl *
ctl_find(int scope, void *arg0, void *arg1)
{
struct ctl *c;
for (c = ctl_list; c != NULL; c = c->next) {
if (ctl_match(c, scope, arg0, arg1))
return c;
}
return NULL;
}
int
ctl_onval(int scope, void *arg0, void *arg1, int val)
{
struct ctl *c;
c = ctl_find(scope, arg0, arg1);
if (c == NULL)
return 0;
c->curval = val;
c->val_mask = ~0U;
return 1;
}
int
ctl_del(int scope, void *arg0, void *arg1)
{
#ifdef DEBUG
char str[64];
#endif
struct ctl *c, **pc;
int found;
found = 0;
pc = &ctl_list;
for (;;) {
c = *pc;
if (c == NULL)
return found;
if (ctl_match(c, scope, arg0, arg1)) {
#ifdef DEBUG
logx(2, "ctl%u: %s: removed", c->addr,
(ctl_fmt(str, sizeof(str), c), str));
#endif
found++;
c->refs_mask &= ~CTL_DEVMASK;
if (c->refs_mask == 0) {
*pc = c->next;
xfree(c);
continue;
}
c->type = CTL_NONE;
c->desc_mask = ~0;
}
pc = &c->next;
}
}
char *
dev_getdisplay(struct dev *d)
{
struct ctl *c;
char *display;
display = "";
for (c = ctl_list; c != NULL; c = c->next) {
if (c->scope == CTL_HW &&
c->u.hw.dev == d &&
c->type == CTL_SEL &&
strcmp(c->group, d->name) == 0 &&
strcmp(c->node0.name, "server") == 0 &&
strcmp(c->func, "device") == 0 &&
c->curval == 1)
display = c->display;
}
return display;
}
void
dev_ctlsync(struct dev *d)
{
struct ctl *c;
struct ctlslot *s;
const char *display;
int found, i;
found = 0;
for (c = ctl_list; c != NULL; c = c->next) {
if (c->scope == CTL_HW &&
c->u.hw.dev == d &&
c->type == CTL_NUM &&
strcmp(c->group, d->name) == 0 &&
strcmp(c->node0.name, "output") == 0 &&
strcmp(c->func, "level") == 0)
found = 1;
}
if (d->master_enabled && found) {
logx(2, "%s: software master level control disabled", d->path);
d->master_enabled = 0;
ctl_del(CTL_DEV_MASTER, d, NULL);
} else if (!d->master_enabled && !found) {
logx(2, "%s: software master level control enabled", d->path);
d->master_enabled = 1;
ctl_new(CTL_DEV_MASTER, d, NULL,
CTL_NUM, "", d->name, "output", -1, "level",
NULL, -1, 127, d->master);
}
display = dev_getdisplay(d);
for (c = ctl_list; c != NULL; c = c->next) {
if (c->scope != CTL_OPT_DEV ||
c->u.opt_dev.dev != d ||
strcmp(c->display, display) == 0)
continue;
strlcpy(c->display, display, CTL_DISPLAYMAX);
c->desc_mask = ~0;
}
for (s = ctlslot_array, i = 0; i < DEV_NCTLSLOT; i++, s++) {
if (s->ops == NULL)
continue;
if (s->opt->dev == d)
s->ops->sync(s->arg);
}
}