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

File: [local] / src / usr.bin / aucat / Attic / midi.c (download)

Revision 1.13, Tue Nov 3 21:31:37 2009 UTC (14 years, 7 months ago) by ratchov
Branch: MAIN
Changes since 1.12: +309 -5 lines

Allow any program using aucat to act as MMC slave and MTC master
transparently.  Multiple audio applications can be started
synchronously from external software/hardware supporting the
standard Start/Stop/Relocate messages. The server clock is exposed
through MTC, allowing non-audio software/hardware to be
synchronized to audio applications.

/*	$OpenBSD: midi.c,v 1.13 2009/11/03 21:31:37 ratchov Exp $	*/
/*
 * 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.
 */
/*
 * TODO
 *
 * use shadow variables (to save NRPNs, LSB of controller) 
 * in the midi merger
 *
 * make output and input identical when only one
 * input is used (fix running status)
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "abuf.h"
#include "aproc.h"
#include "conf.h"
#include "dev.h"
#include "midi.h"

/*
 * input data rate is XFER / TIMO (in bytes per microsecond),
 * it must be slightly larger than the MIDI standard 3125 bytes/s
 */ 
#define MIDITHRU_XFER 340
#define MIDITHRU_TIMO 100000

/*
 * masks to extract command and channel of status byte
 */
#define MIDI_CMDMASK	0xf0
#define MIDI_CHANMASK	0x0f

/*
 * MIDI status bytes of voice messages
 */
#define MIDI_NOFF	0x80		/* note off */
#define MIDI_NON	0x90		/* note on */
#define MIDI_KAT	0xa0		/* key after touch */
#define MIDI_CTL	0xb0		/* controller */
#define MIDI_PC		0xc0		/* program change */
#define MIDI_CAT	0xd0		/* channel after touch */
#define MIDI_BEND	0xe0		/* pitch bend */

/*
 * MIDI controller numbers
 */
#define MIDI_CTLVOL	7		/* volume */
#define MIDI_CTLPAN	11		/* pan */

/*
 * length of voice and common messages (status byte included)
 */
unsigned voice_len[] = { 3, 3, 3, 3, 2, 2, 3 };
unsigned common_len[] = { 0, 2, 3, 2, 0, 0, 1, 1 };

/*
 * send the message stored in of ibuf->r.midi.msg to obuf
 */
void
thru_flush(struct aproc *p, struct abuf *ibuf, struct abuf *obuf)
{
	unsigned ocount, itodo;
	unsigned char *odata, *idata;

	itodo = ibuf->r.midi.used;
	idata = ibuf->r.midi.msg;
	while (itodo > 0) {
		if (!ABUF_WOK(obuf)) {
			abuf_rdiscard(obuf, obuf->used);
			if (p->u.thru.owner == ibuf)
				p->u.thru.owner = NULL;
			return;
		}
		odata = abuf_wgetblk(obuf, &ocount, 0);
		if (ocount > itodo)
			ocount = itodo;
		memcpy(odata, idata, ocount);
		abuf_wcommit(obuf, ocount);
		itodo -= ocount;
		idata += ocount;
	}
	ibuf->r.midi.used = 0;
	p->u.thru.owner = ibuf;
}

/*
 * send the real-time message (one byte) to obuf, similar to thrui_flush()
 */
void
thru_rt(struct aproc *p, struct abuf *ibuf, struct abuf *obuf, unsigned c)
{
	unsigned ocount;
	unsigned char *odata;

	if (!ABUF_WOK(obuf)) {
		abuf_rdiscard(obuf, obuf->used);
		if (p->u.thru.owner == ibuf)
			p->u.thru.owner = NULL;
	}
	odata = abuf_wgetblk(obuf, &ocount, 0);
	odata[0] = c;
	abuf_wcommit(obuf, 1);
}

/*
 * parse ibuf contents and store each message into obuf,
 * use at most ``todo'' bytes (for throttling)
 */
