[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.39, Sun Nov 20 22:54:51 2011 UTC (12 years, 6 months ago) by ratchov
Branch: MAIN
Changes since 1.38: +107 -585 lines

Move mmc/mtc and volume control bits from struct aproc to struct
dev. Allows volume settings to be saved while the device is kept
closed. Besides that, no behabiour changes.

/*	$OpenBSD: midi.c,v 1.39 2011/11/20 22:54:51 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"
#include "sysex.h"
#ifdef DEBUG
#include "dbg.h"
#endif

/*
 * 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 */
#define MIDI_ACK	0xfe		/* active sensing message */

/*
 * 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 abuf *ibuf, struct abuf *obuf)
{
	unsigned ocount, itodo;
	unsigned char *odata, *idata;

	itodo = ibuf->r.midi.used;
	idata = ibuf->r.midi.msg;
#ifdef DEBUG
	if (debug_level >= 4) {
		abuf_dbg(obuf);
		dbg_puts(": flushing ");
		dbg_putu(itodo);
		dbg_puts(" byte message\n");
	}
#endif
	while (itodo > 0) {
		if (!ABUF_WOK(obuf)) {
#ifdef DEBUG
			if (debug_level >= 3) {
				abuf_dbg(obuf);
				dbg_puts(": overrun, discarding ");
				dbg_putu(obuf->used);
				dbg_puts(" bytes\n");
			}
#endif
			abuf_rdiscard(obuf, obuf->used);
			if (obuf->w.midi.owner == ibuf)
				obuf->w.midi.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;
	obuf->w.midi.owner = ibuf;
}

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

#ifdef DEBUG
	if (debug_level >= 4) {
		abuf_dbg(obuf);
		dbg_puts(": ");
		dbg_putx(c);
		dbg_puts(": flushing realtime message\n");
	}
#endif
	if (c == MIDI_ACK)
		return;
	if (!ABUF_WOK(obuf)) {
#ifdef DEBUG
		if (debug_level >= 3) {
			abuf_dbg(obuf);
			dbg_puts(": overrun, discarding ");
			dbg_putu(obuf->used);
			dbg_puts(" bytes\n");
		}
#endif
		abuf_rdiscard(obuf, obuf->used);
		if (obuf->w.midi.owner == ibuf)
			obuf->w.midi.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 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(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 ||
				    obuf->w.midi.owner == ibuf)
					thru_flush(ibuf, obuf);
				else
					ibuf->r.midi.used = 0;
			}
		} else if (c < 0xf8) {
			if (ibuf->r.midi.used == ibuf->r.midi.idx ||
			    obuf->w.midi.owner == ibuf) {
				thru_flush(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(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(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) {
#ifdef DEBUG
		if (debug_level >= 4) {
			abuf_dbg(ibuf);
			dbg_puts(": out of tickets, blocking\n");
		}
#endif
		return 0;
	}
	todo = ibuf->used;
	if (todo > ibuf->tickets)
		todo = ibuf->tickets;
	ibuf->tickets -= todo;
	for (i = LIST_FIRST(&p->outs); i != NULL; i = inext) {
		inext = LIST_NEXT(i, oent);
		if (ibuf->duplex == i)
			continue;
		thru_bcopy(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->ins))
		aproc_del(p);
}

void
thru_hup(struct aproc *p, struct abuf *obuf)
{
	if (!(p->flags & APROC_QUIT))
		return;
	if (LIST_EMPTY(&p->ins))
		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_newout(struct aproc *p, struct abuf *obuf)
{
	obuf->w.midi.owner = NULL;
}

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,
	thru_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->ins); 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);
	timo_set(&p->u.thru.timo, thru_cb, p);
	timo_add(&p->u.thru.timo, MIDITHRU_TIMO);
	return p;
}

/*
 * send a message to the given output
 */
void
ctl_copymsg(struct abuf *obuf, unsigned char *msg, unsigned len)
{
	unsigned ocount, itodo;
	unsigned char *odata, *idata;

	itodo = len;
	idata = msg;
	while (itodo > 0) {
		if (!ABUF_WOK(obuf)) {
#ifdef DEBUG
			if (debug_level >= 3) {
				abuf_dbg(obuf);
				dbg_puts(": overrun, discarding ");
				dbg_putu(obuf->used);
				dbg_puts(" bytes\n");
			}
#endif
			abuf_rdiscard(obuf, obuf->used);
		}
		odata = abuf_wgetblk(obuf, &ocount, 0);
		if (ocount > itodo)
			ocount = itodo;
#ifdef DEBUG
		if (debug_level >= 4) {
			abuf_dbg(obuf);
			dbg_puts(": stored ");
			dbg_putu(ocount);
			dbg_puts(" bytes\n");
		}
#endif
		memcpy(odata, idata, ocount);
		abuf_wcommit(obuf, ocount);
		itodo -= ocount;
		idata += ocount;
	}
}

/*
 * 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)
{
	struct abuf *i, *inext;

	for (i = LIST_FIRST(&p->outs); i != NULL; i = inext) {
		inext = LIST_NEXT(i, oent);
		if (i->duplex && i->duplex == ibuf)
			continue;
		ctl_copymsg(i, msg, len);
		(void)abuf_flush(i);
	}
}

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

	p->u.ctl.delta += delta * MTC_SEC;

	/*
	 * don't send ticks during the count-down
	 * XXX: test not useful, given while() condition
	 */
	if (p->u.ctl.delta < 0)
		return;

	qfrlen = rate * (MTC_SEC / (4 * p->u.ctl.fps));
	while (p->u.ctl.delta >= qfrlen) {
		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);
		p->u.ctl.delta -= qfrlen;
	}
}

