summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/libsndio/aucat.c5
-rw-r--r--lib/libsndio/mio.c4
-rw-r--r--lib/libsndio/sndio.77
-rw-r--r--usr.bin/Makefile5
-rw-r--r--usr.bin/aucat/Makefile4
-rw-r--r--usr.bin/sndiod/Makefile8
-rw-r--r--usr.bin/sndiod/abuf.c139
-rw-r--r--usr.bin/sndiod/abuf.h35
-rw-r--r--usr.bin/sndiod/defs.h70
-rw-r--r--usr.bin/sndiod/dev.c1931
-rw-r--r--usr.bin/sndiod/dev.h230
-rw-r--r--usr.bin/sndiod/dsp.c711
-rw-r--r--usr.bin/sndiod/dsp.h161
-rw-r--r--usr.bin/sndiod/file.c478
-rw-r--r--usr.bin/sndiod/file.h81
-rw-r--r--usr.bin/sndiod/listen.c281
-rw-r--r--usr.bin/sndiod/listen.h36
-rw-r--r--usr.bin/sndiod/midi.c547
-rw-r--r--usr.bin/sndiod/midi.h119
-rw-r--r--usr.bin/sndiod/miofile.c136
-rw-r--r--usr.bin/sndiod/miofile.h30
-rw-r--r--usr.bin/sndiod/opt.c141
-rw-r--r--usr.bin/sndiod/opt.h42
-rw-r--r--usr.bin/sndiod/siofile.c416
-rw-r--r--usr.bin/sndiod/siofile.h43
-rw-r--r--usr.bin/sndiod/sndiod.1541
-rw-r--r--usr.bin/sndiod/sndiod.c491
-rw-r--r--usr.bin/sndiod/sock.c1666
-rw-r--r--usr.bin/sndiod/sock.h69
-rw-r--r--usr.bin/sndiod/sysex.h120
-rw-r--r--usr.bin/sndiod/utils.c182
-rw-r--r--usr.bin/sndiod/utils.h36
32 files changed, 8754 insertions, 11 deletions
diff --git a/lib/libsndio/aucat.c b/lib/libsndio/aucat.c
index e1e5211a3ea..c146616df28 100644
--- a/lib/libsndio/aucat.c
+++ b/lib/libsndio/aucat.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: aucat.c,v 1.55 2012/11/02 10:24:58 ratchov Exp $ */
+/* $OpenBSD: aucat.c,v 1.56 2012/11/23 07:03:28 ratchov Exp $ */
/*
* Copyright (c) 2008 Alexandre Ratchov <alex@caoua.org>
*
@@ -444,8 +444,7 @@ aucat_open(struct aucat *hdl, const char *str, unsigned int mode,
DPRINTF("%s: junk at end of dev name\n", p);
return 0;
}
- if (type)
- devnum += 16; /* XXX */
+ devnum += type * 16; /* XXX */
DPRINTF("aucat_open: host=%s unit=%u devnum=%u opt=%s\n",
host, unit, devnum, opt);
if (host[0] != '\0') {
diff --git a/lib/libsndio/mio.c b/lib/libsndio/mio.c
index 0f130dbe392..f965a148fb5 100644
--- a/lib/libsndio/mio.c
+++ b/lib/libsndio/mio.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: mio.c,v 1.16 2012/10/27 12:08:25 ratchov Exp $ */
+/* $OpenBSD: mio.c,v 1.17 2012/11/23 07:03:28 ratchov Exp $ */
/*
* Copyright (c) 2008 Alexandre Ratchov <alex@caoua.org>
*
@@ -61,6 +61,8 @@ mio_open(const char *str, unsigned int mode, int nbio)
return mio_aucat_open(p, mode, nbio, 0);
if ((p = sndio_parsetype(str, "midithru")) != NULL)
return mio_aucat_open(p, mode, nbio, 1);
+ if ((p = sndio_parsetype(str, "midi")) != NULL)
+ return mio_aucat_open(p, mode, nbio, 2);
if ((p = sndio_parsetype(str, "rmidi")) != NULL) {
return mio_rmidi_open(p, mode, nbio);
}
diff --git a/lib/libsndio/sndio.7 b/lib/libsndio/sndio.7
index 78461bc75ef..e1340a54a2e 100644
--- a/lib/libsndio/sndio.7
+++ b/lib/libsndio/sndio.7
@@ -1,4 +1,4 @@
-.\" $OpenBSD: sndio.7,v 1.10 2012/05/23 19:25:11 ratchov Exp $
+.\" $OpenBSD: sndio.7,v 1.11 2012/11/23 07:03:28 ratchov Exp $
.\"
.\" Copyright (c) 2007 Alexandre Ratchov <alex@caoua.org>
.\"
@@ -14,7 +14,7 @@
.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
.\"
-.Dd $Mdocdate: May 23 2012 $
+.Dd $Mdocdate: November 23 2012 $
.Dt SNDIO 7
.Os
.Sh NAME
@@ -93,6 +93,9 @@ Audio device exposed by
.It Pa midithru
MIDI thru box created with
.Xr sndiod 1 .
+.It Pa midi
+MIDI port exposed by
+.Xr sndiod 1 .
.It Pa default
Any audio device or MIDI port.
.El
diff --git a/usr.bin/Makefile b/usr.bin/Makefile
index 9a024e3975c..5682ebdcfc2 100644
--- a/usr.bin/Makefile
+++ b/usr.bin/Makefile
@@ -1,4 +1,4 @@
-# $OpenBSD: Makefile,v 1.125 2012/08/30 15:53:47 kettenis Exp $
+# $OpenBSD: Makefile,v 1.126 2012/11/23 07:03:28 ratchov Exp $
.include <bsd.own.mk>
@@ -21,7 +21,8 @@ SUBDIR= apply apropos ar arch asa asn1_compile at aucat audioctl awk banner \
pr printenv printf quota radioctl ranlib rcs rdist rdistd \
readlink renice rev rpcgen rpcinfo rs rsh rup ruptime rusers rwall \
rwho sdiff script sed sendbug shar showmount skey \
- skeyaudit skeyinfo skeyinit sort spell split sqlite3 ssh stat su systat \
+ skeyaudit skeyinfo skeyinit sndiod \
+ sort spell split sqlite3 ssh stat su systat \
sudo tail talk tcopy tcpbench tee telnet tftp tic time tip \
tmux top touch tput tr true tset tsort tty usbhidaction usbhidctl \
ul uname unexpand unifdef uniq units \
diff --git a/usr.bin/aucat/Makefile b/usr.bin/aucat/Makefile
index 18053abfc50..523da803605 100644
--- a/usr.bin/aucat/Makefile
+++ b/usr.bin/aucat/Makefile
@@ -1,4 +1,4 @@
-# $OpenBSD: Makefile,v 1.17 2011/12/09 14:36:42 ratchov Exp $
+# $OpenBSD: Makefile,v 1.18 2012/11/23 07:03:28 ratchov Exp $
PROG= aucat
SRCS= aucat.c abuf.c aparams.c aproc.c dev.c midi.c file.c headers.c \
@@ -6,6 +6,4 @@ SRCS= aucat.c abuf.c aparams.c aproc.c dev.c midi.c file.c headers.c \
MAN= aucat.1
CFLAGS+= -Wall -Wstrict-prototypes -Wundef -DDEBUG -I${.CURDIR}/../../lib/libsndio
LDADD+= -lsndio
-LINKS= ${BINDIR}/aucat ${BINDIR}/sndiod
-MLINKS = aucat.1 sndiod.1
.include <bsd.prog.mk>
diff --git a/usr.bin/sndiod/Makefile b/usr.bin/sndiod/Makefile
new file mode 100644
index 00000000000..e8c1eabfcaf
--- /dev/null
+++ b/usr.bin/sndiod/Makefile
@@ -0,0 +1,8 @@
+# $OpenBSD: Makefile,v 1.1 2012/11/23 07:03:28 ratchov Exp $
+
+PROG= sndiod
+SRCS= abuf.c dev.c dsp.c file.c listen.c midi.c miofile.c opt.c siofile.c sndiod.c sock.c utils.c
+MAN= sndiod.1
+CFLAGS+= -Wall -Wstrict-prototypes -Wundef -DDEBUG -I${.CURDIR}/../../lib/libsndio
+LDADD+= -lsndio
+.include <bsd.prog.mk>
diff --git a/usr.bin/sndiod/abuf.c b/usr.bin/sndiod/abuf.c
new file mode 100644
index 00000000000..be89cb3fef6
--- /dev/null
+++ b/usr.bin/sndiod/abuf.c
@@ -0,0 +1,139 @@
+/* $OpenBSD: abuf.c,v 1.1 2012/11/23 07:03:28 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.
+ */
+/*
+ * Simple byte fifo.
+ *
+ * The abuf data is split in two parts: (1) valid data available to the reader
+ * (2) space available to the writer, which is not necessarily unused. It works
+ * as follows: the write starts filling at offset (start + used), once the data
+ * is ready, the writer adds to used the count of bytes available.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "abuf.h"
+#include "defs.h"
+#include "utils.h"
+
+#ifdef DEBUG
+void
+abuf_log(struct abuf *buf)
+{
+ log_putu(buf->start);
+ log_puts("+");
+ log_putu(buf->used);
+ log_puts("/");
+ log_putu(buf->len);
+}
+#endif
+
+void
+abuf_init(struct abuf *buf, unsigned int len)
+{
+ buf->data = xmalloc(len);
+ buf->len = len;
+ buf->used = 0;
+ buf->start = 0;
+}
+
+void
+abuf_done(struct abuf *buf)
+{
+#ifdef DEBUG
+ if (buf->used > 0) {
+ if (log_level >= 3) {
+ log_puts("deleting non-empty buffer, used = ");
+ log_putu(buf->used);
+ log_puts("\n");
+ }
+ }
+#endif
+ xfree(buf->data);
+ buf->data = (void *)0xdeadbeef;
+}
+
+/*
+ * return the reader pointer and the number of bytes available
+ */
+unsigned char *
+abuf_rgetblk(struct abuf *buf, int *rsize)
+{
+ unsigned int count;
+
+ count = buf->len - buf->start;
+ if (count > buf->used)
+ count = buf->used;
+ *rsize = count;
+ return buf->data + buf->start;
+}
+
+/*
+ * discard "count" bytes at the start postion.
+ */
+void
+abuf_rdiscard(struct abuf *buf, int count)
+{
+#ifdef DEBUG
+ if (count < 0 || count > buf->used) {
+ log_puts("abuf_rdiscard: bad count = ");
+ log_putu(count);
+ log_puts("\n");
+ panic();
+ }
+#endif
+ buf->used -= count;
+ buf->start += count;
+ if (buf->start >= buf->len)
+ buf->start -= buf->len;
+}
+
+/*
+ * advance the writer pointer by "count" bytes
+ */
+void
+abuf_wcommit(struct abuf *buf, int count)
+{
+#ifdef DEBUG
+ if (count < 0 || count > (buf->len - buf->used)) {
+ log_puts("abuf_wcommit: bad count = ");
+ log_putu(count);
+ log_puts("\n");
+ panic();
+ }
+#endif
+ buf->used += count;
+}
+
+/*
+ * get writer pointer and the number of bytes writable
+ */
+unsigned char *
+abuf_wgetblk(struct abuf *buf, int *rsize)
+{
+ int end, avail, count;
+
+ end = buf->start + buf->used;
+ if (end >= buf->len)
+ end -= buf->len;
+ avail = buf->len - buf->used;
+ count = buf->len - end;
+ if (count > avail)
+ count = avail;
+ *rsize = count;
+ return buf->data + end;
+}
diff --git a/usr.bin/sndiod/abuf.h b/usr.bin/sndiod/abuf.h
new file mode 100644
index 00000000000..827e79eb9c0
--- /dev/null
+++ b/usr.bin/sndiod/abuf.h
@@ -0,0 +1,35 @@
+/* $OpenBSD: abuf.h,v 1.1 2012/11/23 07:03:28 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.
+ */
+#ifndef ABUF_H
+#define ABUF_H
+
+struct abuf {
+ int start; /* offset (frames) where stored data starts */
+ int used; /* frames stored in the buffer */
+ int len; /* total size of the buffer (frames) */
+ unsigned char *data;
+};
+
+void abuf_init(struct abuf *, unsigned int);
+void abuf_done(struct abuf *);
+void abuf_log(struct abuf *);
+unsigned char *abuf_rgetblk(struct abuf *, int *);
+unsigned char *abuf_wgetblk(struct abuf *, int *);
+void abuf_rdiscard(struct abuf *, int);
+void abuf_wcommit(struct abuf *, int);
+
+#endif /* !defined(ABUF_H) */
diff --git a/usr.bin/sndiod/defs.h b/usr.bin/sndiod/defs.h
new file mode 100644
index 00000000000..0ef7cf81418
--- /dev/null
+++ b/usr.bin/sndiod/defs.h
@@ -0,0 +1,70 @@
+/* $OpenBSD: defs.h,v 1.1 2012/11/23 07:03:28 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.
+ */
+#ifndef DEFS_H
+#define DEFS_H
+
+/*
+ * Log levels:
+ *
+ * 0 - fatal errors: bugs, asserts, internal errors.
+ * 1 - warnings: bugs in clients, failed allocations, non-fatal errors.
+ * 2 - misc information (hardware parameters, incoming clients)
+ * 3 - structural changes (new aproc structures and files stream params changes)
+ * 4 - data blocks and messages
+ */
+extern unsigned int log_level;
+
+/*
+ * MIDI buffer size
+ */
+#define MIDI_BUFSZ 3125 /* 1 second at 31.25kbit/s */
+
+/*
+ * units used for MTC clock.
+ */
+#define MTC_SEC 2400 /* 1 second is 2400 ticks */
+
+/*
+ * device or sub-device mode, must be a superset of corresponding SIO_
+ * and MIO_ constants
+ */
+#define MODE_PLAY 0x01 /* allowed to play */
+#define MODE_REC 0x02 /* allowed to rec */
+#define MODE_MIDIOUT 0x04 /* allowed to read midi */
+#define MODE_MIDIIN 0x08 /* allowed to write midi */
+#define MODE_MON 0x10 /* allowed to monitor */
+#define MODE_RECMASK (MODE_REC | MODE_MON)
+#define MODE_AUDIOMASK (MODE_PLAY | MODE_REC | MODE_MON)
+#define MODE_MIDIMASK (MODE_MIDIIN | MODE_MIDIOUT)
+
+/*
+ * underrun/overrun policies, must be the same as SIO_ constants
+ */
+#define XRUN_IGNORE 0 /* on xrun silently insert/discard samples */
+#define XRUN_SYNC 1 /* catchup to sync to the mix/sub */
+#define XRUN_ERROR 2 /* xruns are errors, eof/hup buffer */
+
+/*
+ * limits
+ */
+#define NCHAN_MAX 16 /* max channel in a stream */
+#define RATE_MIN 4000 /* min sample rate */
+#define RATE_MAX 192000 /* max sample rate */
+#define BITS_MIN 1 /* min bits per sample */
+#define BITS_MAX 32 /* max bits per sample */
+
+#endif /* !defined(DEFS_H) */
diff --git a/usr.bin/sndiod/dev.c b/usr.bin/sndiod/dev.c
new file mode 100644
index 00000000000..bb8f55856dc
--- /dev/null
+++ b/usr.bin/sndiod/dev.c
@@ -0,0 +1,1931 @@
+/* $OpenBSD: dev.c,v 1.1 2012/11/23 07:03:28 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"
+
+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_fill(void *, int);
+void dev_midi_exit(void *);
+
+struct midiops dev_midiops = {
+ dev_midi_imsg,
+ dev_midi_omsg,
+ dev_midi_fill,
+ 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);
+ if (s->ops) {
+ 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)
+{
+#ifdef DEBUG
+ struct slot *s = arg;
+
+ 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));
+}
+
+/*
+ * 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 - d->slot);
+ msg[1] = MIDI_CTL_VOL;
+ msg[2] = s->vol;
+ midi_send(d->midi, msg, 3);
+}
+
+/*
+ * send a master volume MIDI message
+ */
+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));
+}
+
+/*
+ * 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.id0 = SYSEX_AUCAT;
+ x.id1 = SYSEX_AUCAT_SLOTDESC;
+ if (*s->name != '\0') {
+ snprintf((char *)x.u.slotdesc.name, SYSEX_NAMELEN,
+ "%s%u", s->name, s->unit);
+ }
+ x.u.slotdesc.chan = s - d->slot;
+ x.u.slotdesc.end = SYSEX_END;
+ midi_send(d->midi, (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 = d->slot; i < DEV_NSLOT; i++, s++) {
+ dev_midi_slotdesc(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)
+{
+#ifdef DEBUG
+ struct dev *d = arg;
+
+ 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;
+
+ 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_fill(void *arg, int count)
+{
+#ifdef DEBUG
+ struct dev *d = arg;
+
+ dev_log(d);
+ log_puts(": can't receive fill input\n");
+ panic();
+#endif
+}
+
+void
+dev_midi_exit(void *arg)
+{
+ struct dev *d = arg;
+
+ if (log_level >= 1) {
+ dev_log(d);
+ log_puts(": midi end point died\n");
+ }
+ if (d->pstate != DEV_CFG)
+ dev_close(d);
+}
+
+void
+slot_mix_drop(struct slot *s)
+{
+ while (s->mix.drop > 0 && s->mix.buf.used >= s->round * s->mix.bpf) {
+#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.bpf);
+ 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 * s->sub.bpf)
+ 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.bpf);
+ abuf_wcommit(&s->sub.buf, s->round * s->sub.bpf);
+ 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;
+
+ 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
+ play_filt_dec(s, idata, odata, s->round);
+ abuf_rdiscard(&s->mix.buf, s->round * s->mix.bpf);
+}
+
+void
+dev_empty_cycle(struct dev *d)
+{
+ unsigned char *base;
+ int nsamp;
+
+ 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);
+ }
+}
+
+/*
+ * Normalize input levels.
+ */
+void
+dev_mix_adjvol(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++;
+ }
+ weight /= 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
+ }
+}
+
+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
+ 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->mix.buf.used < s->round * s->mix.bpf &&
+ s->pstate == SLOT_STOP) {
+ /*
+ * partial blocks are zero-filled by socket
+ * layer
+ */
+ s->pstate = SLOT_INIT;
+ abuf_done(&s->mix.buf);
+ if (s->mix.decbuf)
+ xfree(s->mix.decbuf);
+ if (s->mix.resampbuf)
+ xfree(s->mix.resampbuf);
+ s->ops->eof(s->arg);
+ *ps = s->next;
+ dev_mix_adjvol(d);
+ continue;
+ }
+ if (s->mix.buf.used < s->round * s->mix.bpf &&
+ !(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);
+ if (s->pstate != SLOT_STOP)
+ s->ops->fill(s->arg);
+ }
+ ps = &s->next;
+ }
+ if (d->encbuf) {
+ enc_do(&d->enc, (unsigned char *)DEV_PBUF(d),
+ d->encbuf, d->round);
+ }
+}
+
+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;
+
+ 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 * s->sub.bpf) {
+ 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 * s->sub.bpf);
+}
+
+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->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) {
+ 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 * s->sub.bpf) {
+ 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;
+ 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_adjvol(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");
+ }
+ dev_sio_stop(d);
+ 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_empty_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 : 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++;
+
+ /*
+ * XXX: below, we allocate a midi input buffer, since we don't
+ * receive raw midi data, so no need to allocate a input
+ * ibuf. Possibly set imsg & fill callbacks to NULL and
+ * use this to in midi_new() to check if buffers need to be
+ * allocated
+ */
+ 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;
+ if (!dev_sio_open(d)) {
+ 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;
+ }
+ dev_sio_close(d);
+ 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);
+ }
+ 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
+ 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
+ }
+ midi_del(d->midi);
+ *p = d->next;
+ 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->pstate = DEV_RUN;
+ dev_sio_start(d);
+ }
+}
+
+/*
+ * 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;
+ }
+ }
+ if (!dev_ref(d))
+ 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;
+ dev_unref(d);
+ 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 first unused "unit" number for this 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 ((umap & (1 << unit)) == 0)
+ break;
+ }
+
+ /*
+ * find a xfree 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 xfree 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_slotdesc(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 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;
+ if (s->ops == NULL)
+ return;
+ 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->dev;
+ unsigned int slot_nch, dev_nch;
+ int startpos;
+
+ /*
+ * start the device if not started
+ */
+ dev_wakeup(d);
+
+ /*
+ * get the current position, the origin is when the first sample
+ * played and/or recorded
+ */
+ startpos = dev_getpos(d) * (int)s->round / (int)d->round;
+ s->delta = startpos;
+ s->delta_rem = 0;
+ s->pstate = SLOT_RUN;
+#ifdef DEBUG
+ if (log_level >= 3) {
+ slot_log(s);
+ log_puts(": attached at ");
+ log_puti(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));
+ }
+ s->mix.drop = 0;
+ s->mix.vol = MIDI_TO_ADATA(s->vol);
+ dev_mix_adjvol(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));
+ }
+
+ /*
+ * N-th recorded block is the N-th played block
+ */
+ s->sub.silence = startpos / (int)s->round;
+ }
+}
+
+/*
+ * if MMC is enabled, and try to attach all slots synchronously, else
+ * simply attach the slot
+ */
+void
+slot_ready(struct slot *s)
+{
+ if (s->tstate == MMC_OFF)
+ slot_attach(s);
+ else {
+ s->tstate = MMC_START;
+ dev_sync_attach(s->dev);
+ }
+}
+
+/*
+ * setup buffers & conversion layers, prepare the slot to receive data
+ * (for playback) or start (recording).
+ */
+void
+slot_start(struct slot *s)
+{
+ unsigned int bufsz;
+#ifdef DEBUG
+ struct dev *d = s->dev;
+
+
+ if (s->pstate != SLOT_INIT) {
+ slot_log(s);
+ log_puts(": slot_start: wrong state\n");
+ panic();
+ }
+#endif
+ bufsz = s->appbufsz;
+ 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
+ s->mix.bpf = s->par.bps *
+ (s->mix.slot_cmax - s->mix.slot_cmin + 1);
+ abuf_init(&s->mix.buf, bufsz * s->mix.bpf);
+ }
+ 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
+ s->sub.bpf = s->par.bps *
+ (s->sub.slot_cmax - s->sub.slot_cmin + 1);
+ abuf_init(&s->sub.buf, bufsz * s->sub.bpf);
+ }
+ 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(SLOT_BUFSZ(s));
+ log_puts(" fr buffers\n");
+ }
+#endif
+ 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;
+
+#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 (s == NULL) {
+ slot_log(s);
+ log_puts(": can't detach, not on list\n");
+ panic();
+ }
+#endif
+ }
+ *ps = s->next;
+ if (s->mode & MODE_RECMASK) {
+ if (s->sub.encbuf)
+ xfree(s->sub.encbuf);
+ if (s->sub.resampbuf)
+ xfree(s->sub.resampbuf);
+ }
+ if (s->mode & MODE_PLAY) {
+ if (s->mix.decbuf)
+ xfree(s->mix.decbuf);
+ if (s->mix.resampbuf)
+ xfree(s->mix.resampbuf);
+ dev_mix_adjvol(s->dev);
+ }
+}
+
+/*
+ * put the slot in stopping state (draining play buffers) or
+ * stop & detach if no data to drain.
+ */
+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;
+}
+
+/*
+ * 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);
+ }
+}
+
+/*
+ * notify the slot that we freed some space in the rec buffer
+ */
+void
+slot_read(struct slot *s)
+{
+ /* nothing yet */
+}
diff --git a/usr.bin/sndiod/dev.h b/usr.bin/sndiod/dev.h
new file mode 100644
index 00000000000..263800fe9f7
--- /dev/null
+++ b/usr.bin/sndiod/dev.h
@@ -0,0 +1,230 @@
+/* $OpenBSD: dev.h,v 1.1 2012/11/23 07:03:28 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.
+ */
+#ifndef DEV_H
+#define DEV_H
+
+#include "abuf.h"
+#include "dsp.h"
+#include "siofile.h"
+
+/*
+ * audio stream state structure
+ */
+
+struct slotops
+{
+ void (*onmove)(void *, int); /* clock tick */
+ void (*onvol)(void *, unsigned int); /* tell client vol changed */
+ void (*fill)(void *); /* request to fill a play block */
+ void (*flush)(void *); /* request to flush a rec block */
+ void (*eof)(void *); /* notify that play drained */
+ void (*mmcstart)(void *); /* request to start */
+ void (*mmcstop)(void *); /* request to stop */
+ void (*mmcloc)(void *, unsigned int); /* relocate to new position */
+ void (*exit)(void *); /* delete client */
+};
+
+struct slot {
+ struct slotops *ops; /* client callbacks */
+ struct slot *next; /* next on the play list */
+ struct dev *dev; /* device this belongs to */
+ void *arg; /* user data for callbacks */
+ struct aparams par; /* socket side params */
+ struct {
+ int weight; /* dynamic range */
+ int maxweight; /* max dynamic range allowed */
+ unsigned int vol; /* volume within the vol */
+ int drop; /* to drop on next read */
+ struct abuf buf; /* socket side buffer */
+ int bpf; /* byte per frame */
+ int slot_cmin, slot_cmax; /* slot source chans */
+ int dev_cmin, dev_cmax; /* device destination chans */
+ struct cmap cmap; /* channel mapper state */
+ struct resamp resamp; /* resampler state */
+ struct conv dec; /* format decoder params */
+ int join; /* channel join factor */
+ int expand; /* channel expand factor */
+ void *resampbuf, *decbuf; /* tmp buffers */
+ } mix;
+ struct {
+ int silence; /* to add on next write */
+ struct abuf buf; /* socket side buffer */
+ int bpf; /* byte per frame */
+ int slot_cmin, slot_cmax; /* slot destination chans */
+ int dev_cmin, dev_cmax; /* device source chans */
+ struct cmap cmap; /* channel mapper state */
+ struct resamp resamp; /* buffer for resampling */
+ struct conv enc; /* buffer for encoding */
+ int join; /* channel join factor */
+ int expand; /* channel expand factor */
+ void *resampbuf, *encbuf; /* tmp buffers */
+ } sub;
+ int xrun; /* underrun policy */
+ int dup; /* mono-to-stereo and alike */
+#define SLOT_BUFSZ(s) \
+ ((s)->appbufsz + (s)->dev->bufsz / (s)->dev->round * (s)->round)
+ int appbufsz; /* slot-side buffer size */
+ int round; /* slot-side block size */
+ int rate; /* slot-side sample rate */
+ int delta; /* pending clock ticks */
+ int delta_rem; /* remainder for delta */
+ int mode; /* MODE_{PLAY,REC} */
+#define SLOT_INIT 0 /* not trying to do anything */
+#define SLOT_START 1 /* buffer allocated */
+#define SLOT_READY 2 /* buffer filled enough */
+#define SLOT_RUN 3 /* buffer attached to device */
+#define SLOT_STOP 4 /* draining */
+ int pstate;
+
+#define SLOT_NAMEMAX 8
+ char name[SLOT_NAMEMAX]; /* name matching [a-z]+ */
+ unsigned int unit; /* instance of name */
+ unsigned int serial; /* global unique number */
+ unsigned int vol; /* current (midi) volume */
+ unsigned int tstate; /* mmc state */
+};
+
+/*
+ * audio device with plenty of slots
+ */
+struct dev {
+ struct dev *next;
+ struct slot *slot_list; /* audio streams attached */
+ struct midi *midi;
+
+ /*
+ * audio device (while opened)
+ */
+ struct siofile_ sio;
+ struct aparams par; /* encoding */
+ int pchan, rchan; /* play & rec channels */
+ adata_t *rbuf; /* rec buffer */
+ adata_t *pbuf; /* array of play buffers */
+#define DEV_PBUF(d) ((d)->pbuf + (d)->poffs * (d)->pchan)
+ int poffs; /* index of current play buf */
+ struct conv enc; /* native->device format */
+ struct conv dec; /* device->native format */
+ unsigned char *encbuf; /* buffer for encoding */
+ unsigned char *decbuf; /* buffer for decoding */
+
+ /*
+ * preallocated audio sub-devices
+ */
+#define DEV_NSLOT 8
+ struct slot slot[DEV_NSLOT];
+ unsigned int serial; /* for slot allocation */
+
+ /*
+ * desired parameters
+ */
+ unsigned int reqmode; /* mode */
+ struct aparams reqpar; /* parameters */
+ int reqpchan, reqrchan; /* play & rec chans */
+ unsigned int reqbufsz; /* buffer size */
+ unsigned int reqround; /* block size */
+ unsigned int reqrate; /* sample rate */
+ unsigned int hold; /* hold the device open ? */
+ unsigned int autovol; /* auto adjust playvol ? */
+ unsigned int autostart; /* don't wait for MMC start */
+ unsigned int refcnt; /* number of openers */
+#define DEV_NMAX 16 /* max number of devices */
+ unsigned int num; /* device serial number */
+#define DEV_CFG 0 /* closed */
+#define DEV_INIT 1 /* stopped */
+#define DEV_RUN 3 /* playin & recording */
+ unsigned int pstate; /* one of above */
+ char *path; /* sio path */
+
+ /*
+ * actual parameters and runtime state (i.e. once opened)
+ */
+ unsigned int mode; /* bitmap of MODE_xxx */
+ unsigned int bufsz, round, rate;
+ unsigned int prime;
+
+ /*
+ * MIDI time code (MTC)
+ */
+ struct {
+ unsigned int origin; /* MTC start time */
+ unsigned int fps; /* MTC frames per second */
+#define MTC_FPS_24 0
+#define MTC_FPS_25 1
+#define MTC_FPS_30 3
+ unsigned int fps_id; /* one of above */
+ unsigned int hr; /* MTC hours */
+ unsigned int min; /* MTC minutes */
+ unsigned int sec; /* MTC seconds */
+ unsigned int fr; /* MTC frames */
+ unsigned int qfr; /* MTC quarter frames */
+ int delta; /* rel. to the last MTC tick */
+ int refs;
+ } mtc;
+
+ /*
+ * MIDI machine control (MMC)
+ */
+#define MMC_OFF 0 /* ignore MMC messages */
+#define MMC_STOP 1 /* stopped, can't start */
+#define MMC_START 2 /* attempting to start */
+#define MMC_RUN 3 /* started */
+ unsigned int tstate; /* one of above */
+ unsigned int master; /* master volume controller */
+};
+
+extern struct dev *dev_list;
+
+void dev_log(struct dev *);
+void dev_close(struct dev *);
+struct dev *dev_new(char *, struct aparams *, unsigned int, unsigned int,
+ unsigned int, unsigned int, unsigned int, unsigned int);
+struct dev *dev_bynum(int);
+void dev_del(struct dev *);
+void dev_adjpar(struct dev *, int, int, int, int, int);
+int dev_init(struct dev *);
+void dev_done(struct dev *);
+int dev_getpos(struct dev *);
+unsigned int dev_roundof(struct dev *, unsigned int);
+
+/*
+ * interface to hardware device
+ */
+void dev_onmove(struct dev *, int);
+void dev_cycle(struct dev *);
+
+/*
+ * midi & midi call-backs
+ */
+void dev_mmcstart(struct dev *);
+void dev_mmcstop(struct dev *);
+void dev_mmcloc(struct dev *, unsigned int);
+void dev_master(struct dev *, unsigned int);
+void dev_midi_vol(struct dev *, struct slot *);
+
+/*
+ * sio_open(3) like interface for clients
+ */
+void slot_log(struct slot *);
+struct slot *slot_new(struct dev *, char *, struct slotops *, void *, int);
+void slot_del(struct slot *);
+void slot_setvol(struct slot *, unsigned int);
+void slot_start(struct slot *);
+void slot_stop(struct slot *);
+void slot_read(struct slot *);
+void slot_write(struct slot *);
+
+#endif /* !defined(DEV_H) */
diff --git a/usr.bin/sndiod/dsp.c b/usr.bin/sndiod/dsp.c
new file mode 100644
index 00000000000..cd76acf292a
--- /dev/null
+++ b/usr.bin/sndiod/dsp.c
@@ -0,0 +1,711 @@
+/* $OpenBSD: dsp.c,v 1.1 2012/11/23 07:03:28 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 <string.h>
+#include "defs.h"
+#include "dsp.h"
+#include "utils.h"
+
+int aparams_ctltovol[128] = {
+ 0,
+ 256, 266, 276, 287, 299, 310, 323, 335,
+ 348, 362, 376, 391, 406, 422, 439, 456,
+ 474, 493, 512, 532, 553, 575, 597, 621,
+ 645, 670, 697, 724, 753, 782, 813, 845,
+ 878, 912, 948, 985, 1024, 1064, 1106, 1149,
+ 1195, 1241, 1290, 1341, 1393, 1448, 1505, 1564,
+ 1625, 1689, 1756, 1825, 1896, 1971, 2048, 2128,
+ 2212, 2299, 2389, 2483, 2580, 2682, 2787, 2896,
+ 3010, 3128, 3251, 3379, 3511, 3649, 3792, 3941,
+ 4096, 4257, 4424, 4598, 4778, 4966, 5161, 5363,
+ 5574, 5793, 6020, 6256, 6502, 6757, 7023, 7298,
+ 7585, 7883, 8192, 8514, 8848, 9195, 9556, 9931,
+ 10321, 10726, 11148, 11585, 12040, 12513, 13004, 13515,
+ 14045, 14596, 15170, 15765, 16384, 17027, 17696, 18390,
+ 19112, 19863, 20643, 21453, 22295, 23170, 24080, 25025,
+ 26008, 27029, 28090, 29193, 30339, 31530, 32768
+};
+
+/*
+ * Generate a string corresponding to the encoding in par,
+ * return the length of the resulting string.
+ */
+int
+aparams_enctostr(struct aparams *par, char *ostr)
+{
+ char *p = ostr;
+
+ *p++ = par->sig ? 's' : 'u';
+ if (par->bits > 9)
+ *p++ = '0' + par->bits / 10;
+ *p++ = '0' + par->bits % 10;
+ if (par->bps > 1) {
+ *p++ = par->le ? 'l' : 'b';
+ *p++ = 'e';
+ if (par->bps != APARAMS_BPS(par->bits) ||
+ par->bits < par->bps * 8) {
+ *p++ = par->bps + '0';
+ if (par->bits < par->bps * 8) {
+ *p++ = par->msb ? 'm' : 'l';
+ *p++ = 's';
+ *p++ = 'b';
+ }
+ }
+ }
+ *p++ = '\0';
+ return p - ostr - 1;
+}
+
+/*
+ * Parse an encoding string, examples: s8, u8, s16, s16le, s24be ...
+ * set *istr to the char following the encoding. Return the number
+ * of bytes consumed.
+ */
+int
+aparams_strtoenc(struct aparams *par, char *istr)
+{
+ char *p = istr;
+ int i, sig, bits, le, bps, msb;
+
+#define IS_SEP(c) \
+ (((c) < 'a' || (c) > 'z') && \
+ ((c) < 'A' || (c) > 'Z') && \
+ ((c) < '0' || (c) > '9'))
+
+ /*
+ * get signedness
+ */
+ if (*p == 's') {
+ sig = 1;
+ } else if (*p == 'u') {
+ sig = 0;
+ } else
+ return 0;
+ p++;
+
+ /*
+ * get number of bits per sample
+ */
+ bits = 0;
+ for (i = 0; i < 2; i++) {
+ if (*p < '0' || *p > '9')
+ break;
+ bits = (bits * 10) + *p - '0';
+ p++;
+ }
+ if (bits < BITS_MIN || bits > BITS_MAX)
+ return 0;
+ bps = APARAMS_BPS(bits);
+ msb = 1;
+ le = ADATA_LE;
+
+ /*
+ * get (optional) endianness
+ */
+ if (p[0] == 'l' && p[1] == 'e') {
+ le = 1;
+ p += 2;
+ } else if (p[0] == 'b' && p[1] == 'e') {
+ le = 0;
+ p += 2;
+ } else if (IS_SEP(*p)) {
+ goto done;
+ } else
+ return 0;
+
+ /*
+ * get (optional) number of bytes
+ */
+ if (*p >= '0' && *p <= '9') {
+ bps = *p - '0';
+ if (bps < (bits + 7) / 8 ||
+ bps > (BITS_MAX + 7) / 8)
+ return 0;
+ p++;
+
+ /*
+ * get (optional) alignement
+ */
+ if (p[0] == 'm' && p[1] == 's' && p[2] == 'b') {
+ msb = 1;
+ p += 3;
+ } else if (p[0] == 'l' && p[1] == 's' && p[2] == 'b') {
+ msb = 0;
+ p += 3;
+ } else if (IS_SEP(*p)) {
+ goto done;
+ } else
+ return 0;
+ } else if (!IS_SEP(*p))
+ return 0;
+
+done:
+ par->msb = msb;
+ par->sig = sig;
+ par->bits = bits;
+ par->bps = bps;
+ par->le = le;
+ return p - istr;
+}
+
+/*
+ * Initialise parameters structure with the defaults natively supported
+ * by the machine.
+ */
+void
+aparams_init(struct aparams *par)
+{
+ par->bps = sizeof(adata_t);
+ par->bits = ADATA_BITS;
+ par->le = ADATA_LE;
+ par->sig = 1;
+ par->msb = 0;
+}
+
+/*
+ * log the given format/channels/encoding
+ */
+void
+aparams_log(struct aparams *par)
+{
+ char enc[ENCMAX];
+
+ aparams_enctostr(par, enc);
+ log_puts(enc);
+}
+
+/*
+ * return true if encoding corresponds to what we store in adata_t
+ */
+int
+aparams_native(struct aparams *par)
+{
+ return par->bps == sizeof(adata_t) && par->bits == ADATA_BITS &&
+ (par->bps == 1 || par->le == ADATA_LE) &&
+ (par->bits == par->bps * 8 || !par->msb);
+}
+
+/*
+ * resample the given number of frames
+ */
+int
+resamp_do(struct resamp *p, adata_t *in, adata_t *out, int todo)
+{
+ unsigned int nch;
+ adata_t *idata;
+ unsigned int oblksz;
+ unsigned int ifr;
+ int s, ds, diff;
+ adata_t *odata;
+ unsigned int iblksz;
+ unsigned int ofr;
+ unsigned int c;
+ adata_t *ctxbuf, *ctx;
+ unsigned int ctx_start;
+
+ /*
+ * Partially copy structures into local variables, to avoid
+ * unnecessary indirections; this also allows the compiler to
+ * order local variables more "cache-friendly".
+ */
+ idata = in;
+ odata = out;
+ diff = p->diff;
+ iblksz = p->iblksz;
+ oblksz = p->oblksz;
+ ctxbuf = p->ctx;
+ ctx_start = p->ctx_start;
+ nch = p->nch;
+ ifr = todo;
+ ofr = oblksz;
+
+ /*
+ * Start conversion.
+ */
+#ifdef DEBUG
+ if (log_level >= 4) {
+ log_puts("resamp: copying ");
+ log_puti(todo);
+ log_puts(" frames, diff = ");
+ log_putu(diff);
+ log_puts("\n");
+ }
+#endif
+ for (;;) {
+ if (diff < 0) {
+ if (ifr == 0)
+ break;
+ ctx_start ^= 1;
+ ctx = ctxbuf + ctx_start;
+ for (c = nch; c > 0; c--) {
+ *ctx = *idata++;
+ ctx += RESAMP_NCTX;
+ }
+ diff += oblksz;
+ ifr--;
+ } else if (diff > 0) {
+ if (ofr == 0)
+ break;
+ ctx = ctxbuf;
+ for (c = nch; c > 0; c--) {
+ s = ctx[ctx_start];
+ ds = ctx[ctx_start ^ 1] - s;
+ ctx += RESAMP_NCTX;
+ *odata++ = s + ADATA_MULDIV(ds, diff, oblksz);
+ }
+ diff -= iblksz;
+ ofr--;
+ } else {
+ if (ifr == 0 || ofr == 0)
+ break;
+ ctx = ctxbuf + ctx_start;
+ for (c = nch; c > 0; c--) {
+ *odata++ = *ctx;
+ ctx += RESAMP_NCTX;
+ }
+ ctx_start ^= 1;
+ ctx = ctxbuf + ctx_start;
+ for (c = nch; c > 0; c--) {
+ *ctx = *idata++;
+ ctx += RESAMP_NCTX;
+ }
+ diff -= iblksz;
+ diff += oblksz;
+ ifr--;
+ ofr--;
+ }
+ }
+ p->diff = diff;
+ p->ctx_start = ctx_start;
+ return oblksz - ofr;
+}
+
+/*
+ * initialize resampler with ibufsz/obufsz factor and "nch" channels
+ */
+void
+resamp_init(struct resamp *p, unsigned int iblksz, unsigned int oblksz, int nch)
+{
+ unsigned int i;
+
+ p->iblksz = iblksz;
+ p->oblksz = oblksz;
+ p->diff = 0;
+ p->idelta = 0;
+ p->odelta = 0;
+ p->nch = nch;
+ p->ctx_start = 0;
+ for (i = 0; i < NCHAN_MAX * RESAMP_NCTX; i++)
+ p->ctx[i] = 0;
+#ifdef DEBUG
+ if (log_level >= 3) {
+ log_puts("resamp: ");
+ log_putu(iblksz);
+ log_puts("/");
+ log_putu(oblksz);
+ log_puts("\n");
+ }
+#endif
+}
+
+/*
+ * encode "todo" frames from native to foreign encoding
+ */
+void
+enc_do(struct conv *p, unsigned char *in, unsigned char *out, int todo)
+{
+ unsigned int f;
+ adata_t *idata;
+ int s;
+ unsigned int oshift;
+ int osigbit;
+ unsigned int obps;
+ unsigned int i;
+ unsigned char *odata;
+ int obnext;
+ int osnext;
+
+#ifdef DEBUG
+ if (log_level >= 4) {
+ log_puts("enc: copying ");
+ log_putu(todo);
+ log_puts(" frames\n");
+ }
+#endif
+ /*
+ * Partially copy structures into local variables, to avoid
+ * unnecessary indirections; this also allows the compiler to
+ * order local variables more "cache-friendly".
+ */
+ idata = (adata_t *)in;
+ odata = out;
+ oshift = p->shift;
+ osigbit = p->sigbit;
+ obps = p->bps;
+ obnext = p->bnext;
+ osnext = p->snext;
+
+ /*
+ * Start conversion.
+ */
+ odata += p->bfirst;
+ for (f = todo * p->nch; f > 0; f--) {
+ s = *idata++;
+ s <<= 32 - ADATA_BITS;
+ s >>= oshift;
+ s ^= osigbit;
+ for (i = obps; i > 0; i--) {
+ *odata = (unsigned char)s;
+ s >>= 8;
+ odata += obnext;
+ }
+ odata += osnext;
+ }
+}
+
+/*
+ * store "todo" frames of silence in foreign encoding
+ */
+void
+enc_sil_do(struct conv *p, unsigned char *out, int todo)
+{
+ unsigned int f;
+ int s;
+ int osigbit;
+ unsigned int obps;
+ unsigned int i;
+ unsigned char *odata;
+ int obnext;
+ int osnext;
+
+#ifdef DEBUG
+ if (log_level >= 4) {
+ log_puts("enc: silence ");
+ log_putu(todo);
+ log_puts(" frames\n");
+ }
+#endif
+ /*
+ * Partially copy structures into local variables, to avoid
+ * unnecessary indirections; this also allows the compiler to
+ * order local variables more "cache-friendly".
+ */
+ odata = out;
+ osigbit = p->sigbit;
+ obps = p->bps;
+ obnext = p->bnext;
+ osnext = p->snext;
+
+ /*
+ * Start conversion.
+ */
+ odata += p->bfirst;
+ for (f = todo * p->nch; f > 0; f--) {
+ s = osigbit;
+ for (i = obps; i > 0; i--) {
+ *odata = (unsigned char)s;
+ s >>= 8;
+ odata += obnext;
+ }
+ odata += osnext;
+ }
+}
+
+/*
+ * initialize encoder from native to foreign encoding
+ */
+void
+enc_init(struct conv *p, struct aparams *par, int nch)
+{
+ p->nch = nch;
+ p->bps = par->bps;
+ p->sigbit = par->sig ? 0 : 1 << (par->bits - 1);
+ if (par->msb) {
+ p->shift = 32 - par->bps * 8;
+ } else {
+ p->shift = 32 - par->bits;
+ }
+ if (!par->le) {
+ p->bfirst = par->bps - 1;
+ p->bnext = -1;
+ p->snext = 2 * par->bps;
+ } else {
+ p->bfirst = 0;
+ p->bnext = 1;
+ p->snext = 0;
+ }
+#ifdef DEBUG
+ if (log_level >= 3) {
+ log_puts("enc: ");
+ aparams_log(par);
+ log_puts(", ");
+ log_puti(p->nch);
+ log_puts(" channels\n");
+ }
+#endif
+}
+
+/*
+ * decode "todo" frames from from foreign to native encoding
+ */
+void
+dec_do(struct conv *p, unsigned char *in, unsigned char *out, int todo)
+{
+ unsigned int f;
+ unsigned int ibps;
+ unsigned int i;
+ int s = 0xdeadbeef;
+ unsigned char *idata;
+ int ibnext;
+ int isnext;
+ int isigbit;
+ unsigned int ishift;
+ adata_t *odata;
+
+#ifdef DEBUG
+ if (log_level >= 4) {
+ log_puts("dec: copying ");
+ log_putu(todo);
+ log_puts(" frames\n");
+ }
+#endif
+ /*
+ * Partially copy structures into local variables, to avoid
+ * unnecessary indirections; this also allows the compiler to
+ * order local variables more "cache-friendly".
+ */
+ idata = in;
+ odata = (adata_t *)out;
+ ibps = p->bps;
+ ibnext = p->bnext;
+ isigbit = p->sigbit;
+ ishift = p->shift;
+ isnext = p->snext;
+
+ /*
+ * Start conversion.
+ */
+ idata += p->bfirst;
+ for (f = todo * p->nch; f > 0; f--) {
+ for (i = ibps; i > 0; i--) {
+ s <<= 8;
+ s |= *idata;
+ idata += ibnext;
+ }
+ idata += isnext;
+ s ^= isigbit;
+ s <<= ishift;
+ s >>= 32 - ADATA_BITS;
+ *odata++ = s;
+ }
+}
+
+/*
+ * initialize decoder from foreign to native encoding
+ */
+void
+dec_init(struct conv *p, struct aparams *par, int nch)
+{
+ p->bps = par->bps;
+ p->sigbit = par->sig ? 0 : 1 << (par->bits - 1);
+ p->nch = nch;
+ if (par->msb) {
+ p->shift = 32 - par->bps * 8;
+ } else {
+ p->shift = 32 - par->bits;
+ }
+ if (par->le) {
+ p->bfirst = par->bps - 1;
+ p->bnext = -1;
+ p->snext = 2 * par->bps;
+ } else {
+ p->bfirst = 0;
+ p->bnext = 1;
+ p->snext = 0;
+ }
+#ifdef DEBUG
+ if (log_level >= 3) {
+ log_puts("dec: ");
+ aparams_log(par);
+ log_puts(", ");
+ log_puti(p->nch);
+ log_puts(" channels\n");
+ }
+#endif
+}
+
+/*
+ * mix "todo" input frames on the output with the given volume
+ */
+void
+cmap_add(struct cmap *p, void *in, void *out, int vol, int todo)
+{
+ adata_t *idata, *odata;
+ int i, j, nch, istart, inext, onext, ostart, y, v;
+
+#ifdef DEBUG
+ if (log_level >= 4) {
+ log_puts("cmap: adding ");
+ log_puti(todo);
+ log_puts(" frames\n");
+ }
+#endif
+ idata = in;
+ odata = out;
+ ostart = p->ostart;
+ onext = p->onext;
+ istart = p->istart;
+ inext = p->inext;
+ nch = p->nch;
+ v = vol; /* XXX */
+
+ /*
+ * map/mix input on the output
+ */
+ for (i = todo; i > 0; i--) {
+ odata += ostart;
+ idata += istart;
+ for (j = nch; j > 0; j--) {
+ y = *odata + ADATA_MUL(*idata, v);
+ if (y >= ADATA_UNIT)
+ y = ADATA_UNIT - 1;
+ else if (y < -ADATA_UNIT)
+ y = -ADATA_UNIT;
+ *odata = y;
+ idata++;
+ odata++;
+ }
+ odata += onext;
+ idata += inext;
+ }
+}
+
+/*
+ * overwrite output with "todo" input frames with with the given volume
+ */
+void
+cmap_copy(struct cmap *p, void *in, void *out, int vol, int todo)
+{
+ adata_t *idata, *odata;
+ int i, j, nch, istart, inext, onext, ostart, v;
+
+#ifdef DEBUG
+ if (log_level >= 4) {
+ log_puts("cmap: copying ");
+ log_puti(todo);
+ log_puts(" frames\n");
+ }
+#endif
+ idata = in;
+ odata = out;
+ ostart = p->ostart;
+ onext = p->onext;
+ istart = p->istart;
+ inext = p->inext;
+ nch = p->nch;
+ v = vol;
+
+ /*
+ * copy to the output buffer
+ */
+ for (i = todo; i > 0; i--) {
+ idata += istart;
+ for (j = ostart; j > 0; j--)
+ *odata++ = 0x1111;
+ for (j = nch; j > 0; j--) {
+ *odata = ADATA_MUL(*idata, v);
+ odata++;
+ idata++;
+ }
+ for (j = onext; j > 0; j--)
+ *odata++ = 0x2222;
+ idata += inext;
+ }
+}
+
+/*
+ * initialize channel mapper, to map a subset of input channel range
+ * into a subset of the output channel range
+ */
+void
+cmap_init(struct cmap *p,
+ int imin, int imax, int isubmin, int isubmax,
+ int omin, int omax, int osubmin, int osubmax)
+{
+ int cmin, cmax;
+
+ cmin = -NCHAN_MAX;
+ if (osubmin > cmin)
+ cmin = osubmin;
+ if (omin > cmin)
+ cmin = omin;
+ if (isubmin > cmin)
+ cmin = isubmin;
+ if (imin > cmin)
+ cmin = imin;
+
+ cmax = NCHAN_MAX;
+ if (osubmax < cmax)
+ cmax = osubmax;
+ if (omax < cmax)
+ cmax = omax;
+ if (isubmax < cmax)
+ cmax = isubmax;
+ if (imax < cmax)
+ cmax = imax;
+
+ p->ostart = cmin - omin;
+ p->onext = omax - cmax;
+ p->istart = cmin - imin;
+ p->inext = imax - cmax;
+ p->nch = cmax - cmin + 1;
+#ifdef DEBUG
+ if (log_level >= 3) {
+ log_puts("cmap: nch = ");
+ log_puti(p->nch);
+ log_puts(", ostart = ");
+ log_puti(p->ostart);
+ log_puts(", onext = ");
+ log_puti(p->onext);
+ log_puts(", istart = ");
+ log_puti(p->istart);
+ log_puts(", inext= ");
+ log_puti(p->inext);
+ log_puts("\n");
+ }
+#endif
+}
+
+/*
+ * produce a square tone, for instance with:
+ *
+ * period = round / (220 * round / rate)
+ */
+int
+sqrtone(int ctx, adata_t *out, int period, int vol, int todo)
+{
+ int i;
+
+ for (i = todo; i > 0; i--) {
+ if (ctx == 0) {
+ vol = -vol;
+ ctx = period / 2;
+ }
+ ctx--;
+ *(out++) += vol;
+ }
+ return ctx;
+}
diff --git a/usr.bin/sndiod/dsp.h b/usr.bin/sndiod/dsp.h
new file mode 100644
index 00000000000..7fe7e7514a7
--- /dev/null
+++ b/usr.bin/sndiod/dsp.h
@@ -0,0 +1,161 @@
+/* $OpenBSD: dsp.h,v 1.1 2012/11/23 07:03:28 ratchov Exp $ */
+/*
+ * Copyright (c) 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.
+ */
+#ifndef DSP_H
+#define DSP_H
+
+#include <sys/types.h>
+#include "defs.h"
+
+/*
+ * Samples are numbers in the interval [-1, 1[, note that 1, the upper
+ * boundary is excluded. We represent them as signed fixed point numbers
+ * of ADATA_BITS. We also assume that 2^(ADATA_BITS - 1) fits in a int.
+ */
+#ifndef ADATA_BITS
+#define ADATA_BITS 16
+#endif
+#define ADATA_LE (BYTE_ORDER == LITTLE_ENDIAN)
+#define ADATA_UNIT (1 << (ADATA_BITS - 1))
+
+#if ADATA_BITS == 16
+
+#define ADATA_MUL(x,y) (((int)(x) * (int)(y)) >> (ADATA_BITS - 1))
+#define ADATA_MULDIV(x,y,z) ((int)(x) * (int)(y) / (int)(z))
+
+typedef short adata_t;
+
+#elif ADATA_BITS == 24
+
+#if defined(__i386__) && defined(__GNUC__)
+
+static inline int
+fp24_mul(int x, int a)
+{
+ int res;
+
+ asm volatile (
+ "imull %2\n\t"
+ "shrdl $23, %%edx, %%eax\n\t"
+ : "=a" (res)
+ : "a" (x), "r" (a)
+ : "%edx"
+ );
+ return res;
+}
+
+static inline int
+fp24_muldiv(int x, int a, int b)
+{
+ int res;
+
+ asm volatile (
+ "imull %2\n\t"
+ "idivl %3\n\t"
+ : "=a" (res)
+ : "a" (x), "d" (a), "r" (b)
+ );
+ return res;
+}
+
+#define ADATA_MUL(x,y) fp24_mul(x, y)
+#define ADATA_MULDIV(x,y,z) fp24_muldiv(x, y, z);
+
+#elif defined(__amd64__) || defined(__sparc64__)
+
+#define ADATA_MUL(x,y) \
+ ((int)(((long long)(x) * (long long)(y)) >> (ADATA_BITS - 1)))
+#define ADATA_MULDIV(x,y,z) \
+ ((int)((long long)(x) * (long long)(y) / (long long)(z)))
+
+#else
+#error "no 24-bit code for this architecture"
+#endif
+
+typedef int adata_t;
+
+#else
+#error "only 16-bit and 24-bit precisions are supported"
+#endif
+
+/*
+ * Maximum size of the encording string (the longest possible
+ * encoding is ``s24le3msb'').
+ */
+#define ENCMAX 10
+
+/*
+ * Default bytes per sample for the given bits per sample.
+ */
+#define APARAMS_BPS(bits) (((bits) <= 8) ? 1 : (((bits) <= 16) ? 2 : 4))
+
+struct aparams {
+ unsigned int bps; /* bytes per sample */
+ unsigned int bits; /* actually used bits */
+ unsigned int le; /* 1 if little endian, 0 if big endian */
+ unsigned int sig; /* 1 if signed, 0 if unsigned */
+ unsigned int msb; /* 1 if msb justified, 0 if lsb justified */
+};
+
+struct resamp {
+#define RESAMP_NCTX 2
+ unsigned int ctx_start;
+ adata_t ctx[NCHAN_MAX * RESAMP_NCTX];
+ unsigned int iblksz, oblksz;
+ int diff;
+ int idelta, odelta; /* remainder of ipos/opos */
+ int nch;
+};
+
+struct conv {
+ int bfirst; /* bytes to skip at startup */
+ unsigned int bps; /* bytes per sample */
+ unsigned int shift; /* shift to get 32bit MSB */
+ int sigbit; /* sign bits to XOR */
+ int bnext; /* to reach the next byte */
+ int snext; /* to reach the next sample */
+ int nch;
+};
+
+struct cmap {
+ int istart;
+ int inext;
+ int onext;
+ int ostart;
+ int nch;
+};
+
+#define MIDI_TO_ADATA(m) (aparams_ctltovol[m] << (ADATA_BITS - 16))
+extern int aparams_ctltovol[128];
+
+void aparams_init(struct aparams *);
+void aparams_log(struct aparams *);
+int aparams_strtoenc(struct aparams *, char *);
+int aparams_enctostr(struct aparams *, char *);
+int aparams_native(struct aparams *);
+
+int resamp_do(struct resamp *, adata_t *, adata_t *, int);
+void resamp_init(struct resamp *, unsigned int, unsigned int, int);
+void enc_do(struct conv *, unsigned char *, unsigned char *, int);
+void enc_sil_do(struct conv *, unsigned char *, int);
+void enc_init(struct conv *, struct aparams *, int);
+void dec_do(struct conv *, unsigned char *, unsigned char *, int);
+void dec_init(struct conv *, struct aparams *, int);
+void cmap_add(struct cmap *, void *, void *, int, int);
+void cmap_copy(struct cmap *, void *, void *, int, int);
+void cmap_init(struct cmap *, int, int, int, int, int, int, int, int);
+
+#endif /* !defined(DSP_H) */
diff --git a/usr.bin/sndiod/file.c b/usr.bin/sndiod/file.c
new file mode 100644
index 00000000000..415bc34666e
--- /dev/null
+++ b/usr.bin/sndiod/file.c
@@ -0,0 +1,478 @@
+/* $OpenBSD: file.c,v 1.1 2012/11/23 07:03:28 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.
+ */
+/*
+ * non-blocking file i/o module: each file can be read or written (or
+ * both). To achieve non-blocking io, we simply use the poll() syscall
+ * in an event loop and dispatch events to sub-modules.
+ *
+ * the module also provides trivial timeout implementation,
+ * derived from:
+ *
+ * anoncvs@moule.caoua.org:/midish
+ *
+ * midish/timo.c rev 1.18
+ * midish/mdep.c rev 1.71
+ *
+ * A timeout is used to schedule the call of a routine (the callback)
+ * there is a global list of timeouts that is processed inside the
+ * event loop. Timeouts work as follows:
+ *
+ * first the timo structure must be initialized with timo_set()
+ *
+ * then the timeout is scheduled (only once) with timo_add()
+ *
+ * if the timeout expires, the call-back is called; then it can
+ * be scheduled again if needed. It's OK to reschedule it again
+ * from the callback
+ *
+ * the timeout can be aborted with timo_del(), it is OK to try to
+ * abort a timout that has expired
+ *
+ */
+
+#include <sys/time.h>
+#include <sys/types.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+
+#include "defs.h"
+#include "file.h"
+#include "utils.h"
+
+#define MAXFDS 100
+#define TIMER_USEC 10000
+
+struct timespec file_ts;
+struct file *file_list;
+struct timo *timo_queue;
+unsigned int timo_abstime;
+int file_slowaccept = 0, file_nfds;
+#ifdef DEBUG
+long long file_wtime, file_utime;
+#endif
+
+/*
+ * initialise a timeout structure, arguments are callback and argument
+ * that will be passed to the callback
+ */
+void
+timo_set(struct timo *o, void (*cb)(void *), void *arg)
+{
+ o->cb = cb;
+ o->arg = arg;
+ o->set = 0;
+}
+
+/*
+ * schedule the callback in 'delta' 24-th of microseconds. The timeout
+ * must not be already scheduled
+ */
+void
+timo_add(struct timo *o, unsigned int delta)
+{
+ struct timo **i;
+ unsigned int val;
+ int diff;
+
+#ifdef DEBUG
+ if (o->set) {
+ log_puts("timo_add: already set\n");
+ panic();
+ }
+ if (delta == 0) {
+ log_puts("timo_add: zero timeout is evil\n");
+ panic();
+ }
+#endif
+ val = timo_abstime + delta;
+ for (i = &timo_queue; *i != NULL; i = &(*i)->next) {
+ diff = (*i)->val - val;
+ if (diff > 0) {
+ break;
+ }
+ }
+ o->set = 1;
+ o->val = val;
+ o->next = *i;
+ *i = o;
+}
+
+/*
+ * abort a scheduled timeout
+ */
+void
+timo_del(struct timo *o)
+{
+ struct timo **i;
+
+ for (i = &timo_queue; *i != NULL; i = &(*i)->next) {
+ if (*i == o) {
+ *i = o->next;
+ o->set = 0;
+ return;
+ }
+ }
+#ifdef DEBUG
+ if (log_level >= 4)
+ log_puts("timo_del: not found\n");
+#endif
+}
+
+/*
+ * routine to be called by the timer when 'delta' 24-th of microsecond
+ * elapsed. This routine updates time referece used by timeouts and
+ * calls expired timeouts
+ */
+void
+timo_update(unsigned int delta)
+{
+ struct timo *to;
+ int diff;
+
+ /*
+ * update time reference
+ */
+ timo_abstime += delta;
+
+ /*
+ * remove from the queue and run expired timeouts
+ */
+ while (timo_queue != NULL) {
+ /*
+ * there is no overflow here because + and - are
+ * modulo 2^32, they are the same for both signed and
+ * unsigned integers
+ */
+ diff = timo_queue->val - timo_abstime;
+ if (diff > 0)
+ break;
+ to = timo_queue;
+ timo_queue = to->next;
+ to->set = 0;
+ to->cb(to->arg);
+ }
+}
+
+/*
+ * initialize timeout queue
+ */
+void
+timo_init(void)
+{
+ timo_queue = NULL;
+ timo_abstime = 0;
+}
+
+/*
+ * destroy timeout queue
+ */
+void
+timo_done(void)
+{
+#ifdef DEBUG
+ if (timo_queue != NULL) {
+ log_puts("timo_done: timo_queue not empty!\n");
+ panic();
+ }
+#endif
+ timo_queue = (struct timo *)0xdeadbeef;
+}
+
+#ifdef DEBUG
+void
+file_log(struct file *f)
+{
+ static char *states[] = { "ini", "bus", "clo", "zom" };
+
+ log_puts(f->ops->name);
+ if (log_level >= 3) {
+ log_puts("(");
+ log_puts(f->name);
+ log_puts("|");
+ log_puts(states[f->state]);
+ log_puts(")");
+ }
+}
+#endif
+
+struct file *
+file_new(struct fileops *ops, void *arg, char *name, unsigned int nfds)
+{
+ struct file *f;
+
+ if (file_nfds + nfds > MAXFDS) {
+#ifdef DEBUG
+ if (log_level >= 1) {
+ log_puts(name);
+ log_puts(": too many polled files\n");
+ }
+#endif
+ return NULL;
+ }
+ f = xmalloc(sizeof(struct file));
+ f->nfds = nfds;
+ f->ops = ops;
+ f->arg = arg;
+ f->name = name;
+ f->state = FILE_INIT;
+ f->next = file_list;
+ file_list = f;
+#ifdef DEBUG
+ if (log_level >= 3) {
+ file_log(f);
+ log_puts(": created\n");
+ }
+#endif
+ file_nfds += f->nfds;
+ return f;
+}
+
+void
+file_del(struct file *f)
+{
+#ifdef DEBUG
+ if (f->state == FILE_ZOMB) {
+ log_puts("bad state in file_del()\n");
+ panic();
+ }
+#endif
+ file_nfds -= f->nfds;
+ f->state = FILE_ZOMB;
+#ifdef DEBUG
+ if (log_level >= 3) {
+ file_log(f);
+ log_puts(": destroyed\n");
+ }
+#endif
+}
+
+int
+file_poll(void)
+{
+ nfds_t nfds, n;
+ struct pollfd pfds[MAXFDS];
+ struct file *f, **pf;
+ struct timespec ts;
+#ifdef DEBUG
+ struct timespec sleepts;
+ struct timespec ts0, ts1;
+ long us;
+ int i;
+#endif
+ long long delta_nsec;
+ int revents, res;
+
+ /*
+ * cleanup zombies
+ */
+ pf = &file_list;
+ while ((f = *pf) != NULL) {
+ if (f->state == FILE_ZOMB) {
+ *pf = f->next;
+ xfree(f);
+ } else
+ pf = &f->next;
+ }
+
+ if (file_list == NULL && timo_queue == NULL) {
+#ifdef DEBUG
+ if (log_level >= 3)
+ log_puts("nothing to do...\n");
+#endif
+ return 0;
+ }
+
+ log_flush();
+#ifdef DEBUG
+ if (log_level >= 4)
+ log_puts("poll:");
+#endif
+ nfds = 0;
+ for (f = file_list; f != NULL; f = f->next) {
+#ifdef DEBUG
+ if (log_level >= 4) {
+ log_puts(" ");
+ file_log(f);
+ }
+#endif
+ n = f->ops->pollfd(f->arg, pfds + nfds);
+ if (n == 0) {
+ f->pfd = NULL;
+ continue;
+ }
+ f->pfd = pfds + nfds;
+ nfds += n;
+#ifdef DEBUG
+ if (log_level >= 4) {
+ log_puts("=");
+ for (i = 0; i < n; i++) {
+ if (i > 0)
+ log_puts(",");
+ log_putx(f->pfd[i].events);
+ }
+ }
+#endif
+ }
+#ifdef DEBUG
+ if (log_level >= 4)
+ log_puts("\n");
+#endif
+
+#ifdef DEBUG
+ clock_gettime(CLOCK_MONOTONIC, &sleepts);
+ file_utime += 1000000000LL * (sleepts.tv_sec - file_ts.tv_sec);
+ file_utime += sleepts.tv_nsec - file_ts.tv_nsec;
+#endif
+ res = poll(pfds, nfds, -1);
+ if (res < 0 && errno != EINTR)
+ err(1, "poll");
+#ifdef DEBUG
+ if (log_level >= 4) {
+ log_puts("poll: return:");
+ for (i = 0; i < nfds; i++) {
+ log_puts(" ");
+ log_putx(pfds[i].revents);
+ }
+ log_puts("\n");
+ }
+#endif
+ clock_gettime(CLOCK_MONOTONIC, &ts);
+#ifdef DEBUG
+ file_wtime += 1000000000LL * (ts.tv_sec - sleepts.tv_sec);
+ file_wtime += ts.tv_nsec - sleepts.tv_nsec;
+#endif
+ delta_nsec = 1000000000LL * (ts.tv_sec - file_ts.tv_sec);
+ delta_nsec += ts.tv_nsec - file_ts.tv_nsec;
+#ifdef DEBUG
+ if (delta_nsec < 0)
+ log_puts("file_poll: negative time interval\n");
+#endif
+ file_ts = ts;
+ if (delta_nsec >= 0 && delta_nsec < 1000000000LL)
+ timo_update(delta_nsec / 1000);
+ else {
+ if (log_level >= 2)
+ log_puts("ignored huge clock delta\n");
+ }
+ if (res <= 0)
+ return 1;
+
+ for (f = file_list; f != NULL; f = f->next) {
+ if (f->pfd == NULL)
+ continue;
+#ifdef DEBUG
+ clock_gettime(CLOCK_MONOTONIC, &ts0);
+#endif
+ revents = f->ops->revents(f->arg, f->pfd);
+ if ((revents & POLLHUP) && (f->state != FILE_ZOMB))
+ f->ops->hup(f->arg);
+ if ((revents & POLLIN) && (f->state != FILE_ZOMB))
+ f->ops->in(f->arg);
+ if ((revents & POLLOUT) && (f->state != FILE_ZOMB))
+ f->ops->out(f->arg);
+#ifdef DEBUG
+ clock_gettime(CLOCK_MONOTONIC, &ts1);
+ us = 1000000L * (ts1.tv_sec - ts0.tv_sec);
+ us += (ts1.tv_nsec - ts0.tv_nsec) / 1000;
+ if (log_level >= 4 || (log_level >= 3 && us >= 5000)) {
+ file_log(f);
+ log_puts(": processed in ");
+ log_putu(us);
+ log_puts("us\n");
+ }
+#endif
+ }
+ return 1;
+}
+
+/*
+ * handler for SIGALRM, invoked periodically
+ */
+void
+file_sigalrm(int i)
+{
+ /* nothing to do, we only want poll() to return EINTR */
+}
+
+
+void
+filelist_init(void)
+{
+ static struct sigaction sa;
+ struct itimerval it;
+ sigset_t set;
+
+ sigemptyset(&set);
+ (void)sigaddset(&set, SIGPIPE);
+ if (sigprocmask(SIG_BLOCK, &set, NULL))
+ err(1, "sigprocmask");
+ file_list = NULL;
+ if (clock_gettime(CLOCK_MONOTONIC, &file_ts) < 0) {
+ perror("clock_gettime");
+ exit(1);
+ }
+ sa.sa_flags = SA_RESTART;
+ sa.sa_handler = file_sigalrm;
+ sigfillset(&sa.sa_mask);
+ if (sigaction(SIGALRM, &sa, NULL) < 0) {
+ perror("sigaction");
+ exit(1);
+ }
+ it.it_interval.tv_sec = 0;
+ it.it_interval.tv_usec = TIMER_USEC;
+ it.it_value.tv_sec = 0;
+ it.it_value.tv_usec = TIMER_USEC;
+ if (setitimer(ITIMER_REAL, &it, NULL) < 0) {
+ perror("setitimer");
+ exit(1);
+ }
+ log_sync = 0;
+ timo_init();
+}
+
+void
+filelist_done(void)
+{
+ struct itimerval it;
+#ifdef DEBUG
+ struct file *f;
+
+ if (file_list != NULL) {
+ for (f = file_list; f != NULL; f = f->next) {
+ file_log(f);
+ log_puts(" not closed\n");
+ }
+ panic();
+ }
+ log_sync = 1;
+ log_flush();
+#endif
+ timerclear(&it.it_value);
+ timerclear(&it.it_interval);
+ if (setitimer(ITIMER_REAL, &it, NULL) < 0) {
+ perror("setitimer");
+ exit(1);
+ }
+ timo_done();
+}
diff --git a/usr.bin/sndiod/file.h b/usr.bin/sndiod/file.h
new file mode 100644
index 00000000000..af9b7eddb5e
--- /dev/null
+++ b/usr.bin/sndiod/file.h
@@ -0,0 +1,81 @@
+/* $OpenBSD: file.h,v 1.1 2012/11/23 07:03:28 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.
+ */
+#ifndef FILE_H
+#define FILE_H
+
+#include <sys/types.h>
+
+struct file;
+struct pollfd;
+
+struct timo {
+ struct timo *next;
+ unsigned int val; /* time to wait before the callback */
+ unsigned int set; /* true if the timeout is set */
+ void (*cb)(void *arg); /* routine to call on expiration */
+ void *arg; /* argument to give to 'cb' */
+};
+
+struct fileops {
+ char *name;
+ int (*pollfd)(void *, struct pollfd *);
+ int (*revents)(void *, struct pollfd *);
+ /*
+ * we have to handle POLLIN and POLLOUT events
+ * in separate handles, since handling POLLIN can
+ * close the file, and continuing (to handle POLLOUT)
+ * would make use of the free()'ed file structure
+ */
+ void (*in)(void *);
+ void (*out)(void *);
+ void (*hup)(void *);
+};
+
+struct file {
+ struct file *next; /* next in file_list */
+ struct pollfd *pfd; /* arg to poll(2) syscall */
+ struct fileops *ops; /* event handlers */
+ void *arg; /* argument to event handlers */
+#define FILE_INIT 0 /* ready */
+#define FILE_ZOMB 1 /* closed, but not free()d yet */
+ unsigned int state; /* one of above */
+ unsigned int nfds; /* max number of descriptors */
+ char *name; /* for debug purposes */
+};
+
+extern struct file *file_list;
+extern int file_slowaccept;
+
+#ifdef DEBUG
+extern long long file_wtime, file_utime;
+#endif
+
+void timo_set(struct timo *, void (*)(void *), void *);
+void timo_add(struct timo *, unsigned int);
+void timo_del(struct timo *);
+
+void filelist_init(void);
+void filelist_done(void);
+void filelist_unlisten(void);
+
+struct file *file_new(struct fileops *, void *, char *, unsigned int);
+void file_del(struct file *);
+void file_log(struct file *);
+
+int file_poll(void);
+
+#endif /* !defined(FILE_H) */
diff --git a/usr.bin/sndiod/listen.c b/usr.bin/sndiod/listen.c
new file mode 100644
index 00000000000..e76b6437dca
--- /dev/null
+++ b/usr.bin/sndiod/listen.c
@@ -0,0 +1,281 @@
+/* $OpenBSD: listen.c,v 1.1 2012/11/23 07:03:28 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.
+ */
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/signal.h>
+#include <sys/stat.h>
+#include <sys/un.h>
+
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <netdb.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "listen.h"
+#include "file.h"
+#include "sock.h"
+#include "utils.h"
+
+int listen_pollfd(void *, struct pollfd *);
+int listen_revents(void *, struct pollfd *);
+void listen_in(void *);
+void listen_out(void *);
+void listen_hup(void *);
+
+struct fileops listen_fileops = {
+ "listen",
+ listen_pollfd,
+ listen_revents,
+ listen_in,
+ listen_out,
+ listen_hup
+};
+
+struct listen *listen_list = NULL;
+
+void
+listen_close(struct listen *f)
+{
+ struct listen **pf;
+
+ for (pf = &listen_list; *pf != f; pf = &(*pf)->next) {
+#ifdef DEBUG
+ if (*pf == NULL) {
+ log_puts("listen_close: not on list\n");
+ panic();
+ }
+#endif
+ }
+ *pf = f->next;
+
+ if (f->path != NULL) {
+ unlink(f->path);
+ xfree(f->path);
+ }
+ file_del(f->file);
+ close(f->fd);
+ xfree(f);
+}
+
+void
+listen_new_un(char *path)
+{
+ int sock, oldumask;
+ struct sockaddr_un sockname;
+ struct listen *f;
+
+ sock = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (sock < 0) {
+ perror("socket");
+ exit(1);
+ }
+ if (unlink(path) < 0 && errno != ENOENT) {
+ perror("unlink");
+ goto bad_close;
+ }
+ sockname.sun_family = AF_UNIX;
+ strlcpy(sockname.sun_path, path, sizeof(sockname.sun_path));
+ oldumask = umask(0111);
+ if (bind(sock, (struct sockaddr *)&sockname,
+ sizeof(struct sockaddr_un)) < 0) {
+ perror("bind");
+ goto bad_close;
+ }
+ if (listen(sock, 1) < 0) {
+ perror("listen");
+ goto bad_close;
+ }
+ umask(oldumask);
+ f = xmalloc(sizeof(struct listen));
+ f->file = file_new(&listen_fileops, f, path, 1);
+ if (f->file == NULL)
+ goto bad_close;
+ f->path = xstrdup(path);
+ if (f->path == NULL) {
+ perror("strdup");
+ exit(1);
+ }
+ f->fd = sock;
+ f->next = listen_list;
+ listen_list = f;
+ return;
+ bad_close:
+ close(sock);
+ exit(1);
+}
+
+void
+listen_new_tcp(char *addr, unsigned int port)
+{
+ char *host, serv[sizeof(unsigned int) * 3 + 1];
+ struct addrinfo *ailist, *ai, aihints;
+ struct listen *f;
+ int s, error, opt = 1, n = 0;
+
+ /*
+ * obtain a list of possible addresses for the host/port
+ */
+ memset(&aihints, 0, sizeof(struct addrinfo));
+ snprintf(serv, sizeof(serv), "%u", port);
+ host = strcmp(addr, "-") == 0 ? NULL : addr;
+ aihints.ai_flags |= AI_PASSIVE;
+ aihints.ai_socktype = SOCK_STREAM;
+ aihints.ai_protocol = IPPROTO_TCP;
+ error = getaddrinfo(host, serv, &aihints, &ailist);
+ if (error) {
+ fprintf(stderr, "%s: %s\n", addr, gai_strerror(error));
+ exit(1);
+ }
+
+ /*
+ * for each address, try create a listening socket bound on
+ * that address
+ */
+ for (ai = ailist; ai != NULL; ai = ai->ai_next) {
+ s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
+ if (s < 0) {
+ perror("socket");
+ continue;
+ }
+ opt = 1;
+ if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR,
+ &opt, sizeof(int)) < 0) {
+ perror("setsockopt");
+ goto bad_close;
+ }
+ if (ai->ai_family == AF_INET6) {
+ /*
+ * make sure IPv6 sockets are restricted to IPv6
+ * addresses because we already use a IP socket
+ * for IP addresses
+ */
+ opt = 1;
+ if (setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY,
+ &opt, sizeof(int)) < 0) {
+ perror("setsockopt");
+ goto bad_close;
+ }
+ }
+
+ if (bind(s, ai->ai_addr, ai->ai_addrlen) < 0) {
+ perror("bind");
+ goto bad_close;
+ }
+ if (listen(s, 1) < 0) {
+ perror("listen");
+ goto bad_close;
+ }
+ f = xmalloc(sizeof(struct listen));
+ f->file = file_new(&listen_fileops, f, addr, 1);
+ if (f == NULL) {
+ bad_close:
+ close(s);
+ continue;
+ }
+ f->path = NULL;
+ f->fd = s;
+ f->next = listen_list;
+ listen_list = f;
+ n++;
+ }
+ freeaddrinfo(ailist);
+ if (n == 0)
+ exit(1);
+}
+
+int
+listen_init(struct listen *f)
+{
+ return 1;
+}
+
+int
+listen_pollfd(void *arg, struct pollfd *pfd)
+{
+ struct listen *f = arg;
+
+ if (file_slowaccept)
+ return 0;
+ pfd->fd = f->fd;
+ pfd->events = POLLIN;
+ return 1;
+}
+
+int
+listen_revents(void *arg, struct pollfd *pfd)
+{
+ return pfd->revents;
+}
+
+void
+listen_in(void *arg)
+{
+ struct listen *f = arg;
+ struct sockaddr caddr;
+ socklen_t caddrlen;
+ int sock, opt;
+
+ caddrlen = sizeof(caddrlen);
+ while ((sock = accept(f->fd, &caddr, &caddrlen)) < 0) {
+ if (errno == EINTR)
+ continue;
+ if (errno == ENFILE || errno == EMFILE)
+ file_slowaccept = 1;
+ else
+ perror("accept");
+ return;
+ }
+ if (fcntl(sock, F_SETFL, O_NONBLOCK) < 0) {
+ perror("fcntl(sock, O_NONBLOCK)");
+ close(sock);
+ return;
+ }
+ if (f->path == NULL) {
+ opt = 1;
+ if (setsockopt(sock, IPPROTO_TCP, TCP_NODELAY,
+ &opt, sizeof(int)) < 0) {
+ perror("setsockopt");
+ close(sock);
+ return;
+ }
+ }
+ if (sock_new(sock) == NULL) {
+ close(sock);
+ return;
+ }
+}
+
+void
+listen_out(void *arg)
+{
+}
+
+void
+listen_hup(void *arg)
+{
+ struct listen *f = arg;
+
+ listen_close(f);
+}
diff --git a/usr.bin/sndiod/listen.h b/usr.bin/sndiod/listen.h
new file mode 100644
index 00000000000..d1090949e62
--- /dev/null
+++ b/usr.bin/sndiod/listen.h
@@ -0,0 +1,36 @@
+/* $OpenBSD: listen.h,v 1.1 2012/11/23 07:03:28 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.
+ */
+#ifndef LISTEN_H
+#define LISTEN_H
+
+struct file;
+
+struct listen {
+ struct listen *next;
+ struct file *file;
+ char *path;
+ int fd;
+};
+
+extern struct listen *listen_list;
+
+void listen_new_un(char *);
+void listen_new_tcp(char *, unsigned int);
+int listen_init(struct listen *);
+void listen_close(struct listen *);
+
+#endif /* !defined(LISTEN_H) */
diff --git a/usr.bin/sndiod/midi.c b/usr.bin/sndiod/midi.c
new file mode 100644
index 00000000000..8e598735f4f
--- /dev/null
+++ b/usr.bin/sndiod/midi.c
@@ -0,0 +1,547 @@
+/* $OpenBSD: midi.c,v 1.1 2012/11/23 07:03:28 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.
+ */
+/*
+ * TODO
+ *
+ * use shadow variables (to save NRPNs, LSB of controller)
+ * in the midi merger
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "abuf.h"
+#include "defs.h"
+#include "dev.h"
+#include "file.h"
+#include "midi.h"
+#include "miofile.h"
+#include "sysex.h"
+#include "utils.h"
+
+/*
+ * input data rate is XFER / TIMO (in bytes per microsecond),
+ * it must be slightly larger than the MIDI standard 3125 bytes/s
+ */
+#define MIDI_XFER 1
+#define MIDI_TIMO 100000
+
+int port_open(struct port *);
+void port_imsg(void *, unsigned char *, int);
+void port_omsg(void *, unsigned char *, int);
+void port_fill(void *, int);
+void port_exit(void *);
+
+struct midiops port_midiops = {
+ port_imsg,
+ port_omsg,
+ port_fill,
+ port_exit
+};
+
+#define MIDI_NEP 32
+struct midi midi_ep[MIDI_NEP];
+struct timo midi_timo;
+struct port *port_list = NULL;
+unsigned int midi_portnum = 0;
+
+struct midithru {
+ unsigned txmask;
+#define MIDITHRU_NMAX 32
+} midithru[MIDITHRU_NMAX];
+
+/*
+ * length of voice and common messages (status byte included)
+ */
+unsigned int voice_len[] = { 3, 3, 3, 3, 2, 2, 3 };
+unsigned int common_len[] = { 0, 2, 3, 2, 0, 0, 1, 1 };
+
+void
+midi_log(struct midi *ep)
+{
+ log_puts("midi");
+ log_putu(ep - midi_ep);
+}
+
+void
+midi_ontimo(void *arg)
+{
+ int i;
+ struct midi *ep;
+
+ for (i = MIDI_NEP, ep = midi_ep; i > 0; i--, ep++) {
+ }
+ timo_add(&midi_timo, MIDI_TIMO);
+}
+
+void
+midi_init(void)
+{
+ timo_set(&midi_timo, midi_ontimo, NULL);
+ timo_add(&midi_timo, MIDI_TIMO);
+}
+
+void
+midi_done(void)
+{
+ timo_del(&midi_timo);
+}
+
+struct midi *
+midi_new(struct midiops *ops, void *arg, int mode)
+{
+ int i;
+ struct midi *ep;
+
+ for (i = 0, ep = midi_ep;; i++, ep++) {
+ if (i == MIDI_NEP)
+ return NULL;
+ if (ep->ops == NULL)
+ break;
+ }
+ ep->ops = ops;
+ ep->arg = arg;
+ ep->used = 0;
+ ep->len = 0;
+ ep->idx = 0;
+ ep->st = 0;
+ ep->txmask = 0;
+ ep->rxmask = 1 << i;
+ ep->mode = mode;
+ /*
+ * client output is our input (ibuf) and our output (obuf) goes
+ * to client input
+ */
+ if (ep->mode & MODE_MIDIOUT) {
+ abuf_init(&ep->ibuf, MIDI_BUFSZ);
+ }
+ if (ep->mode & MODE_MIDIIN) {
+ abuf_init(&ep->obuf, MIDI_BUFSZ);
+ }
+ return ep;
+}
+
+void
+midi_del(struct midi *ep)
+{
+ int i;
+
+ for (i = 0; i < MIDI_NEP; i++)
+ midi_ep[i].txmask &= ~ep->rxmask;
+ for (i = 0; i < MIDITHRU_NMAX; i++)
+ midithru[i].txmask &= ~ep->rxmask;
+
+ /* XXX: drain output */
+ ep->ops = NULL;
+ if (ep->mode & MODE_MIDIOUT) {
+ abuf_done(&ep->ibuf);
+ }
+ if (ep->mode & MODE_MIDIIN) {
+ abuf_done(&ep->obuf);
+ }
+}
+
+/*
+ * add the midi endpoint in the ``tag'' midi thru box
+ */
+void
+midi_tag(struct midi *ep, unsigned int tag)
+{
+ int i;
+ struct midi *m;
+ unsigned members;
+
+ members = midithru[tag].txmask;
+ midithru[tag].txmask |= ep->rxmask;
+
+ for (i = 0, m = midi_ep; i < MIDI_NEP; i++, m++) {
+ if (!(members & (1 << i)))
+ continue;
+ if (ep->mode & MODE_MIDIOUT)
+ ep->txmask |= m->rxmask;
+ if (ep->mode & MODE_MIDIIN)
+ m->txmask |= ep->rxmask;
+ }
+}
+
+/*
+ * remove the midi endpoint from the ``tag'' midi thru box
+ */
+void
+midi_untag(struct midi *ep, unsigned int tag)
+{
+ int i;
+ struct midi *m;
+ unsigned members;
+
+ members = midithru[tag].txmask;
+ midithru[tag].txmask &= ~ep->rxmask;
+
+ for (i = 0, m = midi_ep;; i++, m++) {
+ if (!(members & (1 << i)))
+ continue;
+ ep->txmask &= ~m->rxmask;
+ m->txmask &= ~ep->rxmask;
+ }
+}
+
+/*
+ * broadcast the given message to other members of the thru box
+ */
+void
+midi_send(struct midi *iep, unsigned char *msg, int size)
+{
+ struct midi *oep;
+ int i;
+
+#ifdef DEBUG
+ if (log_level >= 4) {
+ midi_log(iep);
+ log_puts(": sending:");
+ for (i = 0; i < size; i++) {
+ log_puts(" ");
+ log_putx(msg[i]);
+ }
+ log_puts("\n");
+ }
+#endif
+ for (i = 0; i < MIDI_NEP ; i++) {
+ if ((iep->txmask & (1 << i)) == 0)
+ continue;
+ oep = midi_ep + i;
+ if (msg[0] <= 0x7f) {
+ if (oep->owner != iep)
+ continue;
+ } else if (msg[0] <= 0xf7)
+ oep->owner = iep;
+#ifdef DEBUG
+ if (log_level >= 4) {
+ midi_log(iep);
+ log_puts(" -> ");
+ midi_log(oep);
+ log_puts("\n");
+ }
+#endif
+ oep->ops->omsg(oep->arg, msg, size);
+ }
+}
+
+
+void
+midi_fill(struct midi *oep)
+{
+ int i, count;
+ struct midi *iep;
+
+ for (i = 0; i < MIDI_NEP ; i++) {
+ if ((oep->rxmask & (1 << i)) == 0)
+ continue;
+ iep = midi_ep + i;
+ count = midi_in(iep);
+ if (count)
+ iep->ops->fill(iep->arg, count);
+ }
+}
+
+/*
+ * parse the give data chunk, and calling imsg() for each message
+ */
+void
+midi_parse(struct midi *iep, unsigned char *idata, int icount)
+{
+ int i;
+ unsigned char c;
+
+ for (i = 0; i < icount; i++) {
+ c = *idata++;
+ if (c >= 0xf8) {
+ if (c != MIDI_ACK)
+ iep->ops->imsg(iep->arg, &c, 1);
+ } else if (c == SYSEX_END) {
+ if (iep->st == SYSEX_START) {
+ iep->msg[iep->idx++] = c;
+ iep->ops->imsg(iep->arg, iep->msg, iep->idx);
+ }
+ iep->st = 0;
+ iep->idx = 0;
+ } else if (c >= 0xf0) {
+ iep->msg[0] = c;
+ iep->len = common_len[c & 7];
+ iep->st = c;
+ iep->idx = 1;
+ } else if (c >= 0x80) {
+ iep->msg[0] = c;
+ iep->len = voice_len[(c >> 4) & 7];
+ iep->st = c;
+ iep->idx = 1;
+ } else if (iep->st) {
+ if (iep->idx == 0 && iep->st != SYSEX_START)
+ iep->msg[iep->idx++] = iep->st;
+ iep->msg[iep->idx++] = c;
+ if (iep->idx == iep->len) {
+ iep->ops->imsg(iep->arg, iep->msg, iep->idx);
+ if (iep->st >= 0xf0)
+ iep->st = 0;
+ iep->idx = 0;
+ } else if (iep->idx == MIDI_MSGMAX) {
+ /* sysex continued */
+ iep->ops->imsg(iep->arg, iep->msg, iep->idx);
+ iep->idx = 0;
+ }
+ }
+ }
+}
+
+/*
+ * process input data stored in ep->ibuf
+ */
+int
+midi_in(struct midi *iep)
+{
+ unsigned char *idata;
+ int i, icount, maxavail, avail, idone;
+ struct midi *oep;
+
+ /*
+ * calculate the max message size we can process
+ */
+ maxavail = MIDI_BUFSZ;
+ for (i = 0; i < MIDI_NEP ; i++) {
+ if ((iep->txmask & (1 << i)) == 0)
+ continue;
+ oep = midi_ep + i;
+ avail = oep->obuf.len - oep->obuf.used;
+ if (maxavail > avail)
+ maxavail = avail;
+ }
+
+ /*
+ * in the works case output message is twice the
+ * input message (2-byte messages with running status)
+ */
+ maxavail /= 2;
+ idone = 0;
+ for (;;) {
+ idata = abuf_rgetblk(&iep->ibuf, &icount);
+ if (icount > maxavail)
+ icount = maxavail;
+ if (icount == 0)
+ break;
+ maxavail -= icount;
+#ifdef DEBUG
+ if (log_level >= 4) {
+ midi_log(iep);
+ log_puts(": in:");
+ for (i = 0; i < icount; i++) {
+ log_puts(" ");
+ log_putx(idata[i]);
+ }
+ log_puts("\n");
+ }
+#endif
+ midi_parse(iep, idata, icount);
+ abuf_rdiscard(&iep->ibuf, icount);
+ idone += icount;
+ }
+ return idone;
+}
+
+/*
+ * store the given message in the output buffer
+ */
+void
+midi_out(struct midi *oep, unsigned char *idata, int icount)
+{
+ unsigned char *odata;
+ int ocount;
+#ifdef DEBUG
+ int i;
+#endif
+
+ while (icount > 0) {
+ if (oep->obuf.used == oep->obuf.len) {
+#ifdef DEBUG
+ if (log_level >= 2) {
+ midi_log(oep);
+ log_puts(": overrun, discarding ");
+ log_putu(oep->obuf.used);
+ log_puts(" bytes\n");
+ }
+#endif
+ abuf_rdiscard(&oep->obuf, oep->obuf.used);
+ oep->owner = NULL;
+ return;
+ }
+ odata = abuf_wgetblk(&oep->obuf, &ocount);
+ if (ocount > icount)
+ ocount = icount;
+ memcpy(odata, idata, ocount);
+#ifdef DEBUG
+ if (log_level >= 4) {
+ midi_log(oep);
+ log_puts(": out: ");
+ for (i = 0; i < ocount; i++) {
+ log_puts(" ");
+ log_putx(odata[i]);
+ }
+ log_puts("\n");
+ }
+#endif
+ abuf_wcommit(&oep->obuf, ocount);
+ icount -= ocount;
+ idata += ocount;
+ }
+}
+
+#ifdef DEBUG
+void
+port_log(struct port *p)
+{
+ midi_log(p->midi);
+}
+#endif
+
+void
+port_imsg(void *arg, unsigned char *msg, int size)
+{
+ struct port *p = arg;
+
+ midi_send(p->midi, msg, size);
+}
+
+
+void
+port_omsg(void *arg, unsigned char *msg, int size)
+{
+ struct port *p = arg;
+
+ midi_out(p->midi, msg, size);
+}
+
+void
+port_fill(void *arg, int count)
+{
+ /* no flow control */
+}
+
+void
+port_exit(void *arg)
+{
+#ifdef DEBUG
+ struct port *p = arg;
+
+ if (log_level >= 3) {
+ port_log(p);
+ log_puts(": exit\n");
+ }
+#endif
+}
+
+/*
+ * create a new midi port
+ */
+struct port *
+port_new(char *path, unsigned int mode)
+{
+ struct port *c;
+
+ c = xmalloc(sizeof(struct port));
+ c->path = path;
+ c->state = PORT_CFG;
+ c->midi = midi_new(&port_midiops, c, mode);
+ midi_portnum++;
+ c->next = port_list;
+ port_list = c;
+ return c;
+}
+
+/*
+ * destroy the given midi port
+ */
+void
+port_del(struct port *c)
+{
+ struct port **p;
+
+ if (c->state != PORT_CFG)
+ port_close(c);
+ midi_del(c->midi);
+ for (p = &port_list; *p != c; p = &(*p)->next) {
+#ifdef DEBUG
+ if (*p == NULL) {
+ log_puts("port to delete not on list\n");
+ panic();
+ }
+#endif
+ }
+ *p = c->next;
+ xfree(c);
+}
+
+struct port *
+port_bynum(int num)
+{
+ struct port *p;
+
+ for (p = port_list; p != NULL; p = p->next) {
+ if (num-- == 0)
+ return p;
+ }
+ return NULL;
+}
+
+int
+port_open(struct port *c)
+{
+ if (!port_mio_open(c)) {
+ if (log_level >= 1) {
+ log_puts(c->path);
+ log_puts(": failed to open midi port\n");
+ }
+ return 0;
+ }
+ c->state = PORT_INIT;
+ return 1;
+}
+
+int
+port_close(struct port *c)
+{
+#ifdef DEBUG
+ if (c->state == PORT_CFG) {
+ port_log(c);
+ log_puts(": can't close port (not opened)\n");
+ }
+#endif
+ port_mio_close(c);
+ c->state = PORT_CFG;
+ return 1;
+}
+
+int
+port_init(struct port *c)
+{
+ return port_open(c);
+}
+
+void
+port_done(struct port *c)
+{
+ /* XXX: drain? */
+ if (c->state != PORT_CFG)
+ port_close(c);
+}
diff --git a/usr.bin/sndiod/midi.h b/usr.bin/sndiod/midi.h
new file mode 100644
index 00000000000..60b37a2866a
--- /dev/null
+++ b/usr.bin/sndiod/midi.h
@@ -0,0 +1,119 @@
+/* $OpenBSD: midi.h,v 1.1 2012/11/23 07:03:28 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.
+ */
+#ifndef MIDI_H
+#define MIDI_H
+
+#include "abuf.h"
+#include "miofile.h"
+
+/*
+ * 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_CTL_VOL 7 /* volume */
+
+/*
+ * Max coarse value
+ */
+#define MIDI_MAXCTL 127
+
+/*
+ * midi stream state structure
+ */
+
+struct midiops
+{
+ void (*imsg)(void *, unsigned char *, int);
+ void (*omsg)(void *, unsigned char *, int);
+ void (*fill)(void *, int);
+ void (*exit)(void *);
+};
+
+struct midi {
+ struct midiops *ops; /* port/sock/dev callbacks */
+ struct midi *owner; /* current writer stream */
+ unsigned int mode; /* MODE_{MIDIIN,MIDIOUT} */
+ void *arg; /* user data for callbacks */
+#define MIDI_MSGMAX 16 /* max size of MIDI msg */
+ unsigned char msg[MIDI_MSGMAX]; /* parsed input message */
+ unsigned int st; /* input MIDI running status */
+ unsigned int used; /* bytes used in ``msg'' */
+ unsigned int idx; /* current ``msg'' size */
+ unsigned int len; /* expected ``msg'' length */
+ unsigned int txmask; /* list of ep we send to */
+ unsigned int rxmask; /* single ep we accept data for */
+ struct abuf ibuf; /* input buffer */
+ struct abuf obuf; /* output buffer */
+};
+
+/*
+ * midi port
+ */
+struct port {
+ struct port *next;
+ struct port_mio mio;
+#define PORT_CFG 0
+#define PORT_INIT 1
+#define PORT_DRAIN 2
+ unsigned int state;
+ char *path;
+ struct midi *midi;
+ unsigned int refs;
+};
+
+/*
+ * midi control ports
+ */
+extern struct port *port_list;
+
+void midi_init(void);
+void midi_done(void);
+struct midi *midi_new(struct midiops *, void *, int);
+void midi_del(struct midi *);
+void midi_log(struct midi *);
+int midi_in(struct midi *);
+void midi_out(struct midi *, unsigned char *, int);
+void midi_send(struct midi *, unsigned char *, int);
+void midi_fill(struct midi *);
+void midi_tag(struct midi *, unsigned int);
+void midi_untag(struct midi *, unsigned int);
+
+struct port *port_new(char *, unsigned int);
+struct port *port_bynum(int);
+void port_del(struct port *);
+int port_init(struct port *);
+void port_done(struct port *);
+int port_close(struct port *);
+
+#endif /* !defined(MIDI_H) */
diff --git a/usr.bin/sndiod/miofile.c b/usr.bin/sndiod/miofile.c
new file mode 100644
index 00000000000..5fee9f9a1ca
--- /dev/null
+++ b/usr.bin/sndiod/miofile.c
@@ -0,0 +1,136 @@
+/* $OpenBSD: miofile.c,v 1.1 2012/11/23 07:03:28 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 <sys/types.h>
+#include <sys/time.h>
+
+#include <poll.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sndio.h>
+#include "defs.h"
+#include "file.h"
+#include "midi.h"
+#include "miofile.h"
+#include "utils.h"
+
+int port_mio_pollfd(void *, struct pollfd *);
+int port_mio_revents(void *, struct pollfd *);
+void port_mio_in(void *);
+void port_mio_out(void *);
+void port_mio_hup(void *);
+
+struct fileops port_mio_ops = {
+ "mio",
+ port_mio_pollfd,
+ port_mio_revents,
+ port_mio_in,
+ port_mio_out,
+ port_mio_hup
+};
+
+int
+port_mio_open(struct port *p)
+{
+ p->mio.hdl = mio_open(p->path, p->midi->mode, 1);
+ if (p->mio.hdl == NULL)
+ return 0;
+ p->mio.file = file_new(&port_mio_ops, p, p->path, mio_nfds(p->mio.hdl));
+ return 1;
+}
+
+void
+port_mio_close(struct port *p)
+{
+ file_del(p->mio.file);
+ mio_close(p->mio.hdl);
+}
+
+int
+port_mio_pollfd(void *addr, struct pollfd *pfd)
+{
+ struct port *p = addr;
+ struct midi *ep = p->midi;
+ int events = 0;
+
+ if ((ep->mode & MODE_MIDIIN) && ep->ibuf.used < ep->ibuf.len)
+ events |= POLLIN;
+ if ((ep->mode & MODE_MIDIOUT) && ep->obuf.used > 0)
+ events |= POLLOUT;
+ return mio_pollfd(p->mio.hdl, pfd, events);
+}
+
+int
+port_mio_revents(void *addr, struct pollfd *pfd)
+{
+ struct port *p = addr;
+
+ return mio_revents(p->mio.hdl, pfd);
+}
+
+void
+port_mio_in(void *arg)
+{
+ struct port *p = arg;
+ struct midi *ep = p->midi;
+ unsigned char *data;
+ int n, count;
+
+ for (;;) {
+ data = abuf_wgetblk(&ep->ibuf, &count);
+ if (count == 0)
+ break;
+ n = mio_read(p->mio.hdl, data, count);
+ if (n == 0)
+ break;
+ abuf_wcommit(&ep->ibuf, n);
+ midi_in(ep);
+ if (n < count)
+ break;
+ }
+}
+
+void
+port_mio_out(void *arg)
+{
+ struct port *p = arg;
+ struct midi *ep = p->midi;
+ unsigned char *data;
+ int n, count;
+
+ for (;;) {
+ data = abuf_rgetblk(&ep->obuf, &count);
+ if (count == 0)
+ break;
+ n = mio_write(p->mio.hdl, data, count);
+ if (n == 0)
+ break;
+ abuf_rdiscard(&ep->obuf, n);
+ if (n < count)
+ break;
+ midi_fill(ep);
+ }
+}
+
+void
+port_mio_hup(void *arg)
+{
+ struct port *p = arg;
+
+ port_close(p);
+}
diff --git a/usr.bin/sndiod/miofile.h b/usr.bin/sndiod/miofile.h
new file mode 100644
index 00000000000..128bd32ff08
--- /dev/null
+++ b/usr.bin/sndiod/miofile.h
@@ -0,0 +1,30 @@
+/* $OpenBSD: miofile.h,v 1.1 2012/11/23 07:03:28 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.
+ */
+#ifndef MIOFILE_H
+#define MIOFILE_H
+
+struct port;
+
+struct port_mio {
+ struct mio_hdl *hdl;
+ struct file *file;
+};
+
+int port_mio_open(struct port *);
+void port_mio_close(struct port *);
+
+#endif /* !defined(MIOFILE_H) */
diff --git a/usr.bin/sndiod/opt.c b/usr.bin/sndiod/opt.c
new file mode 100644
index 00000000000..ea58f5d613d
--- /dev/null
+++ b/usr.bin/sndiod/opt.c
@@ -0,0 +1,141 @@
+/* $OpenBSD: opt.c,v 1.1 2012/11/23 07:03:28 ratchov Exp $ */
+/*
+ * Copyright (c) 2008-2011 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 "dev.h"
+#include "defs.h"
+#include "opt.h"
+#include "utils.h"
+
+struct opt *opt_list = NULL;
+
+/*
+ * create a new audio sub-device "configuration"
+ */
+struct opt *
+opt_new(char *name, struct dev *dev,
+ int pmin, int pmax, int rmin, int rmax,
+ int maxweight, int mmc, int dup, unsigned int mode)
+{
+ struct opt *o, **po;
+ unsigned int len;
+ char c;
+
+ for (len = 0; name[len] != '\0'; len++) {
+ if (len == OPT_NAMEMAX) {
+ fprintf(stderr, "%s: name too long\n", name);
+ exit(1);
+ }
+ c = name[len];
+ if ((c < 'a' || c > 'z') &&
+ (c < 'A' || c > 'Z')) {
+ fprintf(stderr, "%s: '%c' not allowed\n", name, c);
+ exit(1);
+ }
+ }
+ o = xmalloc(sizeof(struct opt));
+ if (mode & MODE_PLAY) {
+ o->pmin = pmin;
+ o->pmax = pmax;
+ }
+ if (mode & MODE_RECMASK) {
+ o->rmin = rmin;
+ o->rmax = rmax;
+ }
+ o->maxweight = maxweight;
+ o->mmc = mmc;
+ o->dup = dup;
+ o->mode = mode;
+ o->dev = dev;
+ memcpy(o->name, name, len + 1);
+ for (po = &opt_list; *po != NULL; po = &(*po)->next) {
+ if (o->dev->num == (*po)->dev->num &&
+ strcmp(o->name, (*po)->name) == 0) {
+ fprintf(stderr, "%s: already defined\n", o->name);
+ exit(1);
+ }
+ }
+ o->next = NULL;
+ *po = o;
+ if (log_level >= 2) {
+ dev_log(o->dev);
+ log_puts(".");
+ log_puts(o->name);
+ log_puts(":");
+ if (o->mode & MODE_REC) {
+ log_puts(" rec=");
+ log_putu(o->rmin);
+ log_puts(":");
+ log_putu(o->rmax);
+ }
+ if (o->mode & MODE_PLAY) {
+ log_puts(" play=");
+ log_putu(o->pmin);
+ log_puts(":");
+ log_putu(o->pmax);
+ log_puts(" vol=");
+ log_putu(o->maxweight);
+ }
+ if (o->mode & MODE_MON) {
+ log_puts(" mon=");
+ log_putu(o->rmin);
+ log_puts(":");
+ log_putu(o->rmax);
+ }
+ if (o->mode & (MODE_RECMASK | MODE_PLAY)) {
+ if (o->mmc)
+ log_puts(" mmc");
+ if (o->dup)
+ log_puts(" dup");
+ }
+ log_puts("\n");
+ }
+ return o;
+}
+
+struct opt *
+opt_byname(char *name, unsigned int num)
+{
+ struct opt *o;
+
+ for (o = opt_list; o != NULL; o = o->next) {
+ if (o->dev->num != num)
+ continue;
+ if (strcmp(name, o->name) == 0)
+ return o;
+ }
+ return NULL;
+}
+
+void
+opt_del(struct opt *o)
+{
+ struct opt **po;
+
+ for (po = &opt_list; *po != o; po = &(*po)->next) {
+#ifdef DEBUG
+ if (*po == NULL) {
+ log_puts("opt_del: not on list\n");
+ panic();
+ }
+#endif
+ }
+ *po = o->next;
+ xfree(o);
+}
diff --git a/usr.bin/sndiod/opt.h b/usr.bin/sndiod/opt.h
new file mode 100644
index 00000000000..49c185b8e73
--- /dev/null
+++ b/usr.bin/sndiod/opt.h
@@ -0,0 +1,42 @@
+/* $OpenBSD: opt.h,v 1.1 2012/11/23 07:03:28 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.
+ */
+#ifndef OPT_H
+#define OPT_H
+
+struct dev;
+
+struct opt {
+ struct opt *next;
+#define OPT_NAMEMAX 11
+ char name[OPT_NAMEMAX + 1];
+ int maxweight; /* max dynamic range for clients */
+ int pmin, pmax; /* play channels */
+ int rmin, rmax; /* recording channels */
+ int mmc; /* true if MMC control enabled */
+ int dup; /* true if join/expand enabled */
+ int mode; /* bitmap of MODE_XXX */
+ struct dev *dev; /* device to which we're attached */
+};
+
+extern struct opt *opt_list;
+
+struct opt *opt_new(char *, struct dev *, int, int, int, int,
+ int, int, int, unsigned int);
+void opt_del(struct opt *);
+struct opt *opt_byname(char *, unsigned int);
+
+#endif /* !defined(OPT_H) */
diff --git a/usr.bin/sndiod/siofile.c b/usr.bin/sndiod/siofile.c
new file mode 100644
index 00000000000..86cb036cb59
--- /dev/null
+++ b/usr.bin/sndiod/siofile.c
@@ -0,0 +1,416 @@
+/* $OpenBSD: siofile.c,v 1.1 2012/11/23 07:03:28 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 <sys/time.h>
+#include <sys/types.h>
+
+#include <poll.h>
+#include <sndio.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "abuf.h"
+#include "defs.h"
+#include "dev.h"
+#include "dsp.h"
+#include "file.h"
+#include "siofile.h"
+#include "utils.h"
+
+int dev_sio_pollfd(void *, struct pollfd *);
+int dev_sio_revents(void *, struct pollfd *);
+void dev_sio_run(void *);
+void dev_sio_hup(void *);
+
+struct fileops dev_sio_ops = {
+ "sio",
+ dev_sio_pollfd,
+ dev_sio_revents,
+ dev_sio_run,
+ dev_sio_run,
+ dev_sio_hup
+};
+
+void
+dev_sio_onmove(void *arg, int delta)
+{
+ struct dev *d = arg;
+
+#ifdef DEBUG
+ if (log_level >= 4) {
+ dev_log(d);
+ log_puts(": tick, delta = ");
+ log_puti(delta);
+ log_puts("\n");
+ }
+ d->sio.sum_utime += file_utime - d->sio.utime;
+ d->sio.sum_wtime += file_wtime - d->sio.wtime;
+ d->sio.wtime = file_wtime;
+ d->sio.utime = file_utime;
+ if (d->mode & MODE_PLAY)
+ d->sio.pused -= delta;
+ if (d->mode & MODE_REC)
+ d->sio.rused += delta;
+#endif
+ dev_onmove(d, delta);
+}
+
+/*
+ * open the device.
+ */
+int
+dev_sio_open(struct dev *d)
+{
+ struct sio_par par;
+ unsigned int mode = d->mode & (MODE_PLAY | MODE_REC);
+
+ d->sio.hdl = sio_open(d->path, mode, 1);
+ if (d->sio.hdl == NULL) {
+ if (mode != (SIO_PLAY | SIO_REC))
+ return 0;
+ d->sio.hdl = sio_open(d->path, SIO_PLAY, 1);
+ if (d->sio.hdl != NULL)
+ mode = SIO_PLAY;
+ else {
+ d->sio.hdl = sio_open(d->path, SIO_REC, 1);
+ if (d->sio.hdl != NULL)
+ mode = SIO_REC;
+ else
+ return 0;
+ }
+ if (log_level >= 1) {
+ log_puts("warning, device opened in ");
+ log_puts(mode == SIO_PLAY ? "play-only" : "rec-only");
+ log_puts(" mode\n");
+ }
+ }
+ sio_initpar(&par);
+ par.bits = d->par.bits;
+ par.bps = d->par.bps;
+ par.sig = d->par.sig;
+ par.le = d->par.le;
+ par.msb = d->par.msb;
+ if (mode & SIO_PLAY)
+ par.pchan = d->pchan;
+ if (mode & SIO_REC)
+ par.rchan = d->rchan;
+ if (d->bufsz)
+ par.appbufsz = d->bufsz;
+ if (d->round)
+ par.round = d->round;
+ if (d->rate)
+ par.rate = d->rate;
+ if (!sio_setpar(d->sio.hdl, &par))
+ goto bad_close;
+ if (!sio_getpar(d->sio.hdl, &par))
+ goto bad_close;
+ d->par.bits = par.bits;
+ d->par.bps = par.bps;
+ d->par.sig = par.sig;
+ d->par.le = par.le;
+ d->par.msb = par.msb;
+ if (mode & SIO_PLAY)
+ d->pchan = par.pchan;
+ if (mode & SIO_REC)
+ d->rchan = par.rchan;
+ d->bufsz = par.bufsz;
+ d->round = par.round;
+ d->rate = par.rate;
+ if (!(mode & MODE_PLAY))
+ d->mode &= ~(MODE_PLAY | MODE_MON);
+ if (!(mode & MODE_REC))
+ d->mode &= ~MODE_REC;
+ sio_onmove(d->sio.hdl, dev_sio_onmove, d);
+ d->sio.file = file_new(&dev_sio_ops, d, d->path, sio_nfds(d->sio.hdl));
+ return 1;
+ bad_close:
+ sio_close(d->sio.hdl);
+ return 0;
+}
+
+void
+dev_sio_close(struct dev *d)
+{
+#ifdef DEBUG
+ if (log_level >= 3) {
+ dev_log(d);
+ log_puts(": closed\n");
+ }
+#endif
+ file_del(d->sio.file);
+ sio_close(d->sio.hdl);
+}
+
+void
+dev_sio_start(struct dev *d)
+{
+ if (!sio_start(d->sio.hdl)) {
+ if (log_level >= 1) {
+ dev_log(d);
+ log_puts(": failed to start device\n");
+ }
+ return;
+ }
+ if (d->mode & MODE_PLAY) {
+ d->sio.cstate = DEV_SIO_CYCLE;
+ d->sio.todo = 0;
+ } else {
+ d->sio.cstate = DEV_SIO_READ;
+ d->sio.todo = d->round * d->rchan * d->par.bps;
+ }
+#ifdef DEBUG
+ d->sio.pused = 0;
+ d->sio.rused = 0;
+ d->sio.sum_utime = 0;
+ d->sio.sum_wtime = 0;
+ d->sio.wtime = file_wtime;
+ d->sio.utime = file_utime;
+ if (log_level >= 3) {
+ dev_log(d);
+ log_puts(": started\n");
+ }
+#endif
+}
+
+void
+dev_sio_stop(struct dev *d)
+{
+ if (!sio_eof(d->sio.hdl) && !sio_stop(d->sio.hdl)) {
+ if (log_level >= 1) {
+ dev_log(d);
+ log_puts(": failed to stop device\n");
+ }
+ return;
+ }
+#ifdef DEBUG
+ if (log_level >= 3) {
+ dev_log(d);
+ log_puts(": stopped, load avg = ");
+ log_puti(d->sio.sum_utime / 1000);
+ log_puts(" / ");
+ log_puti(d->sio.sum_wtime / 1000);
+ log_puts("\n");
+ }
+#endif
+}
+
+int
+dev_sio_pollfd(void *arg, struct pollfd *pfd)
+{
+ struct dev *d = arg;
+ int events;
+
+ events = (d->sio.cstate == DEV_SIO_READ) ? POLLIN : POLLOUT;
+ return sio_pollfd(d->sio.hdl, pfd, events);
+}
+
+int
+dev_sio_revents(void *arg, struct pollfd *pfd)
+{
+ struct dev *d = arg;
+ int events;
+
+ events = sio_revents(d->sio.hdl, pfd);
+#ifdef DEBUG
+ d->sio.events = events;
+#endif
+ return events;
+}
+
+void
+dev_sio_run(void *arg)
+{
+ struct dev *d = arg;
+ unsigned char *data, *base;
+ unsigned int n;
+
+ /*
+ * sio_read() and sio_write() would block at the end of the
+ * cycle so we *must* return and restart poll()'ing. Otherwise
+ * we may trigger dev_cycle() which would make all clients
+ * underrun (ex, on a play-only device)
+ */
+ for (;;) {
+ if (d->pstate != DEV_RUN)
+ return;
+ switch (d->sio.cstate) {
+ case DEV_SIO_READ:
+#ifdef DEBUG
+ if (!(d->sio.events & POLLIN)) {
+ dev_log(d);
+ log_puts(": recording, but POLLIN not set\n");
+ panic();
+ }
+ if (d->sio.todo == 0) {
+ dev_log(d);
+ log_puts(": can't read data\n");
+ panic();
+ }
+ if (d->prime > 0) {
+ dev_log(d);
+ log_puts(": unexpected data\n");
+ panic();
+ }
+#endif
+ base = d->decbuf ? d->decbuf : (unsigned char *)d->rbuf;
+ data = base +
+ d->rchan * d->round * d->par.bps -
+ d->sio.todo;
+ n = sio_read(d->sio.hdl, data, d->sio.todo);
+ d->sio.todo -= n;
+#ifdef DEBUG
+ if (n == 0 && data == base && !sio_eof(d->sio.hdl)) {
+ dev_log(d);
+ log_puts(": read blocked at cycle start\n");
+ }
+ if (log_level >= 4) {
+ dev_log(d);
+ log_puts(": read ");
+ log_putu(n);
+ log_puts(": bytes, todo ");
+ log_putu(d->sio.todo);
+ log_puts("/");
+ log_putu(d->round * d->rchan * d->par.bps);
+ log_puts("\n");
+ }
+#endif
+ if (d->sio.todo > 0)
+ return;
+#ifdef DEBUG
+ d->sio.rused -= d->round;
+ if (log_level >= 2) {
+ if (d->sio.rused >= d->round) {
+ dev_log(d);
+ log_puts(": rec hw xrun, rused = ");
+ log_puti(d->sio.rused);
+ log_puts("/");
+ log_puti(d->bufsz);
+ log_puts("\n");
+ }
+ if (d->sio.rused < 0 ||
+ d->sio.rused >= d->bufsz) {
+ dev_log(d);
+ log_puts(": out of bounds rused = ");
+ log_puti(d->sio.rused);
+ log_puts("/");
+ log_puti(d->bufsz);
+ log_puts("\n");
+ }
+ }
+#endif
+ d->sio.cstate = DEV_SIO_CYCLE;
+ break;
+ case DEV_SIO_CYCLE:
+#ifdef DEBUG
+ /*
+ * check that we're called at cycle boundary:
+ * either after a recorded block, or when POLLOUT is
+ * raised
+ */
+ if (!((d->mode & MODE_REC) && d->prime == 0) &&
+ !(d->sio.events & POLLOUT)) {
+ dev_log(d);
+ log_puts(": cycle not at block boundary\n");
+ panic();
+ }
+#endif
+ dev_cycle(d);
+ if (d->mode & MODE_PLAY) {
+ d->sio.cstate = DEV_SIO_WRITE;
+ d->sio.todo = d->round * d->pchan * d->par.bps;
+ break;
+ } else {
+ d->sio.cstate = DEV_SIO_READ;
+ d->sio.todo = d->round * d->rchan * d->par.bps;
+ return;
+ }
+ case DEV_SIO_WRITE:
+#ifdef DEBUG
+ if (d->sio.todo == 0) {
+ dev_log(d);
+ log_puts(": can't write data\n");
+ panic();
+ }
+#endif
+ base = d->encbuf ? d->encbuf : (unsigned char *)DEV_PBUF(d);
+ data = base +
+ d->pchan * d->round * d->par.bps -
+ d->sio.todo;
+ n = sio_write(d->sio.hdl, data, d->sio.todo);
+ d->sio.todo -= n;
+#ifdef DEBUG
+ if (n == 0 && data == base && !sio_eof(d->sio.hdl)) {
+ dev_log(d);
+ log_puts(": write blocked at cycle start\n");
+ }
+ if (log_level >= 4) {
+ dev_log(d);
+ log_puts(": wrote ");
+ log_putu(n);
+ log_puts(" bytes, todo ");
+ log_putu(d->sio.todo);
+ log_puts("/");
+ log_putu(d->round * d->pchan * d->par.bps);
+ log_puts("\n");
+ }
+#endif
+ if (d->sio.todo > 0)
+ return;
+#ifdef DEBUG
+ d->sio.pused += d->round;
+ if (log_level >= 2) {
+ if (d->prime == 0 &&
+ d->sio.pused <= d->bufsz - d->round) {
+ dev_log(d);
+ log_puts(": play hw xrun, pused = ");
+ log_puti(d->sio.pused);
+ log_puts("/");
+ log_puti(d->bufsz);
+ log_puts("\n");
+ }
+ if (d->sio.pused < 0 ||
+ d->sio.pused > d->bufsz) {
+ /* device driver or libsndio bug */
+ dev_log(d);
+ log_puts(": out of bounds pused = ");
+ log_puti(d->sio.pused);
+ log_puts("/");
+ log_puti(d->bufsz);
+ log_puts("\n");
+ }
+ }
+#endif
+ d->poffs += d->round;
+ if (d->poffs == d->bufsz)
+ d->poffs = 0;
+ if ((d->mode & MODE_REC) && d->prime == 0) {
+ d->sio.cstate = DEV_SIO_READ;
+ d->sio.todo = d->round * d->rchan * d->par.bps;
+ } else
+ d->sio.cstate = DEV_SIO_CYCLE;
+ return;
+ }
+ }
+}
+
+void
+dev_sio_hup(void *arg)
+{
+ struct dev *d = arg;
+
+ dev_close(d);
+}
diff --git a/usr.bin/sndiod/siofile.h b/usr.bin/sndiod/siofile.h
new file mode 100644
index 00000000000..8c50be50793
--- /dev/null
+++ b/usr.bin/sndiod/siofile.h
@@ -0,0 +1,43 @@
+/* $OpenBSD: siofile.h,v 1.1 2012/11/23 07:03:28 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.
+ */
+#ifndef SIOFILE_H
+#define SIOFILE_H
+
+struct dev;
+
+struct siofile_ {
+ struct sio_hdl *hdl;
+ unsigned int todo;
+#ifdef DEBUG
+ long long wtime, utime;
+ long long sum_wtime, sum_utime;
+ int pused, rused, events;
+#endif
+ struct file *file;
+#define DEV_SIO_READ 0
+#define DEV_SIO_CYCLE 1
+#define DEV_SIO_WRITE 2
+ int cstate;
+};
+
+int dev_sio_open(struct dev *);
+void dev_sio_close(struct dev *);
+void dev_sio_log(struct dev *);
+void dev_sio_start(struct dev *);
+void dev_sio_stop(struct dev *);
+
+#endif /* !defined(SIOFILE_H) */
diff --git a/usr.bin/sndiod/sndiod.1 b/usr.bin/sndiod/sndiod.1
new file mode 100644
index 00000000000..20189fd34c2
--- /dev/null
+++ b/usr.bin/sndiod/sndiod.1
@@ -0,0 +1,541 @@
+.\" $OpenBSD: sndiod.1,v 1.1 2012/11/23 07:03:28 ratchov Exp $
+.\"
+.\" Copyright (c) 2006-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.
+.\"
+.Dd $Mdocdate: November 23 2012 $
+.Dt SNDIOD 1
+.Os
+.Sh NAME
+.Nm sndiod
+.Nd audio/MIDI server
+.Sh SYNOPSIS
+.Nm sndiod
+.Bk -words
+.Op Fl d
+.Op Fl a Ar flag
+.Op Fl b Ar nframes
+.Op Fl C Ar min : Ns Ar max
+.Op Fl c Ar min : Ns Ar max
+.Op Fl e Ar enc
+.Op Fl f Ar device
+.Op Fl j Ar flag
+.Op Fl L Ar addr
+.Op Fl m Ar mode
+.Op Fl q Ar port
+.Op Fl r Ar rate
+.Op Fl s Ar name
+.Op Fl t Ar mode
+.Op Fl U Ar unit
+.Op Fl v Ar volume
+.Op Fl w Ar flag
+.Op Fl z Ar nframes
+.Ek
+.Sh DESCRIPTION
+The
+.Nm
+daemon is an intermediate layer between
+audio or MIDI programs and the hardware.
+It performs the necessary audio processing to
+allow any program to work on any supported hardware.
+By default,
+.Nm
+accepts connections from programs
+running on the same system only;
+it initializes only when programs are using its services,
+allowing
+.Nm
+to consume a negligible amount of system resources the rest of the time.
+Systems with no audio hardware can use
+.Nm
+to keep hot-pluggable devices usable by default at
+virtually no cost.
+.Pp
+.Nm
+operates as follows: it exposes at least one
+.Em sub-device
+that any number of audio programs can connect to and use as if it was
+audio hardware.
+During playback,
+.Nm
+receives audio data concurrently from all programs, mixes it and sends
+the result to the hardware device.
+Similarly, during recording it duplicates audio data recorded
+from the device and sends it to all programs.
+Since audio data flows through the
+.Nm
+process, it has the opportunity to process audio data on the fly:
+.Pp
+.Bl -bullet -offset indent -compact
+.It
+Change the sound encoding to overcome incompatibilities between
+software and hardware.
+.It
+Route the sound from one channel to another,
+join stereo or split mono.
+.It
+Control the per-application playback volume as well as the
+master volume.
+.It
+Monitor the sound being played, allowing one program to record
+what other programs play.
+.El
+.Pp
+Processing is configured on a per sub-device basis, meaning that
+the sound of all programs connected to the same sub-device will be
+processed according to the same configuration.
+Multiple sub-devices can be defined, allowing multiple configurations
+to coexist.
+The user selects the configuration a given program will use
+by selecting the sub-device the program uses.
+.Pp
+.Nm
+exposes MIDI thru boxes (aka a
+.Dq hubs
+for MIDI messages),
+allowing programs to send MIDI messages to each other
+or to hardware MIDI ports in a uniform way.
+.Pp
+Finally,
+.Nm
+exposes a control MIDI port usable for:
+.Pp
+.Bl -bullet -offset indent -compact
+.It
+Volume control.
+.It
+Common clock source for audio and MIDI programs.
+.It
+Start, stop and relocate groups of audio programs.
+.El
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl a Ar flag
+Control whether
+.Nm
+opens the audio device or the MIDI port only when needed or keeps
+it open all the time.
+If the flag is
+.Va on
+then the audio device or MIDI port is kept open all the time, ensuring
+no other program can steal it.
+If the flag is
+.Va off ,
+then it's automatically closed, allowing other programs to have direct
+access to the audio device, or the device to be disconnected.
+The default is
+.Va off .
+.It Fl b Ar nframes
+The buffer size of the audio device in frames.
+A frame consists of one sample for each channel in the stream.
+This is the number of frames that will be buffered before being played
+and thus controls the playback latency.
+The default is 7680 or twice the block size
+.Pq Fl z ,
+if the block size is set.
+.It Xo
+.Fl C Ar min : Ns Ar max ,
+.Fl c Ar min : Ns Ar max
+.Xc
+The range of channel numbers for recording and playback directions,
+respectively any client is allowed to use.
+This is a subset of the audio device channels.
+The default is 0:1, i.e. stereo.
+.It Fl d
+Increase log verbosity.
+.Nm
+logs on
+.Em stderr .
+.It Fl e Ar enc
+Attempt to configure the device to use this encoding.
+The default is
+.Va s16 .
+Encoding names use the follwing scheme: signedness
+.Po
+.Va s
+or
+.Va u
+.Pc
+followed
+by the precision in bits, the byte-order
+.Po
+.Va le
+or
+.Va be
+.Pc ,
+the number of
+bytes per sample, and the alignement
+.Po
+.Va msb
+or
+.Va lsb
+.Pc .
+Only the signedness and the precision are mandatory.
+Examples:
+.Va u8 , s16le , s24le3 , s24le4lsb.
+.It Fl f Ar device
+Add this
+.Xr sndio 7
+audio device to devices used for playing and/or recording.
+Preceding per-device options
+.Pq Fl aberwz
+apply to this device.
+Sub-devices
+.Pq Fl s
+that are applied after will be attached to this device.
+Device mode and parameters are determined from sub-devices
+attached to it.
+.It Fl j Ar flag
+Control whether program channels are joined or expanded if
+the number of channels requested by a program is not equal
+to the device number of channels.
+If the flag is
+.Va off
+then client channels are routed to the corresponding
+device channel, possibly discarding channels not present in the device.
+If the flag is
+.Va on ,
+then a single client channel may be sent on multiple device channels,
+or multiple client channels may be sent to a single device channel.
+For instance, this feature could be used for mono to stereo conversions.
+The default is
+.Ar on .
+.It Fl L Ar addr
+Specify a local network address
+.Nm
+should listen;
+.Nm
+will listen on TCP port 11025+n, where n is the unit number
+specified with
+.Fl U .
+Without this option,
+.Nm
+listens on the
+.Ux Ns -domain
+socket only, and is not reachable from any network.
+If the option argument is
+.Sq -
+then
+.Nm
+will accept connections from any address.
+.It Fl m Ar mode
+Set the sub-device mode.
+Valid modes are
+.Ar play ,
+.Ar rec ,
+and
+.Ar mon ,
+corresponding to playback, recording and monitoring.
+A monitoring stream is a fake recording stream corresponding to
+the mix of all playback streams.
+Multiple modes can be specified, separated by commas,
+but the same sub-device cannot be used for both recording and monitoring.
+The default is
+.Ar play , Ns Ar rec
+(i.e. full-duplex).
+.It Fl q Ar port
+Expose the given MIDI port.
+This allows multiple programs to share the port.
+.It Fl r Ar rate
+Attempt to force the device to use this sample rate in Hertz.
+The default is 48000.
+.It Fl s Ar name
+Add
+.Ar name
+to the list of sub-devices to expose.
+This allows clients to use
+.Nm
+instead of the physical audio device for audio input and output
+in order to share the physical device with other clients.
+Defining multiple sub-devices allows splitting a physical audio device
+into sub-devices having different properties (e.g. channel ranges).
+The given
+.Ar name
+corresponds to the
+.Dq option
+part of the
+.Xr sndio 7
+device name string.
+.It Fl t Ar mode
+Select the way clients are controlled by MIDI Machine Control (MMC)
+messages received by
+.Nm .
+If the mode is
+.Va off
+(the default), then programs are not affected by MMC messages.
+If the mode is
+.Va slave ,
+then programs are started synchronously by MMC start messages;
+additionally, the server clock is exposed as MIDI Time Code (MTC)
+messages allowing MTC-capable software or hardware to be synchronized
+to audio programs.
+.It Fl U Ar unit
+Unit number.
+Each
+.Nm
+server instance has an unique unit number,
+used in
+.Xr sndio 7
+device names.
+The default is 0.
+The unit number must be set before any
+.Fl L
+is used.
+.It Fl v Ar volume
+Software volume attenuation of playback.
+The value must be between 1 and 127,
+corresponding to \-42dB and \-0dB attenuation in 1/3dB steps.
+Clients inherit this parameter.
+Reducing the volume in advance allows a client's volume to stay independent
+from the number of clients as long as their number is small enough.
+18 volume units (i.e. \-6dB attenuation) allows the number
+of playback programs to be doubled.
+The default is 118 i.e. \-3dB.
+.It Fl w Ar flag
+Control
+.Nm
+behaviour when the maximum volume of the hardware is reached
+and a new program starts playing.
+This happens only when volumes are not properly set using the
+.Fl v
+option.
+If the flag is
+.Va on ,
+then the master volume is automatically adjusted to avoid clipping.
+Using
+.Va off
+makes sense in the rare situation where all programs lower their volumes.
+The default is
+.Va on .
+.It Fl z Ar nframes
+The audio device block size in frames.
+This is the number of frames between audio clock ticks,
+i.e. the clock resolution.
+If a sub-device is created with the
+.Fl t
+option, and MTC is used for synchronization, the clock
+resolution must be 96, 100 or 120 ticks per second for maximum
+accuracy.
+For instance, 100 ticks per second at 48000Hz corresponds
+to a 480 frame block size.
+The default is 960 or half of the buffer size
+.Pq Fl b ,
+if the buffer size is set.
+.El
+.Pp
+On the command line,
+per-device parameters
+.Pq Fl aberwz
+must precede the device definition
+.Pq Fl f ,
+and per-sub-device parameters
+.Pq Fl Ccjmtvx
+must precede the sub-device definition
+.Pq Fl s .
+Sub-device definitions
+.Pq Fl s
+must follow the definition of the device
+.Pq Fl f
+to which they are attached.
+.Pp
+If no audio devices
+.Pq Fl f
+are specified,
+settings are applied as if
+the default device is specified.
+If no sub-devices
+.Pq Fl s
+are specified for a device, a default sub-device is
+created attached to it.
+If a device
+.Pq Fl f
+is defined twice, both definitions are merged:
+parameters of the first one are used but sub-devices
+.Pq Fl s
+of both definitions are created.
+The default
+.Xr sndio 7
+device used by
+.Nm
+is
+.Pa rsnd/0 ,
+and the default sub-device exposed by
+.Nm
+is
+.Pa snd/0 .
+.Pp
+If
+.Nm
+is sent
+.Dv SIGHUP ,
+.Dv SIGINT
+or
+.Dv SIGTERM ,
+it terminates.
+.Pp
+By default, when the program cannot accept
+recorded data fast enough or cannot provide data to play fast enough,
+the program is paused, i.e. samples that cannot be written are discarded
+and samples that cannot be read are replaced by silence.
+If a sub-device is created with the
+.Fl t
+option, then recorded samples are discarded,
+but the same amount of silence will be written
+once the program is unblocked, in order to reach the right position in time.
+Similarly silence is played, but the same amount of samples will be discarded
+once the program is unblocked.
+This ensures proper synchronization between programs.
+.Sh MIDI CONTROL
+.Nm
+creates a MIDI port with the same name as the exposed audio
+sub-device to which MIDI programs can connect.
+.Nm
+exposes the audio device clock
+and allows audio device properties to be controlled
+through MIDI.
+.Pp
+A MIDI channel is assigned to each stream, and the volume
+is changed using the standard volume controller (number 7).
+Similarly, when the audio client changes its volume,
+the same MIDI controller message is sent out; it can be used
+for instance for monitoring or as feedback for motorized
+faders.
+.Pp
+The master volume can be changed using the standard master volume
+system exclusive message.
+.Pp
+Streams created with the
+.Fl t
+option are controlled by the following MMC messages:
+.Bl -tag -width relocateXXX -offset indent
+.It relocate
+This message is ignored by audio
+.Nm
+clients, but the given time position is sent to MIDI ports as an MTC
+.Dq "full frame"
+message forcing all MTC-slaves to relocate to the given
+position (see below).
+.It start
+Put all streams in starting mode.
+In this mode,
+.Nm
+waits for all streams to become ready
+to start, and then starts them synchronously.
+Once started, new streams can be created
+.Pq Nm sndiod
+but they will be blocked
+until the next stop-to-start transition.
+.It stop
+Put all streams in stopped mode (the default).
+In this mode, any stream attempting to start playback or recording
+is paused.
+Client streams that are already
+started are not affected until they stop and try to start again.
+.El
+.Pp
+Streams created with the
+.Fl t
+option export the
+.Nm
+device clock using MTC, allowing non-audio
+software or hardware to be synchronized to the audio stream.
+Maximum accuracy is achieved when the number of blocks per
+second is equal to one of the standard MTC clock rates (96, 100 and 120Hz).
+The following sample rates
+.Pq Fl r
+and block sizes
+.Pq Fl z
+are recommended:
+.Pp
+.Bl -bullet -offset indent -compact
+.It
+44100Hz, 441 frames (MTC rate is 100Hz)
+.It
+48000Hz, 400 frames (MTC rate is 120Hz)
+.It
+48000Hz, 480 frames (MTC rate is 100Hz)
+.It
+48000Hz, 500 frames (MTC rate is 96Hz)
+.El
+.Pp
+For instance, the following command will create two devices:
+the default
+.Va snd/0
+and a MIDI-controlled
+.Va snd/0.mmc :
+.Bd -literal -offset indent
+$ sndiod -r 48000 -z 400 -s default -t slave -s mmc
+.Ed
+.Pp
+Streams connected to
+.Va snd/0
+behave normally, while streams connected to
+.Va snd/0.mmc
+wait for the MMC start signal and start synchronously.
+Regardless of which device a stream is connected to,
+its playback volume knob is exposed.
+.Sh EXAMPLES
+Start server using default parameters, creating an
+additional sub-device for output to channels 2:3 only (rear speakers
+on most cards), exposing the
+.Pa snd/0
+and
+.Pa snd/0.rear
+devices:
+.Bd -literal -offset indent
+$ sndiod -s default -c 2:3 -s rear
+.Ed
+.Pp
+Start server creating the default sub-device with low volume and
+an additional sub-device for high volume output, exposing the
+.Pa snd/0
+and
+.Pa snd/0.max
+devices:
+.Bd -literal -offset indent
+$ sndiod -v 65 -s default -v 127 -s max
+.Ed
+.Pp
+Start server configuring the audio device to use
+a 48kHz sample frequency, 240-frame block size,
+and 2-block buffers.
+The corresponding latency is 10ms, which is
+the time it takes the sound to propagate 3.5 meters.
+.Bd -literal -offset indent
+$ sndiod -r 48000 -b 480 -z 240
+.Ed
+.Sh SEE ALSO
+.Xr sndio 7
+.Sh BUGS
+Resampling is low quality; down-sampling especially should be avoided
+when recording.
+.Pp
+Processing is done using 16-bit arithmetic,
+thus samples with more than 16 bits are rounded.
+16 bits (i.e. 97dB dynamic) are largely enough for most applications though.
+Processing precision can be increased to 24-bit at compilation time though.
+.Pp
+If
+.Fl a Ar off
+is used,
+.Nm
+creates sub-devices to expose first
+and then opens the audio hardware on demand.
+Technically, this allows
+.Nm
+to attempt to use one of the sub-devices it exposes as an audio device,
+creating a deadlock.
+There's nothing to prevent the user
+from shooting himself in the foot by creating such a deadlock.
diff --git a/usr.bin/sndiod/sndiod.c b/usr.bin/sndiod/sndiod.c
new file mode 100644
index 00000000000..9ed7da72edf
--- /dev/null
+++ b/usr.bin/sndiod/sndiod.c
@@ -0,0 +1,491 @@
+/* $OpenBSD: sndiod.c,v 1.1 2012/11/23 07:03:28 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 <sys/param.h>
+#include <sys/queue.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/resource.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <grp.h>
+#include <limits.h>
+#include <pwd.h>
+#include <signal.h>
+#include <sndio.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "amsg.h"
+#include "defs.h"
+#include "dev.h"
+#include "file.h"
+#include "listen.h"
+#include "midi.h"
+#include "opt.h"
+#include "sock.h"
+#include "utils.h"
+
+/*
+ * unprivileged user name
+ */
+#ifndef SNDIO_USER
+#define SNDIO_USER "_sndio"
+#endif
+
+/*
+ * priority when run as root
+ */
+#ifndef SNDIO_PRIO
+#define SNDIO_PRIO (-20)
+#endif
+
+/*
+ * sample rate if no ``-r'' is used
+ */
+#ifndef DEFAULT_RATE
+#define DEFAULT_RATE 48000
+#endif
+
+/*
+ * block size if neither ``-z'' nor ``-b'' is used
+ */
+#ifndef DEFAULT_ROUND
+#define DEFAULT_ROUND 960
+#endif
+
+/*
+ * buffer size if neither ``-z'' nor ``-b'' is used
+ */
+#ifndef DEFAULT_BUFSZ
+#define DEFAULT_BUFSZ 7860
+#endif
+
+/*
+ * default device in server mode
+ */
+#ifndef DEFAULT_DEV
+#define DEFAULT_DEV "rsnd/0"
+#endif
+
+unsigned int log_level = 0;
+volatile sig_atomic_t quit_flag = 0;
+
+char usagestr[] = "usage: sndiod [-d] [-a flag] [-b nframes] "
+ "[-C min:max] [-c min:max] [-e enc]\n\t"
+ "[-f device] [-j flag] [-L addr] [-m mode] [-q port] [-r rate]\n\t"
+ "[-s name] [-t mode] [-U unit] [-v volume] [-w flag] [-z nframes]\n";
+
+/*
+ * SIGINT handler, it raises the quit flag. If the flag is already set,
+ * that means that the last SIGINT was not handled, because the process
+ * is blocked somewhere, so exit.
+ */
+void
+sigint(int s)
+{
+ if (quit_flag)
+ _exit(1);
+ quit_flag = 1;
+}
+
+void
+opt_ch(int *rcmin, int *rcmax)
+{
+ char *next, *end;
+ long cmin, cmax;
+
+ errno = 0;
+ cmin = strtol(optarg, &next, 10);
+ if (next == optarg || *next != ':')
+ goto failed;
+ cmax = strtol(++next, &end, 10);
+ if (end == next || *end != '\0')
+ goto failed;
+ if (cmin < 0 || cmax < cmin || cmax >= NCHAN_MAX)
+ goto failed;
+ *rcmin = cmin;
+ *rcmax = cmax;
+ return;
+failed:
+ errx(1, "%s: bad channel range", optarg);
+}
+
+void
+opt_enc(struct aparams *par)
+{
+ int len;
+
+ len = aparams_strtoenc(par, optarg);
+ if (len == 0 || optarg[len] != '\0')
+ errx(1, "%s: bad encoding", optarg);
+}
+
+int
+opt_mmc(void)
+{
+ if (strcmp("off", optarg) == 0)
+ return 0;
+ if (strcmp("slave", optarg) == 0)
+ return 1;
+ errx(1, "%s: off/slave expected", optarg);
+}
+
+int
+opt_onoff(void)
+{
+ if (strcmp("off", optarg) == 0)
+ return 0;
+ if (strcmp("on", optarg) == 0)
+ return 1;
+ errx(1, "%s: on/off expected", optarg);
+}
+
+unsigned int
+opt_mode(void)
+{
+ unsigned int mode = 0;
+ char *p = optarg;
+ size_t len;
+
+ for (p = optarg; *p != '\0'; p++) {
+ len = strcspn(p, ",");
+ if (strncmp("play", p, len) == 0) {
+ mode |= MODE_PLAY;
+ } else if (strncmp("rec", p, len) == 0) {
+ mode |= MODE_REC;
+ } else if (strncmp("mon", p, len) == 0) {
+ mode |= MODE_MON;
+ } else if (strncmp("midi", p, len) == 0) {
+ mode |= MODE_MIDIMASK;
+ } else
+ errx(1, "%s: bad mode", optarg);
+ p += len;
+ if (*p == '\0')
+ break;
+ }
+ if (mode == 0)
+ errx(1, "empty mode");
+ return mode;
+}
+
+void
+setsig(void)
+{
+ struct sigaction sa;
+
+ quit_flag = 0;
+ sigfillset(&sa.sa_mask);
+ sa.sa_flags = SA_RESTART;
+ sa.sa_handler = sigint;
+ if (sigaction(SIGINT, &sa, NULL) < 0)
+ err(1, "sigaction(int) failed");
+ if (sigaction(SIGTERM, &sa, NULL) < 0)
+ err(1, "sigaction(term) failed");
+ if (sigaction(SIGHUP, &sa, NULL) < 0)
+ err(1, "sigaction(hup) failed");
+}
+
+void
+unsetsig(void)
+{
+ struct sigaction sa;
+
+ sigfillset(&sa.sa_mask);
+ sa.sa_flags = SA_RESTART;
+ sa.sa_handler = SIG_DFL;
+ if (sigaction(SIGHUP, &sa, NULL) < 0)
+ err(1, "unsetsig(hup): sigaction failed\n");
+ if (sigaction(SIGTERM, &sa, NULL) < 0)
+ err(1, "unsetsig(term): sigaction failed\n");
+ if (sigaction(SIGINT, &sa, NULL) < 0)
+ err(1, "unsetsig(int): sigaction failed\n");
+}
+
+void
+getbasepath(char *base, size_t size)
+{
+ uid_t uid;
+ struct stat sb;
+ mode_t mask;
+
+ uid = geteuid();
+ if (uid == 0) {
+ mask = 022;
+ snprintf(base, PATH_MAX, "/tmp/aucat");
+ } else {
+ mask = 077;
+ snprintf(base, PATH_MAX, "/tmp/aucat-%u", uid);
+ }
+ if (mkdir(base, 0777 & ~mask) < 0) {
+ if (errno != EEXIST)
+ err(1, "mkdir(\"%s\")", base);
+ }
+ if (stat(base, &sb) < 0)
+ err(1, "stat(\"%s\")", base);
+ if (sb.st_uid != uid || (sb.st_mode & mask) != 0)
+ errx(1, "%s has wrong permissions", base);
+}
+
+void
+privdrop(void)
+{
+ struct passwd *pw;
+
+ if ((pw = getpwnam(SNDIO_USER)) == NULL)
+ errx(1, "unknown user %s", SNDIO_USER);
+ if (setpriority(PRIO_PROCESS, 0, SNDIO_PRIO) < 0)
+ err(1, "setpriority");
+ if (setgroups(1, &pw->pw_gid) ||
+ setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) ||
+ setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid))
+ err(1, "cannot drop privileges");
+}
+
+struct dev *
+mkdev(char *path, struct aparams *par,
+ int mode, int bufsz, int round, int rate, int hold, int autovol)
+{
+ struct dev *d;
+
+ for (d = dev_list; d != NULL; d = d->next) {
+ if (strcmp(d->path, path) == 0)
+ return d;
+ }
+ if (!bufsz && !round) {
+ round = DEFAULT_ROUND;
+ bufsz = DEFAULT_BUFSZ;
+ } else if (!bufsz) {
+ bufsz = round * 2;
+ } else if (!round)
+ round = bufsz / 2;
+ d = dev_new(path, par, mode, bufsz, round, rate, hold, autovol);
+ if (d == NULL)
+ exit(1);
+ return d;
+}
+
+struct opt *
+mkopt(char *path, struct dev *d,
+ int pmin, int pmax, int rmin, int rmax,
+ int mode, int vol, int mmc, int dup)
+{
+ struct opt *o;
+
+ o = opt_new(path, d, pmin, pmax, rmin, rmax,
+ MIDI_TO_ADATA(vol), mmc, dup, mode);
+ if (o == NULL)
+ errx(1, "%s: couldn't create subdev", path);
+ dev_adjpar(d, o->mode, o->pmin, o->pmax, o->rmin, o->rmax);
+ return o;
+}
+
+int
+main(int argc, char **argv)
+{
+ int c, background, unit;
+ int pmin, pmax, rmin, rmax;
+ char base[PATH_MAX], path[PATH_MAX];
+ unsigned int mode, dup, mmc, vol;
+ unsigned int hold, autovol, bufsz, round, rate;
+ const char *str;
+ struct aparams par;
+ struct dev *d;
+ struct port *p;
+ struct listen *l;
+
+ atexit(log_flush);
+
+ /*
+ * global options defaults
+ */
+ vol = 118;
+ dup = 1;
+ mmc = 0;
+ hold = 0;
+ autovol = 1;
+ bufsz = 0;
+ round = 0;
+ rate = DEFAULT_RATE;
+ unit = 0;
+ background = 1;
+ pmin = 0;
+ pmax = 1;
+ rmin = 0;
+ rmax = 1;
+ aparams_init(&par);
+ mode = MODE_PLAY | MODE_REC;
+
+ setsig();
+ filelist_init();
+
+ while ((c = getopt(argc, argv, "a:b:c:C:de:f:j:L:m:q:r:s:t:U:v:w:x:z:")) != -1) {
+ switch (c) {
+ case 'd':
+ log_level++;
+ background = 0;
+ break;
+ case 'U':
+ if (listen_list)
+ errx(1, "-U must come before -L");
+ unit = strtonum(optarg, 0, 15, &str);
+ if (str)
+ errx(1, "%s: unit number is %s", optarg, str);
+ break;
+ case 'L':
+ listen_new_tcp(optarg, AUCAT_PORT + unit);
+ break;
+ case 'm':
+ mode = opt_mode();
+ break;
+ case 'j':
+ dup = opt_onoff();
+ break;
+ case 't':
+ mmc = opt_mmc();
+ break;
+ case 'c':
+ opt_ch(&pmin, &pmax);
+ break;
+ case 'C':
+ opt_ch(&rmin, &rmax);
+ break;
+ case 'e':
+ opt_enc(&par);
+ break;
+ case 'r':
+ rate = strtonum(optarg, RATE_MIN, RATE_MAX, &str);
+ if (str)
+ errx(1, "%s: rate is %s", optarg, str);
+ break;
+ case 'v':
+ vol = strtonum(optarg, 0, MIDI_MAXCTL, &str);
+ if (str)
+ errx(1, "%s: volume is %s", optarg, str);
+ break;
+ case 's':
+ if ((d = dev_list) == NULL) {
+ d = mkdev(DEFAULT_DEV, &par, 0, bufsz, round, rate,
+ hold, autovol);
+ }
+ mkopt(optarg, d, pmin, pmax, rmin, rmax,
+ mode, vol, mmc, dup);
+ break;
+ case 'q':
+ p = port_new(optarg, MODE_MIDIMASK);
+ if (!p)
+ errx(1, "%s: can't open port", optarg);
+ break;
+ case 'a':
+ hold = opt_onoff();
+ break;
+ case 'w':
+ autovol = opt_onoff();
+ break;
+ case 'b':
+ bufsz = strtonum(optarg, 1, RATE_MAX, &str);
+ if (str)
+ errx(1, "%s: buffer size is %s", optarg, str);
+ break;
+ case 'z':
+ round = strtonum(optarg, 1, SHRT_MAX, &str);
+ if (str)
+ errx(1, "%s: block size is %s", optarg, str);
+ break;
+ case 'f':
+ mkdev(optarg, &par, 0, bufsz, round, rate, hold, autovol);
+ break;
+ default:
+ fputs(usagestr, stderr);
+ return 1;
+ }
+ }
+ argc -= optind;
+ argv += optind;
+ if (argc > 0) {
+ fputs(usagestr, stderr);
+ return 1;
+ }
+ if (dev_list == NULL)
+ mkdev(DEFAULT_DEV, &par, 0, bufsz, round, rate, hold, autovol);
+ for (d = dev_list; d != NULL; d = d->next) {
+ if (opt_byname("default", d->num))
+ continue;
+ mkopt("default", d, pmin, pmax, rmin, rmax,
+ mode, vol, mmc, dup);
+ }
+ getbasepath(base, sizeof(base));
+ snprintf(path, PATH_MAX, "%s/%s%u", base, AUCAT_PATH, unit);
+ listen_new_un(path);
+ if (geteuid() == 0)
+ privdrop();
+ midi_init();
+ for (p = port_list; p != NULL; p = p->next) {
+ if (!port_init(p))
+ return 1;
+ }
+ for (d = dev_list; d != NULL; d = d->next) {
+ if (!dev_init(d))
+ return 1;
+ if (d->autostart && (d->mode & MODE_AUDIOMASK))
+ dev_mmcstart(d);
+ }
+ for (l = listen_list; l != NULL; l = l->next) {
+ if (!listen_init(l))
+ return 1;
+ }
+ if (background) {
+ log_flush();
+ log_level = 0;
+ if (daemon(0, 0) < 0)
+ err(1, "daemon");
+ }
+
+ /*
+ * Loop, start audio.
+ */
+ for (;;) {
+ if (quit_flag)
+ break;
+ if (!file_poll())
+ break;
+ }
+ while (listen_list != NULL)
+ listen_close(listen_list);
+ while (sock_list != NULL)
+ sock_close(sock_list);
+ while (opt_list != NULL)
+ opt_del(opt_list);
+ for (d = dev_list; d != NULL; d = d->next)
+ dev_done(d);
+ for (p = port_list; p != NULL; p = p->next)
+ port_done(p);
+ midi_done();
+ while (file_poll())
+ ; /* nothing */
+ while (dev_list)
+ dev_del(dev_list);
+ while (port_list)
+ port_del(port_list);
+ filelist_done();
+ rmdir(base);
+ unsetsig();
+ return 0;
+}
diff --git a/usr.bin/sndiod/sock.c b/usr.bin/sndiod/sock.c
new file mode 100644
index 00000000000..93122aae8a5
--- /dev/null
+++ b/usr.bin/sndiod/sock.c
@@ -0,0 +1,1666 @@
+/* $OpenBSD: sock.c,v 1.1 2012/11/23 07:03:28 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 <sys/types.h>
+#include <netinet/in.h>
+#include <errno.h>
+#include <poll.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "abuf.h"
+#include "defs.h"
+#include "dev.h"
+#include "file.h"
+#include "midi.h"
+#include "opt.h"
+#include "sock.h"
+#include "utils.h"
+
+void sock_attach(struct sock *, int);
+int sock_read(struct sock *);
+int sock_write(struct sock *);
+int sock_execmsg(struct sock *);
+int sock_buildmsg(struct sock *);
+void sock_close(struct sock *);
+
+int sock_pollfd(void *, struct pollfd *);
+int sock_revents(void *, struct pollfd *);
+void sock_in(void *);
+void sock_out(void *);
+void sock_hup(void *);
+
+/*
+ * slot call-backs
+ */
+void sock_slot_onmove(void *, int);
+void sock_slot_onvol(void *, unsigned int);
+void sock_slot_fill(void *);
+void sock_slot_flush(void *);
+void sock_slot_eof(void *);
+void sock_slot_mmcstart(void *);
+void sock_slot_mmcstop(void *);
+void sock_slot_mmcloc(void *, unsigned int);
+void sock_exit(void *);
+
+/*
+ * midi call-backs
+ */
+void sock_midi_imsg(void *, unsigned char *, int);
+void sock_midi_omsg(void *, unsigned char *, int);
+void sock_midi_fill(void *, int);
+
+struct fileops sock_fileops = {
+ "sock",
+ sock_pollfd,
+ sock_revents,
+ sock_in,
+ sock_out,
+ sock_hup
+};
+
+struct slotops sock_slotops = {
+ sock_slot_onmove,
+ sock_slot_onvol,
+ sock_slot_fill,
+ sock_slot_flush,
+ sock_slot_eof,
+ sock_slot_mmcstart,
+ sock_slot_mmcstop,
+ sock_slot_mmcloc,
+ sock_exit
+};
+
+struct midiops sock_midiops = {
+ sock_midi_imsg,
+ sock_midi_omsg,
+ sock_midi_fill,
+ sock_exit
+};
+
+struct sock *sock_list = NULL;
+unsigned int sock_sesrefs = 0; /* connections to the session */
+uint8_t sock_sescookie[AMSG_COOKIELEN]; /* owner of the session */
+
+void
+sock_log(struct sock *f)
+{
+#ifdef DEBUG
+ static char *rstates[] = { "ridl", "rmsg", "rdat", "rret" };
+ static char *wstates[] = { "widl", "wmsg", "wdat" };
+#endif
+ if (f->slot)
+ slot_log(f->slot);
+ else if (f->midi)
+ midi_log(f->midi);
+ else
+ log_puts("sock");
+#ifdef DEBUG
+ if (log_level >= 3) {
+ log_puts(",");
+ log_puts(rstates[f->rstate]);
+ log_puts(",");
+ log_puts(wstates[f->wstate]);
+ }
+#endif
+}
+
+void
+sock_close(struct sock *f)
+{
+ struct sock **pf;
+
+ for (pf = &sock_list; *pf != f; pf = &(*pf)->next) {
+#ifdef DEBUG
+ if (*pf == NULL) {
+ log_puts("sock_close: not on list\n");
+ panic();
+ }
+#endif
+ }
+ *pf = f->next;
+
+#ifdef DEBUG
+ if (log_level >= 3) {
+ sock_log(f);
+ log_puts(": closing\n");
+ }
+#endif
+ if (f->pstate > SOCK_AUTH)
+ sock_sesrefs--;
+ if (f->slot) {
+ slot_del(f->slot);
+ f->slot = NULL;
+ }
+ if (f->midi) {
+ midi_del(f->midi);
+ f->midi = NULL;
+ }
+ file_del(f->file);
+ close(f->fd);
+ xfree(f);
+}
+
+void
+sock_slot_fill(void *arg)
+{
+ struct sock *f = arg;
+ struct slot *s = f->slot;
+
+ f->fillpending += s->round;
+#ifdef DEBUG
+ if (log_level >= 4) {
+ sock_log(f);
+ log_puts(": fill, rmax -> ");
+ log_puti(f->rmax);
+ log_puts(", pending -> ");
+ log_puti(f->fillpending);
+ log_puts("\n");
+ }
+#endif
+}
+
+void
+sock_slot_flush(void *arg)
+{
+ struct sock *f = arg;
+ struct slot *s = f->slot;
+
+ f->wmax += s->round * s->sub.bpf;
+#ifdef DEBUG
+ if (log_level >= 4) {
+ sock_log(f);
+ log_puts(": flush, wmax -> ");
+ log_puti(f->wmax);
+ log_puts("\n");
+ }
+#endif
+}
+
+void
+sock_slot_eof(void *arg)
+{
+ struct sock *f = arg;
+
+#ifdef DEBUG
+ if (log_level >= 3) {
+ sock_log(f);
+ log_puts(": stopped\n");
+ }
+#endif
+ f->stoppending = 1;
+}
+
+void
+sock_slot_onmove(void *arg, int delta)
+{
+ struct sock *f = (struct sock *)arg;
+ struct slot *s = f->slot;
+
+#ifdef DEBUG
+ if (log_level >= 4) {
+ sock_log(f);
+ log_puts(": onmove: delta -> ");
+ log_puti(s->delta);
+ log_puts("\n");
+ }
+#endif
+ if (s->pstate != SOCK_START)
+ return;
+ f->tickpending++;
+}
+
+void
+sock_slot_onvol(void *arg, unsigned int delta)
+{
+ struct sock *f = (struct sock *)arg;
+ struct slot *s = f->slot;
+
+#ifdef DEBUG
+ if (log_level >= 4) {
+ sock_log(f);
+ log_puts(": onvol: vol -> ");
+ log_puti(s->vol);
+ log_puts("\n");
+ }
+#endif
+ if (s->pstate != SOCK_START)
+ return;
+}
+
+void
+sock_midi_imsg(void *arg, unsigned char *msg, int size)
+{
+ struct sock *f = arg;
+
+ midi_send(f->midi, msg, size);
+}
+
+void
+sock_midi_omsg(void *arg, unsigned char *msg, int size)
+{
+ struct sock *f = arg;
+
+ midi_out(f->midi, msg, size);
+}
+
+void
+sock_midi_fill(void *arg, int count)
+{
+ struct sock *f = arg;
+
+ f->fillpending += count;
+}
+
+struct sock *
+sock_new(int fd)
+{
+ struct sock *f;
+
+ f = xmalloc(sizeof(struct sock));
+ f->pstate = SOCK_AUTH;
+ f->opt = NULL;
+ f->slot = NULL;
+ f->midi = NULL;
+ f->tickpending = 0;
+ f->fillpending = 0;
+ f->stoppending = 0;
+ f->wstate = SOCK_WIDLE;
+ f->wtodo = 0xdeadbeef;
+ f->rstate = SOCK_RMSG;
+ f->rtodo = sizeof(struct amsg);
+ f->wmax = f->rmax = 0;
+ f->lastvol = -1;
+ f->file = file_new(&sock_fileops, f, "sock", 1);
+ f->fd = fd;
+ if (f->file == NULL) {
+ xfree(f);
+ return NULL;
+ }
+ f->next = sock_list;
+ sock_list = f;
+ return f;
+}
+
+void
+sock_slot_mmcstart(void *arg)
+{
+#ifdef DEBUG
+ struct sock *f = (struct sock *)arg;
+
+ if (log_level >= 3) {
+ sock_log(f);
+ log_puts(": ignored mmc start signal\n");
+ }
+#endif
+}
+
+void
+sock_slot_mmcstop(void *arg)
+{
+#ifdef DEBUG
+ struct sock *f = (struct sock *)arg;
+
+ if (log_level >= 3) {
+ sock_log(f);
+ log_puts(": ignored mmc stop signal\n");
+ }
+#endif
+}
+
+void
+sock_slot_mmcloc(void *arg, unsigned int mmcpos)
+{
+#ifdef DEBUG
+ struct sock *f = (struct sock *)arg;
+
+ if (log_level >= 3) {
+ sock_log(f);
+ log_puts(": ignored mmc relocate signal\n");
+ }
+#endif
+}
+
+void
+sock_exit(void *arg)
+{
+ struct sock *f = (struct sock *)arg;
+
+#ifdef DEBUG
+ if (log_level >= 3) {
+ sock_log(f);
+ log_puts(": exit\n");
+ }
+#endif
+ sock_close(f);
+}
+
+/*
+ * write on the socke fd and handle errors
+ */
+int
+sock_fdwrite(struct sock *f, void *data, int count)
+{
+ int n;
+
+ n = write(f->fd, data, count);
+ if (n < 0) {
+#ifdef DEBUG
+ if (errno == EFAULT) {
+ log_puts("sock_fdwrite: fault\n");
+ panic();
+ }
+#endif
+ if (errno != EAGAIN) {
+ if (log_level >= 1) {
+ sock_log(f);
+ log_puts(": write filed, errno = ");
+ log_puti(errno);
+ log_puts("\n");
+ }
+ sock_close(f);
+ } else {
+#ifdef DEBUG
+ if (log_level >= 4) {
+ sock_log(f);
+ log_puts(": write blocked\n");
+ }
+#endif
+ }
+ return 0;
+ }
+ if (n == 0) {
+ sock_close(f);
+ return 0;
+ }
+ return n;
+}
+
+/*
+ * read from the socke fd and handle errors
+ */
+int
+sock_fdread(struct sock *f, void *data, int count)
+{
+ int n;
+
+ n = read(f->fd, data, count);
+ if (n < 0) {
+#ifdef DEBUG
+ if (errno == EFAULT) {
+ log_puts("sock_fdread: fault\n");
+ panic();
+ }
+#endif
+ if (errno != EAGAIN) {
+ if (log_level >= 1) {
+ sock_log(f);
+ log_puts(": read failed, errno = ");
+ log_puti(errno);
+ log_puts("\n");
+ }
+ sock_close(f);
+ } else {
+#ifdef DEBUG
+ if (log_level >= 4) {
+ sock_log(f);
+ log_puts(": read blocked\n");
+ }
+#endif
+ }
+ return 0;
+ }
+ if (n == 0) {
+ sock_close(f);
+ return 0;
+ }
+ return n;
+}
+
+/*
+ * read the next message into f->rmsg, return 1 on success
+ */
+int
+sock_rmsg(struct sock *f)
+{
+ int n;
+ char *data;
+
+#ifdef DEBUG
+ if (f->rtodo == 0) {
+ sock_log(f);
+ log_puts(": sock_rmsg: nothing to read\n");
+ panic();
+ }
+#endif
+ data = (char *)&f->rmsg + sizeof(struct amsg) - f->rtodo;
+ n = sock_fdread(f, data, f->rtodo);
+ if (n == 0)
+ return 0;
+ if (n < f->rtodo) {
+ f->rtodo -= n;
+ return 0;
+ }
+ f->rtodo = 0;
+#ifdef DEBUG
+ if (log_level >= 4) {
+ sock_log(f);
+ log_puts(": read full message\n");
+ }
+#endif
+ return 1;
+}
+
+/*
+ * write the message in f->rmsg, return 1 on success
+ */
+int
+sock_wmsg(struct sock *f)
+{
+ int n;
+ char *data;
+
+#ifdef DEBUG
+ if (f->wtodo == 0) {
+ sock_log(f);
+ log_puts(": sock_wmsg: already written\n");
+ }
+#endif
+ data = (char *)&f->wmsg + sizeof(struct amsg) - f->wtodo;
+ n = sock_fdwrite(f, data, f->wtodo);
+ if (n == 0)
+ return 0;
+ if (n < f->wtodo) {
+ f->wtodo -= n;
+ return 0;
+ }
+ f->wtodo = 0;
+#ifdef DEBUG
+ if (log_level >= 4) {
+ sock_log(f);
+ log_puts(": wrote full message\n");
+ }
+#endif
+ return 1;
+}
+
+/*
+ * read data into the slot/midi ring buffer
+ */
+int
+sock_rdata(struct sock *f)
+{
+ struct abuf *buf;
+ unsigned char *data;
+ int n, count;
+
+#ifdef DEBUG
+ if (f->rtodo == 0) {
+ sock_log(f);
+ log_puts(": data block already read\n");
+ panic();
+ }
+#endif
+ if (f->slot)
+ buf = &f->slot->mix.buf;
+ else
+ buf = &f->midi->ibuf;
+ while (f->rtodo > 0) {
+ data = abuf_wgetblk(buf, &count);
+ if (count > f->rtodo)
+ count = f->rtodo;
+ n = sock_fdread(f, data, count);
+ if (n == 0)
+ return 0;
+ f->rtodo -= n;
+ abuf_wcommit(buf, n);
+ }
+#ifdef DEBUG
+ if (log_level >= 4) {
+ sock_log(f);
+ log_puts(": read complete block\n");
+ }
+#endif
+ if (f->slot)
+ slot_write(f->slot);
+ if (f->midi)
+ f->fillpending += midi_in(f->midi);
+ return 1;
+}
+
+/*
+ * read data into the slot/midi ring buffer
+ */
+int
+sock_wdata(struct sock *f)
+{
+ static unsigned char dummy[AMSG_DATAMAX];
+ unsigned char *data = NULL;
+ struct abuf *buf = NULL;
+ int n, count;
+
+#ifdef DEBUG
+ if (f->wtodo == 0) {
+ sock_log(f);
+ log_puts(": attempted to write zero-sized data block\n");
+ panic();
+ }
+#endif
+ if (f->pstate == SOCK_STOP) {
+ while (f->wtodo > 0) {
+ n = sock_fdwrite(f, dummy, f->wtodo);
+ if (n == 0)
+ return 0;
+ f->wtodo -= n;
+ }
+#ifdef DEBUG
+ if (log_level >= 4) {
+ sock_log(f);
+ log_puts(": zero-filled remaining block\n");
+ }
+#endif
+ return 1;
+ }
+ if (f->slot)
+ buf = &f->slot->sub.buf;
+ else
+ buf = &f->midi->obuf;
+ while (f->wtodo > 0) {
+ data = abuf_rgetblk(buf, &count);
+ if (count > f->wtodo)
+ count = f->wtodo;
+ n = sock_fdwrite(f, data, count);
+ if (n == 0)
+ return 0;
+ f->wtodo -= n;
+ abuf_rdiscard(buf, n);
+ }
+ if (f->slot)
+ slot_read(f->slot);
+ if (f->midi)
+ midi_fill(f->midi);
+#ifdef DEBUG
+ if (log_level >= 4) {
+ sock_log(f);
+ log_puts(": wrote complete block\n");
+ }
+#endif
+ return 1;
+}
+
+int
+sock_setpar(struct sock *f)
+{
+ struct slot *s = f->slot;
+ struct dev *d = s->dev;
+ struct amsg_par *p = &f->rmsg.u.par;
+ unsigned int min, max, rate, pchan, rchan, appbufsz;
+
+ rchan = ntohs(p->rchan);
+ pchan = ntohs(p->pchan);
+ appbufsz = ntohl(p->appbufsz);
+ rate = ntohl(p->rate);
+
+ if (AMSG_ISSET(p->bits)) {
+ if (p->bits < BITS_MIN || p->bits > BITS_MAX) {
+#ifdef DEBUG
+ if (log_level >= 1) {
+ sock_log(f);
+ log_puts(": ");
+ log_putu(p->bits);
+ log_puts(": bits out of bounds\n");
+ }
+#endif
+ return 0;
+ }
+ if (AMSG_ISSET(p->bps)) {
+ if (p->bps < ((p->bits + 7) / 8) || p->bps > 4) {
+#ifdef DEBUG
+ if (log_level >= 1) {
+ sock_log(f);
+ log_puts(": ");
+ log_putu(p->bps);
+ log_puts(": wrong bytes per sample\n");
+ }
+#endif
+ return 0;
+ }
+ } else
+ p->bps = APARAMS_BPS(p->bits);
+ s->par.bits = p->bits;
+ s->par.bps = p->bps;
+ }
+ if (AMSG_ISSET(p->sig))
+ s->par.sig = p->sig ? 1 : 0;
+ if (AMSG_ISSET(p->le))
+ s->par.le = p->le ? 1 : 0;
+ if (AMSG_ISSET(p->msb))
+ s->par.msb = p->msb ? 1 : 0;
+ if (AMSG_ISSET(rchan) && (s->mode & MODE_RECMASK)) {
+ if (rchan < 1)
+ rchan = 1;
+ if (rchan > NCHAN_MAX)
+ rchan = NCHAN_MAX;
+ s->sub.slot_cmin = f->opt->rmin;
+ s->sub.slot_cmax = f->opt->rmin + rchan - 1;
+ s->sub.dev_cmin = f->opt->rmin;
+ s->sub.dev_cmax = f->opt->rmax;
+#ifdef DEBUG
+ if (log_level >= 3) {
+ sock_log(f);
+ log_puts(": recording channels ");
+ log_putu(s->sub.slot_cmin);
+ log_puts(":");
+ log_putu(s->sub.slot_cmax);
+ log_puts(" -> ");
+ log_putu(s->sub.dev_cmin);
+ log_puts(":");
+ log_putu(s->sub.dev_cmax);
+ log_puts("\n");
+ }
+#endif
+ }
+ if (AMSG_ISSET(pchan) && (s->mode & MODE_PLAY)) {
+ if (pchan < 1)
+ pchan = 1;
+ if (pchan > NCHAN_MAX)
+ pchan = NCHAN_MAX;
+ s->mix.slot_cmin = f->opt->pmin;
+ s->mix.slot_cmax = f->opt->pmin + pchan - 1;
+ s->mix.dev_cmin = f->opt->pmin;
+ s->mix.dev_cmax = f->opt->pmax;
+#ifdef DEBUG
+ if (log_level >= 3) {
+ sock_log(f);
+ log_puts(": playback channels ");
+ log_putu(s->mix.slot_cmin);
+ log_puts(":");
+ log_putu(s->mix.slot_cmax);
+ log_puts(" -> ");
+ log_putu(s->mix.dev_cmin);
+ log_puts(":");
+ log_putu(s->mix.dev_cmax);
+ log_puts("\n");
+ }
+#endif
+ }
+ if (AMSG_ISSET(rate)) {
+ if (rate < RATE_MIN)
+ rate = RATE_MIN;
+ if (rate > RATE_MAX)
+ rate = RATE_MAX;
+ s->round = dev_roundof(d, rate);
+ s->rate = rate;
+ if (!AMSG_ISSET(appbufsz)) {
+ appbufsz = d->bufsz / d->round * s->round;
+#ifdef DEBUG
+ if (log_level >= 3) {
+ sock_log(f);
+ log_puts(": ");
+ log_putu(appbufsz);
+ log_puts(" frame buffer\n");
+ }
+#endif
+ }
+#ifdef DEBUG
+ if (log_level >= 3) {
+ sock_log(f);
+ log_puts(": ");
+ log_putu(rate);
+ log_puts("Hz sample rate, ");
+ log_putu(s->round);
+ log_puts(" frame blocks\n");
+ }
+#endif
+ }
+ if (AMSG_ISSET(p->xrun)) {
+ if (p->xrun != XRUN_IGNORE &&
+ p->xrun != XRUN_SYNC &&
+ p->xrun != XRUN_ERROR) {
+#ifdef DEBUG
+ if (log_level >= 1) {
+ sock_log(f);
+ log_puts(": ");
+ log_putx(p->xrun);
+ log_puts(": bad xrun policy\n");
+ }
+#endif
+ return 0;
+ }
+ s->xrun = p->xrun;
+ if (f->opt->mmc && s->xrun == XRUN_IGNORE)
+ s->xrun = XRUN_SYNC;
+#ifdef DEBUG
+ if (log_level >= 3) {
+ sock_log(f);
+ log_puts(": 0x");
+ log_putx(s->xrun);
+ log_puts(" xrun policy\n");
+ }
+#endif
+ }
+ if (AMSG_ISSET(appbufsz)) {
+ rate = s->rate;
+ min = 1;
+ max = 1 + rate / d->round;
+ min *= s->round;
+ max *= s->round;
+ appbufsz += s->round - 1;
+ appbufsz -= appbufsz % s->round;
+ if (appbufsz < min)
+ appbufsz = min;
+ if (appbufsz > max)
+ appbufsz = max;
+ s->appbufsz = appbufsz;
+#ifdef DEBUG
+ if (log_level >= 3) {
+ sock_log(f);
+ log_puts(": ");
+ log_putu(s->appbufsz);
+ log_puts(" frame buffer\n");
+ }
+#endif
+ }
+ return 1;
+}
+
+int
+sock_auth(struct sock *f)
+{
+ struct amsg_auth *p = &f->rmsg.u.auth;
+
+ if (sock_sesrefs == 0) {
+ /* start a new session */
+ memcpy(sock_sescookie, p->cookie, AMSG_COOKIELEN);
+ } else if (memcmp(sock_sescookie, p->cookie, AMSG_COOKIELEN) != 0) {
+ /* another session is active, drop connection */
+ return 0;
+ }
+ sock_sesrefs++;
+ f->pstate = SOCK_HELLO;
+ return 1;
+}
+
+int
+sock_hello(struct sock *f)
+{
+ struct amsg_hello *p = &f->rmsg.u.hello;
+ struct slot *s;
+ struct port *c;
+ struct dev *d;
+ unsigned int mode;
+
+ mode = ntohs(p->mode);
+#ifdef DEBUG
+ if (log_level >= 3) {
+ sock_log(f);
+ log_puts(": hello from <");
+ log_puts(p->who);
+ log_puts(">, mode = ");
+ log_putx(mode);
+ log_puts(", ver ");
+ log_putu(p->version);
+ log_puts("\n");
+ }
+#endif
+ if (p->version != AMSG_VERSION) {
+ if (log_level >= 1) {
+ sock_log(f);
+ log_puts(": ");
+ log_putu(p->version);
+ log_puts(": unsupported protocol version\n");
+ }
+ return 0;
+ }
+ switch (mode) {
+ case MODE_MIDIIN:
+ case MODE_MIDIOUT:
+ case MODE_MIDIOUT | MODE_MIDIIN:
+ case MODE_REC:
+ case MODE_PLAY:
+ case MODE_PLAY | MODE_REC:
+ break;
+ default:
+#ifdef DEBUG
+ if (log_level >= 1) {
+ sock_log(f);
+ log_puts(": ");
+ log_putx(mode);
+ log_puts(": unsupported mode\n");
+ }
+#endif
+ return 0;
+ }
+ f->pstate = SOCK_INIT;
+ if (mode & MODE_MIDIMASK) {
+ f->slot = NULL;
+ f->midi = midi_new(&sock_midiops, f, mode);
+ if (f->midi == NULL)
+ return 0;
+ /* XXX: add 'devtype' to libsndio */
+ if (p->devnum < 16) {
+ d = dev_bynum(p->devnum);
+ if (d == NULL)
+ return 0;
+ midi_tag(f->midi, p->devnum);
+ } else if (p->devnum < 32) {
+ midi_tag(f->midi, p->devnum);
+ } else if (p->devnum < 48) {
+ c = port_bynum(p->devnum - 32);
+ if (c == NULL)
+ return 0;
+ if (mode & MODE_MIDIOUT)
+ f->midi->txmask |= c->midi->rxmask;
+ if (mode & MODE_MIDIIN)
+ c->midi->txmask |= f->midi->rxmask;
+ } else
+ return 0;
+ if (mode & MODE_MIDIOUT)
+ f->fillpending = MIDI_BUFSZ;
+ return 1;
+ }
+ f->opt = opt_byname(p->opt, p->devnum);
+ if (f->opt == NULL)
+ return 0;
+#ifdef DEBUG
+ if (log_level >= 3) {
+ sock_log(f);
+ log_puts(": using ");
+ dev_log(f->opt->dev);
+ log_puts(".");
+ log_puts(f->opt->name);
+ log_puts(", mode = ");
+ log_putx(mode);
+ log_puts("\n");
+ }
+#endif
+ if ((mode & MODE_REC) && (f->opt->mode & MODE_MON)) {
+ mode |= MODE_MON;
+ mode &= ~MODE_REC;
+ }
+ if ((mode & f->opt->mode) != mode) {
+ if (log_level >= 1) {
+ sock_log(f);
+ log_puts(": requested mode not allowed\n");
+ }
+ return 0;
+ }
+ s = slot_new(f->opt->dev, p->who, &sock_slotops, f, mode);
+ if (s == NULL)
+ return 0;
+ f->midi = NULL;
+ aparams_init(&s->par);
+ if (s->mode & MODE_PLAY) {
+ s->mix.slot_cmin = f->opt->pmin;
+ s->mix.slot_cmax = f->opt->pmax;
+ }
+ if (s->mode & MODE_RECMASK) {
+ s->sub.slot_cmin = f->opt->rmin;
+ s->sub.slot_cmax = f->opt->rmax;
+ }
+ if (f->opt->mmc) {
+ s->xrun = XRUN_SYNC;
+ s->tstate = MMC_STOP;
+ } else {
+ s->xrun = XRUN_IGNORE;
+ s->tstate = MMC_OFF;
+ }
+ s->mix.maxweight = f->opt->maxweight;
+ s->dup = f->opt->dup;
+ /* XXX: must convert to slot rate */
+ f->slot = s;
+ return 1;
+}
+
+/*
+ * execute the message in f->rmsg, return 1 on success
+ */
+int
+sock_execmsg(struct sock *f)
+{
+ struct slot *s = f->slot;
+ struct amsg *m = &f->rmsg;
+ unsigned char *data;
+ int size, ctl;
+
+ switch (ntohl(m->cmd)) {
+ case AMSG_DATA:
+#ifdef DEBUG
+ if (log_level >= 4) {
+ sock_log(f);
+ log_puts(": DATA message\n");
+ }
+#endif
+ if (s != NULL && f->pstate != SOCK_START) {
+#ifdef DEBUG
+ if (log_level >= 1) {
+ sock_log(f);
+ log_puts(": DATA, wrong state\n");
+ }
+#endif
+ sock_close(f);
+ return 0;
+ }
+ if ((f->slot && !(f->slot->mode & MODE_PLAY)) ||
+ (f->midi && !(f->midi->mode & MODE_MIDIOUT))) {
+#ifdef DEBUG
+ if (log_level >= 1) {
+ sock_log(f);
+ log_puts(": DATA, input-only mode\n");
+ }
+#endif
+ sock_close(f);
+ return 0;
+ }
+ size = ntohl(m->u.data.size);
+ if (size <= 0) {
+#ifdef DEBUG
+ if (log_level >= 1) {
+ sock_log(f);
+ log_puts(": zero size payload\n");
+ }
+#endif
+ sock_close(f);
+ return 0;
+ }
+ if (s != NULL && size % s->mix.bpf != 0) {
+#ifdef DEBUG
+ if (log_level >= 1) {
+ sock_log(f);
+ log_puts(": not aligned to frame\n");
+ }
+#endif
+ sock_close(f);
+ return 0;
+ }
+ if (s != NULL && size > f->ralign) {
+#ifdef DEBUG
+ if (log_level >= 1) {
+ sock_log(f);
+ log_puts(": size = ");
+ log_puti(size);
+ log_puts(": ralign = ");
+ log_puti(f->ralign);
+ log_puts(": not aligned to block\n");
+ }
+#endif
+ sock_close(f);
+ return 0;
+ }
+ f->rstate = SOCK_RDATA;
+ f->rsize = f->rtodo = size;
+ if (s != NULL) {
+ f->ralign -= size;
+ if (f->ralign == 0)
+ f->ralign = s->round * s->mix.bpf;
+ }
+ if (f->rtodo > f->rmax) {
+#ifdef DEBUG
+ if (log_level >= 1) {
+ sock_log(f);
+ log_puts(": unexpected data, size = ");
+ log_putu(size);
+ log_puts(", rmax = ");
+ log_putu(f->rmax);
+ log_puts("\n");
+ }
+#endif
+ sock_close(f);
+ return 0;
+ }
+ f->rmax -= f->rtodo;
+ if (f->rtodo == 0) {
+#ifdef DEBUG
+ if (log_level >= 1) {
+ sock_log(f);
+ log_puts(": zero-length data chunk\n");
+ }
+#endif
+ sock_close(f);
+ return 0;
+ }
+ break;
+ case AMSG_START:
+#ifdef DEBUG
+ if (log_level >= 3) {
+ sock_log(f);
+ log_puts(": START message\n");
+ }
+#endif
+ if (f->pstate != SOCK_INIT) {
+#ifdef DEBUG
+ if (log_level >= 1) {
+ sock_log(f);
+ log_puts(": START, wrong state\n");
+ }
+#endif
+ sock_close(f);
+ return 0;
+ }
+ f->tickpending = 0;
+ f->stoppending = 0;
+ slot_start(s);
+ if (s->mode & MODE_PLAY) {
+ f->fillpending = s->appbufsz;
+ f->ralign = s->round * s->mix.bpf;
+ f->rmax = 0;
+ }
+ if (s->mode & MODE_RECMASK) {
+ f->walign = s->round * s->sub.bpf;
+ f->wmax = 0;
+ }
+ f->pstate = SOCK_START;
+ f->rstate = SOCK_RMSG;
+ f->rtodo = sizeof(struct amsg);
+ if (log_level >= 2) {
+ slot_log(f->slot);
+ log_puts(": ");
+ log_putu(s->rate);
+ log_puts("Hz, ");
+ aparams_log(&s->par);
+ if (s->mode & MODE_PLAY) {
+ log_puts(", play ");
+ log_puti(s->mix.slot_cmin);
+ log_puts(":");
+ log_puti(s->mix.slot_cmax);
+ }
+ if (s->mode & MODE_RECMASK) {
+ log_puts(", rec ");
+ log_puti(s->sub.slot_cmin);
+ log_puts(":");
+ log_puti(s->sub.slot_cmax);
+ }
+ log_puts(", ");
+ log_putu(s->appbufsz / s->round);
+ log_puts(" blocks of ");
+ log_putu(s->round);
+ log_puts(" frames\n");
+ }
+ break;
+ case AMSG_STOP:
+#ifdef DEBUG
+ if (log_level >= 3) {
+ sock_log(f);
+ log_puts(": STOP message\n");
+ }
+#endif
+ if (f->pstate != SOCK_START) {
+#ifdef DEBUG
+ if (log_level >= 1) {
+ sock_log(f);
+ log_puts(": STOP, wrong state\n");
+ }
+#endif
+ sock_close(f);
+ return 0;
+ }
+ f->rmax = 0;
+ if (!(s->mode & MODE_PLAY))
+ f->stoppending = 1;
+ f->pstate = SOCK_STOP;
+ f->rstate = SOCK_RMSG;
+ f->rtodo = sizeof(struct amsg);
+ if (s->mode & MODE_PLAY) {
+ if (f->ralign < s->round * s->mix.bpf) {
+ data = abuf_wgetblk(&s->mix.buf, &size);
+#ifdef DEBUG
+ if (size < f->ralign) {
+ sock_log(f);
+ log_puts(": unaligned stop, size = ");
+ log_putu(size);
+ log_puts(", ralign = ");
+ log_putu(f->ralign);
+ log_puts("\n");
+ panic();
+ }
+#endif
+ memset(data, 0, f->ralign);
+ abuf_wcommit(&s->mix.buf, f->ralign);
+ f->ralign = s->round * s->mix.bpf;
+ }
+ }
+ slot_stop(s);
+ break;
+ case AMSG_SETPAR:
+#ifdef DEBUG
+ if (log_level >= 3) {
+ sock_log(f);
+ log_puts(": SETPAR message\n");
+ }
+#endif
+ if (f->pstate != SOCK_INIT) {
+#ifdef DEBUG
+ if (log_level >= 1) {
+ sock_log(f);
+ log_puts(": SETPAR, wrong state\n");
+ }
+#endif
+ sock_close(f);
+ return 0;
+ }
+ if (!sock_setpar(f)) {
+ sock_close(f);
+ return 0;
+ }
+ f->rtodo = sizeof(struct amsg);
+ f->rstate = SOCK_RMSG;
+ break;
+ case AMSG_GETPAR:
+#ifdef DEBUG
+ if (log_level >= 3) {
+ sock_log(f);
+ log_puts(": GETPAR message\n");
+ }
+#endif
+ if (f->pstate != SOCK_INIT) {
+#ifdef DEBUG
+ if (log_level >= 1) {
+ sock_log(f);
+ log_puts(": GETPAR, wrong state\n");
+ }
+#endif
+ sock_close(f);
+ return 0;
+ }
+ AMSG_INIT(m);
+ m->cmd = htonl(AMSG_GETPAR);
+ m->u.par.legacy_mode = s->mode;
+ m->u.par.bits = s->par.bits;
+ m->u.par.bps = s->par.bps;
+ m->u.par.sig = s->par.sig;
+ m->u.par.le = s->par.le;
+ m->u.par.msb = s->par.msb;
+ if (s->mode & MODE_PLAY) {
+ m->u.par.pchan = htons(s->mix.slot_cmax -
+ s->mix.slot_cmin + 1);
+ }
+ if (s->mode & MODE_RECMASK) {
+ m->u.par.rchan = htons(s->sub.slot_cmax -
+ s->sub.slot_cmin + 1);
+ }
+ m->u.par.rate = htonl(s->rate);
+ m->u.par.appbufsz = htonl(s->appbufsz);
+ m->u.par.bufsz = htonl(SLOT_BUFSZ(s));
+ m->u.par.round = htonl(s->round);
+ f->rstate = SOCK_RRET;
+ f->rtodo = sizeof(struct amsg);
+ break;
+ case AMSG_SETVOL:
+#ifdef DEBUG
+ if (log_level >= 3) {
+ sock_log(f);
+ log_puts(": SETVOL message\n");
+ }
+#endif
+ if (f->pstate < SOCK_INIT) {
+#ifdef DEBUG
+ if (log_level >= 1) {
+ sock_log(f);
+ log_puts(": SETVOL, wrong state\n");
+ }
+#endif
+ sock_close(f);
+ return 0;
+ }
+ ctl = ntohl(m->u.vol.ctl);
+ if (ctl > MIDI_MAXCTL) {
+#ifdef DEBUG
+ if (log_level >= 1) {
+ sock_log(f);
+ log_puts(": SETVOL, volume out of range\n");
+ }
+#endif
+ sock_close(f);
+ return 0;
+ }
+ f->rtodo = sizeof(struct amsg);
+ f->rstate = SOCK_RMSG;
+ f->lastvol = ctl; /* dont trigger feedback message */
+ dev_midi_vol(s->dev, s);
+ slot_setvol(s, ctl);
+ break;
+ case AMSG_AUTH:
+#ifdef DEBUG
+ if (log_level >= 3) {
+ sock_log(f);
+ log_puts(": AUTH message\n");
+ }
+#endif
+ if (f->pstate != SOCK_AUTH) {
+#ifdef DEBUG
+ if (log_level >= 1) {
+ sock_log(f);
+ log_puts(": AUTH, wrong state\n");
+ }
+#endif
+ sock_close(f);
+ return 0;
+ }
+ if (!sock_auth(f)) {
+ sock_close(f);
+ return 0;
+ }
+ f->rstate = SOCK_RMSG;
+ f->rtodo = sizeof(struct amsg);
+ break;
+ case AMSG_HELLO:
+#ifdef DEBUG
+ if (log_level >= 3) {
+ sock_log(f);
+ log_puts(": HELLO message\n");
+ }
+#endif
+ if (f->pstate != SOCK_HELLO) {
+#ifdef DEBUG
+ if (log_level >= 1) {
+ sock_log(f);
+ log_puts(": HELLO, wrong state\n");
+ }
+#endif
+ sock_close(f);
+ return 0;
+ }
+ if (!sock_hello(f)) {
+ sock_close(f);
+ return 0;
+ }
+ AMSG_INIT(m);
+ m->cmd = htonl(AMSG_ACK);
+ f->rstate = SOCK_RRET;
+ f->rtodo = sizeof(struct amsg);
+ break;
+ case AMSG_BYE:
+#ifdef DEBUG
+ if (log_level >= 3) {
+ sock_log(f);
+ log_puts(": BYE message\n");
+ }
+#endif
+ if (s != NULL && f->pstate != SOCK_INIT) {
+#ifdef DEBUG
+ if (log_level >= 1) {
+ sock_log(f);
+ log_puts(": BYE, wrong state\n");
+ }
+#endif
+ }
+ sock_close(f);
+ return 0;
+ default:
+#ifdef DEBUG
+ if (log_level >= 1) {
+ sock_log(f);
+ log_puts(": unknown command in message\n");
+ }
+#endif
+ sock_close(f);
+ return 0;
+ }
+ return 1;
+}
+
+/*
+ * build a message in f->wmsg, return 1 on success and 0 if
+ * there's nothing to do. Assume f->wstate is SOCK_WIDLE
+ */
+int
+sock_buildmsg(struct sock *f)
+{
+ unsigned int size;
+
+ /*
+ * If pos changed (or initial tick), build a MOVE message.
+ */
+ if (f->tickpending) {
+#ifdef DEBUG
+ if (log_level >= 4) {
+ sock_log(f);
+ log_puts(": building MOVE message, delta = ");
+ log_puti(f->slot->delta);
+ log_puts("\n");
+ }
+#endif
+ AMSG_INIT(&f->wmsg);
+ f->wmsg.cmd = htonl(AMSG_MOVE);
+ f->wmsg.u.ts.delta = htonl(f->slot->delta);
+ f->wtodo = sizeof(struct amsg);
+ f->wstate = SOCK_WMSG;
+ f->tickpending = 0;
+ /*
+ * XXX: use tickpending as accumulator rather than
+ * slot->delta
+ */
+ f->slot->delta = 0;
+ return 1;
+ }
+
+ if (f->fillpending > 0) {
+ AMSG_INIT(&f->wmsg);
+ f->wmsg.cmd = htonl(AMSG_FLOWCTL);
+ f->wmsg.u.ts.delta = htonl(f->fillpending);
+ size = f->fillpending;
+ if (f->slot)
+ size *= f->slot->mix.bpf;
+ f->rmax += size;
+#ifdef DEBUG
+ if (log_level >= 4) {
+ sock_log(f);
+ log_puts(": building FLOWCTL message, count = ");
+ log_puti(f->fillpending);
+ log_puts(", rmax -> ");
+ log_puti(f->rmax);
+ log_puts("\n");
+ }
+#endif
+ f->wtodo = sizeof(struct amsg);
+ f->wstate = SOCK_WMSG;
+ f->fillpending = 0;
+ return 1;
+ }
+
+ /*
+ * if volume changed build a SETVOL message
+ */
+ if (f->pstate >= SOCK_START && f->slot->vol != f->lastvol) {
+#ifdef DEBUG
+ if (log_level >= 3) {
+ sock_log(f);
+ log_puts(": building SETVOL message, vol = ");
+ log_puti(f->slot->vol);
+ log_puts("\n");
+ }
+#endif
+ AMSG_INIT(&f->wmsg);
+ f->wmsg.cmd = htonl(AMSG_SETVOL);
+ f->wmsg.u.vol.ctl = htonl(f->slot->vol);
+ f->wtodo = sizeof(struct amsg);
+ f->wstate = SOCK_WMSG;
+ f->lastvol = f->slot->vol;
+ return 1;
+ }
+
+ if (f->midi != NULL && f->midi->obuf.used > 0) {
+ /* XXX: use tickets */
+ size = f->midi->obuf.used;
+ if (size > AMSG_DATAMAX)
+ size = AMSG_DATAMAX;
+ AMSG_INIT(&f->wmsg);
+ f->wmsg.cmd = htonl(AMSG_DATA);
+ f->wmsg.u.data.size = htonl(size);
+ f->wtodo = sizeof(struct amsg);
+ f->wstate = SOCK_WMSG;
+ return 1;
+ }
+
+ /*
+ * If data available, build a DATA message.
+ */
+ if (f->slot != NULL && f->slot->sub.buf.used > 0 && f->wmax > 0) {
+ size = f->slot->sub.buf.used;
+ if (size > AMSG_DATAMAX)
+ size = AMSG_DATAMAX;
+ if (size > f->walign)
+ size = f->walign;
+ if (size > f->wmax)
+ size = f->wmax;
+ size -= size % f->slot->sub.bpf;
+#ifdef DEBUG
+ if (size == 0) {
+ sock_log(f);
+ log_puts(": sock_buildmsg size == 0\n");
+ panic();
+ }
+#endif
+ f->walign -= size;
+ f->wmax -= size;
+ if (f->walign == 0)
+ f->walign = f->slot->round * f->slot->sub.bpf;
+#ifdef DEBUG
+ if (log_level >= 4) {
+ sock_log(f);
+ log_puts(": building audio DATA message, size = ");
+ log_puti(size);
+ log_puts("\n");
+ }
+#endif
+ AMSG_INIT(&f->wmsg);
+ f->wmsg.cmd = htonl(AMSG_DATA);
+ f->wmsg.u.data.size = htonl(size);
+ f->wtodo = sizeof(struct amsg);
+ f->wstate = SOCK_WMSG;
+ return 1;
+ }
+
+ if (f->stoppending) {
+#ifdef DEBUG
+ if (log_level >= 3) {
+ sock_log(f);
+ log_puts(": building STOP message\n");
+ }
+#endif
+ f->stoppending = 0;
+ f->pstate = SOCK_INIT;
+ AMSG_INIT(&f->wmsg);
+ f->wmsg.cmd = htonl(AMSG_STOP);
+ f->wtodo = sizeof(struct amsg);
+ f->wstate = SOCK_WMSG;
+ return 1;
+ }
+#ifdef DEBUG
+ if (log_level >= 4) {
+ sock_log(f);
+ log_puts(": no messages to build anymore, idling...\n");
+ }
+#endif
+ f->wstate = SOCK_WIDLE;
+ return 0;
+}
+
+/*
+ * iteration of the socket reader loop, return 1 on success
+ */
+int
+sock_read(struct sock *f)
+{
+#ifdef DEBUG
+ if (log_level >= 4) {
+ sock_log(f);
+ log_puts(": reading ");
+ log_putu(f->rtodo);
+ log_puts(" todo\n");
+ }
+#endif
+ switch (f->rstate) {
+ case SOCK_RIDLE:
+ return 0;
+ case SOCK_RMSG:
+ if (!sock_rmsg(f))
+ return 0;
+ if (!sock_execmsg(f))
+ return 0;
+ break;
+ case SOCK_RDATA:
+ if (!sock_rdata(f))
+ return 0;
+ f->rstate = SOCK_RMSG;
+ f->rtodo = sizeof(struct amsg);
+ break;
+ case SOCK_RRET:
+ if (f->wstate != SOCK_WIDLE) {
+#ifdef DEBUG
+ if (log_level >= 4) {
+ sock_log(f);
+ log_puts(": can't reply, write-end blocked\n");
+ }
+#endif
+ return 0;
+ }
+ f->wmsg = f->rmsg;
+ f->wstate = SOCK_WMSG;
+ f->wtodo = sizeof(struct amsg);
+ f->rstate = SOCK_RMSG;
+ f->rtodo = sizeof(struct amsg);
+ /* XXX: call sock_wmsg() ? */
+#ifdef DEBUG
+ if (log_level >= 4) {
+ sock_log(f);
+ log_puts(": copied RRET message\n");
+ }
+#endif
+ }
+ return 1;
+}
+
+/*
+ * iteration of the socket writer loop, return 1 on success
+ */
+int
+sock_write(struct sock *f)
+{
+#ifdef DEBUG
+ if (log_level >= 4) {
+ sock_log(f);
+ log_puts(": writing");
+ if (f->wstate != SOCK_WIDLE) {
+ log_puts(" todo = ");
+ log_putu(f->wtodo);
+ }
+ log_puts("\n");
+ }
+#endif
+ switch (f->wstate) {
+ case SOCK_WMSG:
+ if (!sock_wmsg(f))
+ return 0;
+ if (ntohl(f->wmsg.cmd) != AMSG_DATA) {
+ f->wstate = SOCK_WIDLE;
+ f->wtodo = 0xdeadbeef;
+ break;
+ }
+ /*
+ * XXX: why not set f->wtodo in sock_wmsg() ?
+ */
+ f->wstate = SOCK_WDATA;
+ f->wsize = f->wtodo = ntohl(f->wmsg.u.data.size);
+ /* PASSTHROUGH */
+ case SOCK_WDATA:
+ if (!sock_wdata(f))
+ return 0;
+ if (f->wtodo > 0)
+ break;
+ f->wstate = SOCK_WIDLE;
+ f->wtodo = 0xdeadbeef;
+ if (f->pstate == SOCK_STOP) {
+ f->pstate = SOCK_INIT;
+ f->wmax = 0;
+#ifdef DEBUG
+ if (log_level >= 4) {
+ sock_log(f);
+ log_puts(": drained, moved to INIT state\n");
+ }
+#endif
+ }
+ /* PASSTHROUGH */
+ case SOCK_WIDLE:
+ if (f->rstate == SOCK_RRET) {
+ f->wmsg = f->rmsg;
+ f->wstate = SOCK_WMSG;
+ f->wtodo = sizeof(struct amsg);
+ f->rstate = SOCK_RMSG;
+ f->rtodo = sizeof(struct amsg);
+#ifdef DEBUG
+ if (log_level >= 4) {
+ sock_log(f);
+ log_puts(": copied RRET message\n");
+ }
+#endif
+ } else {
+ if (!sock_buildmsg(f))
+ return 0;
+ }
+ break;
+#ifdef DEBUG
+ default:
+ sock_log(f);
+ log_puts(": bad writing end state\n");
+ panic();
+#endif
+ }
+ return 1;
+}
+
+int
+sock_pollfd(void *arg, struct pollfd *pfd)
+{
+ struct sock *f = arg;
+ int events = 0;
+
+ /*
+ * feedback counters, clock ticks and alike may have changed,
+ * prepare a message to trigger writes
+ *
+ * XXX: doing this at the beginning of the cycle is not optimal,
+ * because state is changed at the end of the read cycle, and
+ * thus counters, ret message and alike are generated then.
+ */
+ if (f->wstate == SOCK_WIDLE && f->rstate != SOCK_RRET)
+ sock_buildmsg(f);
+
+ if (f->rstate == SOCK_RMSG ||
+ f->rstate == SOCK_RDATA)
+ events |= POLLIN;
+ if (f->rstate == SOCK_RRET ||
+ f->wstate == SOCK_WMSG ||
+ f->wstate == SOCK_WDATA)
+ events |= POLLOUT;
+ pfd->fd = f->fd;
+ pfd->events = events;
+ return 1;
+}
+
+int
+sock_revents(void *arg, struct pollfd *pfd)
+{
+ return pfd->revents;
+}
+
+void
+sock_in(void *arg)
+{
+ struct sock *f = arg;
+
+ while (sock_read(f))
+ ;
+}
+
+void
+sock_out(void *arg)
+{
+ struct sock *f = arg;
+
+ while (sock_write(f))
+ ;
+}
+
+void
+sock_hup(void *arg)
+{
+ struct sock *f = arg;
+
+ sock_close(f);
+}
diff --git a/usr.bin/sndiod/sock.h b/usr.bin/sndiod/sock.h
new file mode 100644
index 00000000000..94d23f21598
--- /dev/null
+++ b/usr.bin/sndiod/sock.h
@@ -0,0 +1,69 @@
+/* $OpenBSD: sock.h,v 1.1 2012/11/23 07:03:28 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.
+ */
+#ifndef SOCK_H
+#define SOCK_H
+
+#include "amsg.h"
+
+struct opt;
+struct file;
+struct slot;
+struct midi;
+
+struct sock {
+ struct sock *next;
+ int fd;
+ struct file *file;
+ struct amsg rmsg, wmsg; /* messages being sent/received */
+ unsigned int wmax; /* max bytes we're allowed to write */
+ unsigned int rmax; /* max bytes we're allowed to read */
+ unsigned int rsize; /* input bytes to read (DATA msg) */
+ unsigned int wsize; /* output bytes to write (DATA msg) */
+ unsigned int rtodo; /* input bytes not read yet */
+ unsigned int wtodo; /* output bytes not written yet */
+#define SOCK_RIDLE 0 /* not expecting messages */
+#define SOCK_RMSG 1 /* expecting a message */
+#define SOCK_RDATA 2 /* data chunk being read */
+#define SOCK_RRET 3 /* reply being returned */
+ unsigned int rstate; /* state of the read-end FSM */
+#define SOCK_WIDLE 0 /* nothing to do */
+#define SOCK_WMSG 1 /* amsg being written */
+#define SOCK_WDATA 2 /* data chunk being written */
+ unsigned int wstate; /* state of the write-end FSM */
+#define SOCK_AUTH 0 /* waiting for AUTH message */
+#define SOCK_HELLO 1 /* waiting for HELLO message */
+#define SOCK_INIT 2 /* parameter negotiation */
+#define SOCK_START 3 /* filling play buffers */
+#define SOCK_STOP 4 /* draining rec buffers */
+ unsigned int pstate; /* one of the above */
+ int tickpending; /* tick waiting to be transmitted */
+ int fillpending; /* flowctl waiting to be transmitted */
+ int stoppending; /* last STOP ack to be sent */
+ unsigned int walign; /* align written data to this */
+ unsigned int ralign; /* read data is aligned to this */
+ int lastvol; /* last volume */
+ struct opt *opt; /* "subdevice" definition */
+ struct slot *slot; /* audio device slot number */
+ struct midi *midi; /* midi endpoint number */
+ char who[12]; /* label, mostly for debugging */
+};
+
+struct sock *sock_new(int fd);
+void sock_close(struct sock *);
+extern struct sock *sock_list;
+
+#endif /* !defined(SOCK_H) */
diff --git a/usr.bin/sndiod/sysex.h b/usr.bin/sndiod/sysex.h
new file mode 100644
index 00000000000..543faf4e870
--- /dev/null
+++ b/usr.bin/sndiod/sysex.h
@@ -0,0 +1,120 @@
+/* $OpenBSD: sysex.h,v 1.1 2012/11/23 07:03:28 ratchov Exp $ */
+/*
+ * Copyright (c) 2011 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.
+ */
+#ifndef AUCAT_SYSEX_H
+#define AUCAT_SYSEX_H
+
+#include <stdint.h>
+
+/*
+ * start and end markers
+ */
+#define SYSEX_START 0xf0
+#define SYSEX_END 0xf7
+
+/*
+ * type/vendor namespace IDs we use
+ */
+#define SYSEX_TYPE_RT 0x7f /* real-time universal */
+#define SYSEX_TYPE_EDU 0x7d /* non-comercial */
+
+/*
+ * realtime messages in the "universal real-time" namespace
+ */
+#define SYSEX_MTC 0x01 /* mtc messages */
+#define SYSEX_MTC_FULL 0x01 /* mtc full frame message */
+#define SYSEX_CONTROL 0x04
+#define SYSEX_MASTER 0x01
+#define SYSEX_MMC 0x06
+#define SYSEX_MMC_STOP 0x01
+#define SYSEX_MMC_START 0x02
+#define SYSEX_MMC_LOC 0x44
+#define SYSEX_MMC_LOC_LEN 0x06
+#define SYSEX_MMC_LOC_CMD 0x01
+
+/*
+ * aucat-specific messages, in the "edu" namespace
+ */
+#define SYSEX_AUCAT 0x23 /* aucat-specific */
+#define SYSEX_AUCAT_SLOTDESC 0x01 /* mixer info */
+#define SYSEX_AUCAT_DUMPREQ 0x02 /* dump request */
+#define SYSEX_AUCAT_DUMPEND 0x03 /* end of dump */
+
+/*
+ * minimum size of sysex message we accept
+ */
+#define SYSEX_SIZE(m) (5 + sizeof(struct sysex_ ## m))
+
+/*
+ * all possible system exclusive messages we support. For aucat-specific
+ * messages we use the same header as real-time messages to simplify the
+ * message parser
+ */
+struct sysex {
+ uint8_t start;
+ uint8_t type; /* type or vendor id */
+ uint8_t dev; /* device or product id */
+ uint8_t id0; /* message id */
+ uint8_t id1; /* sub-id */
+ union sysex_all {
+ struct sysex_empty {
+ uint8_t end;
+ } empty;
+ struct sysex_master {
+ uint8_t fine;
+ uint8_t coarse;
+ uint8_t end;
+ } master;
+ struct sysex_start {
+ uint8_t end;
+ } start;
+ struct sysex_stop {
+ uint8_t end;
+ } stop;
+ struct sysex_loc {
+ uint8_t len;
+ uint8_t cmd;
+ uint8_t hr;
+ uint8_t min;
+ uint8_t sec;
+ uint8_t fr;
+ uint8_t cent;
+ uint8_t end;
+ } loc;
+ struct sysex_full {
+ uint8_t hr;
+ uint8_t min;
+ uint8_t sec;
+ uint8_t fr;
+ uint8_t end;
+ } full;
+ struct sysex_slotdesc {
+ uint8_t chan; /* channel */
+ uint8_t vol; /* current volume */
+#define SYSEX_NAMELEN 10 /* \0 included */
+ uint8_t name[SYSEX_NAMELEN]; /* stream name */
+ uint8_t end;
+ } slotdesc;
+ struct sysex_dumpreq {
+ uint8_t end;
+ } dumpreq;
+ struct sysex_dumpend {
+ uint8_t end;
+ } dumpend;
+ } u;
+};
+
+#endif /* !defined(AUCAT_SYSEX_H) */
diff --git a/usr.bin/sndiod/utils.c b/usr.bin/sndiod/utils.c
new file mode 100644
index 00000000000..974e20d01c3
--- /dev/null
+++ b/usr.bin/sndiod/utils.c
@@ -0,0 +1,182 @@
+/* $OpenBSD: utils.c,v 1.1 2012/11/23 07:03:28 ratchov Exp $ */
+/*
+ * Copyright (c) 2003-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.
+ */
+/*
+ * log_xxx() routines are used to quickly store traces into a trace buffer.
+ * This allows trances to be collected during time sensitive operations without
+ * disturbing them. The buffer can be flushed on standard error later, when
+ * slow syscalls are no longer disruptive, e.g. at the end of the poll() loop.
+ */
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include "utils.h"
+
+/*
+ * log buffer size
+ */
+#define LOG_BUFSZ 8192
+
+/*
+ * store a character in the log
+ */
+#define LOG_PUTC(c) do { \
+ if (log_used < LOG_BUFSZ) \
+ log_buf[log_used++] = (c); \
+} while (0)
+
+char log_buf[LOG_BUFSZ]; /* buffer where traces are stored */
+unsigned int log_used = 0; /* bytes used in the buffer */
+unsigned int log_sync = 1; /* if true, flush after each '\n' */
+
+/*
+ * write the log buffer on stderr
+ */
+void
+log_flush(void)
+{
+ if (log_used == 0)
+ return;
+ write(STDERR_FILENO, log_buf, log_used);
+ log_used = 0;
+}
+
+/*
+ * store a string in the log
+ */
+void
+log_puts(char *msg)
+{
+ char *p = msg;
+ int c;
+
+ while ((c = *p++) != '\0') {
+ LOG_PUTC(c);
+ if (log_sync && c == '\n')
+ log_flush();
+ }
+}
+
+/*
+ * store a hex in the log
+ */
+void
+log_putx(unsigned long num)
+{
+ char dig[sizeof(num) * 2], *p = dig, c;
+ unsigned int ndig;
+
+ if (num != 0) {
+ for (ndig = 0; num != 0; ndig++) {
+ *p++ = num & 0xf;
+ num >>= 4;
+ }
+ for (; ndig != 0; ndig--) {
+ c = *(--p);
+ c += (c < 10) ? '0' : 'a' - 10;
+ LOG_PUTC(c);
+ }
+ } else
+ LOG_PUTC('0');
+}
+
+/*
+ * store a unsigned decimal in the log
+ */
+void
+log_putu(unsigned long num)
+{
+ char dig[sizeof(num) * 3], *p = dig;
+ unsigned int ndig;
+
+ if (num != 0) {
+ for (ndig = 0; num != 0; ndig++) {
+ *p++ = num % 10;
+ num /= 10;
+ }
+ for (; ndig != 0; ndig--)
+ LOG_PUTC(*(--p) + '0');
+ } else
+ LOG_PUTC('0');
+}
+
+/*
+ * store a signed decimal in the log
+ */
+void
+log_puti(long num)
+{
+ if (num < 0) {
+ LOG_PUTC('-');
+ num = -num;
+ }
+ log_putu(num);
+}
+
+/*
+ * abort program execution after a fatal error
+ */
+void
+panic(void)
+{
+ log_flush();
+ (void)kill(getpid(), SIGABRT);
+ _exit(1);
+}
+
+/*
+ * allocate a (small) abount of memory, and abort if it fails
+ */
+void *
+xmalloc(size_t size)
+{
+ void *p;
+
+ p = malloc(size);
+ if (p == NULL) {
+ log_puts("failed to allocate ");
+ log_putx(size);
+ log_puts(" bytes\n");
+ panic();
+ }
+ return p;
+}
+
+/*
+ * free memory allocated with xmalloc()
+ */
+void
+xfree(void *p)
+{
+ free(p);
+}
+
+/*
+ * xmalloc-style strdup(3)
+ */
+char *
+xstrdup(char *s)
+{
+ size_t size;
+ void *p;
+
+ size = strlen(s) + 1;
+ p = xmalloc(size);
+ memcpy(p, s, size);
+ return p;
+}
diff --git a/usr.bin/sndiod/utils.h b/usr.bin/sndiod/utils.h
new file mode 100644
index 00000000000..060245cb672
--- /dev/null
+++ b/usr.bin/sndiod/utils.h
@@ -0,0 +1,36 @@
+/* $OpenBSD: utils.h,v 1.1 2012/11/23 07:03:28 ratchov Exp $ */
+/*
+ * Copyright (c) 2003-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.
+ */
+
+#ifndef UTILS_H
+#define UTILS_H
+
+#include <sys/types.h>
+
+void log_puts(char *);
+void log_putx(unsigned long);
+void log_putu(unsigned long);
+void log_puti(long);
+void panic(void);
+void log_flush(void);
+
+void *xmalloc(size_t);
+char *xstrdup(char *);
+void xfree(void *);
+
+extern unsigned int log_sync;
+
+#endif