void
thru_bcopy(struct aproc *p, struct abuf *ibuf, struct abuf *obuf, unsigned todo)
{
	unsigned char *idata;
	unsigned c, icount, ioffs;

	idata = NULL;
	icount = ioffs = 0;
	for (;;) {
		if (icount == 0) {
			if (todo == 0)
				break;
			idata = abuf_rgetblk(ibuf, &icount, ioffs);
			if (icount > todo)
				icount = todo;
			if (icount == 0)
				break;
			todo -= icount;
			ioffs += icount;
		}
		c = *idata++;
		icount--;
		if (c < 0x80) {
			if (ibuf->r.midi.idx == 0 && ibuf->r.midi.st) {
				ibuf->r.midi.msg[ibuf->r.midi.used++] = ibuf->r.midi.st;
				ibuf->r.midi.idx++;
			}
			ibuf->r.midi.msg[ibuf->r.midi.used++] = c;
			ibuf->r.midi.idx++;
			if (ibuf->r.midi.idx == ibuf->r.midi.len) {
				thru_flush(p, ibuf, obuf);
				if (ibuf->r.midi.st >= 0xf0)
					ibuf->r.midi.st = 0;
				ibuf->r.midi.idx = 0;
			}
			if (ibuf->r.midi.used == MIDI_MSGMAX) {
				if (ibuf->r.midi.used == ibuf->r.midi.idx ||
				    p->u.thru.owner == ibuf)
					thru_flush(p, ibuf, obuf);
				else
					ibuf->r.midi.used = 0;
			}
		} else if (c < 0xf8) {
			if (ibuf->r.midi.used == ibuf->r.midi.idx ||
			    p->u.thru.owner == ibuf) {
				thru_flush(p, ibuf, obuf);
			} else
				ibuf->r.midi.used = 0;
			ibuf->r.midi.msg[0] = c;
			ibuf->r.midi.used = 1;
			ibuf->r.midi.len = (c >= 0xf0) ? 
			    common_len[c & 7] :
			    voice_len[(c >> 4) & 7];
			if (ibuf->r.midi.len == 1) {
				thru_flush(p, ibuf, obuf);
				ibuf->r.midi.idx = 0;
				ibuf->r.midi.st = 0;
				ibuf->r.midi.len = 0;
			} else { 
				ibuf->r.midi.st = c;
				ibuf->r.midi.idx = 1;
			}
		} else {
			thru_rt(p, ibuf, obuf, c);
		}
	}
}

int
thru_in(struct aproc *p, struct abuf *ibuf)
{
	struct abuf *i, *inext;
	unsigned todo;

	if (!ABUF_ROK(ibuf))
		return 0;
	if (ibuf->tickets == 0) {
		return 0;
	}
	todo = ibuf->used;
	if (todo > ibuf->tickets)
		todo = ibuf->tickets;
	ibuf->tickets -= todo;
	for (i = LIST_FIRST(&p->obuflist); i != NULL; i = inext) {
		inext = LIST_NEXT(i, oent);
		if (ibuf->duplex == i)
			continue;
		thru_bcopy(p, ibuf, i, todo);
		(void)abuf_flush(i);
	}
	abuf_rdiscard(ibuf, todo);
	return 1;
}

int
thru_out(struct aproc *p, struct abuf *obuf)
{
	return 0;
}

void
thru_eof(struct aproc *p, struct abuf *ibuf)
{
	if (!(p->flags & APROC_QUIT))
		return;
	if (LIST_EMPTY(&p->obuflist) || LIST_EMPTY(&p->ibuflist))
		aproc_del(p);
}

void
thru_hup(struct aproc *p, struct abuf *obuf)
{
	if (!(p->flags & APROC_QUIT))
		return;
	if (LIST_EMPTY(&p->obuflist) || LIST_EMPTY(&p->ibuflist))
		aproc_del(p);
}

void
thru_newin(struct aproc *p, struct abuf *ibuf)
{
	ibuf->r.midi.used = 0;
	ibuf->r.midi.len = 0;
	ibuf->r.midi.idx = 0;
	ibuf->r.midi.st = 0;
	ibuf->tickets = MIDITHRU_XFER;
}

