[BACK]Return to dev.c CVS log [TXT][DIR] Up to [local] / src / usr.bin / sndiod

File: [local] / src / usr.bin / sndiod / dev.c (download)

Revision 1.102, Mon May 3 04:29:50 2021 UTC (3 years, 1 month ago) by ratchov
Branch: MAIN
CVS Tags: OPENBSD_7_0_BASE, OPENBSD_7_0
Changes since 1.101: +35 -26 lines

If mode is not allowed in struct opt, then just play/record silence

This is similar to what we already do when device is opened and its
mode doesn't match requested mode. Besides adding consistency, this
change would allow client's opt structure to be changed dynamically.

/*	$OpenBSD: dev.c,v 1.102 2021/05/03 04:29:50 ratchov Exp $	*/
/*
 * Copyright (c) 2008-2012 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 <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_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 *);
struct dev *dev_new(char *, struct aparams *, unsigned int, unsigned int,
    unsigned int, unsigned int, unsigned int, unsigned int);
void dev_adjpar(struct dev *, int, int, int);
int dev_allocbufs(struct dev *);
int dev_open(struct dev *);
void dev_freebufs(struct dev *);
void dev_close(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 *);
void dev_setalt(struct dev *, unsigned int);
unsigned int dev_roundof(struct dev *, unsigned int);
void dev_wakeup(struct dev *);

void slot_ctlname(struct slot *, char *, size_t);
void slot_log(struct slot *);
void slot_del(struct slot *);
void slot_setvol(struct slot *, unsigned int);
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 *);

void ctl_node_log(struct ctl_node *);
void ctl_log(struct ctl *);

struct slotops zomb_slotops = {
	zomb_onmove,
	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];
unsigned int slot_serial;		/* for slot allocation */

/*
 * we support/need a single MTC clock source only
 */
struct mtc mtc_array[1] = {
	{.dev = NULL, .tstate = MTC_STOP}
};

void
slot_array_init(void)
{
	unsigned int i;

	for (i = 0; i < DEV_NSLOT; i++) {
		slot_array[i].unit = i;
		slot_array[i].ops = NULL;
		slot_array[i].vol = MIDI_MAXCTL;
		slot_array[i].opt = NULL;
		slot_array[i].serial = slot_serial++;
		memset(slot_array[i].name, 0, SLOT_NAMEMAX);
	}
}

void
dev_log(struct dev *d)
{
#ifdef DEBUG
	static char *pstates[] = {
		"cfg", "ini", "run"
	};
#endif
	log_puts("snd");
	log_putu(d->num);
#ifdef DEBUG
	if (log_level >= 3) {
		log_puts(" pst=");
		log_puts(pstates[d->pstate]);
	}
#endif
}

void
slot_ctlname(struct slot *s, char *name, size_t size)
{
	snprintf(name, size, "%s%u", s->name, s->unit);
}

void
slot_log(struct slot *s)
{
	char name[CTL_NAMEMAX];
#ifdef DEBUG
	static char *pstates[] = {
		"ini", "sta", "rdy", "run", "stp", "mid"
	};
#endif
	slot_ctlname(s, name, CTL_NAMEMAX);
	log_puts(name);
#ifdef DEBUG
	if (log_level >= 3) {
		log_puts(" vol=");
		log_putu(s->vol);
		if (s->ops) {
			log_puts(",pst=");
			log_puts(pstates[s->pstate]);
		}
	}
#endif
}

void
zomb_onmove(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
	if (log_level >= 3) {
		slot_log(s);
		log_puts(": zomb_eof\n");
	}
#endif
	s->ops = NULL;
}

void
zomb_exit(void *arg)
{
#ifdef DEBUG
	struct slot *s = arg;

	if (log_level >= 3) {
		slot_log(s);
		log_puts(": zomb_exit\n");
	}
#endif
}

/*
 * Broadcast MIDI data to all opts using this device
 */
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);
	}
}

/*
 * send a quarter frame MTC message
 */
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);
			/*
			 * tick messages are sent 2 frames ahead
			 */
			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:
			/* NOTREACHED */
			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;
	}
}

/*
 * send a full frame MTC message
 */
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
	if (log_level >= 3) {
		dev_log(mtc->dev);
		log_puts(": mtc full frame at ");
		log_puti(mtc->delta);
		log_puts(", ");
		log_puti(mtc->fps);
		log_puts(" fps\n");
	}
#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));
}

/*
 * send a volume change MIDI message
 */
void
dev_midi_vol(struct dev *d, struct slot *s)
{
	unsigned char msg[3];

	msg[0] = MIDI_CTL | (s - slot_array);
	msg[1] = MIDI_CTL_VOL;
	msg[2] = s->vol;
	dev_midi_send(d, msg, 3);
}

/*
 * send a master volume MIDI message
 */
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));
}

/*
 * send a sndiod-specific slot description MIDI message
 */