/*
 * send a full frame MTC message
 */
void
ctl_full(struct aproc *p, unsigned origin, unsigned rate, unsigned round, unsigned pos)
{
	unsigned char buf[10];
	unsigned fps;

	p->u.ctl.delta = MTC_SEC * pos;
	if (rate % (30 * 4 * round) == 0) {
		p->u.ctl.fps_id = MTC_FPS_30;
		p->u.ctl.fps = 30;
	} else if (rate % (25 * 4 * round) == 0) {
		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;
	}
#ifdef DEBUG
	if (debug_level >= 3) {
		aproc_dbg(p);
		dbg_puts(": mtc full frame at ");
		dbg_puti(p->u.ctl.delta);
		dbg_puts(", ");
		dbg_puti(p->u.ctl.fps);
		dbg_puts(" fps\n");
	}
#endif
	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);
}

void
ctl_msg_info(struct aproc *p, int slot, char *msg)
{
	struct ctl_slot *s;
	struct sysex *x = (struct sysex *)msg;

	s = p->u.ctl.dev->slot + slot;
	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(x->u.mixinfo.name,
		    SYSEX_NAMELEN, "%s%u", s->name, s->unit);
	}
	x->u.mixinfo.chan = slot;
	x->u.mixinfo.end = SYSEX_END;
}

void
ctl_msg_vol(struct aproc *p, int slot, char *msg)
{
	struct ctl_slot *s;

	s = p->u.ctl.dev->slot + slot;	
	msg[0] = MIDI_CTL | slot;
	msg[1] = MIDI_CTLVOL;
	msg[2] = s->vol;
}

void
ctl_dump(struct aproc *p, struct abuf *obuf)
{
	unsigned i;
	unsigned char msg[sizeof(struct sysex)];
	struct ctl_slot *s;

	for (i = 0, s = p->u.ctl.dev->slot; i < CTL_NSLOT; i++, s++) {
		ctl_msg_info(p, i, msg);
		ctl_copymsg(obuf, msg, SYSEX_SIZE(mixinfo));
		ctl_msg_vol(p, i, msg);
		ctl_copymsg(obuf, msg, 3);
	}
	msg[0] = SYSEX_START;
	msg[1] = SYSEX_TYPE_EDU;
	msg[2] = 0;
	msg[3] = SYSEX_AUCAT;
	msg[4] = SYSEX_AUCAT_DUMPEND;
	msg[5] = SYSEX_END;
	ctl_copymsg(obuf, msg, 6);
	abuf_flush(obuf);
}

/*
 * 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_vol(struct aproc *p, int slot, unsigned vol)
{
	unsigned char msg[3];

	ctl_msg_vol(p, slot, msg);
	ctl_sendmsg(p, NULL, msg, 3);
}

void
ctl_slot(struct aproc *p, int slot)
{
	unsigned char msg[sizeof(struct sysex)];

	ctl_msg_info(p, slot, msg);
	ctl_sendmsg(p, NULL, msg, SYSEX_SIZE(mixinfo));
}

/*
 * handle a MIDI event received from ibuf
 */
void
ctl_ev(struct aproc *p, struct abuf *ibuf)
{
	unsigned chan;
	struct ctl_slot *slot;
	struct sysex *x;
	unsigned fps, len;
#ifdef DEBUG
	unsigned i;

	if (debug_level >= 3) {
		abuf_dbg(ibuf);
		dbg_puts(": got event:");
		for (i = 0; i < ibuf->r.midi.idx; i++) {
			dbg_puts(" ");
			dbg_putx(ibuf->r.midi.msg[i]);
		}
		dbg_puts("\n");
	}
#endif
	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.dev->slot + chan;
		slot->vol = ibuf->r.midi.msg[2];
		if (slot->ops == NULL)
			return;
		slot->ops->vol(slot->arg, slot->vol);
		ctl_sendmsg(p, ibuf, ibuf->r.midi.msg, ibuf->r.midi.len);
	}
	x = (struct sysex *)ibuf->r.midi.msg;
	len = ibuf->r.midi.idx;
	if (x->start != SYSEX_START)
		return;
	if (len < SYSEX_SIZE(empty))
		return;
	switch (x->type) {
	case SYSEX_TYPE_RT:
		if (x->id0 != SYSEX_MMC)
			return;
		switch (x->id1) {
		case SYSEX_MMC_STOP:
			if (len != SYSEX_SIZE(stop))
				return;
#ifdef DEBUG
			if (debug_level >= 3) {
				abuf_dbg(ibuf);
				dbg_puts(": mmc stop\n");
			}
#endif
			dev_mmcstop(p->u.ctl.dev);
			break;
		case SYSEX_MMC_START:
			if (len != SYSEX_SIZE(start))
				return;
#ifdef DEBUG
			if (debug_level >= 3) {
				abuf_dbg(ibuf);
				dbg_puts(": mmc start\n");
			}
#endif
			dev_mmcstart(p->u.ctl.dev);
			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:
				/* XXX: should dev_mmcstop() here */
				return;
			}
			dev_loc(p->u.ctl.dev,
			    (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;
		if (ibuf->duplex)
			ctl_dump(p, ibuf->duplex);
		break;
	}
}

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)
{
	if ((p->flags & APROC_QUIT) && LIST_EMPTY(&p->ins))
		aproc_del(p);
}

void
ctl_hup(struct aproc *p, struct abuf *obuf)
{
	if ((p->flags & APROC_QUIT) && LIST_EMPTY(&p->ins))
		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;
}

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

struct aproc *
ctl_new(char *name, struct dev *dev)
{
	struct aproc *p;

	p = aproc_new(&ctl_ops, name);
	p->u.ctl.dev = dev;
	return p;
}