void
thru_done(struct aproc *p)
{
	timo_del(&p->u.thru.timo);
}

struct aproc_ops thru_ops = {
	"thru",
	thru_in,
	thru_out,
	thru_eof,
	thru_hup,
	thru_newin,
	NULL, /* newout */
	NULL, /* ipos */
	NULL, /* opos */
	thru_done
};

/*
 * call-back invoked periodically to implement throttling at each invocation
 * gain more ``tickets'' for processing.  If one of the buffer was blocked by
 * the throttelling mechanism, then run it
 */
void
thru_cb(void *addr)
{
	struct aproc *p = (struct aproc *)addr;
	struct abuf *i, *inext;
	unsigned tickets;

	timo_add(&p->u.thru.timo, MIDITHRU_TIMO);
	
	for (i = LIST_FIRST(&p->ibuflist); i != NULL; i = inext) {
		inext = LIST_NEXT(i, ient);
		tickets = i->tickets;
		i->tickets = MIDITHRU_XFER;
		if (tickets == 0)
			abuf_run(i);
	}
}

struct aproc *
thru_new(char *name)
{
	struct aproc *p;

	p = aproc_new(&thru_ops, name);
	p->u.thru.owner = NULL;
	timo_set(&p->u.thru.timo, thru_cb, p);
	timo_add(&p->u.thru.timo, MIDITHRU_TIMO);
	return p;
}


/*
 * broadcast a message to all output buffers on the behalf of ibuf.
 * ie. don't sent back the message to the sender
 */
void
ctl_sendmsg(struct aproc *p, struct abuf *ibuf, unsigned char *msg, unsigned len)
{
	unsigned ocount, itodo;
	unsigned char *odata, *idata;
	struct abuf *i, *inext;

	for (i = LIST_FIRST(&p->obuflist); i != NULL; i = inext) {
		inext = LIST_NEXT(i, oent);
		if (i->duplex == ibuf)
			continue;
		itodo = len;
		idata = msg;
		while (itodo > 0) {
			if (!ABUF_WOK(i)) {
				abuf_rdiscard(i, i->used);
			}
			odata = abuf_wgetblk(i, &ocount, 0);
			if (ocount > itodo)
				ocount = itodo;
			memcpy(odata, idata, ocount);
			abuf_wcommit(i, ocount);
			itodo -= ocount;
			idata += ocount;
		}
		(void)abuf_flush(i);
	}
}

/*
 * send a quarter frame MTC message
 */
void
ctl_qfr(struct aproc *p)
{
	unsigned char buf[2];
	unsigned data;

	switch (p->u.ctl.qfr) {
	case 0:
		data = p->u.ctl.fr & 0xf;
		break;
	case 1:
		data = p->u.ctl.fr >> 4;
		break;
	case 2:
		data = p->u.ctl.sec & 0xf;
		break;
	case 3:
		data = p->u.ctl.sec >> 4;
		break;
	case 4:
		data = p->u.ctl.min & 0xf;
		break;
	case 5:
		data = p->u.ctl.min >> 4;
		break;
	case 6:
		data = p->u.ctl.hr & 0xf;
		break;
	case 7:
		data = (p->u.ctl.hr >> 4) | (p->u.ctl.fps_id << 1);
		/*
		 * tick messages are sent 2 frames ahead
		 */
		p->u.ctl.fr += 2;
		if (p->u.ctl.fr < p->u.ctl.fps)
			break;
		p->u.ctl.fr -= p->u.ctl.fps;
		p->u.ctl.sec++;
		if (p->u.ctl.sec < 60)
			break;;
		p->u.ctl.sec = 0;
		p->u.ctl.min++;
		if (p->u.ctl.min < 60)
			break;
		p->u.ctl.min = 0;
		p->u.ctl.hr++;
		if (p->u.ctl.hr < 24)
			break;
		p->u.ctl.hr = 0;
		break;
	default:
		/* NOTREACHED */
		data = 0;
	}
	buf[0] = 0xf1;
	buf[1] = (p->u.ctl.qfr << 4) | data;
	p->u.ctl.qfr++;
	p->u.ctl.qfr &= 7;
	ctl_sendmsg(p, NULL, buf, 2);
}