void
dev_midi_slotdesc(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.dev = SYSEX_DEV_ANY;
	x.id0 = SYSEX_AUCAT;
	x.id1 = SYSEX_AUCAT_SLOTDESC;
	if (s->opt != NULL && s->opt->dev == d)
		slot_ctlname(s, (char *)x.u.slotdesc.name, SYSEX_NAMELEN);
	x.u.slotdesc.chan = (s - slot_array);
	x.u.slotdesc.end = SYSEX_END;
	dev_midi_send(d, (unsigned char *)&x, SYSEX_SIZE(slotdesc));
}

void
dev_midi_dump(struct dev *d)
{
	struct sysex x;
	struct slot *s;
	int i;

	dev_midi_master(d);
	for (i = 0, s = slot_array; i < DEV_NSLOT; i++, s++) {
		if (s->opt != NULL && s->opt->dev != d)
			continue;
		dev_midi_slotdesc(d, s);
		dev_midi_vol(d, s);
	}
	x.start = SYSEX_START;
	x.type = SYSEX_TYPE_EDU;
	x.dev = SYSEX_DEV_ANY;
	x.id0 = SYSEX_AUCAT;
	x.id1 = SYSEX_AUCAT_DUMPEND;
	x.u.dumpend.end = SYSEX_END;
	dev_midi_send(d, (unsigned char *)&x, SYSEX_SIZE(dumpend));
}

int
slot_skip(struct slot *s)
{
	unsigned char *data = (unsigned char *)0xdeadbeef; /* please gcc */
	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
		if (log_level >= 4) {
			slot_log(s);
			log_puts(": skipped a cycle\n");
		}
#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;
}

/*
 * Mix the slot input block over the output block
 */
void
dev_mix_badd(struct dev *d, struct slot *s)
{
	adata_t *idata, *odata, *in;
	int icount, i, offs, vol, nch;

	odata = DEV_PBUF(d);
	idata = (adata_t *)abuf_rgetblk(&s->mix.buf, &icount);
#ifdef DEBUG
	if (icount < s->round * s->mix.bpf) {
		slot_log(s);
		log_puts(": not enough data to mix (");
		log_putu(icount);
		log_puts("bytes)\n");
		panic();
	}
#endif
	if (!(s->opt->mode & MODE_PLAY)) {
		/*
		 * playback not allowed in opt structure, produce silence
		 */
		abuf_rdiscard(&s->mix.buf, s->round * s->mix.bpf);
		return;
	}


	/*
	 * Apply the following processing chain:
	 *
	 *	dec -> resamp-> cmap
	 *
	 * where the first two are optional.
	 */

	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);
		in = s->mix.resampbuf;
	}

	nch = s->mix.cmap.nch;
	vol = ADATA_MUL(s->mix.weight, s->mix.vol) / s->mix.join;
	cmap_add(&s->mix.cmap, in, odata, vol, d->round);

	offs = 0;
	for (i = s->mix.join - 1; i > 0; i--) {
		offs += nch;
		cmap_add(&s->mix.cmap, in + offs, odata, vol, d->round);
	}

	offs = 0;
	for (i = s->mix.expand - 1; i > 0; i--) {
		offs += nch;
		cmap_add(&s->mix.cmap, in, odata + offs, vol, d->round);
	}

	abuf_rdiscard(&s->mix.buf, s->round * s->mix.bpf);
}

