diff options
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 |