sndio/sndiod/dev.c

1924 lines
36 KiB
C

/* $OpenBSD$ */
/*
* Copyright (c) 2008 Alexandre Ratchov <alex@caoua.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "bsd-compat.h"
#include "abuf.h"
#include "defs.h"
#include "dev.h"
#include "dsp.h"
#include "miofile.h"
#include "siofile.h"
#include "midi.h"
#include "opt.h"
#include "sysex.h"
#include "utils.h"
int dev_open(struct dev *);
void dev_close(struct dev *);
void dev_clear(struct dev *);
void dev_master(struct dev *, unsigned int);
void slot_attach(struct slot *);
void slot_ready(struct slot *);
void slot_mix_drop(struct slot *);
void slot_sub_sil(struct slot *);
void zomb_onmove(void *, int);
void zomb_onvol(void *, unsigned int);
void zomb_fill(void *);
void zomb_flush(void *);
void zomb_eof(void *);
void zomb_mmcstart(void *);
void zomb_mmcstop(void *);
void zomb_mmcloc(void *, unsigned int);
void zomb_exit(void *);
void dev_midi_imsg(void *, unsigned char *, int);
void dev_midi_omsg(void *, unsigned char *, int);
void dev_midi_exit(void *);
struct midiops dev_midiops = {
dev_midi_imsg,
dev_midi_omsg,
dev_midi_exit
};
struct slotops zomb_slotops = {
zomb_onmove,
zomb_onvol,
zomb_fill,
zomb_flush,
zomb_eof,
zomb_mmcstart,
zomb_mmcstop,
zomb_mmcloc,
zomb_exit
};
struct dev *dev_list = NULL;
unsigned int dev_sndnum = 0;
void
dev_log(struct dev *d)
{
log_puts("snd");
log_putu(d->num);
}
void
slot_log(struct slot *s)
{
#ifdef DEBUG
static char *pstates[] = {
"ini", "sta", "rdy", "run", "stp", "mid"
};
static char *tstates[] = {
"off", "sta", "run", "stp"
};
#endif
log_puts(s->name);
log_putu(s->unit);
#ifdef DEBUG
if (log_level >= 3) {
log_puts(",vol=");
log_putu(s->vol);
log_puts(",pst=");
log_puts(pstates[s->pstate]);
log_puts(",mmc=");
log_puts(tstates[s->tstate]);
}
#endif
}
void
zomb_onmove(void *arg, int delta)
{
}
void
zomb_onvol(void *arg, unsigned int vol)
{
}
void
zomb_fill(void *arg)
{
}
void
zomb_flush(void *arg)
{
}
void
zomb_eof(void *arg)
{
struct slot *s = arg;
#ifdef DEBUG
if (log_level >= 3) {
slot_log(s);
log_puts(": zomb_eof\n");
}
#endif
s->ops = NULL;
}
void
zomb_mmcstart(void *arg)
{
}
void
zomb_mmcstop(void *arg)
{
}
void
zomb_mmcloc(void *arg, unsigned int pos)
{
}
void
zomb_exit(void *arg)
{
struct slot *s = arg;
#ifdef DEBUG
if (log_level >= 3) {
slot_log(s);
log_puts(": zomb_exit\n");
}
#endif
}
/*
* send a quarter frame MTC message
*/
void
dev_midi_qfr(struct dev *d, int delta)
{
unsigned char buf[2];
unsigned int data;
int qfrlen;
d->mtc.delta += delta * MTC_SEC;
qfrlen = d->rate * (MTC_SEC / (4 * d->mtc.fps));
while (d->mtc.delta >= qfrlen) {
switch (d->mtc.qfr) {
case 0:
data = d->mtc.fr & 0xf;
break;
case 1:
data = d->mtc.fr >> 4;
break;
case 2:
data = d->mtc.sec & 0xf;
break;
case 3:
data = d->mtc.sec >> 4;
break;
case 4:
data = d->mtc.min & 0xf;
break;
case 5:
data = d->mtc.min >> 4;
break;
case 6:
data = d->mtc.hr & 0xf;
break;
case 7:
data = (d->mtc.hr >> 4) | (d->mtc.fps_id << 1);
/*
* tick messages are sent 2 frames ahead
*/
d->mtc.fr += 2;
if (d->mtc.fr < d->mtc.fps)
break;
d->mtc.fr -= d->mtc.fps;
d->mtc.sec++;
if (d->mtc.sec < 60)
break;
d->mtc.sec = 0;
d->mtc.min++;
if (d->mtc.min < 60)
break;
d->mtc.min = 0;
d->mtc.hr++;
if (d->mtc.hr < 24)
break;
d->mtc.hr = 0;
break;
default:
/* NOTREACHED */
data = 0;
}
buf[0] = 0xf1;
buf[1] = (d->mtc.qfr << 4) | data;
d->mtc.qfr++;
d->mtc.qfr &= 7;
midi_send(d->midi, buf, 2);
d->mtc.delta -= qfrlen;
}
}
/*
* send a full frame MTC message
*/
void
dev_midi_full(struct dev *d)
{
struct sysex x;
unsigned int fps;
d->mtc.delta = MTC_SEC * dev_getpos(d);
if (d->rate % (30 * 4 * d->round) == 0) {
d->mtc.fps_id = MTC_FPS_30;
d->mtc.fps = 30;
} else if (d->rate % (25 * 4 * d->round) == 0) {
d->mtc.fps_id = MTC_FPS_25;
d->mtc.fps = 25;
} else {
d->mtc.fps_id = MTC_FPS_24;
d->mtc.fps = 24;
}
#ifdef DEBUG
if (log_level >= 3) {
dev_log(d);
log_puts(": mtc full frame at ");
log_puti(d->mtc.delta);
log_puts(", ");
log_puti(d->mtc.fps);
log_puts(" fps\n");
}
#endif
fps = d->mtc.fps;
d->mtc.hr = (d->mtc.origin / (MTC_SEC * 3600)) % 24;
d->mtc.min = (d->mtc.origin / (MTC_SEC * 60)) % 60;
d->mtc.sec = (d->mtc.origin / (MTC_SEC)) % 60;
d->mtc.fr = (d->mtc.origin / (MTC_SEC / fps)) % fps;
x.start = SYSEX_START;
x.type = SYSEX_TYPE_RT;
x.dev = 0x7f;
x.id0 = SYSEX_MTC;
x.id1 = SYSEX_MTC_FULL;
x.u.full.hr = d->mtc.hr | (d->mtc.fps_id << 5);
x.u.full.min = d->mtc.min;
x.u.full.sec = d->mtc.sec;
x.u.full.fr = d->mtc.fr;
x.u.full.end = SYSEX_END;
d->mtc.qfr = 0;
midi_send(d->midi, (unsigned char *)&x, SYSEX_SIZE(full));
}
void
dev_midi_vol(struct dev *d, struct slot *s)
{
unsigned char msg[3];
msg[0] = MIDI_CTL | (s - d->slot);
msg[1] = MIDI_CTL_VOL;
msg[2] = s->vol;
midi_send(d->midi, msg, 3);
}
void
dev_midi_master(struct dev *d)
{
struct sysex x;
memset(&x, 0, sizeof(struct sysex));
x.start = SYSEX_START;
x.type = SYSEX_TYPE_RT;
x.id0 = SYSEX_CONTROL;
x.id1 = SYSEX_MASTER;
x.u.master.fine = 0;
x.u.master.coarse = d->master;
x.u.master.end = SYSEX_END;
midi_send(d->midi, (unsigned char *)&x, SYSEX_SIZE(master));
}
void
dev_midi_mixinfo(struct dev *d, struct slot *s)
{
struct sysex x;
memset(&x, 0, sizeof(struct sysex));
x.start = SYSEX_START;
x.type = SYSEX_TYPE_EDU;
x.id0 = SYSEX_AUCAT;
x.id1 = SYSEX_AUCAT_MIXINFO;
if (*s->name != '\0') {
snprintf((char *)x.u.mixinfo.name, SYSEX_NAMELEN,
"%s%u", s->name, s->unit);
}
x.u.mixinfo.chan = s - d->slot;
x.u.mixinfo.end = SYSEX_END;
midi_send(d->midi, (unsigned char *)&x, SYSEX_SIZE(mixinfo));
}
void
dev_midi_dump(struct dev *d)
{
struct sysex x;
struct slot *s;
int i;
dev_midi_master(d);
for (i = 0, s = d->slot; i < DEV_NSLOT; i++, s++) {
dev_midi_mixinfo(d, s);
dev_midi_vol(d, s);
}
x.start = SYSEX_START;
x.type = SYSEX_TYPE_EDU;
x.dev = 0;
x.id0 = SYSEX_AUCAT;
x.id1 = SYSEX_AUCAT_DUMPEND;
x.u.dumpend.end = SYSEX_END;
midi_send(d->midi, (unsigned char *)&x, SYSEX_SIZE(dumpend));
}
void
dev_midi_imsg(void *arg, unsigned char *msg, int len)
{
struct dev *d = arg;
#ifdef DEBUG
dev_log(d);
log_puts(": can't receive midi messages\n");
panic();
#endif
}
void
dev_midi_omsg(void *arg, unsigned char *msg, int len)
{
struct dev *d = arg;
struct sysex *x;
unsigned int fps, chan;
#ifdef DEBUG
unsigned int i;
if (log_level >= 3) {
dev_log(d);
log_puts(": got msg:");
for (i = 0; i < len; i++) {
log_puts(" ");
log_putx(msg[i]);
}
log_puts("\n");
}
#endif
if ((msg[0] & MIDI_CMDMASK) == MIDI_CTL && msg[1] == MIDI_CTL_VOL) {
chan = msg[0] & MIDI_CHANMASK;
if (chan >= DEV_NSLOT)
return;
slot_setvol(d->slot + chan, msg[2]);
return;
}
x = (struct sysex *)msg;
if (x->start != SYSEX_START)
return;
if (len < SYSEX_SIZE(empty))
return;
switch (x->type) {
case SYSEX_TYPE_RT:
if (x->id0 == SYSEX_CONTROL && x->id1 == SYSEX_MASTER) {
if (len == SYSEX_SIZE(master))
dev_master(d, x->u.master.coarse);
return;
}
if (x->id0 != SYSEX_MMC)
return;
switch (x->id1) {
case SYSEX_MMC_STOP:
if (len != SYSEX_SIZE(stop))
return;
if (log_level >= 2) {
dev_log(d);
log_puts(": mmc stop\n");
}
dev_mmcstop(d);
break;
case SYSEX_MMC_START:
if (len != SYSEX_SIZE(start))
return;
if (log_level >= 2) {
dev_log(d);
log_puts(": mmc start\n");
}
dev_mmcstart(d);
break;
case SYSEX_MMC_LOC:
if (len != SYSEX_SIZE(loc) ||
x->u.loc.len != SYSEX_MMC_LOC_LEN ||
x->u.loc.cmd != SYSEX_MMC_LOC_CMD)
return;
switch (x->u.loc.hr >> 5) {
case MTC_FPS_24:
fps = 24;
break;
case MTC_FPS_25:
fps = 25;
break;
case MTC_FPS_30:
fps = 30;
break;
default:
dev_mmcstop(d);
return;
}
dev_mmcloc(d,
(x->u.loc.hr & 0x1f) * 3600 * MTC_SEC +
x->u.loc.min * 60 * MTC_SEC +
x->u.loc.sec * MTC_SEC +
x->u.loc.fr * (MTC_SEC / fps) +
x->u.loc.cent * (MTC_SEC / 100 / fps));
break;
}
break;
case SYSEX_TYPE_EDU:
if (x->id0 != SYSEX_AUCAT || x->id1 != SYSEX_AUCAT_DUMPREQ)
return;
if (len != SYSEX_SIZE(dumpreq))
return;
dev_midi_dump(d);
break;
}
}
void
dev_midi_exit(void *arg)
{
struct dev *d = arg;
if (log_level >= 1) {
dev_log(d);
log_puts(": midi end point died\n");
}
dev_close(d);
}
void
slot_mix_drop(struct slot *s)
{
while (s->mix.drop > 0 && s->mix.buf.used >= s->round) {
#ifdef DEBUG
if (log_level >= 4) {
slot_log(s);
log_puts(": dropped a play block\n");
}
#endif
abuf_rdiscard(&s->mix.buf, s->round);
s->mix.drop--;
}
}
void
slot_sub_sil(struct slot *s)
{
unsigned char *data;
int count;
while (s->sub.silence > 0) {
data = abuf_wgetblk(&s->sub.buf, &count);
if (count < s->round)
break;
#ifdef DEBUG
if (log_level >= 4) {
slot_log(s);
log_puts(": inserted a rec block of silence\n");
}
#endif
if (s->sub.encbuf)
enc_sil_do(&s->sub.enc, data, s->round);
else
memset(data, 0, s->round * s->sub.buf.bpf);
abuf_wcommit(&s->sub.buf, s->round);
s->sub.silence--;
}
}
/*
* merge play buffer contents into record buffer as if the
* play stream was recorded
*/
void
dev_mon_snoop(struct dev *d)
{
}
int
play_filt_resamp(struct slot *s, void *res_in, void *out, int todo)
{
int i, offs, vol, nch;
void *in;
if (s->mix.resampbuf) {
todo = resamp_do(&s->mix.resamp,
res_in, s->mix.resampbuf, todo);
in = s->mix.resampbuf;
} else
in = res_in;
nch = s->mix.slot_cmax - s->mix.slot_cmin + 1;
vol = ADATA_MUL(s->mix.weight, s->mix.vol) / s->mix.join;
cmap_add(&s->mix.cmap, in, out, vol, todo);
offs = 0;
for (i = s->mix.join - 1; i > 0; i--) {
offs += nch;
if (offs > s->mix.cmap.inext)
break;
cmap_add(&s->mix.cmap, (adata_t *)in + offs, out, vol, todo);
}
offs = 0;
for (i = s->mix.expand - 1; i > 0; i--) {
offs += nch;
if (offs > s->mix.cmap.onext)
break;
cmap_add(&s->mix.cmap, in, (adata_t *)out + offs, vol, todo);
}
return todo;
}
int
play_filt_dec(struct slot *s, void *in, void *out, int todo)
{
void *tmp;
tmp = s->mix.decbuf;
if (tmp)
dec_do(&s->mix.dec, in, tmp, todo);
return play_filt_resamp(s, tmp ? tmp : in, out, todo);
}
/*
* mix "todo" frames from the input block over the output block; if
* there are frames to drop, less frames are consumed from the input
*/
void
dev_mix_badd(struct dev *d, struct slot *s)
{
adata_t *idata, *odata;
int icount;
#ifdef DEBUG
if (log_level >= 4) {
slot_log(s);
log_puts(": dev_mix_badd: drop = ");
log_puti(s->mix.drop);
log_puts("\n");
}
#endif
odata = DEV_PBUF(d);
idata = (adata_t *)abuf_rgetblk(&s->mix.buf, &icount);
#ifdef DEBUG
if (icount < s->round && s->pstate != SLOT_STOP) {
slot_log(s);
log_puts(": not enough data to mix (");
log_putu(icount);
log_puts(" of ");
log_putu(d->round);
log_puts(")\n");
}
#endif
if (icount > s->round)
icount = s->round;
play_filt_dec(s, idata, odata, icount);
abuf_rdiscard(&s->mix.buf, icount);
}
void
dev_mix_cycle(struct dev *d)
{
struct slot *s, **ps;
unsigned char *base;
int nsamp;
#ifdef DEBUG
if (log_level >= 4) {
dev_log(d);
log_puts(": dev_mix_cycle, poffs = ");
log_puti(d->poffs);
log_puts("\n");
}
#endif
base = (unsigned char *)DEV_PBUF(d);
nsamp = d->round * d->pchan;
memset(base, 0, nsamp * sizeof(adata_t));
ps = &d->slot_list;
while ((s = *ps) != NULL) {
if (!(s->mode & MODE_PLAY)) {
ps = &s->next;
continue;
}
#ifdef DEBUG
s->delay -= s->round;
if (log_level >= 4) {
slot_log(s);
log_puts(": mixing, drop = ");
log_puti(s->mix.drop);
log_puts(" cycles\n");
}
#endif
slot_mix_drop(s);
if (s->mix.drop < 0) {
s->mix.drop++;
ps = &s->next;
continue;
}
if (s->pstate == SLOT_STOP && s->mix.buf.used == 0) {
s->pstate = SLOT_INIT;
abuf_done(&s->mix.buf);
s->ops->eof(s->arg);
*ps = s->next;
continue;
}
if (s->mix.buf.used < s->round && !(s->pstate == SLOT_STOP)) {
if (s->xrun == XRUN_IGNORE) {
if (s->mode & MODE_RECMASK)
s->sub.silence--;
s->delta -= s->round;
#ifdef DEBUG
if (log_level >= 3) {
slot_log(s);
log_puts(": underrun, pause cycle\n");
}
#endif
ps = &s->next;
continue;
}
if (s->xrun == XRUN_SYNC) {
s->mix.drop++;
ps = &s->next;
continue;
}
if (s->xrun == XRUN_ERROR) {
s->ops->exit(s->arg);
*ps = s->next;
continue;
}
} else {
dev_mix_badd(d, s);
s->ops->fill(s->arg);
}
ps = &s->next;
}
if (d->encbuf) {
enc_do(&d->enc, (unsigned char *)DEV_PBUF(d),
d->encbuf, d->round);
}
}
/*
* Normalize input levels.
*/
void
dev_mix_setmaster(struct dev *d)
{
unsigned int n;
struct slot *i, *j;
int weight;
for (i = d->slot_list; i != NULL; i = i->next) {
if (!(i->mode & MODE_PLAY))
continue;
weight = ADATA_UNIT;
if (d->autovol) {
/*
* count the number of inputs that have
* overlapping channel sets
*/
n = 0;
for (j = d->slot_list; j != NULL; j = j->next) {
if (!(j->mode & MODE_PLAY))
continue;
if (i->mix.slot_cmin <= j->mix.slot_cmax &&
i->mix.slot_cmax >= j->mix.slot_cmin)
n++;
}
}
if (weight > i->mix.maxweight)
weight = i->mix.maxweight;
i->mix.weight = ADATA_MUL(weight, MIDI_TO_ADATA(d->master));
#ifdef DEBUG
if (log_level >= 3) {
slot_log(i);
log_puts(": set weight: ");
log_puti(i->mix.weight);
log_puts("/");
log_puti(i->mix.maxweight);
log_puts("\n");
}
#endif
}
}
int
rec_filt_resamp(struct slot *s, void *in, void *res_out, int todo)
{
int i, vol, offs, nch;
void *out = res_out;
out = (s->sub.resampbuf) ? s->sub.resampbuf : res_out;
nch = s->sub.slot_cmax - s->sub.slot_cmin + 1;
vol = ADATA_UNIT / s->sub.join;
cmap_copy(&s->sub.cmap, in, out, vol, todo);
offs = 0;
for (i = s->sub.join - 1; i > 0; i--) {
offs += nch;
cmap_add(&s->sub.cmap, (adata_t *)in + offs, out, vol, todo);
}
offs = 0;
for (i = s->sub.expand - 1; i > 0; i--) {
offs += nch;
cmap_copy(&s->sub.cmap, in, (adata_t *)out + offs, vol, todo);
}
if (s->sub.resampbuf) {
todo = resamp_do(&s->sub.resamp,
s->sub.resampbuf, res_out, todo);
}
return todo;
}
int
rec_filt_enc(struct slot *s, void *in, void *out, int todo)
{
void *tmp;
tmp = s->sub.encbuf;
todo = rec_filt_resamp(s, in, tmp ? tmp : out, todo);
if (tmp)
enc_do(&s->sub.enc, tmp, out, todo);
return todo;
}
/*
* Copy data from slot to device
*/
void
dev_sub_bcopy(struct dev *d, struct slot *s)
{
adata_t *idata, *odata;
int ocount;
#ifdef DEBUG
if (log_level >= 4) {
slot_log(s);
log_puts(": dev_sub_bcopy: silence = ");
log_puti(s->sub.silence);
log_puts("\n");
}
#endif
idata = (s->mode & MODE_MON) ? DEV_PBUF(d) : d->rbuf;
odata = (adata_t *)abuf_wgetblk(&s->sub.buf, &ocount);
#ifdef DEBUG
if (ocount < s->round) {
log_puts("dev_sub_bcopy: not enough space\n");
panic();
}
#endif
ocount = rec_filt_enc(s, idata, odata, d->round);
abuf_wcommit(&s->sub.buf, ocount);
}
void
dev_sub_cycle(struct dev *d)
{
struct slot *s, **ps;
#ifdef DEBUG
if (log_level >= 4) {
dev_log(d);
log_puts(": dev_sub_cycle\n");
}
#endif
if (d->decbuf)
dec_do(&d->dec, d->decbuf, (unsigned char *)d->rbuf, d->round);
ps = &d->slot_list;
while ((s = *ps) != NULL) {
if (!(s->mode & MODE_RECMASK) || s->pstate == SLOT_STOP) {
ps = &s->next;
continue;
}
slot_sub_sil(s);
if (s->sub.silence < 0) {
s->sub.silence++;
ps = &s->next;
continue;
}
if (s->sub.buf.len - s->sub.buf.used < s->round) {
if (s->xrun == XRUN_IGNORE) {
if (s->mode & MODE_PLAY)
s->mix.drop--;
s->delta -= s->round;
#ifdef DEBUG
if (log_level >= 3) {
slot_log(s);
log_puts(": overrun, pause cycle\n");
}
#endif
ps = &s->next;
continue;
}
if (s->xrun == XRUN_SYNC) {
s->sub.silence++;
ps = &s->next;
continue;
}
if (s->xrun == XRUN_ERROR) {
s->ops->exit(s->arg);
*ps = s->next;
continue;
}
} else {
dev_sub_bcopy(d, s);
s->ops->flush(s->arg);
}
ps = &s->next;
}
}
/*
* called at every clock tick by the device
*/
void
dev_onmove(struct dev *d, int delta)
{
long long pos;
struct slot *s, *snext;
/*
* s->ops->onmove() may remove the slot
*/
for (s = d->slot_list; s != NULL; s = snext) {
snext = s->next;
pos = (long long)delta * s->round + s->delta_rem;
s->delta_rem = pos % d->round;
s->delta += pos / (int)d->round;
#ifdef DEBUG
s->delay += pos / (int)d->round;
#endif
if (s->delta >= 0)
s->ops->onmove(s->arg, delta);
}
if (d->tstate == MMC_RUN)
dev_midi_qfr(d, delta);
}
void
dev_master(struct dev *d, unsigned int master)
{
if (log_level >= 2) {
dev_log(d);
log_puts(": master volume set to ");
log_putu(master);
log_puts("\n");
}
d->master = master;
if (d->mode & MODE_PLAY)
dev_mix_setmaster(d);
}
void
dev_cycle(struct dev *d)
{
if (d->slot_list == NULL && d->tstate != MMC_RUN) {
if (log_level >= 2) {
dev_log(d);
log_puts(": device stopped\n");
}
if (d->sio)
siofile_stop(d->sio);
d->pstate = DEV_INIT;
if (d->refcnt == 0)
dev_close(d);
else
dev_clear(d);
return;
}
#ifdef DEBUG
if (log_level >= 4) {
dev_log(d);
log_puts(": device cycle, prime = ");
log_putu(d->prime);
log_puts("\n");
}
#endif
if (d->prime > 0) {
d->prime -= d->round;
dev_mix_cycle(d);
} else {
if (d->mode & MODE_RECMASK)
dev_sub_cycle(d);
if (d->mode & MODE_PLAY)
dev_mix_cycle(d);
}
}
/*
* return the latency that a stream would have if it's attached
*/
int
dev_getpos(struct dev *d)
{
return (d->mode & MODE_PLAY) ? -(d->bufsz - d->prime) : 0;
}
/*
* Create a sndio device
*/
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;
unsigned int i;
if (dev_sndnum == DEV_NMAX) {
if (log_level >= 1)
log_puts("too many devices\n");
return NULL;
}
d = xmalloc(sizeof(struct dev));
d->num = dev_sndnum++;
d->midi = midi_new(&dev_midiops, d, MODE_MIDIIN | MODE_MIDIOUT);
midi_tag(d->midi, d->num);
d->path = path;
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->autostart = 0;
d->refcnt = 0;
d->pstate = DEV_CFG;
d->serial = 0;
for (i = 0; i < DEV_NSLOT; i++) {
d->slot[i].unit = i;
d->slot[i].ops = NULL;
d->slot[i].vol = MIDI_MAXCTL;
d->slot[i].tstate = MMC_OFF;
d->slot[i].serial = d->serial++;
d->slot[i].name[0] = '\0';
}
d->slot_list = NULL;
d->master = MIDI_MAXCTL;
d->mtc.origin = 0;
d->tstate = MMC_STOP;
d->next = dev_list;
dev_list = d;
return d;
}
/*
* adjust device parameters and mode
*/
void
dev_adjpar(struct dev *d, int mode,
int pmin, int pmax, int rmin, 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;
}
}
/*
* Open the device with the dev_reqxxx capabilities. Setup a mixer, demuxer,
* monitor, midi control, and any necessary conversions.
*/
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;
d->sio = siofile_new(d);
if (d->sio == NULL) {
if (log_level >= 1) {
dev_log(d);
log_puts(": ");
log_puts(d->path);
log_puts(": failed to open audio device\n");
}
return 0;
}
if (d->mode & MODE_REC) {
/*
* Create device <-> demuxer buffer
*/
d->rbuf = xmalloc(d->round * d->rchan * sizeof(adata_t));
/*
* Insert a converter, if needed.
*/
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;
}
if (d->mode & MODE_PLAY) {
/*
* Create device <-> mixer buffer
*/
d->pbuf = xmalloc(d->bufsz * d->pchan * sizeof(adata_t));
d->poffs = 0;
d->mode |= MODE_MON;
/*
* Append a converter, if needed.
*/
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;
}
d->pstate = DEV_INIT;
if (log_level >= 2) {
dev_log(d);
log_puts(": ");
log_putu(d->rate);
log_puts("Hz, ");
aparams_log(&d->par);
if (d->mode & MODE_PLAY) {
log_puts(", play 0:");
log_puti(d->pchan - 1);
}
if (d->mode & MODE_REC) {
log_puts(", rec 0:");
log_puti(d->rchan - 1);
}
log_puts(", ");
log_putu(d->bufsz / d->round);
log_puts(" blocks of ");
log_putu(d->round);
log_puts(" frames\n");
}
return 1;
}
/*
* force the device to go in DEV_CFG state, the caller is supposed to
* ensure buffers are drained
*/
void
dev_close(struct dev *d)
{
struct slot *s, *snext;
#ifdef DEBUG
if (log_level >= 3) {
dev_log(d);
log_puts(": closing\n");
}
#endif
while ((s = d->slot_list) != NULL) {
snext = s->next;
if (s->ops)
s->ops->exit(s->arg);
s->ops = NULL;
d->slot_list = snext;
}
if (d->sio) {
siofile_del(d->sio);
d->sio = NULL;
}
dev_clear(d);
d->pstate = DEV_CFG;
}
int
dev_ref(struct dev *d)
{
#ifdef DEBUG
if (log_level >= 3) {
dev_log(d);
log_puts(": device requested\n");
}
#endif
if (d->pstate == DEV_CFG && !dev_open(d))
return 0;
d->refcnt++;
return 1;
}
void
dev_unref(struct dev *d)
{
#ifdef DEBUG
if (log_level >= 3) {
dev_log(d);
log_puts(": device released\n");
}
#endif
d->refcnt--;
if (d->refcnt == 0 && d->pstate == DEV_INIT)
dev_close(d);
}
/*
* initialize the device with the current parameters
*/
int
dev_init(struct dev *d)
{
if ((d->reqmode & MODE_AUDIOMASK) == 0) {
#ifdef DEBUG
dev_log(d);
log_puts(": has no streams\n");
#endif
return 0;
}
if (d->hold && !dev_ref(d))
return 0;
return 1;
}
/*
* Unless the device is already in process of closing, request it to close
*/
void
dev_done(struct dev *d)
{
#ifdef DEBUG
if (log_level >= 3) {
dev_log(d);
log_puts(": draining\n");
}
#endif
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 (num-- == 0)
return d;
}
return NULL;
}
/*
* Free the device
*/
void
dev_del(struct dev *d)
{
struct dev **p;
#ifdef DEBUG
if (log_level >= 3) {
dev_log(d);
log_puts(": deleting\n");
}
#endif
dev_close(d);
for (p = &dev_list; *p != d; p = &(*p)->next) {
#ifdef DEBUG
if (*p == NULL) {
dev_log(d);
log_puts(": device to delete not on the list\n");
panic();
}
#endif
}
*p = d->next;
free(d);
}
unsigned int
dev_roundof(struct dev *d, unsigned int newrate)
{
return (d->round * newrate + d->rate / 2) / d->rate;
}
/*
* If the device is paused, then resume it.
*/
void
dev_wakeup(struct dev *d)
{
if (d->pstate == DEV_INIT) {
#ifdef DEBUG
if (d->mode & MODE_PLAY)
memrnd(d->pbuf, d->bufsz * d->pchan * sizeof(adata_t));
if (d->mode & MODE_REC)
memrnd(d->rbuf, d->round * d->rchan * sizeof(adata_t));
#endif
if (log_level >= 2) {
dev_log(d);
log_puts(": device started\n");
}
if (d->mode & MODE_PLAY) {
d->prime = d->bufsz;
} else {
d->prime = 0;
}
d->pstate = DEV_RUN;
if (d->sio)
siofile_start(d->sio);
}
}
/*
* Clear buffers of the play and record chains so that when the device
* is started, playback and record start in sync.
*/
void
dev_clear(struct dev *d)
{
d->poffs = 0;
}
/*
* check that all clients controlled by MMC are ready to start, if so,
* attach them all at the same position
*/
void
dev_sync_attach(struct dev *d)
{
int i;
struct slot *s;
if (d->tstate != MMC_START) {
if (log_level >= 2) {
dev_log(d);
log_puts(": not started by mmc yet, waiting...\n");
}
return;
}
for (i = 0; i < DEV_NSLOT; i++) {
s = d->slot + i;
if (!s->ops || s->tstate == MMC_OFF)
continue;
if (s->tstate != MMC_START || s->pstate != SLOT_READY) {
#ifdef DEBUG
if (log_level >= 3) {
slot_log(s);
log_puts(": not ready, start delayed\n");
}
#endif
return;
}
}
for (i = 0; i < DEV_NSLOT; i++) {
s = d->slot + i;
if (!s->ops)
continue;
if (s->tstate == MMC_START) {
#ifdef DEBUG
if (log_level >= 3) {
slot_log(s);
log_puts(": started\n");
}
#endif
s->tstate = MMC_RUN;
slot_attach(s);
}
}
d->tstate = MMC_RUN;
dev_midi_full(d);
dev_wakeup(d);
}
/*
* start all slots simultaneously
*/
void
dev_mmcstart(struct dev *d)
{
if (d->tstate == MMC_STOP) {
d->tstate = MMC_START;
dev_sync_attach(d);
#ifdef DEBUG
} else {
if (log_level >= 3) {
dev_log(d);
log_puts(": ignoring mmc start\n");
}
#endif
}
}
/*
* stop all slots simultaneously
*/
void
dev_mmcstop(struct dev *d)
{
int i;
struct slot *s;
switch (d->tstate) {
case MMC_START:
d->tstate = MMC_STOP;
return;
case MMC_RUN:
d->tstate = MMC_STOP;
break;
default:
#ifdef DEBUG
if (log_level >= 3) {
dev_log(d);
log_puts(": ignored mmc stop\n");
}
#endif
return;
}
for (i = 0, s = d->slot; i < DEV_NSLOT; i++, s++) {
if (!s->ops)
continue;
if (s->tstate == MMC_RUN) {
#ifdef DEBUG
if (log_level >= 3) {
slot_log(s);
log_puts(": requested to stop\n");
}
#endif
s->ops->mmcstop(s->arg);
}
}
}
/*
* relocate all slots simultaneously
*/
void
dev_mmcloc(struct dev *d, unsigned int origin)
{
int i;
struct slot *s;
if (log_level >= 2) {
dev_log(d);
log_puts(": relocated to ");
log_putu(origin);
log_puts("\n");
}
if (d->tstate == MMC_RUN)
dev_mmcstop(d);
d->mtc.origin = origin;
for (i = 0, s = d->slot; i < DEV_NSLOT; i++, s++) {
if (!s->ops)
continue;
s->ops->mmcloc(s->arg, d->mtc.origin);
}
if (d->tstate == MMC_RUN)
dev_mmcstart(d);
}
/*
* allocate a new slot and register the given call-backs
*/
struct slot *
slot_new(struct dev *d, char *who, struct slotops *ops, void *arg, int mode)
{
char *p;
char name[SLOT_NAMEMAX];
unsigned int i, unit, umap = 0;
unsigned int ser, bestser, bestidx;
struct slot *s;
/*
* create a ``valid'' control name (lowcase, remove [^a-z], trucate)
*/
for (i = 0, p = who; ; p++) {
if (i == SLOT_NAMEMAX - 1 || *p == '\0') {
name[i] = '\0';
break;
} else if (*p >= 'A' && *p <= 'Z') {
name[i++] = *p + 'a' - 'A';
} else if (*p >= 'a' && *p <= 'z')
name[i++] = *p;
}
if (i == 0)
strlcpy(name, "noname", SLOT_NAMEMAX);
/*
* find the instance number of the control name
*/
for (i = 0, s = d->slot; i < DEV_NSLOT; i++, s++) {
if (s->ops == NULL)
continue;
if (strcmp(s->name, name) == 0)
umap |= (1 << s->unit);
}
for (unit = 0; ; unit++) {
if (unit == DEV_NSLOT) {
if (log_level >= 1) {
log_puts(name);
log_puts(": has too many instances\n");
}
return NULL;
}
if ((umap & (1 << unit)) == 0)
break;
}
/*
* find a free controller slot with the same name/unit
*/
for (i = 0, s = d->slot; i < DEV_NSLOT; i++, s++) {
if (s->ops == NULL &&
strcmp(s->name, name) == 0 &&
s->unit == unit) {
#ifdef DEBUG
if (log_level >= 3) {
log_puts(name);
log_putu(unit);
log_puts(": reused\n");
}
#endif
goto found;
}
}
/*
* couldn't find a matching slot, pick oldest free slot
* and set its name/unit
*/
bestser = 0;
bestidx = DEV_NSLOT;
for (i = 0, s = d->slot; i < DEV_NSLOT; i++, s++) {
if (s->ops != NULL)
continue;
ser = d->serial - s->serial;
if (ser > bestser) {
bestser = ser;
bestidx = i;
}
}
if (bestidx == DEV_NSLOT) {
if (log_level >= 1) {
log_puts(name);
log_putu(unit);
log_puts(": out of sub-device slots\n");
}
return NULL;
}
s = d->slot + bestidx;
if (s->name[0] != '\0')
s->vol = MIDI_MAXCTL;
strlcpy(s->name, name, SLOT_NAMEMAX);
s->serial = d->serial++;
s->unit = unit;
#ifdef DEBUG
if (log_level >= 3) {
log_puts(name);
log_putu(unit);
log_puts(": overwritten slot ");
log_putu(bestidx);
log_puts("\n");
}
#endif
found:
if (!dev_ref(d))
return NULL;
s->dev = d;
s->ops = ops;
s->arg = arg;
s->pstate = SLOT_INIT;
s->tstate = MMC_OFF;
if ((mode & s->dev->mode) != mode) {
if (log_level >= 1) {
slot_log(s);
log_puts(": requested mode not supported\n");
}
return 0;
}
s->mode = mode;
s->par = d->par;
if (s->mode & MODE_PLAY) {
s->mix.slot_cmin = 0;
s->mix.slot_cmax = d->pchan - 1;
}
if (s->mode & MODE_RECMASK) {
s->sub.slot_cmin = 0;
s->sub.slot_cmax = ((s->mode & MODE_MON) ?
d->pchan : d->rchan) - 1;
}
s->xrun = XRUN_IGNORE;
s->dup = 0;
s->appbufsz = d->bufsz;
s->round = d->round;
dev_midi_mixinfo(d, s);
dev_midi_vol(d, s);
return s;
}
/*
* release the given slot
*/
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:
slot_stop(s);
/* PASSTHROUGH */
case SLOT_STOP:
break;
}
dev_unref(s->dev);
s->dev = NULL;
}
/*
* change the slot play volume; called by the slot or by MIDI
*/
void
slot_setvol(struct slot *s, unsigned int vol)
{
#ifdef DEBUG
if (log_level >= 3) {
slot_log(s);
log_puts(": setting volume ");
log_putu(s->vol);
log_puts(" -> ");
log_putu(vol);
log_puts("\n");
}
#endif
s->vol = vol;
if (s->ops == NULL)
return;
s->mix.vol = MIDI_TO_ADATA(s->vol);
}
void
slot_attach(struct slot *s)
{
struct dev *d = s->dev;
unsigned int slot_nch, dev_nch;
/*
* start the device if not started
*/
dev_wakeup(d);
/*
* get the current position, the origin is when the first sample
* played and/or recorded
*/
s->startpos = dev_getpos(d) * (int)s->round / (int)d->round;
s->delta = 0;
s->delta_rem = 0;
s->pstate = SLOT_RUN;
#ifdef DEBUG
s->delay = 0;
if (log_level >= 3) {
slot_log(s);
log_puts(": attached at ");
log_puti(s->startpos);
log_puts("\n");
}
#endif
/*
* We dont check whether the device is dying,
* because dev_xxx() functions are supposed to
* work (i.e., not to crash)
*/
#ifdef DEBUG
if ((s->mode & d->mode) != s->mode) {
slot_log(s);
log_puts(": mode beyond device mode, not attaching\n");
panic();
}
#endif
s->next = d->slot_list;
d->slot_list = s;
if (s->mode & MODE_PLAY) {
slot_nch = s->mix.slot_cmax - s->mix.slot_cmin + 1;
dev_nch = s->mix.dev_cmax - s->mix.dev_cmin + 1;
s->mix.decbuf = NULL;
s->mix.resampbuf = NULL;
s->mix.join = 1;
s->mix.expand = 1;
if (s->dup) {
if (dev_nch > slot_nch)
s->mix.expand = dev_nch / slot_nch;
else if (dev_nch < slot_nch)
s->mix.join = slot_nch / dev_nch;
}
cmap_init(&s->mix.cmap,
s->mix.slot_cmin, s->mix.slot_cmax,
s->mix.slot_cmin, s->mix.slot_cmax,
0, d->pchan - 1,
s->mix.dev_cmin, s->mix.dev_cmax);
if (!aparams_native(&s->par)) {
dec_init(&s->mix.dec, &s->par, slot_nch);
s->mix.decbuf =
xmalloc(d->round * slot_nch * sizeof(adata_t));
}
if (s->rate != d->rate) {
resamp_init(&s->mix.resamp, s->round, d->round,
slot_nch);
s->mix.resampbuf =
xmalloc(d->round * slot_nch * sizeof(adata_t));
}
#ifdef DEBUG
if (log_level >= 3) {
log_puts("play: cc = ");
log_puti(s->mix.cmap.nch);
log_puts(", dev_nch = ");
log_puti(dev_nch);
log_puts(", slot = ");
log_puti(s->mix.slot_cmin);
log_puts(":");
log_puti(s->mix.slot_cmax);
log_puts(", dev = ");
log_puti(s->mix.dev_cmin);
log_puts(":");
log_puti(s->mix.dev_cmax);
log_puts(", join = ");
log_puti(s->mix.join);
log_puts(", expand = ");
log_puti(s->mix.expand);
log_puts("\n");
}
#endif
s->mix.drop = 0;
s->mix.vol = MIDI_TO_ADATA(s->vol);
dev_mix_setmaster(d);
}
if (s->mode & MODE_RECMASK) {
slot_nch = s->sub.slot_cmax - s->sub.slot_cmin + 1;
dev_nch = s->sub.dev_cmax - s->sub.dev_cmin + 1;
s->sub.encbuf = NULL;
s->sub.resampbuf = NULL;
s->sub.join = 1;
s->sub.expand = 1;
if (s->dup) {
if (dev_nch > slot_nch)
s->sub.join = dev_nch / slot_nch;
else if (dev_nch < slot_nch)
s->sub.expand = slot_nch / dev_nch;
}
cmap_init(&s->sub.cmap,
0, ((s->mode & MODE_MON) ? d->pchan : d->rchan) - 1,
s->sub.dev_cmin, s->sub.dev_cmax,
s->sub.slot_cmin, s->sub.slot_cmax,
s->sub.slot_cmin, s->sub.slot_cmax);
if (s->rate != d->rate) {
resamp_init(&s->sub.resamp, d->round, s->round,
slot_nch);
s->sub.resampbuf =
xmalloc(d->round * slot_nch * sizeof(adata_t));
}
if (!aparams_native(&s->par)) {
enc_init(&s->sub.enc, &s->par, slot_nch);
s->sub.encbuf =
xmalloc(d->round * slot_nch * sizeof(adata_t));
}
#ifdef DEBUG
if (log_level >= 3) {
log_puts("sub: cc = ");
log_puti(s->sub.cmap.nch);
log_puts(", dev_nch = ");
log_puti(dev_nch);
log_puts(", slot ");
log_puti(s->sub.slot_cmin);
log_puts(":");
log_puti(s->sub.slot_cmax);
log_puts(", join = ");
log_puti(s->sub.join);
log_puts(", expand = ");
log_puti(s->sub.expand);
log_puts(", dev = ");
log_puti(s->sub.dev_cmin);
log_puts(":");
log_puti(s->sub.dev_cmax);
log_puts("\n");
}
#endif
/*
* N-th recorded block is the N-th played block
*/
s->sub.silence = s->startpos / (int)s->round;
}
}
void
slot_ready(struct slot *s)
{
if (s->tstate == MMC_OFF)
slot_attach(s);
else {
s->tstate = MMC_START;
dev_sync_attach(s->dev);
}
}
/*
* notify the MMC layer that the stream is attempting
* to start. If other streams are not ready, 0 is returned meaning
* that the stream should wait. If other streams are ready, they
* are started, and the caller should start immediately.
*/
void
slot_start(struct slot *s)
{
struct dev *d = s->dev;
unsigned int bufsz;
#ifdef DEBUG
if (s->pstate != SLOT_INIT) {
slot_log(s);
log_puts(": slot_start: wrong state\n");
panic();
}
#endif
bufsz = SLOT_BUFSZ(s);
if (s->mode & MODE_PLAY) {
#ifdef DEBUG
if (log_level >= 3) {
slot_log(s);
log_puts(": playing ");
aparams_log(&s->par);
log_puts(" -> ");
aparams_log(&d->par);
log_puts("\n");
}
#endif
abuf_init(&s->mix.buf, bufsz,
s->par.bps * (s->mix.slot_cmax - s->mix.slot_cmin + 1));
}
if (s->mode & MODE_RECMASK) {
#ifdef DEBUG
if (log_level >= 3) {
slot_log(s);
log_puts(": recording ");
aparams_log(&s->par);
log_puts(" <- ");
aparams_log(&d->par);
log_puts("\n");
}
#endif
abuf_init(&s->sub.buf, bufsz,
s->par.bps * (s->sub.slot_cmax - s->sub.slot_cmin + 1));
}
s->mix.weight = MIDI_TO_ADATA(MIDI_MAXCTL);
#ifdef DEBUG
if (log_level >= 3) {
slot_log(s);
log_puts(": allocated ");
log_putu(s->appbufsz);
log_puts("/");
log_putu(bufsz);
log_puts(" fr buffers\n");
}
#endif
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;
#ifdef DEBUG
if (log_level >= 3) {
slot_log(s);
log_puts(": detaching\n");
}
#endif
for (ps = &s->dev->slot_list; *ps != s; ps = &(*ps)->next) {
#ifdef DEBUG
if (log_level >= 3) {
slot_log(s);
log_puts(": can't detach, no on list\n");
panic();
}
#endif
}
*ps = s->next;
}
void
slot_stop(struct slot *s)
{
#ifdef DEBUG
if (log_level >= 3) {
slot_log(s);
log_puts(": stopping\n");
}
#endif
if (s->pstate == SLOT_START) {
if (s->mode & MODE_PLAY) {
s->pstate = SLOT_READY;
slot_ready(s);
} else
s->pstate = SLOT_INIT;
}
if (s->mode & MODE_RECMASK)
abuf_done(&s->sub.buf);
if (s->pstate == SLOT_READY) {
#ifdef DEBUG
if (log_level >= 3) {
slot_log(s);
log_puts(": not drained (blocked by mmc)\n");
}
#endif
if (s->mode & MODE_PLAY)
abuf_done(&s->mix.buf);
s->ops->eof(s->arg);
s->pstate = SLOT_INIT;
} else {
/* s->pstate == SLOT_RUN */
if (s->mode & MODE_PLAY)
s->pstate = SLOT_STOP;
else {
slot_detach(s);
s->pstate = SLOT_INIT;
s->ops->eof(s->arg);
}
}
if (s->tstate != MMC_OFF)
s->tstate = MMC_STOP;
}
void
slot_write(struct slot *s)
{
if (s->pstate == SLOT_START && s->mix.buf.used == s->mix.buf.len) {
#ifdef DEBUG
if (log_level >= 4) {
slot_log(s);
log_puts(": switching to READY state\n");
}
#endif
s->pstate = SLOT_READY;
slot_ready(s);
}
}
void
slot_read(struct slot *s)
{
}