/*
 * Normalize input levels.
 */
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) {
			/*
			 * 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;
				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
		if (log_level >= 3) {
			slot_log(i);
			log_puts(": set weight: ");
			log_puti(i->mix.weight);
			log_puts("/");
			log_puti(i->opt->maxweight);
			log_puts("\n");
		}
#endif
	}
}

/*
 * Copy data from slot to device
 */
void
dev_sub_bcopy(struct dev *d, struct slot *s)
{
	adata_t *idata, *enc_out, *resamp_out, *cmap_out;
	void *odata;
	int ocount, moffs;

	int i, vol, offs, nch;


	odata = (adata_t *)abuf_wgetblk(&s->sub.buf, &ocount);
#ifdef DEBUG
	if (ocount < s->round * s->sub.bpf) {
		log_puts("dev_sub_bcopy: not enough space\n");
		panic();
	}
#endif
	if (s->opt->mode & MODE_MON) {
		moffs = d->poffs + d->round;
		if (moffs == d->psize)
			moffs = 0;
		idata = d->pbuf + moffs * d->pchan;
	} else if (s->opt->mode & MODE_REC) {
		idata = d->rbuf;
	} else {
		/*
		 * recording not allowed in opt structure, produce silence
		 */
		enc_sil_do(&s->sub.enc, odata, s->round);
		abuf_wcommit(&s->sub.buf, s->round * s->sub.bpf);
		return;
	}

	/*
	 * Apply the following processing chain:
	 *
	 *	cmap -> resamp -> enc
	 *
	 * where the last two are optional.
	 */

	enc_out = odata;
	resamp_out = s->sub.encbuf ? s->sub.encbuf : enc_out;
	cmap_out = s->sub.resampbuf ? s->sub.resampbuf : resamp_out;

	nch = s->sub.cmap.nch;
	vol = ADATA_UNIT / s->sub.join;
	cmap_copy(&s->sub.cmap, idata, cmap_out, vol, d->round);

	offs = 0;
	for (i = s->sub.join - 1; i > 0; i--) {
		offs += nch;
		cmap_add(&s->sub.cmap, idata + offs, cmap_out, vol, d->round);
	}

	offs = 0;
	for (i = s->sub.expand - 1; i > 0; i--) {
		offs += nch;
		cmap_copy(&s->sub.cmap, idata, cmap_out + offs, vol, d->round);
	}

	if (s->sub.resampbuf) {
		resamp_do(&s->sub.resamp,
		    s->sub.resampbuf, resamp_out, d->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);
}

/*
 * run a one block cycle: consume one recorded block from
 * rbuf and produce one play block in pbuf
 */
void
dev_cycle(struct dev *d)
{
	struct slot *s, **ps;
	unsigned char *base;
	int nsamp;

	/*
	 * check if the device is actually used. If it isn't,
	 * then close it
	 */
	if (d->slot_list == NULL && (mtc_array[0].dev != d ||
	    mtc_array[0].tstate != MTC_RUN)) {
		if (log_level >= 2) {
			dev_log(d);
			log_puts(": device stopped\n");
		}
		dev_sio_stop(d);
		d->pstate = DEV_INIT;
		if (d->refcnt == 0)
			dev_close(d);
		return;
	}

	if (d->prime > 0) {
#ifdef DEBUG
		if (log_level >= 4) {
			dev_log(d);
			log_puts(": empty cycle, prime = ");
			log_putu(d->prime);
			log_puts("\n");
		}
#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
	if (log_level >= 4) {
		dev_log(d);
		log_puts(": full cycle: delta = ");
		log_puti(d->delta);
		if (d->mode & MODE_PLAY) {
			log_puts(", poffs = ");
			log_puti(d->poffs);
		}
		log_puts("\n");
	}
#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
		if (log_level >= 4) {
			slot_log(s);
			log_puts(": running");
			log_puts(", skip = ");
			log_puti(s->skip);
			log_puts("\n");
		}
#endif
		/*
		 * skip cycles for XRUN_SYNC correction
		 */
		slot_skip(s);
		if (s->skip < 0) {
			s->skip++;
			ps = &s->next;
			continue;
		}

#ifdef DEBUG
		if (s->pstate == SLOT_STOP && !(s->mode & MODE_PLAY)) {
			slot_log(s);
			log_puts(": rec-only slots can't be drained\n");
			panic();
		}
#endif
		/*
		 * check if stopped stream finished draining
		 */
		if (s->pstate == SLOT_STOP &&
		    s->mix.buf.used < s->round * s->mix.bpf) {
			/*
			 * partial blocks are zero-filled by socket
			 * layer, so s->mix.buf.used == 0 and we can
			 * destroy the buffer
			 */
			*ps = s->next;
			s->pstate = SLOT_INIT;
			s->ops->eof(s->arg);
			slot_freebufs(s);
			dev_mix_adjvol(d);
#ifdef DEBUG
			if (log_level >= 3) {
				slot_log(s);
				log_puts(": drained\n");
			}
#endif
			continue;
		}

		/*
		 * check for xruns
		 */
		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)) {

#ifdef DEBUG
			if (log_level >= 3) {
				slot_log(s);
				log_puts(": xrun, pause cycle\n");
			}
#endif
			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
				slot_log(s);
				log_puts(": bad xrun mode\n");
				panic();
#endif
			}
			continue;
		}
		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
				if (log_level >= 3) {
					slot_log(s);
					log_puts(": prime = ");
					log_puti(s->sub.prime);
					log_puts("\n");
				}
#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);
	}
}

/*
 * called at every clock tick by the device
 */
void
dev_onmove(struct dev *d, int delta)
{
	long long pos;
	struct slot *s, *snext;

	d->delta += delta;

	for (s = d->slot_list; s != NULL; s = snext) {
		/*
		 * s->ops->onmove() may remove the slot
		 */
		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;

	if (log_level >= 2) {
		dev_log(d);
		log_puts(": master volume set to ");
		log_putu(master);
		log_puts("\n");
	}
	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);
		}
	}
}

/*
 * 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, **pd;

	if (dev_sndnum == DEV_NMAX) {
		if (log_level >= 1)
			log_puts("too many devices\n");
		return NULL;
	}
	d = xmalloc(sizeof(struct dev));
	d->alt_list = NULL;
	dev_addname(d,path);
	d->num = dev_sndnum++;
	d->alt_num = -1;

	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;
}

/*
 * add a alternate name
 */