/*
 * send a full frame MTC message
 */
void
ctl_full(struct aproc *p)
{
	unsigned char buf[10];
	unsigned origin = p->u.ctl.origin;
	unsigned fps = p->u.ctl.fps;

	p->u.ctl.hr =  (origin / (3600 * MTC_SEC)) % 24;
	p->u.ctl.min = (origin / (60 * MTC_SEC))   % 60;
	p->u.ctl.sec = (origin / MTC_SEC)          % 60;
	p->u.ctl.fr =  (origin / (MTC_SEC / fps))  % fps;

	buf[0] = 0xf0;
	buf[1] = 0x7f;
	buf[2] = 0x7f;
	buf[3] = 0x01;
	buf[4] = 0x01;
	buf[5] = p->u.ctl.hr | (p->u.ctl.fps_id << 5);
	buf[6] = p->u.ctl.min;
	buf[7] = p->u.ctl.sec;
	buf[8] = p->u.ctl.fr;
	buf[9] = 0xf7;
	p->u.ctl.qfr = 0;
	ctl_sendmsg(p, NULL, buf, 10);
}

/*
 * find the best matching free slot index (ie midi channel).
 * return -1, if there are no free slots anymore
 */
int
ctl_getidx(struct aproc *p, char *who)
{
	char *s;
	struct ctl_slot *slot;
	char name[CTL_NAMEMAX];
	unsigned i, unit, umap = 0;
	unsigned ser, bestser, bestidx;

	/*
	 * create a ``valid'' control name (lowcase, remove [^a-z], trucate)
	 */
	for (i = 0, s = who; ; s++) {
		if (i == CTL_NAMEMAX - 1 || *s == '\0') {
			name[i] = '\0';
			break;
		} else if (*s >= 'A' && *s <= 'Z') {
			name[i++] = *s + 'a' - 'A';
		} else if (*s >= 'a' && *s <= 'z')
			name[i++] = *s;
	}
	if (i == 0)
		strlcpy(name, "noname", CTL_NAMEMAX);

	/*
	 * find the instance number of the control name
	 */
	for (i = 0, slot = p->u.ctl.slot; i < CTL_NSLOT; i++, slot++) {
		if (slot->ops == NULL)
			continue;
		if (strcmp(slot->name, name) == 0)
			umap |= (1 << i);
	} 
	for (unit = 0; ; unit++) {
		if (unit == CTL_NSLOT)
			return -1;
		if ((umap & (1 << unit)) == 0)
			break;
	}
	/*
	 * find a free controller slot with the same name/unit
	 */
	for (i = 0, slot = p->u.ctl.slot; i < CTL_NSLOT; i++, slot++) {
		if (slot->ops == NULL &&
		    strcmp(slot->name, name) == 0 &&
		    slot->unit == unit) {
			return i;
		}
	}

	/*
	 * couldn't find a matching slot, pick oldest free slot
	 * and set its name/unit
	 */
	bestser = 0;
	bestidx = CTL_NSLOT;
	for (i = 0, slot = p->u.ctl.slot; i < CTL_NSLOT; i++, slot++) {
		if (slot->ops != NULL)
			continue;
		ser = p->u.ctl.serial - slot->serial;
		if (ser > bestser) {
			bestser = ser;
			bestidx = i;
		}
	}
	if (bestidx == CTL_NSLOT)
		return -1;
	slot = p->u.ctl.slot + bestidx;
	strlcpy(slot->name, name, CTL_NAMEMAX);
	slot->serial = p->u.ctl.serial++;
	slot->unit = unit;
	slot->vol = MIDI_MAXCTL;
	return bestidx;
}

/*
 * check that all clients controlled by MMC are ready to start,
 * if so, start them all but the caller
 */