int
dev_addname(struct dev *d, char *name)
{
	struct dev_alt *a;

	if (d->alt_list != NULL && d->alt_list->idx == DEV_NMAX - 1) {
		log_puts(name);
		log_puts(": too many alternate names\n");
		return 0;
	}
	a = xmalloc(sizeof(struct dev_alt));
	a->name = name;
	a->idx = (d->alt_list == NULL) ? 0 : d->alt_list->idx + 1;
	a->next = d->alt_list;
	d->alt_list = a;
	return 1;
}

/*
 * set prefered alt device name
 */
void
dev_setalt(struct dev *d, unsigned int idx)
{
	struct dev_alt **pa, *a;

	/* find alt with given index */
	for (pa = &d->alt_list; (a = *pa)->idx != idx; pa = &a->next)
		;

	/* detach from list */
	*pa = a->next;

	/* attach at head */
	a->next = d->alt_list;
	d->alt_list = a;

	/* reopen device with the new alt */
	if (idx != d->alt_num)
		dev_reopen(d);
}

/*
 * adjust device parameters and mode
 */
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;
	}
}

/*
 * Open the device with the dev_reqxxx capabilities. Setup a mixer, demuxer,
 * monitor, midi control, and any necessary conversions.
 *
 * Note that record and play buffers are always allocated, even if the
 * underlying device doesn't support both modes.
 */
int
dev_allocbufs(struct dev *d)
{
	/*
	 * Create record buffer.
	 */

	 /* 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;

	/*
	 * Create play buffer
	 */

	/* Create device <-> mixer buffer */
	d->poffs = 0;
	d->psize = d->bufsz + d->round;
	d->pbuf = xmalloc(d->psize * d->pchan * sizeof(adata_t));
	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;

	/*
	 * Initially fill the record buffer with zeroed samples. This ensures
	 * that when a client records from a play-only device the client just
	 * gets silence.
	 */
	memset(d->rbuf, 0, d->round * d->rchan * sizeof(adata_t));

	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");
		if (d == mtc_array[0].dev)
			log_puts(", mtc");
		log_puts("\n");
	}
	return 1;
}

/*
 * Reset parameters and open the device.
 */
int
dev_open(struct dev *d)
{
	char name[CTL_NAMEMAX];
	struct dev_alt *a;

	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)) {
		if (log_level >= 1) {
			dev_log(d);
			log_puts(": failed to open audio device\n");
		}
		return 0;
	}
	if (!dev_allocbufs(d))
		return 0;

	/* if there are multiple alt devs, add server.device knob */
	if (d->alt_list->next != NULL) {
		for (a = d->alt_list; a != NULL; a = a->next) {
			snprintf(name, sizeof(name), "%d", a->idx);
			ctl_new(CTL_DEV_ALT, d, &a->idx,
			    CTL_SEL, d->name, "server", -1, "device",
			    name, -1, 1, a->idx == d->alt_num);
		}
	}

	d->pstate = DEV_INIT;
	return 1;
}

/*
 * Force all slots to exit and close device, called after an error
 */
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 (c = ctlslot_array, i = 0; i < DEV_NCTLSLOT; i++, c++) {
		if (c->ops == NULL)
			continue;
		if (c->opt->dev != d)
			continue;
		c->ops->exit(c->arg);
		c->ops = NULL;
	}

	for (o = opt_list; o != NULL; o = o->next) {
		if (o->dev != d)
			continue;
		midi_abort(o->midi);
	}

	if (d->pstate != DEV_CFG)
		dev_close(d);
}

/*
 * force the device to go in DEV_CFG state, the caller is supposed to
 * ensure buffers are drained
 */
void
dev_freebufs(struct dev *d)
{
#ifdef DEBUG
	if (log_level >= 3) {
		dev_log(d);
		log_puts(": closing\n");
	}
#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);
	}
}

/*
 * Close the device and exit all slots
 */
void
dev_close(struct dev *d)
{
	struct dev_alt *a;
	unsigned int idx;

	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);
	}
	for (idx = 0, a = d->alt_list; a != NULL; idx++, a = a->next)
		ctl_del(CTL_DEV_ALT, d, &idx);
}

/*
 * Close the device, but attempt to migrate everything to a new sndio
 * device.
 */
int
dev_reopen(struct dev *d)
{
	struct mtc *mtc;
	struct slot *s;
	long long pos;
	unsigned int pstate;
	int delta;

	/* not opened */
	if (d->pstate == DEV_CFG)
		return 1;

	/* save state */
	delta = d->delta;
	pstate = d->pstate;

	if (!dev_sio_reopen(d))
		return 0;

	/* reopen returns a stopped device */
	d->pstate = DEV_INIT;

	/* reallocate new buffers, with new parameters */
	dev_freebufs(d);
	dev_allocbufs(d);

	/*
	 * adjust time positions, make anything go back delta ticks, so
	 * that the new device can start at zero
	 */
	for (s = d->slot_list; s != NULL; s = s->next) {
		pos = (long long)s->delta * d->round + s->delta_rem;
		pos -= (long long)delta * s->round;
		s->delta_rem = pos % (int)d->round;
		s->delta = pos / (int)d->round;
		if (log_level >= 3) {
			slot_log(s);
			log_puts(": adjusted: delta -> ");
			log_puti(s->delta);
			log_puts(", delta_rem -> ");
			log_puti(s->delta_rem);
			log_puts("\n");
		}

		/* reinitilize the format conversion chain */
		slot_initconv(s);
	}
	mtc = &mtc_array[0];
	if (mtc->dev == d && mtc->tstate == MTC_RUN) {
		mtc->delta -= delta * MTC_SEC;
		if (log_level >= 2) {
			dev_log(mtc->dev);
			log_puts(": adjusted mtc: delta ->");
			log_puti(mtc->delta);
			log_puts("\n");
		}
	}

	/* remove old controls and add new ones */
	dev_sioctl_close(d);
	dev_sioctl_open(d);

	/* start the device if needed */
	if (pstate == DEV_RUN)
		dev_wakeup(d);

	return 1;
}

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 (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;
}

/*
 * Free the device
 */
void
dev_del(struct dev *d)
{
	struct dev **p;
	struct dev_alt *a;

#ifdef DEBUG
	if (log_level >= 3) {
		dev_log(d);
		log_puts(": deleting\n");
	}
#endif
	if (d->pstate != DEV_CFG)
		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;
	while ((a = d->alt_list) != NULL) {
		d->alt_list = a->next;
		xfree(a);
	}
	xfree(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) {
		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->poffs = 0;

		/*
		 * empty cycles don't increment delta, so it's ok to
		 * start at 0
		 **/
		d->delta = 0;

		d->pstate = DEV_RUN;
		dev_sio_start(d);
	}
}

/*
 * check that all clients controlled by MMC are ready to start, if so,
 * attach them all at the same position
 */
void
mtc_trigger(struct mtc *mtc)
{
	int i;
	struct slot *s;

	if (mtc->tstate != MTC_START) {
		if (log_level >= 2) {
			dev_log(mtc->dev);
			log_puts(": not started by mmc yet, waiting...\n");
		}
		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
			if (log_level >= 3) {
				slot_log(s);
				log_puts(": not ready, start delayed\n");
			}
#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);
}

/*
 * start all slots simultaneously
 */
void
mtc_start(struct mtc *mtc)
{
	if (mtc->tstate == MTC_STOP) {
		mtc->tstate = MTC_START;
		mtc_trigger(mtc);
#ifdef DEBUG
	} else {
		if (log_level >= 3) {
			dev_log(mtc->dev);
			log_puts(": ignoring mmc start\n");
		}
#endif
	}
}

/*
 * stop all slots simultaneously
 */
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
		if (log_level >= 3) {
			dev_log(mtc->dev);
			log_puts(": ignored mmc stop\n");
		}
#endif
		return;
	}
}

/*
 * relocate all slots simultaneously
 */
void
mtc_loc(struct mtc *mtc, unsigned int origin)
{
	if (log_level >= 2) {
		dev_log(mtc->dev);
		log_puts(": relocated to ");
		log_putu(origin);
		log_puts("\n");
	}
	if (mtc->tstate == MTC_RUN)
		mtc_stop(mtc);
	mtc->origin = origin;
	if (mtc->tstate == MTC_RUN)
		mtc_start(mtc);
}

/*
 * allocate buffers & conversion chain
 */
void
slot_initconv(struct slot *s)
{
	unsigned int dev_nch;
	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->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));
		}
		s->mix.join = 1;
		s->mix.expand = 1;
		if (s->opt->dup && s->mix.cmap.nch > 0) {
			dev_nch = d->pchan < (s->opt->pmax + 1) ?
			    d->pchan - s->opt->pmin :
			    s->opt->pmax - s->opt->pmin + 1;
			if (dev_nch > s->mix.nch)
				s->mix.expand = dev_nch / s->mix.nch;
			else if (s->mix.nch > dev_nch)
				s->mix.join = s->mix.nch / dev_nch;
		}
	}

	if (s->mode & MODE_RECMASK) {
		unsigned int outchan = (s->opt->mode & MODE_MON) ?
		    d->pchan : d->rchan;

		s->sub.encbuf = NULL;
		s->sub.resampbuf = NULL;
		cmap_init(&s->sub.cmap,
		    0, outchan - 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);
		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));
		}
		s->sub.join = 1;
		s->sub.expand = 1;
		if (s->opt->dup && s->sub.cmap.nch > 0) {
			dev_nch = outchan < (s->opt->rmax + 1) ?
			    outchan - s->opt->rmin :
			    s->opt->rmax - s->opt->rmin + 1;
			if (dev_nch > s->sub.nch)
				s->sub.join = dev_nch / s->sub.nch;
			else if (s->sub.nch > dev_nch)
				s->sub.expand = s->sub.nch / dev_nch;
		}

		/*
		 * cmap_copy() doesn't write samples in all channels,
	         * for instance when mono->stereo conversion is
	         * disabled. So we have to prefill cmap_copy() output
	         * with silence.
	         */
		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));
		}
	}
}