int
ctl_trystart(struct aproc *p, int caller)
{
	unsigned i;
	struct ctl_slot *s;

	if (p->u.ctl.tstate != CTL_START) {
		return 0;
	}
	for (i = 0, s = p->u.ctl.slot; i < CTL_NSLOT; i++, s++) {
		if (!s->ops || i == caller)
			continue;
		if (s->tstate != CTL_OFF && s->tstate != CTL_START) {
			return 0;
		}
	}
	for (i = 0, s = p->u.ctl.slot; i < CTL_NSLOT; i++, s++) {
		if (!s->ops || i == caller)
			continue;
		if (s->tstate == CTL_START) {
			s->tstate = CTL_RUN;
			s->ops->start(s->arg);
		}
	}
	if (caller >= 0)
		p->u.ctl.slot[caller].tstate = CTL_RUN;
	p->u.ctl.tstate = CTL_RUN;
	p->u.ctl.delta = MTC_SEC * dev_getpos();
	if (dev_rate % (30 * 4 * dev_round)) {
		p->u.ctl.fps_id = MTC_FPS_30;
		p->u.ctl.fps = 30;
	} else if (dev_rate % (25 * 4 * dev_round)) {
		p->u.ctl.fps_id = MTC_FPS_25;
		p->u.ctl.fps = 25;
	} else {
		p->u.ctl.fps_id = MTC_FPS_24;
		p->u.ctl.fps = 24;
	} 
	ctl_full(p);
	return 1;
}

/*
 * allocate a new slot and register the given call-backs
 */
int
ctl_slotnew(struct aproc *p, char *who, struct ctl_ops *ops, void *arg, int tr)
{
	int idx;
	struct ctl_slot *s;

	if (p == NULL)
		return -1;
	idx = ctl_getidx(p, who);
	if (idx < 0)
		return -1;

	s = p->u.ctl.slot + idx;
	s->ops = ops;
	s->arg = arg;
	s->tstate = tr ? CTL_STOP : CTL_OFF;
	s->ops->vol(s->arg, s->vol);
	ctl_slotvol(p, idx, s->vol);
	return idx;
}

/*
 * release the given slot
 */
void
ctl_slotdel(struct aproc *p, int index)
{
	unsigned i;
	struct ctl_slot *s;

	if (p == NULL)
		return;
	p->u.ctl.slot[index].ops = NULL;
	if (!(p->flags & APROC_QUIT))
		return;
	for (i = 0, s = p->u.ctl.slot; i < CTL_NSLOT; i++, s++) {
		if (s->ops)
			return;
	}
	if (!LIST_EMPTY(&p->obuflist) || !LIST_EMPTY(&p->ibuflist))
		aproc_del(p);
}

/*
 * called at every clock tick by the mixer, delta is positive, unless
 * there's an overrun/underrun
 */
void
ctl_ontick(struct aproc *p, int delta)
{
	int qfrlen;

	/*
	 * don't send ticks before the start signal
	 */
	if (p->u.ctl.tstate != CTL_RUN)
		return;
	
	p->u.ctl.delta += delta * MTC_SEC;

	/*
	 * don't send ticks during the count-down
	 */
	if (p->u.ctl.delta < 0)
		return;

	qfrlen = dev_rate * (MTC_SEC / (4 * p->u.ctl.fps));
	while (p->u.ctl.delta >= qfrlen) {
		ctl_qfr(p);
		p->u.ctl.delta -= qfrlen;
	}
}

/*
 * notifty the mixer that volume changed, called by whom allocad the slot using
 * ctl_slotnew(). Note: it doesn't make sens to call this from within the
 * call-back.
 */
void
ctl_slotvol(struct aproc *p, int slot, unsigned vol)
{
	unsigned char msg[3];

	if (p == NULL)
		return;
	p->u.ctl.slot[slot].vol = vol;
	msg[0] = MIDI_CTL | slot;
	msg[1] = MIDI_CTLVOL;
	msg[2] = vol;
	ctl_sendmsg(p, NULL, msg, 3);
}

/*
 * 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.
 */
int
ctl_slotstart(struct aproc *p, int slot)
{
	struct ctl_slot *s = p->u.ctl.slot + slot;

	if (p == NULL)
		return 1;
	if (s->tstate == CTL_OFF || p->u.ctl.tstate == CTL_OFF)
		return 1;

	/*
	 * if the server already started (the client missed the
	 * start rendez-vous) or the server is stopped, then
	 * tag the client as ``wanting to start''
	 */
	s->tstate = CTL_START;
	return ctl_trystart(p, slot);
}