/*
 * allocate buffers & conversion chain
 */
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
	if (log_level >= 3) {
		slot_log(s);
		log_puts(": allocated ");
		log_putu(s->appbufsz);
		log_puts("/");
		log_putu(SLOT_BUFSZ(s));
		log_puts(" fr buffers\n");
	}
#endif
}

/*
 * free buffers & conversion chain
 */
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);
	}
}

/*
 * allocate a new slot and register the given call-backs
 */
struct slot *
slot_new(struct opt *opt, unsigned int id, char *who,
    struct slotops *ops, void *arg, int mode)
{
	char *p;
	char name[SLOT_NAMEMAX];
	char ctl_name[CTL_NAMEMAX];
	unsigned int i, ser, bestser, bestidx;
	struct slot *unit[DEV_NSLOT];
	struct slot *s;

	/*
	 * create a ``valid'' control name (lowcase, remove [^a-z], truncate)
	 */
	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);

	/*
	 * build a unit-to-slot map for this name
	 */
	for (i = 0; i < DEV_NSLOT; i++)
		unit[i] = NULL;
	for (i = 0, s = slot_array; i < DEV_NSLOT; i++, s++) {
		if (strcmp(s->name, name) == 0)
			unit[s->unit] = s;
	}

	/*
	 * find the free slot with the least unit number and same id
	 */
	for (i = 0; i < DEV_NSLOT; i++) {
		s = unit[i];
		if (s != NULL && s->ops == NULL && s->id == id)
			goto found;
	}

	/*
	 * find the free slot with the least unit number
	 */
	for (i = 0; i < DEV_NSLOT; i++) {
		s = unit[i];
		if (s != NULL && s->ops == NULL) {
			s->id = id;
			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 = slot_array; i < DEV_NSLOT; i++, s++) {
		if (s->ops != NULL)
			continue;
		ser = slot_serial - s->serial;
		if (ser > bestser) {
			bestser = ser;
			bestidx = i;
		}
	}

	if (bestidx == DEV_NSLOT) {
		if (log_level >= 1) {
			log_puts(name);
			log_puts(": out of sub-device slots\n");
		}
		return NULL;
	}

	s = slot_array + bestidx;
	ctl_del(CTL_SLOT_LEVEL, s, NULL);
	s->vol = MIDI_MAXCTL;
	strlcpy(s->name, name, SLOT_NAMEMAX);
	s->serial = slot_serial++;
	for (i = 0; unit[i] != NULL; i++)
		; /* nothing */
	s->unit = i;
	s->id = id;
	s->opt = opt;
	slot_ctlname(s, ctl_name, CTL_NAMEMAX);
	ctl_new(CTL_SLOT_LEVEL, s, NULL,
	    CTL_NUM, "app", ctl_name, -1, "level",
	    NULL, -1, 127, s->vol);

found:
	if (!dev_ref(opt->dev))
		return NULL;
	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;
	dev_midi_slotdesc(s->opt->dev, s);
	dev_midi_vol(s->opt->dev, s);
#ifdef DEBUG
	if (log_level >= 3) {
		slot_log(s);
		log_puts(": using ");
		dev_log(s->opt->dev);
		log_puts(".");
		log_puts(s->opt->name);
		log_puts(", mode = ");
		log_putx(mode);
		log_puts("\n");
	}
#endif
	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:
	case SLOT_STOP:
		slot_stop(s, 0);
		break;
	}
	dev_unref(s->opt->dev);
}

/*
 * change the slot play volume; called either 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(vol);
		log_puts("\n");
	}
#endif
	s->vol = vol;
	s->mix.vol = MIDI_TO_ADATA(s->vol);
}

/*
 * attach the slot to the device (ie start playing & recording
 */
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))) {
		if (log_level >= 1) {
			slot_log(s);
			log_puts(" at ");
			log_puts(s->opt->name);
			log_puts(": mode not allowed on this sub-device\n");
		}
	}

	/*
	 * setup converions layer
	 */
	slot_initconv(s);

	/*
	 * start the device if not started
	 */
	dev_wakeup(d);

	/*
	 * adjust initial clock
	 */
	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
	if (log_level >= 2) {
		slot_log(s);
		log_puts(": attached at ");
		log_puti(s->delta);
		log_puts(" + ");
		log_puti(s->delta_rem);
		log_puts("/");
		log_puti(s->round);
		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)
	 */

	s->next = d->slot_list;
	d->slot_list = s;
	if (s->mode & MODE_PLAY) {
		s->mix.vol = MIDI_TO_ADATA(s->vol);
		dev_mix_adjvol(d);
	}
}

/*
 * if MMC is enabled, and try to attach all slots synchronously, else
 * simply attach the slot
 */
void
slot_ready(struct slot *s)
{
	/*
	 * device may be disconnected, and if so we're called from
	 * slot->ops->exit() on a closed device
	 */
	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);
}

/*
 * setup buffers & conversion layers, prepare the slot to receive data
 * (for playback) or start (recording).
 */
void
slot_start(struct slot *s)
{
	struct dev *d = s->opt->dev;
#ifdef DEBUG
	if (s->pstate != SLOT_INIT) {
		slot_log(s);
		log_puts(": slot_start: wrong state\n");
		panic();
	}
	if (s->mode & MODE_PLAY) {
		if (log_level >= 3) {
			slot_log(s);
			log_puts(": playing ");
			aparams_log(&s->par);
			log_puts(" -> ");
			aparams_log(&d->par);
			log_puts("\n");
		}
	}
	if (s->mode & MODE_RECMASK) {
		if (log_level >= 3) {
			slot_log(s);
			log_puts(": recording ");
			aparams_log(&s->par);
			log_puts(" <- ");
			aparams_log(&d->par);
			log_puts("\n");
		}
	}
#endif
	slot_allocbufs(s);

	if (s->mode & MODE_RECMASK) {
		/*
		 * N-th recorded block is the N-th played block
		 */
		s->sub.prime = d->bufsz / d->round;
	}
	s->skip = 0;

	/*
	 * get the current position, the origin is when the first sample
	 * played and/or recorded
	 */
	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);
	}
}

/*
 * stop playback and recording, and free conversion layers
 */
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) {
			slot_log(s);
			log_puts(": can't detach, not on list\n");
			panic();
		}
#endif
	}
	*ps = s->next;

	/*
	 * adjust clock, go back d->delta ticks so that slot_attach()
	 * could be called with the resulting state
	 */
	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
	if (log_level >= 2) {
		slot_log(s);
		log_puts(": detached at ");
		log_puti(s->delta);
		log_puts(" + ");
		log_puti(s->delta_rem);
		log_puts("/");
		log_puti(d->round);
		log_puts("\n");
	}
#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;
		}
	}
}

/*
 * put the slot in stopping state (draining play buffers) or
 * stop & detach if no data to drain.
 */
void
slot_stop(struct slot *s, int drain)
{
#ifdef DEBUG
	if (log_level >= 3) {
		slot_log(s);
		log_puts(": stopping\n");
	}
#endif
	if (s->pstate == SLOT_START) {
		/*
		 * If in rec-only mode, we're already in the READY or
		 * RUN states. We're here because the play buffer was
		 * not full enough, try to start so it's drained.
		 */
		s->pstate = SLOT_READY;
		slot_ready(s);
	}

	if (s->pstate == SLOT_RUN) {
		if ((s->mode & MODE_PLAY) && drain) {
			/*
			 * Don't detach, dev_cycle() will do it for us
			 * when the buffer is drained.
			 */
			s->pstate = SLOT_STOP;
			return;
		}
		slot_detach(s);
	} else if (s->pstate == SLOT_STOP) {
		slot_detach(s);
	} else {
#ifdef DEBUG
		if (log_level >= 3) {
			slot_log(s);
			log_puts(": not drained (blocked by mmc)\n");
		}
#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
		if (log_level >= 4) {
			slot_log(s);
			log_puts(": catching skipped block\n");
		}
#endif
		if (s->mode & MODE_RECMASK)
			s->ops->flush(s->arg);
		if (s->mode & MODE_PLAY)
			s->ops->fill(s->arg);
		skip--;
	}
}

/*
 * notify the slot that we just wrote in the play buffer, must be called
 * after each write
 */
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);
	}
	slot_skip_update(s);
}

/*
 * notify the slot that we freed some space in the rec buffer
 */
void
slot_read(struct slot *s)
{
	slot_skip_update(s);
}

/*
 * allocate at control slot
 */
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 (!dev_ref(o->dev))
		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;
}

/*
 * free control slot
 */
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;
	dev_unref(s->opt->dev);
}