/*
 * notify the MMC layer that the stream no longer is trying to
 * start (or that it just stopped), meaning that its ``start'' call-back
 * shouldn't be called anymore
 */
void
ctl_slotstop(struct aproc *p, int slot)
{
	struct ctl_slot *s = p->u.ctl.slot + slot;

	if (p == NULL)
		return;
	/*
	 * tag the stream as not trying to start,
	 * unless MMC is turned off
	 */
	if (s->tstate != CTL_OFF)
		s->tstate = CTL_STOP;
}

/*
 * handle a MIDI event received from ibuf
 */
void
ctl_ev(struct aproc *p, struct abuf *ibuf)
{
	unsigned chan;
	struct ctl_slot *slot;
	unsigned fps;
	if ((ibuf->r.midi.msg[0] & MIDI_CMDMASK) == MIDI_CTL &&
	    ibuf->r.midi.msg[1] == MIDI_CTLVOL) {
		chan = ibuf->r.midi.msg[0] & MIDI_CHANMASK;
		if (chan >= CTL_NSLOT)
			return;
		slot = p->u.ctl.slot + chan;
		if (slot->ops == NULL)
			return;
		slot->vol = ibuf->r.midi.msg[2];
		slot->ops->vol(slot->arg, slot->vol);
		ctl_sendmsg(p, ibuf, ibuf->r.midi.msg, ibuf->r.midi.len);
	}
	if (ibuf->r.midi.idx == 6 &&
	    ibuf->r.midi.msg[0] == 0xf0 &&
	    ibuf->r.midi.msg[1] == 0x7f &&	/* type is realtime */
	    ibuf->r.midi.msg[3] == 0x06	&&	/* subtype is mmc */
	    ibuf->r.midi.msg[5] == 0xf7) {	/* subtype is mmc */
		switch (ibuf->r.midi.msg[4]) {
		case 0x01:	/* mmc stop */
			if (p->u.ctl.tstate == CTL_RUN ||
		            p->u.ctl.tstate == CTL_START) 
				p->u.ctl.tstate = CTL_STOP;
			break;
		case 0x02:	/* mmc start */
			if (p->u.ctl.tstate == CTL_STOP) {
				p->u.ctl.tstate = CTL_START;
				(void)ctl_trystart(p, -1);
			}
			break;
		}
	}
	if (ibuf->r.midi.idx == 13 &&
	    ibuf->r.midi.msg[0] == 0xf0 &&
	    ibuf->r.midi.msg[1] == 0x7f &&
	    ibuf->r.midi.msg[3] == 0x06 &&
	    ibuf->r.midi.msg[4] == 0x44 &&
	    ibuf->r.midi.msg[5] == 0x06 &&
	    ibuf->r.midi.msg[6] == 0x01 &&
	    ibuf->r.midi.msg[12] == 0xf7) {
		switch (ibuf->r.midi.msg[7] >> 5) {
		case MTC_FPS_24:
			fps = 24;
			break;
		case MTC_FPS_25:
			fps = 25;
			break;
		case MTC_FPS_30:
			fps = 30;
			break;
		default:
			p->u.ctl.origin = 0;
			return;
		}
		p->u.ctl.origin =
		    (ibuf->r.midi.msg[7] & 0x1f) * 3600 * MTC_SEC +
		    ibuf->r.midi.msg[8] * 60 * MTC_SEC +
		    ibuf->r.midi.msg[9] * MTC_SEC +
		    ibuf->r.midi.msg[10] * (MTC_SEC / fps) +
		    ibuf->r.midi.msg[11] * (MTC_SEC / 100 / fps);
	}
}