int
ctlslot_visible(struct ctlslot *s, struct ctl *c)
{
	if (s->opt == NULL)
		return 1;
	switch (c->scope) {
	case CTL_HW:
	case CTL_DEV_MASTER:
	case CTL_DEV_ALT:
		return (s->opt->dev == c->u.any.arg0);
	case CTL_SLOT_LEVEL:
		return (s->opt->dev == c->u.slot_level.slot->opt->dev);
	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
ctl_node_log(struct ctl_node *c)
{
	log_puts(c->name);
	if (c->unit >= 0)
		log_putu(c->unit);
}

void
ctl_log(struct ctl *c)
{
	if (c->group[0] != 0) {
		log_puts(c->group);
		log_puts("/");
	}
	ctl_node_log(&c->node0);
	log_puts(".");
	log_puts(c->func);
	log_puts("=");
	switch (c->type) {
	case CTL_NONE:
		log_puts("none");
		break;
	case CTL_NUM:
	case CTL_SW:
		log_putu(c->curval);
		break;
	case CTL_VEC:
	case CTL_LIST:
	case CTL_SEL:
		ctl_node_log(&c->node1);
		log_puts(":");
		log_putu(c->curval);
	}
	log_puts(" at ");
	log_putu(c->addr);
	log_puts(" -> ");
	switch (c->scope) {
	case CTL_HW:
		log_puts("hw:");
		log_puts(c->u.hw.dev->name);
		log_puts("/");
		log_putu(c->u.hw.addr);
		break;
	case CTL_DEV_MASTER:
		log_puts("dev_master:");
		log_puts(c->u.dev_master.dev->name);
		break;
	case CTL_DEV_ALT:
		log_puts("dev_alt:");
		log_puts(c->u.dev_alt.dev->name);
		log_putu(c->u.dev_alt.idx);
		break;
	case CTL_SLOT_LEVEL:
		log_puts("slot_level:");
		log_puts(c->u.slot_level.slot->name);
		log_putu(c->u.slot_level.slot->unit);
		break;
	default:
		log_puts("unknown");
	}
}

int
ctl_setval(struct ctl *c, int val)
{
	if (c->curval == val) {
		if (log_level >= 3) {
			ctl_log(c);
			log_puts(": already set\n");
		}
		return 1;
	}
	if (val < 0 || val > c->maxval) {
		if (log_level >= 3) {
			log_putu(val);
			log_puts(": ctl val out of bounds\n");
		}
		return 0;
	}

	switch (c->scope) {
	case CTL_HW:
		if (log_level >= 3) {
			ctl_log(c);
			log_puts(": marked as dirty\n");
		}
		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_DEV_ALT:
		dev_setalt (c->u.dev_alt.dev, c->u.dev_alt.idx);
		return 1;
	case CTL_SLOT_LEVEL:
		slot_setvol(c->u.slot_level.slot, val);
		// XXX change dev_midi_vol() into slot_midi_vol()
		dev_midi_vol(c->u.slot_level.slot->opt->dev, c->u.slot_level.slot);
		c->val_mask = ~0U;
		c->curval = val;
		return 1;
	default:
		if (log_level >= 2) {
			ctl_log(c);
			log_puts(": not writable\n");
		}
		return 1;
	}
}

/*
 * add a ctl
 */
struct ctl *
ctl_new(int scope, void *arg0, void *arg1,
    int type, char *gstr,
    char *str0, int unit0, char *func,
    char *str1, int unit1, int maxval, int val)
{
	struct ctl *c, **pc;
	struct ctlslot *s;
	int addr;
	int i;

	/*
	 * find the smallest unused addr number and
	 * the last position in the list
	 */
	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->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_DEV_ALT:
		c->u.dev_alt.idx = *(unsigned int *)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
	if (log_level >= 2) {
		ctl_log(c);
		log_puts(": added\n");
	}
#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;

		/* nothing to do if no visibility change */
		if (((c->refs_mask & s->self) ^ refs_mask) == 0)
			continue;
		/* if control becomes visble */
		if (refs_mask)
			c->refs_mask |= s->self;
		/* if control is hidden */
		c->desc_mask |= s->self;
	}
}

int
ctl_match(struct ctl *c, int scope, void *arg0, void *arg1)
{
	if (c->type == CTL_NONE || c->scope != scope || c->u.any.arg0 != arg0)
		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_DEV_ALT:
		if (arg1 != NULL && c->u.dev_alt.idx != *(unsigned int *)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;
}

void
ctl_del(int scope, void *arg0, void *arg1)
{
	struct ctl *c, **pc;

	pc = &ctl_list;
	for (;;) {
		c = *pc;
		if (c == NULL)
			return;
		if (ctl_match(c, scope, arg0, arg1)) {
#ifdef DEBUG
			if (log_level >= 2) {
				ctl_log(c);
				log_puts(": removed\n");
			}
#endif
			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;
	}
}

void
dev_ctlsync(struct dev *d)
{
	struct ctl *c;
	struct ctlslot *s;
	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) {
		if (log_level >= 2) {
			dev_log(d);
			log_puts(": software master level control disabled\n");
		}
		d->master_enabled = 0;
		ctl_del(CTL_DEV_MASTER, d, NULL);
	} else if (!d->master_enabled && !found) {
		if (log_level >= 2) {
			dev_log(d);
			log_puts(": software master level control enabled\n");
		}
		d->master_enabled = 1;
		ctl_new(CTL_DEV_MASTER, d, NULL,
		    CTL_NUM, d->name, "output", -1, "level",
		    NULL, -1, 127, d->master);
	}

	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);
	}
}