int
ctl_in(struct aproc *p, struct abuf *ibuf)
{
	unsigned char *idata;
	unsigned c, i, icount;

	if (!ABUF_ROK(ibuf))
		return 0;
	idata = abuf_rgetblk(ibuf, &icount, 0);
	for (i = 0; i < icount; i++) {
		c = *idata++;
		if (c >= 0xf8) {
			/* clock events not used yet */
		} else if (c >= 0xf0) {
			if (ibuf->r.midi.st == 0xf0 && c == 0xf7 &&
			    ibuf->r.midi.idx < MIDI_MSGMAX) {
				ibuf->r.midi.msg[ibuf->r.midi.idx++] = c;
				ctl_ev(p, ibuf);
				continue;
			}
			ibuf->r.midi.msg[0] = c;
			ibuf->r.midi.len = common_len[c & 7];
			ibuf->r.midi.st = c;
			ibuf->r.midi.idx = 1;
		} else if (c >= 0x80) {
			ibuf->r.midi.msg[0] = c;
			ibuf->r.midi.len = voice_len[(c >> 4) & 7];
			ibuf->r.midi.st = c;
			ibuf->r.midi.idx = 1;
		} else if (ibuf->r.midi.st) {
			if (ibuf->r.midi.idx == MIDI_MSGMAX)
				continue;		
			if (ibuf->r.midi.idx == 0)
				ibuf->r.midi.msg[ibuf->r.midi.idx++] = ibuf->r.midi.st;
			ibuf->r.midi.msg[ibuf->r.midi.idx++] = c;
			if (ibuf->r.midi.idx == ibuf->r.midi.len) {
				ctl_ev(p, ibuf);
				ibuf->r.midi.idx = 0;
			}
		}
	}
	abuf_rdiscard(ibuf, icount);
	return 1;
}

int
ctl_out(struct aproc *p, struct abuf *obuf)
{
	return 0;
}

void
ctl_eof(struct aproc *p, struct abuf *ibuf)
{
	unsigned i;
	struct ctl_slot *s;

	if (!(p->flags & APROC_QUIT))
		return;
	for (i = 0, s = p->u.ctl.slot; i < CTL_NSLOT; i++, s++) {
		if (s->ops)
			return;
	}
	if (!LIST_EMPTY(&p->obuflist) || !LIST_EMPTY(&p->ibuflist))
		aproc_del(p);
}

void
ctl_hup(struct aproc *p, struct abuf *obuf)
{
	unsigned i;
	struct ctl_slot *s;

	if (!(p->flags & APROC_QUIT))
		return;
	for (i = 0, s = p->u.ctl.slot; i < CTL_NSLOT; i++, s++) {
		if (s->ops)
			return;
	}
	if (!LIST_EMPTY(&p->obuflist) || !LIST_EMPTY(&p->ibuflist))
		aproc_del(p);
}

void
ctl_newin(struct aproc *p, struct abuf *ibuf)
{
	ibuf->r.midi.used = 0;
	ibuf->r.midi.len = 0;
	ibuf->r.midi.idx = 0;
	ibuf->r.midi.st = 0;
}

void
ctl_done(struct aproc *p)
{
}

struct aproc_ops ctl_ops = {
	"ctl",
	ctl_in,
	ctl_out,
	ctl_eof,
	ctl_hup,
	ctl_newin,
	NULL, /* newout */
	NULL, /* ipos */
	NULL, /* opos */
	ctl_done
};

struct aproc *
ctl_new(char *name)
{
	struct aproc *p;
	struct ctl_slot *s;
	unsigned i;

	p = aproc_new(&ctl_ops, name);
	p->u.ctl.serial = 0;
	p->u.ctl.tstate = CTL_STOP;
	for (i = 0, s = p->u.ctl.slot; i < CTL_NSLOT; i++, s++) {
		p->u.ctl.slot[i].unit = i;
		p->u.ctl.slot[i].ops = NULL;
		p->u.ctl.slot[i].vol = MIDI_MAXCTL;
		p->u.ctl.slot[i].tstate = CTL_OFF;
		p->u.ctl.slot[i].serial = p->u.ctl.serial++;
		strlcpy(p->u.ctl.slot[i].name, "unknown", CTL_NAMEMAX);
	}
	return p;
}